import { Dough, doughsamples } from 'dough-synth';
import doughUrl from 'dough-synth?url';
5import { transpiler } from '@strudel/transpiler';
const doughBaseUrl = doughUrl.split('/').slice(0, -1).join('/') + '/';
7const doughBaseUrl = 'https://unpkg.com/dough-synth@0.1.9/';
9Object.assign(globalThis, { doughsamples }); 10 11export class DoughRepl { 12 pattern; 13 latency = 0.1; 14 cps = 0.5; 15 origin; 16 t0; 17 lasttime; 18 strudel; 19 q = []; 20 constructor() { 21 this.ready = this.init(); 22 } 23 async init() { 24 // init dough immediately, so that it can attach the document click event to initAudio immediately 25 this.dough = new Dough({ 26 base: doughBaseUrl, 27 //base: "../", // local dev 28 onTick: ({ t0, t1 }) => { 29 if (!this.pattern) { 30 return; 31 } 32 this.origin ??= t0; 33 this.t0 = t0; 34 this.lasttime = performance.now(); 35 const a = (t0 - this.origin) * this.cps; 36 const b = (t1 - this.origin) * this.cps; 37 38 const haps = this.pattern.queryArc(a, b).filter((hap) => hap.hasOnset()); 39 if (!haps.length) { 40 return; 41 } 42 haps.forEach((hap) => { 43 const time = hap.whole.begin.valueOf() / this.cps + this.origin + this.latency; 44 const duration = hap.duration.valueOf() / this.cps; 45 const event = { 46 dough: 'play', 47 ...hap.value, 48 time, 49 duration, 50 }; 51 if (event.note && typeof event.note === 'string') { 52 event.note = noteToMidi(event.note); 53 } 54 //console.log("event", JSON.stringify(Object.entries(event))); 55 this.dough.evaluate(event); 56 this.q.push({ event, hap }); 57 }); 58 }, 59 }); 60 // miniAllStrings(); 61 const setcps = (cps) => (this.cps = cps); 62 const setcpm = (cpm) => setcps(cpm / 60); 63 const replScope = { setcps, setcpm }; 64 Object.assign(globalThis, replScope); 65 } 66 async evaluate(code) { 67 await this.ready; 68 let patterns = []; 69 Pattern.prototype.p = function (id) { 70 if (!id.startsWith('_')) { 71 patterns.push(this); 72 } 73 }; 74 75 let { meta } = await evaluate(code, transpiler, { addReturn: false, wrapAsync: true, emitWidgets: false }); 76 const { miniLocations } = meta; 77 78 this.pattern = stack(...patterns); 79 return { miniLocations, pattern: this.pattern }; 80 } 81 stop() { 82 this.pattern = undefined; 83 this.origin = undefined; 84 } 85 prebake() { 86 return Promise.all([ 87 // doughsamples('github:eddyflux/crate') 88 ]); 89 } 90 // tbd: move this to dough-synth 91 get time() { 92 return this.t0 + (performance.now() - this.lasttime) / 1000 + this.latency; 93 } 94 processHaps() { 95 const currentHaps = []; 96 const time = this.time; 97 this.q = this.q.filter(({ event, hap }) => { 98 const end = event.time + event.duration; 99 const isActive = time >= event.time && time <= end; 100 if (isActive) { 101 currentHaps.push(hap); 102 } 103 return end > time; // delete old events 104 // we do NOT return !isActive, because a frame might miss an event, which would cause a leak 105 }); 106 // console.log(this.q.length); // to check for leaks 107 return currentHaps; 108 } 109}