Coding Conventions
Overview
This document aims to outline some of the various coding conventions and patterns used in the sndkit algorithms. This can be used as a guide to help read the programs written here.
Literate Programming
Each algorithm is written using a paradigm known as
literate programming
. In a very small nutshell,
it is a technique that allows programmer
to melt natural language structure with computer logic
structure. In sndkit, algorithms are written in
org markup using org-mode. Between the blocks of text
are noweb style named code blocks, following the conventions
of org-babel. From there, the org documents can either be
rendered to HTML (a process known as weaving
), or
be used to generate C code readable by a C compiler (a
process known as tangling
).
While emacs and org-mode are very conveniently used to write the algorithms, emacs is not a dependency for using sndkit.
For tangling to C code, sndkit uses a custom org tangler
called worgle
, written in ANSI C. It is included with
the sndkit distribution. For weaving into HTML, a tool
called weewiki. weewiki is not included with sndkit. You'll
need to install that program separately in order to render
the HTML pages locally.
Algorithm Overview
At the start of every algorithm is some kind of overview. This provides a few short sentences on what the algorithm does, and maybe something about the inputs and outputs.
Sometimes, some upfront elaboration is needed. This happens in sections afterwards.
Tangled Files
Typically, a sndkit algorithm will tangle out to a single C
and header file, foo.c and foo.h. The declarations
themselves are in a section usually called tangled files
.
Algorithms in sndkit designed to be as self-contained as possible. It should be possible to use the files by themselves without anything else from sndkit. This makes it easy to extract things from sndkit in other projects and use only what is needed.
The downside of this is that yes, there is sometimes redudant code and functionality. This is an acceptable tradeoff for now, as decomposability of sndkit is an important pillar in the design philosophy of this project.
Named Codeblocks
Literate programs, such as the ones made in sndkit, are composed of named code blocks. Blocks are chunks of text that can have blocks nested inside of them. Blocks can also be appended to, which can be a useful way to dynamically add code.
At this point, sndkit program structures follow a pretty predictable structure, and often use the same conventions for named code blocks.
C and Header Tangle Blocks
A sndkit algorithm tangles to two files: a C and a header file. These are considered to be two top-level named blocks. For an algorithm foo, these are named foo.c and foo.h. Inside these blocks contain all the other blocks.
What makes these blocks special are that they explicitely tell the tangler to write to files.
funcs
All C functions are appended to a named code block called
funcs
. This is included inside of the top-level C file
block.
funcdefs
All public function declarations/definitions are appended to
a codeblock called funcdefs
, and are included in the
top-level header file block.
In a literate program, a
funcdefs
block is usually closely followed by a
corresponding func
block.
static_funcdefs
A static function definitions/declarations are done in
a block called static_funcdefs
. This is included towards
the beginning of the C file code block.
typedefs
Public facing structs in C use type definitions, or
typedefs. All typedefs are appended to the typedefs
block,
contained in the header file code block.
structs
Structs used by a particular algorithm are defined in the
structs
codeblock, contained in the header file codeblock.
By default, these are made opaque, but can be exposed by
defining the SK_FOO_PRIV
macro.
macros
macros can be defined in a codeblock called macros
.
Usually these are defined with local scope in the C file
rather than the header file. If there is a different
between local macros in the C file and public ones in
the header, use local_macros
and macros
.
init
A very common design pattern is to have some function
that initializes data in a struct called sk_foo_init
.
Using codeblocks, one can add and initialize variables
in a piecemeal. Variable declarations can be declared in
one block. Initialization can happen in a block called
init
. For a an algorithm with struct sk_foo
, contents
inside of that struct usually would use a code block
called foo
.
SKFLT
Floating point types use a macro called SKFLT
, and by
default this is set to be float
. Every DSP algorithm
has a way to explicitely define this if it hasn't been
previously defined.
Function Naming Conventions
Sndkit algorithms share a common set of things they do like initialization, and computing a sample of audio, or setting a variable. Below are a set of the most common functions, and the names used to describe them.
tick
When a DSP algorithm computes a single sample of audio,
it is called a tick
, and is usually called sk_foo_tick
.
If a tick function takes any audio-rate signals, these
are provided as arguments to the function.
Most DSP algorithms in sndkit are mono, so the functions
will simply return one SKFLT
. For multiple outputs,
the values are stored in pointers at the end of the
function. For stereo processors such as bigverb,
this is the one time where single-letter
camel case variable names are used, such as
inL
, inR
, outL
, or outR
.
init
For intializing data in a struct, the word init
is used,
as in sk_foo_init
. The first argument to this function
is the struct itself, which expects to be sk_foo
.
Following this is the sampling rate sr
, if needed. Any
other init-time variables are supplied after.
init
should only be used to initialize and zero out
data. For dynamic memory allocation for things like
delay lines, del
and new
conventions are used.
del and new
For algorithms that require dynamic memory allocation, such
as bigverb, the del
and new
words are used,
as in sk_foo_new
and sk_foo_del
. A new
function
will allocate and initialize a new instance of sk_foo
.
The del
function will clean up all memory, as well
the instance itself.
setting and getting parameters
It is the convention to use setters and getters rather than manipulate variables directly.
When in doubt, for parameter param
, use sk_foo_param_set
and sk_foo_param_get
. To get and set the parameters.
However, it is typical for setters drop the set
, as in
sk_foo_param
for terseness, as it is understood that it is
a setter.