mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 21:58:37 +00:00
Merge pull request #275 from tidalcycles/csound
add basic csound output
This commit is contained in:
commit
01eca562c5
7215
package-lock.json
generated
7215
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
119
packages/csound/csound.mjs
Normal file
119
packages/csound/csound.mjs
Normal file
@ -0,0 +1,119 @@
|
||||
import { getFrequency, logger, Pattern } from '@strudel.cycles/core';
|
||||
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import csd from './project.csd?raw';
|
||||
// import livecodeOrc from './livecode.orc?raw';
|
||||
import presetsOrc from './presets.orc?raw';
|
||||
|
||||
let csoundLoader, _csound;
|
||||
|
||||
// triggers given instrument name using csound.
|
||||
Pattern.prototype._csound = function (instrument) {
|
||||
instrument = instrument || 'triangle';
|
||||
init(); // not async to support csound inside other patterns + to be able to call pattern methods after it
|
||||
// TODO: find a alternative way to wait for csound to load (to wait with first time playback)
|
||||
return this.onTrigger((time, hap) => {
|
||||
if (!_csound) {
|
||||
logger('[csound] not loaded yet', 'warning');
|
||||
return;
|
||||
}
|
||||
if (typeof hap.value !== 'object') {
|
||||
throw new Error('csound only support objects as hap values');
|
||||
}
|
||||
let { gain = 0.8 } = hap.value;
|
||||
gain *= 0.2;
|
||||
|
||||
const freq = Math.round(getFrequency(hap));
|
||||
const controls = Object.entries({ ...hap.value, freq })
|
||||
.flat()
|
||||
.join('/');
|
||||
// TODO: find out how to send a precise ctx based time
|
||||
// http://www.csounds.com/manual/html/i.html
|
||||
const params = [
|
||||
`"${instrument}"`, // p1: instrument name
|
||||
time - getAudioContext().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
|
||||
gain, // p5: gain
|
||||
`"${controls}"`, // p6 controls as string (like superdirt osc message)
|
||||
];
|
||||
const msg = `i ${params.join(' ')}`;
|
||||
_csound.inputMessage(msg);
|
||||
});
|
||||
};
|
||||
|
||||
// initializes csound + can be used to reevaluate given instrument code
|
||||
export async function csound(code = '') {
|
||||
await init();
|
||||
if (code) {
|
||||
code = `${code}`;
|
||||
// ^ ^
|
||||
// wrapping in backticks makes sure it works when calling as templated function
|
||||
await _csound?.evalCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
Pattern.prototype.define('csound', (a, pat) => pat.csound(a), { composable: false, patternified: true });
|
||||
|
||||
function eventLogger(type, args) {
|
||||
const [msg] = args;
|
||||
if (
|
||||
type === 'message' &&
|
||||
(['[commit: HEAD]'].includes(msg) ||
|
||||
msg.startsWith('--Csound version') ||
|
||||
msg.startsWith('libsndfile') ||
|
||||
msg.startsWith('sr =') ||
|
||||
msg.startsWith('0dBFS') ||
|
||||
msg.startsWith('audio buffered') ||
|
||||
msg.startsWith('writing') ||
|
||||
msg.startsWith('SECTION 1:'))
|
||||
) {
|
||||
// ignore
|
||||
return;
|
||||
}
|
||||
let logType = 'info';
|
||||
if (msg.startsWith('error:')) {
|
||||
logType = 'error';
|
||||
}
|
||||
logger(`[csound] ${msg || ''}`, logType);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (window.__csound__) {
|
||||
// allows using some other csound instance
|
||||
_csound = window.__csound__;
|
||||
} else {
|
||||
const { Csound } = await import('@csound/browser');
|
||||
_csound = await Csound({ audioContext: getAudioContext() });
|
||||
}
|
||||
_csound.removeAllListeners('message');
|
||||
['message'].forEach((k) => _csound.on(k, (...args) => eventLogger(k, args)));
|
||||
await _csound.setOption('-m0d'); // see -m flag https://csound.com/docs/manual/CommandFlags.html
|
||||
await _csound.setOption('--sample-accurate');
|
||||
await _csound.compileCsdText(csd);
|
||||
// await _csound.compileOrc(livecodeOrc);
|
||||
await _csound.compileOrc(presetsOrc);
|
||||
await _csound.start();
|
||||
return _csound;
|
||||
}
|
||||
|
||||
async function init() {
|
||||
csoundLoader = csoundLoader || load();
|
||||
return csoundLoader;
|
||||
}
|
||||
|
||||
let orcCache = {};
|
||||
export async function loadOrc(url) {
|
||||
await init();
|
||||
if (typeof url !== 'string') {
|
||||
throw new Error('loadOrc: expected url string');
|
||||
}
|
||||
if (url.startsWith('github:')) {
|
||||
const [_, path] = url.split('github:');
|
||||
url = `https://raw.githubusercontent.com/${path}`;
|
||||
}
|
||||
if (!orcCache[url]) {
|
||||
orcCache[url] = await fetch(url).then((res) => res.text());
|
||||
}
|
||||
await _csound.compileOrc(orcCache[url]);
|
||||
}
|
||||
2226
packages/csound/livecode.orc
Normal file
2226
packages/csound/livecode.orc
Normal file
File diff suppressed because it is too large
Load Diff
32
packages/csound/package.json
Normal file
32
packages/csound/package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@strudel.cycles/csound",
|
||||
"version": "0.3.0",
|
||||
"description": "csound bindings for strudel",
|
||||
"main": "csound.mjs",
|
||||
"scripts": {
|
||||
"test": "echo \"No tests present.\" && exit 0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tidalcycles",
|
||||
"strudel",
|
||||
"pattern",
|
||||
"livecoding",
|
||||
"algorave"
|
||||
],
|
||||
"author": "Felix Roos <flix91@gmail.com>",
|
||||
"contributors": [
|
||||
"Alex McLean <alex@slab.org>"
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@csound/browser": "^6.18.3"
|
||||
}
|
||||
}
|
||||
96
packages/csound/presets.orc
Normal file
96
packages/csound/presets.orc
Normal file
@ -0,0 +1,96 @@
|
||||
; returns value of given key in given "string map"
|
||||
; keymap("freq", "note/c3/freq/220/gain/0.5")
|
||||
; yields "220"
|
||||
opcode keymap, S, SS
|
||||
Skey, Smap xin
|
||||
idelimiter = strindex(Smap, strcat(Skey, "/"))
|
||||
ifrom = idelimiter + strlen(Skey) + 1
|
||||
Svalue = strsub(Smap, ifrom, strlen(Smap))
|
||||
Svalue = strsub(Svalue, 0, strindex(Svalue, "/"))
|
||||
xout Svalue
|
||||
endop
|
||||
|
||||
; TODO add incredibly dope synths
|
||||
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
|
||||
|
||||
instr pad
|
||||
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
|
||||
|
||||
|
||||
gisine ftgen 0, 0, 4096, 10, 1
|
||||
|
||||
instr bow
|
||||
kpres = 2
|
||||
krat = 0.16
|
||||
kvibf = 6.12723
|
||||
|
||||
kvib linseg 0, 0.5, 0, 1, 1, p3-0.5, 1
|
||||
kvamp = kvib * 0.01
|
||||
asig wgbow .7, p4, kpres, krat, kvibf, kvamp, gisine
|
||||
asig = asig*p5
|
||||
outs asig, asig
|
||||
endin
|
||||
|
||||
|
||||
instr Meta
|
||||
Smap = strget(p6)
|
||||
Sinstrument = keymap("s", Smap)
|
||||
schedule(Sinstrument, 0, p3, p4, p5)
|
||||
; TODO find a way to pipe Sinstrument through effects
|
||||
endin
|
||||
10
packages/csound/project.csd
Normal file
10
packages/csound/project.csd
Normal file
@ -0,0 +1,10 @@
|
||||
<CsoundSynthesizer>
|
||||
<CsInstruments>
|
||||
|
||||
sr=48000
|
||||
ksmps=64
|
||||
nchnls=2
|
||||
0dbfs=1
|
||||
|
||||
</CsInstruments>
|
||||
</CsoundSynthesizer>
|
||||
@ -42,6 +42,7 @@ const modules = [
|
||||
import('@strudel.cycles/osc'),
|
||||
import('@strudel.cycles/serial'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
import('@strudel.cycles/csound'),
|
||||
];
|
||||
|
||||
evalScope(
|
||||
|
||||
@ -88,6 +88,9 @@ const toneHelpersMocked = {
|
||||
strudel.Pattern.prototype.osc = function () {
|
||||
return this;
|
||||
};
|
||||
strudel.Pattern.prototype.csound = function () {
|
||||
return this;
|
||||
};
|
||||
strudel.Pattern.prototype.tone = function () {
|
||||
return this;
|
||||
};
|
||||
@ -170,6 +173,8 @@ evalScope(
|
||||
{
|
||||
// gist,
|
||||
// euclid,
|
||||
csound: id,
|
||||
loadOrc: id,
|
||||
mini,
|
||||
getDrawContext,
|
||||
getAudioContext,
|
||||
|
||||
@ -1379,6 +1379,20 @@ exports[`renders tunes > tune: chop 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders tunes > tune: csoundDemo 1`] = `
|
||||
[
|
||||
"0/1 -> 1/1: {\\"note\\":\\"D3\\"}",
|
||||
"-1/4 -> 1/4: {\\"note\\":\\"Bb3\\"}",
|
||||
"1/4 -> 5/4: {\\"note\\":\\"F3\\"}",
|
||||
"0/1 -> 1/2: {\\"note\\":\\"F4\\"}",
|
||||
"1/2 -> 3/2: {\\"note\\":\\"C4\\"}",
|
||||
"-1/4 -> 1/4: {\\"note\\":\\"A4\\"}",
|
||||
"1/4 -> 3/4: {\\"note\\":\\"A4\\"}",
|
||||
"1/4 -> 3/4: {\\"note\\":\\"A4\\"}",
|
||||
"3/4 -> 7/4: {\\"note\\":\\"E4\\"}",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders tunes > tune: delay 1`] = `
|
||||
[
|
||||
"0/1 -> 1/2: {\\"s\\":\\"bd\\",\\"delay\\":0,\\"delaytime\\":0.16,\\"delayfeedback\\":0.8,\\"speed\\":-1}",
|
||||
@ -8054,6 +8068,36 @@ exports[`renders tunes > tune: juxUndTollerei 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders tunes > tune: loungeSponge 1`] = `
|
||||
[
|
||||
"0/1 -> 3/8: {\\"note\\":\\"B3\\",\\"cutoff\\":1396}",
|
||||
"0/1 -> 3/8: {\\"note\\":\\"D4\\",\\"cutoff\\":1396}",
|
||||
"0/1 -> 3/8: {\\"note\\":\\"E4\\",\\"cutoff\\":1396}",
|
||||
"0/1 -> 3/8: {\\"note\\":\\"G4\\",\\"cutoff\\":1396}",
|
||||
"3/8 -> 3/4: {\\"note\\":\\"B3\\",\\"cutoff\\":1396}",
|
||||
"3/8 -> 3/4: {\\"note\\":\\"D4\\",\\"cutoff\\":1396}",
|
||||
"3/8 -> 3/4: {\\"note\\":\\"E4\\",\\"cutoff\\":1396}",
|
||||
"3/8 -> 3/4: {\\"note\\":\\"G4\\",\\"cutoff\\":1396}",
|
||||
"3/4 -> 1/1: {\\"note\\":\\"B3\\",\\"cutoff\\":1396}",
|
||||
"3/4 -> 1/1: {\\"note\\":\\"D4\\",\\"cutoff\\":1396}",
|
||||
"3/4 -> 1/1: {\\"note\\":\\"E4\\",\\"cutoff\\":1396}",
|
||||
"3/4 -> 1/1: {\\"note\\":\\"G4\\",\\"cutoff\\":1396}",
|
||||
"0/1 -> 1/4: {\\"note\\":\\"C2\\",\\"gain\\":1}",
|
||||
"1/4 -> 1/2: {\\"note\\":\\"C2\\",\\"gain\\":4}",
|
||||
"1/2 -> 3/4: {\\"note\\":\\"C2\\",\\"gain\\":1}",
|
||||
"3/4 -> 1/1: {\\"note\\":\\"C2\\",\\"gain\\":4}",
|
||||
"0/1 -> 3/16: {\\"note\\":\\"A4\\"}",
|
||||
"3/4 -> 15/16: {\\"note\\":\\"A5\\"}",
|
||||
"-1/4 -> -1/16: {\\"note\\":\\"E5\\"}",
|
||||
"1/2 -> 11/16: {\\"note\\":\\"C5\\"}",
|
||||
"0/1 -> 1/2: {\\"s\\":\\"bd\\",\\"bank\\":\\"RolandTR909\\"}",
|
||||
"1/2 -> 1/1: {\\"s\\":\\"bd\\",\\"bank\\":\\"RolandTR909\\"}",
|
||||
"1/4 -> 1/2: {\\"s\\":\\"hh\\",\\"bank\\":\\"RolandTR909\\"}",
|
||||
"3/4 -> 1/1: {\\"s\\":\\"hh\\",\\"bank\\":\\"RolandTR909\\"}",
|
||||
"1/2 -> 1/1: {\\"s\\":\\"cp\\",\\"bank\\":\\"RolandTR909\\"}",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders tunes > tune: meltingsubmarine 1`] = `
|
||||
[
|
||||
"0/1 -> 3/2: {\\"s\\":\\"bd\\",\\"speed\\":0.7519542165100574}",
|
||||
|
||||
@ -764,3 +764,57 @@ note("c3 eb3 g3 bb3").palindrome()
|
||||
.room(.6)
|
||||
.delay(.5).delaytime(.1).delayfeedback(.4)
|
||||
.pianoroll()`;
|
||||
|
||||
export const csoundDemo = `// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
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('CoolSynth')`;
|
||||
|
||||
export const loungeSponge = `
|
||||
// licensed with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
// by Felix Roos
|
||||
// livecode.orc by Steven Yi
|
||||
await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc')
|
||||
|
||||
stack(
|
||||
note("<C^7 A7 Dm7 Fm7>/2".voicings())
|
||||
.cutoff(sine.range(500,2000).round().slow(16))
|
||||
.euclidLegato(3,8).csound('FM1')
|
||||
,
|
||||
note("<C2 A1 D2 F2>/2").ply(8).csound('Bass').gain("1 4 1 4")
|
||||
,
|
||||
note("0 7 [4 3] 2".fast(2/3).off(".25 .125",add("<2 4 -3 -1>"))
|
||||
.slow(2).scale('A4 minor'))
|
||||
.legato(.25).csound('SynHarp')
|
||||
,
|
||||
s("bd*2,[~ hh]*2,~ cp").bank('RolandTR909')
|
||||
)`;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user