plonkjs 0.0.0-stage → 0.2.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 +85 -2
- package/dist/analyse.d.ts +42 -0
- package/dist/analyse.d.ts.map +1 -0
- package/dist/analyse.js +140 -0
- package/dist/analyse.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/player.d.ts +89 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +325 -0
- package/dist/player.js.map +1 -0
- package/dist/song.d.ts +112 -0
- package/dist/song.d.ts.map +1 -0
- package/dist/song.js +239 -0
- package/dist/song.js.map +1 -0
- package/package.json +45 -4
package/README.md
CHANGED
|
@@ -1,3 +1,86 @@
|
|
|
1
|
-
#
|
|
1
|
+
# plonk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Game music made in code.** A song is a few hundred bytes of data, instruments and patterns of notes, synthesized
|
|
4
|
+
live with Web Audio. There are no audio files to download, the engine is about 2 kB brotli, and it works with any
|
|
5
|
+
engine (or none).
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Player, type Song } from "plonkjs";
|
|
9
|
+
|
|
10
|
+
const theme: Song = {
|
|
11
|
+
bpm: 112,
|
|
12
|
+
swing: 0.15,
|
|
13
|
+
instruments: {
|
|
14
|
+
lead: { wave: "square", decay: 0.15, sustain: 0.3, volume: 0.25, echo: 0.3 },
|
|
15
|
+
bass: { wave: "triangle", octave: -1, volume: 0.4 },
|
|
16
|
+
kick: { wave: "sine", drop: 24, decay: 0.12, sustain: 0, volume: 0.6 },
|
|
17
|
+
hat: { wave: "noise", decay: 0.03, sustain: 0, volume: 0.12, filter: { cutoff: 9000 } },
|
|
18
|
+
},
|
|
19
|
+
sections: {
|
|
20
|
+
a: {
|
|
21
|
+
lead: "C5 . E5 G5 | . E5 C5 . | D5 . F5 A5 | G5 - - .",
|
|
22
|
+
bass: "C3 . . . | C3 . G2 . | D3 . . . | G2 . . .",
|
|
23
|
+
kick: "x . . . x . . .",
|
|
24
|
+
hat: ". . x . . . x .",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
order: ["a", "a"],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const player = new Player();
|
|
31
|
+
player.play(theme, { fade: 1 });
|
|
32
|
+
player.tempo(1.2); // hurry up: moves are running out
|
|
33
|
+
player.layer("hat", 0); // drop the hats
|
|
34
|
+
player.stop(1); // fade out over a second
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Patterns
|
|
38
|
+
|
|
39
|
+
Each step is a sixteenth note. Steps are separated by spaces:
|
|
40
|
+
|
|
41
|
+
| Step | Means |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `C4`, `Eb5`, `F#3` | a note (A4 = 440 Hz) |
|
|
44
|
+
| `C4+E4+G4` | a chord |
|
|
45
|
+
| `x`, `X` | a hit (for drums); `X` is accented |
|
|
46
|
+
| `.` | silence |
|
|
47
|
+
| `-` | hold the step before |
|
|
48
|
+
| `\|` | ignored: mark bars however you like |
|
|
49
|
+
|
|
50
|
+
A shorter pattern repeats to fill its section, so `"x . . ."` is a four-on-the-floor kick under a 16-step melody.
|
|
51
|
+
|
|
52
|
+
## Instruments
|
|
53
|
+
|
|
54
|
+
All fields are optional: `wave` (`sine`, `square`, `sawtooth`, `triangle` or `noise`), envelope (`attack`, `decay`,
|
|
55
|
+
`sustain`, `release`), `volume`, `octave`, `detune` (a second oscillator, in cents, for a fatter sound), `filter`
|
|
56
|
+
(`{ cutoff, q, sweep }`, a low-pass with an optional sweep at each note: squelchy basses), `drop` (pitch falls from
|
|
57
|
+
this many semitones above: kicks and bloops), `vibrato` (cents) and `echo` (0-1, a tempo-synced echo).
|
|
58
|
+
|
|
59
|
+
## With a game engine
|
|
60
|
+
|
|
61
|
+
Pass the engine's audio context and a bus, so the music shares its volume and mute. With debuff:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const player = new Player({ context: audio.ctx, destination: audio.bus("music") });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Sounding good
|
|
68
|
+
|
|
69
|
+
- **Keys and degrees:** give a song a `key` ("A minor") and write `^1 ^3 ^5`; named chords (`Am7@3`) work too.
|
|
70
|
+
- **Variation:** `!` accents, `_` ghost notes, `?` / `?30` chances (seeded), and tracks that take turns between
|
|
71
|
+
patterns, for fills.
|
|
72
|
+
- **Space:** `reverb`, `echo` and `pan` per instrument; a master compressor and limiter so the mix never clips.
|
|
73
|
+
- **Sound effects that belong to the music:** `player.sfx(instrument, { degree, quantize: "beat" })` plays in the
|
|
74
|
+
song's key, on its beat if you like.
|
|
75
|
+
|
|
76
|
+
## Checking a mix without speakers
|
|
77
|
+
|
|
78
|
+
`await mix(song)` (in a browser) renders the song offline and reports its loudness in LUFS (ITU-R BS.1770), peaks and
|
|
79
|
+
clipping, spectrum balance, each track's level under the mix, and warnings in plain words ("hat: buried"). Aim for
|
|
80
|
+
about -16 LUFS with no warnings, then let a person listen.
|
|
81
|
+
|
|
82
|
+
## Seeing music without hearing it
|
|
83
|
+
|
|
84
|
+
`notes(song, fromStep, toStep)` returns every note in a window as data (time, instrument, frequency, duration,
|
|
85
|
+
velocity), and `player.played` lists the notes it has scheduled. Tests and agents can check a song, or that the right
|
|
86
|
+
music is playing, without speakers.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Song } from "./song.js";
|
|
2
|
+
/** Sample rate for rendering. BS.1770's K-weighting coefficients below are defined at 48 kHz. */
|
|
3
|
+
export declare const RENDER_RATE = 48000;
|
|
4
|
+
export interface Analysis {
|
|
5
|
+
/** Highest sample, in dBFS (0 = full scale). */
|
|
6
|
+
peakDb: number;
|
|
7
|
+
/** Samples at or beyond full scale. Anything above 0 means audible distortion. */
|
|
8
|
+
clipped: number;
|
|
9
|
+
/** Integrated loudness in LUFS (gated, K-weighted). Web games sit comfortably around -16. */
|
|
10
|
+
lufs: number;
|
|
11
|
+
/** Share of energy below 250 Hz, 250 Hz-4 kHz, and above 4 kHz (0-1 each, summing to 1). */
|
|
12
|
+
bands: {
|
|
13
|
+
low: number;
|
|
14
|
+
mid: number;
|
|
15
|
+
high: number;
|
|
16
|
+
};
|
|
17
|
+
/** Share of the time that's silence (below -60 dBFS). */
|
|
18
|
+
silence: number;
|
|
19
|
+
}
|
|
20
|
+
/** Integrated loudness (LUFS) of channels at `rate`: 400 ms blocks every 100 ms, absolute gate -70, relative -10. */
|
|
21
|
+
export declare function loudness(channels: Float32Array[], rate?: number): number;
|
|
22
|
+
/** Measure rendered audio: peak, clipping, loudness, spectrum balance and silence. */
|
|
23
|
+
export declare function analyse(channels: Float32Array[], rate?: number): Analysis;
|
|
24
|
+
/** Render `seconds` of a song offline (fast, silent) and return its channels. Only these instruments, if given. */
|
|
25
|
+
export declare function render(song: Song, seconds: number, only?: string[]): Promise<Float32Array[]>;
|
|
26
|
+
export interface MixReport {
|
|
27
|
+
/** The whole song. */
|
|
28
|
+
song: Analysis;
|
|
29
|
+
/** Each instrument on its own: its loudness, and how far it sits below the whole mix (LU). */
|
|
30
|
+
tracks: Record<string, {
|
|
31
|
+
lufs: number;
|
|
32
|
+
belowMix: number;
|
|
33
|
+
}>;
|
|
34
|
+
/** Plain-words problems worth fixing, empty if none. */
|
|
35
|
+
warnings: string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Render a song and each of its instruments, and say what's wrong with the mix, if anything: clipping, too loud or
|
|
39
|
+
* too quiet, a track buried under the rest, a muddy low end or harsh highs. For agents and tests that can't listen.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mix(song: Song, seconds?: number): Promise<MixReport>;
|
|
42
|
+
//# sourceMappingURL=analyse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyse.d.ts","sourceRoot":"","sources":["../src/analyse.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,iGAAiG;AACjG,eAAO,MAAM,WAAW,QAAQ,CAAC;AAEjC,MAAM,WAAW,QAAQ;IACvB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AA+CD,qHAAqH;AACrH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,SAAc,GAAG,MAAM,CAoB7E;AAED,sFAAsF;AACtF,wBAAgB,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,IAAI,SAAc,GAAG,QAAQ,CA+B9E;AAED,mHAAmH;AACnH,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAiBlG;AAED,MAAM,WAAW,SAAS;IACxB,sBAAsB;IACtB,IAAI,EAAE,QAAQ,CAAC;IACf,8FAA8F;IAC9F,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,wDAAwD;IACxD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,SAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAkBtE"}
|
package/dist/analyse.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Checking music without ears: render a song offline and measure it. Loudness follows ITU-R BS.1770 (LUFS, the
|
|
2
|
+
// measure streaming services and broadcasters use), peaks and clipping are counted, and the spectrum is split into
|
|
3
|
+
// lows, mids and highs. `analyse` is pure maths over samples, so it runs anywhere; `render` and `mix` need Web Audio's
|
|
4
|
+
// OfflineAudioContext (a browser, or a Node implementation).
|
|
5
|
+
import { Player } from "./player.js";
|
|
6
|
+
/** Sample rate for rendering. BS.1770's K-weighting coefficients below are defined at 48 kHz. */
|
|
7
|
+
export const RENDER_RATE = 48000;
|
|
8
|
+
const db = (x) => (x > 0 ? 20 * Math.log10(x) : -Infinity);
|
|
9
|
+
/** A biquad filter over a whole signal (direct form I). */
|
|
10
|
+
function biquad(x, b0, b1, b2, a1, a2) {
|
|
11
|
+
const y = new Float32Array(x.length);
|
|
12
|
+
let x1 = 0, x2 = 0, y1 = 0, y2 = 0;
|
|
13
|
+
for (let i = 0; i < x.length; i++) {
|
|
14
|
+
const v = b0 * x[i] + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
|
|
15
|
+
x2 = x1;
|
|
16
|
+
x1 = x[i];
|
|
17
|
+
y2 = y1;
|
|
18
|
+
y1 = v;
|
|
19
|
+
y[i] = v;
|
|
20
|
+
}
|
|
21
|
+
return y;
|
|
22
|
+
}
|
|
23
|
+
/** BS.1770 K-weighting at 48 kHz: a high shelf (the head's effect), then a high-pass. */
|
|
24
|
+
const kWeight = (x) => biquad(biquad(x, 1.53512485958697, -2.69169618940638, 1.19839281085285, -1.69065929318241, 0.73248077421585), 1, -2, 1, -1.99004745483398, 0.99007225036621);
|
|
25
|
+
/** A Butterworth biquad (RBJ cookbook), low- or high-pass; applied twice it's a steep 4th-order split. */
|
|
26
|
+
function butter(x, cutoff, rate, type) {
|
|
27
|
+
const w = (2 * Math.PI * cutoff) / rate, cos = Math.cos(w), alpha = Math.sin(w) / Math.SQRT2;
|
|
28
|
+
const a0 = 1 + alpha;
|
|
29
|
+
const b1 = (type === "low" ? 1 - cos : -(1 + cos)) / a0;
|
|
30
|
+
const b0 = (type === "low" ? (1 - cos) / 2 : (1 + cos) / 2) / a0;
|
|
31
|
+
const once = (v) => biquad(v, b0, b1, b0, (-2 * cos) / a0, (1 - alpha) / a0);
|
|
32
|
+
return once(once(x));
|
|
33
|
+
}
|
|
34
|
+
const energy = (x) => x.reduce((s, v) => s + v * v, 0);
|
|
35
|
+
/** Integrated loudness (LUFS) of channels at `rate`: 400 ms blocks every 100 ms, absolute gate -70, relative -10. */
|
|
36
|
+
export function loudness(channels, rate = RENDER_RATE) {
|
|
37
|
+
const weighted = channels.map(kWeight);
|
|
38
|
+
const block = Math.round(rate * 0.4), hop = Math.round(rate * 0.1);
|
|
39
|
+
const blocks = [];
|
|
40
|
+
for (let start = 0; start + block <= (weighted[0]?.length ?? 0); start += hop) {
|
|
41
|
+
let sum = 0;
|
|
42
|
+
for (const ch of weighted) {
|
|
43
|
+
let s = 0;
|
|
44
|
+
for (let i = start; i < start + block; i++)
|
|
45
|
+
s += ch[i] * ch[i];
|
|
46
|
+
sum += s / block;
|
|
47
|
+
}
|
|
48
|
+
blocks.push(sum);
|
|
49
|
+
}
|
|
50
|
+
const lufsOf = (ms) => -0.691 + 10 * Math.log10(ms);
|
|
51
|
+
const loud = blocks.filter((ms) => ms > 0 && lufsOf(ms) > -70);
|
|
52
|
+
if (!loud.length)
|
|
53
|
+
return -Infinity;
|
|
54
|
+
const relative = lufsOf(loud.reduce((a, b) => a + b, 0) / loud.length) - 10;
|
|
55
|
+
const gated = loud.filter((ms) => lufsOf(ms) > relative);
|
|
56
|
+
return lufsOf(gated.reduce((a, b) => a + b, 0) / gated.length);
|
|
57
|
+
}
|
|
58
|
+
/** Measure rendered audio: peak, clipping, loudness, spectrum balance and silence. */
|
|
59
|
+
export function analyse(channels, rate = RENDER_RATE) {
|
|
60
|
+
let peak = 0, clipped = 0, quiet = 0;
|
|
61
|
+
const floor = 10 ** (-60 / 20);
|
|
62
|
+
const n = channels[0]?.length ?? 0;
|
|
63
|
+
for (let i = 0; i < n; i++) {
|
|
64
|
+
let loudest = 0;
|
|
65
|
+
for (const ch of channels) {
|
|
66
|
+
const v = Math.abs(ch[i]);
|
|
67
|
+
if (v >= 1)
|
|
68
|
+
clipped++;
|
|
69
|
+
loudest = Math.max(loudest, v);
|
|
70
|
+
}
|
|
71
|
+
peak = Math.max(peak, loudest);
|
|
72
|
+
if (loudest < floor)
|
|
73
|
+
quiet++;
|
|
74
|
+
}
|
|
75
|
+
// Mono sum for the spectrum split.
|
|
76
|
+
const mono = new Float32Array(n);
|
|
77
|
+
for (const ch of channels)
|
|
78
|
+
for (let i = 0; i < n; i++)
|
|
79
|
+
mono[i] += ch[i] / channels.length;
|
|
80
|
+
const total = energy(mono) || 1;
|
|
81
|
+
const low = energy(butter(mono, 250, rate, "low"));
|
|
82
|
+
const high = energy(butter(mono, 4000, rate, "high"));
|
|
83
|
+
const mid = Math.max(0, total - low - high);
|
|
84
|
+
const sum = low + mid + high || 1;
|
|
85
|
+
return {
|
|
86
|
+
peakDb: db(peak),
|
|
87
|
+
clipped,
|
|
88
|
+
lufs: loudness(channels, rate),
|
|
89
|
+
bands: { low: low / sum, mid: mid / sum, high: high / sum },
|
|
90
|
+
silence: n ? quiet / n : 1,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Render `seconds` of a song offline (fast, silent) and return its channels. Only these instruments, if given. */
|
|
94
|
+
export async function render(song, seconds, only) {
|
|
95
|
+
const ctx = new OfflineAudioContext(2, Math.ceil(seconds * RENDER_RATE), RENDER_RATE);
|
|
96
|
+
const solo = only
|
|
97
|
+
? {
|
|
98
|
+
...song,
|
|
99
|
+
sections: Object.fromEntries(Object.entries(song.sections).map(([name, tracks]) => [
|
|
100
|
+
name,
|
|
101
|
+
Object.fromEntries(Object.entries(tracks).filter(([inst]) => only.includes(inst))),
|
|
102
|
+
])),
|
|
103
|
+
}
|
|
104
|
+
: song;
|
|
105
|
+
const player = new Player({ context: ctx });
|
|
106
|
+
player.prerender(solo, seconds);
|
|
107
|
+
const buf = await ctx.startRendering();
|
|
108
|
+
return Array.from({ length: buf.numberOfChannels }, (_, c) => buf.getChannelData(c));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Render a song and each of its instruments, and say what's wrong with the mix, if anything: clipping, too loud or
|
|
112
|
+
* too quiet, a track buried under the rest, a muddy low end or harsh highs. For agents and tests that can't listen.
|
|
113
|
+
*/
|
|
114
|
+
export async function mix(song, seconds = 20) {
|
|
115
|
+
const whole = analyse(await render(song, seconds));
|
|
116
|
+
const tracks = {};
|
|
117
|
+
for (const inst of Object.keys(song.instruments)) {
|
|
118
|
+
const l = loudness(await render(song, seconds, [inst]));
|
|
119
|
+
tracks[inst] = { lufs: l, belowMix: whole.lufs - l };
|
|
120
|
+
}
|
|
121
|
+
const warnings = [];
|
|
122
|
+
if (whole.clipped > 0)
|
|
123
|
+
warnings.push(`clipping: ${whole.clipped} samples at full scale; lower the volumes`);
|
|
124
|
+
if (whole.lufs > -10)
|
|
125
|
+
warnings.push(`loud: ${whole.lufs.toFixed(1)} LUFS; aim for about -16 so sound effects sit on top`);
|
|
126
|
+
if (whole.lufs < -24)
|
|
127
|
+
warnings.push(`quiet: ${whole.lufs.toFixed(1)} LUFS; aim for about -16`);
|
|
128
|
+
for (const [inst, t] of Object.entries(tracks)) {
|
|
129
|
+
if (!Number.isFinite(t.lufs))
|
|
130
|
+
warnings.push(`${inst}: silent; does any section play it?`);
|
|
131
|
+
else if (t.belowMix > 20)
|
|
132
|
+
warnings.push(`${inst}: buried (${t.belowMix.toFixed(0)} LU under the mix); raise its volume`);
|
|
133
|
+
}
|
|
134
|
+
if (whole.bands.low > 0.7)
|
|
135
|
+
warnings.push(`muddy: ${Math.round(whole.bands.low * 100)}% of the energy is under 250 Hz`);
|
|
136
|
+
if (whole.bands.high > 0.25)
|
|
137
|
+
warnings.push(`harsh: ${Math.round(whole.bands.high * 100)}% of the energy is over 4 kHz`);
|
|
138
|
+
return { song: whole, tracks, warnings };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=analyse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyse.js","sourceRoot":"","sources":["../src/analyse.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { type Analysis, analyse, loudness, type MixReport, mix, RENDER_RATE, render } from "./analyse.js";
|
|
2
|
+
export { Player, type PlayerOptions, type PlayOptions, type SfxOptions } from "./player.js";
|
|
3
|
+
export { chord, degree, frequency, type Instrument, type NoteEvent, notes, parse, parseKey, SCALES, type Song, type Step, sectionSteps, songSteps, stepSeconds, type Track, } from "./song.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,SAAS,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EACL,KAAK,EACL,MAAM,EACN,SAAS,EACT,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,EACL,KAAK,EACL,QAAQ,EACR,MAAM,EACN,KAAK,IAAI,EACT,KAAK,IAAI,EACT,YAAY,EACZ,SAAS,EACT,WAAW,EACX,KAAK,KAAK,GACX,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// plonk: game music made in code. Songs are small pieces of data (instruments and patterns of notes), synthesized live
|
|
2
|
+
// with Web Audio: no audio files to download, tiny, and easy to tweak or make react to the game.
|
|
3
|
+
//
|
|
4
|
+
// import { Player, type Song } from "plonkjs";
|
|
5
|
+
// const player = new Player();
|
|
6
|
+
// player.play(theme);
|
|
7
|
+
export { analyse, loudness, mix, RENDER_RATE, render } from "./analyse.js";
|
|
8
|
+
export { Player } from "./player.js";
|
|
9
|
+
export { chord, degree, frequency, notes, parse, parseKey, SCALES, sectionSteps, songSteps, stepSeconds, } from "./song.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uHAAuH;AACvH,iGAAiG;AACjG,EAAE;AACF,iDAAiD;AACjD,iCAAiC;AACjC,wBAAwB;AAExB,OAAO,EAAiB,OAAO,EAAE,QAAQ,EAAkB,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1G,OAAO,EAAE,MAAM,EAAyD,MAAM,aAAa,CAAC;AAC5F,OAAO,EACL,KAAK,EACL,MAAM,EACN,SAAS,EAGT,KAAK,EACL,KAAK,EACL,QAAQ,EACR,MAAM,EAGN,YAAY,EACZ,SAAS,EACT,WAAW,GAEZ,MAAM,WAAW,CAAC"}
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type Instrument, type NoteEvent, type Song } from "./song.js";
|
|
2
|
+
export interface PlayerOptions {
|
|
3
|
+
/** The audio context to use. Default: a new one. */
|
|
4
|
+
context?: AudioContext;
|
|
5
|
+
/** Where everything goes (a game's music bus, say). Default: the context's speakers. */
|
|
6
|
+
destination?: AudioNode;
|
|
7
|
+
/** Glue the mix with a compressor and stop it clipping with a limiter. Default true. */
|
|
8
|
+
master?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface PlayOptions {
|
|
11
|
+
/** Fade in over this many seconds. */
|
|
12
|
+
fade?: number;
|
|
13
|
+
/** Start from this step (sixteenth notes) instead of the top. */
|
|
14
|
+
at?: number;
|
|
15
|
+
}
|
|
16
|
+
/** A one-shot sound effect from sfx(). */
|
|
17
|
+
export interface SfxOptions {
|
|
18
|
+
/** Scale degree in the song's key (1 = the root, 8 = an octave up). The usual way to stay in tune with the music. */
|
|
19
|
+
degree?: number;
|
|
20
|
+
/** Octave for `degree` (default 4), or for nothing else. */
|
|
21
|
+
octave?: number;
|
|
22
|
+
/** Or a note name ("C5"), a chord name ("Am7@4"), or a frequency in Hz. */
|
|
23
|
+
note?: string;
|
|
24
|
+
freq?: number;
|
|
25
|
+
/** Wait for the next step, beat or bar of the music, so the sound lands in time. Default: play now. */
|
|
26
|
+
quantize?: "step" | "beat" | "bar";
|
|
27
|
+
/** Then wait this many steps more (sixteenth notes of the song, or of 120 bpm with none playing): for jingles. */
|
|
28
|
+
after?: number;
|
|
29
|
+
/** 0-1. Default 0.8. */
|
|
30
|
+
velocity?: number;
|
|
31
|
+
/** Seconds. Default: the instrument's attack plus decay. */
|
|
32
|
+
length?: number;
|
|
33
|
+
}
|
|
34
|
+
export declare class Player {
|
|
35
|
+
readonly context: AudioContext;
|
|
36
|
+
/** The music bus: play() fades it in, stop() fades it out. */
|
|
37
|
+
private readonly out;
|
|
38
|
+
private readonly master;
|
|
39
|
+
private readonly echoIn;
|
|
40
|
+
private readonly reverbIn;
|
|
41
|
+
private readonly delay;
|
|
42
|
+
private noiseBuffer;
|
|
43
|
+
private song;
|
|
44
|
+
/** Audio-clock time of step 0 of the current song, adjusted when the tempo changes. */
|
|
45
|
+
private origin;
|
|
46
|
+
/** Next step to schedule. */
|
|
47
|
+
private nextStep;
|
|
48
|
+
private timer;
|
|
49
|
+
private tempoScale;
|
|
50
|
+
private readonly layers;
|
|
51
|
+
/** Every note scheduled (music and sound effects), for tests and agents: the most recent 256. */
|
|
52
|
+
readonly played: NoteEvent[];
|
|
53
|
+
constructor(options?: PlayerOptions);
|
|
54
|
+
/** Is a song playing? */
|
|
55
|
+
get playing(): boolean;
|
|
56
|
+
/** Play a song from the top (or `at` a step), looping. Replaces whatever was playing. */
|
|
57
|
+
play(song: Song, options?: PlayOptions): void;
|
|
58
|
+
/**
|
|
59
|
+
* @internal For offline rendering (see `render`): schedule `seconds` of a song from time 0 in one go, since an
|
|
60
|
+
* offline context's clock doesn't run until it renders.
|
|
61
|
+
*/
|
|
62
|
+
prerender(song: Song, seconds: number): void;
|
|
63
|
+
/** Stop, fading out over `fade` seconds (default: at once). */
|
|
64
|
+
stop(fade?: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Speed up or slow down (1 = the song's own bpm), without a jump: the next notes simply come sooner or later. For
|
|
67
|
+
* music that tightens as the moves run out, say.
|
|
68
|
+
*/
|
|
69
|
+
tempo(scale: number): void;
|
|
70
|
+
/** Fade one instrument's track in or out (0-1) over `fade` seconds: layers that come and go with the game. */
|
|
71
|
+
layer(instrument: string, volume: number, fade?: number): void;
|
|
72
|
+
/**
|
|
73
|
+
* Play a one-shot sound effect with an instrument, in the music's key and, if asked, on its beat. A pop on scale
|
|
74
|
+
* degree 1, then 2, 3... for each step of a combo climbs the song's scale instead of clashing with it. Returns when
|
|
75
|
+
* (on the audio clock) it plays.
|
|
76
|
+
*/
|
|
77
|
+
sfx(instrument: Instrument, options?: SfxOptions): number;
|
|
78
|
+
private layerGain;
|
|
79
|
+
private record;
|
|
80
|
+
/** Schedule every note due before the lookahead runs out. */
|
|
81
|
+
private schedule;
|
|
82
|
+
/** A seeded noise source, shared by every drum. */
|
|
83
|
+
private noise;
|
|
84
|
+
/** A reverb impulse: stereo noise fading out over `seconds`, seeded so the room sounds the same every time. */
|
|
85
|
+
private impulse;
|
|
86
|
+
/** Build and start one note, into `into`. */
|
|
87
|
+
private voice;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"AAOA,OAAO,EAA4B,KAAK,UAAU,EAAE,KAAK,SAAS,EAAS,KAAK,IAAI,EAAe,MAAM,WAAW,CAAC;AAErH,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,wFAAwF;IACxF,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,wFAAwF;IACxF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,0CAA0C;AAC1C,MAAM,WAAW,UAAU;IACzB,qHAAqH;IACrH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uGAAuG;IACvG,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACnC,kHAAkH;IAClH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,qBAAa,MAAM;IACjB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAW;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,IAAI,CAAmB;IAC/B,uFAAuF;IACvF,OAAO,CAAC,MAAM,CAAK;IACnB,6BAA6B;IAC7B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,iGAAiG;IACjG,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,CAAM;IAElC,YAAY,OAAO,GAAE,aAAkB,EAyCtC;IAED,yBAAyB;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,yFAAyF;IACzF,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI,CAgBhD;IAED;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAa3C;IAED,+DAA+D;IAC/D,IAAI,CAAC,IAAI,SAAI,GAAG,IAAI,CAWnB;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAQzB;IAED,8GAA8G;IAC9G,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,IAAI,CAM1D;IAED;;;;OAIG;IACH,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,GAAE,UAAe,GAAG,MAAM,CAwB5D;IAED,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,MAAM;IAKd,6DAA6D;IAC7D,OAAO,CAAC,QAAQ;IAoBhB,mDAAmD;IACnD,OAAO,CAAC,KAAK;IASb,+GAA+G;IAC/G,OAAO,CAAC,OAAO;IAQf,6CAA6C;IAC7C,OAAO,CAAC,KAAK;CA+Ed"}
|
package/dist/player.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// Plays songs through Web Audio. Notes are scheduled a little ahead on the audio clock (not on frames), so timing
|
|
2
|
+
// stays tight even when the game drops a frame or the tab is busy.
|
|
3
|
+
//
|
|
4
|
+
// The signal path: every voice -> its track's layer gain -> the music bus (faded by play/stop) -> the master bus.
|
|
5
|
+
// Sound effects from sfx() skip the music bus, so they play even when the music is faded out. The echo and reverb
|
|
6
|
+
// are shared sends. The master bus glues everything with a gentle compressor, then a limiter so it never clips.
|
|
7
|
+
import { chord, degree, frequency, notes, stepSeconds } from "./song.js";
|
|
8
|
+
const LOOKAHEAD = 0.15; // seconds of notes scheduled ahead
|
|
9
|
+
const TICK_MS = 40; // how often the scheduler wakes up
|
|
10
|
+
export class Player {
|
|
11
|
+
context;
|
|
12
|
+
/** The music bus: play() fades it in, stop() fades it out. */
|
|
13
|
+
out;
|
|
14
|
+
master;
|
|
15
|
+
echoIn;
|
|
16
|
+
reverbIn;
|
|
17
|
+
delay;
|
|
18
|
+
noiseBuffer;
|
|
19
|
+
song;
|
|
20
|
+
/** Audio-clock time of step 0 of the current song, adjusted when the tempo changes. */
|
|
21
|
+
origin = 0;
|
|
22
|
+
/** Next step to schedule. */
|
|
23
|
+
nextStep = 0;
|
|
24
|
+
timer;
|
|
25
|
+
tempoScale = 1;
|
|
26
|
+
layers = new Map();
|
|
27
|
+
/** Every note scheduled (music and sound effects), for tests and agents: the most recent 256. */
|
|
28
|
+
played = [];
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.context = options.context ?? new AudioContext();
|
|
31
|
+
const ctx = this.context;
|
|
32
|
+
const destination = options.destination ?? ctx.destination;
|
|
33
|
+
// Master: a gentle compressor to glue the mix, then a limiter so it can't clip.
|
|
34
|
+
if (options.master === false)
|
|
35
|
+
this.master = destination;
|
|
36
|
+
else {
|
|
37
|
+
const glue = ctx.createDynamicsCompressor();
|
|
38
|
+
glue.threshold.value = -18;
|
|
39
|
+
glue.knee.value = 12;
|
|
40
|
+
glue.ratio.value = 3;
|
|
41
|
+
glue.attack.value = 0.01;
|
|
42
|
+
glue.release.value = 0.25;
|
|
43
|
+
const limit = ctx.createDynamicsCompressor();
|
|
44
|
+
limit.threshold.value = -2;
|
|
45
|
+
limit.knee.value = 0;
|
|
46
|
+
limit.ratio.value = 20;
|
|
47
|
+
limit.attack.value = 0.002;
|
|
48
|
+
limit.release.value = 0.1;
|
|
49
|
+
glue.connect(limit).connect(destination);
|
|
50
|
+
this.master = glue;
|
|
51
|
+
}
|
|
52
|
+
this.out = ctx.createGain();
|
|
53
|
+
this.out.connect(this.master);
|
|
54
|
+
// A soft echo, shared by every instrument that asks for one.
|
|
55
|
+
this.echoIn = ctx.createGain();
|
|
56
|
+
this.delay = ctx.createDelay(1);
|
|
57
|
+
const feedback = ctx.createGain(), echoWet = ctx.createGain();
|
|
58
|
+
feedback.gain.value = 0.35;
|
|
59
|
+
echoWet.gain.value = 0.5;
|
|
60
|
+
this.echoIn.connect(this.delay);
|
|
61
|
+
this.delay.connect(feedback).connect(this.delay);
|
|
62
|
+
this.delay.connect(echoWet).connect(this.master);
|
|
63
|
+
// A small room: a seeded, decaying noise impulse.
|
|
64
|
+
this.reverbIn = ctx.createGain();
|
|
65
|
+
const room = ctx.createConvolver();
|
|
66
|
+
room.buffer = this.impulse(1.6);
|
|
67
|
+
const roomWet = ctx.createGain();
|
|
68
|
+
roomWet.gain.value = 0.6;
|
|
69
|
+
this.reverbIn.connect(room).connect(roomWet).connect(this.master);
|
|
70
|
+
}
|
|
71
|
+
/** Is a song playing? */
|
|
72
|
+
get playing() {
|
|
73
|
+
return this.timer !== undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Play a song from the top (or `at` a step), looping. Replaces whatever was playing. */
|
|
76
|
+
play(song, options = {}) {
|
|
77
|
+
this.stop();
|
|
78
|
+
void this.context.resume();
|
|
79
|
+
this.song = song;
|
|
80
|
+
this.tempoScale = 1;
|
|
81
|
+
// Echo in time with the music: a dotted eighth.
|
|
82
|
+
this.delay.delayTime.value = Math.min(1, stepSeconds(song.bpm) * 3);
|
|
83
|
+
const now = this.context.currentTime + 0.05;
|
|
84
|
+
this.nextStep = options.at ?? 0;
|
|
85
|
+
this.origin = now - this.nextStep * stepSeconds(song.bpm);
|
|
86
|
+
const g = this.out.gain;
|
|
87
|
+
g.cancelScheduledValues(now);
|
|
88
|
+
g.setValueAtTime(options.fade ? 0.0001 : 1, now);
|
|
89
|
+
if (options.fade)
|
|
90
|
+
g.exponentialRampToValueAtTime(1, now + options.fade);
|
|
91
|
+
this.timer = setInterval(() => this.schedule(), TICK_MS);
|
|
92
|
+
this.schedule();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* @internal For offline rendering (see `render`): schedule `seconds` of a song from time 0 in one go, since an
|
|
96
|
+
* offline context's clock doesn't run until it renders.
|
|
97
|
+
*/
|
|
98
|
+
prerender(song, seconds) {
|
|
99
|
+
this.song = song;
|
|
100
|
+
this.tempoScale = 1;
|
|
101
|
+
this.delay.delayTime.value = Math.min(1, stepSeconds(song.bpm) * 3);
|
|
102
|
+
this.origin = 0.01;
|
|
103
|
+
this.out.gain.setValueAtTime(1, 0);
|
|
104
|
+
const last = Math.ceil(seconds / stepSeconds(song.bpm)) + 1;
|
|
105
|
+
for (const n of notes(song, 0, last)) {
|
|
106
|
+
const instrument = song.instruments[n.instrument];
|
|
107
|
+
if (!instrument || n.time >= seconds)
|
|
108
|
+
continue;
|
|
109
|
+
this.voice(instrument, this.layerGain(n.instrument), n.freq, this.origin + n.time, n.duration, n.velocity);
|
|
110
|
+
this.record(n);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Stop, fading out over `fade` seconds (default: at once). */
|
|
114
|
+
stop(fade = 0) {
|
|
115
|
+
if (this.timer !== undefined)
|
|
116
|
+
clearInterval(this.timer);
|
|
117
|
+
this.timer = undefined;
|
|
118
|
+
const now = this.context.currentTime, g = this.out.gain;
|
|
119
|
+
g.cancelScheduledValues(now);
|
|
120
|
+
g.setValueAtTime(g.value || 0.0001, now);
|
|
121
|
+
if (fade > 0)
|
|
122
|
+
g.exponentialRampToValueAtTime(0.0001, now + fade);
|
|
123
|
+
else
|
|
124
|
+
g.setValueAtTime(0.0001, now);
|
|
125
|
+
for (const layer of this.layers.values())
|
|
126
|
+
layer.disconnect();
|
|
127
|
+
this.layers.clear();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Speed up or slow down (1 = the song's own bpm), without a jump: the next notes simply come sooner or later. For
|
|
131
|
+
* music that tightens as the moves run out, say.
|
|
132
|
+
*/
|
|
133
|
+
tempo(scale) {
|
|
134
|
+
if (!this.song || scale <= 0)
|
|
135
|
+
return;
|
|
136
|
+
const now = this.context.currentTime;
|
|
137
|
+
const oldStep = stepSeconds(this.song.bpm) / this.tempoScale, newStep = stepSeconds(this.song.bpm) / scale;
|
|
138
|
+
const at = (now - this.origin) / oldStep; // where we are, in steps
|
|
139
|
+
this.origin = now - at * newStep;
|
|
140
|
+
this.tempoScale = scale;
|
|
141
|
+
}
|
|
142
|
+
/** Fade one instrument's track in or out (0-1) over `fade` seconds: layers that come and go with the game. */
|
|
143
|
+
layer(instrument, volume, fade = 0.5) {
|
|
144
|
+
const g = this.layerGain(instrument).gain, now = this.context.currentTime;
|
|
145
|
+
g.cancelScheduledValues(now);
|
|
146
|
+
g.setValueAtTime(Math.max(0.0001, g.value), now);
|
|
147
|
+
g.exponentialRampToValueAtTime(Math.max(0.0001, volume), now + Math.max(0.01, fade));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Play a one-shot sound effect with an instrument, in the music's key and, if asked, on its beat. A pop on scale
|
|
151
|
+
* degree 1, then 2, 3... for each step of a combo climbs the song's scale instead of clashing with it. Returns when
|
|
152
|
+
* (on the audio clock) it plays.
|
|
153
|
+
*/
|
|
154
|
+
sfx(instrument, options = {}) {
|
|
155
|
+
const ctx = this.context, now = ctx.currentTime;
|
|
156
|
+
let when = now;
|
|
157
|
+
if (options.quantize && this.song && this.playing) {
|
|
158
|
+
const step = stepSeconds(this.song.bpm) / this.tempoScale;
|
|
159
|
+
const every = options.quantize === "bar" ? 16 : options.quantize === "beat" ? 4 : 1;
|
|
160
|
+
const at = (now - this.origin) / step;
|
|
161
|
+
when = this.origin + Math.ceil(at / every - 1e-6) * every * step;
|
|
162
|
+
}
|
|
163
|
+
if (options.after)
|
|
164
|
+
when += options.after * (stepSeconds(this.song?.bpm ?? 120) / this.tempoScale);
|
|
165
|
+
const shift = 2 ** ((this.song?.transpose ?? 0) / 12);
|
|
166
|
+
const freqs = options.freq !== undefined
|
|
167
|
+
? [options.freq]
|
|
168
|
+
: options.note
|
|
169
|
+
? (chord(options.note) ?? [frequency(options.note) ?? 440])
|
|
170
|
+
: [degree(this.song?.key, options.degree ?? 1, options.octave ?? 4) * shift];
|
|
171
|
+
const length = options.length ?? (instrument.attack ?? 0.005) + (instrument.decay ?? 0.1);
|
|
172
|
+
for (const freq of freqs) {
|
|
173
|
+
this.voice(instrument, this.master, freq, when, length, options.velocity ?? 0.8);
|
|
174
|
+
this.record({ time: when, instrument: "sfx", freq, duration: length, velocity: options.velocity ?? 0.8 });
|
|
175
|
+
}
|
|
176
|
+
return when;
|
|
177
|
+
}
|
|
178
|
+
layerGain(instrument) {
|
|
179
|
+
let g = this.layers.get(instrument);
|
|
180
|
+
if (!g) {
|
|
181
|
+
g = this.context.createGain();
|
|
182
|
+
g.connect(this.out);
|
|
183
|
+
this.layers.set(instrument, g);
|
|
184
|
+
}
|
|
185
|
+
return g;
|
|
186
|
+
}
|
|
187
|
+
record(n) {
|
|
188
|
+
this.played.push(n);
|
|
189
|
+
if (this.played.length > 256)
|
|
190
|
+
this.played.shift();
|
|
191
|
+
}
|
|
192
|
+
/** Schedule every note due before the lookahead runs out. */
|
|
193
|
+
schedule() {
|
|
194
|
+
const song = this.song;
|
|
195
|
+
if (!song)
|
|
196
|
+
return;
|
|
197
|
+
const step = stepSeconds(song.bpm) / this.tempoScale;
|
|
198
|
+
const until = this.context.currentTime + LOOKAHEAD;
|
|
199
|
+
const last = Math.floor((until - this.origin) / step) + 1;
|
|
200
|
+
if (last <= this.nextStep)
|
|
201
|
+
return;
|
|
202
|
+
// notes() works in the song's own time; rescale to the current tempo.
|
|
203
|
+
const scale = stepSeconds(song.bpm) / step;
|
|
204
|
+
for (const n of notes(song, this.nextStep, last)) {
|
|
205
|
+
const instrument = song.instruments[n.instrument];
|
|
206
|
+
if (!instrument)
|
|
207
|
+
continue;
|
|
208
|
+
const when = this.origin + n.time / scale;
|
|
209
|
+
if (when < this.context.currentTime - 0.01)
|
|
210
|
+
continue;
|
|
211
|
+
this.voice(instrument, this.layerGain(n.instrument), n.freq, when, n.duration / scale, n.velocity);
|
|
212
|
+
this.record(n);
|
|
213
|
+
}
|
|
214
|
+
this.nextStep = last;
|
|
215
|
+
}
|
|
216
|
+
/** A seeded noise source, shared by every drum. */
|
|
217
|
+
noise() {
|
|
218
|
+
if (!this.noiseBuffer) {
|
|
219
|
+
const len = this.context.sampleRate;
|
|
220
|
+
this.noiseBuffer = this.context.createBuffer(1, len, this.context.sampleRate);
|
|
221
|
+
fillNoise(this.noiseBuffer.getChannelData(0), 0x2545f491, () => 1);
|
|
222
|
+
}
|
|
223
|
+
return this.noiseBuffer;
|
|
224
|
+
}
|
|
225
|
+
/** A reverb impulse: stereo noise fading out over `seconds`, seeded so the room sounds the same every time. */
|
|
226
|
+
impulse(seconds) {
|
|
227
|
+
const rate = this.context.sampleRate, len = Math.max(1, Math.floor(rate * seconds));
|
|
228
|
+
const buf = this.context.createBuffer(2, len, rate);
|
|
229
|
+
for (let c = 0; c < 2; c++)
|
|
230
|
+
fillNoise(buf.getChannelData(c), 0x51ed270b + c, (i) => (1 - i / len) ** 3);
|
|
231
|
+
return buf;
|
|
232
|
+
}
|
|
233
|
+
/** Build and start one note, into `into`. */
|
|
234
|
+
voice(inst, into, freq, when, length, velocity) {
|
|
235
|
+
const ctx = this.context;
|
|
236
|
+
const attack = inst.attack ?? 0.005, decay = inst.decay ?? 0.1, sustain = inst.sustain ?? 0.6, release = inst.release ?? 0.08;
|
|
237
|
+
const peak = (inst.volume ?? 0.3) * velocity;
|
|
238
|
+
const end = when + Math.max(length, attack + decay);
|
|
239
|
+
const env = ctx.createGain();
|
|
240
|
+
env.gain.setValueAtTime(0.0001, when);
|
|
241
|
+
env.gain.linearRampToValueAtTime(peak, when + attack);
|
|
242
|
+
env.gain.exponentialRampToValueAtTime(Math.max(0.0001, peak * sustain), when + attack + decay);
|
|
243
|
+
env.gain.setValueAtTime(Math.max(0.0001, peak * sustain), end);
|
|
244
|
+
env.gain.exponentialRampToValueAtTime(0.0001, end + release);
|
|
245
|
+
let head = env;
|
|
246
|
+
if (inst.filter) {
|
|
247
|
+
const f = ctx.createBiquadFilter();
|
|
248
|
+
f.type = "lowpass";
|
|
249
|
+
f.Q.value = inst.filter.q ?? 1;
|
|
250
|
+
const base = inst.filter.cutoff;
|
|
251
|
+
if (inst.filter.sweep) {
|
|
252
|
+
f.frequency.setValueAtTime(base + inst.filter.sweep, when);
|
|
253
|
+
f.frequency.exponentialRampToValueAtTime(base, when + attack + decay);
|
|
254
|
+
}
|
|
255
|
+
else
|
|
256
|
+
f.frequency.value = base;
|
|
257
|
+
f.connect(env);
|
|
258
|
+
head = f;
|
|
259
|
+
}
|
|
260
|
+
let tail = env;
|
|
261
|
+
if (inst.pan) {
|
|
262
|
+
const p = ctx.createStereoPanner();
|
|
263
|
+
p.pan.value = Math.max(-1, Math.min(1, inst.pan));
|
|
264
|
+
env.connect(p);
|
|
265
|
+
tail = p;
|
|
266
|
+
}
|
|
267
|
+
tail.connect(into);
|
|
268
|
+
if (inst.echo) {
|
|
269
|
+
const send = ctx.createGain();
|
|
270
|
+
send.gain.value = inst.echo;
|
|
271
|
+
tail.connect(send).connect(this.echoIn);
|
|
272
|
+
}
|
|
273
|
+
if (inst.reverb) {
|
|
274
|
+
const send = ctx.createGain();
|
|
275
|
+
send.gain.value = inst.reverb;
|
|
276
|
+
tail.connect(send).connect(this.reverbIn);
|
|
277
|
+
}
|
|
278
|
+
const stopAt = end + release + 0.05;
|
|
279
|
+
if ((inst.wave ?? "square") === "noise") {
|
|
280
|
+
const src = ctx.createBufferSource();
|
|
281
|
+
src.buffer = this.noise();
|
|
282
|
+
src.connect(head);
|
|
283
|
+
src.start(when);
|
|
284
|
+
src.stop(stopAt);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const pitch = freq * 2 ** (inst.octave ?? 0);
|
|
288
|
+
const oscs = [0, ...(inst.detune ? [inst.detune] : [])].map((cents) => {
|
|
289
|
+
const o = ctx.createOscillator();
|
|
290
|
+
o.type = (inst.wave ?? "square");
|
|
291
|
+
o.detune.value = cents;
|
|
292
|
+
if (inst.drop) {
|
|
293
|
+
o.frequency.setValueAtTime(pitch * 2 ** (inst.drop / 12), when);
|
|
294
|
+
o.frequency.exponentialRampToValueAtTime(Math.max(1, pitch), when + Math.min(0.12, attack + decay));
|
|
295
|
+
}
|
|
296
|
+
else
|
|
297
|
+
o.frequency.setValueAtTime(pitch, when);
|
|
298
|
+
o.connect(head);
|
|
299
|
+
o.start(when);
|
|
300
|
+
o.stop(stopAt);
|
|
301
|
+
return o;
|
|
302
|
+
});
|
|
303
|
+
if (inst.vibrato) {
|
|
304
|
+
const lfo = ctx.createOscillator(), depth = ctx.createGain();
|
|
305
|
+
lfo.frequency.value = 5.5;
|
|
306
|
+
depth.gain.value = inst.vibrato;
|
|
307
|
+
lfo.connect(depth);
|
|
308
|
+
for (const o of oscs)
|
|
309
|
+
depth.connect(o.detune);
|
|
310
|
+
lfo.start(when);
|
|
311
|
+
lfo.stop(stopAt);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Fill with seeded white noise (xorshift), shaped by `envelope(i)`. */
|
|
316
|
+
function fillNoise(data, seed, envelope) {
|
|
317
|
+
let s = seed | 0;
|
|
318
|
+
for (let i = 0; i < data.length; i++) {
|
|
319
|
+
s ^= s << 13;
|
|
320
|
+
s ^= s >>> 17;
|
|
321
|
+
s ^= s << 5;
|
|
322
|
+
data[i] = (((s >>> 0) / 4294967296) * 2 - 1) * envelope(i);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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"}
|
package/dist/song.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** How an instrument sounds. Every field is optional; the defaults make a soft square lead. */
|
|
2
|
+
export interface Instrument {
|
|
3
|
+
/** Oscillator shape, or "noise" for drums. */
|
|
4
|
+
wave?: "sine" | "square" | "sawtooth" | "triangle" | "noise";
|
|
5
|
+
/** Envelope, in seconds (sustain is a level, 0-1). */
|
|
6
|
+
attack?: number;
|
|
7
|
+
decay?: number;
|
|
8
|
+
sustain?: number;
|
|
9
|
+
release?: number;
|
|
10
|
+
/** Loudness, 0-1. */
|
|
11
|
+
volume?: number;
|
|
12
|
+
/** Shift every note by whole octaves. */
|
|
13
|
+
octave?: number;
|
|
14
|
+
/** A second oscillator this many cents apart, for a fatter, chorused sound. */
|
|
15
|
+
detune?: number;
|
|
16
|
+
/** A low-pass filter: cutoff in Hz, resonance (Q), and how far the cutoff sweeps up at each note's start. */
|
|
17
|
+
filter?: {
|
|
18
|
+
cutoff: number;
|
|
19
|
+
q?: number;
|
|
20
|
+
sweep?: number;
|
|
21
|
+
};
|
|
22
|
+
/** Pitch drops from this many semitones above to the note (kicks, "bloop"s). */
|
|
23
|
+
drop?: number;
|
|
24
|
+
/** Vibrato depth in cents, at 5.5 Hz. */
|
|
25
|
+
vibrato?: number;
|
|
26
|
+
/** How much goes to the echo, 0-1. */
|
|
27
|
+
echo?: number;
|
|
28
|
+
/** How much goes to the reverb, 0-1. */
|
|
29
|
+
reverb?: number;
|
|
30
|
+
/** Stereo position, -1 (left) to 1 (right). */
|
|
31
|
+
pan?: number;
|
|
32
|
+
}
|
|
33
|
+
/** A track in a section: one pattern, or several that take turns each time the section plays (for fills). */
|
|
34
|
+
export type Track = string | string[];
|
|
35
|
+
/** A song: instruments, and tracks of patterns in sections played in order. */
|
|
36
|
+
export interface Song {
|
|
37
|
+
/** Beats per minute; each step in a pattern is a sixteenth note. */
|
|
38
|
+
bpm: number;
|
|
39
|
+
/** Delay every other step by this fraction of a step (0-0.5), for a bouncy, swung feel. */
|
|
40
|
+
swing?: number;
|
|
41
|
+
/** The key, for scale degrees (`^1`...) and in-key sound effects: "C major", "A minor", "D dorian"... */
|
|
42
|
+
key?: string;
|
|
43
|
+
/** Shift the whole song by this many semitones. */
|
|
44
|
+
transpose?: number;
|
|
45
|
+
instruments: Record<string, Instrument>;
|
|
46
|
+
/**
|
|
47
|
+
* Patterns per section, per instrument. A pattern is steps separated by spaces. See `parse` for what a step can be.
|
|
48
|
+
* A shorter pattern repeats to fill the section; a list of patterns takes turns, one per time the section plays.
|
|
49
|
+
*/
|
|
50
|
+
sections: Record<string, Record<string, Track>>;
|
|
51
|
+
/** Section names in the order they play. The song loops back to the start. */
|
|
52
|
+
order: string[];
|
|
53
|
+
}
|
|
54
|
+
/** One note to play: when (seconds from the start), on what, how high (Hz, or 0 for a drum hit), for how long. */
|
|
55
|
+
export interface NoteEvent {
|
|
56
|
+
time: number;
|
|
57
|
+
instrument: string;
|
|
58
|
+
freq: number;
|
|
59
|
+
duration: number;
|
|
60
|
+
/** 0-1: accents are louder, ghost notes quieter. */
|
|
61
|
+
velocity: number;
|
|
62
|
+
}
|
|
63
|
+
/** A note name ("A4", "C#5", "Eb3") as a frequency in Hz, or undefined if it isn't one. A4 is 440 Hz. */
|
|
64
|
+
export declare function frequency(name: string): number | undefined;
|
|
65
|
+
/** The scales `key` understands, as semitones above the root. */
|
|
66
|
+
export declare const SCALES: Record<string, number[]>;
|
|
67
|
+
/** A key ("C major", "F# minor pentatonic") as its root (semitones from A, in octave 4's C-to-B) and scale. */
|
|
68
|
+
export declare function parseKey(key?: string): {
|
|
69
|
+
root: number;
|
|
70
|
+
scale: number[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The frequency of scale degree `n` (1 = the root; 8 = the root an octave up; 0 and below go down) of `key`, in
|
|
74
|
+
* `octave` (4 by default, the octave of middle C). Sound effects use this to land in the song's key.
|
|
75
|
+
*/
|
|
76
|
+
export declare function degree(key: string | undefined, n: number, octave?: number): number;
|
|
77
|
+
/** A chord name with an octave ("Am7@3", "Cmaj7@4", "G@3") as frequencies, or undefined. */
|
|
78
|
+
export declare function chord(name: string): number[] | undefined;
|
|
79
|
+
/** A step: what starts there (frequencies, or [0] for a hit), its velocity, chance to play, and length in steps. */
|
|
80
|
+
export interface Step {
|
|
81
|
+
freqs: number[];
|
|
82
|
+
velocity: number;
|
|
83
|
+
/** 0-1: the step plays this often (seeded, so the same song always plays the same way). */
|
|
84
|
+
chance: number;
|
|
85
|
+
length: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse a pattern into steps; `null` where nothing starts (rests and held steps). Throws on anything unreadable.
|
|
89
|
+
*
|
|
90
|
+
* A step is one of:
|
|
91
|
+
* - a note: "C4", "Eb5", "F#3"
|
|
92
|
+
* - a chord: notes joined with "+" ("C4+E4+G4"), or a chord name with an octave ("Am7@3", "Cmaj7@4", "G@3")
|
|
93
|
+
* - a scale degree of the song's key: "^1" (the root), "^3", "^5@5" (in octave 5; octave 4 by default)
|
|
94
|
+
* - a hit, for drums: "x" ("X" is an accented hit)
|
|
95
|
+
* - "." silence, "-" hold the step before, "|" ignored (mark bars)
|
|
96
|
+
*
|
|
97
|
+
* Any note, chord or hit can end with "!" (accent), "_" (ghost: quiet), and "?" (plays half the time) or "?30" (30%).
|
|
98
|
+
*/
|
|
99
|
+
export declare function parse(pattern: string, key?: string): (Step | null)[];
|
|
100
|
+
/** Seconds per step (a sixteenth note). */
|
|
101
|
+
export declare const stepSeconds: (bpm: number) => number;
|
|
102
|
+
/** How many steps a section lasts: its longest pattern. */
|
|
103
|
+
export declare function sectionSteps(section: Record<string, Track>): number;
|
|
104
|
+
/** Steps in one pass through the whole song. */
|
|
105
|
+
export declare const songSteps: (song: Song) => number;
|
|
106
|
+
/**
|
|
107
|
+
* Every note that starts in steps [from, to) of the song (counting from 0, looping through `order` forever), with
|
|
108
|
+
* times in seconds from step 0. The player calls this a little ahead of time; tests and agents can call it to see a
|
|
109
|
+
* song without hearing it.
|
|
110
|
+
*/
|
|
111
|
+
export declare function notes(song: Song, from: number, to: number): NoteEvent[];
|
|
112
|
+
//# sourceMappingURL=song.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"song.d.ts","sourceRoot":"","sources":["../src/song.ts"],"names":[],"mappings":"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"}
|
package/dist/song.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Songs as data, and the pure part of playing them: parsing patterns and turning a song into timed note events. No
|
|
2
|
+
// Web Audio here, so all of it runs (and is tested) anywhere.
|
|
3
|
+
// ---- pitch ------------------------------------------------------------------------------------------------
|
|
4
|
+
const NOTE = /^([A-Ga-g])(#|b)?(-?\d)$/;
|
|
5
|
+
const SEMITONE = { c: -9, d: -7, e: -5, f: -4, g: -2, a: 0, b: 2 };
|
|
6
|
+
const LETTER = /^([A-Ga-g])(#|b)?/;
|
|
7
|
+
/** Semitones from A4 to a note name, or undefined. */
|
|
8
|
+
function semitones(name) {
|
|
9
|
+
const m = NOTE.exec(name);
|
|
10
|
+
if (!m)
|
|
11
|
+
return undefined;
|
|
12
|
+
return SEMITONE[m[1].toLowerCase()] + (m[2] === "#" ? 1 : m[2] === "b" ? -1 : 0) + (Number(m[3]) - 4) * 12;
|
|
13
|
+
}
|
|
14
|
+
const hz = (fromA4) => 440 * 2 ** (fromA4 / 12);
|
|
15
|
+
/** A note name ("A4", "C#5", "Eb3") as a frequency in Hz, or undefined if it isn't one. A4 is 440 Hz. */
|
|
16
|
+
export function frequency(name) {
|
|
17
|
+
const s = semitones(name);
|
|
18
|
+
return s === undefined ? undefined : hz(s);
|
|
19
|
+
}
|
|
20
|
+
/** The scales `key` understands, as semitones above the root. */
|
|
21
|
+
export const SCALES = {
|
|
22
|
+
major: [0, 2, 4, 5, 7, 9, 11],
|
|
23
|
+
minor: [0, 2, 3, 5, 7, 8, 10],
|
|
24
|
+
dorian: [0, 2, 3, 5, 7, 9, 10],
|
|
25
|
+
phrygian: [0, 1, 3, 5, 7, 8, 10],
|
|
26
|
+
lydian: [0, 2, 4, 6, 7, 9, 11],
|
|
27
|
+
mixolydian: [0, 2, 4, 5, 7, 9, 10],
|
|
28
|
+
pentatonic: [0, 2, 4, 7, 9],
|
|
29
|
+
"minor pentatonic": [0, 3, 5, 7, 10],
|
|
30
|
+
blues: [0, 3, 5, 6, 7, 10],
|
|
31
|
+
};
|
|
32
|
+
/** A key ("C major", "F# minor pentatonic") as its root (semitones from A, in octave 4's C-to-B) and scale. */
|
|
33
|
+
export function parseKey(key = "C major") {
|
|
34
|
+
const [rootName = "C", ...rest] = key.trim().split(/\s+/);
|
|
35
|
+
const m = LETTER.exec(rootName);
|
|
36
|
+
const scale = SCALES[(rest.join(" ") || "major").toLowerCase()];
|
|
37
|
+
if (!m || !scale)
|
|
38
|
+
throw new Error(`plonk: "${key}" isn't a key (try "C major", "A minor" or "D dorian")`);
|
|
39
|
+
const root = SEMITONE[m[1].toLowerCase()] + (m[2] === "#" ? 1 : m[2] === "b" ? -1 : 0);
|
|
40
|
+
return { root, scale };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The frequency of scale degree `n` (1 = the root; 8 = the root an octave up; 0 and below go down) of `key`, in
|
|
44
|
+
* `octave` (4 by default, the octave of middle C). Sound effects use this to land in the song's key.
|
|
45
|
+
*/
|
|
46
|
+
export function degree(key, n, octave = 4) {
|
|
47
|
+
const { root, scale } = parseKey(key);
|
|
48
|
+
const i = n - 1;
|
|
49
|
+
const len = scale.length;
|
|
50
|
+
const oct = Math.floor(i / len);
|
|
51
|
+
const within = ((i % len) + len) % len;
|
|
52
|
+
return hz(root + scale[within] + (octave - 4 + oct) * 12);
|
|
53
|
+
}
|
|
54
|
+
const CHORDS = {
|
|
55
|
+
"": [0, 4, 7],
|
|
56
|
+
m: [0, 3, 7],
|
|
57
|
+
"7": [0, 4, 7, 10],
|
|
58
|
+
maj7: [0, 4, 7, 11],
|
|
59
|
+
m7: [0, 3, 7, 10],
|
|
60
|
+
dim: [0, 3, 6],
|
|
61
|
+
aug: [0, 4, 8],
|
|
62
|
+
sus2: [0, 2, 7],
|
|
63
|
+
sus4: [0, 5, 7],
|
|
64
|
+
"6": [0, 4, 7, 9],
|
|
65
|
+
m6: [0, 3, 7, 9],
|
|
66
|
+
add9: [0, 4, 7, 14],
|
|
67
|
+
"9": [0, 4, 7, 10, 14],
|
|
68
|
+
};
|
|
69
|
+
const CHORD = /^([A-G])(#|b)?(maj7|m7|m6|dim|aug|sus2|sus4|add9|m|7|6|9)?@(-?\d)$/;
|
|
70
|
+
/** A chord name with an octave ("Am7@3", "Cmaj7@4", "G@3") as frequencies, or undefined. */
|
|
71
|
+
export function chord(name) {
|
|
72
|
+
const m = CHORD.exec(name);
|
|
73
|
+
if (!m)
|
|
74
|
+
return undefined;
|
|
75
|
+
const root = SEMITONE[m[1].toLowerCase()] + (m[2] === "#" ? 1 : m[2] === "b" ? -1 : 0) + (Number(m[4]) - 4) * 12;
|
|
76
|
+
return CHORDS[m[3] ?? ""].map((s) => hz(root + s));
|
|
77
|
+
}
|
|
78
|
+
const MODS = /([!_]|\?\d*)+$/;
|
|
79
|
+
/**
|
|
80
|
+
* Parse a pattern into steps; `null` where nothing starts (rests and held steps). Throws on anything unreadable.
|
|
81
|
+
*
|
|
82
|
+
* A step is one of:
|
|
83
|
+
* - a note: "C4", "Eb5", "F#3"
|
|
84
|
+
* - a chord: notes joined with "+" ("C4+E4+G4"), or a chord name with an octave ("Am7@3", "Cmaj7@4", "G@3")
|
|
85
|
+
* - a scale degree of the song's key: "^1" (the root), "^3", "^5@5" (in octave 5; octave 4 by default)
|
|
86
|
+
* - a hit, for drums: "x" ("X" is an accented hit)
|
|
87
|
+
* - "." silence, "-" hold the step before, "|" ignored (mark bars)
|
|
88
|
+
*
|
|
89
|
+
* Any note, chord or hit can end with "!" (accent), "_" (ghost: quiet), and "?" (plays half the time) or "?30" (30%).
|
|
90
|
+
*/
|
|
91
|
+
export function parse(pattern, key) {
|
|
92
|
+
const tokens = pattern.split(/\s+/).filter((t) => t && t !== "|");
|
|
93
|
+
const out = [];
|
|
94
|
+
let last;
|
|
95
|
+
for (const token of tokens) {
|
|
96
|
+
if (token === ".") {
|
|
97
|
+
out.push(null);
|
|
98
|
+
last = undefined;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (token === "-") {
|
|
102
|
+
if (last)
|
|
103
|
+
last.length++;
|
|
104
|
+
out.push(null);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const mods = MODS.exec(token)?.[0] ?? "";
|
|
108
|
+
const body = mods ? token.slice(0, -mods.length) : token;
|
|
109
|
+
let velocity = 0.8;
|
|
110
|
+
let freqs;
|
|
111
|
+
if (body === "x" || body === "X") {
|
|
112
|
+
freqs = [0];
|
|
113
|
+
velocity = body === "X" ? 1 : 0.7;
|
|
114
|
+
}
|
|
115
|
+
else if (body.startsWith("^")) {
|
|
116
|
+
const [n, oct] = body.slice(1).split("@");
|
|
117
|
+
if (!n || Number.isNaN(Number(n)))
|
|
118
|
+
throw new Error(`plonk: "${token}" isn't a scale degree (try "^1", "^5" or "^3@5")`);
|
|
119
|
+
freqs = [degree(key, Number(n), oct === undefined ? 4 : Number(oct))];
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
freqs =
|
|
123
|
+
chord(body) ??
|
|
124
|
+
body.split("+").map((n) => {
|
|
125
|
+
const f = frequency(n);
|
|
126
|
+
if (f === undefined)
|
|
127
|
+
throw new Error(`plonk: "${n}" isn't a note (try "C4", "Eb5", "F#3", "Am7@3" or "^3")`);
|
|
128
|
+
return f;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (mods.includes("!"))
|
|
132
|
+
velocity = 1;
|
|
133
|
+
if (mods.includes("_"))
|
|
134
|
+
velocity = 0.4;
|
|
135
|
+
const q = /\?(\d*)/.exec(mods);
|
|
136
|
+
const chance = q ? (q[1] ? Number(q[1]) / 100 : 0.5) : 1;
|
|
137
|
+
last = { freqs, velocity, chance, length: 1 };
|
|
138
|
+
out.push(last);
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
// ---- timing -------------------------------------------------------------------------------------------------
|
|
143
|
+
/** Seconds per step (a sixteenth note). */
|
|
144
|
+
export const stepSeconds = (bpm) => 60 / bpm / 4;
|
|
145
|
+
const variants = (track) => (Array.isArray(track) ? track : [track]);
|
|
146
|
+
/** How many steps a section lasts: its longest pattern. */
|
|
147
|
+
export function sectionSteps(section) {
|
|
148
|
+
let n = 0;
|
|
149
|
+
for (const t of Object.values(section))
|
|
150
|
+
for (const p of variants(t))
|
|
151
|
+
n = Math.max(n, parse(p).length);
|
|
152
|
+
return n;
|
|
153
|
+
}
|
|
154
|
+
/** Steps in one pass through the whole song. */
|
|
155
|
+
export const songSteps = (song) => layout(song).total;
|
|
156
|
+
/** A seeded number in [0, 1) for a step, so chances come out the same every time the song plays. */
|
|
157
|
+
function seeded(...parts) {
|
|
158
|
+
let h = 0x9e3779b9;
|
|
159
|
+
for (const p of parts) {
|
|
160
|
+
h = Math.imul(h ^ (p | 0), 0x85ebca6b);
|
|
161
|
+
h ^= h >>> 13;
|
|
162
|
+
}
|
|
163
|
+
h = Math.imul(h ^ (h >>> 16), 0xc2b2ae35);
|
|
164
|
+
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
|
|
165
|
+
}
|
|
166
|
+
const hashName = (s) => [...s].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619), 2166136261);
|
|
167
|
+
/**
|
|
168
|
+
* A song's parsed layout, worked out once per song object: the player asks for notes many times a second, and
|
|
169
|
+
* re-parsing every pattern each time adds up. (Songs are treated as unchanging once played; pass a new object to
|
|
170
|
+
* change one.)
|
|
171
|
+
*/
|
|
172
|
+
const layouts = new WeakMap();
|
|
173
|
+
function layout(song) {
|
|
174
|
+
let l = layouts.get(song);
|
|
175
|
+
if (l)
|
|
176
|
+
return l;
|
|
177
|
+
const parsed = new Map();
|
|
178
|
+
const steps = (p) => {
|
|
179
|
+
let s = parsed.get(p);
|
|
180
|
+
if (!s) {
|
|
181
|
+
s = parse(p, song.key);
|
|
182
|
+
parsed.set(p, s);
|
|
183
|
+
}
|
|
184
|
+
return s;
|
|
185
|
+
};
|
|
186
|
+
const starts = [];
|
|
187
|
+
const perPass = new Map();
|
|
188
|
+
let at = 0;
|
|
189
|
+
for (const name of song.order) {
|
|
190
|
+
let len = 0;
|
|
191
|
+
for (const t of Object.values(song.sections[name] ?? {}))
|
|
192
|
+
for (const p of variants(t))
|
|
193
|
+
len = Math.max(len, steps(p).length);
|
|
194
|
+
const seen = perPass.get(name) ?? 0;
|
|
195
|
+
perPass.set(name, seen + 1);
|
|
196
|
+
starts.push({ name, at, len, seen });
|
|
197
|
+
at += len;
|
|
198
|
+
}
|
|
199
|
+
l = { total: at, starts, perPass, steps };
|
|
200
|
+
layouts.set(song, l);
|
|
201
|
+
return l;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Every note that starts in steps [from, to) of the song (counting from 0, looping through `order` forever), with
|
|
205
|
+
* times in seconds from step 0. The player calls this a little ahead of time; tests and agents can call it to see a
|
|
206
|
+
* song without hearing it.
|
|
207
|
+
*/
|
|
208
|
+
export function notes(song, from, to) {
|
|
209
|
+
const out = [];
|
|
210
|
+
const step = stepSeconds(song.bpm), swing = Math.max(0, Math.min(0.5, song.swing ?? 0)) * step;
|
|
211
|
+
const { total, starts, perPass, steps } = layout(song);
|
|
212
|
+
if (total === 0)
|
|
213
|
+
return out;
|
|
214
|
+
const shift = 2 ** ((song.transpose ?? 0) / 12);
|
|
215
|
+
for (let i = Math.max(0, Math.floor(from)); i < to; i++) {
|
|
216
|
+
const pass = Math.floor(i / total), within = i % total;
|
|
217
|
+
const sec = starts.find((s) => within >= s.at && within < s.at + s.len);
|
|
218
|
+
if (!sec)
|
|
219
|
+
continue;
|
|
220
|
+
// Which time this section is playing, counting from the very start: picks the variant.
|
|
221
|
+
const occurrence = pass * (perPass.get(sec.name) ?? 1) + sec.seen;
|
|
222
|
+
for (const [instrument, track] of Object.entries(song.sections[sec.name] ?? {})) {
|
|
223
|
+
const options = variants(track);
|
|
224
|
+
const ps = steps(options[occurrence % options.length]);
|
|
225
|
+
if (!ps.length)
|
|
226
|
+
continue;
|
|
227
|
+
const s = ps[(within - sec.at) % ps.length];
|
|
228
|
+
if (!s)
|
|
229
|
+
continue;
|
|
230
|
+
if (s.chance < 1 && seeded(pass, within, hashName(instrument)) >= s.chance)
|
|
231
|
+
continue;
|
|
232
|
+
const time = (pass * total + within) * step + (within % 2 === 1 ? swing : 0);
|
|
233
|
+
for (const freq of s.freqs)
|
|
234
|
+
out.push({ time, instrument, freq: freq * shift, duration: s.length * step, velocity: s.velocity });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=song.js.map
|
package/dist/song.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"song.js","sourceRoot":"","sources":["../src/song.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plonkjs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Game music made in code: songs as tiny data, synthesized live with Web Audio. No audio files, a few kB, any engine",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"game",
|
|
7
|
+
"gamedev",
|
|
8
|
+
"web-game",
|
|
9
|
+
"browser-game",
|
|
10
|
+
"html5-game",
|
|
11
|
+
"music",
|
|
12
|
+
"procedural-music",
|
|
13
|
+
"chiptune",
|
|
14
|
+
"synth",
|
|
15
|
+
"sequencer",
|
|
16
|
+
"web-audio",
|
|
17
|
+
"debuff"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://github.com/debuffjp/debuff/tree/main/packages/plonk#readme",
|
|
21
|
+
"bugs": "https://github.com/debuffjp/debuff/issues",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/debuffjp/debuff.git",
|
|
25
|
+
"directory": "packages/plonk"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -b"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|