diff --git a/js/packages/offline-renderer/elementary-wasm.wasm b/js/packages/offline-renderer/elementary-wasm.wasm index 627312f..12c40e0 100755 Binary files a/js/packages/offline-renderer/elementary-wasm.wasm and b/js/packages/offline-renderer/elementary-wasm.wasm differ diff --git a/js/packages/offline-renderer/index.ts b/js/packages/offline-renderer/index.ts index 9f77202..a1fdfb5 100644 --- a/js/packages/offline-renderer/index.ts +++ b/js/packages/offline-renderer/index.ts @@ -212,4 +212,16 @@ export default class OfflineRenderer extends EventEmitter { setCurrentTimeMs(t) { this._native.setCurrentTimeMs(t); } + + setBeatTime(t: number) { + this._native.setBeatTime(t); + } + + setBpm(bpm: number) { + this._native.setBpm(bpm); + } + + setTimeSignature(numerator: number, denominator: number) { + this._native.setTimeSignature(numerator, denominator); + } } diff --git a/js/packages/web-renderer/index.ts b/js/packages/web-renderer/index.ts index c218322..bfea8d4 100644 --- a/js/packages/web-renderer/index.ts +++ b/js/packages/web-renderer/index.ts @@ -223,6 +223,25 @@ export default class WebRenderer extends EventEmitter { }); } + async setBeatTime(t: number) { + return await this._sendWorkletRequest("setBeatTime", { + time: t, + }); + } + + async setBpm(bpm: number) { + return await this._sendWorkletRequest("setBpm", { + bpm: bpm, + }); + } + + async setTimeSignature(numerator: number, denominator: number) { + return await this._sendWorkletRequest("setTimeSignature", { + numerator: numerator, + denominator: denominator, + }); + } + async pushMidiEvent(time, value) { return await this._sendWorkletRequest("pushMidiEvent", { time, diff --git a/js/packages/web-renderer/index.worklet.js b/js/packages/web-renderer/index.worklet.js index 2ab7d75..a350ee5 100644 --- a/js/packages/web-renderer/index.worklet.js +++ b/js/packages/web-renderer/index.worklet.js @@ -199,6 +199,33 @@ class ElementaryAudioWorkletProcessor extends AudioWorkletProcessor { result: this._native.setCurrentTimeMs(payload.time), }, ]); + case "setBeatTime": + return this.port.postMessage([ + "reply", + { + requestId, + result: this._native.setBeatTime(payload.time), + }, + ]); + case "setBpm": + return this.port.postMessage([ + "reply", + { + requestId, + result: this._native.setBpm(payload.bpm), + }, + ]); + case "setTimeSignature": + return this.port.postMessage([ + "reply", + { + requestId, + result: this._native.setTimeSignature( + payload.numerator, + payload.denominator, + ), + }, + ]); case "pushMidiEvent": let packedValue = 0 | 0; diff --git a/js/packages/web-renderer/raw/elementary-wasm.wasm b/js/packages/web-renderer/raw/elementary-wasm.wasm index 627312f..12c40e0 100755 Binary files a/js/packages/web-renderer/raw/elementary-wasm.wasm and b/js/packages/web-renderer/raw/elementary-wasm.wasm differ diff --git a/runtime/elem/Runtime.h b/runtime/elem/Runtime.h index 9f1016b..dfd1c08 100644 --- a/runtime/elem/Runtime.h +++ b/runtime/elem/Runtime.h @@ -297,7 +297,8 @@ namespace elem userData, true, emptyInputEvents, - emptyOutputEvents + emptyOutputEvents, + CurrentTime() }); } diff --git a/runtime/elem/Types.h b/runtime/elem/Types.h index eb4a5e4..5857d78 100644 --- a/runtime/elem/Types.h +++ b/runtime/elem/Types.h @@ -89,6 +89,37 @@ namespace elem } }; + //============================================================================== + // A struct representing the current global time in various units + struct CurrentTime { + int64_t sampleTime; + double beatTime; + double bpm; + double timeSignatureNumerator; + double timeSignatureDenominator; + + CurrentTime() + : sampleTime(0) + , beatTime(0) + , bpm(120) + , timeSignatureNumerator(4) + , timeSignatureDenominator(4) + {} + + explicit CurrentTime( + int64_t sampleTime, + double beatTime, + double bpm, + double timeSignatureNumerator, + double timeSignatureDenominator) + : sampleTime(sampleTime) + , beatTime(beatTime) + , bpm(bpm) + , timeSignatureNumerator(timeSignatureNumerator) + , timeSignatureDenominator(timeSignatureDenominator) + {} + }; + //============================================================================== // A simple struct representing the inputs to a given GraphNode during the realtime // audio block processing step. @@ -104,6 +135,7 @@ namespace elem bool active; BlockEvents const& inputEvents; BlockEvents& outputEvents; + CurrentTime currentTime; }; //============================================================================== diff --git a/wasm/Main.cpp b/wasm/Main.cpp index 8f8f627..05b3be7 100644 --- a/wasm/Main.cpp +++ b/wasm/Main.cpp @@ -7,10 +7,23 @@ #include "FFT.h" #include "Metro.h" #include "SampleTime.h" +#include "elem/Types.h" using namespace emscripten; +namespace { + inline double sampleTimeToBeatTime(int64_t sampleTime, double bpm, double sampleRate) { + if (sampleRate <= 0.0) return 0.0; + return (sampleTime / sampleRate) * (bpm / 60.0); + } + + inline int64_t beatTimeToSampleTime(double beatTime, double bpm, double sampleRate) { + if (bpm <= 0.0) return 0; + return static_cast((beatTime * 60.0 / bpm) * sampleRate); + } +} + //============================================================================== /** The main processor for the WASM DSP. */ class ElementaryAudioProcessor @@ -212,6 +225,13 @@ class ElementaryAudioProcessor } } + auto const currentTime = elem::CurrentTime { + sampleTime, + beatTime, + bpm, + timeSignatureNumerator, + timeSignatureDenominator, + }; // We just operate on our scratch data. Expect the JavaScript caller to hit // our getInputBufferData and getOutputBufferData to prepare and extract the actual // data for this processor @@ -225,9 +245,11 @@ class ElementaryAudioProcessor true, inputEvents, outputEvents, + currentTime, }); sampleTime += static_cast(numSamples); + beatTime += sampleTimeToBeatTime(numSamples, bpm, sampleRate); // Prepare to receive new events before the next call inputEvents.clear(); @@ -252,12 +274,42 @@ class ElementaryAudioProcessor void setCurrentTime(int const timeInSamples) { sampleTime = timeInSamples; + beatTime = sampleTimeToBeatTime(sampleTime, bpm, sampleRate); } void setCurrentTimeMs(double const timeInMs) { double const timeInSeconds = timeInMs / 1000.0; sampleTime = static_cast(timeInSeconds * sampleRate); + beatTime = sampleTimeToBeatTime(sampleTime, bpm, sampleRate); + } + + void setBeatTime(double const timeInBeats) + { + if (sampleRate <= 0.0 || bpm <= 0.0) + return; + + beatTime = timeInBeats; + sampleTime = beatTimeToSampleTime(timeInBeats, bpm, sampleRate); + } + + void setBpm(double const beatsPerMinute) + { + if (beatsPerMinute <= 0.0) + return; + + bpm = beatsPerMinute; + // Recalculate sampleTime from beatTime at new BPM + sampleTime = beatTimeToSampleTime(beatTime, bpm, sampleRate); + } + + void setTimeSignature(double const numerator, double const denominator) + { + if (numerator <= 0.0 || denominator <= 0.0) + return; + + timeSignatureNumerator = numerator; + timeSignatureDenominator = denominator; } private: @@ -409,8 +461,13 @@ class ElementaryAudioProcessor std::vector scratchPointers; int64_t sampleTime = 0; + double beatTime = 0.0; double sampleRate = 0; + double bpm = 120.0; + double timeSignatureNumerator = 4.0; + double timeSignatureDenominator = 4.0; + size_t numInputChannels = 0; size_t numOutputChannels = 2; @@ -435,5 +492,8 @@ EMSCRIPTEN_BINDINGS(Elementary) { .function("process", &ElementaryAudioProcessor::process) .function("processQueuedEvents", &ElementaryAudioProcessor::processQueuedEvents) .function("setCurrentTime", &ElementaryAudioProcessor::setCurrentTime) - .function("setCurrentTimeMs", &ElementaryAudioProcessor::setCurrentTimeMs); + .function("setCurrentTimeMs", &ElementaryAudioProcessor::setCurrentTimeMs) + .function("setBeatTime", &ElementaryAudioProcessor::setBeatTime) + .function("setBpm", &ElementaryAudioProcessor::setBpm) + .function("setTimeSignature", &ElementaryAudioProcessor::setTimeSignature); };