diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 94f46f44a..a319c0f80 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -73,6 +73,8 @@ export function registerControl(names, ...aliases) { */ export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound'); +export const { rec } = registerControl(['rec', 'n']); + /** * Define a custom webaudio node to use as a sound source. * diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 10087fc61..c3bd39a21 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -3,8 +3,8 @@ import { getAudioContext, registerSound } from './index.mjs'; import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs'; import { logger } from './logger.mjs'; -const bufferCache = {}; // string: Promise -const loadCache = {}; // string: Promise +export const bufferCache = {}; // string: Promise +export const loadCache = {}; // string: Promise export const getCachedBuffer = (url) => bufferCache[url]; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index f0475d771..3d1e6675a 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -95,6 +95,7 @@ function loadWorklets() { return workletsLoading; } +let stream; // this function should be called on first user interaction (to avoid console warning) export async function initAudio(options = {}) { const { disableWorklets = false } = options; @@ -112,6 +113,7 @@ export async function initAudio(options = {}) { } catch (err) { console.warn('could not load AudioWorklet effects', err); } + stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }); logger('[superdough] ready'); } let audioReady; @@ -308,6 +310,48 @@ export function resetGlobalEffects() { analysersData = {}; } +/* async */ function record(name, begin, hapDuration) { + registerSound( + name, + () => { + console.log('trigger recording before its ready...', getAudioContext().currentTime); + }, + {}, + ); + const ac = getAudioContext(); + try { + const inputNode = ac.createMediaStreamSource(stream); + const samples = Math.round(hapDuration * ac.sampleRate); + const options = { samples, begin, end: begin + hapDuration }; + const recorder = getWorklet(ac, 'recording-processor', {}); + recorder.port.postMessage(options); + inputNode.connect(recorder); + /* return */ new Promise((resolve) => { + recorder.port.onmessage = async (e) => { + const audioBuffer = ac.createBuffer(1, samples, ac.sampleRate); + audioBuffer.getChannelData(0).set(e.data.buffer); + const url = `rec:${name}`; + bufferCache[url] = audioBuffer; + loadCache[url] = audioBuffer; + const value = [url]; + console.log('register recording', getAudioContext().currentTime); + registerSound(name, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { + type: 'sample', + samples: value, + baseUrl: undefined, + prebake: false, + tag: undefined, + }); + resolve(name); + }; + }); + return recorder; + } catch (err) { + console.log('err', err); + reject(err); + } +} + export const superdough = async (value, t, hapDuration) => { const ac = getAudioContext(); if (typeof value !== 'object') { @@ -330,6 +374,7 @@ export const superdough = async (value, t, hapDuration) => { // destructure let { s = getDefaultValue('s'), + rec, bank, source, gain = getDefaultValue('gain'), @@ -411,11 +456,19 @@ export const superdough = async (value, t, hapDuration) => { if (bank && s) { s = `${bank}_${s}`; } + if (rec && getSound(rec)) { + s = rec; + value.s = rec; + } // get source AudioNode let sourceNode; if (source) { sourceNode = source(t, value, hapDuration); + } else if (rec && !getSound(rec)) { + console.log('record', rec); + const recorder = record(rec, t, hapDuration); + sourceNode = recorder; } else if (getSound(s)) { const { onTrigger } = getSound(s); const soundHandle = await onTrigger(t, value, onended); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index b06513e79..a63fce06a 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -464,3 +464,47 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { } registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor); + +class RecordingProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.done = false; + this.head = 0; + this.nudge = 0.1; + this.port.onmessage = (e) => { + this.begin = e.data.begin + this.nudge; + + this.samples = e.data.samples; + this.buffer = new Float32Array(this.samples); + }; + } + process(inputs, outputs) { + // noop if scheduled recording begin hasn't been reached + if (currentTime < this.begin) { + return true; + } + if (!this.buffer) { + console.log('buffer not ready..'); + return true; + } + // stop when the buffer is full + if (!this.done && this.head >= this.samples) { + this.done = true; + this.port.postMessage({ buffer: this.buffer }); + return false; + } + + // so far only 1 channel + const input = inputs[0]; + // const output = outputs[0]; + for (let i = 0; i < input[0].length; i++) { + this.buffer[this.head] = input[0][i] * 0.25; + /* output[0][i] = input[0][i]; + output[1][i] = input[0][i]; */ + this.head++; + } + return true; + } +} + +registerProcessor('recording-processor', RecordingProcessor);