aboutsummaryrefslogtreecommitdiff
path: root/site/udo/legacy/sequencing_melodic.udo
diff options
context:
space:
mode:
authorRichard <q@1bpm.net>2025-04-13 18:48:02 +0100
committerRichard <q@1bpm.net>2025-04-13 18:48:02 +0100
commit9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22 (patch)
tree291bd79ce340e67affa755a8a6b4f6a83cce93ea /site/udo/legacy/sequencing_melodic.udo
downloadapps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.gz
apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.tar.bz2
apps.csound.1bpm.net-9fbf91db06a6d4f4b5cd8bb45389a731bb86bf22.zip
initial
Diffstat (limited to 'site/udo/legacy/sequencing_melodic.udo')
-rwxr-xr-xsite/udo/legacy/sequencing_melodic.udo831
1 files changed, 831 insertions, 0 deletions
diff --git a/site/udo/legacy/sequencing_melodic.udo b/site/udo/legacy/sequencing_melodic.udo
new file mode 100755
index 0000000..7d3f546
--- /dev/null
+++ b/site/udo/legacy/sequencing_melodic.udo
@@ -0,0 +1,831 @@
+#ifndef UDO_MELSEQUENCING
+#define UDO_MELSEQUENCING ##
+
+/*
+ Melodic pattern sequencer base
+ Legacy, without microtonal option
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+#include "__config__.udo" ; using fftsize for tuning
+#include "chords.udo" ; chord data
+#include "sequencing.udo" ; sequencer base
+#include "interop.udo" ; for updating host with outvalue
+#include "wavetables.udo" ; for tuning
+#include "json.udo" ; used to update host
+
+; 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_centadd ftgen 0, 0, -gimel_number, -7, 0 ; microtonal midi note additions (0 = no change; 1 = add one semitone; 0.01 = add one cent)
+
+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
+
+; user modifiable variables
+gkmel_pause init 0 ; pause progression changes
+gkmel_advance_trig init 0 ; manual progression advance trigger
+
+
+
+; 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
+*/
+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
+
+/*
+; actions: static actions and pattern references filled by _mel_refreshactions
+;gSmel_actions[] init 1
+
+opcode _mel_refreshactions, 0, 0
+
+endop
+_mel_refreshactions() ; initialise
+*/
+
+instr _mel_debug_printstate
+ index = 0
+ Sjson = json_init()
+ Sjson = json_appendtable(Sjson, "future", gimel_future)
+ while (index < lenarray(gimel_fns)) do
+ Sjson = json_appendtable(Sjson, gSmel_names[index], gimel_fns[index])
+ index += 1
+ od
+ prints Sjson
+ prints "\n\n"
+endin
+
+
+/*
+ Send JSON formatted information on current setup to API host
+*/
+instr mel_updatehost ; use p4 for channel?
+ Sjson = json_init()
+ Sjson = json_appendvalue(Sjson, "sections", gimel_number)
+ Sjson = json_appendarray(Sjson, "chordnames", gSchords)
+ Sjson = json_appendarray(Sjson, "actiontypes", gSmel_actions)
+
+ SjsonFns = json_init()
+ index = 0
+ while (index < lenarray(gimel_fns)) do
+ SjsonFns = json_appendvalue(SjsonFns, gSmel_names[index], gimel_fns[index])
+ index += 1
+ od
+ Sjson = json_appendobject(Sjson, "ftables", SjsonFns)
+
+ io_sendstring("mel_state", Sjson)
+ turnoff
+endin
+
+
+/*
+; new verison using plugin opcode
+instr mel_updatehost ; use p4 for channel?
+ iJson jsoninit
+ jsoninsertval iJson, "sections", gimel_number
+ jsoninsertval iJson, "chordnames", gSchords
+ jsoninsertval iJson, "actiontypes", gSmel_actions
+
+ iJsonFns jsoninit
+ jsoninsertval iJsonFns, gSmel_names, gimel_fns
+ jsoninsert iJson, "ftables", iJsonFns
+ io_sendstring("mel_state", jsondumps(iJson, 1))
+endin
+*/
+
+
+
+
+
+/*
+ Get modulation parameters for current section
+
+ imod1, imod2, imod3, imod4 mel_currentmod
+
+ imod1 modulation parameter 1
+ imod2 modulation parameter 2
+ imod3 modulation parameter 3
+ imod4 modulation parameter 4
+*/
+opcode mel_currentmod, iiii, 0
+ icur = table:i(0, gimel_state)
+ xout table:i(icur, gimel_mod1), table:i(icur, gimel_mod2), table:i(icur, gimel_mod3), table:i(icur, gimel_mod4)
+endop
+
+
+/*
+ Get modulation parameters for current section
+
+ kmod1, kmod2, kmod3, kmod4 mel_currentmod
+
+ kmod1 modulation parameter 1
+ kmod2 modulation parameter 2
+ kmod3 modulation parameter 3
+ kmod4 modulation parameter 4
+*/
+opcode mel_currentmod, kkkk, 0
+ kcur = table:k(0, gimel_state)
+ xout table:k(kcur, gimel_mod1), table:k(kcur, gimel_mod2), table:k(kcur, gimel_mod3), table:k(kcur, gimel_mod4)
+endop
+
+
+/*
+ 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 and update the host
+*/
+instr mel_randomise
+ _mel_randomise()
+ gkmel_futures_refresh_trig = 1
+ event_i "i", "mel_updatehost", 0, 1
+ turnoff
+endin
+
+
+/*
+ Pause progression, for host control
+*/
+instr mel_pause
+ gkmel_pause = p4
+ turnoff
+endin
+
+
+/*
+ Advance progression, for host control
+*/
+instr mel_advance
+ gkmel_advance_trig = 1
+ turnoff
+endin
+
+
+/*
+ Advance progression if paused, for host control
+*/
+instr mel_advanceifpaused
+ if (gkmel_pause == 1) then
+ gkmel_advance_trig = 1
+ endif
+ turnoff
+endin
+
+
+
+opcode mel_nextchangelength, k, 0
+ kcurrent = _mel_currentsectionget:k()
+ klength = table:k(kcurrent, gimel_lengths)
+
+ imaxfutures = ftlen(gimel_future)
+ kindex = 0
+ while (kindex < imaxfutures) do
+ ksection = table:k(kindex, gimel_future)
+ if (ksection != kcurrent) kgoto complete
+ klength += table:k(ksection, gimel_lengths)
+ kindex += 1
+ od
+complete:
+ xout klength
+endop
+
+/*
+ Initialise the sequencer sections; monitor for gkseq_beat triggers and change sections accordingly
+*/
+instr _mel_manager
+#ifndef MEL_HASINIT
+ _mel_randomise()
+#end
+
+ ksectionlength init 0
+ 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
+ ksectionlength = mel_nextchangelength:k()
+ endif
+
+ kstep init 0
+ gkmel_section_change = 0
+
+ kmanualadvance = 0
+ if (gkmel_advance_trig == 1) then
+ kmanualadvance = 1
+ gkmel_advance_trig = 0
+ endif
+
+ if ((gkseq_beat == 1 && gkmel_pause == 0) || kmanualadvance == 1) then
+ if (kstep == 0 || kmanualadvance == 1) then
+ kcurrent = _mel_currentsectionget:k()
+ tablecopy gimel_current_notes, gimel_next_notes
+ knew = _mel_future_pop:k()
+ _mel_currentsectionset(knew)
+
+ ; only send if actually changed
+ if (kcurrent != knew) then
+ io_send("mel_current", knew) ; send current (from next)
+ gkmel_section_change = 1
+ ksectionlength = mel_nextchangelength:k()
+ endif
+ endif
+
+ 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
+
+
+
+/*
+ Extend the current notes and convert to frequency, multiplying by powers of two to be used in mel_tune
+ ifreqs[] _mel_tune_noteprepare inotes[], imult
+
+ ifreqs[] resulting frequencies
+ inotes[] input midi note numbers
+ imult number of times to multiply note contents
+
+*/
+opcode _mel_tune_noteprepare, i[], i[]i
+ iarr[], imult xin
+ inew[] init lenarray(iarr) * imult
+ indexnew = 0
+ index = 0
+ while (index < lenarray(iarr)) do
+ ifreq = cpsmidinn(iarr[index])
+ index2 = 0
+ while (index2 < imult) do
+ if (index2 > 0) then
+ inew[indexnew] = ifreq * (2* (index2+1))
+ else
+ inew[indexnew] = ifreq
+ endif
+ index2 += 1
+ indexnew += 1
+ od
+
+ index += 1
+ od
+ xout inew
+endop
+
+
+/*
+ Create a chord with the specified frequencies
+ aout _mel_tune_chord ifreqs[] [, ifn, index]
+
+ aout resulting chord
+ ifreqs[] frequencies to play
+ ifn wavetable to play with, default = gifnSine
+ index internal index usage for recursion
+*/
+opcode _mel_tune_chord, a, i[]oo
+ ifreqs[], ifn, index xin
+ ifn = (ifn == 0) ? gifnSine : ifn
+ aout = oscil(0.1, ifreqs[index], ifn)
+ if (index < lenarray(ifreqs) - 1) then
+ aout += _mel_tune_chord(ifreqs, ifn, index + 1)
+ endif
+ xout aout
+endop
+
+
+/*
+ Stereo tuning to current melodic sequencer notes
+ aoutL, aoutR mel_tune ainL, ainR, ifn, imult [, ifftrate, ifftdiv]
+
+ aoutL, aoutR output audio
+ ainL, ainR input audio
+ ifn wavetable to use
+ imult multiples of harmonics to generate in tuning
+ ifftrate fft size, defaults to config default
+ ifftdiv fft window division factor (eg 4, 8, 16), defaults to config default
+*/
+opcode mel_tune, aa, aaiioo
+ aL, aR, ifn, imult, ifftrate, ifftdiv xin
+ ifftrate = (ifftrate == 0) ? giFFTsize : ifftrate
+ ifftdiv = (ifftdiv == 0) ? giFFTwinFactor : ifftdiv
+ ifreqs[] _mel_tune_noteprepare mel_currentnotes(), imult
+ fmods pvsanal _mel_tune_chord(ifreqs, ifn), ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL1 pvsanal aL, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fR1 pvsanal aR, ifftrate, ifftrate/ifftdiv, ifftrate, 1
+ fL2 pvsmorph fL1, fmods, 0, 1
+ fR2 pvsmorph fR1, fmods, 0, 1
+ aL1 pvsynth fL2
+ aR1 pvsynth fR2
+ idel = (ifftrate+2)/sr
+ aL1 balance aL1, delay(aL, idel)
+ aR1 balance aR1, delay(aR, idel)
+ xout aL1, aR1
+endop
+
+
+/*
+ Experimental tonal balance of two signals
+
+ aoutput balancetonal ain, aincomparator
+
+ aoutput balanced signal
+ ain signal to apply changes to
+ aincomparator signal to 'extract' frequency contour from
+*/
+opcode balancetonal, a, aa
+ ain, ainc xin
+ aouts[] init 16
+
+ aouts[0] balance butterbp(ain, 100, 200), butterbp(ainc, 100, 200) ; 0 - 200
+ aouts[1] balance butterbp(ain, 400, 400), butterbp(ainc, 400, 400) ; 200 - 600
+ aouts[2] balance butterbp(ain, 800, 400), butterbp(ainc, 800, 400) ; 600 - 1000
+ aouts[3] balance butterbp(ain, 1200, 400), butterbp(ainc, 1200, 400) ; 1000 - 1400
+ aouts[4] balance butterbp(ain, 1700, 600), butterbp(ainc, 1700, 600) ; 1400 - 2000
+ aouts[5] balance butterbp(ain, 2400, 800), butterbp(ainc, 2400, 800) ; 2000 - 2800
+ aouts[6] balance butterbp(ain, 3200, 800), butterbp(ainc, 3200, 800) ; 2800 - 3600
+ aouts[7] balance butterbp(ain, 4200, 1200), butterbp(ainc, 4200, 1200) ; 3600 - 4800
+ aouts[8] balance butterbp(ain, 5400, 1200), butterbp(ainc, 5400, 1200) ; 4800 - 6000
+ aouts[9] balance butterbp(ain, 7000, 2000), butterbp(ainc, 7000, 2000) ; 6000 - 8000
+ aouts[10] balance butterbp(ain, 9000, 2000), butterbp(ainc, 9000, 2000) ; 8000 - 10000
+ aouts[11] balance butterbp(ain, 11000, 2000), butterbp(ainc, 11000, 2000) ; 10000 - 12000
+ aouts[12] balance butterbp(ain, 14000, 4000), butterbp(ainc, 14000, 4000) ; 12000 - 16000
+ aouts[13] balance butterbp(ain, 18000, 4000), butterbp(ainc, 18000, 4000) ; 16000 - 20000
+ aouts[14] balance butterhp(ain, 20000), butterhp(ainc, 20000)
+
+ aout sumarray aouts
+ xout aout
+endop
+
+
+#end