From decb2dc0e9f1167d5cd8fb4d455305be6b9fdfbe Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 4 Oct 2022 00:17:55 +0100 Subject: initial --- sonics/sequencing_melodic.udo | 556 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 556 insertions(+) create mode 100755 sonics/sequencing_melodic.udo (limited to 'sonics/sequencing_melodic.udo') diff --git a/sonics/sequencing_melodic.udo b/sonics/sequencing_melodic.udo new file mode 100755 index 0000000..05fb614 --- /dev/null +++ b/sonics/sequencing_melodic.udo @@ -0,0 +1,556 @@ +#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/__config__.udo" ; using fftsize for tuning +#include "sonics/chords.udo" ; chord data +#include "sonics/sequencing.udo" ; sequencer base +#include "sonics/wavetables.udo" ; for tuning + +; 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_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) 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 + + if (kstep < table:k(_mel_currentsectionget:k(), gimel_lengths) - 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 -- cgit v1.2.3