//////////////////////////////////////// //// //// //// Command Line Sequencer //// //// by Stefan Blixt //// //// v0.3 2007-10-28 //// //// //// //////////////////////////////////////// // This ChucK patch implements a set of 64 slot sequencers, each // consisting of 8 major slots, and 8 minor slots in between. The // major slots trigger at regular intervals, while the pace for // minor slots can be varied. The sequencers are edited by entering // commands on the command line that started the ChucK program: // Command: // ([]|) // = | // = [] // = (1-8) // = (1-8) // = 0[(0-9)[(0-9)]] // selects several slots for edit. Activate by typing '0', followed by the gap between // slots (0 selects all slots in the sequencer, 1 every second, 2 every third and so on), // followed by an offset from the start of the sequence. Default is 000. // = (||||||| // ||||)+ // = z|x|c|v|b|n|m||, // The letters corresponds to sound samples stored in the program. erases samples // at the selected position, ',' leaves the sound sample as it is in the slot, but adds // the selected effects to it. // = r // will cause repeats to start from the target slot and iterate backwards // in the sequence - fades will "fade in" etc. // = fg[1-9]|fh[1-9] // fg - fade out from this slot, using slots with slots spacing // fh - fade in to this slot, using slots with slots spacing // = F[0-9] // sets the volume. 0 is very low, 9 is full volume. // = p[0-9] // change the sample rate for every repeat from the target slot. 0 causes a // drastic fall in pitch, 5 a gentle fall, 6 a gentle rise and 9 a drastic rise. // = P[(1-9)|(a,w,s,e,d,f,t,g,y,h,u,j,k,o,l)[0-9]] // set the sample rate for within a slot. P may be followed by either a preset // pitch (1 causes a hich pitch, 5 causes a pitch slight higher than default, 6 // a slighly lower and 9 a drastically lower pitch), or a "piano key" with // optional octave transpose (0-4 transposes down, 6-9 up), 4 is default. E is // the reference note, e.g. if a recording of an instrument playing an E // is used, 1Pax will play a C-1, and 5Pu6c will play an A#+1. // = o[1-9] // when the slot is to be played, the sequencer will only do so every nth time, // the first time being immediately after this command has been executed. This // command is sequencer-processed, meaning that sequencer slots are occupied // as if nothing was omitted, and the sequencer decides which of these slots // that cause an actual sample to be played. // = j // join this slot with the previously stored sequencer-processed slot. E.g. if // the previous command was 1o2z, the 5jb will cause samples z and b (slots 1 // and 5, respectively) to be played alternatively: sequence 1: only z, // sequence 2: only b, sequence 3: none, sequence 4: only z, and so on. // = i[(1-9)|(a,w,s,e,d,f,t,g,y,h,u,j,k,o,l)[0-9]] // set the minor pace, i.e. the duration between this slot and the next. // The CLI sequencer changes sequencer positions in two steps - the major pace // moves ahead 8 slots at a time, the minor pace moves ahead 1 slot at a time. // If the minor pace reaches the next major slot before the major pace has // advanced, the sequencer moves back 8 slots, looping between two major slots // until the next major advance. The allows Formant-like tones to be generated // from samples. Default is that 8 minor slots will run perfectly until the // next major advance. // // i may be followed by either a set rate fraction (1 causes a very high rate, // 5 causes a slightly higher rate, 6 a slighly lower and 9 a drastically lower // rate), or a "piano key" with optional octave transpose (0-4 transposes down, // 6-9 up), 4 is default. E.g. 1iax will play a C-1, and 5iu6c will play an A#+1. // // = s[0-9] // change the sample's start point. 0 moves is swiftly towards the start of the // sample, 5 slowly towards the start, 6 slowly towards the end, 9 swiftly // towards the end. // // = . // means that, while the sample at the specified slot positions will be changed, // the effects already stored in the slot will remain the same. Cannot be used // along with add effects. // // = w[0-9] // means that the slot will be set to send a MIDI note command when executed. // The digit following 'w' indicates which MIDI device to use (if you only have // one MIDI devices the default setting 0 will suffice). The default MIDI command // is note 64 at full velocity. Pitch effects ( or ) will // change the note number, and fade effects ( or ) changes the // velocity. Some effects (such as start point) will not affect the command if // is selected. // // = (|) // // = l // toggle whether set slots should be kept after played (lock on), or removed. // // = s[0-9] // select which sequencer to edit. All sequencers keep playing constantly, but // only one can be edited at a time. KBHit kb; // ------------- Input ------------- // ------------- Data -------------- 8 => int nVoices; 7 => int nSamples; 16 => int nCommands; 8*8 => int sequenceSize; nVoices*sequenceSize => int nCommons; 10 => int SEQUENCER_AMOUNT; Std.mtof(64) => float REFERENCE_FREQUENCY; 20::ms => dur DEFAULT_MINOR_PACE; 40::ms => dur MIDI_DELAY; 3 => int MIDI_DEVICE_AMOUNT; 64 => int DEFAULT_MIDI_NOTE; // Command states 0 => int STATE_NO_COMMAND; 1 => int STATE_SLOT_SET; 2 => int STATE_SLOT_FRACTION_SET; 3 => int STATE_MULTISLOT_SET; 4 => int STATE_MULTISLOT_STEPS_SET; 5 => int STATE_MULTISLOT_OFFSET_SET; 10 => int STATE_EFFECT_SET; 11 => int STATE_EFFECT_STEPS_SET; 20 => int STATE_EFFECT_PITCH_STEP_SET; 21 => int STATE_EFFECT_OMIT_SET; 22 => int STATE_EFFECT_MINOR_PACE_SET; 23 => int STATE_EFFECT_MINOR_PACE_KEY_SET; 24 => int STATE_EFFECT_START_POINT_CHANGE_SET; 25 => int STATE_EFFECT_FADE_SET_SET; 26 => int STATE_EFFECT_PITCH_SET_SET; 27 => int STATE_EFFECT_PITCH_KEY_SET; 28 => int STATE_EFFECT_START_POINT_SET_SET; 29 => int STATE_EFFECT_MIDI_SET; 40 => int STATE_COMMAND_SEQUENCER_SELECT_SET; // Character constants 27 => int CHAR_ESCAPE; 32 => int CHAR_SPACE; 44 => int CHAR_COMMA; 46 => int CHAR_FULLSTOP; 48 => int CHAR_0; 65 => int CHAR_A; 66 => int CHAR_B; 67 => int CHAR_C; 68 => int CHAR_D; 69 => int CHAR_E; 70 => int CHAR_F; 71 => int CHAR_G; 72 => int CHAR_H; 73 => int CHAR_I; 74 => int CHAR_J; 75 => int CHAR_K; 76 => int CHAR_L; 77 => int CHAR_M; 78 => int CHAR_N; 79 => int CHAR_O; 80 => int CHAR_P; 81 => int CHAR_Q; 82 => int CHAR_R; 83 => int CHAR_S; 84 => int CHAR_T; 85 => int CHAR_U; 86 => int CHAR_V; 87 => int CHAR_W; 88 => int CHAR_X; 89 => int CHAR_Y; 90 => int CHAR_Z; 97 => int CHAR_a; CHAR_a - CHAR_A => int CAPITAL_DIFF; // Interface characters CHAR_F + CAPITAL_DIFF => int COM_EFFECT_FADE; CHAR_F => int COM_EFFECT_FADE_SET; CHAR_R + CAPITAL_DIFF => int COM_EFFECT_REVERSE; CHAR_P + CAPITAL_DIFF => int COM_EFFECT_PITCH_STEP; CHAR_P => int COM_EFFECT_PITCH_SET; CHAR_O + CAPITAL_DIFF => int COM_EFFECT_OMIT; CHAR_J + CAPITAL_DIFF => int COM_EFFECT_JOIN; CHAR_I + CAPITAL_DIFF => int COM_EFFECT_MINOR_PACE; CHAR_S + CAPITAL_DIFF => int COM_EFFECT_START_POINT; CHAR_S => int COM_EFFECT_START_POINT_SET; CHAR_W + CAPITAL_DIFF=> int COM_EFFECT_MIDI; CHAR_FULLSTOP => int COM_EFFECT_CHANGE_SAMPLE; CHAR_0 => int COM_COMMAND_MULTISLOT; CHAR_L + CAPITAL_DIFF => int COM_COMMAND_LOCK_TOGGLE; CHAR_Q + CAPITAL_DIFF => int COM_COMMAND_SEQUENCER_SELECT; CHAR_A + CAPITAL_DIFF => int COM_KEY_C; CHAR_W + CAPITAL_DIFF => int COM_KEY_C_SHARP; CHAR_S + CAPITAL_DIFF => int COM_KEY_D; CHAR_E + CAPITAL_DIFF => int COM_KEY_D_SHARP; CHAR_D + CAPITAL_DIFF => int COM_KEY_E; CHAR_F + CAPITAL_DIFF => int COM_KEY_F; CHAR_T + CAPITAL_DIFF => int COM_KEY_F_SHARP; CHAR_G + CAPITAL_DIFF => int COM_KEY_G; CHAR_Y + CAPITAL_DIFF => int COM_KEY_G_SHARP; CHAR_H + CAPITAL_DIFF => int COM_KEY_A; CHAR_U + CAPITAL_DIFF => int COM_KEY_A_SHARP; CHAR_J + CAPITAL_DIFF => int COM_KEY_H; CHAR_K + CAPITAL_DIFF => int COM_KEY_C1; CHAR_O + CAPITAL_DIFF => int COM_KEY_C1_SHARP; CHAR_L + CAPITAL_DIFF => int COM_KEY_D1; CHAR_Z + CAPITAL_DIFF => int COM_SELECT_SAMPLE_0; CHAR_X + CAPITAL_DIFF => int COM_SELECT_SAMPLE_1; CHAR_C + CAPITAL_DIFF => int COM_SELECT_SAMPLE_2; CHAR_V + CAPITAL_DIFF => int COM_SELECT_SAMPLE_3; CHAR_B + CAPITAL_DIFF => int COM_SELECT_SAMPLE_4; CHAR_N + CAPITAL_DIFF => int COM_SELECT_SAMPLE_5; CHAR_M + CAPITAL_DIFF => int COM_SELECT_SAMPLE_6; "z" => string CHAR_SELECT_SAMPLE_0; "x" => string CHAR_SELECT_SAMPLE_1; "c" => string CHAR_SELECT_SAMPLE_2; "v" => string CHAR_SELECT_SAMPLE_3; "b" => string CHAR_SELECT_SAMPLE_4; "n" => string CHAR_SELECT_SAMPLE_5; "m" => string CHAR_SELECT_SAMPLE_6; CHAR_COMMA => int COM_SELECT_SAMPLE_ADD_EFFECTS; CHAR_SPACE => int COM_SELECT_SAMPLE_ERASE; // Effect attributes 1 => int EFFECT_REVERSE; 2 => int EFFECT_FADE; 4 => int EFFECT_PITCH_STEP; 8 => int EFFECT_OMIT; 16 => int EFFECT_JOIN; 32 => int EFFECT_MINOR_PACE; 64 => int EFFECT_START_POINT; 128 => int EFFECT_FADE_SET; 256 => int EFFECT_PITCH_SET; 512 => int EFFECT_START_POINT_SET; 1024 => int EFFECT_CHANGE_SAMPLE; EFFECT_FADE | EFFECT_PITCH_STEP | EFFECT_MINOR_PACE | EFFECT_START_POINT => int DEFAULT_REPEAT_EFFECTS; // Common attributes 1 => int ATTRIBUTE_LOCKED; 2 => int ATTRIBUTE_MUTE; // Sample attributes -1 => int SAMPLE_UNKNOWN; -2 => int SAMPLE_ERASE; -3 => int SAMPLE_ADD_EFFECTS; // Stuff class SeqSlot { int sample; 1.0 => float amp; 1.0 => float rate; DEFAULT_MINOR_PACE => dur minorPace; int startPoint; int note; -1 => int midiDevice; 0 => int attributes; SeqSlotCommon @ common; } class SeqSlotCommon { -1 => int omit; 0 => int playCount; } // Current command class Command { int state; int slot; int slotFraction; int multiSlotSteps; int multiSlotOffset; int effect; int steps; int space; float pitchTarget; int omit; int minorPace; int startPointChange; int fadeSet; int pitchSet; int startPointSet; int sample; int midiDevice; } Command commandBuffer[nCommands]; commandBuffer[0] @=> Command @ currentCommand; // Global interface state 1 => int lockOn; 0 => int editSequencer; // Stored slots SeqSlot sequences[SEQUENCER_AMOUNT][8*8]; SeqSlotCommon seqSlotCommons[nCommons]; 0 => int nextSeqSlotCommon; class VoiceEvent extends Event { SeqSlot @ seqSlot; DEFAULT_MINOR_PACE => dur minorPace; } VoiceEvent voices[nVoices]; 0 => int nextVoice; class PaceEvent extends Event { 0 => int minor; 0 => int major; } dur majorPace; dur minorPace[10]; PaceEvent paceEvents[10]; // -------------- Sound -------------- // sound class SampleInfo { string filename; int startPos[10]; int rate; dur attack; dur delay; dur release; 0 => int nextSVoice; } SampleInfo sampleInfos[nSamples]; "samples/0002 1-Audio.aif" => sampleInfos[0].filename; 20000 => sampleInfos[0].startPos[0]; 29000 => sampleInfos[0].startPos[1]; 32000 => sampleInfos[0].startPos[2]; 36000 => sampleInfos[0].startPos[3]; 40000 => sampleInfos[0].startPos[4]; 45000 => sampleInfos[0].startPos[5]; 50000 => sampleInfos[0].startPos[6]; 55000 => sampleInfos[0].startPos[7]; 60000 => sampleInfos[0].startPos[8]; 65000 => sampleInfos[0].startPos[9]; 1 => sampleInfos[0].rate; 1::ms => sampleInfos[0].attack; 19::ms => sampleInfos[0].delay; 1::ms => sampleInfos[0].release; "samples/0002 1-Audio.aif" => sampleInfos[1].filename; 30000 => sampleInfos[1].startPos[0]; 1 => sampleInfos[1].rate; 1::ms => sampleInfos[1].attack; 19::ms => sampleInfos[1].delay; 1::ms => sampleInfos[1].release; "samples/0002 1-Audio.aif" => sampleInfos[2].filename; 40000 => sampleInfos[2].startPos[0]; 1 => sampleInfos[2].rate; 1::ms => sampleInfos[2].attack; 19::ms => sampleInfos[2].delay; 1::ms => sampleInfos[2].release; "samples/psch.wav" => sampleInfos[3].filename; 1000 => sampleInfos[3].startPos[0]; 1 => sampleInfos[3].rate; 1::ms => sampleInfos[3].attack; 19::ms => sampleInfos[3].delay; 1::ms => sampleInfos[3].release; "samples/poh.wav" => sampleInfos[4].filename; 1000 => sampleInfos[4].startPos[0]; 1 => sampleInfos[4].rate; 1::ms => sampleInfos[4].attack; 19::ms => sampleInfos[4].delay; 1::ms => sampleInfos[4].release; "samples/snap.wav" => sampleInfos[5].filename; 1000 => sampleInfos[5].startPos[0]; 1 => sampleInfos[5].rate; 1::ms => sampleInfos[5].attack; 19::ms => sampleInfos[5].delay; 1::ms => sampleInfos[5].release; "samples/bam.wav" => sampleInfos[6].filename; 1000 => sampleInfos[6].startPos[0]; 1 => sampleInfos[6].rate; 1::ms => sampleInfos[6].attack; 19::ms => sampleInfos[6].delay; 1::ms => sampleInfos[6].release; SndBuf sndBufs[nSamples][nVoices]; Envelope envs[nSamples][nVoices]; for (0 => int i; i < nSamples; i++) { for (0 => int v; v < nVoices; v++) { sndBufs[i][v] => envs[i][v] => dac; sampleInfos[i].filename => sndBufs[i][v].read; // 0.1 => envs[i][v].gain; } <<< "Sample ", i, ": ", sampleInfos[i].filename >>>; } //------------------------ MIDI -------------------------- MidiOut midiOuts[MIDI_DEVICE_AMOUNT]; for ( 0 => int i; i < MIDI_DEVICE_AMOUNT; i++) { if (midiOuts[i].open(i)) { <<< "Opened MIDI device #", i, ": ", midiOuts[i] >>>; } else { <<< "Could not open MIDI device ", i >>>; } } //------------------------ Code -------------------------- fun void voicePlayer(int voice) { // <<< "Initializing voice #", voice >>>; voices[voice] @=> VoiceEvent @ voiceEvent; MidiMsg midiMsg; while (true) { voiceEvent => now; voiceEvent.seqSlot.sample => int sample; if (voiceEvent.seqSlot.midiDevice > -1) { 0x90 + sample => midiMsg.data1; // Note on + MIDI channel (sample) voiceEvent.seqSlot.note => midiMsg.data2; // Note value (voiceEvent.seqSlot.amp * 127) $ int => midiMsg.data3; // velocity midiOuts[voiceEvent.seqSlot.midiDevice].send(midiMsg); // <<< "Send midi: ", (0x90 + sample), "/", voiceEvent.seqSlot.note, "/", (voiceEvent.seqSlot.amp * 127) >>>; MIDI_DELAY + voiceEvent.minorPace - DEFAULT_MINOR_PACE => now; 0x80 + sample => midiMsg.data1; // Note off + MIDI channel (sample) voiceEvent.seqSlot.note => midiMsg.data2; // Note value 0 => midiMsg.data3; // velocity midiOuts[voiceEvent.seqSlot.midiDevice].send(midiMsg); } else { sampleInfos[sample].nextSVoice++ => int sampleVoice; if (sampleInfos[sample].nextSVoice >= nVoices) { 0 => sampleInfos[sample].nextSVoice; } Math.max(0, voiceEvent.seqSlot.startPoint) $ int => sndBufs[sample][sampleVoice].pos; sampleInfos[sample].rate * voiceEvent.seqSlot.rate => sndBufs[sample][sampleVoice].rate; sampleInfos[sample].attack => envs[sample][sampleVoice].duration; voiceEvent.seqSlot.amp * 0.8 => sndBufs[sample][sampleVoice].gain; envs[sample][sampleVoice].keyOn(); (sampleInfos[sample].attack + sampleInfos[sample].delay) + voiceEvent.minorPace - DEFAULT_MINOR_PACE => now; envs[sample][sampleVoice].keyOff(); } } } for (0 => int i; i < nVoices; i++) { spork ~ voicePlayer(i); } fun void appendCommand(int char) { // <<< "Append: ", char >>>; // ----------------- Check non-slot commands ---------------- if (char == CHAR_ESCAPE) { STATE_NO_COMMAND => currentCommand.state; resetCurrentCommand(); <<< "Reset" >>>; return; } if (currentCommand.state == STATE_NO_COMMAND && char == COM_COMMAND_LOCK_TOGGLE) { 1 - lockOn => lockOn; if (lockOn) { <<< "Lock: on" >>>; } else { <<< "Lock: off" >>>; } return; } if (currentCommand.state == STATE_NO_COMMAND && char == COM_COMMAND_SEQUENCER_SELECT) { STATE_COMMAND_SEQUENCER_SELECT_SET => currentCommand.state; return; } if (currentCommand.state == STATE_COMMAND_SEQUENCER_SELECT_SET) { convertToDigit(char) => int sequencerID; if (sequencerID > -1) { sequencerID => editSequencer; } else { <<< "Bad sequencer ID: ", char >>>; } resetCurrentCommand(); STATE_NO_COMMAND => currentCommand.state; return; } // ----------------- Check slot major/minor ---------------- if (currentCommand.state == STATE_NO_COMMAND) { if (char == COM_COMMAND_MULTISLOT) { STATE_MULTISLOT_SET => currentCommand.state; 0 => currentCommand.multiSlotSteps; 0 => currentCommand.multiSlotOffset; <<< "Multislot" >>>; return; } convertToSlot(char) => currentCommand.slot; if (currentCommand.slot == -1) { <<< "Bad slot: ", char >>>; return; } else { STATE_SLOT_SET => currentCommand.state; <<< "Major slot: ", currentCommand.slot >>>; -1 => currentCommand.slotFraction; return; } } if (currentCommand.state == STATE_SLOT_SET) { convertToSlot(char) => int newSlotFraction; if (newSlotFraction != -1) { newSlotFraction => currentCommand.slotFraction; <<< "Slot fraction: ", currentCommand.slotFraction >>>; STATE_SLOT_FRACTION_SET => currentCommand.state; return; } } if (currentCommand.state == STATE_MULTISLOT_SET) { convertToDigit(char) => int digit; if (digit > -1) { STATE_MULTISLOT_STEPS_SET => currentCommand.state; digit => currentCommand.multiSlotSteps; <<< "Multislot step:", digit >>>; return; } } if (currentCommand.state == STATE_MULTISLOT_STEPS_SET) { convertToDigit(char) => int digit; if (digit > -1) { STATE_MULTISLOT_OFFSET_SET => currentCommand.state; digit => currentCommand.multiSlotOffset; <<< "Multislot offset:", digit >>>; return; } } // ----------------- Check arguments ---------------------- if (currentCommand.state == STATE_EFFECT_PITCH_STEP_SET) { convertToDigit(char) => int digit; if (digit > -1) { if (digit == 0) 1.0/16.0 => currentCommand.pitchTarget; else if (digit == 1) 1.0/8.0 => currentCommand.pitchTarget; else if (digit == 2) 1.0/4.0 => currentCommand.pitchTarget; else if (digit == 3) 1.0/2.0 => currentCommand.pitchTarget; else if (digit == 4) 0.75 => currentCommand.pitchTarget; else if (digit == 5) 1.25 => currentCommand.pitchTarget; else if (digit == 6) 1.5 => currentCommand.pitchTarget; else if (digit == 7) 2.0 => currentCommand.pitchTarget; else if (digit == 8) 4.0 => currentCommand.pitchTarget; else if (digit == 9) 8.0 => currentCommand.pitchTarget; <<< "Pitch step to ", currentCommand.pitchTarget >>>; STATE_EFFECT_SET => currentCommand.state; return; } else { // Default: pitch fall to 1/2 0.5 => currentCommand.pitchTarget; <<< "Pitch step to 0.5 (s)" >>>; STATE_EFFECT_SET => currentCommand.state; } } if (currentCommand.state == STATE_EFFECT_OMIT_SET) { convertToDigit(char) => int digit; if (digit > 0) { <<< "Omit ", digit >>>; digit => currentCommand.omit; STATE_EFFECT_SET => currentCommand.state; return; } else { // Default: omit 6 slots 6 => currentCommand.omit; STATE_EFFECT_SET => currentCommand.state; } } if (currentCommand.state == STATE_EFFECT_MINOR_PACE_SET) { convertToNote(char) => currentCommand.minorPace; if (currentCommand.minorPace== -1) { convertToDigit(char) => currentCommand.minorPace; <<< "Minor pace: ", currentCommand.minorPace >>>; STATE_EFFECT_SET => currentCommand.state; } else { <<< "Minor pace midi note: ", currentCommand.minorPace >>>; STATE_EFFECT_MINOR_PACE_KEY_SET => currentCommand.state; } if (currentCommand .minorPace== -1) { // default: minor pace 4 4 => currentCommand.minorPace; } else { return; } } if (currentCommand.state == STATE_EFFECT_MINOR_PACE_KEY_SET) { convertToDigit(char) => int minorPaceTranspose; if (minorPaceTranspose == -1) { // Default: down 1 octave 12 -=> currentCommand.minorPace; STATE_EFFECT_SET => currentCommand.state; } else { <<< "Minor pace transpose: ", (minorPaceTranspose - 5) >>>; (minorPaceTranspose - 5) * 12 +=> currentCommand.minorPace; while (currentCommand.minorPace <= 9) { 12 +=> currentCommand.minorPace; } STATE_EFFECT_SET => currentCommand.state; return; } } if (currentCommand.state == STATE_EFFECT_START_POINT_CHANGE_SET) { convertToDigit(char) => currentCommand.startPointChange; if (currentCommand.startPointChange != -1) { <<< "Start point glide: ", currentCommand.startPointChange >>>; STATE_EFFECT_SET => currentCommand.state; return; } else { // Default: setting 7 7 => currentCommand.startPointChange; STATE_EFFECT_SET => currentCommand.state; } } if (currentCommand.state == STATE_EFFECT_FADE_SET_SET) { convertToDigit(char) => currentCommand.fadeSet; if (currentCommand.fadeSet != -1) { <<< "Fade set: ", currentCommand.fadeSet >>>; STATE_EFFECT_SET => currentCommand.state; return; } else { // Default: setting 5 (half volume) 5 => currentCommand.fadeSet; STATE_EFFECT_SET => currentCommand.state; } } if (currentCommand.state == STATE_EFFECT_PITCH_SET_SET) { convertToNote(char) => currentCommand.pitchSet; if (currentCommand.pitchSet == -1) { convertToDigit(char) => currentCommand.pitchSet; STATE_EFFECT_SET => currentCommand.state; if (currentCommand.pitchSet == -1) { <<< "Pitch set: ", currentCommand.pitchSet >>>; } } else { <<< "Sample rate midi note: ", currentCommand.pitchSet >>>; STATE_EFFECT_PITCH_KEY_SET => currentCommand.state; } if (currentCommand.pitchSet > -1) { return; } } if (currentCommand.state == STATE_EFFECT_PITCH_KEY_SET) { convertToDigit(char) => int pitchSetTranspose; if (pitchSetTranspose == -1) { // Default: down 1 octave 12 -=> currentCommand.pitchSet; STATE_EFFECT_SET => currentCommand.state; } else { <<< "Pitch transpose: ", (pitchSetTranspose - 5) >>>; (pitchSetTranspose - 5) * 12 +=> currentCommand.pitchSet; while (currentCommand.pitchSet <= 9) { 12 +=> currentCommand.pitchSet; } STATE_EFFECT_SET => currentCommand.state; return; } } if (currentCommand.state == STATE_EFFECT_START_POINT_SET_SET) { convertToDigit(char) => currentCommand.startPointSet; if (currentCommand.startPointSet != -1) { <<< "Start point set: ", currentCommand.startPointSet >>>; STATE_EFFECT_SET => currentCommand.state; return; } else { // Default: setting 8 (slightly ahead) 8 => currentCommand.startPointSet; STATE_EFFECT_SET => currentCommand.state; } } if (currentCommand.state == STATE_EFFECT_MIDI_SET) { convertToDigit(char) => currentCommand.midiDevice; if (currentCommand.midiDevice != -1) { <<< "MIDI device: ", currentCommand.midiDevice >>>; STATE_EFFECT_SET => currentCommand.state; return; } else { // Default: setting 0 (first device) 0 => currentCommand.midiDevice; STATE_EFFECT_SET => currentCommand.state; } } // --------------------- Check effects ------------------------- if (currentCommand.state != STATE_NO_COMMAND) { if (char == COM_EFFECT_REVERSE) { EFFECT_REVERSE |=> currentCommand.effect; STATE_EFFECT_SET => currentCommand.state; <<< "Reverse" >>>; return; } else if (char == COM_EFFECT_FADE) { EFFECT_FADE |=> currentCommand.effect; STATE_EFFECT_SET => currentCommand.state; <<< "Fade" >>>; return; } else if (char == COM_EFFECT_PITCH_STEP) { EFFECT_PITCH_STEP |=> currentCommand.effect; STATE_EFFECT_PITCH_STEP_SET => currentCommand.state; <<< "Pitch step..." >>>; return; } else if (char == COM_EFFECT_OMIT) { EFFECT_OMIT |=> currentCommand.effect; STATE_EFFECT_OMIT_SET => currentCommand.state; <<< "Omit..." >>>; return; } else if (char == COM_EFFECT_JOIN) { EFFECT_JOIN |=> currentCommand.effect; STATE_EFFECT_SET => currentCommand.state; <<< "Join" >>>; return; } else if (char == COM_EFFECT_MINOR_PACE) { EFFECT_MINOR_PACE |=> currentCommand.effect; STATE_EFFECT_MINOR_PACE_SET => currentCommand.state; <<< "Minor pace..." >>>; return; } else if (char == COM_EFFECT_START_POINT) { EFFECT_START_POINT |=> currentCommand.effect; STATE_EFFECT_START_POINT_CHANGE_SET => currentCommand.state; <<< "Start point step..." >>>; return; } else if (char == COM_EFFECT_FADE_SET) { EFFECT_FADE_SET |=> currentCommand.effect; STATE_EFFECT_FADE_SET_SET => currentCommand.state; <<< "Fade set..." >>>; return; } else if (char == COM_EFFECT_PITCH_SET) { EFFECT_PITCH_SET |=> currentCommand.effect; STATE_EFFECT_PITCH_SET_SET => currentCommand.state; <<< "Pitch set..." >>>; return; } else if (char == COM_EFFECT_START_POINT_SET) { EFFECT_START_POINT_SET |=> currentCommand.effect; STATE_EFFECT_START_POINT_SET_SET => currentCommand.state; <<< "Start point set..." >>>; return; } else if (char == COM_EFFECT_CHANGE_SAMPLE) { EFFECT_CHANGE_SAMPLE |=> currentCommand.effect; STATE_EFFECT_SET => currentCommand.state; <<< "Change sample..." >>>; return; } else if (char == COM_EFFECT_MIDI) { STATE_EFFECT_MIDI_SET => currentCommand.state; <<< "Midi note..." >>>; return; } } // ----------------- Check repeat ------------------------- if (currentCommand.state != STATE_NO_COMMAND) { convertToDigit(char) => int digit; if (digit >= 0) { if (currentCommand.state == STATE_EFFECT_STEPS_SET) { digit => currentCommand.space; STATE_EFFECT_SET => currentCommand.state; <<< "Space: ", currentCommand.space >>>; return; } else { if (digit == 0) { 1 => digit; } digit => currentCommand.steps; STATE_EFFECT_STEPS_SET => currentCommand.state; <<< "Steps: ", currentCommand.steps >>>; return; } } } // ---------------------- Check sample ------------------------- if (currentCommand.state != STATE_NO_COMMAND) { convertToSample(char) => int sample; if (sample == SAMPLE_UNKNOWN) { <<< "Bad sample: ", char >>>; } else { sample => currentCommand.sample; STATE_NO_COMMAND => currentCommand.state; executeCommand(currentCommand); resetCurrentCommand(); // printSequence(); } } else { <<< "No command...?" >>>; } } fun void executeCommand(Command @ command) { if (command.multiSlotSteps == -1) { executeOneCommand(command); } else { for (command.multiSlotOffset => int i; i < sequenceSize; command.multiSlotSteps + 1 +=> i) { i/8 => command.slot; i % 8 => command.slotFraction; executeOneCommand(command); } } } fun void executeOneCommand(Command @ command) { command.slot*8 => int targetSlot; if (command.slotFraction != -1) command.slotFraction +=> targetSlot; // <<< "Slot ", targetSlot, " <- sample ", command.sample >>>; if ((command.effect & DEFAULT_REPEAT_EFFECTS) > 0 && command.steps == -1) { // Default: 8 steps, 0 space 7 => command.steps; 0 => command.space; } NULL @=> SeqSlotCommon @ common; if ((command.effect & EFFECT_JOIN) > 0) { if (nextSeqSlotCommon == 0) { seqSlotCommons[nCommons-1] @=> common; } else { seqSlotCommons[nextSeqSlotCommon-1] @=> common; } } if (command.omit != -1) { seqSlotCommons[nextSeqSlotCommon++] @=> common; if (nextSeqSlotCommon >= nCommons) { 0 => nextSeqSlotCommon; } command.omit => common.omit; 0 => common.playCount; } DEFAULT_MINOR_PACE => dur minorPace; if (command.minorPace != -1) { if (command.minorPace == -1) { DEFAULT_MINOR_PACE / 1.5 => minorPace; } else if (command.minorPace == 0) { DEFAULT_MINOR_PACE / 8 => minorPace; } else if (command.minorPace == 1) { DEFAULT_MINOR_PACE / 6 => minorPace; } else if (command.minorPace == 2) { DEFAULT_MINOR_PACE / 4 => minorPace; } else if (command.minorPace == 3) { DEFAULT_MINOR_PACE / 2 => minorPace; } else if (command.minorPace == 4) { DEFAULT_MINOR_PACE / 1.5 => minorPace; } else if (command.minorPace == 5) { DEFAULT_MINOR_PACE * 1.5 => minorPace; } else if (command.minorPace == 6) { DEFAULT_MINOR_PACE * 2 => minorPace; } else if (command.minorPace == 7) { DEFAULT_MINOR_PACE * 4 => minorPace; } else if (command.minorPace == 8) { DEFAULT_MINOR_PACE * 6 => minorPace; } else if (command.minorPace == 9) { DEFAULT_MINOR_PACE * 8 => minorPace; } else { 1::second/Std.mtof(command.minorPace) => minorPace; } } (minorPace / 1::samp) $ int => int minorLength; int startPoint; if (command.sample > -1) { sampleInfos[command.sample].startPos[0] => startPoint; if ((command.effect & EFFECT_START_POINT_SET) > 0) { sampleInfos[command.sample].startPos[command.startPointSet] => startPoint; } } else { 0 => startPoint; } command.sample => int sample; if (command.sample == SAMPLE_ERASE) { -1 => sample; } 0 => int startPointInc; if (command.startPointChange == 0) { -minorLength*8 => startPointInc; } else if (command.startPointChange == 1) { -minorLength*4 => startPointInc; } else if (command.startPointChange == 2) { -minorLength*2 => startPointInc; } else if (command.startPointChange == 3) { -minorLength/2 => startPointInc; } else if (command.startPointChange == 4) { -minorLength/4 => startPointInc; } else if (command.startPointChange == 5) { minorLength/4 => startPointInc; } else if (command.startPointChange == 6) { minorLength/2 => startPointInc; } else if (command.startPointChange == 7) { minorLength*2 => startPointInc; } else if (command.startPointChange == 8) { minorLength*4 => startPointInc; } else if (command.startPointChange == 9) { minorLength*8 => startPointInc; } 1.0 => float targetAmp; if ((command.effect & EFFECT_FADE_SET) > 0) { (command.fadeSet + 1.0)/10.0 => targetAmp; } 1.0 => float refRate; command.pitchSet => int midiNote; if (command.midiDevice >= 0 && midiNote == -1) { DEFAULT_MIDI_NOTE => midiNote; } if ((command.effect & EFFECT_PITCH_SET) > 0) { if (command.pitchSet == -1) { 0.75 => refRate; } else if (command.pitchSet == 0) { 1.0/8.0 => refRate; DEFAULT_MIDI_NOTE - 4*12 => midiNote; } else if (command.pitchSet == 1) { 1.0/4.0 => refRate; DEFAULT_MIDI_NOTE - 3*12 => midiNote; } else if (command.pitchSet == 2) { 1.0/3.0 => refRate; DEFAULT_MIDI_NOTE - 2*12 => midiNote; } else if (command.pitchSet == 3) { 1.0/2.0 => refRate; DEFAULT_MIDI_NOTE - 12 => midiNote; } else if (command.pitchSet == 4) { 0.75 => refRate; DEFAULT_MIDI_NOTE - 5 => midiNote; } else if (command.pitchSet == 5) { 1.25 => refRate; DEFAULT_MIDI_NOTE + 12 => midiNote; } else if (command.pitchSet == 6) { 2.0 => refRate; DEFAULT_MIDI_NOTE + 2*12 => midiNote; } else if (command.pitchSet == 7) { 3.0 => refRate; DEFAULT_MIDI_NOTE + 3*12 => midiNote; } else if (command.pitchSet == 8) { 4.0 => refRate; DEFAULT_MIDI_NOTE + 4*12 => midiNote; } else if (command.pitchSet == 9) { 8.0 => refRate; DEFAULT_MIDI_NOTE - 4*12 => midiNote; } else { Std.mtof(command.pitchSet)/REFERENCE_FREQUENCY => refRate; // <<< "Pitch frac: ", refRate >>>; } } <<< "MIDI note=", midiNote >>>; 0 => int attributes; if (lockOn) { ATTRIBUTE_LOCKED |=> attributes; } if (command.steps != -1) { // <<< "Repeat: ", command.steps, " space=", command.space >>>; for (0 => int i; i <= command.steps; i++) { int iSlot; if ((command.effect & EFFECT_REVERSE) > 0) { (targetSlot - i*(command.space+1) + sequenceSize) % sequenceSize => iSlot; } else { (targetSlot + i*(command.space+1)) % sequenceSize => iSlot; } if (sample == SAMPLE_ERASE) { ATTRIBUTE_MUTE |=> sequences[editSequencer][iSlot].attributes; } else { if (sample == SAMPLE_ADD_EFFECTS || (command.effect & EFFECT_CHANGE_SAMPLE) > 0) { if ((command.effect & EFFECT_CHANGE_SAMPLE) > 0) { startPoint => sequences[editSequencer][iSlot].startPoint; sample => sequences[editSequencer][iSlot].sample; } if ((command.effect & EFFECT_START_POINT) > 0) { sampleInfos[sequences[editSequencer][iSlot].sample].startPos[0] + startPoint => sequences[editSequencer][iSlot].startPoint; } if ((command.effect & EFFECT_START_POINT_SET) > 0) { sampleInfos[sequences[editSequencer][iSlot].sample].startPos[command.startPointSet] => sequences[editSequencer][iSlot].startPoint; } if ((command.effect & (EFFECT_FADE | EFFECT_FADE_SET)) > 0) { targetAmp => sequences[editSequencer][iSlot].amp; } if ((command.effect & (EFFECT_MINOR_PACE)) > 0) { minorPace => sequences[editSequencer][iSlot].minorPace; } if ((command.effect & (EFFECT_PITCH_STEP | EFFECT_PITCH_SET)) > 0) { refRate => sequences[editSequencer][iSlot].rate; } if (command.midiDevice >= 0) { command.midiDevice => sequences[editSequencer][iSlot].midiDevice; midiNote => sequences[editSequencer][iSlot].note; } attributes |=> sequences[editSequencer][iSlot].attributes; if (common != NULL) { common @=> sequences[editSequencer][iSlot].common; } } else { sample => sequences[editSequencer][iSlot].sample; startPoint => sequences[editSequencer][iSlot].startPoint; minorPace => sequences[editSequencer][iSlot].minorPace; targetAmp => sequences[editSequencer][iSlot].amp; refRate => sequences[editSequencer][iSlot].rate; attributes => sequences[editSequencer][iSlot].attributes; command.midiDevice => sequences[editSequencer][iSlot].midiDevice; midiNote => sequences[editSequencer][iSlot].note; common @=> sequences[editSequencer][iSlot].common; } startPointInc +=> startPoint; if ((command.effect & EFFECT_FADE) > 0) { ((command.steps + 1 - i) $ float)/((command.steps + 1) $ float) *=> sequences[editSequencer][iSlot].amp; } if ((command.effect & EFFECT_PITCH_STEP) > 0) { 1.0 + (command.pitchTarget - 1.0)*(i $ float)/(command.steps $ float) *=> sequences[editSequencer][iSlot].rate; } } } } else { if (sample == SAMPLE_ERASE) { ATTRIBUTE_MUTE |=> sequences[editSequencer][targetSlot].attributes; } else { if (sample == SAMPLE_ADD_EFFECTS || (command.effect & EFFECT_CHANGE_SAMPLE) > 0) { if ((command.effect & EFFECT_CHANGE_SAMPLE) > 0) { sample => sequences[editSequencer][targetSlot].sample; startPoint => sequences[editSequencer][targetSlot].startPoint; } sampleInfos[sequences[editSequencer][targetSlot].sample].startPos[0] + startPoint => sequences[editSequencer][targetSlot].startPoint; if ((command.effect & EFFECT_START_POINT_SET) > 0) { sampleInfos[sequences[editSequencer][targetSlot].sample].startPos[command.startPointSet] => sequences[editSequencer][targetSlot].startPoint; } if ((command.effect & (EFFECT_FADE | EFFECT_FADE_SET)) > 0) { targetAmp => sequences[editSequencer][targetSlot].amp; } if ((command.effect & (EFFECT_MINOR_PACE)) > 0) { minorPace => sequences[editSequencer][targetSlot].minorPace; } if ((command.effect & (EFFECT_PITCH_STEP | EFFECT_PITCH_SET)) > 0) { refRate => sequences[editSequencer][targetSlot].rate; } if (command.midiDevice >= 0) { command.midiDevice => sequences[editSequencer][targetSlot].midiDevice; midiNote => sequences[editSequencer][targetSlot].note; } attributes |=> sequences[editSequencer][targetSlot].attributes; if (common != NULL) { common @=> sequences[editSequencer][targetSlot].common; } } else { sample => sequences[editSequencer][targetSlot].sample; minorPace => sequences[editSequencer][targetSlot].minorPace; startPoint => sequences[editSequencer][targetSlot].startPoint; targetAmp => sequences[editSequencer][targetSlot].amp; refRate => sequences[editSequencer][targetSlot].rate; attributes => sequences[editSequencer][targetSlot].attributes; command.midiDevice => sequences[editSequencer][targetSlot].midiDevice; midiNote => sequences[editSequencer][targetSlot].note; common @=> sequences[editSequencer][targetSlot].common; } } } } fun int convertToSlot(int char) { if (char > CHAR_0 && char <= CHAR_0 + 8) { return (char - CHAR_0 - 1); } else { return -1; } } fun int convertToDigit(int char) { if (char < CHAR_0 || char > CHAR_0 + 9) { return -1; } else { return (char - CHAR_0); } } fun int convertToSample(int char) { // <<< "Convert: ", char >>>; if (char == COM_SELECT_SAMPLE_0) { return 0; } else if (char == COM_SELECT_SAMPLE_1) { return 1; } else if (char == COM_SELECT_SAMPLE_2) { return 2; } else if (char == COM_SELECT_SAMPLE_3) { return 3; } else if (char == COM_SELECT_SAMPLE_4) { return 4; } else if (char == COM_SELECT_SAMPLE_5) { return 5; } else if (char == COM_SELECT_SAMPLE_6) { return 6; } else if (char == COM_SELECT_SAMPLE_ADD_EFFECTS) { return SAMPLE_ADD_EFFECTS; } else if (char == COM_SELECT_SAMPLE_ERASE) { return SAMPLE_ERASE; } else { return SAMPLE_UNKNOWN; } } fun int convertToNote(int char) { if (char == COM_KEY_C) { return 60; } else if (char == COM_KEY_C_SHARP) { return 61; } else if (char == COM_KEY_D) { return 62; } else if (char == COM_KEY_D_SHARP) { return 63; } else if (char == COM_KEY_E) { return 64; } else if (char == COM_KEY_F) { return 65; } else if (char == COM_KEY_F_SHARP) { return 66; } else if (char == COM_KEY_G) { return 67; } else if (char == COM_KEY_G_SHARP) { return 68; } else if (char == COM_KEY_A) { return 69; } else if (char == COM_KEY_A_SHARP) { return 70; } else if (char == COM_KEY_H) { return 71; } else if (char == COM_KEY_C1) { return 72; } else if (char == COM_KEY_C1_SHARP) { return 73; } else if (char == COM_KEY_D1) { return 74; } else { return -1; } } fun void resetCurrentCommand() { STATE_NO_COMMAND => currentCommand.state; -1 => currentCommand.multiSlotSteps; -1 => currentCommand.multiSlotOffset; 0 => currentCommand.effect; -1 => currentCommand.steps; 0 => currentCommand.space; -1 => currentCommand.omit; -1 => currentCommand.minorPace; -1 => currentCommand.startPointChange; -1 => currentCommand.fadeSet; -1 => currentCommand.pitchSet; -1 => currentCommand.startPointSet; -1 => currentCommand.midiDevice; printSequence(); } fun void resetSequences() { for (0 => int v; v < SEQUENCER_AMOUNT; v++) { for (0 => int s; s < 8*8; s++) { -1 => sequences[v][s].sample; } DEFAULT_MINOR_PACE => minorPace[v]; } DEFAULT_MINOR_PACE*8 => majorPace; } fun void sequencePlayer(int seqID) { // <<< "Initializing sequencer" >>>; 0 => int majorPosition; 0 => int minorPosition; 0 => int sequencePosition; while (true) { majorPosition*8 + minorPosition => sequencePosition; paceEvents[seqID] => now; if (sequences[seqID][sequencePosition].common == NULL || sequences[seqID][sequencePosition].common.omit == -1 || (sequences[seqID][sequencePosition].common.playCount % (sequences[seqID][sequencePosition].common.omit+1)) == 0) { if ((sequences[seqID][sequencePosition].attributes & ATTRIBUTE_MUTE) > 0) { -1 => sequences[seqID][sequencePosition].sample; } sequences[seqID][sequencePosition].sample => int currentSample; sequences[seqID][sequencePosition].minorPace => minorPace[seqID]; if (currentSample != -1) { // <<< "Seq play start" >>>; sequences[seqID][sequencePosition] @=> voices[nextVoice].seqSlot; voices[nextVoice].signal(); if (++nextVoice >= nVoices) { 0 => nextVoice; } // <<< "Seq play end" >>>; if ((sequences[seqID][sequencePosition].attributes & ATTRIBUTE_LOCKED) == 0) { ATTRIBUTE_MUTE |=> sequences[seqID][sequencePosition].attributes; } } } if (sequences[seqID][sequencePosition].common != NULL) { sequences[seqID][sequencePosition].common.playCount++; } if (paceEvents[seqID].major == 1) { // Major click 0 => minorPosition; majorPosition++; if (majorPosition >= 8) { 0 => majorPosition; } } else if (paceEvents[seqID].minor == 1) { // Minor click minorPosition++; if (minorPosition >= 8) { 0 => minorPosition; } } // Reset event after it has been read 0 => paceEvents[seqID].major; 0 => paceEvents[seqID].minor; sequencePosition++; if (sequencePosition >= 8*8) { 0 => sequencePosition; } } } fun void resetSlot(SeqSlot @ slot) { <<< "Reset" >>>; -1 => slot.sample; // 1.0 => slot.amp; // 1.0 => slot.rate; // DEFAULT_MINOR_PACE => slot.minorPace; // 0 => slot.startPoint; // 0 => slot.attributes; // NULL => slot.common; } fun void printSequence() { "" => string str; for (0 => int i; i < sequenceSize; i++) { if (sequences[editSequencer][i].sample == 0) { str + CHAR_SELECT_SAMPLE_0 => str; } else if (sequences[editSequencer][i].sample == 1) { str + CHAR_SELECT_SAMPLE_1 => str; } else if (sequences[editSequencer][i].sample == 2) { str + CHAR_SELECT_SAMPLE_2 => str; } else if (sequences[editSequencer][i].sample == 3) { str + CHAR_SELECT_SAMPLE_3 => str; } else if (sequences[editSequencer][i].sample == 4) { str + CHAR_SELECT_SAMPLE_4 => str; } else if (sequences[editSequencer][i].sample == 5) { str + CHAR_SELECT_SAMPLE_5 => str; } else if (sequences[editSequencer][i].sample == 6) { str + CHAR_SELECT_SAMPLE_6 => str; } else { str + "." => str; } } <<< "Seq #", editSequencer, ": {", str, "}" >>>; } class MajorPaceMaker { now => time lastMajorPace; now => time nextMajorPace; fun void majorPaceMaker() { while (true) { for (0 => int seqID; seqID < SEQUENCER_AMOUNT; seqID++) { 1 => paceEvents[seqID].major; paceEvents[seqID].signal(); } now + majorPace => nextMajorPace; nextMajorPace => now; nextMajorPace => lastMajorPace; } } } MajorPaceMaker majorPaceMaker; //s[SEQUENCER_AMOUNT]; fun void minorPaceMaker(int seqID) { while (true) { if (now - majorPaceMaker.lastMajorPace < minorPace[seqID] ) { now - majorPaceMaker.lastMajorPace + minorPace[seqID] => now; } else { 1 => paceEvents[seqID].minor; paceEvents[seqID].signal(); minorPace[seqID] => now; } } } resetSequences(); resetCurrentCommand(); for (0 => int s; s < SEQUENCER_AMOUNT; s++) { spork ~ sequencePlayer(s); spork ~ minorPaceMaker(s); } spork ~ majorPaceMaker.majorPaceMaker(); //s[s].majorPaceMaker(); while (true) { kb => now; appendCommand(kb.getchar()); // <<< "KB Hit: ", kb.getchar()>>>; }