#ifndef UDO_MELSEQUENCING
#define UDO_MELSEQUENCING ##
/*
Melodic pattern sequencer base
Slim excerpt for Partial Emergence
This file is part of the SONICS UDO collection by Richard Knight 2021
License: GPL-2.0-or-later
http://1bpm.net
*/
#include "sonics/chords.udo" ; chord data
#include "sonics/sequencing.udo" ; sequencer base
; if these are set, then don't launch the manager automatically. sequencing_melodic_persistence will load accordingly
#ifdef MEL_INITPATH
#define MEL_HASINIT ##
#end
#ifdef MEL_INITDB
#define MEL_HASINIT ##
#end
;-------------------------internal-globals--------------------------------------------------------------------------
gimel_number init 12 ; number of melodic sections available
gimel_state ftgen 0, 0, -4, -7, 0 ; state: current section, next section, current_step (gimel_number)
gimel_chords ftgen 0, 0, -gimel_number, -7, 0 ; chord indexes from melodic.udo for each section
gimel_notes ftgen 0, 0, -gimel_number, -7, 0 ; midi note numbers for each section
gimel_lengths ftgen 0, 0, -gimel_number, -7, 0 ; lengths in beats for each section
gimel_action1 ftgen 0, 0, -gimel_number, -7, 0 ; follow action 1 for each section
gimel_action2 ftgen 0, 0, -gimel_number, -7, 0 ; follow action 2 for each section
gimel_actionthreshold ftgen 0, 0, -gimel_number, -7, 0 ; follow action threshold - below 0.5 is action1, above is action2
gimel_active ftgen 0, 0, -gimel_number, -7, 0 ; whether each section is active or to be ignored
gimel_importance ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary section importance , 0 to 1
gimel_mod1 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 1, 0 to 1
gimel_mod2 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 2, 0 to 1
gimel_mod3 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 3, 0 to 1
gimel_mod4 ftgen 0, 0, -gimel_number, -7, 0 ; arbitrary modulation 4, 0 to 1
gimel_future ftgen 0, 0, -8, -7, 0 ; future sections: 8 in the future
gimel_current_notes ftgen 0, 0, -13, -7, 0 ; current notes: index 0 is the length
gimel_next_notes ftgen 0, 0, -13, -7, 0 ; next notes: index 0 is the length
gimel_temp_random ftgen 0, 0, -gimel_number, -7, 0 ; temp storage for pattern randomisation
gkmel_section_change init 0 ; section change trigger
gkmel_section_change_due init 0 ; how many beats until next section change
gkmel_futures_refresh_trig init 0 ; trigger to set if futures are to be recalculated
gkmel_pause init 0 ; pause progression changes
; actions: static actions and pattern references filled by _mel_refreshactions
gSmel_actions[] init 1
; names and references for persistence and introspection: essentially the tables to be saved
gSmel_names[] fillarray "chords", "notes", "lengths", "action1", "action2",\
"actionthreshold", "active", "importance", "mod1", "mod2", "mod3", "mod4"
gimel_fns[] fillarray gimel_chords, gimel_notes, gimel_lengths, gimel_action1, gimel_action2,\
gimel_actionthreshold, gimel_active, gimel_importance, gimel_mod1, gimel_mod2, gimel_mod3, gimel_mod4
;-----------------------------opcodes-------------------------------------------------------------------------------
/*
Refresh the actions list: static actions and pattern references
*/
opcode _mel_refreshactions, 0, 0
Smel_baseactions[] fillarray "Same", "Next", "Previous", "Random"
gSmel_actions[] init lenarray(Smel_baseactions) + gimel_number
index = 0
while (index < lenarray(gSmel_actions)) do
if (index < 4) then
gSmel_actions[index] = Smel_baseactions[index]
else
gSmel_actions[index] = sprintf("Section %d", index - 3)
endif
index += 1
od
endop
_mel_refreshactions() ; initialise
/*
Get a random midi note from the current section chord
inote mel_randomnote
inote random note from current chord
*/
opcode mel_randomnote, i, 0
ilen = table:i(0, gimel_current_notes)
index = round(random(1, ilen-1))
xout table:i(index, gimel_current_notes)
endop
/*
Get a random midi note from the current section chord
knote mel_randomnote
knote random note from current chord
*/
opcode mel_randomnote, k, 0
klen = table:k(0, gimel_current_notes)
kindex = round:k(random:k(1, klen-1))
xout table:k(kindex, gimel_current_notes)
endop
/*
Get the current section at k-rate
ksection _mel_currentsectionget
ksection current section
*/
opcode _mel_currentsectionget, k, 0
xout table:k(0, gimel_state)
endop
/*
Get the next section at k-rate
ksection _mel_nextsectionget
ksection next section
*/
opcode _mel_nextsectionget, k, 0
xout table:k(0, gimel_future)
endop
/*
Set the current section at k-rate
_mel_currentsectionset ksection
ksection current section to set
*/
opcode _mel_currentsectionset, 0, k
ksection xin
tablew ksection, 0, gimel_state
endop
/*
Get the current section at init time
isection _mel_currentsectionget
usection current section
*/
opcode _mel_currentsectionget, i, 0
xout table:i(0, gimel_state)
endop
/*
Get the length of the current section in seconds
iseconds mel_length
iseconds length in seconds
*/
opcode mel_length, i, 0
xout table:i(_mel_currentsectionget:i(), gimel_lengths) * i(gkseq_beattime)
endop
/*
Get the length of the current section in seconds
kseconds mel_length
kseconds length in seconds
*/
opcode mel_length, k, 0
xout table:k(_mel_currentsectionget:k(), gimel_lengths) * gkseq_beattime
endop
/*
Get the current MIDI note numbers as an array
inotes[] mel_currentnotes
inotes[] the note numbers
*/
opcode mel_currentnotes, i[], 0
ilen = table:i(0, gimel_current_notes)
iout[] init ilen
index = 0
while (index < ilen) do
iout[index] = table:i(index+1, gimel_current_notes)
index += 1
od
xout iout
endop
/*
Call Sinstrument when ktrig is fired, for each note (passed as p4) and the current section length accordingly
mel_eachnote Sinstrument, ktrig[, klength = mel_length:k()]
Sinstrument the instrument name to call
ktrig trigger to active call
klength duration of instrument to call, defaulting to mel_length:k()
*/
opcode mel_eachnote, 0, SkJ
Sinstrument, ktrig, klength xin
if (ktrig == 1) then
kdur = (klength == -1 ) ? mel_length:k() : klength
kindex = 0
while (kindex < table:k(0, gimel_current_notes)) do
schedulek Sinstrument, 0, kdur, table:k(kindex + 1, gimel_current_notes)
kindex += 1
od
endif
endop
/*
Get the most important entry from futures table
kbestindex, kimportance, kbeats mel_future_mostimportant
kbestindex index in gimel_future
kimportance the importance measure
kbeats number of beats until the event occurs
*/
opcode mel_future_mostimportant, kkk, 0
kindex = 0
kimportance = -9999
kbestindex = 0
kbeats = table:k(table:k(0, gimel_state), gimel_lengths) ; current duration base
while (kindex < ftlen(gimel_future)) do
ksection = table:k(kindex, gimel_future)
kimportancetemp = table:k(ksection, gimel_importance)
if (kimportancetemp > kimportance) then
kimportance = kimportancetemp
kbestindex = kindex
endif
kindex += 1
od
kindex = 0
while (kindex < kbestindex) do
kbeats += table:k(table:k(kindex, gimel_future), gimel_lengths)
kindex += 1
od
xout kbestindex, kimportance, kbeats ; * gkseq_beattime
endop
/*
Get the most important entry from futures table
ibestindex, iimportance, ibeats mel_future_mostimportant
ibestindex index in gimel_future
importance the importance measure
ibeats number of beats until the event occurs
*/
opcode mel_future_mostimportant, iii, 0
index = 0
importance = -9999
ibestindex = 0
ibeats = table:i(table:i(0, gimel_state), gimel_lengths) ; current duration base
while (index < ftlen(gimel_future)) do
isection = table:i(index, gimel_future)
importancetemp = table:i(isection, gimel_importance)
if (importancetemp > importance) then
importance = importancetemp
ibestindex = index
endif
index += 1
od
index = 0
while (index < ibestindex) do
ibeats += table:i(table:i(index, gimel_future), gimel_lengths)
index += 1
od
xout ibestindex, importance, ibeats ; * i(gkseq_beattime)
endop
/*
Calculate the next section from a given section
knext _mel_calculatenext kcurrent
knext the calculated next section index
kcurrent the section index to base the calculation upon
*/
opcode _mel_calculatenext, k, k
kthissection xin
knextsection = -1
if (random:k(0, 1) <= table:k(kthissection, gimel_actionthreshold)) then
knextaction = table:k(kthissection, gimel_action2)
else
knextaction = table:k(kthissection, gimel_action1)
endif
; if current is not active, go to next ?
kcurrentactive = table:k(kthissection, gimel_active)
if (kcurrentactive == 0 && knextaction == 0) then
knextaction = 1
endif
; same
if (knextaction == 0) then
knextsection = kthissection
; next or previous
elseif (knextaction >= 1 && knextaction <= 3) then ; specified action
kcount = 0
kactive = 0
knextsection = kthissection
while (kactive == 0 && kcount < gimel_number) do ; loop until active section found or all sections checked
if (knextaction == 1) then ; next
if (knextsection + 1 > gimel_number - 1) then
knextsection = 0
else
knextsection += 1
endif
elseif (knextaction == 2) then ; previous
if (knextsection -1 < 0) then
knextsection = gimel_number - 1
else
knextsection -= 1
endif
endif
kactive = table:k(knextsection, gimel_active)
kcount += 1
od
; random
elseif (knextaction == 3) then
kindex = 0
krandmax = 0
while (kindex < gimel_number) do
if (table:k(kindex, gimel_active) == 1) then
tablew kindex, krandmax, gimel_temp_random
krandmax += 1
endif
kindex += 1
od
knextsection = table:k(round(random(0, krandmax - 1)), gimel_temp_random)
; specific section
elseif (knextaction >= 4) then ; specific active pattern
if (table:k(knextaction - 4, gimel_active) == 1) then
knextsection = knextaction - 4
else
knextsection = kthissection
endif
endif
xout knextsection
endop
/*
Set gimel_next_notes from the first entry in the futures table
*/
opcode _mel_setnextnotes, 0, 0
knext = table:k(0, gimel_future)
chordmidibyindextof gimel_next_notes, table:k(knext, gimel_chords), table:k(knext, gimel_notes)
endop
/*
Pop the next future entry from the futures table, move all future entries down one
and add a new calculated entry accordingly
kcurrent _mel_future_pop
kcurrent the current section to be used now
*/
opcode _mel_future_pop, k, 0
imax = ftlen(gimel_future)
kcurrent = table:k(0, gimel_future)
kindex = 0
while (kindex < imax - 1) do
tablew table:k(kindex + 1, gimel_future), kindex, gimel_future
kindex += 1
od
; write new last entry
tablew _mel_calculatenext(table:k(kindex, gimel_future)), imax - 1, gimel_future
_mel_setnextnotes()
xout kcurrent
endop
/*
Recalculate the futures table (in the event of parameters being changed at runtime etc)
*/
opcode _mel_futures_refresh, 0, O
kindexStart xin ; usually 0, can be a start index (ie 1 leaves the first entry in place)
kindex = kindexStart
imax = ftlen(gimel_future)
; TODO do first, etc
while (kindex < imax) do
if (kindex == 0) then
kcurrent = table:k(0, gimel_state) ; 0 ; get current, rather than 0...
else
kcurrent = table:k(kindex - 1, gimel_future)
endif
tablew _mel_calculatenext(kcurrent), kindex, gimel_future
kindex += 1
od
_mel_setnextnotes()
endop
/*
Set next section, for host control
p4 section number to set as next
*/
instr mel_setnextsection
isection = p4
if (table:i(isection, gimel_active) == 1) then
tablew isection, 0, gimel_future
gkmel_futures_refresh_trig = 2
endif
turnoff
endin
/*
Refresh the futures table, for host control
*/
instr mel_futures_refresh
gkmel_futures_refresh_trig = 1
turnoff
endin
/*
Randomise all section parameters
*/
opcode _mel_randomise, 0, 0
index = 0
iactives[] init 4 + gimel_lengths
iactivenum = 4
while (index < gimel_number) do
tablew round(random(0, lenarray(gSchords) - 1)), index, gimel_chords
tablew round(random(4, 8)), index, gimel_lengths
tablew round(random(48, 70)), index, gimel_notes
tablew random(0, 1), index, gimel_actionthreshold
tablew random(0, 1), index, gimel_importance
tablew random(0, 1), index, gimel_mod1
tablew random(0, 1), index, gimel_mod2
tablew random(0, 1), index, gimel_mod3
tablew random(0, 1), index, gimel_mod4
iactive = round(random(0, 1))
if (iactive == 1) then
iactives[iactivenum-1] = iactive
iactivenum += 1
endif
tablew iactive, index, gimel_active
index += 1
od
; set next action to only active sections
index = 0
while (index < gimel_number) do
iaction1 = iactives[round(random(0, iactivenum))]
iaction2 = iactives[round(random(0, iactivenum))]
tablew iaction1, index, gimel_action1
tablew iaction2, index, gimel_action2
index += 1
od
endop
/*
Randomise all section parameters
*/
instr mel_randomise
_mel_randomise()
gkmel_futures_refresh_trig = 1
turnoff
endin
/*
Initialise the sequencer sections; monitor for gkseq_beat triggers and change sections accordingly
*/
instr _mel_manager
#ifndef MEL_HASINIT
_mel_randomise()
#end
gkmel_futures_refresh_trig init 1
if (gkmel_futures_refresh_trig != 0) then
_mel_futures_refresh(gkmel_futures_refresh_trig - 1) ; if gkmel_futures_refresh_trig is 2, then omit first, otherwise recalculate all
gkmel_futures_refresh_trig = 0
endif
kstep init 0
gkmel_section_change = 0
; do something with gkmel_pause == 0
if (gkseq_beat == 1 && gkmel_pause = 0) then
if (kstep == 0) then
tablecopy gimel_current_notes, gimel_next_notes
kcurrent = _mel_future_pop:k()
_mel_currentsectionset(kcurrent)
gkmel_section_change = 1
endif
ksectionlength = table:k(_mel_currentsectionget:k(), gimel_lengths)
gkmel_section_change_due = ksectionlength - kstep
if (kstep < ksectionlength - 1) then ; current step < current length
kstep += 1
else
kstep = 0
endif
endif ; end each beat
endin
#ifndef MEL_HASINIT
alwayson "_mel_manager"
#end
#end