//****************************************************************************** //Chordial by Unicity // //A recreation of the Chord eurorack module from Qu-Bit Electronix. // //Version 1.0.1 - February 2020 //****************************************************************************** @OnLoad if Unassigned basePitch //pitch values basePitch = 60 refPitch = 60 pitchOffset = 0 //array indices root = 0 third = 1 fifth = 2 seventh = 3 //chord shapes baseChord = [60, 64, 67, 71] chord = [60, 64, 67, 71] currChord = [0,0,0,0] //transform states voicing = 0 inversion = 0 quality = 0 //quality offsets q_third = 4 q_fifth = 7 q_seventh = 11 //xyvalues xvalue = 0 yvalue = 0 //channels channels = [0, 0, 0, 0] badChan = false //drone flag drone = no noteOn = no //voice toggles toggles = [1, 1, 1, 1] //padMode padMode = 1 //velocities (may not be needed) vels = [100, 100, 100, 100] //strum parameters vec = [0.1, 0.3, 0.5] strumOffsets = [0, 0, 0, 0] scalar = 0 strum = no endif Call @SetupGui SetTimerInterval 1000 @End //****************************************************************************** // MIDI //****************************************************************************** //********************************************* //OnMidiNoteOn Function //********************************************* @OnMidiNoteOn noteOn = true basePitch = MIDINote for i = 0 to 3 vels[i] = MIDIVelocity endfor Call @BuildChord Call @TransformChord Call @PlayChord @End //********************************************* //OnMidiNoteOff Function //********************************************* @OnMidiNoteOff noteOn = false if NOT drone AND padMode = 1 for i = 0 to 3 SendMIDINoteOff channels[i], currChord[i], 0, (strumOffsets[i] + 10) endfor currChord = [0, 0, 0, 0] else endif @End //********************************************* //BuildChord function // //This function builds the base chord that is //used as a reference in chord transformations. //The base chord is not neant to be sent out to //other devices downstream. //********************************************* @BuildChord Call @SetQuality baseChord[root] = basePitch baseChord[third] = basePitch + q_third baseChord[fifth] = basePitch + q_fifth baseChord[seventh] = basePitch + q_seventh CopyArray baseChord, chord, 4 @End //********************************************* //TransformChord function // //This function applies transformations such as //voicing, inversion, and quality to the chord //array. It is called either after (re)creating //the baseChord array, or as the result of a //knobchange event from the voicing, inversion, //or quality controls. //********************************************* @TransformChord CopyArray baseChord, chord, 4 tempOffsets = [0, 0, 0, 0] if inversion = 1 chord[root] = chord[root] + 12 elseif inversion = 2 chord[root] = chord[root] + 12 chord[third] = chord[third] + 12 elseif inversion = 3 chord[root] = chord[root] + 12 chord[third] = chord[third] + 12 chord[fifth] = chord[fifth] + 12 endif if voicing = 1 chord[fifth] = chord[fifth] - 12 elseif voicing = 2 chord[third] = chord[third] - 12 elseif voicing = 3 chord[root] = chord[root] -12 chord[fifth] = chord[fifth] - 12 chord[seventh] = chord[seventh] + 12 endif Call @ChordSort @End //********************************************* //PlayChord function // //This function is called whenever a chord is //played and/or changed. If a new chord is to be //played, each element of the chord array is sent //downstream. If a chord has been transformed, //only the new notes in the transformed chord //will be sent downstream //********************************************* @PlayChord if drone for i = 0 to 3 if (currChord[i] <> chord[i]) AND toggles[i] if padMode = 1 SendMIDINoteOff channels[i], currChord[i], 0, strumOffsets[i] SendMIDINoteOn channels[i], chord[i], vels[i], strumOffsets[i] else SendMIDINoteOff channels[i], currChord[i], 0 SendMIDINoteOn channels[i], chord[i], vels[i] endif endif endfor CopyArray chord, currChord, 4 else for i = 0 to 3 if currChord[i] <> chord[i] if padMode = 1 SendMIDINoteOff channels[i], currChord[i], 0, strumOffsets[i] else SendMIDINoteOff channels[i], currChord[i], 0 endif endif endfor for i = 0 to 3 if toggles[i] AND (currChord[i] <> chord[i]) if padMode = 1 SendMIDINoteOn channels[i], chord[i], vels[i], strumOffsets[i] else SendMIDINoteOn channels[i], chord[i], vels[i] endif endif endfor CopyArray chord, currChord, 4 endif Call @RecalcVec @End //********************************************* //SendChord function // //This pattern happens a lot. //********************************************* @SendChord Call @BuildChord Call @TransformChord if drone OR noteOn OR padMode = 0 Call @PlayChord endif @End //********************************************* //KillEmAll // //Don't Panic. All notes off. //********************************************* @KillEmAll for i = 0 to 3 SendMIDINoteOff channels[i], chord[i], 0 endfor @End //****************************************************************************** // GUI //****************************************************************************** //********************************************* //OnKnobChange //********************************************* @OnKnobChange knob = LastKnob value = GetKnobValue LastKnob //Pad Mode if knob = 0 value = Round(TranslateScale (GetKnobValue knob), 0, 127, 0, 1) if padMode <> value Call @ChangePadMode if padMode = 0 LabelKnobs {Pad Mode: Triggers} LabelPads {Voice Triggers} else LabelKnobs {Pad Mode: Toggles} LabelPads {Voice Toggles} endif endif //Voicing elseif knob = 1 xvalue = Round(GetKnobValue knob) SetXYValues xvalue, yvalue value = Round(TranslateScale (GetKnobValue knob), 0, 127, 0, 3) if voicing <> value voicing = value if voicing = 0 LabelKnobs {Voicing: Closed} elseif voicing = 1 LabelKnobs {Voicing: Drop 2} elseif voicing = 2 LabelKnobs {Voicing: Drop 3} elseif voicing = 3 LabelKnobs {Voicing: Spread} endif Call @SendChord endif //Inversion elseif knob = 2 yvalue = Round(GetKnobValue knob) SetXYValues xvalue, yvalue value = Round(TranslateScale (GetKnobValue knob), 0, 127, 0, 3) if inversion <> value inversion = value if inversion = 0 LabelKnobs{Inversion: None} elseif inversion = 1 LabelKnobs{Inversion: 1st Inversion} elseif inversion = 2 LabelKnobs{Inversion: 2nd Inversion} elseif inversion = 3 LabelKnobs{Inversion: 3rd Inversion} endif Call @SendChord endif //Quality elseif knob = 3 value = Round(TranslateScale (GetKnobValue knob), 0, 127, 0, 3) if quality <> value quality = value if quality = 0 LabelKnobs {Quality: Major} LabelPad 1, {3rd} LabelPad 3, {7th} elseif quality = 1 LabelKnobs {Quality: Minor} LabelPad 1, {Minor 3rd} LabelPad 3, {Minor 7th} elseif quality = 2 LabelKnobs {Quality: Sus2} LabelPad 1, {2nd} LabelPad 3, {Minor 7th} elseif quality = 3 LabelKnobs {Quality: Sus4} LabelPad 1, {4th} LabelPad 3, {Minor 7th} endif Call @SendChord endif //Drone/MIDI elseif knob = 4 value = Round(TranslateScale (GetKnobValue knob), 0, 127, 0, 1) if drone <> value drone = value if drone = 0 LabelKnobs {Playback Mode: MIDI} Call @KillEmAll currChord = [0, 0, 0, 0] else LabelKnobs{Playback Mode: Drone} currChord = [0, 0, 0, 0] Call @PlayChord endif endif //Root MIDI Channel elseif knob = 5 value = GetKnobValue knob value = Round(TranslateScale value, 0, 127, 0, 15) if (channels[0] <> value) if (noteOn OR drone) badChan = true LabelKnobs {MIDI Channels cannot be changed while a chord is on!} else channels[0] = value LabelKnobs {Root MIDI Channel: }, (channels[0] + 1) endif endif //Third MIDI Channel elseif knob = 6 value = GetKnobValue knob value = Round(TranslateScale value, 0, 127, 0, 15) if channels[1] <> value if (noteOn OR drone) badChan = true LabelKnobs {MIDI Channels cannot be changed while a chord is on!} else channels[1] = value LabelKnobs {Root MIDI Channel: }, (channels[1] + 1) endif endif //Fifth MIDI Channel elseif knob = 7 value = GetKnobValue knob value = Round(TranslateScale value, 0, 127, 0, 15) if channels[2] <> value if (noteOn OR drone) badChan = true LabelKnobs {MIDI Channels cannot be changed while a chord is on!} else channels[2] = value LabelKnobs {Root MIDI Channel: }, (channels[2] + 1) endif endif //Seventh MIDI Channel elseif knob = 8 value = GetKnobValue knob value = Round(TranslateScale value, 0, 127, 0, 15) if channels[3] <> value if (noteOn OR drone) badChan = true LabelKnobs {MIDI Channels cannot be changed while a chord is on!} else channels[3] = value LabelKnobs {Root MIDI Channel: }, (channels[3] + 1) endif endif //Strum elseif knob = 9 value = Round(GetKnobValue knob) if value > 0 strum = yes else strum = no endif if scalar <> value scalar = value for i = 1 to 3 strumOffsets[i] = (vec[i-1] * scalar) * 10 endfor Call @RecalcVec endif LabelKnobs {Strum length: }, (Round strumOffsets[3]), { ms} endif StartTimer @End //********************************************* //ChangePadMode Function //********************************************* @ChangePadMode if padMode = 1 //1 = toggle mode padMode = 0 for i = 0 to 3 toggles[i] = 0 SendMIDINoteOff channels[i], currChord[i], 0, strumOffsets[i] LatchPad i, 0 endfor else padMode = 1 for i = 0 to 3 toggles[i] = 1 LatchPad i, 1 if drone SendMIDINoteOn channels[i], currChord[i], vels[i], strumOffsets[i] else currChord = [0, 0, 0, 0] endif endfor endif @End //********************************************* //OnPadDown Function //********************************************* @OnPadDown state = PadState LastPad //toggles[LastPad] = NOT toggles[LastPad] LatchPad LastPad, NOT state if padMode = 0 toggles[LastPad] = 1 Call @BuildChord Call @TransformChord SendMIDINoteOn channels[LastPad], chord[LastPad], vels[LastPad] CopyArray chord, currChord, 4 else if NOT state toggles[LastPad] = 1 if drone OR noteOn SendMIDINoteOn channels[LastPad], currChord[LastPad], vels[LastPad], strumOffsets[LastPad] endif else toggles[LastPad] = 0 SendMIDINoteOff channels[LastPad], currChord[LastPad], 0, strumOffsets[LastPad] endif endif @End //********************************************* //OnPadUp function //********************************************* @OnPadUp if padMode = 0 LatchPad LastPad, 0 toggles[LastPad] = 0 SendMIDINoteOff channels[LastPad], currChord[LastPad], 0 endif @End //********************************************* //OnXYChange function //********************************************* @OnXYChange trig = false xVal = GetXValue yVal = GetYValue scaledXVal = Round (TranslateScale GetXValue, 0, 127, 0, 3) scaledYVal = Round (TranslateScale GetYvalue, 0, 127, 0, 3) if scaledXVal <> voicing voicing = scaledXVal trig = true elseif scaledYVal <> inversion inversion = scaledYVal trig = true endif if trig Call @SendChord trig = false endif xvalue = xVal yvalue = yVal SetKnobValue 1, Round xVal SetKnobValue 2, Round yVal @End //********************************************* //OnShiftDown function //********************************************* @OnShiftDown //Knobs if padMode = 0 LabelKnob 0, {Triggers} else LabelKnob 0, {Toggles} endif if voicing = 0 LabelKnob 1, {Closed} elseif voicing = 1 LabelKnob 1, {Drop 2} elseif voicing = 2 LabelKnob 1, {Drop 3} elseif voicing = 3 LabelKnob 1, {Spread} endif if inversion = 0 LabelKnob 2, {None} elseif inversion = 1 LabelKnob 2, {1st Inv} elseif inversion = 2 LabelKnob 2, {2nd Inv} elseif inversion = 3 LabelKnob 2, {3rd Inv} endif if quality = 0 LabelKnob 3, {Major} elseif quality = 1 LabelKnob 3, {Minor} elseif quality = 2 LabelKnob 3, {Sus2} elseif quality = 3 LabelKnob 3, {Sus4} endif if drone = 0 LabelKnob 4, {MIDI} elseif drone = 1 LabelKnob 4, {Drone} endif LabelKnob 5, (channels[0] + 1) LabelKnob 6, (channels[1] + 1) LabelKnob 7, (channels[2] + 1) LabelKnob 8, (channels[3] + 1) if strum LabelKnob 9, {Strum On} else LabelKnob 9, {Strum Off} endif @End //********************************************* //OnShiftUpFunction //********************************************* @OnShiftUp Call @LabelAllKnobs @End //********************************************* //SeupGui function //********************************************* @SetupGui //Layout ShowLayout 0 //Panel SetShortName {Chord} LabelKnobs {Chordial} LabelPads {Voice Toggles} LabelXY {X = Voicing Y = Inversion} Call @LabelAllKnobs //Pads LabelPad 0, {Root} LabelPad 1, {3rd} LabelPad 2, {5th} LabelPad 3, {7th} //Enable voice toggles (pads) for i = 0 to 3 LatchPad i, true endfor SetXYValues 0, 0 Call @SetKnobValues @End //********************************************* //LabelAllKnobs function //********************************************* @LabelAllKnobs //Knobs LabelKnob 0, {Pad Mode} LabelKnob 1, {Voicing} LabelKnob 2, {Inversion} LabelKnob 3, {Quality} LabelKnob 4, {MIDI/Drone} LabelKnob 5, {Root Ch.} LabelKnob 6, {3rd Ch.} LabelKnob 7, {5th Ch.} LabelKnob 8, {7th Ch.} LabelKnob 9, {Strum} @End //********************************************* //SetKnobValues function //********************************************* @SetKnobValues SetKnobValue 0, TranslateScale padMode, 0, 1, 0, 127 SetKnobValue 1, TranslateScale voicing, 0, 3, 0, 127 SetKnobValue 2, TranslateScale inversion, 0, 3, 0, 127 SetKnobValue 3, TranslateScale quality, 0, 3, 0, 127 SetKnobValue 4, TranslateScale drone, 0, 1, 0, 127 SetKnobValue 5, channels[0] SetKnobValue 6, channels[1] SetKnobValue 7, channels[2] SetKnobValue 8, channels[3] SetKnobValue 9, strum @End //****************************************************************************** // UTILS //****************************************************************************** //********************************************* //ChordSort function // //Performs an insertion sort on the chord //array. This is to ensure that the strum offset //values in the strumOffsets array are associated //the correct pitches in the chord array. //********************************************* @ChordSort len = 3 for i = 1 to len key = chord[i] index = (i - 1) safeIndex = index while (index > -1) AND (chord[safeIndex] > key) chord[index + 1] = chord[index] index = (index - 1) if index >= 0 //This check as well as the safeIndex variable //are a band aid to address the syntax error //that is thrown when the index value is used //in the comparison to key of the containing //while loop. Weird science. safeIndex = index endif endwhile chord[index + 1] = key endfor @End //********************************************* //SetQuality function //********************************************* @SetQuality if quality = 0 //Major q_third = 4 q_fifth = 7 q_seventh = 11 elseif quality = 1 //Minor q_third = 3 q_fifth = 7 q_seventh = 10 elseif quality = 2 //Sus2 q_third = 2 q_fifth = 7 q_seventh = 10 elseif quality = 3 //Sus4 q_third = 5 q_fifth = 7 q_seventh = 10 endif @End //********************************************* //OnTimer Function //********************************************* @OnTimer LabelKnobs {Chordial} StopTimer if badChan //Reset channel knobs to values in channels array. badChan = false for i = 0 to 3 SetKnobValue i+5, (TranslateScale channels[i], 0, 15, 0, 127) endfor endif @End //********************************************* //RecalcVec Function // //This function is called after a strum event. //It randomizes the 2nd and 3rd values of the //vector used in the scalar multiplication to //determine the note offset times of a strum. //********************************************* @RecalcVec strumVal2 = (Random 300, 400)/1000 strumVal3 = (Random 500, 620)/1000 vec[1] = strumVal2 vec[2] = strumVal3 @End //********************************************* //Description //********************************************* @Description Chordial V1.0.1 Chordial is a MIDI chord generator based on the Chord eurorack module from Qu-Bit Electronix. The root note of the chord generated by Chordial is set by an incoming MIDI note. The parameters for the generated chord are set with the knobs. The pads are used to toggle or trigger individual chord voices. The XY pad can be used to change the Voicing and Inversion of the chord simultaneously. Scroll down for more info... User Interface -------------- Pads: In the default Voice Toggles mode, the pads will turn each of the generated chord voices on or off. In Voice Triggers mode, the pads will play each voice of the chord for the duration that the pad is held down. Pad Mode: Changes the pads between Voice Toggles and Voice Triggers mode. Voicing: Changes the voicing of the generated chord. The four voicing modes currently implemented are Closed, Drop 2, Drop 3, and Spread. Inversion: Applies inversions to the generated chord. The four inversion modes are None, 1st Inversion, 2nd Inversion, and 3rd Inversion. Quality: Changes the quality of the generated chord. The four qualities currently implemented are Major, Minor, Sus2, and Sus4. Note that the pads will update to display the current intervals determined by the selected quality. MIDI/Drone: Changes between the two play modes. In MIDI mode, a new chord is generated and played with each note-on event sent to Chordial. In Drone mode, a chord will drone continuously, and only those notes that are changed by a change to the chord parameters will be retriggered. Root Channel, 3rd Channel, 5th Channel, 7th Channel: These 4 knobs change the outgoing MIDI channel for each chord voice. This means Chordial can send the note information for each voice in a generated chord out to its own instrument or other MIDI effect. This can be fun! For example, you could send the Root and 5th out to one instrument, send the 3rd out to an arpeggiator in front of a different instrument, and send the 7th out to another instance of Chordial that is triggering yet another instrument. Strum: Applies a delay to the 3rd, 5th, and 7th voices to simulate a strumming motion. The strum time increases as the knob is turned clockwise. There is a slight random adjustment applied to the timing of each interval every time a new chord is generated, so no two strums are exactly the same. Shift: Hold the shift button to see the current values for each parameter.