jevstrudel.git / packages / supradough / dough-export.mjs

this is a poc of how a pattern can be rendered as a wav file using node 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';
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;

await doughsamples('github:eddyflux/crate'); await doughsamples('github:eddyflux/wax');

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);
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});