-- Control the "analogue" side of the synth given MIDI events. #INCLUDE "oak.inc" #USE "seq" --{{{ PROC polysynth (CHAN EVENT in?, []CHAN SIGNAL pitch!, trigger!, velocity!, ctl!) --* In the real world, this would be a polyphonic MIDI-to-CV converter. --* The ctl.ids argument is the list of controller IDs that correspond to channels in the ctl array; -1 is the pitchbend controller (which MIDI deals with specially, since it's higher-resolution than the others). 1 is usually the mod wheel, and 64 the sustain pedal (although this handles sustain internally, you might want to use it for other purposes). PROC polysynth (CHAN EVENT in?, []CHAN SIGNAL pitch!, trigger!, velocity!, ctl!, VAL []INT ctl.ids) VAL INT poly IS SIZE pitch: VAL INT ctls IS SIZE ctl: VAL INT MAX.POLY IS 64: [MAX.POLY]BOOL in.use, released: [MAX.POLY]INT note: [MAX.POLY]REAL32 pitch.s, trigger.s, velocity.s: [MAX.POLY]SIGNAL pitch.v, trigger.v, velocity.v: INITIAL [MAX.POLY]INT poly.log IS [i = 0 FOR MAX.POLY | 0]: INITIAL INT poly.log.pos IS 0: INITIAL BOOL sustain IS FALSE: VAL INT MAX.CTL IS 5: [MAX.CTL]REAL32 ctl.s: [MAX.CTL]SIGNAL ctl.v: SEQ ASSERT (poly <= MAX.POLY) ASSERT (ctls <= MAX.CTL) --{{{ initialise SEQ i = 0 FOR MAX.POLY SEQ in.use[i] := FALSE released[i] := TRUE note[i] := -1 pitch.s[i] := 0.0 trigger.s[i] := 0.0 velocity.s[i] := 0.0 SEQ i = 0 FOR MAX.CTL ctl.s[i] := 0.0 --}}} CHAN BOOL req: --{{{ PROTOCOL SIGNALS PROTOCOL SIGNALS CASE voice ; SIGNAL ; SIGNAL ; SIGNAL controller ; SIGNAL : --}}} CHAN SIGNALS resp: PAR --{{{ data requester WHILE TRUE [MAX.POLY]SIGNAL p.sig, t.sig, v.sig: [MAX.CTL]SIGNAL c.sig: SEQ req ! TRUE SEQ i = 0 FOR poly resp ? CASE voice ; p.sig[i] ; t.sig[i] ; v.sig[i] SEQ i = 0 FOR ctls resp ? CASE controller ; c.sig[i] PAR PAR i = 0 FOR poly PAR pitch[i] ! p.sig[i] trigger[i] ! t.sig[i] velocity[i] ! v.sig[i] PAR i = 0 FOR ctls ctl[i] ! c.sig[i] --}}} --{{{ handle keyboard events TIMER tim: VAL INT sample.time IS INT ROUND (1000000.0 / SAMPLE.RATE): INITIAL INT cycle.start IS 0: INITIAL [MAX.POLY]INT last.step IS [i = 0 FOR MAX.POLY | 0]: INITIAL [MAX.CTL]INT last.ctl.step IS [i = 0 FOR MAX.CTL | 0]: --{{{ PROC fill.to (VAL INT chan, VAL INT step) PROC fill.to (VAL INT chan, VAL INT step) SEQ SEQ i = last.step[chan] FOR (step - last.step[chan]) SEQ pitch.v[chan][i] := pitch.s[chan] trigger.v[chan][i] := trigger.s[chan] velocity.v[chan][i] := velocity.s[chan] last.step[chan] := step : --}}} --{{{ PROC fill.ctl.to (VAL INT ctl, VAL INT step) PROC fill.ctl.to (VAL INT ctl, VAL INT step) SEQ SEQ i = last.ctl.step[ctl] FOR (step - last.ctl.step[ctl]) ctl.v[ctl][i] := ctl.s[ctl] last.ctl.step[ctl] := step : --}}} WHILE TRUE INT now, step: --{{{ PROC handle.ctl (VAL INT ctl, VAL REAL32 value) PROC handle.ctl (VAL INT ctl, VAL REAL32 value) IF IF i = 0 FOR SIZE ctl.ids ctl.ids[i] = ctl SEQ fill.ctl.to (i, step) ctl.s[i] := value TRUE SKIP : --}}} SEQ --{{{ figure out where we are relative to the start of the cycle tim ? now step := (now MINUS cycle.start) / sample.time IF step >= BLOCK.SIZE step := BLOCK.SIZE - 1 TRUE SKIP --}}} PRI ALT in ? CASE INT e.note, e.velocity: note.on ; e.note ; e.velocity --{{{ note on INT n: SEQ IF --{{{ can we retrigger this note? IF i = 0 FOR poly in.use[i] AND (note[i] = e.note) n := i --}}} --{{{ find a free voice IF i = 0 FOR poly NOT in.use[i] n := i --}}} --{{{ nope -- just steal the oldest, then TRUE n := poly.log[poly.log.pos] --}}} --{{{ force it to retrigger trigger.s[n] := 0.0 --}}} fill.to (n, step) in.use[n] := TRUE released[n] := FALSE note[n] := e.note pitch.s[n] := midi.freq (e.note) trigger.s[n] := 1.0 velocity.s[n] := (REAL32 ROUND e.velocity) / 127.0 poly.log[poly.log.pos] := n poly.log.pos := (poly.log.pos + 1) \ poly --}}} INT e.note: note.off ; e.note --{{{ note off IF IF i = 0 FOR poly in.use[i] AND (note[i] = e.note) SEQ fill.to (i, step) released[i] := TRUE IF sustain --{{{ sustain down; don't damp SKIP --}}} TRUE SEQ in.use[i] := FALSE trigger.s[i] := 0.0 TRUE --{{{ that note wasn't sounding anyway SKIP --}}} --}}} INT e.bend: pitchbend ; e.bend --{{{ pitchbend handle.ctl (-1, (REAL32 ROUND e.bend) / 8192.0) --}}} INT e.ctl, e.value: controller ; e.ctl ; e.value --{{{ other controller SEQ handle.ctl (e.ctl, (REAL32 ROUND e.value) / 10.0) IF e.ctl = 64 --{{{ sustain pedal IF e.value >= 64 sustain := TRUE TRUE SEQ sustain := FALSE --{{{ damp any keys that have been released SEQ i = 0 FOR poly IF in.use[i] AND released[i] SEQ in.use[i] := FALSE trigger.s[i] := 0.0 TRUE SKIP --}}} --}}} TRUE SKIP --}}} BOOL b: req ? b --{{{ data requested SEQ tim ? cycle.start SEQ i = 0 FOR poly SEQ fill.to (i, BLOCK.SIZE) resp ! voice ; pitch.v[i] ; trigger.v[i] ; velocity.v[i] last.step[i] := 0 SEQ i = 0 FOR ctls SEQ fill.ctl.to (i, BLOCK.SIZE) resp ! controller ; ctl.v[i] last.ctl.step[i] := 0 --}}} --}}} : --}}}