From c40078ffc9fadbc53fbf7298dda754db963c66ea Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 29 Nov 2022 21:59:22 +0100 Subject: [PATCH] improve csound bindings: - better timing - can now pass instrument to .csound(...) - can now write instruments with csound(...) --- packages/csound/csd.mjs | 60 ++++++++++++++++++++ packages/csound/csound.mjs | 89 ++++++++++++++++++++--------- packages/csound/sounds.csd | 41 -------------- repl/src/tunes.mjs | 112 +++++++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 66 deletions(-) create mode 100644 packages/csound/csd.mjs delete mode 100644 packages/csound/sounds.csd diff --git a/packages/csound/csd.mjs b/packages/csound/csd.mjs new file mode 100644 index 00000000..67adbb3e --- /dev/null +++ b/packages/csound/csd.mjs @@ -0,0 +1,60 @@ +export function csd(code = '') { + return ` + + +-o dac --port=10000 --sample-accurate + + +sr=48000 +ksmps=64 +nchnls=2 +0dbfs=1 + +instr organ + iduration = p3 + ifreq = p4 + igain = p5 + ioct = octcps(ifreq) + + asig = vco2(igain, ifreq, 12, .5) ; my edit + kpwm = oscili(.1, 5) + asig = vco2(igain, ifreq, 4, .5 + kpwm) + asig += vco2(igain/4, ifreq * 2) + + ; filter + ; idepth = 2 + ; acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope + ; asig = zdf_2pole(asig, cpsoct(ioct + acut), 0.5) + + ; amp envelope + iattack = .001 + irelease = .05 + asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0) + + out(asig, asig) + +endin + +instr triangle + iduration = p3 + ifreq = p4 + igain = p5 + ioct = octcps(ifreq) + + asig = vco2(igain, ifreq, 12, .5) + + ; amp envelope + iattack = .001 + irelease = .05 + asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0) + + out(asig, asig) + +endin + +${code} + + + +`; +} diff --git a/packages/csound/csound.mjs b/packages/csound/csound.mjs index 297e5b51..80b0fddf 100644 --- a/packages/csound/csound.mjs +++ b/packages/csound/csound.mjs @@ -1,33 +1,72 @@ -import { Pattern } from '@strudel.cycles/core'; +import { getFrequency, logger, Pattern } from '@strudel.cycles/core'; import { Csound } from '@csound/browser'; // TODO: use dynamic import for code splitting.. -import csd from './sounds.csd?raw'; +import { csd } from './csd.mjs'; import { getAudioContext } from '@strudel.cycles/webaudio'; -let csoundLoader; +let csoundLoader, _csound; -Pattern.prototype.csound = async function () { - if (!csoundLoader) { - csoundLoader = (async () => { - const csound = await Csound({ audioContext: getAudioContext() }); - await csound.setOption('-m0'); - await csound.compileCsdText(csd); - await csound.setControlChannel('main.note.amp', -12); - await csound.start(); - return csound; - })(); - } - const csound = await csoundLoader; +// triggers given instrument name using csound. expects csound function to be called in advance `await csound()` +Pattern.prototype._csound = function (instrument) { + instrument = instrument || 'triangle'; return this.onTrigger((time, hap, currentTime) => { - const { gain = 0.8 } = hap.value; - const deadline = time - currentTime; - const midi = toMidi(getPlayableNoteValue(hap)); + if (!_csound) { + logger('[csound] not loaded yet', 'warning'); + return; + } + let { gain = 0.8 } = hap.value; + gain *= 0.2; + + // const midi = toMidi(getPlayableNoteValue(hap)); + const freq = getFrequency(hap); // TODO: find out how to send a precise ctx based time - // const ctime = `next_time(0.0001)+${deadline.toFixed(4)}`; - const ctime = `${deadline.toFixed(8)}`; - const cmidi = `cpsmidinn(${midi})`; - const cgain = gain ? `ampdbfs(-32 + 32*${gain})` : `0`; - const code = `schedule(1, ${ctime}, .125, ${cmidi}, ${cgain})`; - // console.log('code', code); - csound.evalCode(code); + // http://www.csounds.com/manual/html/i.html + const params = [ + `"${instrument}"`, // p1: instrument name + time - currentTime, //.toFixed(precision), // p2: starting time in arbitrary unit called beats + hap.duration + 0, // p3: duration in beats + // instrument specific params: + freq, //.toFixed(precision), // p4: frequency + // -48 + gain * 24, // p5: gain + gain, // p5: gain + ]; + const msg = `i ${params.join(' ')}`; + // console.log('msg', msg); + _csound.inputMessage(msg); + // _csound.readScore(msg); // slower alternative + // even slower alternative: + /* const code = `schedule(${params.join(', ')})`; + _csound.evalCode(code); */ }); }; + +// initializes csound + can be used to reevaluate given instrument code +export async function csound(code = '') { + code = csd(code); + let isInit = false; + if (!csoundLoader) { + isInit = true; + csoundLoader = (async () => { + _csound = await Csound({ audioContext: getAudioContext() }); + await _csound.setOption('-m0'); + await _csound.compileCsdText(code); + await _csound.start(); + })(); + } + await csoundLoader; + !isInit && (await _csound?.compileCsdText(code)); +} + +// experimental: allows using jsx to eval csound +window.jsxPragma = function (fn, args, text) { + return fn(text); +}; + +// experimental: for use via JSX as ... +export function CsInstruments(text) { + if (Array.isArray(text)) { + text = text[0]; + } + return csound(text); +} + +Pattern.prototype.define('csound', (a, pat) => pat.csound(a), { composable: false, patternified: true }); diff --git a/packages/csound/sounds.csd b/packages/csound/sounds.csd deleted file mode 100644 index 8cbd4d52..00000000 --- a/packages/csound/sounds.csd +++ /dev/null @@ -1,41 +0,0 @@ -;; based on https://kunstmusik.github.io/icsc2022-csound-web/tutorial2-interacting-with-csound/ - - --o dac --port=10000 - - -sr=48000 -ksmps=64 -nchnls=2 -0dbfs=1 - -instr 1 - ioct = octcps(p4) - kpwm = oscili(.1, 5) - asig = vco2(p5, p4, 4, .5 + kpwm) - asig += vco2(p5, p4 * 2) - - idepth = 3 - acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) - asig = zdf_2pole(asig, cpsoct(ioct + acut), 0.125) - - asig *= linsegr:a(1, p3, 1, .125, 0) - - out(asig, asig) - -endin - -opcode next_time, i, i - inext xin - - itime = times:i() - iticks = round(itime / inext) - iticks += 1 - - iout = (iticks * inext) - itime - xout iout -endop - - - - \ No newline at end of file diff --git a/repl/src/tunes.mjs b/repl/src/tunes.mjs index c9224d7a..c65224e8 100644 --- a/repl/src/tunes.mjs +++ b/repl/src/tunes.mjs @@ -991,3 +991,115 @@ export const juxUndTollerei = `note("c3 eb3 g3 bb3").palindrome() .room(.6) .delay(.5).delaytime(.1).delayfeedback(.4) .pianoroll()`; + +export const csoundTest = `await csound\` +instr sawtooth + iduration = p3 + ifreq = p4 + igain = p5 + ioct = octcps(ifreq) + + asig = vco2(igain, ifreq, 0) + + ; amp envelope + iattack = .5 + irelease = .1 + asig *= linsegr:a(0, iattack, 1, iduration, 1, irelease, 0) + + idepth = 2 + acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) + asig = zdf_2pole(asig, 1000, 2) + + out(asig, asig) +endin\` + +stack( + note("/2".voicings()).s('sawtooth'), + note("/2").s('sawtooth') +) +.csound('sawtooth')`; + +export const csoundTest2 = `await csound\` +instr organ + iduration = p3 + ifreq = p4 + igain = p5 + ioct = octcps(ifreq) + + kpwm = oscili(.5, 2) + asig = vco2(igain, ifreq, 4, .5 + kpwm) + asig += vco2(igain/4, ifreq * 2) + + iattack = .01 + irelease = .005 + asig *= linsegr:a(0, iattack, 1, iduration, 0, irelease, 0) + + out(asig, asig) +endin\` + +"<0 2 [4 6](3,4,1) 3*2>" +.off(1/4, add(2)) +.off(1/2, add(6)) +.scale('D minor') +.legato(perlin.range(.2,2).slow(8)) +// .echo(4, 1/8, .5) +.note() +.pianoroll() +.csound('organ');`; + +export const csoundTest3 = `await csound\` +instr CoolSynth + iduration = p3 + ifreq = p4 + igain = p5 + ioct = octcps(ifreq) + + kpwm = oscili(.05, 8) + asig = vco2(igain, ifreq, 4, .5 + kpwm) + asig += vco2(igain, ifreq * 2) + + idepth = 2 + acut = transegr:a(0, .005, 0, idepth, .06, -4.2, 0.001, .01, -4.2, 0) ; filter envelope + asig = zdf_2pole(asig, cpsoct(ioct + acut + 2), 0.5) + + iattack = .01 + isustain = .5 + idecay = .1 + irelease = .1 + asig *= linsegr:a(0, iattack, 1, idecay, isustain, iduration, isustain, irelease, 0) + + out(asig, asig) +endin\` + +"<0 2 [4 6](3,4,1) 3*2>" +.off(1/4, add(2)) +.off(1/2, add(6)) +.scale('D minor') +.note() +//.pianoroll() +.csound("/4")`; + +export const csoundTest4 = `await csound() + +stack( + note("/2".voicings()).csound('organ').gain(.5), + note("/2".superimpose(add(.1))).s('sawtooth').cutoff(800).resonance(10).shape(.3) +)`; + +export const csoundMixed = `await csound() + +stack( + "/2".voicings() + .add(rand.range(-.1,.1)).note() + .csound('organ').gain(1).struct("[~@2 x]*2").legato(.25) + , + "/2" + .superimpose(add(rand.range(-.1,.1))).note() + .s('sawtooth').cutoff(perlin.range(200,500)).resonance(10) + .struct("x(4,6,1) x(5,6,2)") + .decay(.1).sustain(0) + , + s("bd*2,hh:1(4,6),[~ sd]/2") + .room(.5) + .speed(perlin.range(.9,1.1).slow(4)) +).slow(2)`;