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/dist/RLO-Player.js
DELETED
|
@@ -1,724 +0,0 @@
|
|
|
1
|
-
import { Synthesizer } from "./Instruments/Synthesizer.js";
|
|
2
|
-
import { AudioEffects } from "./AudioEffects.js";
|
|
3
|
-
import { RLOTranspiler } from "./RLO-Transpiler.js"; // Corrected import path
|
|
4
|
-
export { RLOTranspiler };
|
|
5
|
-
/**
|
|
6
|
-
* A dummy synthesizer that does absolutely nothing.
|
|
7
|
-
* Useful for completely muting specific MIDI IDs in an instrument map.
|
|
8
|
-
*/
|
|
9
|
-
export const SilentSynth = {
|
|
10
|
-
_playNote: () => { },
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Syntactic sugar for applying a standard ADSR envelope to a Web Audio GainNode.
|
|
14
|
-
* @param gainParam The AudioParam (e.g., gainNode.gain) to animate.
|
|
15
|
-
* @param now The physical start time of the sound.
|
|
16
|
-
* @param duration The total held duration of the note.
|
|
17
|
-
* @param opts ADSR configuration object.
|
|
18
|
-
*/
|
|
19
|
-
export function applyEnvelope(gainParam, now, duration, opts) {
|
|
20
|
-
const a = opts.attack ?? 0.05;
|
|
21
|
-
const d = opts.decay ?? 0.1;
|
|
22
|
-
const s = opts.sustain ?? 0.8;
|
|
23
|
-
const r = opts.release ?? 0.1;
|
|
24
|
-
const peak = opts.peak ?? 1.0;
|
|
25
|
-
gainParam.setValueAtTime(0, now);
|
|
26
|
-
const realDur = Math.max(0, duration);
|
|
27
|
-
if (realDur <= a) {
|
|
28
|
-
const partialPeak = peak * (realDur / (a || 1));
|
|
29
|
-
gainParam.linearRampToValueAtTime(partialPeak, now + realDur * 0.5);
|
|
30
|
-
gainParam.linearRampToValueAtTime(0, now + realDur);
|
|
31
|
-
}
|
|
32
|
-
else if (realDur <= a + d) {
|
|
33
|
-
gainParam.linearRampToValueAtTime(peak, now + a);
|
|
34
|
-
gainParam.linearRampToValueAtTime(0, now + realDur);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
gainParam.linearRampToValueAtTime(peak, now + a);
|
|
38
|
-
gainParam.linearRampToValueAtTime(peak * s, now + a + d);
|
|
39
|
-
const releaseStart = Math.max(now + a + d, now + realDur - r);
|
|
40
|
-
gainParam.setValueAtTime(peak * s, releaseStart);
|
|
41
|
-
gainParam.linearRampToValueAtTime(0, now + realDur);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const hasNoteParser = typeof __ENABLE_NOTE_PARSER__ !== "undefined" ? __ENABLE_NOTE_PARSER__ : true;
|
|
45
|
-
/**
|
|
46
|
-
* Converts a musical note string (e.g., 'C4', 'F#5') to its frequency in Hz.
|
|
47
|
-
* If a number is provided, it is returned as-is.
|
|
48
|
-
*/
|
|
49
|
-
export function Note(pitch) {
|
|
50
|
-
if (typeof pitch === "number")
|
|
51
|
-
return pitch;
|
|
52
|
-
if (!hasNoteParser)
|
|
53
|
-
return 0;
|
|
54
|
-
const match = pitch.match(/^([a-gA-G])([#b]?)(\d)$/);
|
|
55
|
-
if (!match)
|
|
56
|
-
return 0;
|
|
57
|
-
const offsets = {
|
|
58
|
-
C: -9,
|
|
59
|
-
D: -7,
|
|
60
|
-
E: -5,
|
|
61
|
-
F: -4,
|
|
62
|
-
G: -2,
|
|
63
|
-
A: 0,
|
|
64
|
-
B: 2,
|
|
65
|
-
};
|
|
66
|
-
const [, note, accidental, octave] = match;
|
|
67
|
-
let semitone = offsets[note.toUpperCase()];
|
|
68
|
-
if (accidental === "#")
|
|
69
|
-
semitone++;
|
|
70
|
-
if (accidental === "b")
|
|
71
|
-
semitone--;
|
|
72
|
-
semitone += (parseInt(octave, 10) - 4) * 12;
|
|
73
|
-
return Number((440 * Math.pow(2, semitone / 12)).toFixed(2));
|
|
74
|
-
}
|
|
75
|
-
/** A fluent, chainable builder for creating RloData sequences procedurally. */
|
|
76
|
-
export class RLOSequenceBuilder {
|
|
77
|
-
constructor() {
|
|
78
|
-
this._notes = [];
|
|
79
|
-
this._duration = 0;
|
|
80
|
-
}
|
|
81
|
-
addNote(opts) {
|
|
82
|
-
const time = opts.time !== undefined ? opts.time : this._duration;
|
|
83
|
-
const freq = Note(opts.pitch);
|
|
84
|
-
const vel = opts.velocity ?? 1.0;
|
|
85
|
-
this._notes.push(freq, time, opts.duration, vel, opts.instrument);
|
|
86
|
-
this._duration = Math.max(this._duration, time + opts.duration);
|
|
87
|
-
return this;
|
|
88
|
-
}
|
|
89
|
-
setDuration(secs) {
|
|
90
|
-
this._duration = secs;
|
|
91
|
-
return this;
|
|
92
|
-
}
|
|
93
|
-
compile() {
|
|
94
|
-
return { durationSecs: this._duration, notes: this._notes };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const hasTranspiler = typeof __ENABLE_TRANSPILER__ !== "undefined" ? __ENABLE_TRANSPILER__ : true;
|
|
98
|
-
const hasMusicPlayer = typeof __ENABLE_MUSIC_PLAYER__ !== "undefined"
|
|
99
|
-
? __ENABLE_MUSIC_PLAYER__
|
|
100
|
-
: true;
|
|
101
|
-
const hasGameEngine = typeof __ENABLE_GAME_ENGINE__ !== "undefined" ? __ENABLE_GAME_ENGINE__ : true;
|
|
102
|
-
const hasWorkerMetronome = typeof __ENABLE_WORKER_METRONOME__ !== "undefined"
|
|
103
|
-
? __ENABLE_WORKER_METRONOME__
|
|
104
|
-
: false;
|
|
105
|
-
/**
|
|
106
|
-
* A "dumb" mapping mechanism for strict JS13K size-coding.
|
|
107
|
-
* Bypasses the closest-match algorithm and explicitly assigns synths to individual IDs.
|
|
108
|
-
* @param assignments An array of synthesizer modules and the exact MIDI IDs they should respond to.
|
|
109
|
-
* @returns A fully populated array of 129 elements, with unassigned slots remaining null.
|
|
110
|
-
*/
|
|
111
|
-
export function createDirectMap(assignments) {
|
|
112
|
-
const mapArray = new Array(129).fill(null);
|
|
113
|
-
assignments.forEach((a) => {
|
|
114
|
-
a.ids.forEach((id) => (mapArray[id] = a.synth));
|
|
115
|
-
});
|
|
116
|
-
return mapArray;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Mathematically maps instruments by MIDI ID, auto-falling back to the closest available synthesizer.
|
|
120
|
-
* @param modules An array of synthesizer module configurations to map.
|
|
121
|
-
* @returns A fully populated array of 129 synthesizer instruments.
|
|
122
|
-
*/
|
|
123
|
-
export function createInstrumentMap(modules) {
|
|
124
|
-
const mapArray = new Array(129).fill(null);
|
|
125
|
-
modules.forEach((m) => {
|
|
126
|
-
for (let i = m.start; i <= m.end; i++)
|
|
127
|
-
mapArray[i] = m.synth;
|
|
128
|
-
});
|
|
129
|
-
for (let i = 0; i < 129; i++) {
|
|
130
|
-
if (mapArray[i] === null) {
|
|
131
|
-
let closest = null;
|
|
132
|
-
let minDiff = Infinity;
|
|
133
|
-
for (let j = 0; j < 129; j++) {
|
|
134
|
-
if (mapArray[j] !== null) {
|
|
135
|
-
const diff = Math.abs(i - j);
|
|
136
|
-
if (diff < minDiff) {
|
|
137
|
-
minDiff = diff;
|
|
138
|
-
closest = mapArray[j];
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
mapArray[i] = closest;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return mapArray;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Creates a new instrument map by merging custom synth overrides into an existing base map.
|
|
149
|
-
* @param baseMap The base instrument map array.
|
|
150
|
-
* @param overrides An array of synthesizer module configurations to override in the base map.
|
|
151
|
-
* @returns A new array of synthesizer instruments with the overrides applied.
|
|
152
|
-
*/
|
|
153
|
-
export function extendInstrumentMap(baseMap, overrides) {
|
|
154
|
-
const newMap = [...baseMap];
|
|
155
|
-
overrides.forEach((m) => {
|
|
156
|
-
for (let i = m.start; i <= m.end; i++)
|
|
157
|
-
newMap[i] = m.synth;
|
|
158
|
-
});
|
|
159
|
-
return newMap;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* RLOCore: The bare-metal procedural sequencer.
|
|
163
|
-
* Zero networking, zero master effects. Just pure math to audio waves.
|
|
164
|
-
*/
|
|
165
|
-
export class RLOCore {
|
|
166
|
-
/**
|
|
167
|
-
* Initializes a new RLOCore instance.
|
|
168
|
-
* @param audioContext The Web Audio API context.
|
|
169
|
-
* @param instrumentMap The instrument routing map to use for synthesis.
|
|
170
|
-
*/
|
|
171
|
-
constructor(audioContext, instrumentMap = []) {
|
|
172
|
-
/** Indicates whether a sequence is currently playing. */
|
|
173
|
-
this._isPlaying = false;
|
|
174
|
-
/** Stores the timer ID for the standard JavaScript timeout metronome. */
|
|
175
|
-
this._timer = null;
|
|
176
|
-
/** Stores the Web Worker instance for accurate background metronome timing. */
|
|
177
|
-
this._workerTimer = null;
|
|
178
|
-
/** Incremental ID to keep track of the current playback sequence and prevent overlapping schedules. */
|
|
179
|
-
this._sequenceId = 0;
|
|
180
|
-
/** The current track playback time in seconds. */
|
|
181
|
-
this._trkT = 0;
|
|
182
|
-
/** The total duration of the currently playing track in seconds. */
|
|
183
|
-
this._trkD = 0; // _currentTrackDuration
|
|
184
|
-
/** Array of active audio nodes that need to be cleaned up when playback stops. */
|
|
185
|
-
this._activeNodes = [];
|
|
186
|
-
/** The current global volume level (0.0 to 1.0). */
|
|
187
|
-
this._volume = 0.5;
|
|
188
|
-
/** The target time to seek to on the next scheduling tick. */
|
|
189
|
-
this._seekTarget = null;
|
|
190
|
-
/** The playback speed multiplier (1.0 is normal speed). */
|
|
191
|
-
this.playbackRate = 1.0;
|
|
192
|
-
/** The object URL for the Web Worker script, used for cleanup. */
|
|
193
|
-
this._workerUrl = null;
|
|
194
|
-
this._ctx = audioContext;
|
|
195
|
-
this._instrumentMap = instrumentMap;
|
|
196
|
-
if (hasWorkerMetronome) {
|
|
197
|
-
const blob = new Blob([
|
|
198
|
-
`
|
|
199
|
-
let timerID = null;
|
|
200
|
-
self.onmessage = function(e) {
|
|
201
|
-
if (e.data === 'start') {
|
|
202
|
-
if (timerID) clearInterval(timerID);
|
|
203
|
-
timerID = setInterval(() => self.postMessage('tick'), 50);
|
|
204
|
-
} else if (e.data === 'stop' && timerID) {
|
|
205
|
-
clearInterval(timerID); timerID = null;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
`,
|
|
209
|
-
], { type: "application/javascript" });
|
|
210
|
-
this._workerUrl = URL.createObjectURL(blob);
|
|
211
|
-
this._workerTimer = new Worker(this._workerUrl);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
/** Gets the current time of the audio context. */
|
|
215
|
-
get _now() {
|
|
216
|
-
return this._ctx.currentTime;
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Sets the global playback volume.
|
|
220
|
-
* @param vol The volume level (0.0 to 1.0).
|
|
221
|
-
*/
|
|
222
|
-
setVolume(vol) {
|
|
223
|
-
this._volume = vol;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Seeks the currently playing track to the specified time in seconds.
|
|
227
|
-
* @param timeInSeconds The target time in seconds.
|
|
228
|
-
*/
|
|
229
|
-
seek(timeInSeconds) {
|
|
230
|
-
if (this._trkD > 0) {
|
|
231
|
-
this._seekTarget = Math.max(0, timeInSeconds) % this._trkD;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
/** Stops playback and clears all scheduled audio nodes and timers. */
|
|
235
|
-
stop() {
|
|
236
|
-
this._isPlaying = false;
|
|
237
|
-
this._sequenceId++;
|
|
238
|
-
if (this._timer)
|
|
239
|
-
clearTimeout(this._timer);
|
|
240
|
-
if (this._workerTimer)
|
|
241
|
-
this._workerTimer.postMessage("stop");
|
|
242
|
-
const nodesToClean = this._activeNodes;
|
|
243
|
-
this._activeNodes = [];
|
|
244
|
-
nodesToClean.forEach((node) => {
|
|
245
|
-
try {
|
|
246
|
-
node.disconnect();
|
|
247
|
-
}
|
|
248
|
-
catch (e) { }
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
/** Destroys the core engine, releasing memory and background workers. */
|
|
252
|
-
dispose() {
|
|
253
|
-
this.stop();
|
|
254
|
-
if (this._workerTimer) {
|
|
255
|
-
this._workerTimer.terminate();
|
|
256
|
-
this._workerTimer = null;
|
|
257
|
-
}
|
|
258
|
-
if (this._workerUrl) {
|
|
259
|
-
URL.revokeObjectURL(this._workerUrl);
|
|
260
|
-
this._workerUrl = null;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
/** Creates and returns a new GainNode. */
|
|
264
|
-
_gain() {
|
|
265
|
-
return this._ctx.createGain();
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Applies a linear fade to an AudioParam over a specified duration.
|
|
269
|
-
* @param gain The AudioParam to fade.
|
|
270
|
-
* @param target The target value.
|
|
271
|
-
* @param time The duration of the fade in seconds.
|
|
272
|
-
*/
|
|
273
|
-
_fade(gain, target, time) {
|
|
274
|
-
gain.setValueAtTime(time > 0 ? 0 : target, this._now);
|
|
275
|
-
if (time > 0)
|
|
276
|
-
gain.linearRampToValueAtTime(target, this._now + time);
|
|
277
|
-
}
|
|
278
|
-
/** Creates a pre-configured DynamicsCompressorNode for mastering. */
|
|
279
|
-
_comp(ctx = this._ctx) {
|
|
280
|
-
const c = ctx.createDynamicsCompressor();
|
|
281
|
-
c.threshold.value = -24;
|
|
282
|
-
c.knee.value = 12;
|
|
283
|
-
c.ratio.value = 8;
|
|
284
|
-
c.attack.value = 0.001;
|
|
285
|
-
c.release.value = 0.25;
|
|
286
|
-
return c;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Sets up the master audio routing graph for this core.
|
|
290
|
-
* @param fadeInTime Optional fade-in duration in seconds.
|
|
291
|
-
* @returns An object containing the destination AudioNode.
|
|
292
|
-
*/
|
|
293
|
-
_createRouting(fadeInTime = 0) {
|
|
294
|
-
const gain = this._gain();
|
|
295
|
-
this._fade(gain.gain, this._volume, fadeInTime);
|
|
296
|
-
gain.connect(this._ctx.destination);
|
|
297
|
-
this._activeNodes.push(gain);
|
|
298
|
-
return { destination: gain };
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Plays a compiled RLO sequence.
|
|
302
|
-
* @param track The compiled RloData to play.
|
|
303
|
-
* @param loopOrOpts Whether the track should loop, or an Options Object.
|
|
304
|
-
* @param fadeInTime Optional fade-in duration in seconds (default 0).
|
|
305
|
-
*/
|
|
306
|
-
playSequence(track, loopOrOpts = true, oldFadeInTime = 0) {
|
|
307
|
-
let loop = true;
|
|
308
|
-
let fadeInTime = oldFadeInTime;
|
|
309
|
-
if (typeof loopOrOpts === "object") {
|
|
310
|
-
loop = loopOrOpts.loop ?? true;
|
|
311
|
-
fadeInTime = loopOrOpts.fadeInTime ?? 0;
|
|
312
|
-
if (loopOrOpts.playbackRate !== undefined)
|
|
313
|
-
this.playbackRate = loopOrOpts.playbackRate;
|
|
314
|
-
if (loopOrOpts.volume !== undefined)
|
|
315
|
-
this.setVolume(loopOrOpts.volume);
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
loop = loopOrOpts;
|
|
319
|
-
}
|
|
320
|
-
this.stop();
|
|
321
|
-
if (this._ctx.state === "suspended")
|
|
322
|
-
this._ctx.resume();
|
|
323
|
-
this._isPlaying = true;
|
|
324
|
-
const currentSequenceId = ++this._sequenceId;
|
|
325
|
-
this._trkD = track.durationSecs;
|
|
326
|
-
const { destination } = this._createRouting(fadeInTime);
|
|
327
|
-
const synthesizer = new Synthesizer(this._ctx, destination, this._instrumentMap);
|
|
328
|
-
const lookaheadTime = 0.5;
|
|
329
|
-
this._trkT = -0.05 * this.playbackRate;
|
|
330
|
-
let lastScheduleTime = this._now;
|
|
331
|
-
let loopOffsetSecs = 0;
|
|
332
|
-
let notePtr = 0;
|
|
333
|
-
const notes = track.notes;
|
|
334
|
-
const len = notes.length;
|
|
335
|
-
const schedule = () => {
|
|
336
|
-
if (!this._isPlaying || this._sequenceId !== currentSequenceId)
|
|
337
|
-
return;
|
|
338
|
-
const currentPhysicalTime = this._now;
|
|
339
|
-
const deltaPhysical = currentPhysicalTime - lastScheduleTime;
|
|
340
|
-
lastScheduleTime = currentPhysicalTime;
|
|
341
|
-
if (this._seekTarget !== null) {
|
|
342
|
-
this._trkT = this._seekTarget;
|
|
343
|
-
loopOffsetSecs = 0;
|
|
344
|
-
notePtr = 0;
|
|
345
|
-
while (notePtr < len && notes[notePtr + 1] < this._seekTarget) {
|
|
346
|
-
notePtr += 5;
|
|
347
|
-
}
|
|
348
|
-
this._seekTarget = null;
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
this._trkT += deltaPhysical * this.playbackRate;
|
|
352
|
-
}
|
|
353
|
-
const loopDurationSecs = track.durationSecs;
|
|
354
|
-
while (notePtr < len) {
|
|
355
|
-
const freq = notes[notePtr];
|
|
356
|
-
const noteTime = notes[notePtr + 1];
|
|
357
|
-
const noteDur = notes[notePtr + 2];
|
|
358
|
-
const velocity = notes[notePtr + 3];
|
|
359
|
-
const instrumentId = notes[notePtr + 4];
|
|
360
|
-
const noteTrackTime = loopOffsetSecs + noteTime;
|
|
361
|
-
if (noteTrackTime < this._trkT + lookaheadTime * this.playbackRate) {
|
|
362
|
-
const notePhysicalTime = currentPhysicalTime +
|
|
363
|
-
Math.max(0, (noteTrackTime - this._trkT) / this.playbackRate);
|
|
364
|
-
synthesizer._playNote(instrumentId, notePhysicalTime, freq, noteDur / this.playbackRate, velocity);
|
|
365
|
-
notePtr += 5;
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (notePtr >= len) {
|
|
372
|
-
if (loop) {
|
|
373
|
-
notePtr = 0;
|
|
374
|
-
loopOffsetSecs += loopDurationSecs;
|
|
375
|
-
schedule();
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
if (this._workerTimer)
|
|
379
|
-
this._workerTimer.postMessage("stop");
|
|
380
|
-
}
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
if (!this._workerTimer) {
|
|
384
|
-
this._timer = setTimeout(schedule, 50);
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
if (this._workerTimer) {
|
|
388
|
-
this._workerTimer.onmessage = () => schedule();
|
|
389
|
-
this._workerTimer.postMessage("start");
|
|
390
|
-
}
|
|
391
|
-
schedule();
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* RLOMusicPlayer: The standard music player.
|
|
396
|
-
* Includes network fetching, gzip decoding, convolution reverb, compression, and UI playback controls.
|
|
397
|
-
*/
|
|
398
|
-
export class RLOMusicPlayer extends RLOCore {
|
|
399
|
-
/**
|
|
400
|
-
* Initializes a new RLOMusicPlayer instance with advanced effects and network capabilities.
|
|
401
|
-
* @param audioContext The Web Audio API context.
|
|
402
|
-
* @param instrumentMap Optional custom instrument map.
|
|
403
|
-
*/
|
|
404
|
-
constructor(audioContext, instrumentMap = []) {
|
|
405
|
-
super(audioContext, instrumentMap);
|
|
406
|
-
/** The master volume control node. */
|
|
407
|
-
this._masterGain = null;
|
|
408
|
-
/** The convolver node used for reverb effects. */
|
|
409
|
-
this._reverb = null;
|
|
410
|
-
/** The gain node controlling the wet/dry mix of the reverb effect. */
|
|
411
|
-
this._fxGain = null;
|
|
412
|
-
/** A cache of recently loaded tracks to prevent redundant network requests. */
|
|
413
|
-
this._trackCache = new Map();
|
|
414
|
-
/** The maximum number of tracks to store in the cache. */
|
|
415
|
-
this._maxCacheSize = 20;
|
|
416
|
-
/** The current acoustic space simulation mode. */
|
|
417
|
-
this._reverbMode = "concert";
|
|
418
|
-
if (!hasMusicPlayer)
|
|
419
|
-
return;
|
|
420
|
-
AudioEffects._generateReverb(this._ctx, "concert");
|
|
421
|
-
AudioEffects._generateReverb(this._ctx, "studio");
|
|
422
|
-
this._visibilityHandler = () => {
|
|
423
|
-
if (document.hidden) {
|
|
424
|
-
this._ctx.suspend();
|
|
425
|
-
}
|
|
426
|
-
else if (this._isPlaying) {
|
|
427
|
-
this._ctx.resume();
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
// Only pause the AudioContext on tab hide if we AREN'T explicitly running in the background
|
|
431
|
-
if (typeof document !== "undefined" && !hasWorkerMetronome) {
|
|
432
|
-
document.addEventListener("visibilitychange", this._visibilityHandler);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Changes the acoustic space simulation for the reverb effect.
|
|
437
|
-
* @param mode 'concert' (large hall) or 'studio' (small room).
|
|
438
|
-
*/
|
|
439
|
-
setReverbMode(mode) {
|
|
440
|
-
if (!hasMusicPlayer)
|
|
441
|
-
return;
|
|
442
|
-
this._reverbMode = mode;
|
|
443
|
-
if (this._reverb && this._fxGain) {
|
|
444
|
-
this._reverb.buffer = AudioEffects._generateReverb(this._ctx, mode);
|
|
445
|
-
this._fxGain.gain.setTargetAtTime(mode === "studio" ? 0.05 : 0.2, this._now, 0.1);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
/** Disposes the player, clearing caches and removing event listeners. */
|
|
449
|
-
dispose() {
|
|
450
|
-
if (!hasMusicPlayer)
|
|
451
|
-
return super.dispose();
|
|
452
|
-
super.dispose();
|
|
453
|
-
if (typeof document !== "undefined" && !hasWorkerMetronome) {
|
|
454
|
-
document.removeEventListener("visibilitychange", this._visibilityHandler);
|
|
455
|
-
}
|
|
456
|
-
this._trackCache.clear();
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Sets the playback volume, smoothly ramping to the target value.
|
|
460
|
-
* @param vol The volume level (0.0 to 1.0).
|
|
461
|
-
*/
|
|
462
|
-
setVolume(vol) {
|
|
463
|
-
super.setVolume(vol);
|
|
464
|
-
if (!hasMusicPlayer)
|
|
465
|
-
return;
|
|
466
|
-
if (this._masterGain) {
|
|
467
|
-
this._masterGain.gain.setTargetAtTime(vol, this._now, 0.1);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
/** Returns the current playback time of the track in seconds. */
|
|
471
|
-
getCurrentTime() {
|
|
472
|
-
if (!hasMusicPlayer)
|
|
473
|
-
return 0;
|
|
474
|
-
if (this._trkD === 0 || !this._isPlaying)
|
|
475
|
-
return 0;
|
|
476
|
-
return Math.max(0, this._trkT) % this._trkD;
|
|
477
|
-
}
|
|
478
|
-
/** Returns the total duration of the currently loaded track in seconds. */
|
|
479
|
-
getTotalDuration() {
|
|
480
|
-
if (!hasMusicPlayer)
|
|
481
|
-
return 0;
|
|
482
|
-
return this._trkD;
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Overrides the core routing to include reverb, filtering, and mastering compression.
|
|
486
|
-
* @param fadeInTime Optional fade-in duration in seconds.
|
|
487
|
-
* @returns An object containing the destination AudioNode.
|
|
488
|
-
*/
|
|
489
|
-
_createRouting(fadeInTime = 0) {
|
|
490
|
-
if (!hasMusicPlayer)
|
|
491
|
-
return super._createRouting(fadeInTime);
|
|
492
|
-
this._reverb = this._ctx.createConvolver();
|
|
493
|
-
this._reverb.buffer = AudioEffects._generateReverb(this._ctx, this._reverbMode);
|
|
494
|
-
this._masterGain = this._gain();
|
|
495
|
-
this._activeNodes.push(this._reverb, this._masterGain);
|
|
496
|
-
this._fade(this._masterGain.gain, this._volume, fadeInTime);
|
|
497
|
-
this._fxGain = this._gain();
|
|
498
|
-
this._fxGain.gain.value = this._reverbMode === "studio" ? 0.05 : 0.2;
|
|
499
|
-
this._activeNodes.push(this._fxGain);
|
|
500
|
-
const masterFilter = this._ctx.createBiquadFilter();
|
|
501
|
-
masterFilter.type = "lowpass";
|
|
502
|
-
masterFilter.frequency.value = Math.min(this._ctx.sampleRate / 2 - 1, 6500);
|
|
503
|
-
this._activeNodes.push(masterFilter);
|
|
504
|
-
const compressor = this._comp();
|
|
505
|
-
this._activeNodes.push(compressor);
|
|
506
|
-
this._masterGain
|
|
507
|
-
.connect(masterFilter)
|
|
508
|
-
.connect(compressor)
|
|
509
|
-
.connect(this._ctx.destination);
|
|
510
|
-
this._masterGain
|
|
511
|
-
.connect(this._reverb)
|
|
512
|
-
.connect(this._fxGain)
|
|
513
|
-
.connect(masterFilter);
|
|
514
|
-
return { destination: this._masterGain };
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Loads and plays a track from a URL, a JSON object, or an existing RloData object.
|
|
518
|
-
* @param trackUrlOrData The URL to fetch, or the raw track data.
|
|
519
|
-
* @param fadeInOrOpts Optional fade-in duration in seconds, or an Options Object.
|
|
520
|
-
* @param oldLoop Whether to loop the track (default true).
|
|
521
|
-
*/
|
|
522
|
-
async play(trackUrlOrData, fadeInOrOpts = 0, oldLoop = true) {
|
|
523
|
-
if (!hasMusicPlayer)
|
|
524
|
-
return;
|
|
525
|
-
let track;
|
|
526
|
-
let fadeInTime = 0;
|
|
527
|
-
let optionsObj = {};
|
|
528
|
-
if (typeof fadeInOrOpts === "object") {
|
|
529
|
-
fadeInTime = fadeInOrOpts.fadeInTime ?? 0;
|
|
530
|
-
optionsObj = fadeInOrOpts;
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
fadeInTime = fadeInOrOpts;
|
|
534
|
-
optionsObj = { fadeInTime, loop: oldLoop };
|
|
535
|
-
}
|
|
536
|
-
if (typeof trackUrlOrData === "string") {
|
|
537
|
-
if (this._trackCache.has(trackUrlOrData)) {
|
|
538
|
-
track = this._trackCache.get(trackUrlOrData);
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
|
-
const response = await fetch(trackUrlOrData);
|
|
542
|
-
if (!hasTranspiler || trackUrlOrData.toLowerCase().endsWith(".json")) {
|
|
543
|
-
const text = await response.text();
|
|
544
|
-
track = JSON.parse(text);
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
const rawBuffer = await response.arrayBuffer();
|
|
548
|
-
const view = new DataView(rawBuffer);
|
|
549
|
-
// Check for 'RLO' magic bytes to see if the browser natively decompressed it
|
|
550
|
-
const isDecompressed = view.byteLength >= 3 &&
|
|
551
|
-
view.getUint8(0) === 82 &&
|
|
552
|
-
view.getUint8(1) === 76 &&
|
|
553
|
-
view.getUint8(2) === 79;
|
|
554
|
-
if (isDecompressed) {
|
|
555
|
-
track = RLOTranspiler._decodeBinary(rawBuffer);
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
const ds = new window.DecompressionStream("gzip");
|
|
559
|
-
const writer = ds.writable.getWriter();
|
|
560
|
-
writer.write(rawBuffer);
|
|
561
|
-
writer.close();
|
|
562
|
-
track = RLOTranspiler._decodeBinary(await new Response(ds.readable).arrayBuffer());
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
this._trackCache.set(trackUrlOrData, track);
|
|
566
|
-
if (this._trackCache.size > this._maxCacheSize) {
|
|
567
|
-
const firstKey = this._trackCache.keys().next().value;
|
|
568
|
-
if (firstKey)
|
|
569
|
-
this._trackCache.delete(firstKey);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
track = trackUrlOrData;
|
|
575
|
-
}
|
|
576
|
-
this.playSequence(track, optionsObj);
|
|
577
|
-
}
|
|
578
|
-
/** Stops playback and gently resets the master gain nodes. */
|
|
579
|
-
stop() {
|
|
580
|
-
if (!hasMusicPlayer)
|
|
581
|
-
return super.stop();
|
|
582
|
-
if (this._masterGain) {
|
|
583
|
-
const now = this._now;
|
|
584
|
-
this._masterGain.gain.cancelScheduledValues(now);
|
|
585
|
-
this._masterGain.gain.setValueAtTime(0, now);
|
|
586
|
-
this._masterGain = null;
|
|
587
|
-
this._fxGain = null;
|
|
588
|
-
this._reverb = null;
|
|
589
|
-
}
|
|
590
|
-
super.stop();
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* RLOGameEngine: Built for instant action.
|
|
595
|
-
* Features a persistent master routing bus so SFX can play over background music
|
|
596
|
-
* without interrupting each other or causing clipping.
|
|
597
|
-
*/
|
|
598
|
-
export class RLOGameEngine extends RLOCore {
|
|
599
|
-
/**
|
|
600
|
-
* Initializes a new RLOGameEngine instance tailored for dynamic real-time audio.
|
|
601
|
-
* @param audioContext The Web Audio API context.
|
|
602
|
-
* @param instrumentMap Optional custom instrument map.
|
|
603
|
-
*/
|
|
604
|
-
constructor(audioContext, instrumentMap = []) {
|
|
605
|
-
super(audioContext, instrumentMap);
|
|
606
|
-
if (!hasGameEngine)
|
|
607
|
-
return;
|
|
608
|
-
this._masterGain = this._gain();
|
|
609
|
-
this._masterGain.gain.value = this._volume;
|
|
610
|
-
const compressor = this._comp();
|
|
611
|
-
this._masterGain.connect(compressor).connect(this._ctx.destination);
|
|
612
|
-
this._musicGain = this._gain();
|
|
613
|
-
this._musicGain.connect(this._masterGain);
|
|
614
|
-
this._sfxGain = this._gain();
|
|
615
|
-
this._sfxGain.connect(this._masterGain);
|
|
616
|
-
this._sfxSynthesizer = new Synthesizer(this._ctx, this._sfxGain, this._instrumentMap);
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Smoothly changes the master volume for all game audio.
|
|
620
|
-
* @param vol The volume level (0.0 to 1.0).
|
|
621
|
-
*/
|
|
622
|
-
setVolume(vol) {
|
|
623
|
-
super.setVolume(vol);
|
|
624
|
-
if (!hasGameEngine)
|
|
625
|
-
return;
|
|
626
|
-
this._masterGain.gain.setTargetAtTime(vol, this._now, 0.1);
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Smoothly changes the volume strictly for the background music routing bus.
|
|
630
|
-
* @param vol The volume level (0.0 to 1.0).
|
|
631
|
-
*/
|
|
632
|
-
setMusicVolume(vol) {
|
|
633
|
-
if (!hasGameEngine)
|
|
634
|
-
return;
|
|
635
|
-
this._musicGain.gain.setTargetAtTime(vol, this._now, 0.1);
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Smoothly changes the volume strictly for the sound effects routing bus.
|
|
639
|
-
* @param vol The volume level (0.0 to 1.0).
|
|
640
|
-
*/
|
|
641
|
-
setSFXVolume(vol) {
|
|
642
|
-
if (!hasGameEngine)
|
|
643
|
-
return;
|
|
644
|
-
this._sfxGain.gain.setTargetAtTime(vol, this._now, 0.1);
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Overrides the core routing to tap into the persistent master game bus.
|
|
648
|
-
* @param fadeInTime Optional fade-in duration in seconds.
|
|
649
|
-
* @returns An object containing the destination AudioNode.
|
|
650
|
-
*/
|
|
651
|
-
_createRouting(fadeInTime = 0) {
|
|
652
|
-
if (!hasGameEngine)
|
|
653
|
-
return super._createRouting(fadeInTime);
|
|
654
|
-
const musicGain = this._gain();
|
|
655
|
-
musicGain.connect(this._musicGain);
|
|
656
|
-
this._activeNodes.push(musicGain);
|
|
657
|
-
this._fade(musicGain.gain, 1, fadeInTime);
|
|
658
|
-
return { destination: musicGain };
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* Plays a fire-and-forget sound effect dynamically at runtime.
|
|
662
|
-
* @param instrumentId The General MIDI ID of the instrument to use (e.g., 128 for percussion).
|
|
663
|
-
* @param freqOrNote The frequency in Hz, or a Note String (e.g. 'E4').
|
|
664
|
-
* @param duration The duration of the sound effect in seconds.
|
|
665
|
-
* @param velocityOrOpts The velocity (0.0 to 1.0), or an Options Object.
|
|
666
|
-
* @param oldTimeOffset Optional delay in seconds before the sound effect plays.
|
|
667
|
-
*/
|
|
668
|
-
playSFX(instrumentId, freqOrNote, duration, velocityOrOpts = 1.0, oldTimeOffset = 0) {
|
|
669
|
-
if (!hasGameEngine)
|
|
670
|
-
return;
|
|
671
|
-
if (this._ctx.state === "suspended")
|
|
672
|
-
this._ctx.resume();
|
|
673
|
-
let velocity = 1.0;
|
|
674
|
-
let timeOffset = oldTimeOffset;
|
|
675
|
-
if (typeof velocityOrOpts === "object") {
|
|
676
|
-
velocity = velocityOrOpts.velocity ?? 1.0;
|
|
677
|
-
timeOffset = velocityOrOpts.timeOffset ?? 0;
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
velocity = velocityOrOpts;
|
|
681
|
-
}
|
|
682
|
-
this._sfxSynthesizer._playNote(instrumentId, this._now + timeOffset, Note(freqOrNote), duration, velocity);
|
|
683
|
-
}
|
|
684
|
-
/**
|
|
685
|
-
* Plays an entire compiled RLO sequence as a fire-and-forget sound effect.
|
|
686
|
-
* Note: This bypasses the sequencer and schedules all notes instantly. Ideal for short, complex jingles.
|
|
687
|
-
* @param track The compiled RloData to play.
|
|
688
|
-
* @param timeOffset Optional delay in seconds before the sequence starts.
|
|
689
|
-
*/
|
|
690
|
-
playSFXSequence(track, timeOffset = 0) {
|
|
691
|
-
if (!hasGameEngine)
|
|
692
|
-
return;
|
|
693
|
-
if (this._ctx.state === "suspended")
|
|
694
|
-
this._ctx.resume();
|
|
695
|
-
const start = this._now + timeOffset;
|
|
696
|
-
const n = track.notes;
|
|
697
|
-
const len = n.length;
|
|
698
|
-
for (let i = 0; i < len; i += 5) {
|
|
699
|
-
this._sfxSynthesizer._playNote(n[i + 4], start + n[i + 1], n[i], n[i + 2], n[i + 3]);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Plays a background music track, routing it through the master game bus.
|
|
704
|
-
* @param track The compiled RloData to play.
|
|
705
|
-
* @param loopOrOpts Whether the track should loop indefinitely (default true), or an Options Object.
|
|
706
|
-
* @param fadeInTime Optional fade-in duration in seconds (default 0).
|
|
707
|
-
*/
|
|
708
|
-
playMusic(track, loopOrOpts = true, oldFadeInTime = 0) {
|
|
709
|
-
if (!hasGameEngine)
|
|
710
|
-
return;
|
|
711
|
-
let opts = loopOrOpts;
|
|
712
|
-
if (typeof loopOrOpts === "object" && loopOrOpts.volume !== undefined) {
|
|
713
|
-
this.setMusicVolume(loopOrOpts.volume);
|
|
714
|
-
opts = { ...loopOrOpts };
|
|
715
|
-
delete opts.volume;
|
|
716
|
-
}
|
|
717
|
-
this.playSequence(track, opts, oldFadeInTime);
|
|
718
|
-
}
|
|
719
|
-
/** Stops the currently playing background music. SFX will continue to function. */
|
|
720
|
-
stopMusic() {
|
|
721
|
-
super.stop();
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
//# sourceMappingURL=RLO-Player.js.map
|