Quick overview of the sheepsinth architecture --------------------------------------------- The synth is divided into 2 parts: CORE and CONTROL. The core part runs as interrupt service routines for the 4 independent hardware timers present on the PIC18 microcontroller. This part is very time critical and therefore mostly hardcoded. It has some parameters that can configure the way in which the core synth responds to timer events. This part effectively generates the sound by setting an output pin in the high or low state. The control part changes the configurable parameters of the CORE synth at a rate of approximately 200 Hz. This is enough to control the 'motion' of a sound to a fairly high resolution. The control part is less time critical and contains the bulk of the code. A lot is possible here, since about 10000 machine cycles can be used per control tick. CORE ---- The synth audio core has 4 different event sources: 3 x 16 bit timers for waveform generation tied to OSC0, OSC1 and OSC2, and an 8 bit timer used for control rate time sync generation. The 3 oscillator timer events are translated to audio output using the following algorithms: The engine is controlled with a single byte variable 'synth' which contains the following info (see top of synth-core.f) - osc1 noise generation on/off - oscillator sync bits - mixer algorithm: silence, xmod, reso, osc1 This information is used in the following way: - The interrupt service routines for the different timers will save their output in the 'osc-bus' output bus variable. The noise bit determines the functionality of OSC1 as either a noise source (1) or a square wave (0). The sync bits determine wether an oscillator event can reset a connected osciallator. OSC0 can reset both OSC1 and OSC2, and OSC1 can reset OSC2. - The mixer algorithm combines the 3 oscillator outputs into a single synth audio output. 'silence' will set a 0 on the bus, 'xmod' or cross modulation wil XOR all 3 bits, 'reso' will compute OSC0 modulated by (XOR) OSC1 which uses OSC2 as an evelope (AND). The 'osc1' mixer will just select the output of OSC1. So to summarize, OSC0 is always the master, free-floating oscillator. OSC1 can be used as a cross modulation oscillator using the 'xmod' mixer or as a formant/resonant frequency using the 'reso' mixer. OSC2 is a modulator in 'xmod' mode, and serves as a resonance envelope in 'reso' mode. This is the low level part of the synth. I invite you to have a look at the source to see how this is implemented. The file is synth-core.f CONTROL ------- oscillator PERIOD and FREQUENCY ------------------------------- The oscillators are driven by 3 system timers. Such a timer is a piece of independent hardware on the microcontroller chip, residing next to the CPU, that will increment the value in a (16 bit) register, and send an interrupt to the CPU when this register overflows from #xFFFF to #x0000. This interrupt is used to update the oscillator outputs, which will be further combined to current audio output using the mixing algorithm, and reloading the associated hardware timer register so the next event is generated after a certain delay. Sheepsint contains 3 words that can set the 16 bit values used to update the hardware timers: _p0 _p1 _p2 These words take unsigned 16 bit values, which in PURRR18 are encoded as a low byte followed by a high byte: lo hi -- To compute the frequency of the resulting timer events, simply divide the CPU instruction frequency (probably 2 MHz) by the value passed to _p For interactive testing it's easier to use the corresponding words p0 p1 p2 which take only unsigned 8 bit values. The counterparts of these words are the words _p0@ _p1@ _p2@ and p0@ p1@ p2@ which will return the current values of the oscillator periods as a 16 bit (two byte) and 8 bit (one byte) value respectively. For example, to increment the period by 1 in 8 bit mode do: p0@ 1 + p0 PRESETS ------- In order to make configuration of the synth easier, the control part contains some pre-defined words that set the synth state to some particular sound generation algorithm, setting the periods, sync bits, noise bit and mixing algorithm. Let's start with a basic square wave, which has only a single parameter, the frequency. You select a square wave using the word 'square'. Then set the frequency using i.e. '100 p0' The synth contains a noise generator. The word 'noise' activates it, running at a sample frequency of about 8kHz. To switch the synth to cross modulation, use the word 'xmod2' to set it to OSC0/OSC1 xmod, and the word 'xmod3' to set it to OSC0/OSC1/OSC2 xmod. Random (frequency) cross modulation can be selected using the word 'rxmod'. To switch the synth to reso/formant mode use the 'reso' word. Here OSC0 is the base frequency, OSC1 is the frormant/resonant frequencey and OSC2 sets the amount of resonance, or formant presence. The word 'pwm' takes a single argument and uses OSC0 and OSC1 in xmod2 mode to produce a pulse width modulated square wave. To top it off, the word 'silence' will disconnect all oscillator outputs from the audio output. NOTES and OCTAVES ----------------- Instead of setting oscillator periods directly, it is possible to use semi tones and octaves. For this use the word 'octave' to set the current octave, which will influence all subsequent 'note' words, and use the words 'note0' 'note1' and 'note2' to set the oscillator semitone in the current octave. 0 means C, 1 means C# etc.. for example 2 octave 5 note0 will set OSC0 to E in octave 2. CONTROL RATE TIMER ------------------ The words 'wait-control' and 'wait-note' can be used to synchronize to the control rate timer. They will busy-wait till the next control or note tick, and resume execution. The control tick defaults to about 244 Hz, and the note tick defaults to 7.8 Hz, or 1/16 note at 114 BPM. It is possible to use different tick synchronization using the word 'synch-tick' which is explained in synth-control.f INTERACTION ----------- The default behaviour of the synth is to run a main loop which reads out the knobs and modifies control parameters accordingly. This loops checks activity on the serial port. It will run the command interpreter (monitor) if a byte is recevied. You can use the words 'on' and 'off' to swich the synth core on or off, disabling all interrupts. This is intended as a 'master on/off' switch. Note that 'off' switches off the sound, but also the control rate timer! REFERENCE --------- on -- turn on synth engine off -- turn off synth engine square -- square wave oscillator xmod2 -- first 2 oscillators XMOD xmod3 -- all 3 oscillators XMOD rxmod -- random 3 oscillator XMOD noise -- noise generator pwm p -- set pwm, setting OSC1 in terms of OSC2 p0 period -- set period of oscillator 0 (same for p1 p2) p0@ -- period get the period of oscillator 0 (same for p1@ p2@) note0 n -- set note number (same for note1 note2) octave o -- set the octave for the note words _p0 pl ph -- set 16bit period of 0 (same for 1 2) _p0@ -- pl ph get ... (same for 1 2)