Look Mum No Computer organ console code
Project website: https://www.lookmumnocomputer.com/projects#/joans-church-organ
- Channel 1: Pedal keyboard
- Channel 2: Great keyboard
- Channel 3: Swell keyboard
- Channel 13: Principal pipes
- Channel 14: String pipes
- Channel 15: Flute pipes
- Channel 16: Reed pipes
The code works using buffers :
- One buffer for the keys
- One buffer for the stops
- One buffer for the pipes
Every time a change is made to the keys and/or stops, the corresponding buffer is updated and the pipes buffer is recomputed to reflect the new configuration. The pipes buffer is then compared to the current pipes configuration and MIDI note on/off messages are sent accordingly to reflect the new configuration in the buffer.
The setup()
function initializes the MIDI stuff. All buffers are empty at startup. The panic()
function is only there for convenience so we can reset all the pipes when the Arduino starts or by pressing the reset button.
The magic happens in the main loop()
.
Every time a key or a stop on/off status has changed, the buffers are updated accordingly.
The buffer for keys is the keys
boolean array (one row per channel) and the buffer for the stops is.... stops
. true when key/stop is on, false when off.
The keys
buffer update is handled by the setKey()
function (which is called by handleMidiNoteOn()
and handleMidiNoteOff()
by the MIDI library) and the stops
buffer is updated by refreshStops()
(the newStops
is a temporary array used only within refreshStops()
to compute the differences since these are not triggered via MIDI events such as CCs)
If any change was detected in the keys
or the stops
buffer, the shouldUpdatePipes
global variable is set to true
.
If no change is made, Step 1 runs again.
The incoming MIDI messages are processed by MIDI.read()
. MIDI.read()
only reads one message so if there are more than one message awaiting in the input buffer (which may happen when a large amount of MIDI date is received during a short time like in Circus Galop), we read all of them until the MIDI in buffer is empty (MIDI.getTransport()->available()
returns the amount of bytes awaiting to be processed in the MIDI in buffer).
It's important to keep the MIDI in buffer clear because the next steps take some time to run and this reduces the risk of having it overruning and losing incoming MIDI messages such as notes off while the code is running. The buffer is handled by the hardware so it runs simultaneously with the code.
When a change was detected, the shouldUpdatePipes
is reset to false
.
The big ass updatePipes()
function is then called.
The new pipes buffer is computed using the temporary newPipes
array that is first cleared (all set to false
) by clearNewPipesBuffer()
.
For each keyboard key that is on, we determine which pipe(s) should be on. This is made in the setBufferPipesForKey()
function based on the key pitch and the keyboard (MIDI channel) it comes from (that's the code you wrote initially in the note on handler).
The setNewBufferPipe()
is called to flag a pipe as "on" in the temporary newPipes
by passing its pitch and MIDI channel. The setNewBufferPipe()
makes sure that the pitch is between 0 and 127 because of the stops that can transpose out of the MIDI specs.
Now that we have the new pipes statuses in newPipes
, it's compared to the current pipes
buffer. The appropriate MIDI note on/note off event is sent using the sendNoteEvent()
function when a difference is detected and pipes
is updated to reflect newPipes
.
The sendNoteEvent()
sends the actual Note on / note of events using the MIDI library. Since sending MIDI data is a blocking operation that can take several ms, we also process the MIDI in data as we do in the main loop()
so we don't have MIDI in message stacking up in the input buffer and prevent losing MIDI messages.
When this process is complete, we may already have the shouldUpdatePipes
set to true
if some MIDI in messages were received during updatePipes()
.
Depending on the time updatePipes()
took to run, some notes may appear to have been dropped (ie if the same note was on then off during the same updatePipes()
iteration, it won't be heard at all because only the last status is used).
Sources:
- List of pipe organ stops
- Dictionary of the most frequently used organ stops
- Pitch Levels
- Functions of the organ: Couplers
- Lesson 7 Mixture & Mutation Stops
Organ stops set which sets of pipes will be activated by the keyboards keys.
There are 3 kinds of stops on the Joan's church organ:
- Regular stops that activates a single row of pipes for the keyboard, with or without octave transposition.
- Mutation stops that activates a single row of pipes for the keyboard but with a transposition that ends up to a different note (can be a fifth or a third).
- Coupler stops that allows to play the stops of another keyboard in addition to its own. For example, the "Swell to Great" means that all stops from the Swell keyboard can be played on the Great keyboard (playing the Great keyboard will play the Great + Swell stops).
Some organs also have Mixture stops that basically combine several rows of pipes and/or mutations. Joan's church organ doesn't have mixture stops but has a preset board and buttons that provides similar functionality.
The organ stops also have a number to determine the pitch, in feet, as a power of two (2, 4, 8, 16, 32 etc.). As for organ pipe sizes, the larger is the number, the lower is the pitch.
A 8′ pitch corresponds to the middle C (MIDI pitch 60) and results in no transposition. A 4′ pitch is a 1 octave up transposition (half size, double pitch), 16′ is 1 octave down (double size, half pitch), 2′ is 2 octaves up, 32′ is 2 octaves down etc. Some large organs even have 64′ stops and can produce infrasound down to 8 Hz.
Mutation stops have a pitch that is not a power of 2 pitch such as 22⁄3′ that corresponds to 1 octave + 1 fifth (12 + 7 semitones).
- Principal: The classic organ straight zinc flue pipes.
- Flutes: The square wooden flue pipes.
- Strings: The straight metal flue pipes with a hole on the top.
- Reeds: The cone shaped metal reed pipes.
Based on the pitch level and the list of pipe organ stops on Wikipedia, we can determine the implementation for the Joan's church organ.
Stop | Pipes row | Pitch offset | Description |
---|---|---|---|
Swell Keyboard | |||
Open Diapason 8 | Principal | 0 | A flue stop that is the "backbone" sound of the organ. |
Stopped Diapason 8 | Flute | 0 | A basic stopped 8′ and/or 16' flute in the manuals. |
Principal 4 | Principal | +12 | A 4′ Principal. "Prestant" often indicates ranks that have pipes mounted in the front of the organ case. |
Flute 4 | Flute | +12 | The Speelfluyt was reconstructed by Jürgen Ahrend for the Schnitger organ in the Martinikerk Groningen out of one remaining pipe. |
Fifteenth 2 | Principal | +24 | The manual 2′ Principal or Diapason; its name merely signifies that it is above (i.e. "super") the 4′ Octave. |
Twelfth 22⁄3 | Principal | +19 | A principal mutation stop of 2+2⁄3′ |
Great Keyboard | |||
Open Diapason 8 | Principal | 0 | A flue stop that is the "backbone" sound of the organ. |
Lieblich 8 | Flute | 0 | Means "Soft Flute" in german. |
Salicional 8 | String | 0 | An 8′ (sometimes 4' or 16') string stop, softer in tone than the Gamba. |
Gemshorn 4 | Flute | +12 | A flue stop usually at 4' or 2' pitch but sometimes 8' pitch; similar tone as Spitz Flute (A 4' or 2' pitch flute with metal pipes tapered to a point at the top). |
Salicet 4 | String | +12 | An 8′ (sometimes 4' or 16') string stop, softer in tone than the Gamba. |
Nazard 22⁄3 | Flute | +19 | A flute mutation stop of 2+2⁄3′ pitch (sounding a twelfth above written pitch) |
Horn 8 | Reed | 0 | A 16', 8' and/or sometimes 4' pitch reed stop imitative of the instrument. |
Clarion 4 | Reed | +12 | A 4′ or 2′ Pitch Trumpet, it is a chorus reed. |
Pedal Board | |||
Bass Flute 8 | Flute | 0 | |
Bourdon 16 | Flute | -12 | A wide-scaled stopped-flute, usually 16′ and/or 8′ pitch on the manuals, and 16′ (sometimes 8'), and/or 32′ pitch in the pedals (where it may be called Subbass or Contra Bourdon) |
Couplers:
- Swell to Great: Great keyboard plays Great + Swell stops.
- Swell to Pedal: Pedal keyboard plays Pedal + Swell stops.
- Great to Pedal: Pedal keyboard plays Pedal + Great stops.
If using Visual Studio Code, add "__AVR_ATmega32U4__"
to the defines
in your .vscode/c_cpp_properties.json
file to avoid the annoying error on MIDI_CREATE_DEFAULT_INSTANCE()
.