@sudobility/music_player 0.1.0 → 0.1.2
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 +73 -0
- package/dist/shared/bus.d.ts.map +1 -0
- package/dist/shared/bus.js +83 -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 +735 -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,93 @@
|
|
|
1
|
+
import { PlaybackBus } from '../shared/bus.js';
|
|
2
|
+
export class MockMusicPlayer {
|
|
3
|
+
calls = [];
|
|
4
|
+
/** The score it was last handed, for tests to assert on. */
|
|
5
|
+
loadedScore = null;
|
|
6
|
+
position = new Set();
|
|
7
|
+
soundingListeners = new Set();
|
|
8
|
+
transport = new Set();
|
|
9
|
+
loadState = new Set();
|
|
10
|
+
/** A real bus, so a consumer's tests can publish into it exactly as the engine would. */
|
|
11
|
+
bus = new PlaybackBus();
|
|
12
|
+
async load(score, opts = {}) {
|
|
13
|
+
this.loadedScore = score;
|
|
14
|
+
this.calls.push(opts.visibleTrackIds
|
|
15
|
+
? `load(visible:${opts.visibleTrackIds.length})`
|
|
16
|
+
: 'load');
|
|
17
|
+
}
|
|
18
|
+
async play() {
|
|
19
|
+
this.calls.push('play');
|
|
20
|
+
}
|
|
21
|
+
pause() {
|
|
22
|
+
this.calls.push('pause');
|
|
23
|
+
}
|
|
24
|
+
stop() {
|
|
25
|
+
this.calls.push('stop');
|
|
26
|
+
}
|
|
27
|
+
seek(tick) {
|
|
28
|
+
this.calls.push(`seek(${tick})`);
|
|
29
|
+
}
|
|
30
|
+
setLoop(range) {
|
|
31
|
+
this.calls.push(range ? `setLoop(${range.startTick},${range.endTick})` : 'clearLoop');
|
|
32
|
+
}
|
|
33
|
+
setTempoMultiplier(m) {
|
|
34
|
+
this.calls.push(`setTempoMultiplier(${m})`);
|
|
35
|
+
}
|
|
36
|
+
setMetronome(on) {
|
|
37
|
+
this.calls.push(`setMetronome(${on})`);
|
|
38
|
+
}
|
|
39
|
+
setMasterVolume(v) {
|
|
40
|
+
this.calls.push(`setMasterVolume(${v})`);
|
|
41
|
+
}
|
|
42
|
+
applyMix(tracks) {
|
|
43
|
+
this.calls.push(`applyMix(${tracks.length})`);
|
|
44
|
+
}
|
|
45
|
+
setVisibleTracks(ids) {
|
|
46
|
+
this.calls.push(`setVisibleTracks(${ids.length})`);
|
|
47
|
+
}
|
|
48
|
+
noteOn(midi, program, isPercussion = false) {
|
|
49
|
+
this.calls.push(`noteOn(${midi},${program},${isPercussion})`);
|
|
50
|
+
}
|
|
51
|
+
noteOff(midi) {
|
|
52
|
+
this.calls.push(`noteOff(${midi})`);
|
|
53
|
+
}
|
|
54
|
+
onPosition(fn) {
|
|
55
|
+
this.position.add(fn);
|
|
56
|
+
return () => this.position.delete(fn);
|
|
57
|
+
}
|
|
58
|
+
onSounding(fn) {
|
|
59
|
+
this.soundingListeners.add(fn);
|
|
60
|
+
return () => this.soundingListeners.delete(fn);
|
|
61
|
+
}
|
|
62
|
+
onTransport(fn) {
|
|
63
|
+
this.transport.add(fn);
|
|
64
|
+
return () => this.transport.delete(fn);
|
|
65
|
+
}
|
|
66
|
+
onLoadState(fn) {
|
|
67
|
+
this.loadState.add(fn);
|
|
68
|
+
return () => this.loadState.delete(fn);
|
|
69
|
+
}
|
|
70
|
+
dispose() {
|
|
71
|
+
this.calls.push('dispose');
|
|
72
|
+
}
|
|
73
|
+
// ---- test drivers: let a suite push events the way a real engine would ----
|
|
74
|
+
emitPosition(tick) {
|
|
75
|
+
this.bus.publishPosition(tick);
|
|
76
|
+
for (const fn of this.position)
|
|
77
|
+
fn(tick);
|
|
78
|
+
}
|
|
79
|
+
emitSounding(notes) {
|
|
80
|
+
this.bus.publishSounding(notes);
|
|
81
|
+
for (const fn of this.soundingListeners)
|
|
82
|
+
fn(notes);
|
|
83
|
+
}
|
|
84
|
+
emitTransport(state) {
|
|
85
|
+
for (const fn of this.transport)
|
|
86
|
+
fn(state);
|
|
87
|
+
}
|
|
88
|
+
emitLoadState(state) {
|
|
89
|
+
for (const fn of this.loadState)
|
|
90
|
+
fn(state);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mocks/index.ts"],"names":[],"mappings":"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"}
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { PlaybackLoadState, PlaybackTrack, Score, ScoreRange, SoundingNote, TransportPlaybackState } from '@sudobility/music_types';
|
|
2
|
+
import type { PlaybackEngine } from './engine.js';
|
|
3
|
+
import { PlaybackBus } from './shared/bus.js';
|
|
4
|
+
import type { IMusicPlayer, Unsubscribe } from './types.js';
|
|
5
|
+
export declare class MusicPlayer implements IMusicPlayer {
|
|
6
|
+
private readonly engine;
|
|
7
|
+
/**
|
|
8
|
+
* Where position and sounding notes go instead of the host's store.
|
|
9
|
+
*
|
|
10
|
+
* Both arrive far too often to route through React state — see `bus.ts`.
|
|
11
|
+
*/
|
|
12
|
+
readonly bus: PlaybackBus;
|
|
13
|
+
/**
|
|
14
|
+
* How the loaded plan's performance time maps onto the written score.
|
|
15
|
+
*
|
|
16
|
+
* Replaced whenever the plan is — a repeat added or removed changes it. The
|
|
17
|
+
* identity default means a player with nothing loaded translates nothing.
|
|
18
|
+
*/
|
|
19
|
+
private timeline;
|
|
20
|
+
/** The loaded plan's tempo conversion, so the playhead can be told how fast ticks pass. */
|
|
21
|
+
private tempo;
|
|
22
|
+
/** The transport's half/double-speed control, kept here since the store no longer holds it for us. */
|
|
23
|
+
private multiplier;
|
|
24
|
+
/** Mirrors the engine, so `load` can tell a mix change from a reload without asking the host. */
|
|
25
|
+
private state;
|
|
26
|
+
private score;
|
|
27
|
+
private visibleTrackIds;
|
|
28
|
+
private readonly loadStateListeners;
|
|
29
|
+
constructor(engine: PlaybackEngine);
|
|
30
|
+
load(score: Score, opts?: {
|
|
31
|
+
visibleTrackIds?: readonly string[];
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
play(): Promise<void>;
|
|
34
|
+
pause(): void;
|
|
35
|
+
stop(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Seeks to a *written* position.
|
|
38
|
+
*
|
|
39
|
+
* The engine is given the first performance position of that tick, which is
|
|
40
|
+
* what "play from here" means to somebody reading the page — the first time
|
|
41
|
+
* through, not the repeat. The caret is the host's to move.
|
|
42
|
+
*/
|
|
43
|
+
seek(scoreTick: number): void;
|
|
44
|
+
setLoop(range: ScoreRange | null): void;
|
|
45
|
+
/** The whole score, for a host that wants a loop but has no selection to derive one from. */
|
|
46
|
+
wholeScoreRange(): ScoreRange | null;
|
|
47
|
+
setTempoMultiplier(multiplier: number): void;
|
|
48
|
+
setMetronome(enabled: boolean): void;
|
|
49
|
+
setMasterVolume(volume: number): void;
|
|
50
|
+
applyMix(tracks: readonly PlaybackTrack[]): void;
|
|
51
|
+
setVisibleTracks(visibleTrackIds: readonly string[]): void;
|
|
52
|
+
noteOn(midi: number, program: number, isPercussion?: boolean): void;
|
|
53
|
+
noteOff(midi: number): void;
|
|
54
|
+
onPosition(fn: (scoreTick: number) => void): Unsubscribe;
|
|
55
|
+
onSounding(fn: (notes: readonly SoundingNote[]) => void): Unsubscribe;
|
|
56
|
+
onTransport(fn: (state: TransportPlaybackState) => void): Unsubscribe;
|
|
57
|
+
onLoadState(fn: (state: PlaybackLoadState) => void): Unsubscribe;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
/**
|
|
60
|
+
* How many score ticks pass per real second at the current position.
|
|
61
|
+
*
|
|
62
|
+
* Read off the plan's own tempo conversion — the same one the engine
|
|
63
|
+
* schedules against, so the playhead's smoothing and the audio agree about
|
|
64
|
+
* how fast the music is going. The multiplier is the transport's half/double
|
|
65
|
+
* speed control: the engine divides logical seconds by it, so one real second
|
|
66
|
+
* is that many logical seconds of music.
|
|
67
|
+
*/
|
|
68
|
+
private ticksPerSecond;
|
|
69
|
+
/**
|
|
70
|
+
* A track sounds only if it is visible *and* not muted.
|
|
71
|
+
*
|
|
72
|
+
* Visibility used to reach the notation and the print sheet but never the
|
|
73
|
+
* audio, so hiding a track removed it from view while it went on playing.
|
|
74
|
+
* Combining the two axes here rather than writing visibility into the
|
|
75
|
+
* channel's mute means an explicit mute survives being hidden and shown
|
|
76
|
+
* again — the score keeps the user's intent, and this only ever derives from
|
|
77
|
+
* it.
|
|
78
|
+
*/
|
|
79
|
+
private applyTrackAudibility;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAEV,iBAAiB,EAEjB,aAAa,EACb,KAAK,EACL,UAAU,EACV,YAAY,EACZ,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5D,qBAAa,WAAY,YAAW,YAAY;IAgClC,OAAO,CAAC,QAAQ,CAAC,MAAM;IA/BnC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,cAAqB;IAEjC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAA2D;IAE3E,2FAA2F;IAC3F,OAAO,CAAC,KAAK,CAAsC;IAEnD,sGAAsG;IACtG,OAAO,CAAC,UAAU,CAAK;IAEvB,iGAAiG;IACjG,OAAO,CAAC,KAAK,CAAqC;IAElD,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,eAAe,CAAkC;IAEzD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAE/B;gBAEyB,MAAM,EAAE,cAAc;IAsC7C,IAAI,CACR,KAAK,EAAE,KAAK,EACZ,IAAI,GAAE;QAAE,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAO,GACjD,OAAO,CAAC,IAAI,CAAC;IA4BV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAIZ;;;;;;OAMG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAK7B,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI;IAIvC,6FAA6F;IAC7F,eAAe,IAAI,UAAU,GAAG,IAAI;IAKpC,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAK5C,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIpC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIrC,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAIhD,gBAAgB,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAK1D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,UAAQ,GAAG,IAAI;IAMjE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3B,UAAU,CAAC,EAAE,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,WAAW;IAIxD,UAAU,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,KAAK,IAAI,GAAG,WAAW;IAIrE,WAAW,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,GAAG,WAAW;IAIrE,WAAW,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,WAAW;IAKhE,OAAO,IAAI,IAAI;IAKf;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAUtB;;;;;;;;;OASG;IACH,OAAO,CAAC,oBAAoB;CAS7B"}
|
package/dist/player.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport: everything `PlaybackController` did that was not the store.
|
|
3
|
+
*
|
|
4
|
+
* It came from music_lib, where it read Zustand directly for the score, the
|
|
5
|
+
* visible tracks, the selection and the caret. Those four are editing state and
|
|
6
|
+
* stayed behind; what arrives here is score maths and engine driving. The host
|
|
7
|
+
* binds the two together — see music_lib's playback adapter.
|
|
8
|
+
*/
|
|
9
|
+
import { scoreEndTick, sourceTickFor, performanceTickFor, } from '@sudobility/music_types';
|
|
10
|
+
import { getMusicPositionSource } from '@sudobility/music_types';
|
|
11
|
+
import { PlaybackBus } from './shared/bus.js';
|
|
12
|
+
import { playbackPlan, playbackTracks, resolveVoice } from './shared/plan.js';
|
|
13
|
+
export class MusicPlayer {
|
|
14
|
+
engine;
|
|
15
|
+
/**
|
|
16
|
+
* Where position and sounding notes go instead of the host's store.
|
|
17
|
+
*
|
|
18
|
+
* Both arrive far too often to route through React state — see `bus.ts`.
|
|
19
|
+
*/
|
|
20
|
+
bus = new PlaybackBus();
|
|
21
|
+
/**
|
|
22
|
+
* How the loaded plan's performance time maps onto the written score.
|
|
23
|
+
*
|
|
24
|
+
* Replaced whenever the plan is — a repeat added or removed changes it. The
|
|
25
|
+
* identity default means a player with nothing loaded translates nothing.
|
|
26
|
+
*/
|
|
27
|
+
timeline = { segments: [], durationTicks: 0 };
|
|
28
|
+
/** The loaded plan's tempo conversion, so the playhead can be told how fast ticks pass. */
|
|
29
|
+
tempo = null;
|
|
30
|
+
/** The transport's half/double-speed control, kept here since the store no longer holds it for us. */
|
|
31
|
+
multiplier = 1;
|
|
32
|
+
/** Mirrors the engine, so `load` can tell a mix change from a reload without asking the host. */
|
|
33
|
+
state = 'stopped';
|
|
34
|
+
score = null;
|
|
35
|
+
visibleTrackIds = null;
|
|
36
|
+
loadStateListeners = new Set();
|
|
37
|
+
constructor(engine) {
|
|
38
|
+
this.engine = engine;
|
|
39
|
+
engine.setObserver({
|
|
40
|
+
/*
|
|
41
|
+
Translated here, and only here.
|
|
42
|
+
|
|
43
|
+
The engine reports *performance* time, which differs from written time
|
|
44
|
+
the moment a repeat exists. Converting at this one boundary means the
|
|
45
|
+
caret, the following-scroll, the bar/beat readout and the position
|
|
46
|
+
scrubber all keep receiving a score tick and none of them has to know
|
|
47
|
+
repeats exist. Without repeats the timeline is the identity and this
|
|
48
|
+
costs a single comparison.
|
|
49
|
+
*/
|
|
50
|
+
onPositionTick: tick => {
|
|
51
|
+
const scoreTick = sourceTickFor(this.timeline, tick);
|
|
52
|
+
// Refreshed every report rather than once at play, so a fermata or a
|
|
53
|
+
// tempo change mid-piece projects at the speed actually in force.
|
|
54
|
+
getMusicPositionSource().setRate(this.ticksPerSecond(scoreTick));
|
|
55
|
+
this.bus.publishPosition(scoreTick);
|
|
56
|
+
},
|
|
57
|
+
onActiveNotes: notes => this.bus.publishSounding(notes),
|
|
58
|
+
onStateChange: state => {
|
|
59
|
+
this.state = state;
|
|
60
|
+
// Smoothing only applies while the transport advances, and the playhead
|
|
61
|
+
// re-anchors on the transition so a pause does not leave it projecting
|
|
62
|
+
// forward across the time spent stopped.
|
|
63
|
+
getMusicPositionSource().setPlaying(state === 'playing', this.ticksPerSecond(this.bus.positionTick));
|
|
64
|
+
this.bus.publishTransport(state);
|
|
65
|
+
},
|
|
66
|
+
// Optional on the contract: an engine with nothing to load never calls it.
|
|
67
|
+
onLoadStateChange: state => {
|
|
68
|
+
for (const listener of this.loadStateListeners)
|
|
69
|
+
listener(state);
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async load(score, opts = {}) {
|
|
74
|
+
this.score = score;
|
|
75
|
+
if (opts.visibleTrackIds !== undefined) {
|
|
76
|
+
this.visibleTrackIds = opts.visibleTrackIds;
|
|
77
|
+
}
|
|
78
|
+
if (this.state === 'playing') {
|
|
79
|
+
// Tracks only: rebuilding every note to change a gain would undo the
|
|
80
|
+
// reason this branch exists. The host's edit lock is what makes it safe —
|
|
81
|
+
// while playing, the only score change that can reach here is a mix
|
|
82
|
+
// change, and anything arriving from outside that path must `stop()`
|
|
83
|
+
// first or it will be heard as the old score.
|
|
84
|
+
this.engine.applyMix(playbackTracks(score));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const plan = playbackPlan(score);
|
|
88
|
+
// Kept in step with what the engine holds: a repeat added or removed
|
|
89
|
+
// changes the mapping, and a stale one would put the caret in the wrong
|
|
90
|
+
// bar for every frame until the next load.
|
|
91
|
+
this.timeline = plan.timeline;
|
|
92
|
+
this.tempo = plan.tempo;
|
|
93
|
+
await this.engine.load(plan);
|
|
94
|
+
// `load` reseeds every channel's mute from `Track.muted`, so hidden tracks
|
|
95
|
+
// would sound again without this.
|
|
96
|
+
this.applyTrackAudibility();
|
|
97
|
+
}
|
|
98
|
+
async play() {
|
|
99
|
+
await this.engine.play();
|
|
100
|
+
}
|
|
101
|
+
pause() {
|
|
102
|
+
this.engine.pause();
|
|
103
|
+
}
|
|
104
|
+
stop() {
|
|
105
|
+
this.engine.stop();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Seeks to a *written* position.
|
|
109
|
+
*
|
|
110
|
+
* The engine is given the first performance position of that tick, which is
|
|
111
|
+
* what "play from here" means to somebody reading the page — the first time
|
|
112
|
+
* through, not the repeat. The caret is the host's to move.
|
|
113
|
+
*/
|
|
114
|
+
seek(scoreTick) {
|
|
115
|
+
if (!this.score)
|
|
116
|
+
return;
|
|
117
|
+
this.engine.seek(performanceTickFor(this.timeline, Math.max(0, scoreTick)));
|
|
118
|
+
}
|
|
119
|
+
setLoop(range) {
|
|
120
|
+
this.engine.setLoop(range);
|
|
121
|
+
}
|
|
122
|
+
/** The whole score, for a host that wants a loop but has no selection to derive one from. */
|
|
123
|
+
wholeScoreRange() {
|
|
124
|
+
if (!this.score)
|
|
125
|
+
return null;
|
|
126
|
+
return { startTick: 0, endTick: scoreEndTick(this.score), trackIds: [] };
|
|
127
|
+
}
|
|
128
|
+
setTempoMultiplier(multiplier) {
|
|
129
|
+
this.multiplier = multiplier;
|
|
130
|
+
this.engine.setTempoMultiplier(multiplier);
|
|
131
|
+
}
|
|
132
|
+
setMetronome(enabled) {
|
|
133
|
+
this.engine.setMetronome(enabled);
|
|
134
|
+
}
|
|
135
|
+
setMasterVolume(volume) {
|
|
136
|
+
this.engine.setMasterVolume(volume);
|
|
137
|
+
}
|
|
138
|
+
applyMix(tracks) {
|
|
139
|
+
this.engine.applyMix([...tracks]);
|
|
140
|
+
}
|
|
141
|
+
setVisibleTracks(visibleTrackIds) {
|
|
142
|
+
this.visibleTrackIds = visibleTrackIds;
|
|
143
|
+
this.applyTrackAudibility();
|
|
144
|
+
}
|
|
145
|
+
noteOn(midi, program, isPercussion = false) {
|
|
146
|
+
// Resolved here: the engine has no GM tables, and a percussion program
|
|
147
|
+
// addresses a kit rather than an instrument.
|
|
148
|
+
this.engine.noteOn(midi, resolveVoice(program, isPercussion));
|
|
149
|
+
}
|
|
150
|
+
noteOff(midi) {
|
|
151
|
+
this.engine.noteOff(midi);
|
|
152
|
+
}
|
|
153
|
+
onPosition(fn) {
|
|
154
|
+
return this.bus.onPosition(fn);
|
|
155
|
+
}
|
|
156
|
+
onSounding(fn) {
|
|
157
|
+
return this.bus.onSounding(fn);
|
|
158
|
+
}
|
|
159
|
+
onTransport(fn) {
|
|
160
|
+
return this.bus.onTransport(fn);
|
|
161
|
+
}
|
|
162
|
+
onLoadState(fn) {
|
|
163
|
+
this.loadStateListeners.add(fn);
|
|
164
|
+
return () => this.loadStateListeners.delete(fn);
|
|
165
|
+
}
|
|
166
|
+
dispose() {
|
|
167
|
+
this.loadStateListeners.clear();
|
|
168
|
+
this.engine.dispose();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* How many score ticks pass per real second at the current position.
|
|
172
|
+
*
|
|
173
|
+
* Read off the plan's own tempo conversion — the same one the engine
|
|
174
|
+
* schedules against, so the playhead's smoothing and the audio agree about
|
|
175
|
+
* how fast the music is going. The multiplier is the transport's half/double
|
|
176
|
+
* speed control: the engine divides logical seconds by it, so one real second
|
|
177
|
+
* is that many logical seconds of music.
|
|
178
|
+
*/
|
|
179
|
+
ticksPerSecond(tick) {
|
|
180
|
+
if (!this.tempo)
|
|
181
|
+
return 0;
|
|
182
|
+
// A second of music, measured at the playhead: how many ticks fit between
|
|
183
|
+
// now and one second from now, which handles a tempo change mid-piece
|
|
184
|
+
// without needing to know the tempo map's shape.
|
|
185
|
+
const perSecond = this.tempo.secondsToTicks(this.tempo.ticksToSeconds(tick) + 1) - tick;
|
|
186
|
+
return Math.max(0, perSecond * this.multiplier);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A track sounds only if it is visible *and* not muted.
|
|
190
|
+
*
|
|
191
|
+
* Visibility used to reach the notation and the print sheet but never the
|
|
192
|
+
* audio, so hiding a track removed it from view while it went on playing.
|
|
193
|
+
* Combining the two axes here rather than writing visibility into the
|
|
194
|
+
* channel's mute means an explicit mute survives being hidden and shown
|
|
195
|
+
* again — the score keeps the user's intent, and this only ever derives from
|
|
196
|
+
* it.
|
|
197
|
+
*/
|
|
198
|
+
applyTrackAudibility() {
|
|
199
|
+
const score = this.score;
|
|
200
|
+
if (!score)
|
|
201
|
+
return;
|
|
202
|
+
const visible = this.visibleTrackIds;
|
|
203
|
+
for (const track of score.tracks) {
|
|
204
|
+
const hidden = visible !== null && !visible.includes(track.id);
|
|
205
|
+
this.engine.setTrackMute(track.id, track.muted || hidden);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline audio rendering on React Native.
|
|
3
|
+
*
|
|
4
|
+
* Export goes through the same samples as playback, for the same reason the web
|
|
5
|
+
* export goes through the same soundfont: the file is supposed to be a
|
|
6
|
+
* recording of what was heard, and two voicing paths drift apart the first time
|
|
7
|
+
* one is tuned.
|
|
8
|
+
*
|
|
9
|
+
* The engine could not simply be reused, because it schedules against a *live*
|
|
10
|
+
* context's clock and pumps notes 200ms at a time — rendering an export that
|
|
11
|
+
* way would take as long as the piece. `RenderPlan` is already fully resolved
|
|
12
|
+
* (seconds, not ticks; mute and solo applied by `renderEvents` in music_lib),
|
|
13
|
+
* so everything can be scheduled up front against an `OfflineAudioContext` and
|
|
14
|
+
* rendered as fast as the device manages. What is shared is the part that
|
|
15
|
+
* matters: `PackLibrary` picks the sample, and `planVoice` decides the gain and
|
|
16
|
+
* the release.
|
|
17
|
+
*/
|
|
18
|
+
import type { AudioRenderer, RenderTrack } from '@sudobility/music_types';
|
|
19
|
+
import type { AudioApi } from '../playback/audio-api.js';
|
|
20
|
+
export type OfflineRendererDeps = {
|
|
21
|
+
loadAudioApi?: () => Promise<AudioApi>;
|
|
22
|
+
fetchPack?: (url: string) => Promise<string>;
|
|
23
|
+
packBase?: string;
|
|
24
|
+
percussionBase?: string;
|
|
25
|
+
};
|
|
26
|
+
/** The pack a track's notes come from — the same rule live playback uses. */
|
|
27
|
+
export declare function packNameForRenderTrack(track: RenderTrack): string | null;
|
|
28
|
+
export declare function createRNSoundfontRenderer(deps?: OfflineRendererDeps): AudioRenderer;
|
|
29
|
+
//# sourceMappingURL=offline-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline-render.d.ts","sourceRoot":"","sources":["../../../src/rn/audio/offline-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,aAAa,EAA4B,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQpG,OAAO,KAAK,EAAE,QAAQ,EAAwC,MAAM,0BAA0B,CAAC;AAM/F,MAAM,MAAM,mBAAmB,GAAG;IAChC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAMxE;AASD,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,mBAAwB,GAAG,aAAa,CA0EvF"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { headroomTrimFor } from '../../shared/mix.js';
|
|
2
|
+
import { gmPackName, percussionPackName } from '../playback/gm-pack-name.js';
|
|
3
|
+
import { PackLibrary } from '../playback/pack-library.js';
|
|
4
|
+
import { RELEASE_SECONDS, planVoice } from '../playback/voice-plan.js';
|
|
5
|
+
import { sustains } from '../playback/expression.js';
|
|
6
|
+
import { applySustainLoop } from '../playback/sustain-loop.js';
|
|
7
|
+
import { loadAudioApi } from '../playback/audio-api.js';
|
|
8
|
+
const SAMPLE_RATE = 44100;
|
|
9
|
+
/** Stereo, so panning survives into the file. */
|
|
10
|
+
const CHANNELS = 2;
|
|
11
|
+
/** The pack a track's notes come from — the same rule live playback uses. */
|
|
12
|
+
export function packNameForRenderTrack(track) {
|
|
13
|
+
// The kit-versus-instrument distinction is already resolved into
|
|
14
|
+
// `voiceProgram`/`voiceName` by music_lib, which owns the GM tables.
|
|
15
|
+
return track.isPercussion
|
|
16
|
+
? percussionPackName(track.voiceProgram)
|
|
17
|
+
: gmPackName(track.voiceProgram, track.voiceName);
|
|
18
|
+
}
|
|
19
|
+
function defaultFetchPack(url) {
|
|
20
|
+
return fetch(url).then((r) => {
|
|
21
|
+
if (!r.ok)
|
|
22
|
+
throw new Error(`Sample pack ${url} responded ${r.status}`);
|
|
23
|
+
return r.text();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function createRNSoundfontRenderer(deps = {}) {
|
|
27
|
+
return {
|
|
28
|
+
async render(plan) {
|
|
29
|
+
const api = await (deps.loadAudioApi ?? loadAudioApi)();
|
|
30
|
+
if (!api.OfflineAudioContext) {
|
|
31
|
+
throw new Error('react-native-audio-api is missing OfflineAudioContext; export needs >=0.13.');
|
|
32
|
+
}
|
|
33
|
+
const library = new PackLibrary({
|
|
34
|
+
fetchPack: deps.fetchPack ?? defaultFetchPack,
|
|
35
|
+
decodeAudioData: (bytes) => api.decodeAudioData(bytes),
|
|
36
|
+
packBase: deps.packBase,
|
|
37
|
+
percussionBase: deps.percussionBase,
|
|
38
|
+
});
|
|
39
|
+
const packByTrack = new Map();
|
|
40
|
+
for (const track of plan.tracks) {
|
|
41
|
+
const name = packNameForRenderTrack(track);
|
|
42
|
+
if (name)
|
|
43
|
+
packByTrack.set(track.id, name);
|
|
44
|
+
}
|
|
45
|
+
// Only the packs notes are actually played from: a plan lists every track
|
|
46
|
+
// so the headroom is right, including silent ones, and downloading a pack
|
|
47
|
+
// for a track that sounds nothing would be minutes of nothing.
|
|
48
|
+
const sounding = new Set([...plan.events].map((e) => packByTrack.get(e.trackId)).filter(Boolean));
|
|
49
|
+
for (const name of sounding)
|
|
50
|
+
await library.ensure(name);
|
|
51
|
+
// Headroom is sized by how many tracks *exist*, not how many sound — the
|
|
52
|
+
// same rule live playback applies, or an export of a muted-heavy score
|
|
53
|
+
// comes out a couple of dB louder than what was heard.
|
|
54
|
+
const headroom = headroomTrimFor(plan.tracks.length);
|
|
55
|
+
// The tail of the last note has to fit inside the file.
|
|
56
|
+
const seconds = plan.durationSec + RELEASE_SECONDS;
|
|
57
|
+
const ctx = new api.OfflineAudioContext({
|
|
58
|
+
numberOfChannels: CHANNELS,
|
|
59
|
+
length: Math.ceil(seconds * SAMPLE_RATE),
|
|
60
|
+
sampleRate: SAMPLE_RATE,
|
|
61
|
+
});
|
|
62
|
+
const master = ctx.createGain();
|
|
63
|
+
master.gain.value = headroom;
|
|
64
|
+
master.connect(ctx.destination);
|
|
65
|
+
const trackById = new Map(plan.tracks.map((t) => [t.id, t]));
|
|
66
|
+
for (const event of plan.events) {
|
|
67
|
+
const packName = packByTrack.get(event.trackId);
|
|
68
|
+
const track = trackById.get(event.trackId);
|
|
69
|
+
if (!packName || !track)
|
|
70
|
+
continue;
|
|
71
|
+
const voicing = library.voice(packName, event.midi, track.isPercussion);
|
|
72
|
+
if (!voicing)
|
|
73
|
+
continue;
|
|
74
|
+
// `velocity` arrives 0..1 here but `planVoice` takes a 0..127 MIDI
|
|
75
|
+
// value, so it is scaled back up rather than passed through — getting
|
|
76
|
+
// this backwards renders a silent file, which is why it has a test.
|
|
77
|
+
const voice = planVoice({
|
|
78
|
+
atSeconds: event.startSec,
|
|
79
|
+
durationSeconds: event.durationSec,
|
|
80
|
+
velocity: event.velocity * 127,
|
|
81
|
+
trackGain: track.volume,
|
|
82
|
+
choice: voicing.choice,
|
|
83
|
+
// Same expression as playback — velocity curve, per-instrument
|
|
84
|
+
// release, velocity-to-brightness — so the file is a recording of
|
|
85
|
+
// what was heard. Percussion opts out for the same reason it does live.
|
|
86
|
+
program: track.isPercussion ? undefined : track.midiProgram,
|
|
87
|
+
});
|
|
88
|
+
const mayLoop = !track.isPercussion && sustains(track.midiProgram);
|
|
89
|
+
buildOfflineVoice(ctx, master, voicing.buffer, voice, track.pan, mayLoop);
|
|
90
|
+
}
|
|
91
|
+
const rendered = await ctx.startRendering();
|
|
92
|
+
return { samples: toMono(rendered), sampleRate: rendered.sampleRate };
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function buildOfflineVoice(ctx, master, buffer, voice, pan, mayLoop) {
|
|
97
|
+
const source = ctx.createBufferSource();
|
|
98
|
+
source.buffer = buffer;
|
|
99
|
+
if (voice.detuneCents !== 0)
|
|
100
|
+
source.detune.value = voice.detuneCents;
|
|
101
|
+
if (mayLoop)
|
|
102
|
+
applySustainLoop(source, buffer, voice.releaseAt - voice.startAt, voice.sampleMidi);
|
|
103
|
+
const amp = ctx.createGain();
|
|
104
|
+
amp.gain.setValueAtTime(voice.gain, voice.startAt);
|
|
105
|
+
amp.gain.setValueAtTime(voice.gain, voice.releaseAt);
|
|
106
|
+
amp.gain.linearRampToValueAtTime(0, voice.endAt);
|
|
107
|
+
const panner = ctx.createStereoPanner();
|
|
108
|
+
panner.pan.value = Math.max(-1, Math.min(1, pan));
|
|
109
|
+
if (voice.cutoffHz === null) {
|
|
110
|
+
source.connect(amp);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const filter = ctx.createBiquadFilter();
|
|
114
|
+
filter.type = 'lowpass';
|
|
115
|
+
filter.frequency.value = voice.cutoffHz;
|
|
116
|
+
source.connect(filter);
|
|
117
|
+
filter.connect(amp);
|
|
118
|
+
}
|
|
119
|
+
amp.connect(panner);
|
|
120
|
+
panner.connect(master);
|
|
121
|
+
source.start(voice.startAt);
|
|
122
|
+
source.stop(voice.endAt);
|
|
123
|
+
}
|
|
124
|
+
/** The `DecodedAudio` contract is mono; the render is stereo so panning is real. */
|
|
125
|
+
function toMono(buffer) {
|
|
126
|
+
const out = new Float32Array(buffer.length);
|
|
127
|
+
for (let c = 0; c < buffer.numberOfChannels; c += 1) {
|
|
128
|
+
const data = buffer.getChannelData(c);
|
|
129
|
+
for (let i = 0; i < data.length; i += 1)
|
|
130
|
+
out[i] += data[i] / buffer.numberOfChannels;
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=offline-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline-render.js","sourceRoot":"","sources":["../../../src/rn/audio/offline-render.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,iDAAiD;AACjD,MAAM,QAAQ,GAAG,CAAC,CAAC;AASnB,6EAA6E;AAC7E,MAAM,UAAU,sBAAsB,CAAC,KAAkB;IACvD,iEAAiE;IACjE,qEAAqE;IACrE,OAAO,KAAK,CAAC,YAAY;QACvB,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,YAAY,CAAC;QACxC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3B,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,GAAG,cAAc,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAA4B,EAAE;IACtE,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,IAAgB;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,EAAE,CAAC;YACxD,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;YACjG,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC;gBAC9B,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,gBAAgB;gBAC7C,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC;gBACtD,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,cAAc,EAAE,IAAI,CAAC,cAAc;aACpC,CAAC,CAAC;YAEH,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC9C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChC,MAAM,IAAI,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;gBAC3C,IAAI,IAAI;oBAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAC5C,CAAC;YACD,0EAA0E;YAC1E,0EAA0E;YAC1E,+DAA+D;YAC/D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC,CAAC;YAC9G,KAAK,MAAM,IAAI,IAAI,QAAQ;gBAAE,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAExD,yEAAyE;YACzE,uEAAuE;YACvE,uDAAuD;YACvD,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACrD,wDAAwD;YACxD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,GAAG,eAAe,CAAC;YACnD,MAAM,GAAG,GAA0B,IAAI,GAAG,CAAC,mBAAmB,CAAC;gBAC7D,gBAAgB,EAAE,QAAQ;gBAC1B,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC;gBACxC,UAAU,EAAE,WAAW;aACxB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;YAC7B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAEhC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAChD,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC3C,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK;oBAAE,SAAS;gBAElC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;gBACxE,IAAI,CAAC,OAAO;oBAAE,SAAS;gBAEvB,mEAAmE;gBACnE,sEAAsE;gBACtE,oEAAoE;gBACpE,MAAM,KAAK,GAAG,SAAS,CAAC;oBACtB,SAAS,EAAE,KAAK,CAAC,QAAQ;oBACzB,eAAe,EAAE,KAAK,CAAC,WAAW;oBAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,GAAG;oBAC9B,SAAS,EAAE,KAAK,CAAC,MAAM;oBACvB,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,+DAA+D;oBAC/D,kEAAkE;oBAClE,wEAAwE;oBACxE,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW;iBAC5D,CAAC,CAAC;gBAEH,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBACnE,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC5E,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,cAAc,EAAE,CAAC;YAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,CAAC;QACxE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CACxB,GAA0B,EAC1B,MAA2C,EAC3C,MAAqB,EACrB,KAAmC,EACnC,GAAW,EACX,OAAgB;IAEhB,MAAM,MAAM,GAAG,GAAG,CAAC,kBAAkB,EAAE,CAAC;IACxC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,IAAI,KAAK,CAAC,WAAW,KAAK,CAAC;QAAE,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC;IACrE,IAAI,OAAO;QAAE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAEjG,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;IAC7B,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACnD,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACrD,GAAG,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,GAAG,CAAC,kBAAkB,EAAE,CAAC;IACxC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAElD,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC5B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,GAAG,CAAC,kBAAkB,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC;QACxB,MAAM,CAAC,SAAS,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC;QACxC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IACD,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpB,MAAM,CAAC,OAAO,CAAC,MAAe,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,oFAAoF;AACpF,SAAS,MAAM,CAAC,MAAqB;IACnC,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAE,GAAG,MAAM,CAAC,gBAAgB,CAAC;IACxF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The React Native player: a sample-based FluidR3 engine.
|
|
3
|
+
*
|
|
4
|
+
* Playback is samples rather than the web's libfluidsynth-in-an-AudioWorklet —
|
|
5
|
+
* React Native has neither WebAssembly nor `addModule`, so that engine cannot
|
|
6
|
+
* be ported, only replaced. It is replaced with the *same font*, pre-rendered
|
|
7
|
+
* per note, so a phone plays the recordings a browser plays. See
|
|
8
|
+
* `playback/sample-engine.ts`.
|
|
9
|
+
*/
|
|
10
|
+
import { RNSamplePlaybackEngine } from './playback/sample-engine.js';
|
|
11
|
+
import { createRNSoundfontRenderer } from './audio/offline-render.js';
|
|
12
|
+
import { MusicPlayer } from '../player.js';
|
|
13
|
+
export * from '../types.js';
|
|
14
|
+
export * from '../singleton.js';
|
|
15
|
+
export { MusicPlayer, RNSamplePlaybackEngine, createRNSoundfontRenderer };
|
|
16
|
+
export { renderEvents } from '../shared/render-events.js';
|
|
17
|
+
export { playbackPlan, playbackTracks, resolveVoice } from '../shared/plan.js';
|
|
18
|
+
/**
|
|
19
|
+
* `expression-table` is deliberately *not* re-exported here: the app that owns
|
|
20
|
+
* the soundfont needs `MEASURED_FROM_SHA256` to check the two still agree, and
|
|
21
|
+
* reaching it through this entry drags in the RN optional peers, which a web
|
|
22
|
+
* app does not install. It has its own dependency-free subpath,
|
|
23
|
+
* `@sudobility/music_player/rn/expression-table`.
|
|
24
|
+
*/
|
|
25
|
+
export type RNMusicPlayerOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* Where the instrument packs are served from. An app that would rather not
|
|
28
|
+
* depend on a third party at runtime can copy them and point this at its own
|
|
29
|
+
* CDN — they are CC-BY 3.0, so that is allowed, and it is the recommended
|
|
30
|
+
* setup for anything shipping to a store.
|
|
31
|
+
*/
|
|
32
|
+
packBase?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Where the drum-kit packs are served from. **Required for percussion**;
|
|
35
|
+
* there is no default because nobody publishes usably-licensed GM percussion.
|
|
36
|
+
*/
|
|
37
|
+
percussionBase?: string;
|
|
38
|
+
};
|
|
39
|
+
/** The React Native player, built fresh. Hand it to `initializeMusicPlayer`. */
|
|
40
|
+
export declare function createMusicPlayer(options?: RNMusicPlayerOptions): MusicPlayer;
|
|
41
|
+
/**
|
|
42
|
+
* Renders a plan to PCM, faster than realtime.
|
|
43
|
+
*
|
|
44
|
+
* Scheduled up front against an `OfflineAudioContext` rather than in real time.
|
|
45
|
+
* Sharing `PackLibrary` and `planVoice` with the engine is what keeps the file
|
|
46
|
+
* a recording of what was heard. The caller encodes and saves; this package
|
|
47
|
+
* does not touch files.
|
|
48
|
+
*/
|
|
49
|
+
export declare function renderSamples(options?: RNMusicPlayerOptions): (plan: import("@sudobility/music_types").RenderPlan) => Promise<import("@sudobility/music_types").DecodedAudio>;
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rn/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC/E;;;;;;GAMG;AAEH,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,oBAAyB,GACjC,WAAW,CAOb;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,mHAK/D"}
|