1// this is a poc of how a pattern can be rendered as a wav file using node 2// run via: node dough-export.mjs 3import fs from 'node:fs'; 4import WavEncoder from 'wav-encoder'; 5import { evalScope } from '@strudel/core'; 6import { miniAllStrings } from '@strudel/mini'; 7import { Dough } from './dough.mjs'; 8 9await evalScope( 10 import('@strudel/core'), 11 import('@strudel/mini'), 12 import('@strudel/tonal'), 13 // import('@strudel/tonal'), 14); 15 16miniAllStrings(); // allows using single quotes for mini notation / skip transpilation 17 18let sampleRate = 48000, 19 cps = 0.4; 20 21/* await doughsamples('github:eddyflux/crate'); 22await doughsamples('github:eddyflux/wax'); */ 23 24let pat = note('c,eb,g,<bb c4 d4 eb4>') 25 .s('sine') 26 .press() 27 .add(note(24)) 28 .fmi(3) 29 .fmh(5.01) 30 .dec(0.4) 31 .delay('.6:<.12 .22>:.8') 32 .jux(press) 33 .rarely(add(note('12'))) 34 .lpf(400) 35 .lpq(0.2) 36 .lpd(0.4) 37 .lpenv(3) 38 .fmdecay(0.4) 39 .fmenv(1) 40 .postgain(0.6) 41 .stack(s('<pink white>*8').dec(0.07).rarely(ply('2')).delay(0.5).hpf(sine.range(200, 2000).slow(4)).hpq(0.2)) 42 .stack( 43 s('[- white@3]*2') 44 .dec(0.4) 45 .hpf('<2000!3 <4000 8000>>*4') 46 .hpq(0.6) 47 .ply('<1 2>*4') 48 .postgain(0.5) 49 .delay(0.5) 50 .jux(rev) 51 .lpf(5000), 52 ) 53 .stack( 54 note('<c2 - [- f1] ->*2') 55 .s('square') 56 .lpf(sine.range(100, 300).slow(4)) 57 .lpe(1) 58 .segment(8) 59 .lpd(0.3) 60 .lpq(0.2) 61 .dec(0.2) 62 .speed('<1 2>') 63 .ply('<1 2>') 64 .postgain(1), 65 ) 66 .stack( 67 chord('<Cm Cm7 Cm9 Cm11 Fm Fm7 Fm9 Fm11>') 68 .voicing() 69 .s('<sine>') 70 .clip(1) 71 .rel(0.4) 72 .vib('4:.2') 73 .gain(0.7) 74 .hpf(1200) 75 .fm(0.5) 76 .att(1) 77 .lpa(0.5) 78 .lpf(200) 79 .lpenv(4) 80 .chorus(0.8), 81 ) 82 .slow(1 / cps); 83 84let cycles = 30; 85let seconds = cycles + 1; // 1s release tail 86const haps = pat.queryArc(0, cycles); 87 88const dough = new Dough(sampleRate); 89 90console.log('spawn voices...'); 91haps.forEach((hap) => { 92 hap.value._begin = Number(hap.whole.begin); 93 hap.value._duration = hap.duration /* / cps */; 94 dough.scheduleSpawn(hap.value); 95}); 96console.log(`render ${seconds}s long buffer, each dot is 1 second:`); 97const buffers = [new Float32Array(seconds * sampleRate), new Float32Array(seconds * sampleRate)]; 98let t = performance.now(); 99while (dough.t <= buffers[0].length) { 100 dough.update(); 101 buffers[0][dough.t] = dough.out[0]; 102 buffers[1][dough.t] = dough.out[1]; 103 if (dough.t % sampleRate === 0) { 104 process.stdout.write('.'); 105 } 106} 107const took = (performance.now() - t) / 1000; 108const load = (took / seconds) * 100; 109const speed = (seconds / took).toFixed(2); 110console.log(''); 111console.log(`done! 112rendered ${seconds}s in ${took.toFixed(2)}s 113speed: ${speed}x 114load: ${load.toFixed(2)}%`); 115 116const patternAudio = { 117 sampleRate, 118 channelData: buffers, 119}; 120 121WavEncoder.encode(patternAudio).then((buffer) => { 122 fs.writeFileSync('pattern.wav', new Float32Array(buffer)); 123});