aboutsummaryrefslogtreecommitdiff
path: root/sonics
diff options
context:
space:
mode:
authorRichard <richard@1bpm.net>2022-10-04 00:17:55 +0100
committerRichard <richard@1bpm.net>2022-10-04 00:17:55 +0100
commitdecb2dc0e9f1167d5cd8fb4d455305be6b9fdfbe (patch)
tree9a9a586bbb6268e52290c017ec95d3f672822980 /sonics
downloadcsd-partialemergence-decb2dc0e9f1167d5cd8fb4d455305be6b9fdfbe.tar.gz
csd-partialemergence-decb2dc0e9f1167d5cd8fb4d455305be6b9fdfbe.tar.bz2
csd-partialemergence-decb2dc0e9f1167d5cd8fb4d455305be6b9fdfbe.zip
initial
Diffstat (limited to 'sonics')
-rwxr-xr-xsonics/README.md36
-rwxr-xr-xsonics/__config__.udo35
-rwxr-xr-xsonics/array_tools.udo27
-rwxr-xr-xsonics/bussing.udo126
-rwxr-xr-xsonics/chords.udo447
-rwxr-xr-xsonics/frequency_tools.udo359
-rwxr-xr-xsonics/instrument_gchord1.udo261
-rwxr-xr-xsonics/instrument_portchord.udo133
-rwxr-xr-xsonics/instrument_sineblips.udo77
-rwxr-xr-xsonics/pgdb.udo16
-rwxr-xr-xsonics/sequencing.udo99
-rwxr-xr-xsonics/sequencing_melodic.udo556
-rwxr-xr-xsonics/sequencing_melodic_persistence.udo54
-rwxr-xr-xsonics/sequencing_melodic_portamento.udo172
-rwxr-xr-xsonics/sounddb.udo229
-rwxr-xr-xsonics/soundxdb.udo202
-rwxr-xr-xsonics/uniqueid.udo78
-rwxr-xr-xsonics/wavetables.udo30
18 files changed, 2937 insertions, 0 deletions
diff --git a/sonics/README.md b/sonics/README.md
new file mode 100755
index 0000000..14763ae
--- /dev/null
+++ b/sonics/README.md
@@ -0,0 +1,36 @@
+# SONICS
+*Specialised Operations Notably Implemented in Csound*
+
+The UDO files in this directory are a slimmed excerpt from the SONICS collection. The full collection will be made available in the future.
+SONICS is a framework of frontends, Csound UDOs and PostgreSQL database functionality allowing for easier sound processing, sequencing and live performance.
+The frontend components consist of elements using SDL, Qt and HTML, while audio engines can utilise Pyo, Puredata and Csound. Data is stored on disk and in databases (of which PostgreSQL, MySQL/MariaDB and SQLite are supported).
+Only the Csound UDO and PostgreSQL database components are featured in the extract for Partial Emergence, and those are mainly limited to the required functionality for the installation but may have some reuse potential.
+
+## Key concepts
+
+### Sequencing and time
+sequencing.udo contains opcodes to maintain a master clock on which other opcodes rely.
+
+
+### Melodic progressions
+sequencing_melodic.udo features a system of chord progression management based around set and stochastic rules.
+The f-tables defined at the top of the file contain details of the current chord notes.
+
+sequencing_melodic_persistence.udo deals with loading and saving chord progressions to/from disk or database.
+
+sequencing_melodic_portamento.udo makes the current chord note frequencies available in f-tables with an applied portamento based on a time that can be set globally.
+
+
+### Sound DB
+sounddb.udo provides a way to load sounds to f-tables stored on disk and defined in the database. Sounds are allocated collections which can be of a regular or melodic type. Melodic sounds also have the corresponding MIDI note number stored in the database and thus can then be retrieved based on the requested note. Functions in the database handle the retrieval and calculation of the pitch ratio adjustment required to make the sound match the requested note.
+
+soundxdb.udo provides the same interface as sounddb.udo, but utilises a database extract to allow for offline sound retrieval.
+
+
+### Bussing
+bussing.udo provides stereo wrappers around chnset/chnget/chnmix opcodes to allow for easier bussing, and a default master bus which has a global amplitude control.
+
+
+### Instruments
+While SONICS is mainly a control framework, there are also extension opcodes which use the framework to provide audio processing and generation; the UDO files are prepended with *instrument_*
+
diff --git a/sonics/__config__.udo b/sonics/__config__.udo
new file mode 100755
index 0000000..962e78a
--- /dev/null
+++ b/sonics/__config__.udo
@@ -0,0 +1,35 @@
+#ifndef UDO_CONFIG
+#define UDO_CONFIG ##
+/*
+ SONICS config
+ 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
+*/
+
+
+; database: allow macro overrides from command line or pre-include etc
+#ifndef PGDB_HOST
+#define PGDB_HOST #192.168.1.69#
+#endif
+
+#ifndef PGDB_NAME
+#define PGDB_NAME #partialemergence#
+#endif
+
+#ifndef PGDB_USER
+#define PGDB_USER #partialemergence#
+#endif
+
+#ifndef PGDB_PASSWORD
+#define PGDB_PASSWORD #dj939jfh9sh948nd#
+#endif
+
+
+; FFT defaults
+giFFTsize = 512
+giFFTwinFactor = 4
+
+#end
diff --git a/sonics/array_tools.udo b/sonics/array_tools.udo
new file mode 100755
index 0000000..69ea2b9
--- /dev/null
+++ b/sonics/array_tools.udo
@@ -0,0 +1,27 @@
+#ifndef UDO_ARRAYTOOLS
+#define UDO_ARRAYTOOLS ##
+/*
+ Array tools
+ 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
+*/
+
+
+/*
+ Get a random value from an array
+
+ ivalue arr_random iarray[]
+
+ ivalue selected value
+ iarray[] array to evaluate
+*/
+opcode arr_random, i, i[]
+ iarray[] xin
+ ivalue = iarray[round(random(0, lenarray(iarray) - 1))]
+ xout ivalue
+endop
+
+#end
diff --git a/sonics/bussing.udo b/sonics/bussing.udo
new file mode 100755
index 0000000..83993f6
--- /dev/null
+++ b/sonics/bussing.udo
@@ -0,0 +1,126 @@
+#ifndef UDO_BUSSING
+#define UDO_BUSSING ##
+/*
+ Bus handling
+ 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
+*/
+
+gkmastervolume init 1
+
+
+/*
+ Get the stereo L and R names for a singular bus name
+
+ SnameL, SnameR bus_name Sbus
+
+ SnameL left bus identifier
+ SnameR right bus identifier
+
+ Sbus bus name
+*/
+opcode bus_name, SS, S
+ Sbus xin
+ xout sprintf("%sL", Sbus), sprintf("%sR", Sbus)
+endop
+
+
+/*
+ Read from a stereo bus, but do not clear it
+
+ aL, aR bus_tap Sbus
+
+ aL left channel
+ aR right channel
+
+ Sbus bus name
+*/
+opcode bus_tap, aa, S
+ Sbus xin
+ SbusL, SbusR bus_name Sbus
+ aL chnget SbusL
+ aR chnget SbusR
+ xout aL, aR
+endop
+
+/*
+ Read from a stereo bus, and then clear the bus
+
+ aL, aR bus_read Sbus
+
+ aL left channel
+ aR right channel
+
+ Sbus bus name
+*/
+opcode bus_read, aa, S
+ Sbus xin
+ SbusL, SbusR bus_name Sbus
+ aL chnget SbusL
+ aR chnget SbusR
+ chnclear SbusL
+ chnclear SbusR
+ xout aL, aR
+endop
+
+
+/*
+ Set to a stereo bus
+
+ bus_set Sbus, aL, aR
+
+ Sbus bus name
+ aL left channel
+ aR right channel
+*/
+opcode bus_set, 0, Saa
+ Sbus, aL, aR xin
+ SbusL, SbusR bus_name Sbus
+ chnset aL, SbusL
+ chnset aR, SbusR
+endop
+
+/*
+ Mix to a stereo bus
+
+ bus_mix Sbus, aL, aR
+
+ Sbus bus name
+ aL left channel
+ aR right channel
+*/
+opcode bus_mix, 0, Saa
+ Sbus, aL, aR xin
+ SbusL, SbusR bus_name Sbus
+ chnmix aL, SbusL
+ chnmix aR, SbusR
+endop
+
+
+/*
+ Mix to master bus
+
+ bus_masterout aL, aR
+
+ aL left channel
+ aR right channel
+*/
+opcode bus_masterout, 0, aa
+ aL, aR xin
+ chnmix aL, "mainL"
+ chnmix aR, "mainR"
+endop
+
+
+instr _mainmixer
+ aL, aR bus_read "main"
+ aL = aL*gkmastervolume
+ aR = aR*gkmastervolume
+ outs aL, aR
+endin
+alwayson "_mainmixer"
+
+#end
diff --git a/sonics/chords.udo b/sonics/chords.udo
new file mode 100755
index 0000000..b862e26
--- /dev/null
+++ b/sonics/chords.udo
@@ -0,0 +1,447 @@
+#ifndef UDO_CHORDS
+#define UDO_CHORDS ##
+/*
+ Chord interval data and harmonic formation opcodes
+ 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
+*/
+
+
+; chord names
+gSchords[] fillarray "Augmented",
+ "Augmented 11th",
+ "Augmented major 7th",
+ "Augmented 7th",
+ "Augmented 6th",
+ "Diminished",
+ "Diminished major 7th",
+ "Diminished 7th",
+ "Dominant",
+ "Dominant 11th",
+ "Dominant minor 9th",
+ "Dominant 9th",
+ "Dominant parallel",
+ "Dominant 7th",
+ "Dominant 7th b5",
+ "Dominant 13th",
+ "Dream",
+ "Elektra",
+ "Farben",
+ "Harmonic 7th",
+ "Augmented 9th",
+ "Leadingtone",
+ "Lydian",
+ "Major",
+ "Major 11th",
+ "Major 7th",
+ "Major 7th sharp 11th",
+ "Major 6th",
+ "Major 9th",
+ "Major 13th",
+ "Mediant",
+ "Minor",
+ "Minor 11th",
+ "Minor major 7th",
+ "Minor 9th",
+ "Minor 7th",
+ "Half diminished 7th",
+ "Minor 6th",
+ "Minor 13th",
+ "Mu",
+ "Mystic",
+ "Neapolitan",
+ "Ninth augmented 5th",
+ "Ninth b5th",
+ "Northern lights",
+ "Napoleon hexachord",
+ "Petrushka",
+ "Power",
+ "Psalms",
+ "Secondary dominant",
+ "Secondary leadingtone",
+ "Secondary supertonic",
+ "Sevensix",
+ "7th b9",
+ "7th suspension 4",
+ "Sixth 9th",
+ "Suspended",
+ "Subdominant",
+ "Subdominant parallel",
+ "Submediant",
+ "Subtonic",
+ "Supertonic",
+ "So what",
+ "Thirteenth b9th",
+ "Thirteenth b9th b5th",
+ "Tonic counter parallel",
+ "Tonic",
+ "Tonic parallel",
+ "Tristan",
+ "Viennese trichord 1",
+ "Viennese trichord 2"
+
+; octave and note names
+gSoctaves[] fillarray "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
+gSnotenames[] fillarray "C", "C#", "D", "D#", "E", "F", "F#", "G", "A", "Asharp", "B"
+
+; chord interval definitions with index in gichordfns corresponding to names in gSchords
+gichordfns = ftgen(0, 0, -71, -2, 0)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 8), 0, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 10, 2, 6), 1, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 8, 11), 2, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 8, 10), 3, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 6, 8), 4, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 6), 5, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 11), 6, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 9), 7, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 8, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 10, 2, 5), 9, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 1), 10, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 2), 11, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 12, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 10), 13, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 6, 10), 14, gichordfns)
+tabw_i(ftgen(0, 0, -7, -2, 0, 4, 7, 10, 2, 5, 9), 15, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 5, 6, 7), 16, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 7, 9, 1, 4), 17, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 8, 11, 4, 9), 18, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 10), 19, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 3), 20, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 6), 21, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 11, 6), 22, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 23, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 11, 2, 5), 24, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 11), 25, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 11, 6), 26, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 4, 7, 9), 27, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 11, 2), 28, gichordfns)
+tabw_i(ftgen(0, 0, -7, -2, 0, 4, 7, 11, 2, 6, 9), 29, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 30, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 31, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 3, 7, 10, 2, 5), 32, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 7, 11), 33, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 3, 7, 10, 2), 34, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 7, 10), 35, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 10), 36, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 7, 9), 37, gichordfns)
+tabw_i(ftgen(0, 0, -7, -2, 0, 3, 7, 10, 2, 5, 9), 38, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 2, 4, 7), 39, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 6, 10, 4, 9, 2), 40, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 1, 5, 8), 41, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 8, 10, 2), 42, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 6, 10, 2), 43, gichordfns)
+tabw_i(ftgen(0, 0, -11, -2, 1, 2, 8, 0, 3, 6, 7, 10, 11, 4, 7), 44, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 1, 4, 5, 8, 9), 45, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 1, 4, 6, 7, 10), 46, gichordfns)
+tabw_i(ftgen(0, 0, -2, -2, 0, 7), 47, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 48, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 49, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 6), 50, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 51, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 9, 10), 52, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 10, 1), 53, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 5, 7, 10), 54, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 4, 7, 9, 2), 55, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 5, 7), 56, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 57, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 58, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 59, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 60, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 61, gichordfns)
+tabw_i(ftgen(0, 0, -5, -2, 0, 5, 10, 3, 7), 62, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 7, 10, 1, 9), 63, gichordfns)
+tabw_i(ftgen(0, 0, -6, -2, 0, 4, 6, 10, 1, 9), 64, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 65, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 4, 7), 66, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 3, 7), 67, gichordfns)
+tabw_i(ftgen(0, 0, -4, -2, 0, 3, 6, 10), 68, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 1, 6), 69, gichordfns)
+tabw_i(ftgen(0, 0, -3, -2, 0, 6, 7), 70, gichordfns)
+
+
+
+/*
+ Get chord intervals array by index
+ intervals[] chordintervalsbyindex index
+
+ intervals[] intervals for the chord obtained from gichordfns
+ index index in gichordfns to retrieve, corresponding to gSchords names
+*/
+opcode chordintervalsbyindex, i[], i
+ index xin
+ intervals[] tab2array table:i(index, gichordfns)
+ xout intervals
+endop
+
+
+/*
+ Get chord intervals array by index
+ kintervals[] chordintervalsbyindex kindex
+
+ kintervals[] intervals for the chord obtained from gichordfns
+ kindex index in gichordfns to retrieve, corresponding to gSchords names
+*/
+opcode chordintervalsbyindex, k[], k
+ kindex xin
+ kintervals[] init 99 ; TODO : FIX AROUND THIS??
+ copyf2array kintervals, table:k(kindex, gichordfns)
+ ;kintervals[] tab2array
+ xout kintervals
+endop
+
+
+/*
+ Get index of chord name
+ index chordindexbyname Schord
+
+ index index in gichordfns and gSchords
+ Schord chord name as in gSchords
+*/
+opcode chordindexbyname, i, S
+ Schord xin
+ index = 0
+ while (index < lenarray(gSchords)) do
+ if (strcmp(gSchords[index], Schord) == 0) then
+ igoto done
+ endif
+ index += 1
+ od
+ index = 0
+done:
+ xout index
+endop
+
+
+
+/*
+ Get index of chord name
+ index chordindexbyname Schord
+
+ kindex index in gichordfns and gSchords
+ Schord chord name as in gSchords
+*/
+opcode chordindexbyname, k, S
+ Schord xin
+ kindex = 0
+ while (kindex < lenarray:k(gSchords)) do
+ if (strcmpk(gSchords[kindex], Schord) == 0) then
+ kgoto done
+ endif
+ kindex += 1
+ od
+ kindex = 0
+done:
+ xout kindex
+endop
+
+
+/*
+ Get chord intervals by name: return the array from gichordfns that corresponds to the gSchords entry
+ intervals[] chordintervals Schord
+
+ intervals[] intervals for the chord obtained from gichordfns
+ Schord chord name as in gSchords
+*/
+opcode chordintervals, i[], S
+ Schord xin
+ index chordindexbyname Schord
+ intervals[] chordintervalsbyindex index
+ xout intervals
+endop
+
+
+/*
+ Get chord intervals by name: return the array from gichordfns that corresponds to the gSchords entry
+ kintervals[] chordintervals Schord
+
+ kintervals[] intervals for the chord obtained from gichordfns
+ Schord chord name as in gSchords
+*/
+opcode chordintervals, k[], S
+ Schord xin
+ kindex chordindexbyname Schord
+ kintervals[] chordintervalsbyindex kindex
+ xout kintervals
+endop
+
+
+
+
+/*
+ Get the midi note numbers or hz for a chord named Schord using inote as the root midi note number
+ inotes[] chordmidi Schord, inote, [iashz=0]
+
+ inotes[] midi note numbers or hz
+ Schord chord name as in gSchords
+ inote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidi, i[], Sio
+ Schord, inote, iashz xin
+ intervals[] chordintervals Schord
+ index = 0
+ while (index < lenarray:i(intervals)) do
+ ivalue = intervals[index] + inote
+ intervals[index] = (iashz == 1) ? cpsmidinn:i(ivalue) : ivalue
+ index += 1
+ od
+ xout intervals
+endop
+
+
+/*
+ Get the midi note numbers or hz for a chord named Schord using knote as the root midi note number
+ knotes[] chordmidi Schord, knote, [iashz=0]
+
+ knotes[] midi note numbers or hz
+ Schord chord name as in gSchords
+ knote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidi, k[], Sko
+ Schord, knote, iashz xin
+ kintervals[] chordintervals Schord
+ kindex = 0
+ while (kindex < lenarray:k(kintervals)) do
+ kvalue = kintervals[kindex] + knote
+ kintervals[kindex] = (iashz == 1) ? cpsmidinn:k(kvalue) : kvalue
+ kindex += 1
+ od
+ xout kintervals
+endop
+
+
+
+/*
+ Get the midi note numbers or hz for a chord from gichordfns by index, using inote as the root midi note number
+ inotes[] chordmidibyindex index, inote, [iashz=0]
+
+ inotes[] midi note numbers or hz
+ index chord index as in gichordfns
+ inote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidibyindex, i[], iio
+ indexc, inote, iashz xin
+ intervals[] chordintervalsbyindex indexc
+ index = 0
+ while (index < lenarray:i(intervals)) do
+ ivalue = intervals[index] + inote
+ intervals[index] = (iashz == 1) ? cpsmidinn:i(ivalue) : ivalue
+ index += 1
+ od
+ xout intervals
+endop
+
+
+
+/*
+ Get the midi note numbers or hz for a chord from gichordfns by index, using knote as the root midi note number
+ knotes[] chordmidibyindex kindex, knote, [iashz=0]
+
+ knotes[] midi note numbers or hz
+ kindex chord index as in gichordfns
+ knote root midi note number
+ iashz 1 returns hz, 0 returns midi note numbers
+*/
+opcode chordmidibyindex, k[], kko
+ kindexc, knote, iashz xin
+ kintervals[] chordintervalsbyindex kindexc
+ kindex = 0
+ while (kindex < lenarray:k(kintervals)) do
+ kvalue = kintervals[kindex] + knote
+ kintervals[kindex] = (iashz == 1) ? cpsmidinn:k(kvalue) : kvalue
+ kindex += 1
+ od
+ xout kintervals
+endop
+
+
+/*
+ Insert midi note numbers or hz for a chord into a table at k-rate, with the first index set as the length, as used by sequencing_melodic.udo
+
+ chordmidibyindextof ifn, kindex, knote, iashz
+
+ ifn table to set values in
+ kindex chord index as in gichordfns
+ knote root midi note number
+ iashz 1 returns hz, 0 sets midi note numbers
+
+*/
+opcode chordmidibyindextof, 0, ikko
+ ifn, kindexc, knote, iashz xin
+ kintervalfn = table:k(kindexc, gichordfns)
+ klen = tableng:k(kintervalfn)
+ tablewkt klen, 0, ifn
+ kindex = 0
+ while (kindex < klen) do
+ tablewkt tablekt:k(kindex, kintervalfn)+knote, kindex+1, ifn
+ kindex += 1
+ od
+endop
+
+
+/*
+ Insert midi note numbers or hz for a chord into a table at init time, with the first index set as the length, as used by sequencing_melodic.udo
+
+ chordmidibyindextof ifn, kindex, knote, iashz
+
+ ifn table to set values in
+ index chord index as in gichordfns
+ inote root midi note number
+ iashz 1 returns hz, 0 sets midi note numbers
+
+*/
+opcode chordmidibyindextof, 0, iiio
+ ifn, indexc, inote, iashz xin
+ intervalfn = table:i(indexc, gichordfns)
+ ilen = tableng:i(intervalfn)
+ tablew ilen, 0, ifn
+ index = 0
+ while (index < ilen) do
+ tablew table:i(index, intervalfn)+inote, index+1, ifn
+ index += 1
+ od
+endop
+
+
+/*
+ LEGACY SUPPORT: possibly deprecated
+ Get the note frequencies for a chord named Schord using inote as the root midi note number
+ inotes[] chordmidicps Schord, inote
+
+ inotes[] note frequencies in hz
+ Schord chord name as in gSchords
+ inote root midi note number
+*/
+opcode chordmidicps, i[], Si
+ Schord, inote xin
+ inotes[] chordmidi Schord, inote, 1
+ xout inotes
+endop
+
+
+
+/*
+ LEGACY SUPPORT: possibly deprecated
+ Get the note frequencies for a chord from gichordfns by index, using inote as the root midi note number
+ inotes[] chordmidicpsbyindex index, inote
+
+ inotes[] note frequencies in hz
+ index chord index as in gichordfns
+ inote root midi note number
+*/
+opcode chordmidicpsbyindex, i[], ii
+ index, inote xin
+ inotes[] chordmidibyindex index, inote, 1
+ xout inotes
+endop
+
+
+
+
+#end
+
diff --git a/sonics/frequency_tools.udo b/sonics/frequency_tools.udo
new file mode 100755
index 0000000..c74739f
--- /dev/null
+++ b/sonics/frequency_tools.udo
@@ -0,0 +1,359 @@
+#ifndef UDO_FREQUENCYTOOLS
+#define UDO_FREQUENCYTOOLS ##
+/*
+ Frequency tools/effects: shifters, ring modulation, delays, chorus etc
+ 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/wavetables.udo"
+
+
+
+/*
+ Mono frequency shifter with hilbert transform
+
+ aoutput freqshift1 ainput, kfrequency
+
+ aoutput output audio
+ ainput input audio
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+*/
+opcode freqshift1, a, ak
+ ain, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ areal, aimag hilbert ain
+ amod1 = areal * acos
+ amod2 = aimag * asin
+ ashift = (amod1 - amod2) * 0.7
+ xout ashift
+endop
+
+
+/*
+ Stereo frequency shifter with hilbert transform
+
+ aoutputL, aoutputR freqshift1 ainputL, ainputR, kfrequency
+
+ aoutputL output audio left
+ aoutputR output audio right
+ ainputL input audio left
+ ainputR input audio right
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+*/
+opcode freqshift1, aa, aak
+ ainL, ainR, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ arealL, aimagL hilbert ainL
+ arealR, aimagR hilbert ainR
+ amod1L = arealL * acos
+ amod2L = aimagL * asin
+ amod1R = arealR * acos
+ amod2R = aimagR * asin
+ ashiftL = (amod1L - amod2L) * 0.7
+ ashiftR = (amod1R - amod2R) * 0.7
+ xout ashiftL, ashiftR
+endop
+
+
+/*
+ Mono ring modulator with hilbert transform
+
+ aoutput ringmod1 ainput, kfrequency
+
+ aoutput output audio
+ ainput input audio
+ kfrequency modulation frequency [MIN(0) MAX(10000) DEFAULT(440)]
+*/
+opcode ringmod1, a, ak
+ ain, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ areal, aimag hilbert ain
+ amod1 = areal * acos
+ amod2 = aimag * asin
+ aupshift = (amod1 - amod2) * 0.7
+ adownshift = (amod1 + amod2) * 0.7
+ xout aupshift+adownshift
+endop
+
+/*
+ Stereo ring modulator with hilbert transform
+
+ aoutputL, aoutputR ringmod1 ainputL, ainputR, kfrequency
+
+ aoutputL output audio left
+ aoutputR output audio right
+ ainputL input audio left
+ ainputR input audio right
+ kfrequency modulation frequency [MIN(0) MAX(10000) DEFAULT(440)]
+*/
+opcode ringmod1, aa, aak
+ ainL, ainR, kfreq xin
+ asin oscili 1, kfreq, gifnSine
+ acos oscili 1, kfreq, gifnSine, .25
+ arealL, aimagL hilbert ainL
+ arealR, aimagR hilbert ainR
+ amod1L = arealL * acos
+ amod2L = aimagL * asin
+ amod1R = arealR * acos
+ amod2R = aimagR * asin
+ aupshiftL = (amod1L - amod2L) * 0.7
+ adownshiftL = (amod1L + amod2L) * 0.7
+ aupshiftR = (amod1R - amod2R) * 0.7
+ adownshiftR = (amod1R + amod2R) * 0.7
+ xout aupshiftL+adownshiftL, aupshiftR+adownshiftR
+endop
+
+
+/*
+ Mono frequency shifter with direct modulation
+
+ aoutput freqshift2 ainput, kfrequency, [kshiftmode=1]
+
+ aoutput output audio
+ ainput input audio
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+ kshiftmode shift mode [TYPE(bool) DEFAULT(1)]
+*/
+opcode freqshift2, a, akP
+ ain, kfreq, kshiftmode xin
+ isr4 = sr * 0.25
+
+ ko1frq = isr4 - (1 - kshiftmode) * kfreq
+ aqo1r oscil 1.0, ko1frq, gifnSine, 0.25 ; cosine
+ aqo1i oscil 1.0, ko1frq, gifnSine, 0.0 ; sine
+
+ ko2frq = isr4 + kshiftmode * kfreq
+ aqo2r oscil 1.0, ko2frq, gifnSine, 0.25 ; cosine
+ aqo2i oscil 1.0, ko2frq, gifnSine, 0.0 ; sine
+ awq1r = ain * aqo1r
+
+ awf1r biquad awq1r, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2r biquad awf1r, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3r biquad awf2r, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+ aw1fr = awf3r * 0.051532459925
+ awq2r = aw1fr * aqo2r
+
+ awq1i = ain * aqo1i
+ awf1i biquad awq1i, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2i biquad awf1i, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3i biquad awf2i, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+
+ aw1fi = awf3i * 0.051532459925
+ awq2i = aw1fi * aqo2i
+ aout = awq2r + awq2i
+ xout aout
+endop
+
+
+/*
+ Stereo frequency shifter with direct modulation
+
+ aoutputL, aoutputR freqshift2 ainputL, ainputR, kfrequency, [kshiftmode=1]
+
+ aoutputL output audio left
+ aoutputR output audio right
+ ainputL input audio left
+ ainputR input audio right
+ kfrequency shift frequency [MIN(-10000) MAX(10000) DEFAULT(-500)]
+ kshiftmode shift mode [TYPE(bool) DEFAULT(1)]
+*/
+opcode freqshift2, aa, aakP
+ ainL, ainR, kfreq, kshiftmode xin
+ isr4 = sr * 0.25
+
+ ko1frq = isr4 - (1 - kshiftmode) * kfreq
+ aqo1r oscil 1.0, ko1frq, gifnSine, 0.25 ; cosine
+ aqo1i oscil 1.0, ko1frq, gifnSine, 0.0 ; sine
+ ko2frq = isr4 + kshiftmode * kfreq
+ aqo2r oscil 1.0, ko2frq, gifnSine, 0.25 ; cosine
+ aqo2i oscil 1.0, ko2frq, gifnSine, 0.0 ; sine
+
+ awq1rL = ainL * aqo1r
+ awq1rR = ainR * aqo1r
+
+ ; Left
+ awf1rL biquad awq1rL, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2rL biquad awf1rL, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3rL biquad awf2rL, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+ aw1frL = awf3rL * 0.051532459925
+ awq2rL = aw1frL * aqo2r
+
+ awq1iL = ainL * aqo1i
+ awf1iL biquad awq1iL, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2iL biquad awf1iL, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3iL biquad awf2iL, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+
+ aw1fiL = awf3iL * 0.051532459925
+ awq2iL = aw1fiL * aqo2i
+ aoutL = awq2rL + awq2iL
+
+ ; Right
+ awf1rR biquad awq1rR, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2rR biquad awf1rR, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3rR biquad awf2rR, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+ aw1frR = awf3rR * 0.051532459925
+ awq2rR = aw1frR * aqo2r
+
+ awq1iR = ainR * aqo1i
+ awf1iR biquad awq1iR, 1, 1.6375276435, 1, 1, -0.93027644018, 0.37171017225
+ awf2iR biquad awf1iR, 1, 0.56037176307, 1, 1, -0.40320752514, 0.73736786626
+ awf3iR biquad awf2iR, 1, 0.19165327787, 1, 1, -0.15398586410, 0.94001488557
+
+ aw1fiR = awf3iR * 0.051532459925
+ awq2iR = aw1fiR * aqo2i
+ aoutR = awq2rR + awq2iR
+
+ xout aoutL, aoutR
+endop
+
+
+
+/*
+ Bit depth reducer/crusher
+
+ aout bitcrush ain, [krush=16]
+
+ aout crushed signal
+ ain input signal
+ krush bits to reduce to [TYPE(int) MIN(1) MAX(128) DEFAULT(16)]
+
+*/
+opcode bitcrush, a, aJ
+ a1, krush xin
+ krush = (krush == -1) ? 16 : krush
+ a1 = round:a(a1 * krush) / krush
+ xout a1
+endop
+
+
+/*
+ Bit depth reducer/crusher (stereo)
+
+ aoutL, aoutR bitcrush ainL, ainR, [krush=16]
+
+ aoutL crushed signal left
+ aoutR crushed signal right
+ ainL input signal left
+ ainR input signal right
+ krush bits to reduce to [TYPE(int) MIN(1) MAX(128) DEFAULT(16)]
+
+*/
+opcode bitcrush, aa, aaJ
+ aL, aR, krush xin
+ krush = (krush == -1) ? 16 : krush
+ aL = round:a(aL * krush) / krush
+ aR = round:a(aR * krush) / krush
+ xout aL, aR
+endop
+
+
+
+/*
+ Resonant delay based tuner
+
+ aout delaytuner ain, kfrequency, kfeedback
+
+ aout tuned/delayed signal summed with input
+ ain input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ kfeedback feedback amount [MIN(0) MAX(1) DEFAULT(0.5)]
+*/
+opcode delaytuner, a, akk
+ ain, kfrequency, kfeedback xin
+ adump delayr 1
+ adelayed deltap (1/kfrequency)
+ delayw ain + (adelayed * kfeedback)
+ aout = ain + adelayed
+ xout aout
+endop
+
+
+/*
+ Resonant delay based tuner (stereo)
+
+ aoutL, aoutR delaytuner ainL, ainR, kfrequency, kfeedback
+
+ aoutL, aoutR tuned/delayed signal summed with input
+ ainL, ainR input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ kfeedback feedback amount [MIN(0) MAX(1) DEFAULT(0.5)]
+*/
+opcode delaytuner, aa, aakk
+ ainL, ainR, kfrequency, kfeedback xin
+ aoutL delaytuner ainL, kfrequency, kfeedback
+ aoutR delaytuner ainR, kfrequency, kfeedback
+ xout aoutL, aoutR
+endop
+
+
+/*
+ Resonant delay based tuner with hold control. When held, only outputs effected, not dry
+
+ aout glitchtuner ain, kfrequency, ktrig
+
+ aout output signal
+ ain input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ khold apply if 1, bypass if 0
+*/
+opcode glitchtuner, a, akk
+ ain, kfrequency, khold xin
+ adump delayr 1
+ adelayed deltap (1/kfrequency)
+ if (khold >= 1) then
+ aout = adelayed
+ else
+ aout = ain
+ endif
+ delayw aout
+ xout aout
+endop
+
+
+/*
+ Resonant delay based tuner with hold control (stereo). When held, only outputs effected, not dry
+
+ aout glitchtuner ain, kfrequency, ktrig
+
+ aoutL, aoutR output signal
+ ainL, ainR input signal
+ kfrequency cps to tune to [MIN(20) MAX(10000) DEFAULT(440)]
+ khold apply if 1, bypass if 0
+*/
+opcode glitchtuner, aa, aakk
+ ainL, ainR, kfrequency, khold xin
+ aoutL glitchtuner ainL, kfrequency, khold
+ aoutR glitchtuner ainR, kfrequency, khold
+ xout aoutL, aoutR
+endop
+
+
+
+/*
+ Simple chorus
+
+ aoutL, aoutR simplechorus ainL, ainR, irateL, irateR
+
+ aoutL, aoutR output signal
+ ainL, ainR input signal
+ irateL delay rate in Hz left
+ irateR delay rate in Hz right
+*/
+opcode simplechorus, aa, aaii
+ aL, aR, irateL, irateR xin
+ alfoL oscil irateL, unirand(1)
+ alfoR oscil irateR, unirand(1)
+ aL vdelay3 aL, (0.01 + alfoL) * 1000, 1000
+ aR vdelay3 aR, (0.01 + alfoR) * 1000, 1000
+ xout aL, aR
+endop
+
+#end
+
diff --git a/sonics/instrument_gchord1.udo b/sonics/instrument_gchord1.udo
new file mode 100755
index 0000000..314ea0a
--- /dev/null
+++ b/sonics/instrument_gchord1.udo
@@ -0,0 +1,261 @@
+#ifndef UDO_FNMI_GCHORD1
+#define UDO_FNMI_GCHORD1 ##
+/*
+ Portamento glitch-out textural chord player
+ 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/wavetables.udo"
+#include "sonics/sequencing_melodic_portamento.udo"
+#include "sonics/sounddb.udo"
+#include "sonics/frequency_tools.udo"
+#include "sonics/uniqueid.udo"
+
+/*
+ sounddb glitchy chord player
+ aL, aR fnmi_gchord1 icollectionid, iattacktime, ireleasetime, icompressmode, kchangechance [, ipitchratio=1, ireadtype=0, ireloadtime=10]
+ aL, aR fnmi_gchord1 Scollection, iattacktime, ireleasetime, icompressmode, kchangechance [, ipitchratio=1, ireadtype=0, ireloadtime=10]
+
+ aL, aR audio output
+
+ icollectionid sounddb collection ID to use
+ Scollection sounddb collection name to use
+ iattacktime start fade in time
+ ireleasetime fade out time on host instrument note end
+ icompressmode 0 = none ; 1 = harshwall ; 2 = normal
+ kchangechance glitchy item change rate chance (1 = every quarter beat)
+ ipitchratio default pitch augmentation ratio
+ ireadtype 0 = sndwarp ; 1 = mincer
+ ireloadtime seconds between reloads of subinstruments to ensure variation in source sound
+*/
+
+opcode fnmi_gchord1, aa, iiiikpoj
+ icollectionid, iattacktime, ireleasetime, icompressmode, kchangechance, ipitchratio, ireadtype, ireloadtime xin
+ ilen = p3
+ ireloadtime = (ireloadtime == -1) ? 10 : ireloadtime
+ instanceid = uniqueid()
+
+ iusedinstruments[] uniqueinstrnums "_fnmi_gchord1_notehold", ftlen(gimel_freqs)
+
+ ; set up notehold instruments
+ index = 0
+ while (index < lenarray(iusedinstruments)) do
+ schedule iusedinstruments[index], 0, ilen, index, icollectionid, ireleasetime, instanceid, ipitchratio, ireadtype
+ index += 1
+ od
+
+
+ ; reload random notehold instrument at periodic intervals (ie to change source sound)
+ klastchangetime init 0
+ ktime timeinsts
+ if (ktime - klastchangetime > ireloadtime) then
+ kindex = round:k(random(0, lenarray(iusedinstruments)-1))
+ kinstrument = iusedinstruments[kindex]
+ turnoff2 kinstrument, 4, 1
+ schedulek kinstrument, ireleasetime*0.5, ilen-ktime, kindex, icollectionid, ireleasetime, instanceid, ipitchratio, ireadtype
+ klastchangetime = ktime
+ endif
+
+
+ ; if host instrument of opcode ends, turn off all notehold instances
+ kreleasing init 0
+ if (release:k() == 1 && kreleasing == 0) then
+ kreleasing = 1
+ kindex = 0
+ while (kindex < lenarray(iusedinstruments)) do
+ turnoff2 iusedinstruments[kindex], 4, 1
+ kindex += 1
+ od
+ endif
+
+ ; if at end of host instrument note length, add release time for relevant fade out
+ if (lastcycle:k() == 1) then
+ xtratim ireleasetime
+ endif
+
+
+ ; trigger for variations in individual notehold instruments
+ idivisions = 4
+ as, aps syncphasor -(gkseq_beathz*idivisions), a(gkseq_beat)
+ ktrig trigger k(as), 0.1, 0
+ chnset ktrig, sprintf("fnmi_gchord1_qtrig%d", instanceid)
+
+
+ ; 'global' change chance for the notehold instruments
+ kchangechance = 0.5
+ chnset kchangechance, sprintf("fnmi_gchord1_changechance%d", instanceid)
+
+ ; feed from the notehold instruments
+ aL, aR bus_read sprintf("fnmi_gchord1_out%d", instanceid)
+
+ if (icompressmode == 1) then
+ acomp noise 0.2, 0.4
+ aL balance aL, acomp
+ aR balance aL, acomp
+ elseif (icompressmode == 2) then
+ aL compress aL, aL, -5, 40, 40, 6, 0, 0.1, 0
+ aR compress aR, aR, -5, 40, 40, 6, 0, 0.1, 0
+ aL *= 30
+ aR *= 30
+ endif
+
+ aL dcblock aL
+ aR dcblock aR
+
+
+ kamp linsegr 0, iattacktime, 1, ilen - iattacktime, 1, ireleasetime, 0
+
+ xout aL*kamp, aR*kamp
+endop
+
+; overload for named collection
+opcode fnmi_gchord1, aa, Siiikpoj
+ Scollection, iattacktime, ireleasetime, icompressmode, kchangechance, ipitchratio, ireadtype, ireloadtime xin
+ aL, aR fnmi_gchord1 sounddb_getcollectionid(Scollection), iattacktime, ireleasetime, icompressmode, kchangechance, ipitchratio, ireadtype, ireloadtime
+ xout aL, aR
+endop
+
+
+
+/*
+ Used internally by fnmi_gchord1 for sound generation and return via channel
+*/
+instr _fnmi_gchord1_notehold
+ index = p4
+ icollectionid = p5
+ ireleasetime = p6
+ instanceid = p7
+ iuserpitchratio = p8
+ ireadtype = p9
+ kamp table index, gimel_amps
+
+ aL init 0
+ aR init 0
+ if (kamp > 0) then ; all processing
+ kamp *= 0.32 ;0.05
+ kfreq table index, gimel_freqs
+ ibasenote random 30, 50
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, ibasenote
+ ifn = gisounddb[ifileid][0]
+
+ ipitchratio *= ((ireadtype == 0) ? (ftsr(ifn) / sr) : 1) * iuserpitchratio ; sr adjustment for sndwarp required
+ ilen = ftlen(ifn) / ftsr(ifn)
+
+ ; pitch lfo
+ alfo oscil 2.5, 0.15, gifnSine
+ kfreq += k(alfo)
+
+ kpitchratio = (kfreq / cpsmidinn(ibasenote)) * ipitchratio
+
+ istart = random(0, 0.1) ;* ilen
+ iend = random(istart+0.1, 0.4) ; 0.9
+
+ kreadmode init 0
+
+ if (kreadmode == 0) then
+ atime = (abs(oscil(iend-istart, random(0.001, 0.1), gifnSine, random(0, 1)))) * ilen ; TODO: don't think + istart is required here
+ elseif (kreadmode == 1) then
+ atime = (istart * ilen) + ((phasor(random(2, 10)) * (ilen * (iend - istart))))
+ else
+ atime = (istart * ilen) + ((phasor(-random(2, 10)) * (ilen * (iend - istart))))
+ endif
+
+ if (ireadtype == 0) then
+ aL, aR sndwarpst kamp, atime, interp(kpitchratio), ifn, istart, 441*random(1, 100), 44*random(1, 10), 8, gifnHalfSine, 1
+ elseif (ireadtype == 1) then
+ aL, aR mincer atime, kamp, kpitchratio, ifn, 0
+ endif
+
+ kdo_crush init 0
+ kdo_diff init 0
+ kdo_delaytuner init 0
+ kdo_ringmod init 0
+ kdelmult init 8
+ kcrushrange init 4
+ kringmodmult init 2
+ khpfreq init 150
+ kpan init random(0, 1)
+
+ if (kdo_crush == 1) then
+ kcrush = abs:k(oscil:k(kcrushrange, random(0.01, 0.3))) + kcrushrange
+ kcrushamount = abs:k(oscil:k(0.7, random(0.001, 0.2), gifnSaw, random(0, 1)))
+ aLbc, aRbc bitcrush aL, aR, kcrush
+ aL += aLbc * kcrushamount
+ aR += aRbc * kcrushamount
+ endif
+
+ if (kdo_ringmod == 1) then
+ aL, aR ringmod1 aL, aR, kfreq*kringmodmult ;portk(kfreq*kringmodmult, 0.01)
+ endif
+
+ if (kdo_delaytuner == 1) then
+ kdelaytuneramount = abs:k(oscil:k(0.5, random(0.001, 0.2), gifnSine, random(0, 1)))
+ aLdt, aRdt delaytuner aL, aR, max:k(1, kpitchratio)*kdelmult, 0.9 ; portk(kdelmult, 0.1)
+ aL += aLdt * kdelaytuneramount
+ aR += aRdt * kdelaytuneramount
+ endif
+
+ aL butterhp aL, khpfreq
+ aR butterhp aR, khpfreq
+
+ if (kdo_diff == 1) then
+ aL diff aL
+ endif
+
+ ktrig = chnget:k(sprintf("fnmi_gchord1_qtrig%d", instanceid))
+ kchangechance = chnget:k(sprintf("fnmi_gchord1_changechance%d", instanceid))
+
+ if (ktrig == 1 && random:k(0, 1) < kchangechance) then
+ if (random:k(0, 1) > 0.9) then
+ kreadmode = round:k(random:k(0, 2))
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ khpfreq = random:k(250, 2500)
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kdelmult = round:k(random:k(8, 16))
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kcrushrange = round:k(random:k(2, 64))
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kdo_crush = 1 - kdo_crush
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kdo_delaytuner = 1 - kdo_delaytuner
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kdo_ringmod = 1 - kdo_ringmod
+ endif
+
+ if (random:k(0, 1) > 0.95) then
+ kringmodmult = pow:k(2, round:k(random:k(-1, 2))) ; 3 up to 8
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kpan = random:k(0, 1)
+ endif
+
+ if (random:k(0, 1) > 0.9) then
+ kdo_diff = 1 - kdo_diff
+ endif
+ endif
+
+ aL *= kpan
+ aR *= (1-kpan)
+ endif ; if amp > 0
+
+ krelamp linsegr 1, p3, 1, ireleasetime, 0
+ bus_mix(sprintf("fnmi_gchord1_out%d", instanceid), aL*krelamp, aR*krelamp)
+endin
+
+#end
diff --git a/sonics/instrument_portchord.udo b/sonics/instrument_portchord.udo
new file mode 100755
index 0000000..97c1ec2
--- /dev/null
+++ b/sonics/instrument_portchord.udo
@@ -0,0 +1,133 @@
+#ifndef UDO_FNMI_PORTCHORD
+#define UDO_FNMI_PORTCHORD ##
+/*
+ Portamento recursive chord players
+ 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"
+#include "sonics/sequencing_melodic_persistence.udo"
+#include "sonics/sequencing_melodic_portamento.udo"
+#include "sonics/wavetables.udo"
+#include "sonics/sounddb.udo"
+
+
+/*
+ Play continuous chords from melodic sequencer with portamento, using oscil as an instrument and a specified wavetable
+
+ aL, aR portchord_wave [iwavefn=gifnSine, ifreqmult=1, ivibdepth=1, ivibrate=3, index=0]
+
+ aL, aR stereo outputs
+ iwavefn the f-table to use with oscil
+ ifreqmult frequency multiplier of the chord note frequencies to be applied
+ ivibdepth vibrato depth
+ ivibrate vibrato rate in Hz
+ index internal start index of the chord notes; could also be used to specify starting note offset
+*/
+opcode portchord_wave, aa, jpjjo
+ iwavefn, ifreqmult, ivibdepth, ivibrate, index xin
+
+ iwavefn = (iwavefn == -1) ? gifnSine : iwavefn
+ ivibdepth = (ivibdepth == -1) ? 1 : ivibdepth
+ ivibrate = (ivibrate == -1) ? 3 : ivibrate
+
+ kamp table index, gimel_amps
+ kfreq table index, gimel_freqs
+
+ klfo = oscil:k(ivibdepth, ivibrate) ;oscil:k(7, 5)
+ kfreq += klfo
+ kfreq *= ifreqmult
+
+ ;kamp portk kamp, (i(gkseq_beattime) * gimel_portamento_beatratio) ; fade out when change
+
+ aL oscil kamp*0.1, kfreq, iwavefn
+ ipan = random(0, 1)
+ aR = aL * ipan
+ aL *= (1 - ipan)
+
+ if (index + 1 < ftlen(gimel_amps)) then
+ aLx, aRx portchord_wave iwavefn, ifreqmult, ivibdepth, ivibrate, index + 1
+ aL += aLx
+ aR += aRx
+ endif
+
+ xout aL, aR
+endop
+
+
+
+/*
+ Play continuous chords from melodic sequencer with portamento, using a sounddb collection as source sounds
+
+ aL, aR portchord_sound icollectionid [, imode=1, ifreqmult=1, ifftsize=giFFTsize, index=0]
+
+ aL, aR stereo outputs
+ icollectionid collection ID from sounddb to use for the playback
+ imode 0 = read with sndwarp; 1 = read with mincer
+ ifreqmult frequency multiplier of the chord note frequencies to be applied
+ ifftsize FFT size to use when imode = 1 ; default to global setting in __config__.udo
+ index internal start index of the chord notes; could also be used to specify starting note offset
+*/
+opcode portchord_sound, aa, ippjo
+ icollectionid, imode, ifreqmult, ifftsize, index xin
+
+ ifftsize = (ifftsize == -1) ? giFFTsize : ifftsize
+
+ inote = round(random(50, 80))
+ ibasefreq = cpsmidinn(inote)
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+
+ ifn = gisounddb[ifileid][0]
+ ichannels = gisounddb[ifileid][1]
+ idur = gisounddb[ifileid][2]
+ irmsnorm = gisounddb[ifileid][3]
+
+ kampb table index, gimel_amps
+ kfreq table index, gimel_freqs
+
+ kamp portk kampb, (i(gkseq_beattime) * gimel_portamento_beatratio) ; fade out when change
+
+ kpitch = (kfreq / ibasefreq) * ifreqmult ; actual pitch adjustment
+
+ istart = random(0.05, 0.2)
+ iend = random(istart+0.1, 0.8)
+ atime = abs(oscil(iend - istart, random(0.001, 0.1), gifnSine, random(0, 1))) + istart
+
+
+ klfo = oscil:k(random(0.0001, 0.009), random(1, 5)) + 1
+ kpitch *= klfo
+
+ if (kamp != 0) then
+ if (imode == 0) then
+ kpitch *= (ftsr(ifn) / sr) ; adjustment for sndwarp required
+
+ ;apitch interp kpitch
+ aL, aR sndwarpst kamp, atime, kpitch, ifn, istart, 4410, 441, 8, gifnHalfSine, 1
+
+ else
+ if (ichannels == 2) then
+ aL, aR mincer atime, kamp, kpitch, ifn, 0, ifftsize
+ else
+ aL mincer atime, kamp, kpitch, ifn, 0, ifftsize
+ aR = aL
+ endif
+ endif
+ endif
+
+ aL *= (1 - irmsnorm) * 0.5
+ aR *= (1 - irmsnorm) * 0.5
+
+ ; recursion for all chord parts
+ if (index + 1 < ftlen(gimel_amps)) then
+ aLx, aRx portchord_sound icollectionid, imode, ifreqmult, ifftsize, index + 1
+ aL += aLx
+ aR += aRx
+ endif
+ xout aL, aR
+endop
+
+#end
diff --git a/sonics/instrument_sineblips.udo b/sonics/instrument_sineblips.udo
new file mode 100755
index 0000000..7ddc407
--- /dev/null
+++ b/sonics/instrument_sineblips.udo
@@ -0,0 +1,77 @@
+#ifndef UDO_FNMI_SINEBLIP
+#define UDO_FNMI_SINEBLIP ##
+/*
+ Stochastic sequenced sine blip instrument
+ 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/bussing.udo"
+#include "sonics/sequencing_melodic.udo"
+
+
+/*
+ Randomised sine blip playback internal instrument
+*/
+instr _fnmi_sineblip
+ Sbus = p4
+ inote = mel_randomnote:i() + 12
+ if (random(0, 1) > 0.5) then
+ inote += 12
+ endif
+
+ if (random(0, 1) > 0.5) then
+ inote += 12
+ endif
+
+ if (random(0, 1) > 0.5) then
+ inote += 12
+ endif
+
+ if (random(0, 1) > 0.99) then
+ inote += 1
+ endif
+ ibasefreq = cpsmidinn(inote)
+ ifreqL = ibasefreq + random(-5, 5)
+ ifreqR = ibasefreq + random(-5, 5)
+ iampL = random(0.5, 1)
+ iampR = random(0.5, 1)
+ aL oscil iampL, ifreqL
+ aR oscil iampR, ifreqR
+
+ if (random(0, 1) > 0.5) then
+ kamp line 1, p3, 0
+ else
+ kamp linseg 1, p3*0.9, 1, p3*0.1, 0
+ endif
+ bus_mix(Sbus, aL*0.6*kamp, aR*0.6*kamp)
+endin
+
+
+/*
+ Randomised sine blip playback scheduler
+*/
+instr fnmi_sineblips
+ if (p4 == 0) then
+ Sbus = "main"
+ else
+ Sbus = p4
+ endif
+
+ inum = random(1, 8)
+ iqtime = i(gkseq_quartertime)
+ itimeindex = random(0, 8)
+ index = 0
+ while (index < inum) do
+ itime = seq_swingtime:i(iqtime * itimeindex, itimeindex)
+ schedule "_fnmi_sineblip", itime, random(0.05, 0.1), Sbus
+ itimeindex += random(1, 4)
+ index += 1
+ od
+ xtratim iqtime * itimeindex
+endin
+
+#end
diff --git a/sonics/pgdb.udo b/sonics/pgdb.udo
new file mode 100755
index 0000000..be2598a
--- /dev/null
+++ b/sonics/pgdb.udo
@@ -0,0 +1,16 @@
+#ifndef UDO_PGDB
+#define UDO_PGDB ##
+/*
+ PostgreSQL connection and tools
+ Slim excerpt for Partial Emergence
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "sonics/__config__.udo"
+
+gidb dbconnect "postgresql", "$PGDB_HOST", "$PGDB_NAME", "$PGDB_USER", "$PGDB_PASSWORD"
+
+#end
diff --git a/sonics/sequencing.udo b/sonics/sequencing.udo
new file mode 100755
index 0000000..b81b65a
--- /dev/null
+++ b/sonics/sequencing.udo
@@ -0,0 +1,99 @@
+#ifndef UDO_SEQUENCING
+#define UDO_SEQUENCING ##
+/*
+ Sequencing 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
+*/
+
+gkseq_tempo init 120 ; tempo BPM
+gkseq_beat init 0 ; trigger fired on each beat
+gkseq_beattime init 0 ; time in seconds of one beat (read only; set by BPM)
+gkseq_quartertime init 0 ; time in seconds of one quarter beat (read only; set by BPM)
+gkseq_beathz init 0 ; Hz of one beat (read only; set by BPM)
+gkseq_swing init 0.2 ; swing amount
+
+
+/*
+ Instrument to control the main beat metronome and beat time globals
+*/
+instr _seq_manager
+ gkseq_beat metro gkseq_tempo / 60
+ gkseq_beattime = 60 / gkseq_tempo
+ gkseq_quartertime = gkseq_beattime / 4
+ gkseq_beathz = (1 / 60) * gkseq_tempo
+endin
+alwayson "_seq_manager"
+
+
+
+/*
+ Get the swung time for a given time, if appropriate. If the index given is a second 16th, time will be swung
+
+ kresult seq_swingtime ktime, kindex, kswing
+
+ kresult resulting time
+ ktime the time to consider
+ kindex beat index, beginning with 0
+ kswing the swing amount (0 to 1)
+*/
+opcode seq_swingtime, k, kkJ
+ ktime, kindex, kswing xin
+ kswing = (kswing == -1) ? gkseq_swing : kswing
+ if ((kindex+1) % 2 == 0) then
+ ktime = ktime + (gkseq_quartertime*kswing)
+ endif
+ xout ktime
+endop
+
+
+/*
+ Get the swung time for a given time, if appropriate. If the index given is a second 16th, time will be swung
+
+ iresult seq_swingtime itime, iindex, iswing
+
+ iresult resulting time
+ itime the time to consider
+ iindex beat index, beginning with 0
+ iswing the swing amount (0 to 1)
+*/
+opcode seq_swingtime, i, iij
+ itime, index, iswing xin
+ iswing = (iswing == -1) ? i(gkseq_swing) : iswing
+ if ((index+1) % 2 == 0) then
+ itime = itime + (i(gkseq_quartertime)*iswing)
+ endif
+ xout itime
+endop
+
+
+/*
+ Set the tempo in BPM
+
+ seq_settempo ktempo
+
+ ktempo the tempo in BPM
+*/
+opcode seq_settempo, 0, k
+ ktempo xin
+ gkseq_tempo = ktempo
+endop
+
+
+/*
+ Set the tempo in BPM; typically for host control
+
+ p4 the tempo in BPM
+*/
+instr seq_settempo
+ itempo = p4
+ gkseq_tempo = itempo
+ turnoff
+endin
+
+
+
+#end
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
diff --git a/sonics/sequencing_melodic_persistence.udo b/sonics/sequencing_melodic_persistence.udo
new file mode 100755
index 0000000..cd1943d
--- /dev/null
+++ b/sonics/sequencing_melodic_persistence.udo
@@ -0,0 +1,54 @@
+#ifndef UDO_MELSEQUENCINGPERSIST
+#define UDO_MELSEQUENCINGPERSIST ##
+/*
+ Melodic sequencer persistence: saving/loading from files and database
+ Slim excerpt for Partial Emergence
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+#include "sonics/sequencing_melodic.udo"
+#include "sonics/array_tools.udo"
+
+/*
+ Load state from file
+
+ p4 path to load from
+*/
+instr mel_loadstate_fs
+ Spath = p4
+ isize = -1
+ iline = 0
+
+ ftload Spath, 1,\
+ gimel_chords, gimel_notes,
+ gimel_lengths, gimel_action1,\
+ gimel_action2, gimel_actionthreshold,\
+ gimel_active, gimel_importance,\
+ gimel_mod1, gimel_mod2,\
+ gimel_mod3, gimel_mod4,\
+ gimel_state
+
+ gkmel_futures_refresh_trig = 1
+ turnoff
+endin
+
+
+
+; if MEL_INITPATH is set, load the specified progression data accordingly
+#ifdef MEL_HASINIT
+instr _mel_persistence_init
+#ifdef MEL_INITPATH
+ subinstrinit "mel_loadstate_fs", "$MEL_INITPATH"
+#end
+ alwayson "_mel_manager"
+ turnoff
+endin
+schedule "_mel_persistence_init", 0, 60
+
+; end MEL_HASINIT
+#end
+
+#end
diff --git a/sonics/sequencing_melodic_portamento.udo b/sonics/sequencing_melodic_portamento.udo
new file mode 100755
index 0000000..208919d
--- /dev/null
+++ b/sonics/sequencing_melodic_portamento.udo
@@ -0,0 +1,172 @@
+#ifndef UDO_MELSEQUENCINGPORT
+#define UDO_MELSEQUENCINGPORT ##
+
+/*
+ Extension to sequencing_melodic.udo which permits usage of k-rate frequency arrays
+ 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/sequencing_melodic.udo"
+#include "sonics/wavetables.udo"
+
+
+gimel_freqs ftgen 0, 0, -12, -7, 0 ; current notes: index 0 is the length
+gimel_amps ftgen 0, 0, -12, -7, 0 ; current notes: index 0 is the length
+gimel_portamento_beatratio init 0.5 ; portamento time as ratio of current beat time
+gimel_linetype init 0 ; 0=pre-section, 1=post-section
+
+
+/*
+ Automate a frequency/amp line
+*/
+instr _mel_linedraw
+ index = p4
+ ifreq = p5
+ iamp = p6
+
+ icurrentfreq table index, gimel_freqs
+
+ if (icurrentfreq == 0 && ifreq != 0) then
+ tablew ifreq, index, gimel_freqs
+ elseif (ifreq != 0 && icurrentfreq != ifreq) then
+ tablew line:k(icurrentfreq, p3, ifreq), index, gimel_freqs
+ endif
+
+ icurrentamp table index, gimel_amps
+ if (icurrentamp != iamp) then
+ tablew line:k(icurrentamp, p3, iamp), index, gimel_amps
+ endif
+endin
+
+
+instr _mel_linestep_inner
+ if (timeinstk() == 1) then
+ turnoff2 "_mel_linedraw", 0, 0
+ endif
+
+ if (table:i(1, gimel_next_notes) != 0) then
+ index = 0
+ while (index < table:i(0, gimel_next_notes)) do
+ event_i "i", "_mel_linedraw", 1/kr, p3, index, cpsmidinn(table:i(index + 1, gimel_next_notes)), 1
+ index += 1
+ od
+ while (index < ftlen(gimel_freqs)) do
+ event_i "i", "_mel_linedraw", 1/kr, p3, index, 0, 0
+ index += 1
+ od
+ endif
+endin
+
+
+instr _mel_linestep
+ icurrentduration mel_length
+ ilinetime = (i(gkseq_beattime) * gimel_portamento_beatratio)
+ if (gimel_linetype == 0) then
+ inextline = icurrentduration - ilinetime
+ else
+ inextline = icurrentduration
+ endif
+ event_i "i", "_mel_linestep_inner", inextline, ilinetime
+ turnoff
+endin
+
+
+/*
+ Portamento manager: respond to gkmel_section_change trigger by calling _mel_linestep instrument
+*/
+instr _mel_linemanager
+ ; set initial freqs
+ index = 0
+ while (index < table:i(0, gimel_current_notes)) do
+ tablew cpsmidinn(table:i(index + 1, gimel_current_notes)), index, gimel_freqs
+ tablew 1, index, gimel_amps
+ index += 1
+ od
+ while (index < ftlen(gimel_freqs)) do
+ tablew 0, index, gimel_amps
+ index += 1
+ od
+
+ schedkwhen gkmel_section_change, 0, 1, "_mel_linestep", 0, 1
+endin
+
+schedule "_mel_linemanager", 0.1, 36000 ; notes not ready on 0
+;alwayson "_mel_linemanager"
+
+
+
+
+
+/*
+ Recursively create a chord to be used by mel_tune_portamento; internal use only
+
+ aout _mel_tune_chord_portamento kfreqmult, ifn, imaxmult, imult, index
+
+ aout chord output
+ kfreqmult frequency multiplier to apply to tuning
+ ifn wavetable to use
+ imaxmult multiples of harmonics to generate in tuning
+ imult internal multiplier for recursion
+ index internal index for recursion
+
+*/
+opcode _mel_tune_chord_portamento, a, kiipo
+ kfreqmult, ifn, imaxmult, imult, index xin
+
+
+ if (index + 1 > ftlen(gimel_amps)) then
+ index = 0
+ imult += 1
+ endif
+
+ aout = oscil(table:k(index, gimel_amps), kfreqmult * table:k(index, gimel_freqs) * pow:k(2, imult), ifn) * 0.1
+ ; recursion for all chord parts
+ if (imult <= imaxmult) then
+
+ aout += _mel_tune_chord_portamento(kfreqmult, ifn, imaxmult, imult, 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
+ imaxmult multiples of harmonics to generate in tuning (defaults to 4)
+ ifftrate fft size, defaults to config default
+ ifftdiv fft window division factor (eg 4, 8, 16), defaults to config default
+ kfreqmult frequency multiplier to apply to tuning
+*/
+opcode mel_tune_portamento, aa, aaooooP
+ aL, aR, ifn, imaxmult, ifftrate, ifftdiv, kfreqmult xin
+ ifn = (ifn == 0) ? gifnSine : ifn
+ imaxmult = (imaxmult == 0) ? 4 : imaxmult
+ ifftrate = (ifftrate == 0) ? giFFTsize : ifftrate
+ ifftdiv = (ifftdiv == 0) ? giFFTwinFactor : ifftdiv
+ fmods pvsanal _mel_tune_chord_portamento(kfreqmult, ifn, imaxmult), 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
+
+
+#end
+
diff --git a/sonics/sounddb.udo b/sonics/sounddb.udo
new file mode 100755
index 0000000..502d007
--- /dev/null
+++ b/sonics/sounddb.udo
@@ -0,0 +1,229 @@
+#ifndef UDO_SOUNDDB
+#define UDO_SOUNDDB ##
+/*
+ SQL database interface to sound object management.
+ 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
+
+*/
+
+; if XDB extract has been loaded, don't use database
+#ifdef XDB_SET
+#include "sonics/soundxdb.udo"
+#else
+
+#include "sonics/pgdb.udo"
+
+; set max number of files for global array allocation
+imaxindex dbscalar gidb, "SELECT MAX(id)+1 FROM file"
+gisounddb[][] init imaxindex, 4
+
+
+/*
+ Load file to gisounddb: to be used internally and passed parameters from database
+
+ _sounddb_loadfile ifileid, Spath, ichannels, iduration, irmsnorm, isamplerate
+
+ ifileid database file ID, corresponds to index of gisounddb
+ Spath path to load sound file from
+ ichannels number of channels
+ iduration sound duration
+ irmsnorm normalisation factor
+ isamplerate sample rate
+*/
+opcode _sounddb_loadfile, 0, iSiiii
+ ifileid, Spath, ichannels, iduration, irmsnorm, isamplerate xin
+ isize = iduration * isamplerate * ichannels
+ ifn = ftgen(0, 0, isize, 1, strcat("$SOUND_BASE/", Spath), 0, 0, 0)
+ gisounddb[ifileid][0] = ifn
+ gisounddb[ifileid][1] = ichannels
+ gisounddb[ifileid][2] = iduration
+ gisounddb[ifileid][3] = irmsnorm
+endop
+
+
+/*
+ Get file details for a give file ID
+
+ ifn, ichannels, iduration, irmsnorm sounddb_get ifileid
+
+ ifn ftable number containing sound
+ ichannels number of channels in file
+ iduration duration of file in seconds
+ irmsnorm RMS normalisation factor
+ ifileid file ID to look up
+*/
+opcode sounddb_get, iiii, i
+ ifileid xin
+ xout gisounddb[ifileid][0], gisounddb[ifileid][1], gisounddb[ifileid][2], gisounddb[ifileid][3]
+endop
+
+
+/*
+ Load files to gisounddb if not already loaded, to be passed a 2D string array as returned from a database query. Returns the file IDs in an array
+
+ ifileids[] _sounddb_loadobject SqueryResult[][]
+
+ ifileids[] database file IDs, which also correspond to indexes in gisounddb
+ SqueryResult[][] query result from database with each row containing file ID, path, channels, duration, RMS normalisation factor and samplerate
+*/
+opcode _sounddb_loadobject, i[], S[][]
+ Sres[][] xin
+ iarraylength = lenarray(Sres)
+ idata[] init iarraylength
+ index = 0
+ while (index < iarraylength) do
+ ifileid strtod Sres[index][0] ; fileid
+ idata[index] = ifileid
+
+ if (gisounddb[ifileid][0] == 0) then ; load required
+ _sounddb_loadfile ifileid, Sres[index][1], strtod(Sres[index][2]), strtod(Sres[index][3]), strtod(Sres[index][4]), strtod(Sres[index][5])
+ endif
+ index += 1
+ od
+ xout idata
+endop
+
+
+/*
+ Load a sound to gisounddb if not already loaded, based on a specified query using f_nearestnote.
+ Return the file ID and the result of column 6, which is the ratio to the nearest pitch requested.
+ Used internally by the sounddb_mel_nearestnote opcodes which select one row
+
+ ifileid, ipitchratio _sounddb_mel_nearestnote_inner Squery
+
+ ifileid file ID
+ ipitchratio pitch ratio to note requested
+ Squery query to evaluate
+*/
+opcode _sounddb_mel_nearestnote_inner, ii, S
+ Squery xin
+ Sres[][] dbarray gidb, Squery
+ ifileid strtod Sres[0][0]
+
+ if (gisounddb[ifileid][0] == 0) then ; load required
+ _sounddb_loadfile ifileid, Sres[0][1], strtod(Sres[0][2]), strtod(Sres[0][3]), strtod(Sres[0][4]), strtod(Sres[0][5])
+ endif
+ xout ifileid, strtod(Sres[0][6])
+endop
+
+
+; nearest note query base
+#define SOUNDDB_NNQUERYBASE #SELECT file_id, path, channels, duration, rmsnormal, samplerate, pitchratio FROM f_nearestnote#
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote Scollection, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ Scollection collection name
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, Si
+ Scollection, inote xin
+ ifileid, ipitchratio _sounddb_mel_nearestnote_inner sprintf("$SOUNDDB_NNQUERYBASE (%f, '%s')", inote, Scollection)
+ xout ifileid, ipitchratio
+endop
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ icollectionid collection ID
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, ii
+ icollectionid, inote xin
+ ifileid, ipitchratio _sounddb_mel_nearestnote_inner sprintf("$SOUNDDB_NNQUERYBASE (%f, %d)", inote, icollectionid)
+ xout ifileid, ipitchratio
+endop
+
+
+/*
+ Get the ID of a filecollection by name
+
+ icollectionid sounddb_getcollectionid Scollection
+
+ icollectionid collection ID
+ Scollection collection name
+*/
+opcode sounddb_getcollectionid, i, S
+ Scollection xin
+ icollectionid = dbscalar(gidb, sprintf("SELECT id FROM filecollection WHERE name = '%s'", Scollection))
+ xout icollectionid
+endop
+
+
+/*
+ Get the collection ID and file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[], icollectionid sounddb_getcollection Scollection
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ icollectionid collection ID
+ Scollection collection name
+*/
+opcode sounddb_getcollection, i[]i, S
+ Scollection xin
+ Sbase = {{select file_id, path, channels, duration, rmsnormal, samplerate, fc.id
+ from svw.analysis_basic_collectionnorm a
+ join filecollection fc on fc.id = a.filecollection_id
+ where %s
+ }}
+
+ if (strindex(Scollection, ",") > 0) then
+ Sclause = "(1=2"
+ index = 1
+ Stemp = Scollection
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ Sclause strcat Sclause, sprintf(" OR fc.name='%s'", strsub(Stemp, 0, index))
+ Stemp strsub Stemp, index+1
+ else
+ Sclause strcat Sclause, sprintf(" OR fc.name='%s'", Stemp)
+ endif
+ od
+ Sclause strcat Sclause, ")"
+ else
+ Sclause = sprintf("fc.name = '%s'", Scollection)
+ endif
+
+ Squery sprintf Sbase, Sclause
+prints Squery
+prints "\n\n"
+ Sres[][] dbarray gidb, Squery
+ idata[] _sounddb_loadobject Sres
+ icollectionid = strtod(Sres[0][6])
+ xout idata, icollectionid
+endop
+
+
+/*
+ Get the file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[] sounddb_getcollection Scollection
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ Scollection collection name
+*/
+opcode sounddb_getcollection, i[], S
+ Scollection xin
+ idata[], icollectionid sounddb_getcollection Scollection
+ xout idata
+endop
+
+; end of XDB_SET
+#end
+
+#end
diff --git a/sonics/soundxdb.udo b/sonics/soundxdb.udo
new file mode 100755
index 0000000..c27b2bf
--- /dev/null
+++ b/sonics/soundxdb.udo
@@ -0,0 +1,202 @@
+#ifndef UDO_SOUNDXDB
+#define UDO_SOUNDXDB ##
+/*
+ SQL database extract interface to sound object management.
+ File containing extract definitions must be included before this.
+ Slim excerpt for Partial Emergence
+
+ This file is part of the SONICS UDO collection by Richard Knight 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+
+*/
+
+#ifndef XDB_SET
+prints "Database extract not defined; cannot continue.\n\n\n"
+exitnow
+#end
+
+
+#ifndef XDB_MINNOTE
+#define XDB_MINNOTE #0#
+#end
+
+
+
+
+
+/*
+ Get the ID of a filecollection by name
+
+ icollectionid sounddb_getcollectionid Scollection
+
+ icollectionid collection ID
+ Scollection collection name
+*/
+opcode sounddb_getcollectionid, i, S
+ Scollection xin
+ index = 0
+ while (index < lenarray(gSxdb_collections)) do
+ if (strcmp(gSxdb_collections[index], Scollection) == 0) then
+ igoto complete
+ endif
+ index += 1
+ od
+ index = -1
+complete:
+ xout index
+endop
+
+
+
+/*
+ Get the file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[] sounddb_getcollection Scollection
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ Scollection collection name
+*/
+opcode sounddb_getcollection, i[], S
+ Scollection xin
+ idata[], icollectionid sounddb_getcollection Scollection
+ xout idata
+endop
+
+
+
+
+
+/*
+ Get the collection ID and file IDs of a filecollection, also loading each file to gisounddb
+
+ ifileids[], icollectionid sounddb_getcollection Scollection
+
+ ifileids[] file IDs in the collection, accessible as indexes to f-tables in gisounddb
+ icollectionid collection ID
+ Scollection collection name
+*/
+opcode sounddb_getcollection, i[]i, S
+ Scollection xin
+
+ itotalsize = 0
+ if (strindex(Scollection, ",") > 0) then
+ index = 1
+ Stemp = Scollection
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ icollectionid = sounddb_getcollectionid(strsub(Stemp, 0, index))
+ itotalsize += ftlen(gixdb_collectionsfn[icollectionid])
+ Stemp strsub Stemp, index+1
+ else
+ icollectionid = sounddb_getcollectionid(Stemp)
+ itotalsize += ftlen(gixdb_collectionsfn[icollectionid])
+ endif
+ od
+
+ idata[] init itotalsize
+ iwriteindex = 0
+ index = 1
+ Stemp = Scollection
+ while (index > 0) do
+ index strindex Stemp, ","
+ if (index > 0) then
+ icollectionid = sounddb_getcollectionid(strsub(Stemp, 0, index))
+ ifn = gixdb_collectionsfn[icollectionid]
+ ireadindex = 0
+ while (ireadindex < ftlen(ifn)) do
+ idata[iwriteindex] table ireadindex, ifn
+ ireadindex += 1
+ iwriteindex += 1
+ od
+ Stemp strsub Stemp, index+1
+ else
+ icollectionid = sounddb_getcollectionid(Stemp)
+ ifn = gixdb_collectionsfn[icollectionid]
+ ireadindex = 0
+ while (ireadindex < ftlen(ifn)) do
+ idata[iwriteindex] table ireadindex, ifn
+ ireadindex += 1
+ iwriteindex += 1
+ od
+ endif
+ od
+
+ else
+ icollectionid = sounddb_getcollectionid(Stemp)
+ idata[] tab2array gixdb_collectionsfn[icollectionid]
+ igoto complete
+ endif
+
+complete:
+ xout idata, icollectionid
+endop
+
+
+
+
+/*
+ gixdb_pitchreference
+ has 127 * 4 entries. get start and end indexes for gixdb_pitchnotes by
+ index = (inote + (icollectionid * 127)) * 2
+
+ then values
+ iminindex table index, gixdb_pitchreference
+ imaxindex table index+1, gixdb_pitchreference
+
+ then get the actual files which are between min and max indexes eg
+
+ iselected = round(random(iminindex, imaxindex))
+ ifileid table iselected, gixdb_pitchnotes
+ ipitchadjust table iselected, gixdb_pitchadjust
+
+
+
+*/
+
+
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ icollectionid collection ID
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, ii
+ icollectionid, inote xin
+ irefindex = ((inote - $XDB_MINNOTE) + (icollectionid * 128)) * 2 ;, gixdb_pitchreference
+ iselected = round(random(tab_i(irefindex, gixdb_pitchreference), tab_i(irefindex+1, gixdb_pitchreference)))
+ ifileid tab_i iselected, gixdb_pitchnotes
+ ipitchratio tab_i iselected, gixdb_pitchadjust
+
+ xout ifileid, ipitchratio
+endop
+
+
+
+/*
+ Get the nearest note in a filecollection, return the file ID and the pitch ratio adjustment required to the requested note.
+
+ ifileid, ipitchratio sounddb_mel_nearestnote Scollection, inote
+
+ ifileid file ID, corresponding to index in gisounddb
+ ipitchratio pitch ratio adjustment required to make the file match the requested note
+ Scollection collection name
+ inote MIDI note number
+*/
+opcode sounddb_mel_nearestnote, ii, Si
+ Scollection, inote xin
+ icollectionid = sounddb_getcollectionid(Scollection)
+ ifileid, ipitchratio sounddb_mel_nearestnote icollectionid, inote
+ xout ifileid, ipitchratio
+endop
+
+
+
+#end
diff --git a/sonics/uniqueid.udo b/sonics/uniqueid.udo
new file mode 100755
index 0000000..84799ec
--- /dev/null
+++ b/sonics/uniqueid.udo
@@ -0,0 +1,78 @@
+#ifndef UDO_UNIQUEID
+#define UDO_UNIQUEID ##
+/*
+ Unique ID assignments
+ Slim excerpt for Partial Emergence
+
+ This file is part of the SONICS UDO collection by Richard Knight 2021, 2022
+ License: GPL-2.0-or-later
+ http://1bpm.net
+*/
+
+
+; globals for internal use
+giUniqueID = 0
+giUniqueFrac = 0
+
+
+/*
+ Get a unique integer ID
+
+ id uniqueid
+
+ id the ID
+*/
+opcode uniqueid, i, 0
+ id = giUniqueID
+ giUniqueID += 1
+ xout id
+endop
+
+
+/*
+ Get a unique decimal/fractional ID
+
+ id uniquefrac
+
+ id the ID
+*/
+opcode uniquefrac, i, 0
+ id = giUniqueFrac
+ giUniqueFrac += 0.0000001 ; smallest for 32bit
+ if (giUniqueFrac >= 1) then
+ giUniqueFrac = 0
+ endif
+ xout id
+endop
+
+
+/*
+ Get an array of unique fractional instrument numbers given a base instrument number
+
+ instrs[] uniqueinstrnums instrnum, inum
+ instrs[] uniqueinstrnums Sinstr, inum
+
+ instrs[] array of unique fractional numbers for the instrument number instrnum
+ Sinstr the base instrument name
+ instrnum the base instrument number
+ inum how many references to generate
+*/
+opcode uniqueinstrnums, i[], ii
+ instrnum, inum xin
+ instrs[] init inum
+ index = 0
+ while (index < inum) do
+ instrs[index] = instrnum + uniquefrac()
+ index += 1
+ od
+ xout instrs
+endop
+
+; overload for named instrument
+opcode uniqueinstrnums, i[], Si
+ Sinstr, inum xin
+ instrs[] uniqueinstrnums nstrnum(Sinstr), inum
+ xout instrs
+endop
+
+#endif
diff --git a/sonics/wavetables.udo b/sonics/wavetables.udo
new file mode 100755
index 0000000..e77d305
--- /dev/null
+++ b/sonics/wavetables.udo
@@ -0,0 +1,30 @@
+#ifndef UDO_WAVETABLES
+#define UDO_WAVETABLES ##
+
+/*
+ Standard regular wave function tables
+ 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
+*/
+
+ipoints = 16384
+gifnSine ftgen 0, 0, ipoints, 10, 1
+gifnSquare ftgen 0, 0, ipoints, 10, 1, 0 , .33, 0, .2 , 0, .14, 0 , .11, 0, .09
+gifnSaw ftgen 0, 0, ipoints, 10, 0, .2, 0, .4, 0, .6, 0, .8, 0, 1, 0, .8, 0, .6, 0, .4, 0, .2
+gifnPulse ftgen 0, 0, ipoints, 10, 1, 1, 1, 1, 0.7, 0.5, 0.3, 0.1
+gifnCosine ftgen 0, 0, ipoints, 9, 1, 1, 90
+gifnHalfSine ftgen 0, 0, 1024, 9, 0.5, 1, 0
+gifnSigmoid ftgen 0, 0, 257, 9, .5, 1, 270
+
+giwavetables[] fillarray gifnSine, gifnSquare, gifnSaw, gifnPulse, gifnCosine, gifnHalfSine, gifnSigmoid
+gSwavetables[] fillarray "Sine", "Square", "Saw", "Pulse", "Cosine", "Half sine", "Sigmoid"
+
+opcode wavetable_random, i, 0
+ xout giwavetables[int(random(0, lenarray(giwavetables)-1))]
+endop
+
+#end
+