jevstrudel.git / packages / dough / dough.mjs
1/*
2dough.mjs
3Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/osc/osc.mjs>
4This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program.  If not, see <https://www.gnu.org/licenses/>.
5*/
6
7import { register, noteToMidi } from '@strudel/core';
8import { Dough, doughsamples } from 'dough-synth';
9import { getAudioContext, ensureMinimalOutput } from '@strudel/webaudio';
10import wasm from 'dough-synth/dough.wasm?url';
11import workletCode from 'dough-synth/dough.js?raw';
12
13Object.assign(globalThis, { doughsamples });
14
15let D;
16/**
17 *
18 * initializes dough ahead of time. you don't need to use this, but if you do, you can await it, so dough is ready before the pattern starts.
19 *
20 * @name initDough
21 * @tags external_io
22 * @memberof Pattern
23 * @example
24 * await initDough()
25 * $: chord("<Dm9 Dm11 Dm7>").offset("<-1 0 1 0>").voicing()
26 * .phaser(3).fm(1.4).fmh(1.01)
27 * .dough()
28 */
29export function initDough() {
30  if (!D || D.audioContext !== getAudioContext()) {
31    D = new Dough({
32      wasm,
33      workletCode,
34      audioContext: getAudioContext(),
35    });
36  }
37  return D.ready;
38}
39
40export async function doughTrigger(hap, _currentTime, cps = 1, targetTime) {
41  const offset = D.context_offset?.[0];
42  if (!offset) {
43    return; // not ready
44  }
45  hap.ensureObjectValue();
46  const event = {
47    dough: 'play',
48    ...hap.value,
49    time: targetTime - offset,
50    duration: hap.duration / cps,
51  };
52  if (typeof event.note === 'string') {
53    event.note = noteToMidi(event.note);
54  }
55  D.evaluate(event);
56}
57
58/**
59 *
60 * Uses dough as the audio engine. more info at https://dough.strudel.cc
61 *
62 * @name dough
63 * @tags external_io
64 * @memberof Pattern
65 * @returns Pattern
66 * @example
67 * $: chord("<Dm9 Dm11 Dm7>").offset("<-1 0 1 0>").voicing()
68 * .phaser(3).fm(1.4).fmh(1.01)
69 * .dough()
70 */
71export const dough = register('dough', (pat) => {
72  initDough();
73  ensureMinimalOutput();
74  return pat.onTrigger(doughTrigger);
75});