@sudobility/music_player 0.1.0 → 0.1.1
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/CLAUDE.md +335 -0
- package/dist/core.d.ts +33 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +33 -0
- package/dist/core.js.map +1 -0
- package/dist/engine.d.ts +103 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +2 -0
- package/dist/engine.js.map +1 -0
- package/dist/mocks/index.d.ts +46 -0
- package/dist/mocks/index.d.ts.map +1 -0
- package/dist/mocks/index.js +93 -0
- package/dist/mocks/index.js.map +1 -0
- package/dist/player.d.ts +81 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +209 -0
- package/dist/player.js.map +1 -0
- package/dist/rn/audio/offline-render.d.ts +29 -0
- package/dist/rn/audio/offline-render.d.ts.map +1 -0
- package/dist/rn/audio/offline-render.js +134 -0
- package/dist/rn/audio/offline-render.js.map +1 -0
- package/dist/rn/index.d.ts +50 -0
- package/dist/rn/index.d.ts.map +1 -0
- package/dist/rn/index.js +39 -0
- package/dist/rn/index.js.map +1 -0
- package/dist/rn/playback/audio-api.d.ts +93 -0
- package/dist/rn/playback/audio-api.d.ts.map +1 -0
- package/dist/rn/playback/audio-api.js +24 -0
- package/dist/rn/playback/audio-api.js.map +1 -0
- package/dist/rn/playback/expression-table.d.ts +22 -0
- package/dist/rn/playback/expression-table.d.ts.map +1 -0
- package/dist/rn/playback/expression-table.js +926 -0
- package/dist/rn/playback/expression-table.js.map +1 -0
- package/dist/rn/playback/expression.d.ts +48 -0
- package/dist/rn/playback/expression.d.ts.map +1 -0
- package/dist/rn/playback/expression.js +120 -0
- package/dist/rn/playback/expression.js.map +1 -0
- package/dist/rn/playback/gm-pack-name.d.ts +40 -0
- package/dist/rn/playback/gm-pack-name.d.ts.map +1 -0
- package/dist/rn/playback/gm-pack-name.js +61 -0
- package/dist/rn/playback/gm-pack-name.js.map +1 -0
- package/dist/rn/playback/pack-library.d.ts +44 -0
- package/dist/rn/playback/pack-library.d.ts.map +1 -0
- package/dist/rn/playback/pack-library.js +85 -0
- package/dist/rn/playback/pack-library.js.map +1 -0
- package/dist/rn/playback/pack-names.json +130 -0
- package/dist/rn/playback/sample-engine.d.ts +216 -0
- package/dist/rn/playback/sample-engine.d.ts.map +1 -0
- package/dist/rn/playback/sample-engine.js +669 -0
- package/dist/rn/playback/sample-engine.js.map +1 -0
- package/dist/rn/playback/sample-pack.d.ts +71 -0
- package/dist/rn/playback/sample-pack.d.ts.map +1 -0
- package/dist/rn/playback/sample-pack.js +112 -0
- package/dist/rn/playback/sample-pack.js.map +1 -0
- package/dist/rn/playback/sustain-loop.d.ts +24 -0
- package/dist/rn/playback/sustain-loop.d.ts.map +1 -0
- package/dist/rn/playback/sustain-loop.js +111 -0
- package/dist/rn/playback/sustain-loop.js.map +1 -0
- package/dist/rn/playback/voice-plan.d.ts +60 -0
- package/dist/rn/playback/voice-plan.d.ts.map +1 -0
- package/dist/rn/playback/voice-plan.js +38 -0
- package/dist/rn/playback/voice-plan.js.map +1 -0
- package/dist/shared/bus.d.ts +62 -0
- package/dist/shared/bus.d.ts.map +1 -0
- package/dist/shared/bus.js +73 -0
- package/dist/shared/bus.js.map +1 -0
- package/dist/shared/midi.d.ts +18 -0
- package/dist/shared/midi.d.ts.map +1 -0
- package/dist/shared/midi.js +25 -0
- package/dist/shared/midi.js.map +1 -0
- package/dist/shared/mix.d.ts +75 -0
- package/dist/shared/mix.d.ts.map +1 -0
- package/dist/shared/mix.js +115 -0
- package/dist/shared/mix.js.map +1 -0
- package/dist/shared/note-queue.d.ts +43 -0
- package/dist/shared/note-queue.d.ts.map +1 -0
- package/dist/shared/note-queue.js +54 -0
- package/dist/shared/note-queue.js.map +1 -0
- package/dist/shared/plan.d.ts +34 -0
- package/dist/shared/plan.d.ts.map +1 -0
- package/dist/shared/plan.js +134 -0
- package/dist/shared/plan.js.map +1 -0
- package/dist/shared/pump-window.d.ts +38 -0
- package/dist/shared/pump-window.d.ts.map +1 -0
- package/dist/shared/pump-window.js +13 -0
- package/dist/shared/pump-window.js.map +1 -0
- package/dist/shared/render-events.d.ts +25 -0
- package/dist/shared/render-events.d.ts.map +1 -0
- package/dist/shared/render-events.js +79 -0
- package/dist/shared/render-events.js.map +1 -0
- package/dist/shared/sounding-set.d.ts +45 -0
- package/dist/shared/sounding-set.d.ts.map +1 -0
- package/dist/shared/sounding-set.js +81 -0
- package/dist/shared/sounding-set.js.map +1 -0
- package/dist/shared/test-plan.d.ts +36 -0
- package/dist/shared/test-plan.d.ts.map +1 -0
- package/dist/shared/test-plan.js +83 -0
- package/dist/shared/test-plan.js.map +1 -0
- package/dist/singleton.d.ts +23 -0
- package/dist/singleton.d.ts.map +1 -0
- package/dist/singleton.js +26 -0
- package/dist/singleton.js.map +1 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/web/audio/offline-synth.d.ts +24 -0
- package/dist/web/audio/offline-synth.d.ts.map +1 -0
- package/dist/web/audio/offline-synth.js +92 -0
- package/dist/web/audio/offline-synth.js.map +1 -0
- package/dist/web/audio/soundfont-render.d.ts +31 -0
- package/dist/web/audio/soundfont-render.d.ts.map +1 -0
- package/dist/web/audio/soundfont-render.js +130 -0
- package/dist/web/audio/soundfont-render.js.map +1 -0
- package/dist/web/audio/synth-types.d.ts +24 -0
- package/dist/web/audio/synth-types.d.ts.map +1 -0
- package/dist/web/audio/synth-types.js +2 -0
- package/dist/web/audio/synth-types.js.map +1 -0
- package/dist/web/index.d.ts +40 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +59 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/playback/channel-allocator.d.ts +54 -0
- package/dist/web/playback/channel-allocator.d.ts.map +1 -0
- package/dist/web/playback/channel-allocator.js +72 -0
- package/dist/web/playback/channel-allocator.js.map +1 -0
- package/dist/web/playback/click.d.ts +9 -0
- package/dist/web/playback/click.d.ts.map +1 -0
- package/dist/web/playback/click.js +61 -0
- package/dist/web/playback/click.js.map +1 -0
- package/dist/web/playback/clock.d.ts +36 -0
- package/dist/web/playback/clock.d.ts.map +1 -0
- package/dist/web/playback/clock.js +64 -0
- package/dist/web/playback/clock.js.map +1 -0
- package/dist/web/playback/governor.d.ts +45 -0
- package/dist/web/playback/governor.d.ts.map +1 -0
- package/dist/web/playback/governor.js +33 -0
- package/dist/web/playback/governor.js.map +1 -0
- package/dist/web/playback/quiet-stub-notices.d.ts +55 -0
- package/dist/web/playback/quiet-stub-notices.d.ts.map +1 -0
- package/dist/web/playback/quiet-stub-notices.js +93 -0
- package/dist/web/playback/quiet-stub-notices.js.map +1 -0
- package/dist/web/playback/soundfont-engine.d.ts +281 -0
- package/dist/web/playback/soundfont-engine.d.ts.map +1 -0
- package/dist/web/playback/soundfont-engine.js +716 -0
- package/dist/web/playback/soundfont-engine.js.map +1 -0
- package/dist/web/playback/soundfont-loader.d.ts +30 -0
- package/dist/web/playback/soundfont-loader.d.ts.map +1 -0
- package/dist/web/playback/soundfont-loader.js +68 -0
- package/dist/web/playback/soundfont-loader.js.map +1 -0
- package/dist/web/playback/synth-host.d.ts +142 -0
- package/dist/web/playback/synth-host.d.ts.map +1 -0
- package/dist/web/playback/synth-host.js +293 -0
- package/dist/web/playback/synth-host.js.map +1 -0
- package/package.json +18 -3
- package/dist/index.d.ts +0 -14
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A score flattened into timed events, ready for an offline audio render.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately score-in, events-out: the actual rendering needs Tone.js and an
|
|
5
|
+
* audio context, so it lives in music_io. Everything *musical* about an export
|
|
6
|
+
* — which tracks sound, when each note starts, how long the file has to be —
|
|
7
|
+
* is decided here, where it can be tested without a browser.
|
|
8
|
+
*/
|
|
9
|
+
import { TempoMap } from '@sudobility/music_types';
|
|
10
|
+
import { fermataTempoMap } from '@sudobility/music_types';
|
|
11
|
+
import { flattenScoreNotes } from '@sudobility/music_types';
|
|
12
|
+
import { resolveVoice } from './plan.js';
|
|
13
|
+
/** Release tail, so the last note is not cut off mid-decay. */
|
|
14
|
+
const TAIL_SEC = 1;
|
|
15
|
+
/**
|
|
16
|
+
* The plan an export should sound, matching live playback.
|
|
17
|
+
*
|
|
18
|
+
* **Mute and solo are respected**, because they are part of how the score
|
|
19
|
+
* currently sounds, and an export that ignored them would not match what you
|
|
20
|
+
* just heard. Solo wins: if anything is soloed, only soloed tracks sound.
|
|
21
|
+
*
|
|
22
|
+
* Every track appears in `tracks` regardless — silent, muted, soloed away —
|
|
23
|
+
* because the renderer sizes its mix headroom by how many channels exist, as
|
|
24
|
+
* playback does. Only a track's *notes* are dropped when it is silenced.
|
|
25
|
+
*
|
|
26
|
+
* `isPercussion` comes from the clef and not the MIDI channel, and the voice is
|
|
27
|
+
* left for the renderer to resolve from `midiProgram`/`instrumentName`: both
|
|
28
|
+
* are the signals live playback uses, and the export sounding different from
|
|
29
|
+
* the thing it is an export *of* is the bug being fixed here.
|
|
30
|
+
*
|
|
31
|
+
* That parity is now structural: the notes come from `flattenScoreNotes`, the
|
|
32
|
+
* same traversal `playbackPlan` uses. Walking the score separately is what let
|
|
33
|
+
* the two drift over ties.
|
|
34
|
+
*/
|
|
35
|
+
export function renderEvents(score) {
|
|
36
|
+
// The same derived map live playback uses, so an exported file holds its
|
|
37
|
+
// pauses for exactly as long as the transport just did.
|
|
38
|
+
const tempoMap = new TempoMap(fermataTempoMap(score), score.ppq);
|
|
39
|
+
const anySolo = score.tracks.some(t => t.solo);
|
|
40
|
+
const tracks = score.tracks.map(track => {
|
|
41
|
+
const isPercussion = track.clef === 'percussion';
|
|
42
|
+
// The renderer picks its sample pack from this, and a percussion track's
|
|
43
|
+
// midiProgram addresses a kit rather than an instrument.
|
|
44
|
+
const voice = resolveVoice(track.midiProgram, isPercussion, track.instrumentName);
|
|
45
|
+
return {
|
|
46
|
+
id: track.id,
|
|
47
|
+
midiProgram: track.midiProgram,
|
|
48
|
+
instrumentName: track.instrumentName,
|
|
49
|
+
isPercussion,
|
|
50
|
+
volume: track.volume,
|
|
51
|
+
pan: track.pan,
|
|
52
|
+
voiceProgram: voice.program,
|
|
53
|
+
voiceName: voice.name,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
// The same traversal live playback uses, so ties are joined here too. They
|
|
57
|
+
// were not, and a tie re-articulated in the exported file while sustaining
|
|
58
|
+
// on screen — measured: one note of 960 ticks live, two of 0.5s offline.
|
|
59
|
+
const silenced = new Set(score.tracks.filter(t => (anySolo ? !t.solo : t.muted)).map(t => t.id));
|
|
60
|
+
const events = [];
|
|
61
|
+
for (const note of flattenScoreNotes(score)) {
|
|
62
|
+
if (silenced.has(note.trackId))
|
|
63
|
+
continue;
|
|
64
|
+
const startSec = tempoMap.ticksToSeconds(note.tick);
|
|
65
|
+
const endSec = tempoMap.ticksToSeconds(note.tick + note.durTicks);
|
|
66
|
+
events.push({
|
|
67
|
+
trackId: note.trackId,
|
|
68
|
+
midi: note.midi,
|
|
69
|
+
startSec,
|
|
70
|
+
// Never zero: a rounding error should not silence a note.
|
|
71
|
+
durationSec: Math.max(0.01, endSec - startSec),
|
|
72
|
+
velocity: note.velocity / 127,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
events.sort((a, b) => a.startSec - b.startSec);
|
|
76
|
+
const lastEnd = events.reduce((max, e) => Math.max(max, e.startSec + e.durationSec), 0);
|
|
77
|
+
return { tracks, events, durationSec: lastEnd + TAIL_SEC };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=render-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-events.js","sourceRoot":"","sources":["../../src/shared/render-events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which notes are sounding at a given moment, by two monotonic cursors.
|
|
3
|
+
*
|
|
4
|
+
* Replaces deriving the set from a list of pending note-offs the pump walked
|
|
5
|
+
* every tick — a list that no longer exists, because releases happen in the
|
|
6
|
+
* worklet now. One cursor over notes by start, one over the same notes by end;
|
|
7
|
+
* both only ever move forward, so a step costs the number of notes that
|
|
8
|
+
* actually started or stopped rather than the size of the score.
|
|
9
|
+
*
|
|
10
|
+
* `advanceTo` returns `null` when nothing changed, which is the point. The
|
|
11
|
+
* engine used to hand its observer a freshly-allocated array twenty times a
|
|
12
|
+
* second whether or not a single note had begun or ended, waking every
|
|
13
|
+
* consumer downstream — notation colour, the piano keyboard's key lights — for
|
|
14
|
+
* nothing.
|
|
15
|
+
*/
|
|
16
|
+
import type { SoundingNote } from '@sudobility/music_types';
|
|
17
|
+
import type { PlaybackNote as ScheduledNote } from '@sudobility/music_types';
|
|
18
|
+
export declare class SoundingSet {
|
|
19
|
+
private byStart;
|
|
20
|
+
private byEnd;
|
|
21
|
+
private startCursor;
|
|
22
|
+
private endCursor;
|
|
23
|
+
/**
|
|
24
|
+
* Keyed by note id so an end can remove the right entry, valued by the
|
|
25
|
+
* resolved note so a consumer never has to search the score for the track and
|
|
26
|
+
* pitch the scheduler already knew.
|
|
27
|
+
*/
|
|
28
|
+
private sounding;
|
|
29
|
+
/** Forces the next `advanceTo` to emit, so a reset is always observable. */
|
|
30
|
+
private dirty;
|
|
31
|
+
load(notes: readonly ScheduledNote[], secondsForTick: (tick: number) => number): void;
|
|
32
|
+
/**
|
|
33
|
+
* Drops everything sounding and moves both cursors to `positionSeconds`.
|
|
34
|
+
*
|
|
35
|
+
* A note spanning that moment is deliberately *not* restored: the engine
|
|
36
|
+
* silences the synth on every seek and stop, so nothing is audible there
|
|
37
|
+
* either. Highlighting a note that is not sounding would be a lie.
|
|
38
|
+
*/
|
|
39
|
+
reset(positionSeconds: number): void;
|
|
40
|
+
/** The ids sounding at `positionSeconds`, or `null` if they are what they were. */
|
|
41
|
+
advanceTo(positionSeconds: number): SoundingNote[] | null;
|
|
42
|
+
/** How many notes are sounding. */
|
|
43
|
+
get size(): number;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=sounding-set.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sounding-set.d.ts","sourceRoot":"","sources":["../../src/shared/sounding-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,IAAI,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7E,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAA2D;IAC1E,OAAO,CAAC,KAAK,CAAqD;IAClE,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,SAAS,CAAK;IACtB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAmC;IACnD,4EAA4E;IAC5E,OAAO,CAAC,KAAK,CAAQ;IAErB,IAAI,CACF,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GACvC,IAAI;IAgBP;;;;;;OAMG;IACH,KAAK,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI;IAWpC,mFAAmF;IACnF,SAAS,CAAC,eAAe,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI;IA2BzD,mCAAmC;IACnC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export class SoundingSet {
|
|
2
|
+
byStart = [];
|
|
3
|
+
byEnd = [];
|
|
4
|
+
startCursor = 0;
|
|
5
|
+
endCursor = 0;
|
|
6
|
+
/**
|
|
7
|
+
* Keyed by note id so an end can remove the right entry, valued by the
|
|
8
|
+
* resolved note so a consumer never has to search the score for the track and
|
|
9
|
+
* pitch the scheduler already knew.
|
|
10
|
+
*/
|
|
11
|
+
sounding = new Map();
|
|
12
|
+
/** Forces the next `advanceTo` to emit, so a reset is always observable. */
|
|
13
|
+
dirty = true;
|
|
14
|
+
load(notes, secondsForTick) {
|
|
15
|
+
this.byStart = notes
|
|
16
|
+
.map(n => ({
|
|
17
|
+
note: { noteId: n.noteId, trackId: n.trackId, midi: n.midi },
|
|
18
|
+
startSeconds: secondsForTick(n.tick),
|
|
19
|
+
}))
|
|
20
|
+
.sort((a, b) => a.startSeconds - b.startSeconds);
|
|
21
|
+
this.byEnd = notes
|
|
22
|
+
.map(n => ({
|
|
23
|
+
noteId: n.noteId,
|
|
24
|
+
endSeconds: secondsForTick(n.tick + n.durTicks),
|
|
25
|
+
}))
|
|
26
|
+
.sort((a, b) => a.endSeconds - b.endSeconds);
|
|
27
|
+
this.reset(0);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Drops everything sounding and moves both cursors to `positionSeconds`.
|
|
31
|
+
*
|
|
32
|
+
* A note spanning that moment is deliberately *not* restored: the engine
|
|
33
|
+
* silences the synth on every seek and stop, so nothing is audible there
|
|
34
|
+
* either. Highlighting a note that is not sounding would be a lie.
|
|
35
|
+
*/
|
|
36
|
+
reset(positionSeconds) {
|
|
37
|
+
this.sounding.clear();
|
|
38
|
+
this.startCursor = lowerBound(this.byStart, positionSeconds, e => e.startSeconds);
|
|
39
|
+
this.endCursor = lowerBound(this.byEnd, positionSeconds, e => e.endSeconds);
|
|
40
|
+
this.dirty = true;
|
|
41
|
+
}
|
|
42
|
+
/** The ids sounding at `positionSeconds`, or `null` if they are what they were. */
|
|
43
|
+
advanceTo(positionSeconds) {
|
|
44
|
+
let changed = this.dirty;
|
|
45
|
+
this.dirty = false;
|
|
46
|
+
while (this.startCursor < this.byStart.length &&
|
|
47
|
+
this.byStart[this.startCursor].startSeconds <= positionSeconds) {
|
|
48
|
+
const { note } = this.byStart[this.startCursor];
|
|
49
|
+
this.sounding.set(note.noteId, note);
|
|
50
|
+
this.startCursor += 1;
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
while (this.endCursor < this.byEnd.length &&
|
|
54
|
+
this.byEnd[this.endCursor].endSeconds <= positionSeconds) {
|
|
55
|
+
// Deleting an id that was never added — its start was skipped by a reset
|
|
56
|
+
// — is a no-op, which is what makes seeking into a held note safe.
|
|
57
|
+
if (this.sounding.delete(this.byEnd[this.endCursor].noteId))
|
|
58
|
+
changed = true;
|
|
59
|
+
this.endCursor += 1;
|
|
60
|
+
}
|
|
61
|
+
return changed ? [...this.sounding.values()] : null;
|
|
62
|
+
}
|
|
63
|
+
/** How many notes are sounding. */
|
|
64
|
+
get size() {
|
|
65
|
+
return this.sounding.size;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** First index whose key is >= `value`. */
|
|
69
|
+
function lowerBound(items, value, key) {
|
|
70
|
+
let lo = 0;
|
|
71
|
+
let hi = items.length;
|
|
72
|
+
while (lo < hi) {
|
|
73
|
+
const mid = (lo + hi) >> 1;
|
|
74
|
+
if (key(items[mid]) < value)
|
|
75
|
+
lo = mid + 1;
|
|
76
|
+
else
|
|
77
|
+
hi = mid;
|
|
78
|
+
}
|
|
79
|
+
return lo;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=sounding-set.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sounding-set.js","sourceRoot":"","sources":["../../src/shared/sounding-set.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-built `PlaybackPlan`s for engine tests.
|
|
3
|
+
*
|
|
4
|
+
* The engines take a plan, not a score, so their tests build one directly
|
|
5
|
+
* rather than importing a score fixture and a converter from music_lib —
|
|
6
|
+
* which is what kept the platform layer depending on the domain. A plan is
|
|
7
|
+
* plain data, so this is also clearer: a test that needs two notes says so
|
|
8
|
+
* instead of asserting against whatever a fixture happens to contain.
|
|
9
|
+
*
|
|
10
|
+
* The default tempo is linear: 120 BPM at ppq 480 is 960 ticks per second, so
|
|
11
|
+
* `ticksToSeconds` is `tick / 960` and the arithmetic in assertions stays
|
|
12
|
+
* readable.
|
|
13
|
+
*/
|
|
14
|
+
import type { PlaybackNote, PlaybackPlan, PlaybackTrack, TempoConversion } from '@sudobility/music_types';
|
|
15
|
+
export declare const TICKS_PER_SECOND = 960;
|
|
16
|
+
export declare const LINEAR_TEMPO: TempoConversion;
|
|
17
|
+
export declare function testTrack(over: Partial<PlaybackTrack> & {
|
|
18
|
+
id: string;
|
|
19
|
+
}): PlaybackTrack;
|
|
20
|
+
export declare function testNote(over: Partial<PlaybackNote> & {
|
|
21
|
+
trackId: string;
|
|
22
|
+
}): PlaybackNote;
|
|
23
|
+
export declare function testPlan(over?: Partial<PlaybackPlan>): PlaybackPlan;
|
|
24
|
+
/** `count` pitched tracks numbered `t0..`, each on its own GM program. */
|
|
25
|
+
export declare function planOfTracks(count: number, isPercussion?: boolean): PlaybackPlan;
|
|
26
|
+
/**
|
|
27
|
+
* Two tracks over four bars of 4/4 — a quarter note per beat, alternating.
|
|
28
|
+
*
|
|
29
|
+
* The length is load-bearing at both ends. Long enough that the transport tests,
|
|
30
|
+
* which advance the clock by seconds and assert on scheduling several seconds
|
|
31
|
+
* ahead, still have music left to schedule; short enough that the sounding-note
|
|
32
|
+
* test, which steps ten seconds looking for the set to empty, sees it end. At
|
|
33
|
+
* 960 ticks per second this runs eight seconds.
|
|
34
|
+
*/
|
|
35
|
+
export declare function twoTrackPlan(): PlaybackPlan;
|
|
36
|
+
//# sourceMappingURL=test-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-plan.d.ts","sourceRoot":"","sources":["../../src/shared/test-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,EAChB,MAAM,yBAAyB,CAAC;AAEjC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,eAAO,MAAM,YAAY,EAAE,eAG1B,CAAC;AAEF,wBAAgB,SAAS,CACvB,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5C,aAAa,CAcf;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAChD,YAAY,CAYd;AAED,wBAAgB,QAAQ,CAAC,IAAI,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,YAAY,CAevE;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,YAAY,UAAQ,GACnB,YAAY,CAMd;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAoB3C"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const TICKS_PER_SECOND = 960;
|
|
2
|
+
export const LINEAR_TEMPO = {
|
|
3
|
+
ticksToSeconds: tick => tick / TICKS_PER_SECOND,
|
|
4
|
+
secondsToTicks: seconds => seconds * TICKS_PER_SECOND,
|
|
5
|
+
};
|
|
6
|
+
export function testTrack(over) {
|
|
7
|
+
const midiProgram = over.midiProgram ?? 0;
|
|
8
|
+
return {
|
|
9
|
+
midiProgram,
|
|
10
|
+
instrumentName: 'x',
|
|
11
|
+
isPercussion: false,
|
|
12
|
+
volume: 1,
|
|
13
|
+
pan: 0,
|
|
14
|
+
muted: false,
|
|
15
|
+
solo: false,
|
|
16
|
+
voiceProgram: midiProgram,
|
|
17
|
+
voiceName: 'x',
|
|
18
|
+
...over,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function testNote(over) {
|
|
22
|
+
return {
|
|
23
|
+
tick: 0,
|
|
24
|
+
durTicks: 480,
|
|
25
|
+
midi: 60,
|
|
26
|
+
// MIDI's 0-127, which is the unit `PlaybackNote` carries — `RenderPlan` is
|
|
27
|
+
// the one that uses 0..1. This read 0.8 for a while, which both engines
|
|
28
|
+
// treat as the quietest note there is.
|
|
29
|
+
velocity: 96,
|
|
30
|
+
noteId: `n-${over.tick ?? 0}-${over.trackId}`,
|
|
31
|
+
...over,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function testPlan(over = {}) {
|
|
35
|
+
const tracks = over.tracks ?? [testTrack({ id: 't0' })];
|
|
36
|
+
const notes = over.notes ?? [];
|
|
37
|
+
return {
|
|
38
|
+
tracks,
|
|
39
|
+
notes,
|
|
40
|
+
clicks: over.clicks ?? [],
|
|
41
|
+
tempo: over.tempo ?? LINEAR_TEMPO,
|
|
42
|
+
// Identity by default: an engine test is about scheduling, not repeats,
|
|
43
|
+
// and this is what a score with no repeats produces.
|
|
44
|
+
timeline: over.timeline ?? { segments: [], durationTicks: 0 },
|
|
45
|
+
durationTicks: over.durationTicks ??
|
|
46
|
+
notes.reduce((n, note) => Math.max(n, note.tick + note.durTicks), 0),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** `count` pitched tracks numbered `t0..`, each on its own GM program. */
|
|
50
|
+
export function planOfTracks(count, isPercussion = false) {
|
|
51
|
+
return testPlan({
|
|
52
|
+
tracks: Array.from({ length: count }, (_, i) => testTrack({ id: `t${i}`, midiProgram: i, isPercussion, voiceProgram: i })),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Two tracks over four bars of 4/4 — a quarter note per beat, alternating.
|
|
57
|
+
*
|
|
58
|
+
* The length is load-bearing at both ends. Long enough that the transport tests,
|
|
59
|
+
* which advance the clock by seconds and assert on scheduling several seconds
|
|
60
|
+
* ahead, still have music left to schedule; short enough that the sounding-note
|
|
61
|
+
* test, which steps ten seconds looking for the set to empty, sees it end. At
|
|
62
|
+
* 960 ticks per second this runs eight seconds.
|
|
63
|
+
*/
|
|
64
|
+
export function twoTrackPlan() {
|
|
65
|
+
const BEATS = 16;
|
|
66
|
+
const tracks = [
|
|
67
|
+
testTrack({ id: 't0', midiProgram: 0, voiceProgram: 0 }),
|
|
68
|
+
testTrack({ id: 't1', midiProgram: 1, voiceProgram: 1 }),
|
|
69
|
+
];
|
|
70
|
+
const notes = Array.from({ length: BEATS }, (_, i) => testNote({
|
|
71
|
+
trackId: i % 2 === 0 ? 't0' : 't1',
|
|
72
|
+
tick: i * 480,
|
|
73
|
+
durTicks: 480,
|
|
74
|
+
midi: 60 + (i % 12),
|
|
75
|
+
noteId: `n${i}`,
|
|
76
|
+
}));
|
|
77
|
+
const clicks = Array.from({ length: BEATS }, (_, i) => ({
|
|
78
|
+
tick: i * 480,
|
|
79
|
+
accent: i % 4 === 0,
|
|
80
|
+
}));
|
|
81
|
+
return testPlan({ tracks, notes, clicks });
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=test-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-plan.js","sourceRoot":"","sources":["../../src/shared/test-plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The player singleton.
|
|
3
|
+
*
|
|
4
|
+
* Same shape as the rest of the family's services — initialise once at
|
|
5
|
+
* start-up, read it everywhere, reset it in tests.
|
|
6
|
+
*
|
|
7
|
+
* A singleton because there is one transport: one piece of music is playing,
|
|
8
|
+
* and two players would be two playheads, which is exactly the disagreement
|
|
9
|
+
* `MusicPosition` exists to prevent.
|
|
10
|
+
*/
|
|
11
|
+
import type { IMusicPlayer } from './types.js';
|
|
12
|
+
export declare class MusicPlayerNotInitializedError extends Error {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Idempotent, so a re-mounting composition root does not swap the transport out
|
|
17
|
+
* from under everything subscribed to it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function initializeMusicPlayer(player: IMusicPlayer): IMusicPlayer;
|
|
20
|
+
export declare function getMusicPlayer(): IMusicPlayer;
|
|
21
|
+
/** Test-only: clears the singleton so suites cannot leak into each other. */
|
|
22
|
+
export declare function resetMusicPlayer(): void;
|
|
23
|
+
//# sourceMappingURL=singleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../src/singleton.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,qBAAa,8BAA+B,SAAQ,KAAK;;CAOxD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAGxE;AAED,wBAAgB,cAAc,IAAI,YAAY,CAG7C;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
let instance = null;
|
|
2
|
+
export class MusicPlayerNotInitializedError extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super('The music player has not been initialized. Call initializeMusicPlayer() from your app composition root before using playback.');
|
|
5
|
+
this.name = 'MusicPlayerNotInitializedError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Idempotent, so a re-mounting composition root does not swap the transport out
|
|
10
|
+
* from under everything subscribed to it.
|
|
11
|
+
*/
|
|
12
|
+
export function initializeMusicPlayer(player) {
|
|
13
|
+
if (!instance)
|
|
14
|
+
instance = player;
|
|
15
|
+
return instance;
|
|
16
|
+
}
|
|
17
|
+
export function getMusicPlayer() {
|
|
18
|
+
if (!instance)
|
|
19
|
+
throw new MusicPlayerNotInitializedError();
|
|
20
|
+
return instance;
|
|
21
|
+
}
|
|
22
|
+
/** Test-only: clears the singleton so suites cannot leak into each other. */
|
|
23
|
+
export function resetMusicPlayer() {
|
|
24
|
+
instance = null;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=singleton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"singleton.js","sourceRoot":"","sources":["../src/singleton.ts"],"names":[],"mappings":"AAYA,IAAI,QAAQ,GAAwB,IAAI,CAAC;AAEzC,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IACvD;QACE,KAAK,CACH,+HAA+H,CAChI,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC/C,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAoB;IACxD,IAAI,CAAC,QAAQ;QAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,8BAA8B,EAAE,CAAC;IAC1D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB;IAC9B,QAAQ,GAAG,IAAI,CAAC;AAClB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { PlaybackBus } from './shared/bus.js';
|
|
2
|
+
import type { PlaybackLoadState, PlaybackTrack, Score, ScoreRange, SoundingNote, TransportPlaybackState } from '@sudobility/music_types';
|
|
3
|
+
export type Unsubscribe = () => void;
|
|
4
|
+
/**
|
|
5
|
+
* The transport.
|
|
6
|
+
*
|
|
7
|
+
* Takes a `Score`, not a plan: this package owns `playbackPlan`, so making the
|
|
8
|
+
* caller build one first would be a two-step dance whose second step is here.
|
|
9
|
+
* The *engine* underneath still takes a `PlaybackPlan` and does no score maths,
|
|
10
|
+
* which is where music_io's "handed a plan, never a score" rule was always
|
|
11
|
+
* actually about.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately has no caret, and no `togglePlay`, `seekToMeasure`,
|
|
14
|
+
* `previousMeasure`, `nextMeasure` or loop-from-selection. Those read and write
|
|
15
|
+
* the caret or the selection, both of which are editing state; a copy of either
|
|
16
|
+
* here is a second thing that can disagree with the first. See `MusicPosition`
|
|
17
|
+
* in music_types for what that cost last time. They live in music_lib's
|
|
18
|
+
* playback adapter and compose the primitives below.
|
|
19
|
+
*/
|
|
20
|
+
export interface IMusicPlayer {
|
|
21
|
+
/**
|
|
22
|
+
* Adopts a score.
|
|
23
|
+
*
|
|
24
|
+
* **While playing, this is a mix change and nothing else** — the note queue is
|
|
25
|
+
* kept. The edit lock is what makes that safe: while the transport runs, the
|
|
26
|
+
* only score change that can reach here is a mix change. A score arriving
|
|
27
|
+
* from outside that path (a generation result, an opened snapshot) must be
|
|
28
|
+
* preceded by `stop()`, or it will be heard as the old score.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Rejects on failure rather than swallowing. The host translates: the message
|
|
32
|
+
* a user sees is a localized string, and this package carries no copy.
|
|
33
|
+
*/
|
|
34
|
+
load(score: Score, opts?: {
|
|
35
|
+
visibleTrackIds?: readonly string[];
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
play(): Promise<void>;
|
|
38
|
+
pause(): void;
|
|
39
|
+
stop(): void;
|
|
40
|
+
/** Seeks to a *written* tick; the first performance of it, if repeats make several. */
|
|
41
|
+
seek(scoreTick: number): void;
|
|
42
|
+
setLoop(range: ScoreRange | null): void;
|
|
43
|
+
setTempoMultiplier(multiplier: number): void;
|
|
44
|
+
setMetronome(enabled: boolean): void;
|
|
45
|
+
setMasterVolume(volume: number): void;
|
|
46
|
+
/** Live, without rebuilding the note queue: mixing is not editing. */
|
|
47
|
+
applyMix(tracks: readonly PlaybackTrack[]): void;
|
|
48
|
+
/** A track sounds only if it is visible *and* not muted. */
|
|
49
|
+
setVisibleTracks(visibleTrackIds: readonly string[]): void;
|
|
50
|
+
/**
|
|
51
|
+
* Sounds a pitch for as long as it is held — auditioning a key while editing.
|
|
52
|
+
*
|
|
53
|
+
* Touches no transport state on purpose: no caret move, no play/pause change,
|
|
54
|
+
* and no appearance in the active-note highlighting that follows the score.
|
|
55
|
+
*
|
|
56
|
+
* `isPercussion` must be passed for a percussion-clef track, or the tap sounds
|
|
57
|
+
* a pitched instrument while the same note plays back as a drum. The program
|
|
58
|
+
* is resolved to a voice here rather than by the caller, because a percussion
|
|
59
|
+
* program addresses a kit and only this package's tables know which.
|
|
60
|
+
*/
|
|
61
|
+
noteOn(midi: number, program: number, isPercussion?: boolean): void;
|
|
62
|
+
noteOff(midi: number): void;
|
|
63
|
+
/**
|
|
64
|
+
* The three high-frequency channels, as one object.
|
|
65
|
+
*
|
|
66
|
+
* Exposed rather than wrapped so a host binds React to the same bus the
|
|
67
|
+
* engine publishes into — two would be two answers to "what is sounding".
|
|
68
|
+
* `positionTick` and `sounding` hang off it for a subscriber joining
|
|
69
|
+
* mid-playback, which is why they are held at all.
|
|
70
|
+
*/
|
|
71
|
+
readonly bus: PlaybackBus;
|
|
72
|
+
onPosition(fn: (scoreTick: number) => void): Unsubscribe;
|
|
73
|
+
onSounding(fn: (notes: readonly SoundingNote[]) => void): Unsubscribe;
|
|
74
|
+
onTransport(fn: (state: TransportPlaybackState) => void): Unsubscribe;
|
|
75
|
+
/**
|
|
76
|
+
* Synth load progress. Not one of the bus's three high-frequency channels —
|
|
77
|
+
* it reports per percent and behaves like ordinary React state, so the host
|
|
78
|
+
* puts it in its store. It is here because the host has no other way to hear
|
|
79
|
+
* about it.
|
|
80
|
+
*/
|
|
81
|
+
onLoadState(fn: (state: PlaybackLoadState) => void): Unsubscribe;
|
|
82
|
+
dispose(): void;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,KAAK,EACL,UAAU,EACV,YAAY,EACZ,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;IAEb,uFAAuF;IACvF,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAAC;IACxC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACrC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtC,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IAEjD,4DAA4D;IAC5D,gBAAgB,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAE3D;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACpE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAE1B,UAAU,CAAC,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,WAAW,CAAC;IACzD,UAAU,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,KAAK,IAAI,GAAG,WAAW,CAAC;IACtE,WAAW,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,GAAG,WAAW,CAAC;IACtE;;;;;OAKG;IACH,WAAW,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,WAAW,CAAC;IAEjE,OAAO,IAAI,IAAI,CAAC;CACjB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SynthesizerLike } from './synth-types.js';
|
|
2
|
+
export type LoadedOfflineSynth = {
|
|
3
|
+
synth: SynthesizerLike;
|
|
4
|
+
sfontId: number;
|
|
5
|
+
sampleRate: number;
|
|
6
|
+
};
|
|
7
|
+
export type OfflineSynthRequest = {
|
|
8
|
+
fluidsynthModuleUrl: string;
|
|
9
|
+
fontUrl: string;
|
|
10
|
+
loadFont: (url: string) => Promise<ArrayBuffer>;
|
|
11
|
+
sampleRate: number;
|
|
12
|
+
/**
|
|
13
|
+
* Builds the synth. Injected only by tests — the real path first loads
|
|
14
|
+
* libfluidsynth into the page through a script tag, which a unit test has no
|
|
15
|
+
* way to satisfy. Supplying this skips that bring-up, the same seam
|
|
16
|
+
* `SynthHost` exposes for the same reason.
|
|
17
|
+
*/
|
|
18
|
+
createSynth?: () => SynthesizerLike;
|
|
19
|
+
};
|
|
20
|
+
/** The shared offline synth, building it on first use. */
|
|
21
|
+
export declare function getOfflineSynth(request: OfflineSynthRequest): Promise<LoadedOfflineSynth>;
|
|
22
|
+
/** Drops the shared synth. Exposed for tests and teardown, not used in normal flow. */
|
|
23
|
+
export declare function releaseOfflineSynth(): void;
|
|
24
|
+
//# sourceMappingURL=offline-synth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline-synth.d.ts","sourceRoot":"","sources":["../../../src/web/audio/offline-synth.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAUxD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AA6BF,MAAM,MAAM,mBAAmB,GAAG;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,eAAe,CAAC;CACrC,CAAC;AAEF,0DAA0D;AAC1D,wBAAsB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAgC/F;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A long-lived synth for offline rendering, loaded once per page.
|
|
3
|
+
*
|
|
4
|
+
* Export cannot share playback's synth: Web Audio nodes belong to the context
|
|
5
|
+
* that created them, playback runs on a realtime `AudioContext`, and rendering
|
|
6
|
+
* faster than realtime needs an `OfflineAudioContext`. Nothing bridges the two.
|
|
7
|
+
* Worse, an `OfflineAudioContext` renders exactly once, so the previous design
|
|
8
|
+
* built a fresh worklet and reloaded the 23MB soundfont on *every export* —
|
|
9
|
+
* about five seconds each time, with libfluidsynth's build-stub notices along
|
|
10
|
+
* for the ride.
|
|
11
|
+
*
|
|
12
|
+
* `Synthesizer.render()` avoids all of it by needing no audio context at all:
|
|
13
|
+
* it fills buffers straight from the WASM synth. So one synth is built, the
|
|
14
|
+
* font is loaded into it once, and every export after the first is immediate.
|
|
15
|
+
*
|
|
16
|
+
* This one runs on the main thread rather than in a worklet, which is fine
|
|
17
|
+
* precisely because it is offline — there is no audio callback to starve.
|
|
18
|
+
*/
|
|
19
|
+
import { CHANNELS_PER_INSTANCE } from '../playback/channel-allocator.js';
|
|
20
|
+
import { seedMainThreadQuietModule } from '../playback/quiet-stub-notices.js';
|
|
21
|
+
/** Matches playback's ceiling, so an export sounds like what was heard. */
|
|
22
|
+
const OFFLINE_POLYPHONY = 2048;
|
|
23
|
+
/** Resolves once `libfluidsynth-*.js` has been loaded and its WASM is ready. */
|
|
24
|
+
let fluidsynthReady = null;
|
|
25
|
+
/** The loaded synth, keyed so a changed font or rate rebuilds rather than lying. */
|
|
26
|
+
let cached = null;
|
|
27
|
+
/**
|
|
28
|
+
* Loads libfluidsynth into the *main thread*.
|
|
29
|
+
*
|
|
30
|
+
* A script tag rather than `addModule`: the non-worklet synth reads the module
|
|
31
|
+
* from the global scope, where `addModule` would put it inside the worklet
|
|
32
|
+
* instead. Memoised, since loading it twice would replace the global mid-flight.
|
|
33
|
+
*/
|
|
34
|
+
async function ensureFluidsynth(moduleUrl) {
|
|
35
|
+
fluidsynthReady ??= (async () => {
|
|
36
|
+
if (typeof document === 'undefined') {
|
|
37
|
+
throw new Error('Offline rendering needs a document to load libfluidsynth');
|
|
38
|
+
}
|
|
39
|
+
// Before the script runs: emscripten reads the global Module on load.
|
|
40
|
+
seedMainThreadQuietModule();
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
const script = document.createElement('script');
|
|
43
|
+
script.src = moduleUrl;
|
|
44
|
+
script.onload = () => resolve();
|
|
45
|
+
script.onerror = () => reject(new Error(`Failed to load ${moduleUrl}`));
|
|
46
|
+
document.head.appendChild(script);
|
|
47
|
+
});
|
|
48
|
+
const jsSynth = await import('js-synthesizer');
|
|
49
|
+
await jsSynth.waitForReady();
|
|
50
|
+
})();
|
|
51
|
+
return fluidsynthReady;
|
|
52
|
+
}
|
|
53
|
+
/** The shared offline synth, building it on first use. */
|
|
54
|
+
export async function getOfflineSynth(request) {
|
|
55
|
+
const key = `${request.fluidsynthModuleUrl}|${request.fontUrl}|${request.sampleRate}`;
|
|
56
|
+
if (cached?.key === key)
|
|
57
|
+
return await cached.synth;
|
|
58
|
+
const building = (async () => {
|
|
59
|
+
let synth;
|
|
60
|
+
if (request.createSynth) {
|
|
61
|
+
synth = request.createSynth();
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await ensureFluidsynth(request.fluidsynthModuleUrl);
|
|
65
|
+
const jsSynth = await import('js-synthesizer');
|
|
66
|
+
synth = new jsSynth.Synthesizer();
|
|
67
|
+
}
|
|
68
|
+
// The same channel count playback uses, because both share
|
|
69
|
+
// `allocateChannels`: on a 16-channel synth an export would address
|
|
70
|
+
// channels that do not exist and silently lose every track past the
|
|
71
|
+
// sixteenth. Polyphony matches too, so an export sounds like what was heard.
|
|
72
|
+
synth.init(request.sampleRate, {
|
|
73
|
+
midiChannelCount: CHANNELS_PER_INSTANCE,
|
|
74
|
+
polyphony: OFFLINE_POLYPHONY,
|
|
75
|
+
});
|
|
76
|
+
const sfontId = await synth.loadSFont(await request.loadFont(request.fontUrl));
|
|
77
|
+
return { synth, sfontId, sampleRate: request.sampleRate };
|
|
78
|
+
})();
|
|
79
|
+
cached = { key, synth: building };
|
|
80
|
+
try {
|
|
81
|
+
return await building;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
cached = null; // a failed build must not be handed to the next caller
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Drops the shared synth. Exposed for tests and teardown, not used in normal flow. */
|
|
89
|
+
export function releaseOfflineSynth() {
|
|
90
|
+
cached = null;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=offline-synth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline-synth.js","sourceRoot":"","sources":["../../../src/web/audio/offline-synth.ts"],"names":[],"mappings":"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"}
|