rlo-engine 1.0.3
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 +493 -0
- package/dist/AudioEffects.d.ts +16 -0
- package/dist/AudioEffects.d.ts.map +1 -0
- package/dist/AudioEffects.js +41 -0
- package/dist/AudioEffects.js.map +1 -0
- package/dist/Instruments/Analog/AnalogSynthBase.d.ts +22 -0
- package/dist/Instruments/Analog/AnalogSynthBase.d.ts.map +1 -0
- package/dist/Instruments/Analog/AnalogSynthBase.js +30 -0
- package/dist/Instruments/Analog/AnalogSynthBase.js.map +1 -0
- package/dist/Instruments/Analog/BassSynth.d.ts +14 -0
- package/dist/Instruments/Analog/BassSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/BassSynth.js +25 -0
- package/dist/Instruments/Analog/BassSynth.js.map +1 -0
- package/dist/Instruments/Analog/ChiptuneSynth.d.ts +14 -0
- package/dist/Instruments/Analog/ChiptuneSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/ChiptuneSynth.js +19 -0
- package/dist/Instruments/Analog/ChiptuneSynth.js.map +1 -0
- package/dist/Instruments/Analog/FMSynth.d.ts +15 -0
- package/dist/Instruments/Analog/FMSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/FMSynth.js +23 -0
- package/dist/Instruments/Analog/FMSynth.js.map +1 -0
- package/dist/Instruments/Analog/FormantSynth.d.ts +14 -0
- package/dist/Instruments/Analog/FormantSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/FormantSynth.js +26 -0
- package/dist/Instruments/Analog/FormantSynth.js.map +1 -0
- package/dist/Instruments/Analog/LeadSynth.d.ts +13 -0
- package/dist/Instruments/Analog/LeadSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/LeadSynth.js +21 -0
- package/dist/Instruments/Analog/LeadSynth.js.map +1 -0
- package/dist/Instruments/Analog/OrganSynth.d.ts +13 -0
- package/dist/Instruments/Analog/OrganSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/OrganSynth.js +24 -0
- package/dist/Instruments/Analog/OrganSynth.js.map +1 -0
- package/dist/Instruments/Analog/PadSynth.d.ts +14 -0
- package/dist/Instruments/Analog/PadSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/PadSynth.js +22 -0
- package/dist/Instruments/Analog/PadSynth.js.map +1 -0
- package/dist/Instruments/Analog/ReeseBassSynth.d.ts +14 -0
- package/dist/Instruments/Analog/ReeseBassSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/ReeseBassSynth.js +22 -0
- package/dist/Instruments/Analog/ReeseBassSynth.js.map +1 -0
- package/dist/Instruments/Analog/StringSynth.d.ts +13 -0
- package/dist/Instruments/Analog/StringSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/StringSynth.js +19 -0
- package/dist/Instruments/Analog/StringSynth.js.map +1 -0
- package/dist/Instruments/Analog/WoodwindSynth.d.ts +14 -0
- package/dist/Instruments/Analog/WoodwindSynth.d.ts.map +1 -0
- package/dist/Instruments/Analog/WoodwindSynth.js +35 -0
- package/dist/Instruments/Analog/WoodwindSynth.js.map +1 -0
- package/dist/Instruments/CoreSynthBase.d.ts +29 -0
- package/dist/Instruments/CoreSynthBase.d.ts.map +1 -0
- package/dist/Instruments/CoreSynthBase.js +91 -0
- package/dist/Instruments/CoreSynthBase.js.map +1 -0
- package/dist/Instruments/Decay/AdditiveSynth.d.ts +15 -0
- package/dist/Instruments/Decay/AdditiveSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/AdditiveSynth.js +24 -0
- package/dist/Instruments/Decay/AdditiveSynth.js.map +1 -0
- package/dist/Instruments/Decay/BrassSynth.d.ts +13 -0
- package/dist/Instruments/Decay/BrassSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/BrassSynth.js +29 -0
- package/dist/Instruments/Decay/BrassSynth.js.map +1 -0
- package/dist/Instruments/Decay/ChromaticPercussionSynth.d.ts +15 -0
- package/dist/Instruments/Decay/ChromaticPercussionSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/ChromaticPercussionSynth.js +23 -0
- package/dist/Instruments/Decay/ChromaticPercussionSynth.js.map +1 -0
- package/dist/Instruments/Decay/DecaySynthBase.d.ts +23 -0
- package/dist/Instruments/Decay/DecaySynthBase.d.ts.map +1 -0
- package/dist/Instruments/Decay/DecaySynthBase.js +35 -0
- package/dist/Instruments/Decay/DecaySynthBase.js.map +1 -0
- package/dist/Instruments/Decay/ElectricGuitarSynth.d.ts +15 -0
- package/dist/Instruments/Decay/ElectricGuitarSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/ElectricGuitarSynth.js +31 -0
- package/dist/Instruments/Decay/ElectricGuitarSynth.js.map +1 -0
- package/dist/Instruments/Decay/EthnicSynth.d.ts +15 -0
- package/dist/Instruments/Decay/EthnicSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/EthnicSynth.js +21 -0
- package/dist/Instruments/Decay/EthnicSynth.js.map +1 -0
- package/dist/Instruments/Decay/GuitarSynth.d.ts +14 -0
- package/dist/Instruments/Decay/GuitarSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/GuitarSynth.js +23 -0
- package/dist/Instruments/Decay/GuitarSynth.js.map +1 -0
- package/dist/Instruments/Decay/KarplusSynth.d.ts +10 -0
- package/dist/Instruments/Decay/KarplusSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/KarplusSynth.js +34 -0
- package/dist/Instruments/Decay/KarplusSynth.js.map +1 -0
- package/dist/Instruments/Decay/PianoSynth.d.ts +14 -0
- package/dist/Instruments/Decay/PianoSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/PianoSynth.js +32 -0
- package/dist/Instruments/Decay/PianoSynth.js.map +1 -0
- package/dist/Instruments/Decay/SlapBassSynth.d.ts +15 -0
- package/dist/Instruments/Decay/SlapBassSynth.d.ts.map +1 -0
- package/dist/Instruments/Decay/SlapBassSynth.js +28 -0
- package/dist/Instruments/Decay/SlapBassSynth.js.map +1 -0
- package/dist/Instruments/ISynthInstrument.d.ts +16 -0
- package/dist/Instruments/ISynthInstrument.d.ts.map +1 -0
- package/dist/Instruments/ISynthInstrument.js +2 -0
- package/dist/Instruments/ISynthInstrument.js.map +1 -0
- package/dist/Instruments/InstrumentFactory.d.ts +2 -0
- package/dist/Instruments/InstrumentFactory.d.ts.map +1 -0
- package/dist/Instruments/InstrumentFactory.js +4 -0
- package/dist/Instruments/InstrumentFactory.js.map +1 -0
- package/dist/Instruments/Speciality/DrumSynth.d.ts +11 -0
- package/dist/Instruments/Speciality/DrumSynth.d.ts.map +1 -0
- package/dist/Instruments/Speciality/DrumSynth.js +61 -0
- package/dist/Instruments/Speciality/DrumSynth.js.map +1 -0
- package/dist/Instruments/Speciality/SoundEffectsSynth.d.ts +12 -0
- package/dist/Instruments/Speciality/SoundEffectsSynth.d.ts.map +1 -0
- package/dist/Instruments/Speciality/SoundEffectsSynth.js +27 -0
- package/dist/Instruments/Speciality/SoundEffectsSynth.js.map +1 -0
- package/dist/Instruments/Synthesizer.d.ts +15 -0
- package/dist/Instruments/Synthesizer.d.ts.map +1 -0
- package/dist/Instruments/Synthesizer.js +27 -0
- package/dist/Instruments/Synthesizer.js.map +1 -0
- package/dist/RLO-Player.d.ts +298 -0
- package/dist/RLO-Player.d.ts.map +1 -0
- package/dist/RLO-Player.js +724 -0
- package/dist/RLO-Player.js.map +1 -0
- package/dist/RLO-Transpiler.d.ts +22 -0
- package/dist/RLO-Transpiler.d.ts.map +1 -0
- package/dist/RLO-Transpiler.js +133 -0
- package/dist/RLO-Transpiler.js.map +1 -0
- package/dist/compiler.d.ts +40 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +268 -0
- package/dist/compiler.js.map +1 -0
- package/dist/crush.d.ts +9 -0
- package/dist/crush.d.ts.map +1 -0
- package/dist/crush.js +20 -0
- package/dist/crush.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/midi-parser.d.ts +10 -0
- package/dist/midi-parser.d.ts.map +1 -0
- package/dist/midi-parser.js +130 -0
- package/dist/midi-parser.js.map +1 -0
- package/dist/rlo-engine.min.js +655 -0
- package/dist/rlo-engine.min.js.map +1 -0
- package/dist/rlo-engine.min.umd.js +2 -0
- package/dist/rlo-engine.min.umd.js.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/vite.config.d.ts +7 -0
- package/dist/vite.config.d.ts.map +1 -0
- package/dist/vite.config.js +244 -0
- package/dist/vite.config.js.map +1 -0
- package/dist/vite.config.js13k.d.ts +7 -0
- package/dist/vite.config.js13k.d.ts.map +1 -0
- package/dist/vite.config.js13k.js +72 -0
- package/dist/vite.config.js13k.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
# RLO Audio Engine
|
|
2
|
+
|
|
3
|
+
An optimized compiler and procedural Web Audio engine designed specifically for web-based games.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
### 🚀 Two Ways to Use the Engine
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Paradigm A: The NPM Module (Standard Usage)
|
|
14
|
+
|
|
15
|
+
For modern game developers who want a drop-in library that handles everything automatically.
|
|
16
|
+
|
|
17
|
+
### 1. Install the Package
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install rlo-engine
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Compile Your Audio Tracks
|
|
24
|
+
|
|
25
|
+
Installing the package automatically exposes the `rlo` CLI tool. Drop your MIDI files into a local folder and run the compiler to shrink them into `.rlo` binaries:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx rlo --in-midi ./assets/midi --out ./public/audio
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
_(Note: The compiler also accepts zip archives! Use `--in-zips ./assets/zips`)_
|
|
32
|
+
|
|
33
|
+
### 3. Initialize the Engine (Mapping Your Instruments)
|
|
34
|
+
|
|
35
|
+
Depending on your goals, you can initialize the engine in three different ways:
|
|
36
|
+
|
|
37
|
+
**Option 1: The Fallback (Zero Config, Full Bundle)**
|
|
38
|
+
If you initialize the engine without providing a list of instruments, it automatically falls back to a master list containing every default synthesizer. It plays every MIDI instrument out-of-the-box, but forces your bundler to pack every synth.
|
|
39
|
+
|
|
40
|
+
**Option 2: The Extension (Adding Experimental Synths)**
|
|
41
|
+
If you want to use the default master list, but also want to inject one of the "Disabled by default" synths (like the `ChiptuneSynth`), you can import `MasterInstrumentMap` and merge them together using `extendInstrumentMap`.
|
|
42
|
+
|
|
43
|
+
**Option 3: The Override (Minimal Footprint)**
|
|
44
|
+
To minimize footprint, you can pass a custom map using `createInstrumentMap`. This overrides the master list, ensuring _only_ the synths you explicitly import are included in your final JavaScript bundle.
|
|
45
|
+
|
|
46
|
+
**Option 4: Muting an Instrument**
|
|
47
|
+
If you want to use the default master list but completely mute a specific instrument (like the Drum Kit), you can overwrite its ID with the built-in `SilentSynth`.
|
|
48
|
+
|
|
49
|
+
#### Included Synthesizers
|
|
50
|
+
|
|
51
|
+
The engine routes MIDI instrument IDs to the following built-in synths automatically. If an exact ID isn't mapped, it mathematically routes to the closest one.
|
|
52
|
+
|
|
53
|
+
| Synthesizer | Default MIDI Range | Description |
|
|
54
|
+
| -------------------------- | ------------------- | ---------------------------------------------------------------------------------- |
|
|
55
|
+
| `PianoSynth` | 0 - 7 | Acoustic & Electric Pianos, Clavinet |
|
|
56
|
+
| `FMSynth` | 4 - 5 | **(Disabled by default)** 80s FM Electric Pianos (DX7 style) |
|
|
57
|
+
| `ChromaticPercussionSynth` | 8 - 15 | Glockenspiel, Vibraphone, Marimba, Bells |
|
|
58
|
+
| `AdditiveSynth` | 14, 98, 112 | **(Disabled by default)** Tubular Bells, Crystal, Tinkle Bell |
|
|
59
|
+
| `OrganSynth` | 16 - 23 | Hammond, Church, Reed Organs, Accordion |
|
|
60
|
+
| `GuitarSynth` | 24 - 26 | Acoustic, Nylon, Jazz Guitars |
|
|
61
|
+
| `KarplusSynth` | 24 - 25 | **(Disabled by default)** Hyper-realistic physical modeling Acoustic/Nylon Guitars |
|
|
62
|
+
| `ElectricGuitarSynth` | 27 - 31 | Clean, Overdrive, Distortion, Harmonics |
|
|
63
|
+
| `BassSynth` | 32 - 39 | Acoustic, Finger, Pick, Synth Basses |
|
|
64
|
+
| `ReeseBassSynth` | 38 - 39 | **(Disabled by default)** Synth Bass 1 & 2 |
|
|
65
|
+
| `StringSynth` | 40 - 55 | Violins, Violas, Choirs, Orchestral Harps |
|
|
66
|
+
| `FormantSynth` | 52 - 54 | **(Disabled by default)** Choir Aahs, Voice Oohs, Synth Voice |
|
|
67
|
+
| `BrassSynth` | 56 - 71 | Trumpets, Trombones, French Horns, Synth Brass |
|
|
68
|
+
| `WoodwindSynth` | 72 - 79 | Flutes, Oboes, Clarinets, Ocarinas |
|
|
69
|
+
| `ChiptuneSynth` | 80 - 82 | **(Disabled by default)** Retro 8-bit Square waves (GameBoy style) |
|
|
70
|
+
| `SlapBassSynth` | 36 - 37 | **(Disabled by default)** 80s Slap Bass (DX7/Seinfeld style) |
|
|
71
|
+
| `LeadSynth` | 83 - 87 | Modern Analog Leads, Sawtooths |
|
|
72
|
+
| `PadSynth` | 88 - 95 | Warm Pads, Sweeps, Halo, Bowed Glass |
|
|
73
|
+
| `SoundEffectsSynth` | 96 - 103, 120 - 127 | Sci-Fi, Rain, Breath, Seashore, Gunshots, Footsteps |
|
|
74
|
+
| `EthnicSynth` | 104 - 111 | Sitar, Shamisen, Koto, Kalimba |
|
|
75
|
+
| `DrumSynth` | 112 - 119, 128 | Woodblocks, Taikos, Percussion, and Channel 10 Drum Kits |
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import {
|
|
79
|
+
RLOGameEngine,
|
|
80
|
+
MasterInstrumentMap,
|
|
81
|
+
extendInstrumentMap,
|
|
82
|
+
createInstrumentMap,
|
|
83
|
+
RetroInstrumentMap,
|
|
84
|
+
PianoSynth,
|
|
85
|
+
ChiptuneSynth,
|
|
86
|
+
DrumSynth,
|
|
87
|
+
SilentSynth,
|
|
88
|
+
} from "rlo-engine";
|
|
89
|
+
|
|
90
|
+
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
91
|
+
|
|
92
|
+
// OPTION 1: The Fallback (Loads all default instruments automatically)
|
|
93
|
+
const engine1 = new RLOGameEngine(ctx);
|
|
94
|
+
|
|
95
|
+
// OPTION 2: The Extension (Keeps defaults, but injects the disabled ChiptuneSynth)
|
|
96
|
+
const extendedMap = extendInstrumentMap(MasterInstrumentMap, [
|
|
97
|
+
{ synth: new ChiptuneSynth(), start: 80, end: 82 },
|
|
98
|
+
]);
|
|
99
|
+
const engine2 = new RLOGameEngine(ctx, extendedMap);
|
|
100
|
+
|
|
101
|
+
// OPTION 3: The Override (Minimal footprint! Only loads exactly what you pass)
|
|
102
|
+
const customMap = createInstrumentMap([
|
|
103
|
+
{ synth: new PianoSynth(), start: 0, end: 7 },
|
|
104
|
+
{ synth: new ChiptuneSynth(), start: 80, end: 82 },
|
|
105
|
+
{ synth: new DrumSynth(), start: 128, end: 128 },
|
|
106
|
+
]);
|
|
107
|
+
const engine3 = new RLOGameEngine(ctx, customMap);
|
|
108
|
+
|
|
109
|
+
// OPTION 5: Pre-Packaged Maps (e.g. 8-Bit Retro)
|
|
110
|
+
// Only bundles ChiptuneSynth and DrumSynth automatically
|
|
111
|
+
const retroEngine = new RLOGameEngine(ctx, RetroInstrumentMap);
|
|
112
|
+
|
|
113
|
+
// OPTION 4: Muting (Keeps defaults, but mutes the Drum Kit at ID 128)
|
|
114
|
+
const mutedMap = extendInstrumentMap(MasterInstrumentMap, [
|
|
115
|
+
{ synth: SilentSynth, start: 128, end: 128 },
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
// RLOGameEngine expects decoded RloData objects!
|
|
119
|
+
// To load binary .rlo files, see the "Decoding .rlo Binary Files" section below.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Decoding `.rlo` Binary Files
|
|
123
|
+
|
|
124
|
+
If you are using `RLOMusicPlayer`, it handles fetching and decompression automatically via `player.play('/track.rlo')`. However, if you are using `RLOGameEngine` or `RLOCore`, you must fetch and decode the `.rlo` files yourself.
|
|
125
|
+
|
|
126
|
+
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
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
import { RLOTranspiler } from "rlo-engine";
|
|
130
|
+
|
|
131
|
+
async function loadAndPlayRLO(url, engine) {
|
|
132
|
+
const res = await fetch(url);
|
|
133
|
+
|
|
134
|
+
// 1. Unzip the file using the native Browser DecompressionStream
|
|
135
|
+
const ds = new DecompressionStream("gzip");
|
|
136
|
+
const decompressedStream = res.body.pipeThrough(ds);
|
|
137
|
+
const buffer = await new Response(decompressedStream).arrayBuffer();
|
|
138
|
+
|
|
139
|
+
// 2. Decode the binary buffer into the RloData object format
|
|
140
|
+
const trackData = RLOTranspiler._decodeBinary(buffer);
|
|
141
|
+
|
|
142
|
+
// 3. Play it!
|
|
143
|
+
engine.playMusic(trackData); // Use playSequence(trackData) if using RLOCore
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Paradigm B: The JS13k Boilerplate (High Compression)
|
|
150
|
+
|
|
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.
|
|
152
|
+
|
|
153
|
+
### 1. Clone & Install
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git clone https://github.com/yourusername/rlo-engine.git
|
|
157
|
+
cd rlo-engine
|
|
158
|
+
npm install
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 2. Local Testing (The Test Rig)
|
|
162
|
+
|
|
163
|
+
To hear your tracks, test the synthesizers, or debug sequence arrays, you can use the included `index.html` test rig.
|
|
164
|
+
|
|
165
|
+
1. Drop your `.mid` or `.zip` files into `tracks/midi/` or `tracks/zips/`.
|
|
166
|
+
2. Run `npm run start`.
|
|
167
|
+
3. A web page will open where you can play your sequences, test the Convolution Reverb, and trigger Game Engine sound effects.
|
|
168
|
+
|
|
169
|
+
### 3. Configure & Crush Your Engine
|
|
170
|
+
|
|
171
|
+
To achieve the absolute smallest file size, the JS13k build uses a dedicated entry point (`crush.ts`) to completely bypass the master instrument map.
|
|
172
|
+
|
|
173
|
+
1. Open `crush.ts` and import **only** the exact classes you need. Bake the map directly into the core wrapper so your game code stays tiny:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// crush.ts
|
|
177
|
+
import { RLOCore as BaseCore, createDirectMap } from "./RLO-Player.js";
|
|
178
|
+
import { DrumSynth } from "./Instruments/Speciality/DrumSynth.js";
|
|
179
|
+
import { PianoSynth } from "./Instruments/Decay/PianoSynth.js";
|
|
180
|
+
|
|
181
|
+
const crushMap = createDirectMap([
|
|
182
|
+
{ synth: new PianoSynth(), ids: [0] },
|
|
183
|
+
{ synth: new DrumSynth(), ids: [128] },
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
export class RLOCore extends BaseCore {
|
|
187
|
+
constructor(ctx: AudioContext, customMap?: any) {
|
|
188
|
+
super(ctx, customMap || crushMap);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- _Any synth not explicitly imported and mapped here will be removed by Rollup's tree-shaking._
|
|
194
|
+
|
|
195
|
+
#### What are the mapping parameters used for?
|
|
196
|
+
|
|
197
|
+
The `createDirectMap` function takes an array of objects that connect incoming musical notes to the correct audio synthesizer:
|
|
198
|
+
|
|
199
|
+
1. **`synth`** _(e.g., `new PianoSynth()`)_
|
|
200
|
+
This instantiates the physical "instrument" that will sit in memory waiting for notes.
|
|
201
|
+
2. **`ids`** _(e.g., `[36]` or `[0, 1, 2]`)_
|
|
202
|
+
This is an array of **General MIDI Instrument Numbers**. When you compile a MIDI file into an `.rlo` array, every note is tagged with an ID from `0` to `128`.
|
|
203
|
+
- If a note comes in tagged with ID `36`, the engine routes it to the `SlapBassSynth`.
|
|
204
|
+
- If you want a single synth to cover multiple IDs, expand the array: `ids: [0, 4, 6]`.
|
|
205
|
+
- _Note: ID `128` is specially reserved for the Drum/Percussion Kit._
|
|
206
|
+
|
|
207
|
+
3. **Crush the Engine:** Run `npm run build:crush`
|
|
208
|
+
4. Copy the minified `dist/rlo-engine.min.js` file and your `dist/tracks/` folder directly into your game.
|
|
209
|
+
|
|
210
|
+
#### Engine Feature Flags (`vite.config.ts`)
|
|
211
|
+
|
|
212
|
+
Set these `define` flags to `false` to squeeze out even more bytes via dead-code elimination:
|
|
213
|
+
|
|
214
|
+
| Flag | Description | Default |
|
|
215
|
+
| ----------------------------- | -------------------------------------------------------------------------- | ------- |
|
|
216
|
+
| `__ENABLE_TRANSPILER__` | Decodes binary `.rlo` files. Disable if you only use raw JSON arrays. | `true` |
|
|
217
|
+
| `__ENABLE_MUSIC_PLAYER__` | Includes network fetching, convolver reverb, and track caching. | `true` |
|
|
218
|
+
| `__ENABLE_GAME_ENGINE__` | Includes the dedicated SFX routing bus. | `true` |
|
|
219
|
+
| `__ENABLE_WORKER_METRONOME__` | Uses an un-throttled Web Worker for bulletproof background tab scheduling. | `false` |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## API Reference
|
|
224
|
+
|
|
225
|
+
The library exports three specialized execution cores depending on your game's needs. All constructors accept an optional `customMap` parameter. If omitted, they will safely fall back to the master list of all instruments.
|
|
226
|
+
|
|
227
|
+
### 1. `RLOGameEngine` (For Standard Games)
|
|
228
|
+
|
|
229
|
+
Built for instant action. Features a persistent master routing bus and a dynamics compressor. Sound effects are fire-and-forget and won't interrupt the background sequencer or cause clipping. Includes dedicated internal volume routing for music and SFX.
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
import { RLOGameEngine } from "rlo-engine";
|
|
233
|
+
|
|
234
|
+
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
235
|
+
const engine = new RLOGameEngine(ctx); // Omit 2nd parameter to use all instruments
|
|
236
|
+
|
|
237
|
+
// Dedicated bus volumes
|
|
238
|
+
engine.setMusicVolume(0.8);
|
|
239
|
+
engine.setSFXVolume(1.0);
|
|
240
|
+
|
|
241
|
+
engine.playMusic("/tracks/boss-theme.rlo", {
|
|
242
|
+
loop: true,
|
|
243
|
+
fadeInTime: 0.5,
|
|
244
|
+
volume: 0.9,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Fire-and-forget SFX
|
|
248
|
+
engine.playSFX(128, 60, 0.5, { velocity: 1.0 }); // Kick drum thump
|
|
249
|
+
engine.playSFX(9, "B5", 0.1, { velocity: 0.7 }); // Glockenspiel coin sound using Note Pitch Helper!
|
|
250
|
+
|
|
251
|
+
// Exact audio-thread scheduling using timeOffset (e.g. for arpeggios without setTimeout)
|
|
252
|
+
engine.playSFX(83, "A4", 0.2, { velocity: 0.7, timeOffset: 0.0 }); // Note 1 plays instantly
|
|
253
|
+
engine.playSFX(83, "C#5", 0.2, { velocity: 0.7, timeOffset: 0.1 }); // Note 2 plays 100ms later
|
|
254
|
+
engine.playSFX(83, "E5", 0.2, { velocity: 0.7, timeOffset: 0.2 }); // Note 3 plays 200ms later
|
|
255
|
+
|
|
256
|
+
// Define a short sequence (durationSecs, [freq, time, duration, velocity, instrumentId, ...])
|
|
257
|
+
const myLevelCompleteData = {
|
|
258
|
+
durationSecs: 1.0,
|
|
259
|
+
notes: [
|
|
260
|
+
440.0,
|
|
261
|
+
0.0,
|
|
262
|
+
0.2,
|
|
263
|
+
0.8,
|
|
264
|
+
83, // A4
|
|
265
|
+
554.37,
|
|
266
|
+
0.2,
|
|
267
|
+
0.2,
|
|
268
|
+
0.8,
|
|
269
|
+
83, // C#5
|
|
270
|
+
659.25,
|
|
271
|
+
0.4,
|
|
272
|
+
0.4,
|
|
273
|
+
0.8,
|
|
274
|
+
83, // E5
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Play an entire compiled RLO sequence as a fire-and-forget SFX payload!
|
|
279
|
+
engine.playSFXSequence(myLevelCompleteData);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 2. `RLOMusicPlayer` (Rich Playback)
|
|
283
|
+
|
|
284
|
+
Best for general music players. Includes network fetching, gzip decompression, convolution reverb environments, and UI playback tracking.
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
import { RLOMusicPlayer } from "rlo-engine";
|
|
288
|
+
|
|
289
|
+
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
290
|
+
const player = new RLOMusicPlayer(ctx); // Omit 2nd parameter to use all instruments
|
|
291
|
+
|
|
292
|
+
await player.play("/tracks/my-song.rlo", { fadeInTime: 0.5, loop: true });
|
|
293
|
+
player.setVolume(0.8);
|
|
294
|
+
player.setReverbMode("studio"); // 'concert' or 'studio'
|
|
295
|
+
|
|
296
|
+
console.log(`Time: ${player.getCurrentTime()} / ${player.getTotalDuration()}`);
|
|
297
|
+
player.stop();
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### 3. `RLOCore` (Micro Size)
|
|
301
|
+
|
|
302
|
+
The base procedural sequencer. No networking, no master effects. Converts 1D memory arrays to soundwaves.
|
|
303
|
+
|
|
304
|
+
```javascript
|
|
305
|
+
import { RLOCore } from "rlo-engine";
|
|
306
|
+
|
|
307
|
+
const ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
308
|
+
const core = new RLOCore(ctx); // Omit 2nd parameter to use all instruments
|
|
309
|
+
|
|
310
|
+
// RLOCore is bare-metal and bypasses the transpiler, so you must pass a raw JSON object!
|
|
311
|
+
core.playSequence(
|
|
312
|
+
{ durationSecs: 2.0, notes: [440, 0, 0.5, 1, 83] },
|
|
313
|
+
{ loop: true },
|
|
314
|
+
);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Understanding The Data Format
|
|
320
|
+
|
|
321
|
+
To achieve the smallest possible file size and zero garbage-collection overhead, sequences are flattened into a 1-dimensional numerical array. Every 5 numbers in the `notes` array represents a single musical note:
|
|
322
|
+
|
|
323
|
+
`[ frequency, time, duration, velocity, instrumentId ]`
|
|
324
|
+
|
|
325
|
+
- **`frequency`**: The pitch of the note in Hertz (e.g., `440.0` for A4).
|
|
326
|
+
- **`time`**: Start time in seconds.
|
|
327
|
+
- **`duration`**: How long the note is held in seconds.
|
|
328
|
+
- **`velocity`**: The volume/velocity (`0.0` to `1.0`).
|
|
329
|
+
- **`instrumentId`**: General MIDI instrument number (`0` to `127`), or `128` for the percussion engine.
|
|
330
|
+
|
|
331
|
+
**Bypassing the Transpiler for Instant Web Games:**
|
|
332
|
+
You can skip `.rlo` files entirely and pass compiled JSON arrays directly into the player. This is ideal for tiny HTML5 games where you want to hardcode the audio payload directly into your Javascript bundle for zero-latency execution.
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
player.play({ durationSecs: 12.5, notes: [440, 0, 1, 1.0, 1 /* ... */] });
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## How Synthesizers Work (Building Custom Instruments)
|
|
341
|
+
|
|
342
|
+
Instead of playing audio files, synthesizers in RLO generate sound waves mathematically. Every synthesizer extends the `CoreSynthBase` class (or `AnalogSynthBase` for sustained ADSR instruments) and implements the `_playNote` method.
|
|
343
|
+
|
|
344
|
+
The base class provides hyper-minified utility wrappers around the native Web Audio API (e.g., `_osc`, `_gain`, `_set`, `_lin`, `_exp`, `_on`) to keep your custom synth code tiny.
|
|
345
|
+
|
|
346
|
+
If you want a totally unique sound, you can easily create your own synth and inject it via a custom instrument map!
|
|
347
|
+
|
|
348
|
+
### Example: Custom 8-Bit Square Wave Synth
|
|
349
|
+
|
|
350
|
+
```javascript
|
|
351
|
+
import { CoreSynthBase, Osc, applyEnvelope } from "rlo-engine";
|
|
352
|
+
|
|
353
|
+
export class MyCustomSynth extends CoreSynthBase {
|
|
354
|
+
_playNote(ctx, masterGain, time, freq, duration, velocity) {
|
|
355
|
+
// 1. Create a volume node (Gain) routed to the master bus
|
|
356
|
+
const gain = this._gain(ctx, 0, masterGain);
|
|
357
|
+
|
|
358
|
+
// 2. Create a sound wave (Oscillator)
|
|
359
|
+
const osc = this._osc(ctx, Osc.Square, freq, gain);
|
|
360
|
+
|
|
361
|
+
// 3. Mathematical Volume Envelope (ADSR)
|
|
362
|
+
applyEnvelope(gain.gain, time, duration, {
|
|
363
|
+
attack: 0.05,
|
|
364
|
+
release: 0.1,
|
|
365
|
+
peak: velocity * 0.5,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// 4. Start & Stop the oscillator
|
|
369
|
+
this._on(osc, time, time + duration);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## What Happens if an Instrument is Missing? (Fallback Behavior)
|
|
377
|
+
|
|
378
|
+
Because this engine is built defensively, it handles missing synthesizers in two completely different ways depending on your build pipeline to ensure your game never crashes:
|
|
379
|
+
|
|
380
|
+
### 1. The Standard NPM Fallback (Smart Algorithm)
|
|
381
|
+
|
|
382
|
+
If you initialize the engine using `createInstrumentMap` (which the default `MasterInstrumentMap` uses), the engine runs a mathematical **"closest-match"** algorithm during setup.
|
|
383
|
+
If a MIDI file calls for a Harpsichord (ID `6`), but you didn't map a Harpsichord, the engine loops through the IDs, realizes that the `PianoSynth` (mapped to IDs `0-7`) is mathematically the closest acoustic relative, and automatically routes the Harpsichord notes to the Piano. Every single slot from 0 to 128 is guaranteed to be filled.
|
|
384
|
+
|
|
385
|
+
### 2. The JS13k Crush Fallback (Safe Silence)
|
|
386
|
+
|
|
387
|
+
If you use `createDirectMap` (used in `crush.ts`), you explicitly bypass the smart algorithm to save bytes. Slots you don't map are left as `null`. When the engine receives a note:
|
|
388
|
+
|
|
389
|
+
1. **Exact Match:** It checks for the exact ID (e.g., ID `36` for Slap Bass).
|
|
390
|
+
2. **Default Fallback:** If unmapped, it automatically falls back to whatever synthesizer is sitting at ID `0` (traditionally the Acoustic Piano).
|
|
391
|
+
3. **Safe Silence:** If ID `0` is _also_ unmapped, the engine safely ignores the note. It plays silence instead of throwing a fatal JavaScript `undefined` exception and crashing your game loop.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 🤖 Guide to Prompts (For Humans & AI)
|
|
396
|
+
|
|
397
|
+
Because RLO synthesizes sounds mathematically from raw Web Audio API oscillators, it does not behave like a standard MIDI soundfont player. Writing (or prompting an AI to write) good sequence arrays requires specific formatting and acoustic tricks.
|
|
398
|
+
|
|
399
|
+
### The "Master Prompt" (Copy & Paste to AI)
|
|
400
|
+
|
|
401
|
+
If you want another AI to successfully generate a new track for this engine on the first try, copy and paste this exact prompt to them:
|
|
402
|
+
|
|
403
|
+
```text
|
|
404
|
+
Act as an expert procedural audio sequencer. Generate a JSON sequence for the RLO engine.
|
|
405
|
+
Vibe: "[INSERT SONG OR VIBE HERE]".
|
|
406
|
+
|
|
407
|
+
Strict Format Requirements:
|
|
408
|
+
1. Output valid JSON only, using this exact structure: `{ "durationSecs": <number>, "notes": [f, t, d, v, i, ...] }`.
|
|
409
|
+
2. Every note is flattened into exactly 5 numbers: `frequency` (Hz), `time` (seconds), `duration` (seconds), `velocity` (0.0 to 1.0), and `instrumentId` (0-128).
|
|
410
|
+
3. CRITICAL: The `notes` array MUST be strictly sorted chronologically by `time`.
|
|
411
|
+
|
|
412
|
+
Acoustic & Procedural Rules:
|
|
413
|
+
- Articulation Gaps: Never let notes touch perfectly. Make `duration` slightly shorter than the time between notes (e.g., if notes are spaced by 0.25s, use duration 0.20s).
|
|
414
|
+
- Velocity Humanization: Alternate velocities slightly (e.g., 0.95, 0.70, 0.85) to create an organic groove.
|
|
415
|
+
- Electric Guitar (ID 27): To create heavy distortion, stack 2 or 3 frequencies (Root, Fifth, Octave) into power chords at the EXACT SAME `time`, at high velocity (0.9 - 1.0). The engine's compressor will squash them into natural overdrive.
|
|
416
|
+
- Drums (ID 128): 40Hz/60Hz @ 1.0 vel = Kick. 80Hz @ 1.0 vel = Snare. 100Hz+ @ 0.6 vel = Hi-Hats. Drum durations should always be 0.1s.
|
|
417
|
+
- Strings/Pads (ID 48/88): Use long, multi-second durations with lower velocities (0.3) to swell underneath melodies.
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### The "NPM Implementation Prompt" (Copy & Paste to AI)
|
|
421
|
+
|
|
422
|
+
If you want an AI to write the boilerplate initialization code for your web game using the standard NPM package, use this prompt:
|
|
423
|
+
|
|
424
|
+
```text
|
|
425
|
+
Act as an expert Web Audio game developer. I am using the `rlo-engine` NPM package.
|
|
426
|
+
Write the TypeScript/JavaScript initialization code to set up the `RLOGameEngine`.
|
|
427
|
+
|
|
428
|
+
Rules:
|
|
429
|
+
1. Import `RLOGameEngine` from `rlo-engine`.
|
|
430
|
+
2. Instantiate the engine with a standard Web AudioContext.
|
|
431
|
+
3. Do NOT pass a custom instrument map (rely on the engine's default master list).
|
|
432
|
+
4. Provide a simple example of playing a background music track (`.rlo` file) and triggering a sound effect.
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### The "Custom Instrument Map Prompt" (Copy & Paste to AI)
|
|
436
|
+
|
|
437
|
+
If you want an AI to help you override the default routing with specific synths, use this prompt:
|
|
438
|
+
|
|
439
|
+
```text
|
|
440
|
+
Act as an expert Web Audio developer using the `rlo-engine` NPM package.
|
|
441
|
+
Write the code to create a custom instrument map using `createInstrumentMap` and inject it into `RLOMusicPlayer`.
|
|
442
|
+
|
|
443
|
+
I only want to use: [INSERT DESIRED SYNTHS, e.g., Pianos and Synth Basses].
|
|
444
|
+
|
|
445
|
+
Rules:
|
|
446
|
+
1. Import the necessary synth classes (e.g., `PianoSynth`, `BassSynth`) and `createInstrumentMap`.
|
|
447
|
+
2. Map them to their correct General MIDI IDs (e.g., Piano: 0-7, Bass: 32-39).
|
|
448
|
+
3. Pass the custom map as the second argument when initializing the player.
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### The "JS13k Optimizer Prompt" (Copy & Paste to AI)
|
|
452
|
+
|
|
453
|
+
If you are using Paradigm B (the JS13k boilerplate) and want an AI to optimize your Vite build, use this prompt:
|
|
454
|
+
|
|
455
|
+
```text
|
|
456
|
+
Act as an expert JS13k optimizer. I am using the `rlo-engine` dual-build pipeline.
|
|
457
|
+
I need to update the `crush.ts` file to export only the following instruments: [INSERT DESIRED SYNTHS HERE].
|
|
458
|
+
|
|
459
|
+
Rules:
|
|
460
|
+
1. Write the exact code for `crush.ts` to import the requested synths, map them using `createDirectMap`, and export a wrapped `RLOCore` that uses this map by default.
|
|
461
|
+
2. Remind me to run `npm run build:crush` to strip the unused synths from the final bundle via dead-code elimination.
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### The "Custom Synthesizer Prompt" (Copy & Paste to AI)
|
|
465
|
+
|
|
466
|
+
If you want an AI to write a completely custom DSP synthesizer for your game, use this prompt:
|
|
467
|
+
|
|
468
|
+
```text
|
|
469
|
+
Act as an expert Web Audio DSP engineer. I am using the `rlo-engine`.
|
|
470
|
+
Write a custom synthesizer class that extends `CoreSynthBase`.
|
|
471
|
+
I want it to sound like: [INSERT DESIRED SOUND HERE, e.g., a spooky Theremin].
|
|
472
|
+
|
|
473
|
+
Rules:
|
|
474
|
+
1. Implement the `_playNote(ctx: AudioContext, masterGain: GainNode, time: number, freq: number, duration: number, velocity: number)` method.
|
|
475
|
+
2. Use the base class utilities: `this._gain`, `this._osc`, `this._set`, `this._lin`, `this._exp`, and `this._on`.
|
|
476
|
+
3. Ensure oscillators are properly scheduled to start at `time` and stop at `time + duration`.
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Credits & Licensing
|
|
482
|
+
|
|
483
|
+
The Beethoven test files included in the `beeth` zip archive are sequenced by Bernd Krueger.
|
|
484
|
+
The MIDI, audio (MP3, OGG), and video files of Bernd Krueger are licensed under the CC-BY-SA Germany License. This means that you can use and adapt the files, as long as you attribute the copyright holder:
|
|
485
|
+
|
|
486
|
+
- **Name:** Bernd Krueger
|
|
487
|
+
- **Source:** http://www.piano-midi.de
|
|
488
|
+
|
|
489
|
+
The distribution or public playback of the files is only allowed under identical license conditions. The scores are open source.
|
|
490
|
+
|
|
491
|
+
## Disclaimer
|
|
492
|
+
|
|
493
|
+
This software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ReverbMode = "concert" | "studio";
|
|
2
|
+
/**
|
|
3
|
+
* Utility class for generating programmatic audio effects.
|
|
4
|
+
*/
|
|
5
|
+
export declare class AudioEffects {
|
|
6
|
+
private static _cachedConcert;
|
|
7
|
+
private static _cachedStudio;
|
|
8
|
+
/**
|
|
9
|
+
* Generates a mathematical impulse response for a Convolution Reverb node.
|
|
10
|
+
* @param {AudioContext} ctx - The active AudioContext.
|
|
11
|
+
* @param {ReverbMode} mode - The type of acoustic space to simulate.
|
|
12
|
+
* @returns {AudioBuffer} The synthesized reverb impulse buffer.
|
|
13
|
+
*/
|
|
14
|
+
static _generateReverb(ctx: AudioContext, mode?: ReverbMode): AudioBuffer;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=AudioEffects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioEffects.d.ts","sourceRoot":"","sources":["../AudioEffects.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE9C;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,cAAc,CAA4B;IACzD,OAAO,CAAC,MAAM,CAAC,aAAa,CAA4B;IAExD;;;;;OAKG;WACW,eAAe,CAC3B,GAAG,EAAE,YAAY,EACjB,IAAI,GAAE,UAAqB,GAC1B,WAAW;CA6Bf"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility class for generating programmatic audio effects.
|
|
3
|
+
*/
|
|
4
|
+
export class AudioEffects {
|
|
5
|
+
static { this._cachedConcert = null; }
|
|
6
|
+
static { this._cachedStudio = null; }
|
|
7
|
+
/**
|
|
8
|
+
* Generates a mathematical impulse response for a Convolution Reverb node.
|
|
9
|
+
* @param {AudioContext} ctx - The active AudioContext.
|
|
10
|
+
* @param {ReverbMode} mode - The type of acoustic space to simulate.
|
|
11
|
+
* @returns {AudioBuffer} The synthesized reverb impulse buffer.
|
|
12
|
+
*/
|
|
13
|
+
static _generateReverb(ctx, mode = "studio") {
|
|
14
|
+
if (mode === "concert" && this._cachedConcert)
|
|
15
|
+
return this._cachedConcert;
|
|
16
|
+
if (mode === "studio" && this._cachedStudio)
|
|
17
|
+
return this._cachedStudio;
|
|
18
|
+
const isStudio = mode === "studio";
|
|
19
|
+
const length = ctx.sampleRate *
|
|
20
|
+
(isStudio ? 0.2 : 1.5); /** 0.2s for Studio, 1.5s for Concert */
|
|
21
|
+
const decay = isStudio ? 8 : 4; /** Exponential decay factor */
|
|
22
|
+
const impulse = ctx.createBuffer(2, length, ctx.sampleRate);
|
|
23
|
+
for (let i = 0; i < 2; i++) {
|
|
24
|
+
const channel = impulse.getChannelData(i);
|
|
25
|
+
let lastOut = 0;
|
|
26
|
+
for (let j = 0; j < length; j++) {
|
|
27
|
+
/** Simple lowpass filter to absorb harsh high frequencies in the tail */
|
|
28
|
+
const noise = Math.random() * 2 - 1;
|
|
29
|
+
lastOut = lastOut + 0.3 * (noise - lastOut);
|
|
30
|
+
/** True exponential acoustic decay multiplied by a linear fade to ensure 0 at the tail */
|
|
31
|
+
channel[j] =
|
|
32
|
+
lastOut * Math.exp(-decay * (j / length)) * (1 - j / length);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Cache the newly generated impulse
|
|
36
|
+
return isStudio
|
|
37
|
+
? (this._cachedStudio = impulse)
|
|
38
|
+
: (this._cachedConcert = impulse);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=AudioEffects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioEffects.js","sourceRoot":"","sources":["../AudioEffects.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,OAAO,YAAY;aACR,mBAAc,GAAuB,IAAI,CAAC;aAC1C,kBAAa,GAAuB,IAAI,CAAC;IAExD;;;;;OAKG;IACI,MAAM,CAAC,eAAe,CAC3B,GAAiB,EACjB,OAAmB,QAAQ;QAE3B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC,cAAc,CAAC;QAC1E,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC;QAEvE,MAAM,QAAQ,GAAG,IAAI,KAAK,QAAQ,CAAC;QACnC,MAAM,MAAM,GACV,GAAG,CAAC,UAAU;YACd,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,wCAAwC;QAClE,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,+BAA+B;QAE/D,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;YAC1C,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAChC,yEAAyE;gBACzE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;gBACpC,OAAO,GAAG,OAAO,GAAG,GAAG,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC;gBAC5C,0FAA0F;gBAC1F,OAAO,CAAC,CAAC,CAAC;oBACR,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QAED,oCAAoC;QACpC,OAAO,QAAQ;YACb,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;YAChC,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,CAAC;IACtC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CoreSynthBase } from "../CoreSynthBase.js";
|
|
2
|
+
export type AnalogCfg = {
|
|
3
|
+
v: number;
|
|
4
|
+
a: number;
|
|
5
|
+
r: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class for sustained analog synthesizers.
|
|
9
|
+
* Handles GainNode creation, Nyquist safety limits, and ADSR volume envelopes.
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class AnalogSynthBase extends CoreSynthBase {
|
|
12
|
+
protected _c: AnalogCfg;
|
|
13
|
+
_playNote(ctx: AudioContext, masterGain: GainNode, time: number, freq: number, duration: number, velocity: number): void;
|
|
14
|
+
protected _cfg(d: number): AnalogCfg;
|
|
15
|
+
/**
|
|
16
|
+
* Set up the specific oscillators and filters for this instrument.
|
|
17
|
+
* You must start() and stop() your own oscillators.
|
|
18
|
+
* @returns The final audio node in the chain to connect to the master mix. If omitted, the base envelope gain is used.
|
|
19
|
+
*/
|
|
20
|
+
protected abstract _setupSynthesis(ctx: AudioContext, gain: GainNode, time: number, freq: number, velocity: number, sustainTime: number, releaseTime: number, stopTime: number): AudioNode | void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=AnalogSynthBase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalogSynthBase.d.ts","sourceRoot":"","sources":["../../../Instruments/Analog/AnalogSynthBase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D;;;GAGG;AACH,8BAAsB,eAAgB,SAAQ,aAAa;IACzD,SAAS,CAAC,EAAE,EAAE,SAAS,CAA+B;IAE/C,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;IA+BP,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS;IAIpC;;;;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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CoreSynthBase } from "../CoreSynthBase.js";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for sustained analog synthesizers.
|
|
4
|
+
* Handles GainNode creation, Nyquist safety limits, and ADSR volume envelopes.
|
|
5
|
+
*/
|
|
6
|
+
export class AnalogSynthBase extends CoreSynthBase {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this._c = { v: 0.5, a: 0.05, r: 0.1 };
|
|
10
|
+
}
|
|
11
|
+
_playNote(ctx, masterGain, time, freq, duration, velocity) {
|
|
12
|
+
const gain = ctx.createGain();
|
|
13
|
+
const c = this._cfg(duration);
|
|
14
|
+
const peakVol = Math.max(0.001, velocity * c.v);
|
|
15
|
+
this._set(gain.gain, 0, time);
|
|
16
|
+
this._lin(gain.gain, peakVol, time + c.a);
|
|
17
|
+
const sustainTime = Math.max(c.a, duration);
|
|
18
|
+
this._set(gain.gain, peakVol, time + sustainTime);
|
|
19
|
+
this._lin(gain.gain, 0.001, time + sustainTime + c.r);
|
|
20
|
+
const stopTime = time + sustainTime + c.r;
|
|
21
|
+
const output = this._setupSynthesis(ctx, gain, time, freq, velocity, sustainTime, c.r, stopTime);
|
|
22
|
+
const finalNode = output || gain;
|
|
23
|
+
finalNode.connect(masterGain);
|
|
24
|
+
this._gc(ctx, time, stopTime + 0.1, finalNode);
|
|
25
|
+
}
|
|
26
|
+
_cfg(d) {
|
|
27
|
+
return this._c;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=AnalogSynthBase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnalogSynthBase.js","sourceRoot":"","sources":["../../../Instruments/Analog/AnalogSynthBase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIpD;;;GAGG;AACH,MAAM,OAAgB,eAAgB,SAAQ,aAAa;IAA3D;;QACY,OAAE,GAAc,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IA2DxD,CAAC;IAzDQ,SAAS,CACd,GAAiB,EACjB,UAAoB,EACpB,IAAY,EACZ,IAAY,EACZ,QAAgB,EAChB,QAAgB;QAEhB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;QAE9B,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAEhD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAE1C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAAG,IAAI,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC;QAE1C,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CACjC,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,WAAW,EACX,CAAC,CAAC,CAAC,EACH,QAAQ,CACT,CAAC;QAEF,MAAM,SAAS,GAAG,MAAM,IAAI,IAAI,CAAC;QACjC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,SAAS,CAAC,CAAC;IACjD,CAAC;IAES,IAAI,CAAC,CAAS;QACtB,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;CAiBF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DecaySynthBase } from "../Decay/DecaySynthBase.js";
|
|
2
|
+
/**
|
|
3
|
+
* Synthesizer strategy for deep Bass instruments using sawtooth waves.
|
|
4
|
+
*/
|
|
5
|
+
export declare class BassSynth extends DecaySynthBase {
|
|
6
|
+
protected _c: {
|
|
7
|
+
v: number;
|
|
8
|
+
a: number;
|
|
9
|
+
d: number;
|
|
10
|
+
m: number;
|
|
11
|
+
};
|
|
12
|
+
protected _setupSynthesis(ctx: AudioContext, masterGain: GainNode, gain: GainNode, time: number, freq: number, velocity: number, safeDuration: number, stopTime: number): AudioNode | void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=BassSynth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BassSynth.d.ts","sourceRoot":"","sources":["../../../Instruments/Analog/BassSynth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG5D;;GAEG;AACH,qBAAa,SAAU,SAAQ,cAAc;IAC3C,SAAS,CAAC,EAAE;;;;;MAAuC;IAEnD,SAAS,CAAC,eAAe,CACvB,GAAG,EAAE,YAAY,EACjB,UAAU,EAAE,QAAQ,EACpB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,SAAS,GAAG,IAAI;CA0BpB"}
|