This is an account of a journey into ``Binary Sound Synthesis'' (BSS),
which started early 2005 as a test application\footnote{The original
synthesizer ran early 2005 on a PIC12F628, an 8 bit microcontroller
with 6 i/o pins which I had configured to run at 1 MIPS. The current
one runs since mid 2006 on a PIC18F1220, an 8 bit micro with 18 pins
and a slightly more powerful ISA. The original synth is a
synchronous one running at $8$kHz. At each sampling point the output
state of $3$ oscillators is determined. The control updates run at
$200$Hz, while the note updates run at $8$Hz, which is $1/4$ note at
$120$bpm. The current one uses 3 asynchronous hardware timers, thus
a higher maximal rate of $2$MHz. In a nutshell, the synth contains
2 main algorithms: cross modulation (XOR mixer), a formant synth
(AND/OR mixer), LFSR pseudo-random sequence generator. It uses 3
oscillators that can be synchronized. Most of the interesting parts
are in the controller that reconfigures the oscillators.} for a
Forth compiler for the 8--bit Microchip PIC architectures. With BSS
is meant the process of generating audible sound from digital
square--wave signals using an absolute minimum of logic or code.
This is old stuff. The digital approach dates from the era of early
8--bit game machines, and as such a lot of the algorithms are no
longer used. Currently there are few reasons to not use floating or
fixed point math with PCM outputs.
The point of this paper is partly to archive and document old
techniques, and to shine an idiosyncratic light on the matter. I find
this a facinating subject. Probably because it is so different from
the standard real and complex number based signal processing.
In BSS the main focus is on the time component, since it is the timing
between switching a speaker on and off which becomes the only means of
controlling the sound.
\section{Cycles}
The simplest sounds we can produce are based on periodic square
waveforms. An oscillator can be can be implemented using a frequency
divider or pulse counter, which generates one output event for each
$k$ input events. Multiple such oscillators can then be connected to
a hardware timer interrupt.
Mathematically, this can be related to addition in the ring of
integers modulo $k$. We'll denote this algebraic structure as
$\ZZ_k$, the cyclic group of order $k$. This section talks about such
groups, as they will appear as multiplicative groups of Galois Fields.
\subsection{Counters and Cyclic Groups}
Cyclic groups are groups, necessarily abelian, that can be generated
by a single element. In our canonical representation, this element
will be $1$ in $\ZZ_k$. If $k+1$ is a power of a prime, it is the
multiplicative group of the Galois Field $\GF(k+1)$, which we will
encounter later. All abelian groups are composed of direct products of
prime order cyclic groups, but not all abelian groups are cyclic. The
prototypical example being the Klein $4$--group $\ZZ_2 \times \ZZ_2$.
The exact relation between a group and a counter is as follows. We use
additive notiation because all the groups are abelian. To each
counter we associate a group $\G$, a generator $g\in\G$, and a state
$s\in\G$. On each input event, the state $s$ will be replaced with
$s+g$. Whenever the state reaches $s=0$, the unit element of the
group, an output event is generated. The division factor is the order
of $g$, which is the smallest integer $o$ for which
$\underbrace{g+g+\ldots+g}_{o \text{ times}} = 0$.
For example in $\ZZ_{6}$ where $\{0,6,12,\ldots\}$ denote the same
element, the generator $1$ has order $6$, the generator $2$ has order
$3$ and the generator $3$ has order $2$.
\subsection{Composite Numbers}
Groups with prime order are not so interesting to us, they generate
only one kind of cycle. What interests us most is the combination of
timers. Let's have a look at the example of highly degenerate groups,
which have a number of elements equal to a composite number like the
number of seconds in a week $604800 = 2^7 3^3 5^2 7$.
Note that from the size of an arbitrary group we can't necessarily
deduce its structure. However, requiring that the group is cyclic is
enough, since there's only one of a given size. This makes cyclic
groups look a lot like positive integers. A cyclic group can be
constructed explicitly as a product of cyclic groups of prime power
order, ensuring the component groups have orders that are mutually
coprime. This is again analogous to how positive integers behave. A
point of difference, however, is that groups of prime power order are
not a product of smaller cycles, but they do contain all smaller prime
power cyclic groups as subgroups.
The number above gives the group
$$\ZZ_{2^7} \times \ZZ_{3^3} \times \ZZ_{5^2} \times \ZZ_{7},$$ which
consists of $4$--tuples with elements from the respective groups.
This group is essentially the same as $\ZZ_{2^7 3^3 5^2 7}$.
I'll denote the group order as
$$\#\G=\prod_{n=1}^{N} p_n ^ {m_n},$$ and $\G$ as the abstract group,
where $\ZZ_{\#\G}$ and $\times_{n=1}^{N} \ZZ_{p_n^{m_n}}$ are two
isomorphic representations.
In our representation of $\G$, the cyclic subgroup of order $q_n$ in
$\G$ can be generated from the element
$$g_n = {\#\G \over p_n^{m_n}},$$ giving a homomorphism from $\G$ to
$\ZZ_{p_n^{m_n}}$. Combining $N$ such homomorphisms allows the
construction of an isomorphism
$$f : \times_n Z_{p_n^{m_n}} \to Z_{\#G} : (x_1, \ldots, x_N) \mapsto
\sum_n x_n g_n.$$ This leads us to the more concrete observation that
an oscillator based on $\G$ can be implemented either as a single
counter $\ZZ_{\#G}$, using a single generator, or a ``wired or'' of $N$
counters $\ZZ_{p_n^{m_n}}$, each with its own generator, where the
whole acts as a single frequency divider which only generates an event
if all counters have $s_n=0$.
\subsection{Musical Scale}
This makes one wonder if it's not possible to construct a group
that is very degenerate, in a way that it
produces a relatively well spread out ``frequency spectrum'' which
maps well to the (logarithmic) frequency resolution of human hearing.
Keeping the number of large prime factors small, it might even be
possible to create some kind of musical scale with inherent
``smoothness''. Probably, if there are enough small primes, large
prime numbers are not really necessary since frequency resolution for
large numbers is less of a problem.
For example $d = 604800 = 2^7 3^3 5^2 7$, which is the number of
seconds in a week. Moreover, $d+1$ is prime, so it has an associated
Galois field, but this is no longer a polynomial field,
In order to investigate the usefulness of this, we could plot
out the possible frequencies that can be generated using this scheme
on a logarithmic scale. On the other hand, using fields might be
overkill. Using just cyclic groups (counters) might be better here.
Given a a degenerate cyclic group $\G$, how many different (cyclic)
subgroups does it have? All subgroups of cyclic groups are cyclic, so
there is a one to one correspondence of the order of all possible
combinations of subgroups of $\G$ and the combinations of prime
factors of $\#\G$. Which leads us to the number of possible groups
$$\prod_N (m_n+1),$$ since the subgroups of $\ZZ_{p^m}$ are $\{\ZZ_{1},
\ZZ_{p}, \ldots, \ZZ_{p^m}\}$, which totals $m+1$.
The subgroups, or numbers representing the orders, can be arranged in a
$N$ dimensional cuboid, addressed with coordinates $(x_1,\ldots,x_N)$
where each coordinate is limited to $0 \leq x_n \leq m_n$. The order
can be computed as
$$\#\G(X) = \#\G(x_1, \ldots, x_N) = \prod^N p_n^{x_n},$$ which
corresponds to the the subgroups $\G(X) = \times^N \ZZ_{p_n^{x_n}}$ of $\G$
The things we are interested in is the distribution of $\G(X)^{-1}$,
since it represents the number of frequences we can generate by
dividing a master clock, which would be the CPU clock for example. All
frequencies that can be generated in this way have a fairly simple
harmonic relationship, making it possible to generate smooth scales
and chords.
\subsection{Playing with Subgroups of $\ZZ_{p^m}$}
Given a divider $\ZZ_{2^m}$, obtaining one for $\ZZ_{2^{m'}}$ with
$m' exponential
% what about matrices over GF(2)? what's to find there?
% most theorems in linear algebra / projective geometry
% exclude GF(2) as a base field, because it behaves so differently.
%% \subsection{Matrix Representation}
%% Over $\GF(2)$ polynomials can be represented by (infinte) matrices,
%% while polynomial fields can be represented by (finite) matrices.
\subsection{Application : Linear Noise Generators}
The cheapest way to generate noise for the application at hand is to
use exactly the impulse response of linear systems described by
difference equations as explained above. If the period of the bit
sequence is large enough, the human auditory pattern detection will
not be able to recover the redundancy.
Following the same algebraic trick as exposed above, suppose the
difference equation is expressed by $p(x)y(x) = u(x)$, and $u(x) = 1$,
corresponding to the impulse sequence $(1000\ldots)$, what we need to
do is to find out which period is related to $p(x)$. In other words,
we have to find the $s_p(x)$ with the lowest possible degree such that
$${1 \over p(x)} = {s_p(x) \over x^P + 1} = s_p(x) \sum_n x^{Pn} ,$$
where $P$ is the period of the bit sequence corresponding to
$p(x)^{-1}$. This is equivalent to the congruence relation
$$x^P = 1 \mod p(x).$$ To get the smallest $P$ satisfying this
equation as large as possible, it is necessary to choose $p(x)$ to be
a \emph{primitive} polynomial, which means it is irreducible over
$\GF(2)[x]$, and $x^n\mod p(x)$ generates all nonzero elements in the
field modulo $p(x)$, also denoted as $\GF(2^n)$ with $n$ the degree of
$p$.
%% In the quotient field $\GF(2)[x] / (p(x))$, the
%% element $x + (p(x))$ generates the entire multiplicative group. Here
%% $(p(x))$ denotes the ideal in $\GF(2)[x]$ generated by $p(x)$. The
%% multiplication is commutative, so it is always possible to construct
%% such a generator. Whether this generator is mapped to $x$ depends on
%% the polynomial $p(x)$.
The direct implementation of the difference equation is called a
\emph{Linear feedback shift register} or LFSR. The next output is
computed as the sum of a certain number of taps. I originally used a
$16$bit LFSR with taps $(15,14,12,3)$, but the parallel variant is
simpler to implement given a parallel XOR operation is available.
This parallel variant is related more directly to the Galois field
$\GF(2^n)$. More specificly, the output of the generator is taken to
be any coefficient of the polynomial sequence generated by $x$, or
$\sum_k s_{k,n}x^k = s(x)_n = x^n \mod p(x)$, where the output
sequence could be $s_{0,n}$.
To illustrate the difference between an irreducible and a primitive
polynomial, take the irreducible polynomials $x^8+x^4+x^3+x^2+1$ and
$x^8+x^4+x^3+x+1$. Both are irreducible, but only the former produces
a primitive field. In the field of polynomials modulo the latter one,
the $x+1$ is a generator, but not $x$. Note that these fields are
isomorphic, or essentially equal, but their representations are
different.
Implementing the parallel scheme for the former polynomial on a
digital computer is straightforward. Multiplication of the state
polynomial $s(x)$ by $x$ corresponds to a left shift of the register
containing the coefficient bits. The bit that's shifted off (the
coefficient of $x^8$) is replaced by adding the other terms
$x^4+x^3+x^2+1$ to the current state. In other words, when a bit
carries over, \verb+1Dh+ $=$ \verb+00011101b+ is XORed with the state.
%% \subsection{Using a multiplier}
%% When a multiplier is present, a lot more intersting things can be
%% done. One of them is the use of the linear congruential method for
%% random number generation.
%% \section{Resurrection}
%% Digging on the web.
\section{Other Discrete Structures}
%\subsection{Sampling and Formants}
% \subsection{Diadic Numbers}
\subsection{Phase and Frequency Modulation}
What happens when we modulate the generators of an additive subgroup,
or period of counters? Let's call the first \emph{phase} modulation
and the second \emph{frequency} modulation.
Using an oscillator with period $M$ and generator $1$ to generate the
generator of a second oscillator $F$ gives the following function
$$F_n = \sum_{i=0}^n i = {n(n-1) \over 2}\mod F.$$
\subsection{Cellular Automata}
Another interesting way to generate signals is by using cellular
automata. Using the ideas above, we could represent a finite row of
nonzero cells as a polynomial, and the update function as another
polynomial. Giving rise to an update function in the ring $\GF(2)[x]$
or any finite field $\GF(2)[x] / (p(x))$.
\subsection{The NNT and the Fields $\GF(2^{2^n}+1)$}
For $0 \leq k \leq 4$ the Fermat numbers $F_k = 2^{2^k}+1$ are prime,
which means there exists a primitive field $\F_k = \GF(F_k)$. This
field is isomorphic to $\mathbb{Z} / F_k \mathbb{Z}$ the integers
modulo $F_k$.
Implementing this representation requires the implementation of a
modulo operation. This can be quite expensive, especially on cheap
hardware lacking a fast hardware divider. However, it is possible to
embed a representation of $\F_k$ in the ring $\R_{k+1}$, where we take
$\R_k$ to be the ring represented by $\mathbb{Z} M_{k} / \mathbb{Z}$,
the integers modulo $M_{k} = 2^{2^k}-1$, where the $\{M_k : k \in
\mathbb{N}\}$ form a subset of the set of Mersenne numbers $\{2^n-1 :
n \in \mathbb{N}\}$.
The implementation of this ring relies on the reduction modulo $M_k$,
which is a cheap operation if $K=2^k$ is (a multiple of) the machine
wordsize. Suppose we want to reduce a number $[c:a]$ of $2K$ bits
modulo $M_k$, represented by the concatenation of two $K$ bit words
$c$ and $a$. This gives
\begin{align*}
[c:a] &= 2^Kc+a \\
&= (2^K-1)c+c+a \\
&= c+a
\end{align*}
with operations modulo $M_k$. The consequence of this is that both
addition and multiplication in $\R_{k}$ can be performed by the
default finite wordlenght unsigned integer operations, followed by
this very simple reduction step. This reduction is just addition due
to the availability of $c$ as a separately addressable entity, namely
carry bit for unsigned addition and high word for unsigned
multiplication.
So how to embed $\F_k$ in $\R_{k+1}$? The map
$$f_k : \R_{k+1} \to (2^{2^k} - 1)\R_{k+1} : x \mapsto x 2^{2^k - 1}
(2^{2^k}-1) = x 1_k$$ will do just that. Here
$$(2^{2^k} - 1)\R_{k+1} = \{(2^{2^k} - 1)m : m \in \R_{k+1}\},$$
which we will call $I_k$ is the ideal generated by $2^{2^k} - 1$ in the ring
$\R_{k+1}$. The element
$$1_k = 2^{2^k - 1}(2^{2^k}-1)$$ is the representative in $\R_{k+1}$
of the multiplicative unit of $\F_k$. It can be used to generate the
additive group, which gives us a clear relation between integers and
the representation in $\R_{k+1}$ of their associated elements in $\F_k$.
So how can we see this is a representation of $\F_k$? It is easy to
see that $f_k$ is a group homomorphism for the additive group of
$\R_{k+1}$ since $f_k(a+b) = (a+b)1_k = a1_k + b1_k = f_k(a) +
f_k(b)$. To see that $f_k$ is a ring homomorphism, rests us to show
that $f_k$ is a group homomorphism for the multiplicative group. We
need $f_k(a) f_k(b) = a b (1_k)^2 = a b 1_k = f_k(ab)$ which is valid
if $1_k^2 = 1_k$. That this is so can easily be verified.
%% This is so since
%% \begin{align*}
%% 1_k^2
%% &= (2^{2^k - 1} (2^{2^k}-1))^2 \\
%% &= (2^{2^{k+1} - 2}) (2^{2^{k+1}} - 2^{2^k + 1} + 1) \\
%% &= (2^{2^{k+1} - 2}) (- 2^{2^k + 1} + 2) \\
%% &= 2^{2^k} (2^{2^{k} - 2}) 2 (-2^{2^k} + 1) \\
%% &= (2^{2^{k} - 1}) (-2^{2^{k+1}} + 2^{2^k}) \\
%% &= (2^{2^{k} - 1}) (-1 + 2^{2^k}) \\
%% &= 1_k
%% \end{align*}
%% with all operations in $\R_{k+1}$. Note there are no divisions here,
%% only multiplication and modulo reduction using $2^{2^{k+1}} = 1$.
The kernel $\{x : f_k(x)=0\}$ of this ring homomorphism is $\{xF_k :
x\in\R_{k+1}\}$, which is a maximal ideal of $\R_{k+1}$, which means
that $\R_{k+1} / \{xF_k\}$ is a field. This structure is carried into
the ideal $M_k\R_{k+1}$ by $f$.
To find this representation by construction see \verb+mersenne.tex+,
which uses arguments to identify $1_k$ in the additive subgroup
generated by $M_k$, by requiring the property $1_k^j = 1_k$, for $0
\leq j \leq F_k$,
\subsection{Shape of $1_k$}
Returning to the embeddings of $\F_{k}$ in $\R_{k+1}$, the interesting
thing what $1_k$ exactly looks like.
\begin{align*}
1_0 &= \0\1 \\
1_1 &= \0\1\1\0 \\
1_2 &= \0\1\1\1.\1\0\0\0 \\
1_3 &= \0\1\1\1.\1\1\1\1.\1\0\0\0.\0\0\0\0 \\
1_4 &= \0\1\1\1.\1\1\1\1.\1\1\1\1.\1\1\1\1.\1\0\0\0.\0\0\0\0.\0\0\0\0.\0\0\0\0
\end{align*}
Here the dots just separates nibbles. This looks like a nice square
wave! Another interesting thing to see is that in $\R_{k+1}$, there
is only one silence, which is all zero, since it is the same element
as all one.
What do the other numbers in the ideal $M_k \R_{k+1}$ look like? The
redundancy is $M_k : 1$, which gets large quite fast. Are the
waveforms special in some way so we can use them as sounds?
\subsection{Resonant Filter}
Using two state output, there is really no straightforward way to add
two signals, performing additive synthesis. Composing a signal
consisting of different frequencies by adding them together modulo 2
behaves more like cross modulation than addition. The audible spectrum
will contain not only the sum, but also the modulation components.
However, it is possible to approximate $2$ tone polyphony by
synthesizing waveforms that have two clearly distinct harmonic
components. The approach is based on the following observation. The
sequence
$$\1\0\1\0\1\0\0\0\0\0\0\0\1\0\1\0\1\0\0\0\0\0\0\0 \ldots$$ resembles
the response of a resonant filter with period $2$ and time constant
$6$, to an impulse signal with period $12$, while the sequence
$$\1\0\1\0\1\0\1\1\1\1\1\1\0\1\0\1\0\1\0\0\0\0\0\0 \ldots$$ resembles
a response of the same filter to a square wave with period $24$. It
is even so that this particular scenario can be expressed in terms of
generating functions, when the resonant filter is represented by a
polynomial (finite), stretching the analogy even further.
This system has $3$ degrees of freedom: the periodicity, the resonance
frequency and the time constant. This can be implemented as the
product (AND) of a pulse width modulated low frequency signal
oscillator, and a high frequency oscillator which has its phase reset
for each $0 \to 1$ transition of the low frequency signal.
It is probably possible to combine two of those to get two formants
which is enough to encode very basic vowel spectra. This requires 3
timers, which fits perfectly in the available hardware.
% distorted walky talky ??
\subsection{Chaotic Oscillator}
An emulation of the qualitative behaviour of a chaotic oscillator can
be obtained by driving the Resonant Filter system described above with
a base period that is irregular.
\subsection{Pulse Width Modulation}
Now, this is sort of cheating, since the purpose of PWM is really
efficient switched digital to analog conversion. Using it, we give up
the ``true'' binary output, and add a layer that will allow us to vary
the voltage (on average) between more than 2 levels.
The \verb+18f1220+ chips contain a hardware PWM module with $8$ bit
resolution ($10$ bit for the enhanced version). A fixed period
oscillator is used as the timing source to drive a pin high and low
with a programmable duty cycle.
I do wonder wether it's possible or desirable to do some computations
in $\GF(257)$. I don't see much however, other than convolution using
NTT.
\def\sd{\Sigma\Delta}
\subsection{$\sd$ Modulation}
Note that if switching efficiency isn't all important, $\sd$
modulation gives much better performance for 1--bit audio digital to
analog conversion. It generates higher frequency waveforms, but better
low frequency resolution.
It can also be used as a cheap way to do synthesis, since it behaves
approximately as a reverse frequency to voltage converter: output
pulse frequency is on average proportional to the input value.
With multibit digital input, it is very easy to implement: the binary
$\sd$ signal is the carry bit of a simple input accumulator. When the
accumulator overflows, one ``packet'' of energy is transferred to the
output.
\subsection{Gray Code}
Gray codes are (cyclic) sequences of $N$ binary digits, where each
number in the sequence differs only by one bit from the previous. They
can be understood as \emph{Hamiltonian paths} on a $N$--dimensional
hypercube, where one moves along edges of the cube.
However, the most commonly used gray code is one with a particular
kind of symmetry. It is called the \emph{binary reflected} grey code
or BRGC, which is a recursive scheme that traverses both $N-1$
dimensional halves of the hypercube in the opposite direction. This
rule is applied recursively and terminates at the trivial $1$
dimensional case.
If $\bar{s}$ is the reversal of the sequence $s$, $s : t$ is the
concatenation of sequences $s$ and $t$, and $b(s)$ prepends each bit
vector in the sequence $s$ with bit $b$, the BRGC sequence recursion
relation is $$s_{n+1} = 0(s_n) : 1(\bar{s}_n),$$ where $s_0$ is the
length $1$ sequence containing the empty vector.
% the wikipedia page about this is unclear, since a 4D, 16 cycle coil is a BRGC.. maybe it's the ``constrained'' part?
%% Gray codes are related to the \emph{snake in a box} problem, ore more
%% specifically, the \emph{coil in a box} problem.
% interesting: single track codes
\section{Control and Note Sequencing}
This is a whole other ballpark. With \emph{control} is meant the
evolution of the configuration of the synth engine at a slower
rate. This is called \emph{haptic} rate and is related to our ability
to experience mechanic vibrations. The next rate down is \emph{note}
rate, and roughly corresponds to the frequency of events that can be
experienced visually. This cascade of time scales is called the
\emph{frequency hierarchy}.
The lower frequency parts are much less limited by the speed and
simplicity of the hardware, since more time can be used to do actual
computations. This makes it rather pointless to try to exhaust the
possibilities. However, here are some general ideas.
\subsection{Transient Stack}
In a binary synth there is no direct way to mix sounds. One way of
bringing discrete events to the forground is by temporarily
interrupting stable background sounds.
The typical example would be to synthesize a sequence with
\emph{bass}, \emph{bassdrum}, \emph{snare} and \emph{hihat}, which are
ordered here left to right with time extent decreasing. Mixing the 4
will boil down to playing the shortest sound until it is done.
\subsection{Wavetable}
Since there's a wavetable player, it is possible to do some control
rate timbre synthesis by generating or updating wavetables. There are
a lot of possibilities here. Maybe the most interesting parts are
transform domain methods like the \emph{Walsh--Hadamard} transform.
\subsection{Chirps}
A simple form of frequency modulation using a linear ramp or a
sawtooth signal is easily implemented. This can be used to generate
drum-like sounds.
\section{Design and Implementation}
\subsection{Frequency Hierarchy}
Human perception already seems to have a built in time hierarchy
roughly ordered from high to low as \emph{hearing}, \emph{feeling},
\emph{seeing}, \emph{moving}, \emph{remembering}. This is just
a vague description, since a lot of these time scales overlap. However,
this idea of time hierarchy can easily be reflected in the way we keep time
in the synth, using the concepts of \emph{sample}, \emph{control} and
\emph{note} frequency.
This is fairly standard practice. We're going a step further in that
each of these levels has an associated language, which metaprograms
the previous one. This is possible since efficiency can be traded for
expressive power as we move up the time scale ladder.
\begin{itemize}
\item[N)] Note state updates switch control engines.
\item[C)] Control state updates switch synth engines,
synth parameters and signal routing.
\item[S)] Synth updates produce waveform samples.
\end{itemize}
It's probably easiest to have the low levels pre--empt the higher
ones, so computations can be spread out without explicit cooperative
multitasking. There's no reason to not have cooperative multitasking
on the high abstraction layer though.
%% The first layer's language is plain data: all synth engines are fixed,
%% only the current routine and engine, and it's parameters can be
%% changes. The second layer's language is threaded forth. Current
%% control word is any forth word, but actual code is still in flash
%% memory. The third layer's language is a music language.
\subsection{Synth Architectures}
\begin{itemize}
\item An asynchronous design using the $3$ high resolution timers to
generate events, and the low resolution timer to drive the noise
generator and the other time bases.
\item A synchronous design with a software synth engine running at a
fixed sample rate, from which all other time scales can be derived.
\item A similar synchronous design, but using $10$ bit hardware PWM as
output.
\end{itemize}
\subsection{Synth Patch}
The idea is to make the synth as configurable as possible, but at the
same time keeping the configurability contained in a small number of
bits so the transient stack can be implemented efficiently. The way to
do this is to eliminate all redundancy (symmetry) in the configuration
data.
Also the decoding of this information should be very straightforward,
since it has to be computed at each synchronous or asynchronous engine
event. I'll call the total configuration state, not including the
engine state, as the \emph{synth patch}.
The synth engine consists of 4 \emph{generators}. One noise generator
\verb+[noise]+ and 3 square wave oscillators \verb+[osc0]+,
\verb+[osc1]+ and \verb+[osc2]+. The oscillators and the noise
generator each have an associated update period, which is part of the
patch data. There state is not.
These oscillators can be synced to each other as single master sync $0
\to 1,2$ or cascaded sync $0 \to 1 \to 2$. This is encoded as one bit
for \verb+[osc1]+ and two for \verb+[osc2]+, leaving room for one
external sync event.
I'd like to parametrize the output combination of the 3 square wave
oscillators and the noise generator using a limited set of boolean
functions. For $4$ inputs, there are $2^{2^4}=2^{16}$ such
functions. To see this, draw the Karnaugh map of a $4$ input function,
and observe it has $16$ squares in total. Some things that could be
divided out are: the polarity of the output, the polarity of the noise
signal and the polarity of each of the input square waves.
Working it from the other side, the necessary combinations to get the
absolute minumum functionality are
\begin{itemize}
\item \verb+[xmod]+ Add (XOR) the output of 3 square wave
outputs. This can be used to generate PWM.
\item \verb+[reso]+ Multiply (AND) \verb+[osc1]+ and \verb+[osc2]+, and
add (AND) with \verb+osc0+.
\item \verb+[gate]+ Multiply (AND) the output of 3 square wave
outputs.
\end{itemize}
% is da wel waar?
\subsection{Synth Algorithms}
Currently, there's only $3$. Square waves, pseudorandom noise and
sample playback. Together with intermodulation schemes described
above, this gives already quite a rich palette.
\subsection{Misc Hacks}
So evaluate $AND$ of a number of bits in a byte, mask out all the bits
that are not necessary, and use the $ADD$ operation. This can also be
used to evaluate expressions of the form $f = a + bc$, which occur in
the \verb+[reso]+ mixer. Here I combine 3 waveforms as $f = a +
bc$. Using ordinary adder logic this gives $(a,b,c) + (0,0,1) =
(f,x,x)$. Adding more bits allows to propagate the result into the
carry flag.
Rotating oscillator period's bit rep.
\section{Electronics}
I think a lot can be done by combining analog and digital electronics.
One of the prototypical examples is of course the analog time varying
reso filter. Some other interesting things could be done by using
comparator based chaotic oscillators (switched unstable systems) and
time constant capturing.