Hey guys, thanks a lot for having me. GP is awesome!
I am trying to write a gpscript to use in gig performer 5.0.28 to play diatonic chords of a given scale, for example, in c major harmony, play c major chord when C key is triggered, d minor chord played when D key is triggered, e minor chord played when E key is triggered… and so on.
It should include:
A) Key + Scale selector widget
B) Extended chords selector (7th, 9th, 11th)
C) Scales selector (include otion to select Major, Minor, Dorian, Mixolydian, lydian, phrygian, aeolian, locrian, melodic minor and harmonic minor scales)
D)Transpose scale
E) Use widget to choose key & scale live
Key selector shows “C”, “C#”, “D”, …
Scale selector shows “Major”, “Minor”, “Dorian”, …
Chord selector shows “Triad”, “7th”, “9th”, “11th”
THE SCRIPT:
// — G P S C R I P T : DIATONIC CHORD PLAYER —
// This script automatically plays the diatonic chords (Triad, 7th, 9th, 11th)
// of a selected key and scale, triggered by the input notes.
// REQUIRES:
// 1. A Key Selector Widget (12 positions, value 0-11)
// 2. A Scale Selector Widget (9 positions, value 0-8)
// 3. A Chord Extension Selector Widget (4 positions, value 0-3)
// 4. A Transpose Widget (Integer or Knob, value -12 to 12)
// ====================================================================
// 1. WIDGET AND GLOBAL VARIABLE DECLARATIONS
// ====================================================================
// Declare Widgets
var
// A) Key Selector (0 = C, 11 = B)
KeySelector : Widget
// B) Chord Extension Selector (0=Triad, 1=7th, 2=9th, 3=11th)
ChordExtensionSelector : Widget
// C) Scale Selector (0=Major, 1=Minor, etc.)
ScaleSelector : Widget
// D) Transpose Scale (Integer widget, e.g., -12 to 12)
TransposeWidget : Widget
// Global State Variables
CurrentKey : Integer = 0 // C Major by default
CurrentScaleIndex : Integer = 0 // Major by default
CurrentExtensionIndex : Integer = 0 // Triad by default
CurrentTranspose : Integer = 0
// Array to store the last chord played for Note Off purposes
heldChordNotes : Integer array = [0, 0, 0, 0, 0, 0] // Up to 6 notes (11th chord)
// Constants and Arrays for scale and note names
KEY_NAMES : String array = [
"C", "C#", "D", "D#", "E", "F",
"F#", "G", "G#", "A", "A#", "B"
]
SCALE_NAMES : String array = [
"Major (Ionian)", "Minor (Aeolian)", "Dorian", "Phrygian",
"Lydian", "Mixolydian", "Locrian", "Harmonic Minor", "Melodic Minor"
]
EXTENSION_NAMES : String array = [
"Triad (1-3-5)", "7th (1-3-5-7)", "9th (1-3-5-7-9)", "11th (1-3-5-7-9-11)"
]
// SCALE DEFINITION (Intervals from the root, in semitones)
// The arrays contain the semitone offsets for the 8 notes of the scale (0 to 12).
// [1st, 2nd, 3rd, 4th, 5th, 6th, 7th, 8th]
const
// All 9 scale definitions (as a 2D array is complicated in GPScript,
// we use a single large array for simplicity, accessing using index * 8 + degree)
ScaleIntervals : Integer array = [
// 0: Major (Ionian)
0, 2, 4, 5, 7, 9, 11, 12,
// 1: Natural Minor (Aeolian)
0, 2, 3, 5, 7, 8, 10, 12,
// 2: Dorian
0, 2, 3, 5, 7, 9, 10, 12,
// 3: Phrygian
0, 1, 3, 5, 7, 8, 10, 12,
// 4: Lydian
0, 2, 4, 6, 7, 9, 11, 12,
// 5: Mixolydian
0, 2, 4, 5, 7, 9, 10, 12,
// 6: Locrian
0, 1, 3, 5, 6, 8, 10, 12,
// 7: Harmonic Minor
0, 2, 3, 5, 7, 8, 11, 12,
// 8: Melodic Minor
0, 2, 3, 5, 7, 9, 11, 12
]
// Diatonic Chord Degrees (relative to the root of the chord):
// These are the scale degrees to stack for each extension (1, 3, 5, 7, 9, 11)
// Note: Degrees are 1-based (1 to 7). The 9th is the 2nd degree + octave, 11th is 4th + octave.
const
// [1st, 3rd, 5th, 7th, 9th, 11th]
CHORD_DEGREES : Integer array = [1, 3, 5, 7, 2, 4]
// Max number of notes for each extension
const
MAX_NOTES_BY_EXTENSION : Integer array = [3, 4, 5, 6] // Triad, 7th, 9th, 11th
// ====================================================================
// 2. WIDGET CHANGE HANDLERS (Live control)
// ====================================================================
// A) Update Key and label
On WidgetValueChanged(newValue : double) from KeySelector
CurrentKey = Round(newValue)
SetWidgetLabel(KeySelector, KEY_NAMES[CurrentKey])
End
// C) Update Scale and label
On WidgetValueChanged(newValue : double) from ScaleSelector
CurrentScaleIndex = Round(newValue)
SetWidgetLabel(ScaleSelector, SCALE_NAMES[CurrentScaleIndex])
End
// B) Update Chord Extension and label
On WidgetValueChanged(newValue : double) from ChordExtensionSelector
CurrentExtensionIndex = Round(newValue)
SetWidgetLabel(ChordExtensionSelector, EXTENSION_NAMES[CurrentExtensionIndex])
End
// D) Update Transpose
On WidgetValueChanged(newValue : double) from TransposeWidget
CurrentTranspose = Round(newValue)
End
// ====================================================================
// 3. CORE NOTE PROCESSING LOGIC
// ====================================================================
// Helper function to turn off the previously held chord
Function TurnOffPreviousChord(vel : Integer)
var i : Integer
for i = 0 to Size(heldChordNotes) - 1 do
if heldChordNotes[i] > 0 then
SendNoteOffNow(heldChordNotes[i], vel)
end
// Reset the note in the held list
heldChordNotes[i] = 0
end
End
// Helper function to get the semitone interval of a scale degree.
// Scale degrees are 1-based (1st, 2nd, 3rd… up to 11th is 7 + 4 = 11 degrees total)
// The ScaleIntervals array only holds 8 notes (1st to 8th)
Function GetScaleSemitoneInterval(scaleIndex : Integer, degree : Integer) : Integer
// Ensure degree is within bounds (1 to 7/8 in the base octave)
// We use the full scale definition (8 notes)
var baseIndex : Integer = scaleIndex * 8
var octave : Integer = (degree - 1) div 7
var degreeInOctave : Integer = (degree - 1) mod 7
// The scale intervals array is 0-based for its index, so we access index degreeInOctave + 1
// e.g. 1st degree (0 mod 7) is index 0. 8th degree is index 7 (12 semitones).
var intervalOffset : Integer = ScaleIntervals[baseIndex + degreeInOctave]
// Add octave shift
return intervalOffset + (octave * 12)
End
// Main Note Handler
On NoteEvent(n : NoteMessage)
var
inputNote : Integer = n.Note
inputVelocity : Integer = n.Velocity
i : Integer
// The scale index offset in the ScaleIntervals array
scaleRoot : Integer = CurrentKey
// Only process Note On messages
if n.IsNoteOn then
// 1. Turn off the previous chord before calculating the new one
TurnOffPreviousChord(inputVelocity)
// 2. Identify the input note's scale degree (1st, 2nd, 3rd, 4th, 5th, 6th, or 7th)
// We look for the diatonic note that matches the input note.
var
diatonicDegree : Integer = -1 // 1-based degree (1 to 7)
noteClass : Integer = inputNote mod 12 // C=0 to B=11
scaleInterval : Integer // interval from the scale root
// Loop through the 7 scale degrees (1st to 7th)
for i = 0 to 6 do
// Get the semitone interval for this degree in the current scale
// Degree is 1-based, so i + 1
scaleInterval = ScaleIntervals[CurrentScaleIndex * 8 + i]
// Calculate the chromatic note class for this degree
if noteClass = (scaleRoot + scaleInterval) mod 12 then
diatonicDegree = i + 1 // Found the degree (1-based)
break // Stop searching
end
end
// 3. If the input note is NOT in the selected scale, ignore it.
if diatonicDegree = -1 then
// For now, we just pass the note through if it's not diatonic
// You can change this to 'SwallowEvent()' to completely filter non-diatonic notes
PropagateEvent(n)
return
end
// 4. Calculate the chord based on the diatonic degree
var
numNotes : Integer = MAX_NOTES_BY_EXTENSION[CurrentExtensionIndex]
degreeOffset : Integer
chordDegree : Integer
semitoneOffset : Integer
chordNote : Integer
j : Integer = 0 // Index for heldChordNotes
// Loop through the required number of notes (3 for Triad, up to 6 for 11th)
for i = 0 to numNotes - 1 do
// The degree of the chord being built: 1st, 3rd, 5th, 7th, 9th, 11th
// These correspond to the CHORD_DEGREES array
chordDegree = CHORD_DEGREES[i]
// The actual scale degree we are looking for:
// e.g., if input is 1st degree (C in C Major), and we want the 3rd of the chord (E),
// then the target degree is 1 (input degree) + 2 (interval to 3rd) = 3rd degree.
degreeOffset = diatonicDegree + (chordDegree - 1)
// 5. Get the semitone offset for the target degree from the MAIN KEY ROOT (CurrentKey)
// The semitone offset is calculated using the scale intervals array:
semitoneOffset = GetScaleSemitoneInterval(CurrentScaleIndex, degreeOffset)
// 6. Calculate the final note number (Root + Semitone Offset + Transpose)
// The root octave is determined by the input note's octave
var rootOctaveStart : Integer = (inputNote div 12) * 12 + scaleRoot
// We need to adjust the chord root to the correct octave based on the input note.
// Simplified approach: find the lowest possible note for the chord root (diatonicDegree)
// in the vicinity of the input note.
// Let's use the input note's MIDI note number as the 1st of the chord for simplicity,
// unless the input note is higher than the root of the scale degree.
// For diatonic chords, the input note *is* the root of the chord (1st degree).
// chordNote = inputNote + (semitoneOffset - (inputNote - scaleRoot) mod 12) + CurrentTranspose
// Simplified: Chord root is Input Note + Offset
chordNote = inputNote + (semitoneOffset - (inputNote - scaleRoot) mod 12) + CurrentTranspose
// A more robust way (which we must use for stability):
// The lowest note of the chord (the chord root, or 1st degree) is `inputNote`.
// The relative interval we need to add is `semitoneOffset` relative to the
// 1st degree of the chord (which is `inputNote`).
// Since the input note *is* the root of the chord (e.g., C -> Cmaj, D -> Dmin),
// the required intervals are the semitone differences from the *root* of the chord,
// which is the `diatonicDegree`'s interval.
// 1. Find the chromatic note number of the 1st, 3rd, 5th, etc. in the scale.
// 2. Transpose them to the octave of the input note.
// Let's re-run the calculation of the chord note interval relative to the CHORD ROOT
// The chord root is `inputNote`. We need the interval of the 3rd, 5th, 7th, etc.
var baseDegreeOffset : Integer = (diatonicDegree - 1) * 8
var rootInterval : Integer = ScaleIntervals[CurrentScaleIndex * 8 + (diatonicDegree - 1)]
var noteInterval : Integer
// The semitone difference from the root of the chord to the note we want to play
// e.g., Cmaj: C is 1st (0), E is 3rd (4), G is 5th (7).
// To get the 3rd: We need the semitone difference between the 3rd degree of the scale
// (e.g., E) and the 1st degree of the scale (e.g., C).
// Target degree in the full scale (1 to 11)
var targetScaleDegree : Integer = (diatonicDegree - 1) + (CHORD_DEGREES[i] - 1)
// Get the semitone interval of the target degree relative to the scale root
var targetSemitoneOffset : Integer = GetScaleSemitoneInterval(CurrentScaleIndex, targetScaleDegree + 1)
// Get the semitone interval of the chord root relative to the scale root
var chordRootSemitoneOffset : Integer = GetScaleSemitoneInterval(CurrentScaleIndex, diatonicDegree)
// The interval of the note *relative to the chord root*
noteInterval = targetSemitoneOffset - chordRootSemitoneOffset
// Correct the octave: If the interval is negative (e.g., 9th degree interval - 1st degree interval)
// then we need to add 12 to bring it to the next octave.
// Note: This logic for 9th/11th is complex. We must ensure `GetScaleSemitoneInterval`
// already handles octaves correctly for degrees > 7 (which it does, e.g., 9th=2nd+12).
// The final MIDI note to play:
chordNote = inputNote + noteInterval + CurrentTranspose
// 7. Play the note and store it
SendNoteOnNow(chordNote, inputVelocity)
heldChordNotes[j] = chordNote
j = j + 1
end
// Process Note Off messages
else
// 1. Turn off all notes of the chord.
// We only do this on the first NoteOff received, assuming the chord root is the key being released.
TurnOffPreviousChord(inputVelocity)
// 2. Important: Swallow the event. The script has handled the Note Off for the chord.
SwallowEvent()
end
End
// ====================================================================
// 4. INITIALIZATION
// ====================================================================
On Activate
// Initialize widget values and labels to match default state (C Major, Triad)
// Key Selector (C)
SetWidgetValue(KeySelector, CurrentKey)
SetWidgetLabel(KeySelector, KEY_NAMES[CurrentKey])
// Scale Selector (Major)
SetWidgetValue(ScaleSelector, CurrentScaleIndex)
SetWidgetLabel(ScaleSelector, SCALE_NAMES[CurrentScaleIndex])
// Chord Extension Selector (Triad)
SetWidgetValue(ChordExtensionSelector, CurrentExtensionIndex)
SetWidgetLabel(ChordExtensionSelector, EXTENSION_NAMES[CurrentExtensionIndex])
// Transpose (0)
SetWidgetValue(TransposeWidget, 0)
// Log for debugging
Print("Diatonic Chord Player Initialized. Key: " + KEY_NAMES[CurrentKey] + ", Scale: " + SCALE_NAMES[CurrentScaleIndex])
End
I am getting this error and doesn’t compile:
Diatonic Chords (Script Entity: Rackspace) - - Syntax Error in “Main”: Line 54, Col 30: Unexpected or unrecognized token: ‘array’
Extraneous input ‘array’ expecting ‘=’
Any assistance? Looking forward to your feedbacks