rlo-engine 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/dist/Core/AudioMath.d.ts +29 -0
- package/dist/Core/AudioMath.d.ts.map +1 -0
- package/dist/Core/AudioMath.js +64 -0
- package/dist/Core/AudioMath.js.map +1 -0
- package/dist/Core/InstrumentMap.d.ts +36 -0
- package/dist/Core/InstrumentMap.d.ts.map +1 -0
- package/dist/Core/InstrumentMap.js +62 -0
- package/dist/Core/InstrumentMap.js.map +1 -0
- package/dist/Core/RLOCore.d.ts +49 -0
- package/dist/Core/RLOCore.d.ts.map +1 -0
- package/dist/Core/RLOCore.js +188 -0
- package/dist/Core/RLOCore.js.map +1 -0
- package/dist/Core/SequenceBuilder.d.ts +23 -0
- package/dist/Core/SequenceBuilder.d.ts.map +1 -0
- package/dist/Core/SequenceBuilder.js +31 -0
- package/dist/Core/SequenceBuilder.js.map +1 -0
- package/dist/Instruments/Analog/AnalogSynthBase.d.ts +17 -7
- package/dist/Instruments/Analog/AnalogSynthBase.d.ts.map +1 -1
- package/dist/Instruments/Analog/AnalogSynthBase.js +27 -16
- package/dist/Instruments/Analog/AnalogSynthBase.js.map +1 -1
- package/dist/Instruments/Analog/BassSynth.d.ts +12 -5
- package/dist/Instruments/Analog/BassSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/BassSynth.js +17 -10
- package/dist/Instruments/Analog/BassSynth.js.map +1 -1
- package/dist/Instruments/Analog/ChiptuneSynth.d.ts +11 -4
- package/dist/Instruments/Analog/ChiptuneSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/ChiptuneSynth.js +12 -5
- package/dist/Instruments/Analog/ChiptuneSynth.js.map +1 -1
- package/dist/Instruments/Analog/FMSynth.d.ts +11 -4
- package/dist/Instruments/Analog/FMSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/FMSynth.js +14 -7
- package/dist/Instruments/Analog/FMSynth.js.map +1 -1
- package/dist/Instruments/Analog/FormantSynth.d.ts +11 -4
- package/dist/Instruments/Analog/FormantSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/FormantSynth.js +12 -5
- package/dist/Instruments/Analog/FormantSynth.js.map +1 -1
- package/dist/Instruments/Analog/LeadSynth.d.ts +11 -4
- package/dist/Instruments/Analog/LeadSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/LeadSynth.js +14 -7
- package/dist/Instruments/Analog/LeadSynth.js.map +1 -1
- package/dist/Instruments/Analog/OrganSynth.d.ts +11 -4
- package/dist/Instruments/Analog/OrganSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/OrganSynth.js +17 -10
- package/dist/Instruments/Analog/OrganSynth.js.map +1 -1
- package/dist/Instruments/Analog/PadSynth.js +7 -7
- package/dist/Instruments/Analog/PadSynth.js.map +1 -1
- package/dist/Instruments/Analog/ReeseBassSynth.d.ts +11 -4
- package/dist/Instruments/Analog/ReeseBassSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/ReeseBassSynth.js +13 -6
- package/dist/Instruments/Analog/ReeseBassSynth.js.map +1 -1
- package/dist/Instruments/Analog/StringSynth.js +4 -4
- package/dist/Instruments/Analog/StringSynth.js.map +1 -1
- package/dist/Instruments/Analog/WoodwindSynth.d.ts +11 -4
- package/dist/Instruments/Analog/WoodwindSynth.d.ts.map +1 -1
- package/dist/Instruments/Analog/WoodwindSynth.js +19 -12
- package/dist/Instruments/Analog/WoodwindSynth.js.map +1 -1
- package/dist/Instruments/CoreSynthBase.d.ts +31 -24
- package/dist/Instruments/CoreSynthBase.d.ts.map +1 -1
- package/dist/Instruments/CoreSynthBase.js +53 -34
- package/dist/Instruments/CoreSynthBase.js.map +1 -1
- package/dist/Instruments/Decay/AdditiveSynth.d.ts +12 -5
- package/dist/Instruments/Decay/AdditiveSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/AdditiveSynth.js +9 -2
- package/dist/Instruments/Decay/AdditiveSynth.js.map +1 -1
- package/dist/Instruments/Decay/BrassSynth.d.ts +11 -4
- package/dist/Instruments/Decay/BrassSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/BrassSynth.js +22 -15
- package/dist/Instruments/Decay/BrassSynth.js.map +1 -1
- package/dist/Instruments/Decay/ChromaticPercussionSynth.d.ts +12 -5
- package/dist/Instruments/Decay/ChromaticPercussionSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/ChromaticPercussionSynth.js +16 -9
- package/dist/Instruments/Decay/ChromaticPercussionSynth.js.map +1 -1
- package/dist/Instruments/Decay/DecaySynthBase.d.ts +14 -8
- package/dist/Instruments/Decay/DecaySynthBase.d.ts.map +1 -1
- package/dist/Instruments/Decay/DecaySynthBase.js +22 -15
- package/dist/Instruments/Decay/DecaySynthBase.js.map +1 -1
- package/dist/Instruments/Decay/ElectricGuitarSynth.d.ts +12 -5
- package/dist/Instruments/Decay/ElectricGuitarSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/ElectricGuitarSynth.js +19 -12
- package/dist/Instruments/Decay/ElectricGuitarSynth.js.map +1 -1
- package/dist/Instruments/Decay/EthnicSynth.d.ts +12 -5
- package/dist/Instruments/Decay/EthnicSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/EthnicSynth.js +13 -6
- package/dist/Instruments/Decay/EthnicSynth.js.map +1 -1
- package/dist/Instruments/Decay/GuitarSynth.d.ts +12 -5
- package/dist/Instruments/Decay/GuitarSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/GuitarSynth.js +14 -7
- package/dist/Instruments/Decay/GuitarSynth.js.map +1 -1
- package/dist/Instruments/Decay/KarplusSynth.d.ts +7 -0
- package/dist/Instruments/Decay/KarplusSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/KarplusSynth.js +16 -8
- package/dist/Instruments/Decay/KarplusSynth.js.map +1 -1
- package/dist/Instruments/Decay/PianoSynth.d.ts +12 -5
- package/dist/Instruments/Decay/PianoSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/PianoSynth.js +19 -12
- package/dist/Instruments/Decay/PianoSynth.js.map +1 -1
- package/dist/Instruments/Decay/SlapBassSynth.d.ts +12 -5
- package/dist/Instruments/Decay/SlapBassSynth.d.ts.map +1 -1
- package/dist/Instruments/Decay/SlapBassSynth.js +13 -6
- package/dist/Instruments/Decay/SlapBassSynth.js.map +1 -1
- package/dist/Instruments/ISynthInstrument.d.ts +6 -0
- package/dist/Instruments/ISynthInstrument.d.ts.map +1 -1
- package/dist/Instruments/Speciality/DrumSynth.d.ts +7 -0
- package/dist/Instruments/Speciality/DrumSynth.d.ts.map +1 -1
- package/dist/Instruments/Speciality/DrumSynth.js +30 -21
- package/dist/Instruments/Speciality/DrumSynth.js.map +1 -1
- package/dist/Instruments/Speciality/SoundEffectsSynth.d.ts +7 -0
- package/dist/Instruments/Speciality/SoundEffectsSynth.d.ts.map +1 -1
- package/dist/Instruments/Speciality/SoundEffectsSynth.js +20 -12
- package/dist/Instruments/Speciality/SoundEffectsSynth.js.map +1 -1
- package/dist/Instruments/Synthesizer.d.ts +5 -0
- package/dist/Instruments/Synthesizer.d.ts.map +1 -1
- package/dist/Instruments/Synthesizer.js +7 -1
- package/dist/Instruments/Synthesizer.js.map +1 -1
- package/dist/Players/RLOGameEngine.d.ts +35 -0
- package/dist/Players/RLOGameEngine.d.ts.map +1 -0
- package/dist/Players/RLOGameEngine.js +98 -0
- package/dist/Players/RLOGameEngine.js.map +1 -0
- package/dist/Players/RLOMusicPlayer.d.ts +35 -0
- package/dist/Players/RLOMusicPlayer.d.ts.map +1 -0
- package/dist/Players/RLOMusicPlayer.js +167 -0
- package/dist/Players/RLOMusicPlayer.js.map +1 -0
- package/dist/RLO-Transpiler.d.ts +37 -15
- package/dist/RLO-Transpiler.d.ts.map +1 -1
- package/dist/RLO-Transpiler.js +139 -117
- package/dist/RLO-Transpiler.js.map +1 -1
- package/dist/compiler.d.ts +50 -31
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +165 -230
- package/dist/compiler.js.map +1 -1
- package/dist/crush.d.ts +1 -5
- package/dist/crush.d.ts.map +1 -1
- package/dist/crush.js +10 -3
- package/dist/crush.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/midi-parser.d.ts +14 -0
- package/dist/midi-parser.d.ts.map +1 -1
- package/dist/midi-parser.js +21 -0
- package/dist/midi-parser.js.map +1 -1
- package/dist/rlo-engine.min.js +441 -439
- package/dist/rlo-engine.min.js.map +1 -1
- package/dist/rlo-engine.min.umd.js +1 -1
- package/dist/rlo-engine.min.umd.js.map +1 -1
- package/dist/types.d.ts +21 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/vite.config.d.ts.map +1 -1
- package/dist/vite.config.js +16 -3
- package/dist/vite.config.js.map +1 -1
- package/dist/vite.config.js13k.d.ts.map +1 -1
- package/dist/vite.config.js13k.js +1 -0
- package/dist/vite.config.js13k.js.map +1 -1
- package/package.json +1 -1
- package/dist/Instruments/InstrumentFactory.d.ts +0 -2
- package/dist/Instruments/InstrumentFactory.d.ts.map +0 -1
- package/dist/Instruments/InstrumentFactory.js +0 -4
- package/dist/Instruments/InstrumentFactory.js.map +0 -1
- package/dist/RLO-Player.d.ts +0 -298
- package/dist/RLO-Player.d.ts.map +0 -1
- package/dist/RLO-Player.js +0 -724
- package/dist/RLO-Player.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,6 +4,17 @@ An optimized compiler and procedural Web Audio engine designed specifically for
|
|
|
4
4
|
|
|
5
5
|
RLO converts sequence files (like MIDI) into 1D numerical arrays, bypassing the need for external parsing libraries or audio samples. Audio is synthesized dynamically at runtime using pure mathematics and the native Web Audio API.
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
Demo PWA game using this library : https://github.com/SiliconStreetDev1/NeonBlitz
|
|
9
|
+
|
|
10
|
+
🎮 **[PLAY NEON BLITZ DIRECTLY IN YOUR BROWSER HERE!](https://siliconstreetdev1.github.io/NeonBlitz/)** 🎮
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
See JS13ksize working example:
|
|
14
|
+
https://github.com/SiliconStreetDev1/NeonBlitzjs13k
|
|
15
|
+
|
|
16
|
+
📜 **[View Changelog (v1.1.0 Update)](./CHANGELOG.md)**
|
|
17
|
+
|
|
7
18
|
### 🚀 Two Ways to Use the Engine
|
|
8
19
|
|
|
9
20
|
This library uses a **Hybrid Architecture**. You can either install it directly via NPM for standard development, or clone it as a build-pipeline boilerplate to achieve high compression for JS13K competitions.
|
|
@@ -126,7 +137,7 @@ If you are using `RLOMusicPlayer`, it handles fetching and decompression automat
|
|
|
126
137
|
The `.rlo` files generated by the CLI compiler are **gzipped binary buffers**. Here is the standard boilerplate to fetch, unzip, and decode them using native browser APIs:
|
|
127
138
|
|
|
128
139
|
```javascript
|
|
129
|
-
import {
|
|
140
|
+
import { decodeBinary } from "rlo-engine";
|
|
130
141
|
|
|
131
142
|
async function loadAndPlayRLO(url, engine) {
|
|
132
143
|
const res = await fetch(url);
|
|
@@ -137,7 +148,7 @@ async function loadAndPlayRLO(url, engine) {
|
|
|
137
148
|
const buffer = await new Response(decompressedStream).arrayBuffer();
|
|
138
149
|
|
|
139
150
|
// 2. Decode the binary buffer into the RloData object format
|
|
140
|
-
const trackData =
|
|
151
|
+
const trackData = decodeBinary(buffer);
|
|
141
152
|
|
|
142
153
|
// 3. Play it!
|
|
143
154
|
engine.playMusic(trackData); // Use playSequence(trackData) if using RLOCore
|
|
@@ -148,7 +159,7 @@ async function loadAndPlayRLO(url, engine) {
|
|
|
148
159
|
|
|
149
160
|
## Paradigm B: The JS13k Boilerplate (High Compression)
|
|
150
161
|
|
|
151
|
-
For size-coding and JS13k developers. By cloning the repo and using the custom build pipeline, you bypass ES module boundaries, allowing the Terser minifier to mangle internal properties and strip out any synthesizers you don't actively use.
|
|
162
|
+
For size-coding and JS13k developers. By cloning the repo and using the custom build pipeline, you bypass ES module boundaries, allowing the Terser minifier to mangle internal properties and strip out any synthesizers you don't actively use. **The current highly-optimized core engine sits at ~2.2KB Gzipped.**
|
|
152
163
|
|
|
153
164
|
### 1. Clone & Install
|
|
154
165
|
|
|
@@ -356,7 +367,7 @@ export class MyCustomSynth extends CoreSynthBase {
|
|
|
356
367
|
const gain = this._gain(ctx, 0, masterGain);
|
|
357
368
|
|
|
358
369
|
// 2. Create a sound wave (Oscillator)
|
|
359
|
-
const osc = this._osc(ctx,
|
|
370
|
+
const osc = this._osc(ctx, "square", freq, gain);
|
|
360
371
|
|
|
361
372
|
// 3. Mathematical Volume Envelope (ADSR)
|
|
362
373
|
applyEnvelope(gain.gain, time, duration, {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ADSREnvelope {
|
|
2
|
+
attack?: number;
|
|
3
|
+
decay?: number;
|
|
4
|
+
sustain?: number;
|
|
5
|
+
release?: number;
|
|
6
|
+
peak?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Mathematically applies an Attack-Decay-Sustain-Release (ADSR) volume envelope to an AudioParam.
|
|
10
|
+
*
|
|
11
|
+
* @reason Why we use explicit `setValueAtTime` and `linearRampToValueAtTime`:
|
|
12
|
+
* Web Audio nodes often suffer from clicking/popping if values jump instantaneously.
|
|
13
|
+
* By using linear ramps, we ensure smooth zero-crossing transitions. Furthermore,
|
|
14
|
+
* the logic elegantly handles edge-cases where the note duration is shorter than
|
|
15
|
+
* the Attack or Decay phase by dynamically calculating partial peaks and
|
|
16
|
+
* preemptively jumping to the Release phase, preventing sustained hanging notes.
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyEnvelope(gainParam: AudioParam, now: number, duration: number, opts: ADSREnvelope): void;
|
|
19
|
+
/**
|
|
20
|
+
* Converts a scientific pitch notation string (e.g., "C4", "F#3") to frequency in Hz.
|
|
21
|
+
*
|
|
22
|
+
* @reason Why this is included but conditionally compiled:
|
|
23
|
+
* This parser is highly convenient for developers writing sequences manually via the API,
|
|
24
|
+
* allowing `Note("C4")` instead of `261.63`. However, the transpiler strips string notes
|
|
25
|
+
* during binary compilation, so we wrap this in `__ENABLE_NOTE_PARSER__` to allow Rollup
|
|
26
|
+
* to entirely remove the Regex from the JS13K build if unused.
|
|
27
|
+
*/
|
|
28
|
+
export declare function Note(pitch: string | number): number;
|
|
29
|
+
//# sourceMappingURL=AudioMath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioMath.d.ts","sourceRoot":"","sources":["../../Core/AudioMath.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,UAAU,EACrB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,YAAY,GACjB,IAAI,CAwBN;AAKD;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAYnD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mathematically applies an Attack-Decay-Sustain-Release (ADSR) volume envelope to an AudioParam.
|
|
3
|
+
*
|
|
4
|
+
* @reason Why we use explicit `setValueAtTime` and `linearRampToValueAtTime`:
|
|
5
|
+
* Web Audio nodes often suffer from clicking/popping if values jump instantaneously.
|
|
6
|
+
* By using linear ramps, we ensure smooth zero-crossing transitions. Furthermore,
|
|
7
|
+
* the logic elegantly handles edge-cases where the note duration is shorter than
|
|
8
|
+
* the Attack or Decay phase by dynamically calculating partial peaks and
|
|
9
|
+
* preemptively jumping to the Release phase, preventing sustained hanging notes.
|
|
10
|
+
*/
|
|
11
|
+
export function applyEnvelope(gainParam, now, duration, opts) {
|
|
12
|
+
const a = opts.attack ?? 0.05;
|
|
13
|
+
const d = opts.decay ?? 0.1;
|
|
14
|
+
const s = opts.sustain ?? 0.8;
|
|
15
|
+
const r = opts.release ?? 0.1;
|
|
16
|
+
const peak = opts.peak ?? 1.0;
|
|
17
|
+
gainParam.setValueAtTime(0, now);
|
|
18
|
+
const realDur = Math.max(0, duration);
|
|
19
|
+
if (realDur <= a) {
|
|
20
|
+
const partialPeak = peak * (realDur / (a || 1));
|
|
21
|
+
gainParam.linearRampToValueAtTime(partialPeak, now + realDur * 0.5);
|
|
22
|
+
gainParam.linearRampToValueAtTime(0, now + realDur);
|
|
23
|
+
}
|
|
24
|
+
else if (realDur <= a + d) {
|
|
25
|
+
gainParam.linearRampToValueAtTime(peak, now + a);
|
|
26
|
+
gainParam.linearRampToValueAtTime(0, now + realDur);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
gainParam.linearRampToValueAtTime(peak, now + a);
|
|
30
|
+
gainParam.linearRampToValueAtTime(peak * s, now + a + d);
|
|
31
|
+
const releaseStart = Math.max(now + a + d, now + realDur - r);
|
|
32
|
+
gainParam.setValueAtTime(peak * s, releaseStart);
|
|
33
|
+
gainParam.linearRampToValueAtTime(0, now + realDur);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const hasNoteParser = typeof __ENABLE_NOTE_PARSER__ !== "undefined" ? __ENABLE_NOTE_PARSER__ : true;
|
|
37
|
+
/**
|
|
38
|
+
* Converts a scientific pitch notation string (e.g., "C4", "F#3") to frequency in Hz.
|
|
39
|
+
*
|
|
40
|
+
* @reason Why this is included but conditionally compiled:
|
|
41
|
+
* This parser is highly convenient for developers writing sequences manually via the API,
|
|
42
|
+
* allowing `Note("C4")` instead of `261.63`. However, the transpiler strips string notes
|
|
43
|
+
* during binary compilation, so we wrap this in `__ENABLE_NOTE_PARSER__` to allow Rollup
|
|
44
|
+
* to entirely remove the Regex from the JS13K build if unused.
|
|
45
|
+
*/
|
|
46
|
+
export function Note(pitch) {
|
|
47
|
+
if (typeof pitch === "number")
|
|
48
|
+
return pitch;
|
|
49
|
+
if (!hasNoteParser)
|
|
50
|
+
return 0;
|
|
51
|
+
const match = pitch.match(/^([a-gA-G])([#b]?)(\d)$/);
|
|
52
|
+
if (!match)
|
|
53
|
+
return 0;
|
|
54
|
+
const offsets = { C: -9, D: -7, E: -5, F: -4, G: -2, A: 0, B: 2 };
|
|
55
|
+
const [, note, accidental, octave] = match;
|
|
56
|
+
let semitone = offsets[note.toUpperCase()];
|
|
57
|
+
if (accidental === "#")
|
|
58
|
+
semitone++;
|
|
59
|
+
if (accidental === "b")
|
|
60
|
+
semitone--;
|
|
61
|
+
semitone += (parseInt(octave, 10) - 4) * 12;
|
|
62
|
+
return Number((440 * Math.pow(2, semitone / 12)).toFixed(2));
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=AudioMath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioMath.js","sourceRoot":"","sources":["../../Core/AudioMath.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ISynthInstrument } from "../Instruments/ISynthInstrument.js";
|
|
2
|
+
/**
|
|
3
|
+
* Helper synth that produces zero sound, used as a fallback or placeholder.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SilentSynth: ISynthInstrument;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a raw mapping between explicit MIDI Program IDs (0-128) and Synthesizer instances.
|
|
8
|
+
*
|
|
9
|
+
* @reason Used primarily by the `crush` JS13k build where only 2 or 3 synths are bundled,
|
|
10
|
+
* avoiding the need for complex fuzzy-matching loops.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createDirectMap(assignments: {
|
|
13
|
+
synth: ISynthInstrument;
|
|
14
|
+
ids: number[];
|
|
15
|
+
}[]): ISynthInstrument[];
|
|
16
|
+
/**
|
|
17
|
+
* Populates a 128-slot Array mapping MIDI Program IDs to Synthesizer instances.
|
|
18
|
+
*
|
|
19
|
+
* @reason The NPM library comes with 16 default synths. Standard MIDI files might
|
|
20
|
+
* request Program ID 44 (Contrabass), but we only have `StringSynth` mapped to 40-55.
|
|
21
|
+
* This function uses a fuzzy "closest neighbor" algorithm to automatically fill
|
|
22
|
+
* any missing gaps (e.g. mapping ID 44 to the nearest available synth ID). This guarantees
|
|
23
|
+
* that imported MIDI files will always produce sound, even if the user hasn't explicitly
|
|
24
|
+
* instantiated all 128 GM instruments.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createInstrumentMap(modules: {
|
|
27
|
+
synth: ISynthInstrument;
|
|
28
|
+
start: number;
|
|
29
|
+
end: number;
|
|
30
|
+
}[]): ISynthInstrument[];
|
|
31
|
+
export declare function extendInstrumentMap(baseMap: ISynthInstrument[], overrides: {
|
|
32
|
+
synth: ISynthInstrument;
|
|
33
|
+
start: number;
|
|
34
|
+
end: number;
|
|
35
|
+
}[]): ISynthInstrument[];
|
|
36
|
+
//# sourceMappingURL=InstrumentMap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InstrumentMap.d.ts","sourceRoot":"","sources":["../../Core/InstrumentMap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,gBAEzB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,GAAG,gBAAgB,EAAE,CAM7G;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,gBAAgB,EAAE,CAuB1H;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,SAAS,EAAE;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,gBAAgB,EAAE,CAMzJ"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper synth that produces zero sound, used as a fallback or placeholder.
|
|
3
|
+
*/
|
|
4
|
+
export const SilentSynth = {
|
|
5
|
+
_playNote: () => { },
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Creates a raw mapping between explicit MIDI Program IDs (0-128) and Synthesizer instances.
|
|
9
|
+
*
|
|
10
|
+
* @reason Used primarily by the `crush` JS13k build where only 2 or 3 synths are bundled,
|
|
11
|
+
* avoiding the need for complex fuzzy-matching loops.
|
|
12
|
+
*/
|
|
13
|
+
export function createDirectMap(assignments) {
|
|
14
|
+
const mapArray = new Array(129).fill(null);
|
|
15
|
+
assignments.forEach((a) => {
|
|
16
|
+
a.ids.forEach((id) => (mapArray[id] = a.synth));
|
|
17
|
+
});
|
|
18
|
+
return mapArray;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Populates a 128-slot Array mapping MIDI Program IDs to Synthesizer instances.
|
|
22
|
+
*
|
|
23
|
+
* @reason The NPM library comes with 16 default synths. Standard MIDI files might
|
|
24
|
+
* request Program ID 44 (Contrabass), but we only have `StringSynth` mapped to 40-55.
|
|
25
|
+
* This function uses a fuzzy "closest neighbor" algorithm to automatically fill
|
|
26
|
+
* any missing gaps (e.g. mapping ID 44 to the nearest available synth ID). This guarantees
|
|
27
|
+
* that imported MIDI files will always produce sound, even if the user hasn't explicitly
|
|
28
|
+
* instantiated all 128 GM instruments.
|
|
29
|
+
*/
|
|
30
|
+
export function createInstrumentMap(modules) {
|
|
31
|
+
const mapArray = new Array(129).fill(null);
|
|
32
|
+
modules.forEach((m) => {
|
|
33
|
+
for (let i = m.start; i <= m.end; i++)
|
|
34
|
+
mapArray[i] = m.synth;
|
|
35
|
+
});
|
|
36
|
+
for (let i = 0; i < 129; i++) {
|
|
37
|
+
if (mapArray[i] === null) {
|
|
38
|
+
let closest = null;
|
|
39
|
+
let minDiff = Infinity;
|
|
40
|
+
for (let j = 0; j < 129; j++) {
|
|
41
|
+
if (mapArray[j] !== null) {
|
|
42
|
+
const diff = Math.abs(i - j);
|
|
43
|
+
if (diff < minDiff) {
|
|
44
|
+
minDiff = diff;
|
|
45
|
+
closest = mapArray[j];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
mapArray[i] = closest;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return mapArray;
|
|
53
|
+
}
|
|
54
|
+
export function extendInstrumentMap(baseMap, overrides) {
|
|
55
|
+
const newMap = [...baseMap];
|
|
56
|
+
overrides.forEach((m) => {
|
|
57
|
+
for (let i = m.start; i <= m.end; i++)
|
|
58
|
+
newMap[i] = m.synth;
|
|
59
|
+
});
|
|
60
|
+
return newMap;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=InstrumentMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InstrumentMap.js","sourceRoot":"","sources":["../../Core/InstrumentMap.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAqB;IAC3C,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,WAAyD;IACvF,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACxB,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAkE;IACpG,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACpB,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE;YAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACzB,IAAI,OAAO,GAAG,IAAI,CAAC;YACnB,IAAI,OAAO,GAAG,QAAQ,CAAC;YACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC7B,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;oBACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAC7B,IAAI,IAAI,GAAG,OAAO,EAAE,CAAC;wBACnB,OAAO,GAAG,IAAI,CAAC;wBACf,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;oBACxB,CAAC;gBACH,CAAC;YACH,CAAC;YACD,QAAQ,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAA2B,EAAE,SAAoE;IACnI,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAC5B,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;IAC7D,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RloData } from "../types.js";
|
|
2
|
+
import { ISynthInstrument } from "../Instruments/ISynthInstrument.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options for playing a compiled RLO sequence.
|
|
5
|
+
*/
|
|
6
|
+
export interface PlaySequenceOptions {
|
|
7
|
+
loop?: boolean;
|
|
8
|
+
fadeInTime?: number;
|
|
9
|
+
playbackRate?: number;
|
|
10
|
+
volume?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Base Engine Core responsible for the precise scheduling and timing of Web Audio nodes.
|
|
14
|
+
*
|
|
15
|
+
* @reason Why we use a Worker for the metronome:
|
|
16
|
+
* In modern browsers, `setTimeout` or `setInterval` on the main thread is throttled
|
|
17
|
+
* to ~1000ms if the user switches browser tabs (to save battery).
|
|
18
|
+
* By offloading the timing loop to a Web Worker, we guarantee that background tabs
|
|
19
|
+
* continue to schedule audio nodes perfectly at 50ms intervals without skipping a beat.
|
|
20
|
+
*/
|
|
21
|
+
export declare class RLOCore {
|
|
22
|
+
protected _ctx: AudioContext;
|
|
23
|
+
protected _isPlaying: boolean;
|
|
24
|
+
protected _timer: ReturnType<typeof setTimeout> | null;
|
|
25
|
+
protected _workerTimer: Worker | null;
|
|
26
|
+
protected _sequenceId: number;
|
|
27
|
+
protected _trkT: number;
|
|
28
|
+
protected _trkD: number;
|
|
29
|
+
protected _activeNodes: AudioNode[];
|
|
30
|
+
protected _volume: number;
|
|
31
|
+
protected _seekTarget: number | null;
|
|
32
|
+
playbackRate: number;
|
|
33
|
+
protected _instrumentMap: ISynthInstrument[];
|
|
34
|
+
protected _workerUrl: string | null;
|
|
35
|
+
constructor(audioContext: AudioContext, instrumentMap?: ISynthInstrument[]);
|
|
36
|
+
protected get _now(): number;
|
|
37
|
+
setVolume(vol: number): void;
|
|
38
|
+
seek(timeInSeconds: number): void;
|
|
39
|
+
stop(): void;
|
|
40
|
+
dispose(): void;
|
|
41
|
+
protected _createGain(): GainNode;
|
|
42
|
+
protected _applyLinearFade(gain: AudioParam, target: number, time: number): void;
|
|
43
|
+
protected _createCompressor(ctx?: AudioContext): DynamicsCompressorNode;
|
|
44
|
+
protected _createRouting(fadeInTime?: number): {
|
|
45
|
+
destination: AudioNode;
|
|
46
|
+
};
|
|
47
|
+
playSequence(track: RloData, loopOrOpts?: boolean | PlaySequenceOptions, oldFadeInTime?: number): void;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=RLOCore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RLOCore.d.ts","sourceRoot":"","sources":["../../Core/RLOCore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAGtE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD;;;;;;;;GAQG;AACH,qBAAa,OAAO;IAClB,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC;IAC7B,SAAS,CAAC,UAAU,EAAE,OAAO,CAAS;IACtC,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAQ;IAC9D,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC7C,SAAS,CAAC,WAAW,EAAE,MAAM,CAAK;IAClC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAK;IAC5B,SAAS,CAAC,KAAK,EAAE,MAAM,CAAK;IAC5B,SAAS,CAAC,YAAY,EAAE,SAAS,EAAE,CAAM;IACzC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAO;IAChC,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAQ;IAErC,YAAY,EAAE,MAAM,CAAO;IAElC,SAAS,CAAC,cAAc,EAAE,gBAAgB,EAAE,CAAC;IAC7C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;gBAE/B,YAAY,EAAE,YAAY,EAAE,aAAa,GAAE,gBAAgB,EAAO;IAgB9E,SAAS,KAAK,IAAI,IAAI,MAAM,CAE3B;IAEM,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI5B,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAMjC,IAAI,IAAI,IAAI;IAcZ,OAAO,IAAI,IAAI;IAYtB,SAAS,CAAC,WAAW,IAAI,QAAQ;IAIjC,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKhF,SAAS,CAAC,iBAAiB,CAAC,GAAG,GAAE,YAAwB,GAAG,sBAAsB;IAUlF,SAAS,CAAC,cAAc,CAAC,UAAU,GAAE,MAAU,GAAG;QAAE,WAAW,EAAE,SAAS,CAAA;KAAE;IAQrE,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,GAAE,OAAO,GAAG,mBAA0B,EAAE,aAAa,GAAE,MAAU,GAAG,IAAI;CAkGvH"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Synthesizer } from "../Instruments/Synthesizer.js";
|
|
2
|
+
const hasWorkerMetronome = typeof __ENABLE_WORKER_METRONOME__ !== "undefined" ? __ENABLE_WORKER_METRONOME__ : false;
|
|
3
|
+
/**
|
|
4
|
+
* Base Engine Core responsible for the precise scheduling and timing of Web Audio nodes.
|
|
5
|
+
*
|
|
6
|
+
* @reason Why we use a Worker for the metronome:
|
|
7
|
+
* In modern browsers, `setTimeout` or `setInterval` on the main thread is throttled
|
|
8
|
+
* to ~1000ms if the user switches browser tabs (to save battery).
|
|
9
|
+
* By offloading the timing loop to a Web Worker, we guarantee that background tabs
|
|
10
|
+
* continue to schedule audio nodes perfectly at 50ms intervals without skipping a beat.
|
|
11
|
+
*/
|
|
12
|
+
export class RLOCore {
|
|
13
|
+
constructor(audioContext, instrumentMap = []) {
|
|
14
|
+
this._isPlaying = false;
|
|
15
|
+
this._timer = null;
|
|
16
|
+
this._workerTimer = null;
|
|
17
|
+
this._sequenceId = 0;
|
|
18
|
+
this._trkT = 0;
|
|
19
|
+
this._trkD = 0;
|
|
20
|
+
this._activeNodes = [];
|
|
21
|
+
this._volume = 0.5;
|
|
22
|
+
this._seekTarget = null;
|
|
23
|
+
this.playbackRate = 1.0;
|
|
24
|
+
this._workerUrl = null;
|
|
25
|
+
this._ctx = audioContext;
|
|
26
|
+
this._instrumentMap = instrumentMap;
|
|
27
|
+
if (hasWorkerMetronome) {
|
|
28
|
+
const blob = new Blob([
|
|
29
|
+
"let t=null;self.onmessage=e=>{let d=e.data;if(d=='start'){if(t)clearInterval(t);t=setInterval(()=>self.postMessage('tick'),50)}else if(d=='stop'&&t){clearInterval(t);t=null}};"
|
|
30
|
+
], { type: "application/javascript" });
|
|
31
|
+
this._workerUrl = URL.createObjectURL(blob);
|
|
32
|
+
this._workerTimer = new Worker(this._workerUrl);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
get _now() {
|
|
36
|
+
return this._ctx.currentTime;
|
|
37
|
+
}
|
|
38
|
+
setVolume(vol) {
|
|
39
|
+
this._volume = vol;
|
|
40
|
+
}
|
|
41
|
+
seek(timeInSeconds) {
|
|
42
|
+
if (this._trkD > 0) {
|
|
43
|
+
this._seekTarget = Math.max(0, timeInSeconds) % this._trkD;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
stop() {
|
|
47
|
+
this._isPlaying = false;
|
|
48
|
+
this._sequenceId++;
|
|
49
|
+
if (this._timer)
|
|
50
|
+
clearTimeout(this._timer);
|
|
51
|
+
if (hasWorkerMetronome && this._workerTimer)
|
|
52
|
+
this._workerTimer.postMessage("stop");
|
|
53
|
+
const nodesToClean = this._activeNodes;
|
|
54
|
+
this._activeNodes = [];
|
|
55
|
+
while (nodesToClean.length) {
|
|
56
|
+
try {
|
|
57
|
+
nodesToClean.pop().disconnect();
|
|
58
|
+
}
|
|
59
|
+
catch (e) { }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
dispose() {
|
|
63
|
+
this.stop();
|
|
64
|
+
if (hasWorkerMetronome && this._workerTimer) {
|
|
65
|
+
this._workerTimer.terminate();
|
|
66
|
+
this._workerTimer = null;
|
|
67
|
+
}
|
|
68
|
+
if (hasWorkerMetronome && this._workerUrl) {
|
|
69
|
+
URL.revokeObjectURL(this._workerUrl);
|
|
70
|
+
this._workerUrl = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
_createGain() {
|
|
74
|
+
return this._ctx.createGain();
|
|
75
|
+
}
|
|
76
|
+
_applyLinearFade(gain, target, time) {
|
|
77
|
+
gain.setValueAtTime(time > 0 ? 0 : target, this._now);
|
|
78
|
+
if (time > 0)
|
|
79
|
+
gain.linearRampToValueAtTime(target, this._now + time);
|
|
80
|
+
}
|
|
81
|
+
_createCompressor(ctx = this._ctx) {
|
|
82
|
+
const c = ctx.createDynamicsCompressor();
|
|
83
|
+
c.threshold.value = -24;
|
|
84
|
+
c.knee.value = 12;
|
|
85
|
+
c.ratio.value = 8;
|
|
86
|
+
c.attack.value = 0.001;
|
|
87
|
+
c.release.value = 0.25;
|
|
88
|
+
return c;
|
|
89
|
+
}
|
|
90
|
+
_createRouting(fadeInTime = 0) {
|
|
91
|
+
const gain = this._createGain();
|
|
92
|
+
this._applyLinearFade(gain.gain, this._volume, fadeInTime);
|
|
93
|
+
gain.connect(this._ctx.destination);
|
|
94
|
+
this._activeNodes.push(gain);
|
|
95
|
+
return { destination: gain };
|
|
96
|
+
}
|
|
97
|
+
playSequence(track, loopOrOpts = true, oldFadeInTime = 0) {
|
|
98
|
+
let loop = true;
|
|
99
|
+
let fadeInTime = oldFadeInTime;
|
|
100
|
+
if (typeof loopOrOpts === "object") {
|
|
101
|
+
loop = loopOrOpts.loop ?? true;
|
|
102
|
+
fadeInTime = loopOrOpts.fadeInTime ?? 0;
|
|
103
|
+
this.playbackRate = loopOrOpts.playbackRate ?? this.playbackRate;
|
|
104
|
+
if (loopOrOpts.volume !== undefined)
|
|
105
|
+
this.setVolume(loopOrOpts.volume);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
loop = loopOrOpts;
|
|
109
|
+
}
|
|
110
|
+
this.stop();
|
|
111
|
+
if (this._ctx.state === "suspended")
|
|
112
|
+
this._ctx.resume();
|
|
113
|
+
this._isPlaying = true;
|
|
114
|
+
const currentSequenceId = ++this._sequenceId;
|
|
115
|
+
this._trkD = track.durationSecs;
|
|
116
|
+
const { destination } = this._createRouting(fadeInTime);
|
|
117
|
+
const synthesizer = new Synthesizer(this._ctx, destination, this._instrumentMap);
|
|
118
|
+
const lookaheadTime = 0.5;
|
|
119
|
+
this._trkT = -0.05 * this.playbackRate;
|
|
120
|
+
let lastScheduleTime = this._now;
|
|
121
|
+
let loopOffsetSecs = 0;
|
|
122
|
+
let notePtr = 0;
|
|
123
|
+
const notes = track.notes;
|
|
124
|
+
const len = notes.length;
|
|
125
|
+
const schedule = () => {
|
|
126
|
+
if (!this._isPlaying || this._sequenceId !== currentSequenceId)
|
|
127
|
+
return;
|
|
128
|
+
if (len === 0)
|
|
129
|
+
return; // Prevent infinite loop on empty tracks
|
|
130
|
+
const currentPhysicalTime = this._now;
|
|
131
|
+
const deltaPhysical = currentPhysicalTime - lastScheduleTime;
|
|
132
|
+
lastScheduleTime = currentPhysicalTime;
|
|
133
|
+
if (this._seekTarget !== null) {
|
|
134
|
+
this._trkT = this._seekTarget;
|
|
135
|
+
loopOffsetSecs = 0;
|
|
136
|
+
notePtr = 0;
|
|
137
|
+
while (notePtr < len && notes[notePtr + 1] < this._seekTarget) {
|
|
138
|
+
notePtr += 5;
|
|
139
|
+
}
|
|
140
|
+
this._seekTarget = null;
|
|
141
|
+
// Duck the volume briefly to hide the 0.5s lookahead buffer overlap
|
|
142
|
+
const duckGain = destination;
|
|
143
|
+
if (duckGain.gain) {
|
|
144
|
+
duckGain.gain.cancelScheduledValues(currentPhysicalTime);
|
|
145
|
+
duckGain.gain.setValueAtTime(0.001, currentPhysicalTime);
|
|
146
|
+
duckGain.gain.exponentialRampToValueAtTime(this._volume, currentPhysicalTime + 0.5);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this._trkT += deltaPhysical * this.playbackRate;
|
|
151
|
+
}
|
|
152
|
+
const loopDurationSecs = track.durationSecs;
|
|
153
|
+
while (notePtr < len) {
|
|
154
|
+
const noteTrackTime = loopOffsetSecs + notes[notePtr + 1];
|
|
155
|
+
if (noteTrackTime >= this._trkT + lookaheadTime * this.playbackRate)
|
|
156
|
+
break;
|
|
157
|
+
// The "Lag Chord" drop threshold:
|
|
158
|
+
// If a thread freeze occurred, drop notes that are more than 150ms in the past
|
|
159
|
+
if (this._trkT - noteTrackTime <= 0.15 * this.playbackRate) {
|
|
160
|
+
const notePhysicalTime = currentPhysicalTime + Math.max(0, (noteTrackTime - this._trkT) / this.playbackRate);
|
|
161
|
+
synthesizer._playNote(notes[notePtr + 4], notePhysicalTime, notes[notePtr], notes[notePtr + 2] / this.playbackRate, notes[notePtr + 3]);
|
|
162
|
+
}
|
|
163
|
+
notePtr += 5;
|
|
164
|
+
}
|
|
165
|
+
if (notePtr >= len) {
|
|
166
|
+
if (loop) {
|
|
167
|
+
notePtr = 0;
|
|
168
|
+
loopOffsetSecs += loopDurationSecs;
|
|
169
|
+
schedule();
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
if (hasWorkerMetronome && this._workerTimer)
|
|
173
|
+
this._workerTimer.postMessage("stop");
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!hasWorkerMetronome || !this._workerTimer) {
|
|
178
|
+
this._timer = setTimeout(schedule, 50);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
if (hasWorkerMetronome && this._workerTimer) {
|
|
182
|
+
this._workerTimer.onmessage = () => schedule();
|
|
183
|
+
this._workerTimer.postMessage("start");
|
|
184
|
+
}
|
|
185
|
+
schedule();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=RLOCore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RLOCore.js","sourceRoot":"","sources":["../../Core/RLOCore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RloData } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fluent API for constructing RLO sequences programmatically without using a compiler.
|
|
4
|
+
*
|
|
5
|
+
* @reason This class directly pushes `[frequency, time, duration, velocity, instrument]`
|
|
6
|
+
* tuples into a flat `number[]` array. By avoiding intermediary JS Objects `{ note: 'C4', dur: 1 }`
|
|
7
|
+
* we completely eliminate Garbage Collection (GC) pauses during sequence generation,
|
|
8
|
+
* making it incredibly fast for procedural music generation at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export declare class RLOSequenceBuilder {
|
|
11
|
+
private _notes;
|
|
12
|
+
private _duration;
|
|
13
|
+
addNote(opts: {
|
|
14
|
+
instrument: number;
|
|
15
|
+
pitch: string | number;
|
|
16
|
+
duration: number;
|
|
17
|
+
time?: number;
|
|
18
|
+
velocity?: number;
|
|
19
|
+
}): this;
|
|
20
|
+
setDuration(secs: number): this;
|
|
21
|
+
compile(): RloData;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=SequenceBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SequenceBuilder.d.ts","sourceRoot":"","sources":["../../Core/SequenceBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,SAAS,CAAa;IAEvB,OAAO,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;KAAE,GAAG,IAAI;IAQxH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,OAAO,IAAI,OAAO;CAG1B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Note } from "./AudioMath.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fluent API for constructing RLO sequences programmatically without using a compiler.
|
|
4
|
+
*
|
|
5
|
+
* @reason This class directly pushes `[frequency, time, duration, velocity, instrument]`
|
|
6
|
+
* tuples into a flat `number[]` array. By avoiding intermediary JS Objects `{ note: 'C4', dur: 1 }`
|
|
7
|
+
* we completely eliminate Garbage Collection (GC) pauses during sequence generation,
|
|
8
|
+
* making it incredibly fast for procedural music generation at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export class RLOSequenceBuilder {
|
|
11
|
+
constructor() {
|
|
12
|
+
this._notes = [];
|
|
13
|
+
this._duration = 0;
|
|
14
|
+
}
|
|
15
|
+
addNote(opts) {
|
|
16
|
+
const time = opts.time !== undefined ? opts.time : this._duration;
|
|
17
|
+
const freq = Note(opts.pitch);
|
|
18
|
+
const vel = opts.velocity ?? 1.0;
|
|
19
|
+
this._notes.push(freq, time, opts.duration, vel, opts.instrument);
|
|
20
|
+
this._duration = Math.max(this._duration, time + opts.duration);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
setDuration(secs) {
|
|
24
|
+
this._duration = secs;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
compile() {
|
|
28
|
+
return { durationSecs: this._duration, notes: this._notes };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=SequenceBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SequenceBuilder.js","sourceRoot":"","sources":["../../Core/SequenceBuilder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC;;;;;;;GAOG;AACH,MAAM,OAAO,kBAAkB;IAA/B;QACU,WAAM,GAAa,EAAE,CAAC;QACtB,cAAS,GAAW,CAAC,CAAC;IAiBhC,CAAC;IAfQ,OAAO,CAAC,IAAyG;QACtH,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;QAClE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAClE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACM,WAAW,CAAC,IAAY;QAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IACM,OAAO;QACZ,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IAC9D,CAAC;CACF"}
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { CoreSynthBase } from "../CoreSynthBase.js";
|
|
2
2
|
export type AnalogCfg = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
_peakVelocity: number;
|
|
4
|
+
_attackTimeSeconds: number;
|
|
5
|
+
_releaseTimeSeconds: number;
|
|
6
|
+
_maxDurationSeconds?: number;
|
|
6
7
|
};
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Synthesizer instrument implementation.
|
|
10
|
+
*
|
|
11
|
+
* @reason Acoustic Design:
|
|
12
|
+
* Encapsulates the specific Web Audio node routing and ADSR parameters
|
|
13
|
+
* required to physically model this instrument within the 13KB limit.
|
|
14
|
+
*
|
|
15
|
+
* @reason Separation of Sustained vs Decaying Physics:
|
|
16
|
+
* Synthesizers like an Organ will sustain at peak volume infinitely as long as the key is held,
|
|
17
|
+
* whereas a Piano will physically decay to silence even if the key remains held down.
|
|
18
|
+
* This base class encapsulates the mathematical ADSR envelope logic specifically for
|
|
19
|
+
* infinite-sustain physics, ensuring derived classes only need to define the timbre (Oscillators/Filters).
|
|
10
20
|
*/
|
|
11
21
|
export declare abstract class AnalogSynthBase extends CoreSynthBase {
|
|
12
|
-
protected
|
|
22
|
+
protected _envelopeConfig: AnalogCfg;
|
|
13
23
|
_playNote(ctx: AudioContext, masterGain: GainNode, time: number, freq: number, duration: number, velocity: number): void;
|
|
14
|
-
protected
|
|
24
|
+
protected _getEnvelopeConfig(_decayTimeSeconds: number): AnalogCfg;
|
|
15
25
|
/**
|
|
16
26
|
* Set up the specific oscillators and filters for this instrument.
|
|
17
27
|
* You must start() and stop() your own oscillators.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalogSynthBase.d.ts","sourceRoot":"","sources":["../../../Instruments/Analog/AnalogSynthBase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"AnalogSynthBase.d.ts","sourceRoot":"","sources":["../../../Instruments/Analog/AnalogSynthBase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAe,MAAM,qBAAqB,CAAC;AAEjE,MAAM,MAAM,SAAS,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzI;;;;;;;;;;;;GAYG;AACH,8BAAsB,eAAgB,SAAQ,aAAa;IACzD,SAAS,CAAC,eAAe,EAAE,SAAS,CAAyG;IAEtI,SAAS,CACd,GAAG,EAAE,YAAY,EACjB,UAAU,EAAE,QAAQ,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,IAAI;IAgCP,SAAS,CAAC,kBAAkB,CAAC,iBAAiB,EAAE,MAAM,GAAG,SAAS;IAIlE;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,CAChC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,SAAS,GAAG,IAAI;CACpB"}
|