diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 383c2ffa7..94f034480 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -445,6 +445,20 @@ export const { coarse } = registerControl('coarse'); */ export const { drive } = registerControl('drive'); +/** + * Allows you to set the output channels on the interface + * + * @name byteBeatExpression + * @synonyms bbexpr + * + * @param {number | Pattern} byteBeatExpression pattern the output channels + * @example + * note("e a d b g").channels("3:4") + * + */ +export const { byteBeatExpression, bbexpr } = registerControl('byteBeatExpression', 'bbexpr'); + + /** * Allows you to set the output channels on the interface * diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 55b72d165..0afbb3d67 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -137,6 +137,7 @@ const defaultDefaultValues = { shapevol: 1, distortvol: 1, delay: 0, + byteBeatExpression: '0', delayfeedback: 0.5, delaytime: 0.25, orbit: 1, @@ -475,6 +476,7 @@ export const superdough = async (value, t, hapDuration) => { let { s = getDefaultValue('s'), bank, + byteBeatExpression, source, gain = getDefaultValue('gain'), postgain = getDefaultValue('postgain'), diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 9a1e4d150..63c5a0b37 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -144,6 +144,63 @@ export function registerSynthSounds() { { prebake: true, type: 'synth' }, ); + + registerSound( + 'bytebeat', + (begin, value, onended) => { + const ac = getAudioContext(); + let { byteBeatExpression } = value; + + let { duration} = value; + const [attack, decay, sustain, release] = getADSRValues( + [value.attack, value.decay, value.sustain, value.release], + 'linear', + [0.001, 0.05, 0.6, 0.01], + ); + const holdend = begin + duration; + const end = holdend + release + 0.01; + let o = getWorklet( + ac, + 'byte-beat-processor', + { + + begin, + end, + }, + { + outputChannelCount: [2], + }, + ); + + + o.port.postMessage(byteBeatExpression); + + let envGain = gainNode(1); + envGain = o.connect(envGain); + + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); + + let timeoutNode = webAudioTimeout( + ac, + () => { + destroyAudioWorkletNode(o); + envGain.disconnect(); + onended(); + }, + begin, + end, + ); + + return { + node: envGain, + stop: (time) => { + timeoutNode.stop(time); + }, + }; + }, + { prebake: true, type: 'synth' }, + ); + registerSound( 'pulse', (begin, value, onended) => { diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 66148ecbc..bd52e3233 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -767,7 +767,10 @@ registerProcessor('pulse-oscillator', PulseOscillatorProcessor); class ByteBeatProcessor extends AudioWorkletProcessor { constructor() { super(); - this.pi = _PI; + this.bb = '0' + this.port.onmessage = (event) => { + this.bb = event.data; + }; } static get parameterDescriptors() { @@ -798,13 +801,20 @@ class ByteBeatProcessor extends AudioWorkletProcessor { if (currentTime >= params.end[0]) { return false; } + const bb = this.bb + + const f = Function('t', 'return ' + bb) + const output = outputs[0]; for (let i = 0; i < (output[0].length ?? 0); i++) { + let t = currentTime + t *= sampleRate + const signal = ((f(t)&255)/127.5 - 1)/4 for (let o = 0; o < output.length; o++) { // Combination of both oscillators with envelope applied - output[o][i] = 0.15 * (out0 - out1) * this.envf; + output[o][i] = signal } } @@ -812,4 +822,4 @@ class ByteBeatProcessor extends AudioWorkletProcessor { } } -registerProcessor('byte-beat-processor', BeatBeatProcessor); +registerProcessor('byte-beat-processor', ByteBeatProcessor);