Intelligent Voice Allocation, Chord Recognition and Live MIDI Control
Update to Version 4.0: Added CC controls, voice leading mode and separate natural seventh parameter
Divisi & Microtuning Engine V4.0 distributes incoming MIDI notes across individual instrument channels, identifies supported chords and applies adjustable microtuning relative to the detected root. It combines three voice allocation modes with dedicated bass routing, configurable chord acceptance and assignable MIDI CC control.
The engine supports up to ten Divisi voices on output channels 1–10. A separate bass output can be selected from channels 11–16. Chord recognition always considers every held Divisi input note, even when fewer voices are available for playback.
Three Voice Allocation Modes
0 — Sorted
Sorted mode selects notes according to the voice count and Low/High Priority setting, then assigns the selected notes in descending pitch order to consecutive output channels.
The highest selected note goes to channel 1, the next highest to channel 2, and so on. When the held note set changes, the engine recalculates this distribution. A note may therefore move to another channel and be retriggered.
Use Sorted when each output channel represents a defined position within the current voicing, such as the highest, second-highest or lowest selected part.
1 — Stable
Stable mode assigns each new note to an available channel and keeps that assignment until the voice is released or replaced. Adding another note does not redistribute existing voices.
When all available channels are occupied, a new attack replaces the oldest assigned voice. A displaced note remains part of chord recognition while its input key is held, but it does not automatically return when a channel becomes available.
A genuine repeated attack on an already assigned pitch uses that pitch’s existing channel. Chord recognition and tuning changes update pitch bend without retriggering the assigned voices.
Use Stable for immediate response and consistent channel ownership, particularly with ensembles of identical instruments.
2 — Voice Leading
Voice Leading preserves held and sustained voices on their existing channels while assigning new notes according to each available channel’s previous pitch.
Each channel remembers its last actually played MIDI note. This memory remains available after release and across pauses. The engine uses it to reduce the total pitch movement between successive groups of notes.
Voice Leading preserves the exact input pitches. It does not transpose notes, change octaves or generate a different chord voicing.
New attacks are collected during a configurable window. At the end of that window, the engine assigns them together:
- Held, sustained and otherwise reserved channels remain protected.
- Available channels with pitch memory are preferred.
- The total absolute pitch movement is minimized.
- Equal solutions are resolved deterministically by choosing the lowest channel sequence for the new notes sorted in descending pitch order.
Without pitch memory, the first group follows the Sorted ordering: descending pitches are assigned to the lowest available channels.
For example, if channels 1–3 previously played G–E–B, a completely new D–F–A group is assigned as F–D–A. This produces smaller total movement than assigning the new pitches solely by their current order.
If E and G remain held while C major changes to E minor, E and G retain their channels. Only the newly played B needs an assignment.
When more new notes arrive than there are available channels, Low/High Priority selects among those new notes. Existing voices are never displaced. Unselected notes remain silent until played again, although held input notes still participate in chord recognition.
Voice Leading Timing and Memory
Voice Leading Collection Time sets the collection window from 0 to 100 ms, with a default of 20 ms.
The first new attack establishes a fixed deadline. Subsequent attacks join the same window without extending it. Changing the collection time affects the next window; an existing window keeps its original deadline.
At 0 ms, allocation is immediate and may depend on the order in which notes arrive.
Note-offs for already sounding voices are processed immediately. With sustain engaged, a released voice continues to occupy its channel until the pedal releases it.
Short notes released before their collection window ends are preserved. They are played after collection and then released according to their original duration, with a minimum duration of 1 ms. Buffered repeated attacks remain separate and retain their relative timing on the same assigned channel.
The event buffer is bounded. If it becomes full, additional attacks are discarded while existing releases remain protected. An English status message reports the overload.
Reset Voice Leading Memory is a momentary control. Setting it to 1 triggers a reset and returns the control to 0. Free channels forget their previous pitches; occupied channels retain their current notes as reference points. The reset does not stop voices or change their tuning.
Memory is also cleared on activation, a completed allocation-mode change, a Divisi input-channel change or Divisi panic.
Reducing the voice count safely ends voices outside the new channel range and clears their memory. Increasing the count does not automatically restore previously silent held notes.
Allocation-mode changes are deferred until Divisi keys, sustain, sounding voices and pending Voice Leading events have been released or completed.
Chord Recognition
In Input mode, the engine analyzes the complete set of held Divisi input notes. Recognition is independent of the number of voices being played.
Octave doublings do not change the recognized pitch-class set. Additional foreign pitch classes prevent a match. A supported triad contained inside a larger, unsupported chord is insufficient to activate microtuning.
The selected bass input can contribute held notes below the lowest held Divisi note. Higher bass notes do not enter the analysis. Notes from other input channels do not influence chord recognition.
The lowest analyzed note determines the actual bass position used to validate the chord.
Chord Analysis Delay controls the harmonic evaluation delay separately from Voice Leading collection. When harmonic evaluation and voice allocation become due together, tuning is updated first.
In Input mode, releasing all Divisi keys immediately disables microtuning, including for any remaining sustained ensemble voices, bass notes or held melody notes.
Chord Acceptance Levels
Chord Acceptance Level provides nine cumulative levels. Each level includes the forms enabled by the preceding levels.
| Level |
Additional accepted forms |
| 0 |
Major triads with the root in the bass |
| 1 |
Major triads with the third or fifth in the bass |
| 2 |
Minor triads and permitted inversions |
| 3 |
Power chords, sus2 and sus4 |
| 4 |
Major add9 and minor add9 |
| 5 |
Major seventh and minor seventh |
| 6 |
Dominant seventh |
| 7 |
Minor ninth |
| 8 |
Major ninth, dominant ninth, dominant seventh sus4 and dominant ninth sus4 |
All recognized forms require the root to be present and the complete pitch-class set to match.
Major and minor families generally require the root, third or perfect fifth in the bass. Minor seventh chords have a stricter rule: only root-position bass is accepted. Minor ninth chords retain the general minor-family bass rule.
Power and suspended forms require the root or perfect fifth in the bass. When a suspended set supports multiple interpretations, the interpretation with the bass as root takes precedence.
Consequently, Am7/A is accepted at the appropriate level, while C6/C, which contains the same pitch classes as Am7/C, does not activate microtuning. Chords with an unsupported seventh or foreign tone in the bass are rejected.
Level 7 preserves the previous chord acceptance scope. Level 8 introduces the additional ninth and suspended-seventh forms.
Microtuning and Root Modes
The engine provides twelve interval-tuning controls, from the root through the major seventh. Each has a range of −500 to +500 in units of 0.1 cent. For example, a value of 153 raises the interval by 15.3 cents.
In Fixed mode, tuning is calculated relative to the selected Fixed Root. Automatic chord acceptance and the optional natural-seventh rule are bypassed.
In Input mode, tuning is active only when the complete analyzed note set matches an accepted chord with a permitted bass position.
Microtuning Enabled is the master switch for both root modes. Turning it off immediately clears the microtuning offsets on all sixteen output channels, including held notes. Normal pitch bend remains available.
Turning it on recalculates the current tuning. Stable and Voice Leading assignments remain intact, and tuning changes do not generate additional attacks.
Each Divisi channel receives the offset for its assigned note. On a polyphonic bass or melody channel, the highest held note determines the shared channel offset.
Pitch Bend Range must match the receiving instrument’s configured range for the tuning amounts to be correct.
Optional Natural Seventh
Natural Seventh for Dominant 7 optionally replaces the normal minor-seventh offset for accepted dominant seventh chords in Input mode.
When enabled, the seventh uses an offset approximately 31.2 cents below equal temperament, relative to the root’s tuning adjustment. The rule applies to accepted dominant seventh chords with the root, third or fifth in the bass.
Minor seventh, ninth and suspended-seventh chords continue to use the regular Minor Seventh Tuning control. Fixed mode also retains the normal interval-tuning settings.
Dedicated Bass Routing
Bass Input Channel selects the external bass source. A value of 0 disables the external bass function. Selecting the same input channel as Divisi also disables the separate bass function.
Bass Output Channel selects a reserved output from channels 11–16. Bass notes and associated channel messages are redirected to this output.
Bass Chord Analysis Enabled controls only whether eligible bass notes contribute to recognition. Turning it off preserves bass playback and routing. The bass continues to receive tuning relative to any accepted chord determined from the remaining Divisi input.
Changing the bass input ends the old bass notes and clears their tracking. Changing the bass output transfers held bass notes to the new output and resets the old output’s pitch bend.
Live MIDI CC Control
Eleven assignable CC controls provide remote access to allocation, root mode, fixed root, voice count, priority, chord acceptance, bass analysis, microtuning, natural seventh, Voice Leading collection time and memory reset.
CC Control Input Channel selects the channel used for remote control. A value of 0 disables CC control. Each assignment defaults to −1, meaning unassigned; CC0 is a valid assignment.
| Function |
CC interpretation |
| Voice Allocation |
0–42 Sorted; 43–85 Stable; 86–127 Voice Leading |
| Root Mode |
0–63 Fixed; 64–127 Input |
| Fixed Root |
Twelve ranges from C through B |
| Voice Count |
Ten ranges from 1 through 10 |
| Note Priority |
0–63 Low; 64–127 High |
| Chord Acceptance Level |
Nine ranges from 0 through 8 |
| Bass Chord Analysis |
0–63 Off; 64–127 On |
| Microtuning |
0–63 Off; 64–127 On |
| Natural Seventh |
0–63 Off; 64–127 On |
| Voice Leading Collection Time |
Values 0–127 mapped to 0–100 ms |
| Voice Leading Memory Reset |
One reset on a transition from low to high |
The reset CC triggers on a transition from 0–63 to 64–127. The first high value after activation or reset-control configuration also triggers. Further high values do not trigger again until a low value is received.
CC64 and CC120–127 are reserved. Assigning them to remote functions leaves those assignments inactive. Duplicate assignments are also inactive, and the help display identifies the conflict.
Valid assigned control messages are consumed before instrument routing. Other CC messages retain their normal processing.
Remote changes update the visible parameters and use the same engine update path as manual changes.
English Controls and Status Display
All forty parameters have English display names and contextual descriptions. Changing a parameter manually or through CC displays its corresponding help text.
Descriptions cover the selected state, routing behavior, chord acceptance levels and CC value mappings. They also report disabled inputs, reserved assignments and duplicate CC conflicts.
Deferred allocation-mode changes remain visible as an additional status message, even when another parameter’s description is displayed. The last selected description remains visible until another control changes.
Upgrade and Verification
V4.0 retains the original thirty-six parameter positions and appends four new controls. The first allocation parameter keeps its existing internal name but expands its range from 0–1 to 0–2.
Existing widgets and variations should be checked during migration. Stable remains the actual parameter value 1. A Voice Allocation CC value of 127 now selects Voice Leading.
// V4.0: Sorted, Stable and Voice Leading; channel memory, timed events and English help.
var
// 0 = Sorted; 1 = Stable; 2 = Voice Leading with channel memory and grouped attacks.
// Change modes only after releasing all Divisi keys and the sustain pedal.
VoiceMode_0Sorted_1Stable("Voice Allocation (0 Sorted / 1 Stable / 2 Voice Leading)") : Parameter 0..2 = 0
// --- MAIN CONTROLS ---
Mode_0Fixed_1Input("Root Mode (0 Fixed / 1 Input)") : Parameter 0..1 = 1
FixedChordRoot("Fixed Root (1 C / 12 B)") : Parameter 1..12 = 1
InputMidiChannel("Divisi Input Channel") : Parameter 1..16 = 2
DivisiVoiceCount("Divisi Voice Count") : Parameter 1..10 = 4
Priority_0Low_1High("Note Priority (0 Low / 1 High)") : Parameter 0..1 = 1
PitchBendRange_Semi("Pitch Bend Range (Semitones)") : Parameter 1..24 = 2
ChordDelay_ms("Chord Analysis Delay (ms)") : Parameter 0..100 = 70
// 0 major root position; 1 major inversions; 2 minor; 3 power/sus; 4 add9;
// 5 major7/minor7; 6 dominant7; 7 minor9 (V3.1); 8 major9/dominant9/7sus4/9sus4.
ChordAcceptanceLevel("Chord Acceptance Level") : Parameter 0..8 = 7
BassInputMidiChannel("Bass Input Channel (0 Off)") : Parameter 0..16 = 3
BassOutputMidiChannel("Bass Output Channel") : Parameter 11..16 = 16
// 0 = external bass disabled. Sharing the Divisi input channel disables external bass.
// Tuning values in 0.1 cent: 153 = 15.3 cents.
Tune_1root("Root Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_2minorSecond("Minor Second Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_3MajorSecond("Major Second Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_4minorThird("Minor Third Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_5MajorThird("Major Third Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_6PerfectFourth("Perfect Fourth Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_7diminishedFifth("Diminished Fifth Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_8PerfectFifth("Perfect Fifth Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_9minorSixth("Minor Sixth Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_10MajorSixth("Major Sixth Tuning (0.1 Cent)") : Parameter -500..500 = 0
Tune_11minorSeventh("Minor Seventh Tuning (0.1 Cent)") : Parameter -500..500 = 0
// Only complete, accepted dominant 7 chords; includes permitted inversions.
NaturalSeventh_0Off_1On("Natural Seventh for Dominant 7") : Parameter 0..1 = 0
Tune_12MajorSeventh("Major Seventh Tuning (0.1 Cent)") : Parameter -500..500 = 0
// Live control: existing parameter indices remain unchanged.
MicroTuning_0Off_1On("Microtuning Enabled") : Parameter 0..1 = 1
BassAnalysis_0Off_1On("Bass Chord Analysis Enabled") : Parameter 0..1 = 1
ControlMidiChannel("CC Control Input Channel (0 Off)") : Parameter 0..16 = 0
// -1 = unassigned. CC64 and CC120-127 are reserved; duplicate assignments are inactive.
CC_VoiceMode("CC Assignment: Voice Allocation") : Parameter -1..127 = -1
CC_RootMode("CC Assignment: Root Mode") : Parameter -1..127 = -1
CC_FixedRoot("CC Assignment: Fixed Root") : Parameter -1..127 = -1
CC_VoiceCount("CC Assignment: Voice Count") : Parameter -1..127 = -1
CC_Priority("CC Assignment: Note Priority") : Parameter -1..127 = -1
CC_ChordAcceptanceLevel("CC Assignment: Chord Acceptance Level") : Parameter -1..127 = -1
CC_BassAnalysis("CC Assignment: Bass Chord Analysis") : Parameter -1..127 = -1
CC_MicroTuning("CC Assignment: Microtuning") : Parameter -1..127 = -1
CC_NaturalSeventh("CC Assignment: Natural Seventh") : Parameter -1..127 = -1
VoiceLeadingDelay_ms("Voice Leading Collection Time (ms)") : Parameter 0..100 = 20
ResetVoiceLeadingMemory("Reset Voice Leading Memory") : Parameter 0..1 = 0
CC_VoiceLeadingDelay("CC Assignment: Voice Leading Collection Time") : Parameter -1..127 = -1
CC_VoiceLeadingReset("CC Assignment: Voice Leading Memory Reset") : Parameter -1..127 = -1
AccompHeldVelocities : Integer[128]
AccompPlayingVoices : Integer[128]
TargetVoices : Integer[128]
SelectedNotes : Integer[128]
BassHeldVelocities : Integer[128]
BassPlayingVelocities : Integer[128]
MelodyHeldCh1 : Integer[128]; MelodyHeldCh2 : Integer[128]; MelodyHeldCh3 : Integer[128]; MelodyHeldCh4 : Integer[128]
MelodyHeldCh5 : Integer[128]; MelodyHeldCh6 : Integer[128]; MelodyHeldCh7 : Integer[128]; MelodyHeldCh8 : Integer[128]
MelodyHeldCh9 : Integer[128]; MelodyHeldCh10 : Integer[128]; MelodyHeldCh11 : Integer[128]; MelodyHeldCh12 : Integer[128]
MelodyHeldCh13 : Integer[128]; MelodyHeldCh14 : Integer[128]; MelodyHeldCh15 : Integer[128]; MelodyHeldCh16 : Integer[128]
ActiveInputMidiChannel : Integer = 2
ActiveBassInputMidiChannel : Integer = 3
ActiveBassOutputMidiChannel : Integer = 16
ActiveVoiceMode : Integer = 0
ChannelPlayingNotes : Integer[11]
VoiceStartOrder : Integer[11]
VoiceOrderCounter : Integer = 0
DivisiSustainDown : Boolean = false
DivisiSustainValue : Integer = 0
DisplayedVoiceMode : Integer = -1
DisplayedRequestedMode : Integer = -1
GlobalActiveRoot : Integer = 0
MicroTuningActive : Boolean = false
// 0 none/fixed; 1 major; 2 minor; 3 power; 4 sus2; 5 sus4; 6 add9;
// 7 minor add9; 8 major7; 9 minor7; 10 dominant7; 11 minor9;
// 12 major9; 13 dominant9; 14 dominant7sus4; 15 dominant9sus4.
AnalyzedChordType : Integer = 0
ChannelPBOffset : Integer[17]
LastSentPB : Integer[17]
GlobalPBValue : Integer = 8192
PitchClasses : Integer[12]
TargetTime : Double = 0.0
EvaluationScheduled : Boolean = false
ParameterUpdatePending : Boolean = true
// Help follows the last changed control; show the acceptance level at startup.
HelpParameterIndex : Integer = 8
HelpUpdatePending : Boolean = true
// 0 free; 1 collecting; 2 scheduled attack; 3 scheduled release.
VLState : Integer[128]; VLNote : Integer[128]; VLVelocity : Integer[128]
VLToken : Integer[128]; VLChannel : Integer[128]; VLInputToken : Integer[128]
VLAttackTime : Double[128]; VLReleaseTime : Double[128]
VLOnDue : Double[128]; VLOffDue : Double[128]
VLMemory : Integer[11]; VLReserved : Integer[11]; VLChannelToken : Integer[11]
VLReleased : Boolean[11]
VLPowers : Integer[11]
VLCost0 : Integer[256]; VLCost1 : Integer[256]; VLCost2 : Integer[256]; VLCost3 : Integer[256]
VLFreeChannels : Integer[10]; VLNewNotes : Integer[10]
VLAssignments : Integer[128]; VLPendingPitch : Integer[128]
VLTokenCounter : Integer = 0
VLBatchActive : Boolean = false
VLBatchDue : Double = 0.0
VLOverflow : Boolean = false
VLResetCCHigh : Boolean = false
Initialization
var i : Integer
for i = 0; i < 128; i = i + 1 do
AccompHeldVelocities[i] = 0; AccompPlayingVoices[i] = 0
TargetVoices[i] = 0; SelectedNotes[i] = 0
BassHeldVelocities[i] = 0; BassPlayingVelocities[i] = 0
MelodyHeldCh1[i] = 0; MelodyHeldCh2[i] = 0; MelodyHeldCh3[i] = 0; MelodyHeldCh4[i] = 0
MelodyHeldCh5[i] = 0; MelodyHeldCh6[i] = 0; MelodyHeldCh7[i] = 0; MelodyHeldCh8[i] = 0
MelodyHeldCh9[i] = 0; MelodyHeldCh10[i] = 0; MelodyHeldCh11[i] = 0; MelodyHeldCh12[i] = 0
MelodyHeldCh13[i] = 0; MelodyHeldCh14[i] = 0; MelodyHeldCh15[i] = 0; MelodyHeldCh16[i] = 0
end
for i = 0; i <= 16; i = i + 1 do ChannelPBOffset[i] = 0; LastSentPB[i] = -1 end
for i = 0; i < 12; i = i + 1 do PitchClasses[i] = 0 end
for i = 0; i <= 10; i = i + 1 do ChannelPlayingNotes[i] = -1; VoiceStartOrder[i] = 0 end
for i = 0; i < 128; i = i + 1 do VLState[i] = 0; VLInputToken[i] = 0 end
for i = 0; i <= 10; i = i + 1 do
VLMemory[i] = -1; VLReserved[i] = -1; VLChannelToken[i] = 0; VLReleased[i] = false
end
VLPowers[0] = 1
for i = 1; i <= 10; i = i + 1 do VLPowers[i] = VLPowers[i - 1] * 2 end
VLTokenCounter = 0; VLBatchActive = false; VLOverflow = false; VLResetCCHigh = false
ActiveVoiceMode = VoiceMode_0Sorted_1Stable
VoiceOrderCounter = 0; DivisiSustainDown = false; DivisiSustainValue = 0
DisplayedVoiceMode = -1; DisplayedRequestedMode = -1
ActiveInputMidiChannel = InputMidiChannel
ActiveBassInputMidiChannel = BassInputMidiChannel
ActiveBassOutputMidiChannel = BassOutputMidiChannel
if ActiveBassInputMidiChannel == ActiveInputMidiChannel then ActiveBassInputMidiChannel = 0 end
GlobalActiveRoot = 0; MicroTuningActive = false; AnalyzedChordType = 0; GlobalPBValue = 8192
TargetTime = 0.0; EvaluationScheduled = false; ParameterUpdatePending = true
HelpParameterIndex = 8; HelpUpdatePending = true
SetTimersRunning(true)
End
On Activate
var i : Integer
if ActiveVoiceMode == 2 then
for i = 1; i <= 10; i = i + 1 do
if ChannelPlayingNotes[i] >= 0 then
SendNow(MakeControlChangeMessageEx(64, 0, i))
SendNow(MakeNoteMessageEx(ChannelPlayingNotes[i], 0, i))
SendNow(MakeControlChangeMessageEx(120, 0, i))
end
end
end
for i = 0; i < 128; i = i + 1 do
AccompHeldVelocities[i] = 0; AccompPlayingVoices[i] = 0
TargetVoices[i] = 0; SelectedNotes[i] = 0
BassHeldVelocities[i] = 0; BassPlayingVelocities[i] = 0
MelodyHeldCh1[i] = 0; MelodyHeldCh2[i] = 0; MelodyHeldCh3[i] = 0; MelodyHeldCh4[i] = 0
MelodyHeldCh5[i] = 0; MelodyHeldCh6[i] = 0; MelodyHeldCh7[i] = 0; MelodyHeldCh8[i] = 0
MelodyHeldCh9[i] = 0; MelodyHeldCh10[i] = 0; MelodyHeldCh11[i] = 0; MelodyHeldCh12[i] = 0
MelodyHeldCh13[i] = 0; MelodyHeldCh14[i] = 0; MelodyHeldCh15[i] = 0; MelodyHeldCh16[i] = 0
end
for i = 0; i <= 16; i = i + 1 do ChannelPBOffset[i] = 0; LastSentPB[i] = -1 end
for i = 0; i < 12; i = i + 1 do PitchClasses[i] = 0 end
for i = 0; i <= 10; i = i + 1 do ChannelPlayingNotes[i] = -1; VoiceStartOrder[i] = 0 end
for i = 0; i < 128; i = i + 1 do VLState[i] = 0; VLInputToken[i] = 0 end
for i = 0; i <= 10; i = i + 1 do
VLMemory[i] = -1; VLReserved[i] = -1; VLChannelToken[i] = 0; VLReleased[i] = false
end
VLPowers[0] = 1
for i = 1; i <= 10; i = i + 1 do VLPowers[i] = VLPowers[i - 1] * 2 end
VLTokenCounter = 0; VLBatchActive = false; VLOverflow = false; VLResetCCHigh = false
ActiveVoiceMode = VoiceMode_0Sorted_1Stable
VoiceOrderCounter = 0; DivisiSustainDown = false; DivisiSustainValue = 0
DisplayedVoiceMode = -1; DisplayedRequestedMode = -1
ActiveInputMidiChannel = InputMidiChannel
ActiveBassInputMidiChannel = BassInputMidiChannel
ActiveBassOutputMidiChannel = BassOutputMidiChannel
if ActiveBassInputMidiChannel == ActiveInputMidiChannel then ActiveBassInputMidiChannel = 0 end
GlobalActiveRoot = 0; MicroTuningActive = false; AnalyzedChordType = 0; GlobalPBValue = 8192
TargetTime = 0.0; EvaluationScheduled = false; ParameterUpdatePending = true
HelpParameterIndex = 8; HelpUpdatePending = true
SetTimersRunning(true)
End
Function GetMelodyVelocity(ch : Integer, noteNum : Integer) Returns Integer
result = 0
if ch == 1 then result = MelodyHeldCh1[noteNum]
elsif ch == 2 then result = MelodyHeldCh2[noteNum]
elsif ch == 3 then result = MelodyHeldCh3[noteNum]
elsif ch == 4 then result = MelodyHeldCh4[noteNum]
elsif ch == 5 then result = MelodyHeldCh5[noteNum]
elsif ch == 6 then result = MelodyHeldCh6[noteNum]
elsif ch == 7 then result = MelodyHeldCh7[noteNum]
elsif ch == 8 then result = MelodyHeldCh8[noteNum]
elsif ch == 9 then result = MelodyHeldCh9[noteNum]
elsif ch == 10 then result = MelodyHeldCh10[noteNum]
elsif ch == 11 then result = MelodyHeldCh11[noteNum]
elsif ch == 12 then result = MelodyHeldCh12[noteNum]
elsif ch == 13 then result = MelodyHeldCh13[noteNum]
elsif ch == 14 then result = MelodyHeldCh14[noteNum]
elsif ch == 15 then result = MelodyHeldCh15[noteNum]
elsif ch == 16 then result = MelodyHeldCh16[noteNum]
end
End
Function SetMelodyVelocity(ch : Integer, noteNum : Integer, vel : Integer)
if ch == 1 then MelodyHeldCh1[noteNum] = vel
elsif ch == 2 then MelodyHeldCh2[noteNum] = vel
elsif ch == 3 then MelodyHeldCh3[noteNum] = vel
elsif ch == 4 then MelodyHeldCh4[noteNum] = vel
elsif ch == 5 then MelodyHeldCh5[noteNum] = vel
elsif ch == 6 then MelodyHeldCh6[noteNum] = vel
elsif ch == 7 then MelodyHeldCh7[noteNum] = vel
elsif ch == 8 then MelodyHeldCh8[noteNum] = vel
elsif ch == 9 then MelodyHeldCh9[noteNum] = vel
elsif ch == 10 then MelodyHeldCh10[noteNum] = vel
elsif ch == 11 then MelodyHeldCh11[noteNum] = vel
elsif ch == 12 then MelodyHeldCh12[noteNum] = vel
elsif ch == 13 then MelodyHeldCh13[noteNum] = vel
elsif ch == 14 then MelodyHeldCh14[noteNum] = vel
elsif ch == 15 then MelodyHeldCh15[noteNum] = vel
elsif ch == 16 then MelodyHeldCh16[noteNum] = vel
end
End
Function GetTuningOffset(interval : Integer) Returns Integer
var centsTenths : Integer = 0
var pbRangeSemi : Integer = PitchBendRange_Semi
var rootCentsTenths : Integer = Tune_1root
if MicroTuningActive and MicroTuning_0Off_1On == 1 then
if interval == 0 then centsTenths = Tune_1root
elsif interval == 1 then centsTenths = Tune_2minorSecond
elsif interval == 2 then centsTenths = Tune_3MajorSecond
elsif interval == 3 then centsTenths = Tune_4minorThird
elsif interval == 4 then centsTenths = Tune_5MajorThird
elsif interval == 5 then centsTenths = Tune_6PerfectFourth
elsif interval == 6 then centsTenths = Tune_7diminishedFifth
elsif interval == 7 then centsTenths = Tune_8PerfectFifth
elsif interval == 8 then centsTenths = Tune_9minorSixth
elsif interval == 9 then centsTenths = Tune_10MajorSixth
elsif interval == 10 then
if NaturalSeventh_0Off_1On == 1 and Mode_0Fixed_1Input == 1 and AnalyzedChordType == 10 then
// Approximately 7:4 relative to the tuned root, in tenths of a cent.
centsTenths = rootCentsTenths - 312
else
centsTenths = Tune_11minorSeventh
end
elsif interval == 11 then centsTenths = Tune_12MajorSeventh
end
if pbRangeSemi < 1 then pbRangeSemi = 1 end
result = (centsTenths * 8192) / (pbRangeSemi * 1000)
else
result = 0
end
End
// Analyze all Divisi notes and ONLY lower notes from the selected bass input.
// Check each permitted chord shape against the actual lowest analyzed note.
Function FindRoot() Returns Integer
var i : Integer
var lowestDivisi : Integer = -1
var lowestNote : Integer = -1
var pitchClassCount : Integer = 0
var foundRoot : Integer = -1
var foundType : Integer = 0
var bassInterval : Integer
var candidateType : Integer
var acceptanceLevel : Integer = ChordAcceptanceLevel
var fixedRootValue : Integer = FixedChordRoot
AnalyzedChordType = 0
if Mode_0Fixed_1Input == 0 then
result = fixedRootValue - 1
else
for i = 0; i < 12; i = i + 1 do PitchClasses[i] = 0 end
for i = 0; i < 128; i = i + 1 do
if AccompHeldVelocities[i] > 0 then
PitchClasses[i % 12] = 1
if lowestDivisi == -1 then lowestDivisi = i end
end
end
if lowestDivisi >= 0 then
lowestNote = lowestDivisi
if ActiveBassInputMidiChannel > 0 and BassAnalysis_0Off_1On == 1 then
for i = 0; i < lowestDivisi; i = i + 1 do
if BassHeldVelocities[i] > 0 then
PitchClasses[i % 12] = 1
if i < lowestNote then lowestNote = i end
end
end
end
for i = 0; i < 12; i = i + 1 do
if PitchClasses[i] == 1 then pitchClassCount = pitchClassCount + 1 end
end
for i = 0; i < 12; i = i + 1 do
candidateType = 0
bassInterval = (lowestNote % 12 - i + 12) % 12
if PitchClasses[i] == 1 then
// Major family: root, major third or perfect fifth in the bass.
if PitchClasses[(i + 4) % 12] == 1 and PitchClasses[(i + 7) % 12] == 1 then
if bassInterval == 0 or bassInterval == 4 or bassInterval == 7 then
if pitchClassCount == 3 then
if bassInterval == 0 or acceptanceLevel >= 1 then candidateType = 1 end
elsif pitchClassCount == 4 then
if PitchClasses[(i + 2) % 12] == 1 and acceptanceLevel >= 4 then candidateType = 6
elsif PitchClasses[(i + 11) % 12] == 1 and acceptanceLevel >= 5 then candidateType = 8
elsif PitchClasses[(i + 10) % 12] == 1 and acceptanceLevel >= 6 then candidateType = 10
end
elsif pitchClassCount == 5 and acceptanceLevel >= 8 then
if PitchClasses[(i + 2) % 12] == 1 then
if PitchClasses[(i + 11) % 12] == 1 then candidateType = 12
elsif PitchClasses[(i + 10) % 12] == 1 then candidateType = 13
end
end
end
end
end
// Minor family: minor 7 permits ONLY root bass; minor 9 keeps normal inversions.
if PitchClasses[(i + 3) % 12] == 1 and PitchClasses[(i + 7) % 12] == 1 then
if bassInterval == 0 or bassInterval == 3 or bassInterval == 7 then
if pitchClassCount == 3 and acceptanceLevel >= 2 then candidateType = 2
elsif pitchClassCount == 4 then
if PitchClasses[(i + 2) % 12] == 1 and acceptanceLevel >= 4 then candidateType = 7
elsif PitchClasses[(i + 10) % 12] == 1 and bassInterval == 0 and acceptanceLevel >= 5 then candidateType = 9
end
elsif pitchClassCount == 5 and acceptanceLevel >= 7 then
if PitchClasses[(i + 2) % 12] == 1 and PitchClasses[(i + 10) % 12] == 1 then candidateType = 11 end
end
end
end
// Power and suspended families: root or perfect fifth in the bass.
if PitchClasses[(i + 7) % 12] == 1 and (bassInterval == 0 or bassInterval == 7) then
if acceptanceLevel >= 3 then
if pitchClassCount == 2 then candidateType = 3
elsif pitchClassCount == 3 then
if PitchClasses[(i + 2) % 12] == 1 then candidateType = 4
elsif PitchClasses[(i + 5) % 12] == 1 then candidateType = 5
end
elsif pitchClassCount == 4 and acceptanceLevel >= 8 then
if PitchClasses[(i + 5) % 12] == 1 and PitchClasses[(i + 10) % 12] == 1 then candidateType = 14 end
elsif pitchClassCount == 5 and acceptanceLevel >= 8 then
if PitchClasses[(i + 2) % 12] == 1 and PitchClasses[(i + 5) % 12] == 1 and PitchClasses[(i + 10) % 12] == 1 then candidateType = 15 end
end
end
end
// The exact pitch-class count rejects every additional foreign pitch class.
// For ambiguous suspended chords, prefer the bass as root.
if candidateType > 0 then
if foundRoot == -1 or bassInterval == 0 then
foundRoot = i
foundType = candidateType
end
end
end
end
end
AnalyzedChordType = foundType
result = foundRoot
end
End
Function SendPitchBendToChannels()
var i : Integer
var finalPB : Integer
for i = 1; i <= 16; i = i + 1 do
finalPB = GlobalPBValue + ChannelPBOffset[i]
if finalPB < 0 then finalPB = 0 end
if finalPB > 16383 then finalPB = 16383 end
if finalPB != LastSentPB[i] then
SendNow(MakePitchBendMessageEx(finalPB, i))
LastSentPB[i] = finalPB
end
end
End
Function ResetChannelPitchBend(ch : Integer)
ChannelPBOffset[ch] = 0
SendNow(MakePitchBendMessageEx(GlobalPBValue, ch))
LastSentPB[ch] = GlobalPBValue
End
// Bass and melody each use the highest held note on their channel.
Function UpdateMelodyPB()
var maxEngineCh : Integer = DivisiVoiceCount
var ch : Integer
var i : Integer
var highestNote : Integer
var interval : Integer
if MicroTuningActive then
for ch = 1; ch <= 16; ch = ch + 1 do
if ch > maxEngineCh then
ChannelPBOffset[ch] = 0
highestNote = -1
for i = 0; i < 128; i = i + 1 do
if ch == ActiveBassOutputMidiChannel then
if BassHeldVelocities[i] > 0 then highestNote = i end
elsif GetMelodyVelocity(ch, i) > 0 then highestNote = i
end
end
if highestNote >= 0 then
interval = (highestNote % 12 - GlobalActiveRoot + 12) % 12
ChannelPBOffset[ch] = GetTuningOffset(interval)
end
end
end
else
for ch = 1; ch <= 16; ch = ch + 1 do ChannelPBOffset[ch] = 0 end
end
SendPitchBendToChannels()
End
// Update pitch bend only: chord changes never retrigger notes in stable mode.
Function RefreshVoicePitchBend()
var i : Integer
var chTarget : Integer
var interval : Integer
for i = 1; i <= 16; i = i + 1 do ChannelPBOffset[i] = 0 end
for i = 0; i < 128; i = i + 1 do
chTarget = TargetVoices[i]
if chTarget > 0 then
interval = (i % 12 - GlobalActiveRoot + 12) % 12
ChannelPBOffset[chTarget] = GetTuningOffset(interval)
end
end
UpdateMelodyPB()
End
Function ClearStableVoice(noteNum : Integer)
var ch : Integer = AccompPlayingVoices[noteNum]
if ch > 0 then
ChannelPlayingNotes[ch] = -1
VoiceStartOrder[ch] = 0
end
AccompPlayingVoices[noteNum] = 0
TargetVoices[noteNum] = 0
SelectedNotes[noteNum] = 0
End
// A stolen note still participates in input analysis but never returns automatically.
Function ForceEndStableVoice(noteNum : Integer)
var ch : Integer = AccompPlayingVoices[noteNum]
if ch > 0 then
if DivisiSustainDown then SendNow(MakeControlChangeMessageEx(64, 0, ch)) end
SendNow(MakeNoteMessageEx(noteNum, 0, ch))
if DivisiSustainDown then SendNow(MakeControlChangeMessageEx(120, 0, ch)) end
ClearStableVoice(noteNum)
end
End
Function StartStableVoice(noteNum : Integer, vel : Integer)
var voiceLimit : Integer = DivisiVoiceCount
var ch : Integer = AccompPlayingVoices[noteNum]
var i : Integer
var oldestOrder : Integer = 0
// A genuine new attack on the same note uses its previous channel.
if ch > 0 then
ForceEndStableVoice(noteNum)
else
ch = 0
for i = 1; i <= voiceLimit; i = i + 1 do
if ch == 0 and ChannelPlayingNotes[i] < 0 then ch = i end
end
if ch == 0 then
for i = 1; i <= voiceLimit; i = i + 1 do
if ch == 0 or VoiceStartOrder[i] < oldestOrder then
ch = i
oldestOrder = VoiceStartOrder[i]
end
end
if ch > 0 then ForceEndStableVoice(ChannelPlayingNotes[ch]) end
end
end
if ch > 0 then
VoiceOrderCounter = VoiceOrderCounter + 1
VoiceStartOrder[ch] = VoiceOrderCounter
ChannelPlayingNotes[ch] = noteNum
AccompPlayingVoices[noteNum] = ch
TargetVoices[noteNum] = ch
SelectedNotes[noteNum] = 1
// Newly used channels also receive the current sustain pedal value.
if DivisiSustainDown then SendNow(MakeControlChangeMessageEx(64, DivisiSustainValue, ch)) end
RefreshVoicePitchBend()
SendNow(MakeNoteMessageEx(noteNum, vel, ch))
end
End
// Voice Leading keeps 128 paired attack/release records: at most 256 pending events.
Function ResetVoiceLeadingMemoryNow()
var ch : Integer
for ch = 1; ch <= 10; ch = ch + 1 do VLMemory[ch] = ChannelPlayingNotes[ch] end
End
Function ClearVoiceLeadingState()
var i : Integer
for i = 0; i < 128; i = i + 1 do VLState[i] = 0; VLInputToken[i] = 0 end
for i = 0; i <= 10; i = i + 1 do
VLMemory[i] = -1; VLReserved[i] = -1; VLChannelToken[i] = 0; VLReleased[i] = false
end
VLBatchActive = false; VLOverflow = false
End
Function VoiceLeadingBusy() Returns Boolean
var i : Integer
result = VLBatchActive
for i = 0; i < 128; i = i + 1 do
if VLState[i] > 0 then result = true end
end
End
Function RefreshVLReservations()
var i : Integer
var ch : Integer
for ch = 1; ch <= 10; ch = ch + 1 do VLReserved[ch] = -1 end
for i = 0; i < 128; i = i + 1 do
ch = VLChannel[i]
if VLState[i] > 0 and ch > 0 then VLReserved[ch] = VLNote[i] end
end
End
Function ExistingVLChannel(noteNum : Integer) Returns Integer
var ch : Integer
result = AccompPlayingVoices[noteNum]
for ch = 1; ch <= 10; ch = ch + 1 do
if VLReserved[ch] == noteNum then result = ch end
end
End
Function ReleaseVLVoice(noteNum : Integer, token : Integer)
var ch : Integer = AccompPlayingVoices[noteNum]
if ch > 0 then
if VLChannelToken[ch] == token then
SendNow(MakeNoteMessageEx(noteNum, 0, ch))
VLReleased[ch] = true
if not DivisiSustainDown then
ClearStableVoice(noteNum); VLChannelToken[ch] = 0
end
end
end
End
Function StartVLVoice(noteNum : Integer, vel : Integer, ch : Integer, token : Integer)
var oldNote : Integer = ChannelPlayingNotes[ch]
if oldNote >= 0 then ForceEndStableVoice(oldNote) end
ChannelPlayingNotes[ch] = noteNum; AccompPlayingVoices[noteNum] = ch
TargetVoices[noteNum] = ch; SelectedNotes[noteNum] = 1
VLChannelToken[ch] = token; VLReleased[ch] = false; VLMemory[ch] = noteNum
if DivisiSustainDown then SendNow(MakeControlChangeMessageEx(64, DivisiSustainValue, ch)) end
RefreshVoicePitchBend()
SendNow(MakeNoteMessageEx(noteNum, vel, ch))
End
Function MarkVLInputRelease(noteNum : Integer)
var i : Integer
var token : Integer = VLInputToken[noteNum]
var duration : Double
var currentTime : Double = TimeSinceStartup()
if token > 0 then
for i = 0; i < 128; i = i + 1 do
if VLState[i] == 1 or VLState[i] == 2 then
if VLToken[i] == token then
VLReleaseTime[i] = currentTime
duration = currentTime - VLAttackTime[i]
if duration < 1.0 then duration = 1.0 end
VLOffDue[i] = VLOnDue[i] + duration
end
end
end
ReleaseVLVoice(noteNum, token)
end
VLInputToken[noteNum] = 0
End
Function QueueVLAttack(noteNum : Integer, vel : Integer)
var i : Integer
var slot : Integer = -1
var ch : Integer
var pending : Boolean = false
var delayMs : Integer = VoiceLeadingDelay_ms
// A second attack implicitly releases the previous input generation of this pitch.
ch = ExistingVLChannel(noteNum)
MarkVLInputRelease(noteNum)
RefreshVLReservations()
if ch == 0 then ch = ExistingVLChannel(noteNum) end
for i = 0; i < 128; i = i + 1 do
if VLState[i] > 0 and VLNote[i] == noteNum then pending = true end
if VLState[i] == 0 and slot < 0 then slot = i end
end
if ch > 0 and not pending then
VLTokenCounter = VLTokenCounter + 1; VLInputToken[noteNum] = VLTokenCounter
StartVLVoice(noteNum, vel, ch, VLTokenCounter)
elsif slot >= 0 then
VLTokenCounter = VLTokenCounter + 1; VLInputToken[noteNum] = VLTokenCounter
VLState[slot] = 1; VLNote[slot] = noteNum; VLVelocity[slot] = vel
VLToken[slot] = VLTokenCounter; VLChannel[slot] = 0
VLAttackTime[slot] = TimeSinceStartup(); VLReleaseTime[slot] = -1.0
VLOnDue[slot] = 0.0; VLOffDue[slot] = 0.0
if not VLBatchActive then
VLBatchDue = TimeSinceStartup() + IntToFloat(delayMs); VLBatchActive = true
if VLOverflow then HelpUpdatePending = true end
VLOverflow = false
end
else
VLOverflow = true; HelpUpdatePending = true
end
End
// Suffix dynamic programming: unknown-channel penalty dominates every possible pitch distance.
// Reconstruction scans channels ascending, giving the exact lexicographic tie-break.
Function VLMaskSize(mask : Integer) Returns Integer
var remainder : Integer = mask
var i : Integer
result = 0
for i = 0; i < 10; i = i + 1 do result = result + remainder % 2; remainder = remainder / 2 end
End
Function VLGetCost(mask : Integer) Returns Integer
var offset : Integer = mask % 256
if mask < 256 then result = VLCost0[offset]
elsif mask < 512 then result = VLCost1[offset]
elsif mask < 768 then result = VLCost2[offset]
else
result = VLCost3[offset]
end
End
Function VLSetCost(mask : Integer, value : Integer)
var offset : Integer = mask % 256
if mask < 256 then VLCost0[offset] = value
elsif mask < 512 then VLCost1[offset] = value
elsif mask < 768 then VLCost2[offset] = value
else
VLCost3[offset] = value
end
End
Function SolveVLAssignment(freeCount : Integer, noteCount : Integer)
var mask : Integer
var slot : Integer
var used : Integer
var nextMask : Integer
var cost : Integer
var distance : Integer
var ch : Integer
var chosen : Integer
var stateCount : Integer = VLPowers[freeCount]
for mask = stateCount - 1; mask >= 0; mask = mask - 1 do
used = VLMaskSize(mask); VLSetCost(mask, 1000000)
if used == noteCount then VLSetCost(mask, 0)
elsif used < noteCount then
for slot = 0; slot < freeCount; slot = slot + 1 do
if (mask / VLPowers[slot]) % 2 == 0 then
ch = VLFreeChannels[slot]; distance = 1280
if VLMemory[ch] >= 0 then
distance = VLNewNotes[used] - VLMemory[ch]
if distance < 0 then distance = 0 - distance end
end
nextMask = mask + VLPowers[slot]; cost = distance + VLGetCost(nextMask)
if cost < VLGetCost(mask) then VLSetCost(mask, cost) end
end
end
end
end
mask = 0
for used = 0; used < noteCount; used = used + 1 do
chosen = -1
for slot = 0; slot < freeCount; slot = slot + 1 do
if (mask / VLPowers[slot]) % 2 == 0 and chosen < 0 then
ch = VLFreeChannels[slot]; distance = 1280
if VLMemory[ch] >= 0 then
distance = VLNewNotes[used] - VLMemory[ch]
if distance < 0 then distance = 0 - distance end
end
nextMask = mask + VLPowers[slot]
if distance + VLGetCost(nextMask) == VLGetCost(mask) then chosen = slot end
end
end
if chosen >= 0 then
VLAssignments[VLNewNotes[used]] = VLFreeChannels[chosen]
mask = mask + VLPowers[chosen]
end
end
End
Function FlushVLBatch()
var i : Integer
var n : Integer
var ch : Integer
var j : Integer
var freeCount : Integer = 0
var selectedCount : Integer = 0
var noteCount : Integer = 0
var limit : Integer = DivisiVoiceCount
var priority : Integer = Priority_0Low_1High
var earliest : Double
var firstDue : Double
var duration : Double
var currentTime : Double = TimeSinceStartup()
if VLBatchActive and currentTime >= VLBatchDue then
RefreshVLReservations()
for n = 0; n < 128; n = n + 1 do VLAssignments[n] = 0; VLPendingPitch[n] = 0 end
for i = 0; i < 128; i = i + 1 do
if VLState[i] == 1 then VLPendingPitch[VLNote[i]] = 1 end
end
for ch = 1; ch <= limit; ch = ch + 1 do
if ChannelPlayingNotes[ch] < 0 and VLReserved[ch] < 0 then
VLFreeChannels[freeCount] = ch; freeCount = freeCount + 1
end
end
for n = 0; n < 128; n = n + 1 do
if VLPendingPitch[n] > 0 then
ch = ExistingVLChannel(n)
if ch > 0 then VLAssignments[n] = ch end
end
end
// Priority selects only new pitches; occupied or reserved channels are protected.
if priority == 0 then
for n = 0; n < 128; n = n + 1 do
if VLPendingPitch[n] > 0 and VLAssignments[n] == 0 and selectedCount < freeCount then
VLPendingPitch[n] = 2; selectedCount = selectedCount + 1
end
end
else
for n = 127; n >= 0; n = n - 1 do
if VLPendingPitch[n] > 0 and VLAssignments[n] == 0 and selectedCount < freeCount then
VLPendingPitch[n] = 2; selectedCount = selectedCount + 1
end
end
end
for n = 127; n >= 0; n = n - 1 do
if VLPendingPitch[n] == 2 then VLNewNotes[noteCount] = n; noteCount = noteCount + 1 end
end
if noteCount > 0 then SolveVLAssignment(freeCount, noteCount) end
for n = 0; n < 128; n = n + 1 do
if VLPendingPitch[n] > 0 then
ch = VLAssignments[n]; earliest = currentTime
for i = 0; i < 128; i = i + 1 do
if VLState[i] == 1 and VLNote[i] == n then
if VLAttackTime[i] < earliest then earliest = VLAttackTime[i] end
end
end
firstDue = currentTime
// Preserve order even if collection time changes while earlier repetitions are scheduled.
for j = 0; j < 128; j = j + 1 do
if VLState[j] == 2 and VLNote[j] == n then
duration = VLOnDue[j] + earliest - VLAttackTime[j]
if duration > firstDue then firstDue = duration end
end
end
for i = 0; i < 128; i = i + 1 do
if VLState[i] == 1 and VLNote[i] == n then
if ch > 0 then
VLState[i] = 2; VLChannel[i] = ch
VLOnDue[i] = firstDue + VLAttackTime[i] - earliest
if VLReleaseTime[i] >= 0.0 then
duration = VLReleaseTime[i] - VLAttackTime[i]
if duration < 1.0 then duration = 1.0 end
VLOffDue[i] = VLOnDue[i] + duration
end
else
VLState[i] = 0
if VLInputToken[n] == VLToken[i] then VLInputToken[n] = 0 end
end
end
end
end
end
VLBatchActive = false; RefreshVLReservations()
end
End
Function RunVLEvents()
var j : Integer
var lateness : Double
var iteration : Integer
var i : Integer
var nextEvent : Integer
var due : Double
var nextDue : Double
var currentTime : Double = TimeSinceStartup()
var token : Integer
FlushVLBatch()
currentTime = TimeSinceStartup()
// At most two output events per paired record; no uncancellable SendLater messages.
for iteration = 0; iteration < 256; iteration = iteration + 1 do
nextEvent = -1; nextDue = currentTime + 1.0
for i = 0; i < 128; i = i + 1 do
due = currentTime + 1.0
if VLState[i] == 2 then due = VLOnDue[i]
elsif VLState[i] == 3 then due = VLOffDue[i]
end
if due <= currentTime then
if nextEvent < 0 then nextEvent = i; nextDue = due
elsif due < nextDue then nextEvent = i; nextDue = due
elsif due == nextDue then
if VLToken[i] < VLToken[nextEvent] then nextEvent = i end
end
end
end
if nextEvent < 0 then iteration = 256
else
i = nextEvent; token = VLToken[i]
if VLState[i] == 2 then
// Late timer ticks shift this pitch's future attacks/releases together.
lateness = TimeSinceStartup() - VLOnDue[i]
if lateness > 0.0 then
for j = 0; j < 128; j = j + 1 do
if VLState[j] == 2 and VLNote[j] == VLNote[i] and VLToken[j] >= token then
VLOnDue[j] = VLOnDue[j] + lateness
VLOffDue[j] = VLOffDue[j] + lateness
end
end
end
StartVLVoice(VLNote[i], VLVelocity[i], VLChannel[i], token)
if VLReleaseTime[i] >= 0.0 then VLState[i] = 3
else
VLState[i] = 0
end
else
ReleaseVLVoice(VLNote[i], token); VLState[i] = 0
end
end
end
RefreshVLReservations(); RefreshVoicePitchBend()
End
Function EnforceStableVoiceLimit()
var i : Integer
var voiceLimit : Integer = DivisiVoiceCount
var ch : Integer
var changed : Boolean = false
if ActiveVoiceMode == 1 or ActiveVoiceMode == 2 then
for ch = voiceLimit + 1; ch <= 10; ch = ch + 1 do
if ChannelPlayingNotes[ch] >= 0 then
ForceEndStableVoice(ChannelPlayingNotes[ch])
changed = true
end
if ActiveVoiceMode == 2 then
VLMemory[ch] = -1; VLReserved[ch] = -1; VLChannelToken[ch] = 0
for i = 0; i < 128; i = i + 1 do
if VLState[i] > 0 and VLChannel[i] == ch then
if VLInputToken[VLNote[i]] == VLToken[i] then VLInputToken[VLNote[i]] = 0 end
VLState[i] = 0
end
end
end
end
if changed then RefreshVoicePitchBend() end
end
End
Function ApplyVoiceMode()
var requestedMode : Integer = VoiceMode_0Sorted_1Stable
var i : Integer
var busy : Boolean = DivisiSustainDown
for i = 0; i < 128; i = i + 1 do
if AccompHeldVelocities[i] > 0 or AccompPlayingVoices[i] > 0 then busy = true end
end
if VoiceLeadingBusy() then busy = true end
if requestedMode != ActiveVoiceMode and not busy then
ClearVoiceLeadingState()
ActiveVoiceMode = requestedMode
for i = 0; i <= 10; i = i + 1 do ChannelPlayingNotes[i] = -1; VoiceStartOrder[i] = 0 end
VoiceOrderCounter = 0
ParameterUpdatePending = true
end
if DisplayedVoiceMode != ActiveVoiceMode or DisplayedRequestedMode != requestedMode then
HelpUpdatePending = true
DisplayedVoiceMode = ActiveVoiceMode
DisplayedRequestedMode = requestedMode
end
End
// Shared update path for manual changes and remote CC changes.
Function RequestParameterUpdate(helpIndex : Integer, musicalChange : Boolean)
if musicalChange then ParameterUpdatePending = true end
// A manual or remote master-off change clears bends without waiting for the timer.
if helpIndex == 24 and MicroTuning_0Off_1On == 0 then
MicroTuningActive = false
RefreshVoicePitchBend()
end
if helpIndex == 26 or helpIndex == 39 then VLResetCCHigh = false end
if helpIndex == 37 and ResetVoiceLeadingMemory == 1 then
ResetVoiceLeadingMemoryNow(); ResetVoiceLeadingMemory = 0
VLOverflow = false
end
HelpParameterIndex = helpIndex
HelpUpdatePending = true
End
Function GetAssignedCC(index : Integer) Returns Integer
result = -1
if index == 0 then result = CC_VoiceMode
elsif index == 1 then result = CC_RootMode
elsif index == 2 then result = CC_FixedRoot
elsif index == 3 then result = CC_VoiceCount
elsif index == 4 then result = CC_Priority
elsif index == 5 then result = CC_ChordAcceptanceLevel
elsif index == 6 then result = CC_BassAnalysis
elsif index == 7 then result = CC_MicroTuning
elsif index == 8 then result = CC_NaturalSeventh
elsif index == 9 then result = CC_VoiceLeadingDelay
elsif index == 10 then result = CC_VoiceLeadingReset
end
End
Function GetCCTargetName(index : Integer) Returns String
result = "Unknown"
if index == 0 then result = "Voice Allocation"
elsif index == 1 then result = "Root Mode"
elsif index == 2 then result = "Fixed Root"
elsif index == 3 then result = "Voice Count"
elsif index == 4 then result = "Note Priority"
elsif index == 5 then result = "Chord Acceptance Level"
elsif index == 6 then result = "Bass Chord Analysis"
elsif index == 7 then result = "Microtuning"
elsif index == 8 then result = "Natural Seventh"
elsif index == 9 then result = "Voice Leading Collection Time"
elsif index == 10 then result = "Voice Leading Memory Reset"
end
End
Function GetCCUseCount(ccNum : Integer) Returns Integer
var i : Integer
result = 0
if ccNum >= 0 then
for i = 0; i < 11; i = i + 1 do
if GetAssignedCC(i) == ccNum then result = result + 1 end
end
end
End
Function IsReservedControlCC(ccNum : Integer) Returns Boolean
result = ccNum == 64 or ccNum >= 120
End
Function GetCCAssignmentStatus(index : Integer) Returns String
var ccNum : Integer = GetAssignedCC(index)
var controlChannel : Integer = ControlMidiChannel
var i : Integer
result = GetCCTargetName(index) + ": "
if ccNum < 0 then
result = result + "unassigned (-1)."
else
result = result + "CC " + ccNum + "."
if IsReservedControlCC(ccNum) then
result = result + " Inactive: CC64 and CC120-127 are reserved."
elsif GetCCUseCount(ccNum) > 1 then
result = result + " Inactive duplicate; also assigned to "
for i = 0; i < 11; i = i + 1 do
if i != index and GetAssignedCC(i) == ccNum then
result = result + GetCCTargetName(i) + "; "
end
end
end
end
if controlChannel == 0 then
result = result + " CC remote control disabled (channel 0)."
else
result = result + " Control input channel " + controlChannel + "."
end
End
Function GetCCConfigurationWarning() Returns String
var i : Integer
var ccNum : Integer
result = ""
for i = 0; i < 11; i = i + 1 do
ccNum = GetAssignedCC(i)
if ccNum >= 0 then
if IsReservedControlCC(ccNum) then
result = result + " " + GetCCTargetName(i) + " CC " + ccNum + " inactive (reserved)."
elsif GetCCUseCount(ccNum) > 1 then
result = result + " " + GetCCTargetName(i) + " CC " + ccNum + " inactive (duplicate)."
end
end
end
End
Function GetCCAssignmentHelp(index : Integer) Returns String
result = GetCCAssignmentStatus(index)
if index == 0 then result = result + " Values 0-42 select Sorted; 43-85 Stable; 86-127 Voice Leading. CC127 now selects Voice Leading. Switching waits for keys, voices, sustain and queued events."
elsif index == 1 then result = result + " Values 0-63 select Fixed Root; 64-127 select Input chord analysis. Microtuning Enabled remains the master switch."
elsif index == 2 then result = result + " Values 0-127 select 12 equal-width ranges: C, C#, D, D#, E, F, F#, G, G#, A, A#, B. Used only in Fixed mode."
elsif index == 3 then result = result + " Values 0-127 select 10 equal-width ranges for 1-10 Divisi output voices. Every held Divisi input note still enters analysis."
elsif index == 4 then result = result + " Values 0-63 select Low; 64-127 select High. Sorted selects held notes; Voice Leading selects only new overflow notes and protects occupied voices. Stable ignores priority."
elsif index == 5 then result = result + " Values 0-127 select 9 equal-width ranges for cumulative levels 0-8: root-position major, major inversions, minor, power/sus, add9, major7/minor7, dominant7, minor9, then extended ninth/sus chords."
elsif index == 6 then result = result + " Values 0-63 exclude external bass from chord analysis; 64-127 include eligible lower bass notes. Bass routing and playback continue."
elsif index == 7 then result = result + " Values 0-63 disable all microtuning immediately; 64-127 enable the current fixed-root or accepted-chord tuning. Normal pitch bend continues."
elsif index == 8 then result = result + " Values 0-63 use Minor Seventh Tuning; 64-127 enable the natural seventh for accepted dominant7 chords in Input mode only."
elsif index == 9 then result = result + " Values 0-127 select collection time 0-100 ms. Only Voice Leading new attacks wait; running windows keep their original deadline. At 0 ms input order can affect allocation."
elsif index == 10 then result = result + " A low-to-high transition (0-63 to 64-127) resets free-channel memory once. The first high value after activation also triggers; repeated high values do not. Sounding voices stay assigned."
end
result = result + " -1 leaves this function unassigned; CC0 is valid. Active control CCs are consumed."
End
// Consume only unique, non-reserved assignments on the selected control channel.
Function HandleControlCC(ch : Integer, ccNum : Integer, ccVal : Integer) Returns Boolean
var controlChannel : Integer = ControlMidiChannel
var index : Integer = -1
var i : Integer
var newValue : Integer
var oldValue : Integer
result = false
if controlChannel > 0 and ch == controlChannel and not IsReservedControlCC(ccNum) then
if GetCCUseCount(ccNum) == 1 then
for i = 0; i < 11; i = i + 1 do
if GetAssignedCC(i) == ccNum then index = i end
end
result = true
newValue = 0
if ccVal >= 64 then newValue = 1 end
if index == 0 then newValue = (ccVal * 3) / 128
elsif index == 9 then newValue = (ccVal * 101) / 128
elsif index == 2 then newValue = (ccVal * 12) / 128 + 1
elsif index == 3 then newValue = (ccVal * 10) / 128 + 1
elsif index == 5 then newValue = (ccVal * 9) / 128
end
if index == 0 then
oldValue = VoiceMode_0Sorted_1Stable
if oldValue != newValue then
VoiceMode_0Sorted_1Stable = newValue
RequestParameterUpdate(0, true)
end
elsif index == 1 then
oldValue = Mode_0Fixed_1Input
if oldValue != newValue then
Mode_0Fixed_1Input = newValue
RequestParameterUpdate(1, true)
end
elsif index == 2 then
oldValue = FixedChordRoot
if oldValue != newValue then
FixedChordRoot = newValue
RequestParameterUpdate(2, true)
end
elsif index == 3 then
oldValue = DivisiVoiceCount
if oldValue != newValue then
DivisiVoiceCount = newValue
RequestParameterUpdate(4, true)
end
elsif index == 4 then
oldValue = Priority_0Low_1High
if oldValue != newValue then
Priority_0Low_1High = newValue
RequestParameterUpdate(5, true)
end
elsif index == 5 then
oldValue = ChordAcceptanceLevel
if oldValue != newValue then
ChordAcceptanceLevel = newValue
RequestParameterUpdate(8, true)
end
elsif index == 6 then
oldValue = BassAnalysis_0Off_1On
if oldValue != newValue then
BassAnalysis_0Off_1On = newValue
RequestParameterUpdate(25, true)
end
elsif index == 7 then
oldValue = MicroTuning_0Off_1On
if oldValue != newValue then
MicroTuning_0Off_1On = newValue
RequestParameterUpdate(24, true)
end
elsif index == 8 then
oldValue = NaturalSeventh_0Off_1On
if oldValue != newValue then
NaturalSeventh_0Off_1On = newValue
RequestParameterUpdate(22, true)
end
elsif index == 9 then
oldValue = VoiceLeadingDelay_ms
if oldValue != newValue then
VoiceLeadingDelay_ms = newValue; RequestParameterUpdate(36, false)
end
elsif index == 10 then
if ccVal >= 64 and not VLResetCCHigh then
ResetVoiceLeadingMemory = 1; RequestParameterUpdate(37, false)
end
VLResetCCHigh = ccVal >= 64
end
end
end
End
// The help area describes the last changed control without interrupting MIDI processing.
Function UpdateParameterHelp()
var message : String = ""
var requestedMode : Integer = VoiceMode_0Sorted_1Stable
var level : Integer = ChordAcceptanceLevel
var bassInputChannel : Integer = BassInputMidiChannel
var divisiInputChannel : Integer = InputMidiChannel
var controlChannel : Integer = ControlMidiChannel
if HelpParameterIndex == 0 then
if requestedMode == 0 then message = "Sorted: assign selected notes by descending pitch. Low/High priority chooses notes."
elsif requestedMode == 1 then
message = "Stable: notes keep their channels. A new attack replaces the oldest voice when full."
else
message = "Voice Leading: held and sustained voices keep their channels. Grouped new notes minimize movement from remembered pitches; full voices are protected. Short attacks and repetitions are preserved."
end
message = message + " Modes: 0 Sorted reorders voices; 1 Stable binds attacks and replaces the oldest full voice; 2 Voice Leading groups new attacks using persistent channel memory. Collection Time sets its window; played pitches are unchanged."
elsif HelpParameterIndex == 1 then
if Mode_0Fixed_1Input == 0 then message = "Fixed: tune relative to Fixed Root when Microtuning Enabled is On. Chord acceptance and natural seventh are bypassed."
else
message = "Input: all held Divisi notes determine the chord; eligible bass notes join when Bass Chord Analysis is On. Microtuning Enabled is the master switch."
end
elsif HelpParameterIndex == 2 then
message = "Fixed Root: 1=C, 2=C#, 3=D, 4=D#, 5=E, 6=F, 7=F#, 8=G, 9=G#, 10=A, 11=A#, 12=B. Fixed mode only."
elsif HelpParameterIndex == 3 then
message = "Divisi Input: MIDI channel for ensemble notes. All held input notes enter chord analysis."
elsif HelpParameterIndex == 4 then
message = "Voice Count: use output channels 1 up to this number. All held input notes enter chord analysis. Reducing the count ends higher-channel voices. Voice Leading protects occupied channels; increasing the count does not revive silent held notes."
elsif HelpParameterIndex == 5 then
if Priority_0Low_1High == 0 then message = "Low Priority: Sorted selects the lowest held notes. Voice Leading selects the lowest new overflow notes; occupied voices stay protected. Stable ignores priority."
else
message = "High Priority: Sorted selects the highest held notes. Voice Leading selects the highest new overflow notes; occupied voices stay protected. Stable ignores priority."
end
elsif HelpParameterIndex == 6 then
message = "Pitch Bend Range: must match the instrument's range in semitones for correct tuning offsets."
elsif HelpParameterIndex == 7 then
message = "Chord Delay: harmonic analysis waits after input changes. Stable attacks remain immediate; Voice Leading uses its separate collection time. Running note releases remain immediate."
elsif HelpParameterIndex == 8 then
if level == 0 then message = "Level 0: Major triads only, with the root in the bass."
elsif level == 1 then message = "Level 1: Major triads, including inversions with the third or fifth in the bass."
elsif level == 2 then message = "Level 2: Adds minor triads and their permitted inversions."
elsif level == 3 then message = "Level 3: Adds power chords, sus2 and sus4. Suspended and power chords require root or fifth bass."
elsif level == 4 then message = "Level 4: Adds major add9 and minor add9 chords."
elsif level == 5 then message = "Level 5: Adds major7 and minor7. Minor7 requires the root in the bass."
elsif level == 6 then message = "Level 6: Adds dominant7 chords with root, third or fifth in the bass."
elsif level == 7 then message = "Level 7: Adds minor9 chords. This matches the V3.1 chord selection."
else
message = "Level 8: Adds major9, dominant9, dominant7sus4 and dominant9sus4. Existing bass rules still apply."
end
elsif HelpParameterIndex == 9 then
if bassInputChannel == 0 then message = "Bass Input: external bass analysis and dedicated routing are disabled."
elsif bassInputChannel == divisiInputChannel then message = "Bass Input: external bass is disabled because its input channel matches Divisi Input."
else
message = "Bass Input: route this channel to Bass Output. Bass Chord Analysis determines whether lower bass notes enter chord detection; playback continues when analysis is Off."
end
elsif HelpParameterIndex == 10 then
message = "Bass Output: reserved channel 11-16. Highest held bass note sets its shared microtuning offset."
elsif HelpParameterIndex == 11 then
message = "Root Tuning: offset for the chord root. 10=1 cent; 153=15.3 cents. Natural seventh follows this offset."
elsif HelpParameterIndex == 12 then
message = "Minor Second: tuning offset 1 semitone above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 13 then
message = "Major Second: tuning offset 2 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 14 then
message = "Minor Third: tuning offset 3 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 15 then
message = "Major Third: tuning offset 4 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 16 then
message = "Perfect Fourth: tuning offset 5 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 17 then
message = "Diminished Fifth: tuning offset 6 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 18 then
message = "Perfect Fifth: tuning offset 7 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 19 then
message = "Minor Sixth: tuning offset 8 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 20 then
message = "Major Sixth: tuning offset 9 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 21 then
message = "Minor Seventh: offset 10 semitones above root. 10=1 cent. Natural Seventh can override this for dominant7."
elsif HelpParameterIndex == 22 then
if NaturalSeventh_0Off_1On == 0 then message = "Natural Seventh Off: use Minor Seventh Tuning for every accepted chord."
else
message = "Natural Seventh On: accepted dominant7 in Input mode uses approximately 7:4, following Root Tuning. Other forms and Fixed mode keep normal tuning; Microtuning Enabled must be On."
end
elsif HelpParameterIndex == 23 then
message = "Major Seventh: tuning offset 11 semitones above the chord root. 10=1 cent; positive raises, negative lowers."
elsif HelpParameterIndex == 24 then
if MicroTuning_0Off_1On == 0 then
message = "Microtuning Off: all 16 micro-offsets are zero, including held Divisi, bass and melody notes. Fixed and Input tuning are disabled; normal pitch bend continues."
else
message = "Microtuning On: Fixed mode uses Fixed Root; Input mode tunes only accepted chords. Current held voices are updated without Stable or Voice Leading retriggers; normal pitch bend continues."
end
elsif HelpParameterIndex == 25 then
if BassAnalysis_0Off_1On == 0 then
message = "Bass Analysis Off: external bass does not influence chord recognition. Bass routing and playback continue, tuned relative to any accepted Divisi chord. Fixed mode is unaffected."
else
message = "Bass Analysis On: held external bass notes below the lowest held Divisi note join chord recognition. Higher bass notes do not count; bass playback continues. Fixed mode is unaffected."
end
if bassInputChannel == 0 then
message = message + " External bass is disabled because Bass Input is 0."
elsif bassInputChannel == divisiInputChannel then
message = message + " External bass is disabled because Bass Input matches Divisi Input."
end
elsif HelpParameterIndex == 26 then
if controlChannel == 0 then
message = "CC remote control disabled (channel 0). Select 1-16 to enable assignments on that input channel. Manual controls remain available."
else
message = "CC Control: only input channel " + controlChannel + " changes assigned controls. Unique, non-reserved assigned CCs are consumed; other CCs keep normal routing. -1 means unassigned; CC0 is valid."
end
elsif HelpParameterIndex >= 27 and HelpParameterIndex <= 35 then
message = GetCCAssignmentHelp(HelpParameterIndex - 27)
elsif HelpParameterIndex == 36 then
message = "Voice Leading Collection: 0-100 ms, default 20. The first new attack fixes the deadline; later attacks do not extend it. Running note releases are immediate. Buffered short notes keep their played duration; repetitions keep their timing. Changes apply to the next window. At 0 ms allocation can depend on input order."
elsif HelpParameterIndex == 37 then
message = "Memory Reset: set 1 to reset once; this control returns to 0. Free channels forget previous pitches; sounding channels retain their current note. Notes, pedal, pending events and tuning remain unchanged. Memory otherwise survives pauses and resets on activation, applied mode/input changes or Divisi panic."
elsif HelpParameterIndex == 38 then message = GetCCAssignmentHelp(9)
elsif HelpParameterIndex == 39 then message = GetCCAssignmentHelp(10)
end
if requestedMode != ActiveVoiceMode then
message = message + " Mode change pending: release all Divisi keys and sustain; pending attacks and releases must finish."
end
if HelpParameterIndex < 27 then message = message + GetCCConfigurationWarning() end
if VLOverflow then message = message + " Voice Leading buffer full: additional attacks were dropped; existing releases are preserved." end
SetDisplayMessage(message)
HelpUpdatePending = false
End
Function EvaluateAccompaniment()
var voiceLimit : Integer = DivisiVoiceCount
var i : Integer
var voiceCount : Integer = 0
var currentCh : Integer = 1
var analyzedRoot : Integer
analyzedRoot = FindRoot()
MicroTuningActive = false
if analyzedRoot >= 0 then
GlobalActiveRoot = analyzedRoot
if MicroTuning_0Off_1On == 1 then MicroTuningActive = true end
end
if ActiveVoiceMode == 1 or ActiveVoiceMode == 2 then
// Keep channel assignments for sustained notes; stolen notes have no output.
for i = 0; i < 128; i = i + 1 do
TargetVoices[i] = AccompPlayingVoices[i]
SelectedNotes[i] = 0
if TargetVoices[i] > 0 then SelectedNotes[i] = 1 end
end
RefreshVoicePitchBend()
else
// Original allocation: select low/high notes, then assign in descending pitch order.
for i = 0; i < 128; i = i + 1 do TargetVoices[i] = 0; SelectedNotes[i] = 0 end
if Priority_0Low_1High == 0 then
for i = 0; i < 128; i = i + 1 do
if AccompHeldVelocities[i] > 0 and voiceCount < voiceLimit then
SelectedNotes[i] = 1; voiceCount = voiceCount + 1
end
end
else
for i = 127; i >= 0; i = i - 1 do
if AccompHeldVelocities[i] > 0 and voiceCount < voiceLimit then
SelectedNotes[i] = 1; voiceCount = voiceCount + 1
end
end
end
for i = 127; i >= 0; i = i - 1 do
if SelectedNotes[i] == 1 and currentCh <= voiceLimit and currentCh <= 10 then
TargetVoices[i] = currentCh
currentCh = currentCh + 1
end
end
RefreshVoicePitchBend()
for i = 0; i < 128; i = i + 1 do
if AccompPlayingVoices[i] > 0 and TargetVoices[i] != AccompPlayingVoices[i] then
SendNow(MakeNoteMessageEx(i, 0, AccompPlayingVoices[i]))
end
end
for i = 0; i < 128; i = i + 1 do
if TargetVoices[i] > 0 and TargetVoices[i] != AccompPlayingVoices[i] then
SendNow(MakeNoteMessageEx(i, AccompHeldVelocities[i], TargetVoices[i]))
end
AccompPlayingVoices[i] = TargetVoices[i]
end
end
End
Function ScheduleChordEvaluation()
var i : Integer
var keysHeldCount : Integer = 0
var delayMs : Integer = ChordDelay_ms
for i = 0; i < 128; i = i + 1 do
if AccompHeldVelocities[i] > 0 then keysHeldCount = keysHeldCount + 1 end
end
if keysHeldCount == 0 then
EvaluationScheduled = false
EvaluateAccompaniment()
else
TargetTime = TimeSinceStartup() + IntToFloat(delayMs)
EvaluationScheduled = true
end
End
Function StopMelodyChannel(ch : Integer)
var i : Integer
var maxEngineCh : Integer = DivisiVoiceCount
// Also end notes held by the pedal after their keys have been released.
// Do not send channel-wide All Notes Off on occupied Divisi outputs.
if ch > maxEngineCh then SendNow(MakeControlChangeMessageEx(64, 0, ch)) end
for i = 0; i < 128; i = i + 1 do
if GetMelodyVelocity(ch, i) > 0 then SendNow(MakeNoteMessageEx(i, 0, ch)) end
SetMelodyVelocity(ch, i, 0)
end
if ch > maxEngineCh then
SendNow(MakeControlChangeMessageEx(123, 0, ch))
ResetChannelPitchBend(ch)
end
End
Function StopBassOutput()
var i : Integer
SendNow(MakeControlChangeMessageEx(64, 0, ActiveBassOutputMidiChannel))
for i = 0; i < 128; i = i + 1 do
if BassPlayingVelocities[i] > 0 then
SendNow(MakeNoteMessageEx(i, 0, ActiveBassOutputMidiChannel))
end
BassPlayingVelocities[i] = 0
end
SendNow(MakeControlChangeMessageEx(123, 0, ActiveBassOutputMidiChannel))
ResetChannelPitchBend(ActiveBassOutputMidiChannel)
End
// Check before each event so routing changes cannot leave stale trackers.
Function ApplyRoutingChanges()
var newInput : Integer = InputMidiChannel
var newBassInput : Integer = BassInputMidiChannel
var newBassOutput : Integer = BassOutputMidiChannel
var i : Integer
if newBassInput == newInput then newBassInput = 0 end
if newInput != ActiveInputMidiChannel then
for i = 0; i < 128; i = i + 1 do
if AccompPlayingVoices[i] > 0 then
SendNow(MakeNoteMessageEx(i, 0, AccompPlayingVoices[i]))
end
AccompHeldVelocities[i] = 0; AccompPlayingVoices[i] = 0
TargetVoices[i] = 0; SelectedNotes[i] = 0
end
for i = 1; i <= 10; i = i + 1 do
if ChannelPlayingNotes[i] >= 0 then ChannelPlayingNotes[i] = -1 end
VoiceStartOrder[i] = 0
end
if DivisiSustainDown then
for i = 1; i <= DivisiVoiceCount; i = i + 1 do
SendNow(MakeControlChangeMessageEx(64, 0, i))
SendNow(MakeControlChangeMessageEx(120, 0, i))
end
end
DivisiSustainDown = false; DivisiSustainValue = 0
if newInput != ActiveBassOutputMidiChannel then StopMelodyChannel(newInput) end
ClearVoiceLeadingState()
ActiveInputMidiChannel = newInput
EvaluationScheduled = false
ParameterUpdatePending = true
end
if newBassInput != ActiveBassInputMidiChannel or newBassOutput != ActiveBassOutputMidiChannel then
StopBassOutput()
if newBassInput != ActiveBassInputMidiChannel then
for i = 0; i < 128; i = i + 1 do BassHeldVelocities[i] = 0 end
if newBassInput > 0 then StopMelodyChannel(newBassInput) end
end
if newBassOutput != ActiveBassOutputMidiChannel then StopMelodyChannel(newBassOutput) end
ActiveBassInputMidiChannel = newBassInput
ActiveBassOutputMidiChannel = newBassOutput
UpdateMelodyPB()
for i = 0; i < 128; i = i + 1 do
if BassHeldVelocities[i] > 0 then
SendNow(MakeNoteMessageEx(i, BassHeldVelocities[i], ActiveBassOutputMidiChannel))
BassPlayingVelocities[i] = BassHeldVelocities[i]
end
end
ParameterUpdatePending = true
end
ApplyVoiceMode()
EnforceStableVoiceLimit()
End
// Disjoint callbacks identify the changed control and use one update path.
On ParameterValueChanged
Matching VoiceMode_0Sorted_1Stable
RequestParameterUpdate(0, true)
End
On ParameterValueChanged
Matching Mode_0Fixed_1Input
RequestParameterUpdate(1, true)
End
On ParameterValueChanged
Matching FixedChordRoot
RequestParameterUpdate(2, true)
End
On ParameterValueChanged
Matching InputMidiChannel
RequestParameterUpdate(3, true)
End
On ParameterValueChanged
Matching DivisiVoiceCount
RequestParameterUpdate(4, true)
End
On ParameterValueChanged
Matching Priority_0Low_1High
RequestParameterUpdate(5, true)
End
On ParameterValueChanged
Matching PitchBendRange_Semi
RequestParameterUpdate(6, true)
End
On ParameterValueChanged
Matching ChordDelay_ms
RequestParameterUpdate(7, true)
End
On ParameterValueChanged
Matching ChordAcceptanceLevel
RequestParameterUpdate(8, true)
End
On ParameterValueChanged
Matching BassInputMidiChannel
RequestParameterUpdate(9, true)
End
On ParameterValueChanged
Matching BassOutputMidiChannel
RequestParameterUpdate(10, true)
End
On ParameterValueChanged
Matching Tune_1root
RequestParameterUpdate(11, true)
End
On ParameterValueChanged
Matching Tune_2minorSecond
RequestParameterUpdate(12, true)
End
On ParameterValueChanged
Matching Tune_3MajorSecond
RequestParameterUpdate(13, true)
End
On ParameterValueChanged
Matching Tune_4minorThird
RequestParameterUpdate(14, true)
End
On ParameterValueChanged
Matching Tune_5MajorThird
RequestParameterUpdate(15, true)
End
On ParameterValueChanged
Matching Tune_6PerfectFourth
RequestParameterUpdate(16, true)
End
On ParameterValueChanged
Matching Tune_7diminishedFifth
RequestParameterUpdate(17, true)
End
On ParameterValueChanged
Matching Tune_8PerfectFifth
RequestParameterUpdate(18, true)
End
On ParameterValueChanged
Matching Tune_9minorSixth
RequestParameterUpdate(19, true)
End
On ParameterValueChanged
Matching Tune_10MajorSixth
RequestParameterUpdate(20, true)
End
On ParameterValueChanged
Matching Tune_11minorSeventh
RequestParameterUpdate(21, true)
End
On ParameterValueChanged
Matching NaturalSeventh_0Off_1On
RequestParameterUpdate(22, true)
End
On ParameterValueChanged
Matching Tune_12MajorSeventh
RequestParameterUpdate(23, true)
End
On ParameterValueChanged
Matching MicroTuning_0Off_1On
RequestParameterUpdate(24, true)
End
On ParameterValueChanged
Matching BassAnalysis_0Off_1On
RequestParameterUpdate(25, true)
End
On ParameterValueChanged
Matching ControlMidiChannel
RequestParameterUpdate(26, false)
End
On ParameterValueChanged
Matching CC_VoiceMode
RequestParameterUpdate(27, false)
End
On ParameterValueChanged
Matching CC_RootMode
RequestParameterUpdate(28, false)
End
On ParameterValueChanged
Matching CC_FixedRoot
RequestParameterUpdate(29, false)
End
On ParameterValueChanged
Matching CC_VoiceCount
RequestParameterUpdate(30, false)
End
On ParameterValueChanged
Matching CC_Priority
RequestParameterUpdate(31, false)
End
On ParameterValueChanged
Matching CC_ChordAcceptanceLevel
RequestParameterUpdate(32, false)
End
On ParameterValueChanged
Matching CC_BassAnalysis
RequestParameterUpdate(33, false)
End
On ParameterValueChanged
Matching CC_MicroTuning
RequestParameterUpdate(34, false)
End
On ParameterValueChanged
Matching CC_NaturalSeventh
RequestParameterUpdate(35, false)
End
On ParameterValueChanged
Matching VoiceLeadingDelay_ms
RequestParameterUpdate(36, false)
End
On ParameterValueChanged
Matching ResetVoiceLeadingMemory
RequestParameterUpdate(37, false)
End
On ParameterValueChanged
Matching CC_VoiceLeadingDelay
RequestParameterUpdate(38, false)
End
On ParameterValueChanged
Matching CC_VoiceLeadingReset
RequestParameterUpdate(39, false)
End
Function ProcessHarmonicUpdates()
if ParameterUpdatePending then
ParameterUpdatePending = false; EvaluateAccompaniment()
end
if EvaluationScheduled and TimeSinceStartup() >= TargetTime then
EvaluationScheduled = false; EvaluateAccompaniment()
end
End
On TimerTick(ms : Double)
ApplyRoutingChanges(); ProcessHarmonicUpdates()
if ActiveVoiceMode == 2 then RunVLEvents(); ApplyVoiceMode() end
if HelpUpdatePending then UpdateParameterHelp() end
End
On NoteEvent(m : NoteMessage)
var noteNum : Integer = GetNoteNumber(m)
var vel : Integer = GetVelocity(m)
var ch : Integer = GetChannel(m)
ApplyRoutingChanges()
if ActiveVoiceMode == 2 then ProcessHarmonicUpdates(); RunVLEvents() end
if ch == ActiveInputMidiChannel then
if ActiveVoiceMode == 2 then
if IsNoteOn(m) and vel > 0 then
AccompHeldVelocities[noteNum] = vel; QueueVLAttack(noteNum, vel)
else
AccompHeldVelocities[noteNum] = 0; MarkVLInputRelease(noteNum)
RefreshVLReservations(); RefreshVoicePitchBend()
end
elsif ActiveVoiceMode == 1 then
if IsNoteOn(m) and vel > 0 then
AccompHeldVelocities[noteNum] = vel
StartStableVoice(noteNum, vel)
else
AccompHeldVelocities[noteNum] = 0
if AccompPlayingVoices[noteNum] > 0 then
SendNow(MakeNoteMessageEx(noteNum, 0, AccompPlayingVoices[noteNum]))
if not DivisiSustainDown then ClearStableVoice(noteNum) end
RefreshVoicePitchBend()
end
end
else
if IsNoteOn(m) and vel > 0 then
AccompHeldVelocities[noteNum] = vel
if AccompPlayingVoices[noteNum] > 0 then
SendNow(MakeNoteMessageEx(noteNum, 0, AccompPlayingVoices[noteNum]))
AccompPlayingVoices[noteNum] = 0
end
else
AccompHeldVelocities[noteNum] = 0
end
end
ScheduleChordEvaluation()
if ActiveVoiceMode == 2 then ProcessHarmonicUpdates(); RunVLEvents() end
ApplyVoiceMode()
elsif ActiveBassInputMidiChannel > 0 and ch == ActiveBassInputMidiChannel then
if IsNoteOn(m) and vel > 0 then
BassHeldVelocities[noteNum] = vel
if BassPlayingVelocities[noteNum] > 0 then
SendNow(MakeNoteMessageEx(noteNum, 0, ActiveBassOutputMidiChannel))
end
UpdateMelodyPB()
SendNow(MakeNoteMessageEx(noteNum, vel, ActiveBassOutputMidiChannel))
BassPlayingVelocities[noteNum] = vel
else
BassHeldVelocities[noteNum] = 0; BassPlayingVelocities[noteNum] = 0
SendNow(MakeNoteMessageEx(noteNum, 0, ActiveBassOutputMidiChannel))
UpdateMelodyPB()
end
ScheduleChordEvaluation()
elsif ch != ActiveBassOutputMidiChannel then
if IsNoteOn(m) and vel > 0 then
SetMelodyVelocity(ch, noteNum, vel)
UpdateMelodyPB()
SendNow(m)
else
SetMelodyVelocity(ch, noteNum, 0)
SendNow(m)
UpdateMelodyPB()
end
end
End
On ControlChangeEvent(m : ControlChangeMessage)
var i : Integer
var ccNum : Integer = GetCCNumber(m)
var ccVal : Integer = GetCCValue(m)
var ch : Integer = GetChannel(m)
var controlHandled : Boolean = HandleControlCC(ch, ccNum, ccVal)
ApplyRoutingChanges()
if controlHandled then
if ParameterUpdatePending then
ParameterUpdatePending = false
EvaluateAccompaniment()
end
if HelpUpdatePending then UpdateParameterHelp() end
else
if ch == ActiveInputMidiChannel then
if ccNum == 64 then
DivisiSustainValue = ccVal
DivisiSustainDown = ccVal >= 64
end
for i = 1; i <= DivisiVoiceCount; i = i + 1 do
if i <= 10 then SendNow(MakeControlChangeMessageEx(ccNum, ccVal, i)) end
end
if ccNum == 64 and not DivisiSustainDown and ActiveVoiceMode == 1 then
for i = 0; i < 128; i = i + 1 do
if AccompHeldVelocities[i] == 0 and AccompPlayingVoices[i] > 0 then ClearStableVoice(i) end
end
RefreshVoicePitchBend()
end
if ccNum == 64 and not DivisiSustainDown and ActiveVoiceMode == 2 then
for i = 1; i <= 10; i = i + 1 do
if ChannelPlayingNotes[i] >= 0 and VLReleased[i] then
ClearStableVoice(ChannelPlayingNotes[i]); VLChannelToken[i] = 0
end
end
RefreshVLReservations(); RefreshVoicePitchBend()
end
if ccNum == 120 or ccNum == 123 then
for i = 1; i <= DivisiVoiceCount; i = i + 1 do SendNow(MakeControlChangeMessageEx(64, 0, i)) end
for i = 0; i < 128; i = i + 1 do
if AccompPlayingVoices[i] > 0 then SendNow(MakeNoteMessageEx(i, 0, AccompPlayingVoices[i])) end
AccompHeldVelocities[i] = 0; AccompPlayingVoices[i] = 0
TargetVoices[i] = 0; SelectedNotes[i] = 0
end
for i = 0; i <= 10; i = i + 1 do ChannelPlayingNotes[i] = -1; VoiceStartOrder[i] = 0 end
DivisiSustainDown = false; DivisiSustainValue = 0
ClearVoiceLeadingState()
ScheduleChordEvaluation()
end
ApplyVoiceMode()
elsif ActiveBassInputMidiChannel > 0 and ch == ActiveBassInputMidiChannel then
SendNow(MakeControlChangeMessageEx(ccNum, ccVal, ActiveBassOutputMidiChannel))
elsif ch != ActiveBassOutputMidiChannel then
SendNow(m)
end
end
End
On PitchBendEvent(m : PitchBendMessage)
ApplyRoutingChanges()
GlobalPBValue = GetPitchBendValue(m)
SendPitchBendToChannels()
End
On MidiEvent(m : MidiMessage)
var i : Integer
var ch : Integer = GetChannel(m)
ApplyRoutingChanges()
if ch == ActiveInputMidiChannel then
for i = 1; i <= DivisiVoiceCount; i = i + 1 do
if i <= 10 then SendNow(WithChannel(m, i)) end
end
elsif ActiveBassInputMidiChannel > 0 and ch == ActiveBassInputMidiChannel then
SendNow(WithChannel(m, ActiveBassOutputMidiChannel))
elsif ch != ActiveBassOutputMidiChannel then
SendNow(m)
end
End
```