pepperscore 0.2.0 → 0.4.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +26 -12
  3. package/README.md +127 -9
  4. package/dist/THIRD_PARTY_NOTICES.txt +1222 -0
  5. package/dist/assets/{import.worker-D0XfuAHu.js → import.worker-Rop-z7az.js} +2 -2
  6. package/dist/assets/verovio.worker-DQm-ikdG.js +7 -0
  7. package/dist/audio.d.ts +24 -0
  8. package/dist/audio.js +198 -0
  9. package/dist/audio.js.map +1 -0
  10. package/dist/components/InstrumentChooser.d.ts +17 -0
  11. package/dist/components/OctaveTool.d.ts +21 -0
  12. package/dist/components/PepperScore.d.ts +96 -1
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.js +3738 -3382
  15. package/dist/index.js.map +1 -1
  16. package/dist/instruments.d.ts +2 -0
  17. package/dist/instruments.js +2 -0
  18. package/dist/lib/chrome.d.ts +10 -2
  19. package/dist/lib/file-state.d.ts +45 -5
  20. package/dist/lib/header-controls.d.ts +7 -3
  21. package/dist/lib/hit-test.d.ts +18 -0
  22. package/dist/lib/instrument-model.d.ts +80 -0
  23. package/dist/lib/instrument-switch-state.d.ts +169 -0
  24. package/dist/lib/instrument-view.d.ts +92 -0
  25. package/dist/lib/instruments.d.ts +82 -0
  26. package/dist/lib/metronome.d.ts +33 -0
  27. package/dist/lib/notation/document.d.ts +11 -1
  28. package/dist/lib/notation/mei-spelling.d.ts +9 -1
  29. package/dist/lib/notation/mei-tab.d.ts +58 -0
  30. package/dist/lib/notation/mei-view.d.ts +8 -0
  31. package/dist/lib/playback/timeline.d.ts +4 -1
  32. package/dist/lib/prefs.d.ts +67 -0
  33. package/dist/lib/range-fit-core.d.ts +26 -0
  34. package/dist/lib/render.d.ts +36 -1
  35. package/dist/lib/score.d.ts +23 -1
  36. package/dist/lib/sound-players.d.ts +9 -1
  37. package/dist/lib/tab/fret-assign.d.ts +53 -0
  38. package/dist/lib/tab/tab-parts.d.ts +64 -0
  39. package/dist/lib/transpose.d.ts +3 -2
  40. package/dist/lib/verovio-protocol.d.ts +15 -0
  41. package/dist/lib/verovio-runtime.d.ts +9 -1
  42. package/dist/range-fit-core-B1rMWdI4.js +468 -0
  43. package/dist/range-fit-core-B1rMWdI4.js.map +1 -0
  44. package/dist/runtime-files.d.ts +15 -0
  45. package/dist/{sf2-player-CRlOJDhD.js → sf2-player-oWC6yrUH.js} +28 -6
  46. package/dist/sf2-player-oWC6yrUH.js.map +1 -0
  47. package/dist/sound-players-DdLBBZaR.js +1172 -0
  48. package/dist/sound-players-DdLBBZaR.js.map +1 -0
  49. package/dist/style.css +1 -1
  50. package/dist/verovio/LICENSE +858 -0
  51. package/dist/verovio/verovio-module.mjs +0 -0
  52. package/dist/verovio/verovio.mjs +397 -0
  53. package/package.json +14 -2
  54. package/dist/assets/verovio.worker-DkHQmOsT.js +0 -19034
  55. package/dist/sf2-player-CRlOJDhD.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ export * from './lib/instrument-model';
2
+ export * from './lib/range-fit-core';
@@ -0,0 +1,2 @@
1
+ import { _ as e, a as t, c as n, d as r, f as i, g as a, h as o, i as s, l as c, m as l, n as u, o as d, p as f, r as p, s as m, t as h, u as g, v as _ } from "./range-fit-core-B1rMWdI4.js";
2
+ export { t as EMBED_INSTRUMENTS, d as INSTRUMENTS, m as INSTRUMENT_IDS, n as LEVELS, h as applyOverrides, c as instrumentFor, g as instrumentName, u as joinRuns, r as keyAlter, i as pitchMidi, p as planShifts, f as spellMidi, l as spelledSoundingMidi, s as tieChainOf, o as transposePitch, a as transposePitchSpelled, e as writtenKeyFifths, _ as writtenPitch };
@@ -1,6 +1,6 @@
1
1
  /** Chrome presets (docs/specs/pepperscore-embeddable-component.md, "Chrome presets") and per-control overrides. No runtime imports. */
2
2
  export type ChromePreset = 'full' | 'minimal' | 'none';
3
- export type PepperScoreControls = Partial<Record<'mixer' | 'openFile' | 'drawer' | 'transport' | 'viewToggle' | 'zoom', boolean>>;
3
+ export type PepperScoreControls = Partial<Record<'mixer' | 'openFile' | 'drawer' | 'transport' | 'viewToggle' | 'zoom' | 'tabToggle' | 'instrumentSwitch' | 'octaveTool', boolean>>;
4
4
  export type ResolvedChrome = {
5
5
  header: boolean;
6
6
  transport: boolean;
@@ -9,6 +9,12 @@ export type ResolvedChrome = {
9
9
  mixer: boolean;
10
10
  drawer: boolean;
11
11
  zoom: boolean;
12
+ /** The Notation / TAB / Both switch (shown only for a score with a stave that can take TAB). */
13
+ tabToggle: boolean;
14
+ /** The instrument switcher (instrument, level and fit to range per part). */
15
+ instrumentSwitch: boolean;
16
+ /** The reader's octave tool (move notes or passages by an octave). */
17
+ octaveTool: boolean;
12
18
  /** The overlaid play/stop button of `minimal`. */
13
19
  compactPlay: boolean;
14
20
  /** Playhead, note highlight and follow. */
@@ -21,6 +27,8 @@ export type ResolvedChrome = {
21
27
  /**
22
28
  * `transport` covers stop, play, BPM, transpose, key flip and bars; `viewToggle` the Vertical/Endless toggle; `openFile` the
23
29
  * Open button, file input, open dialog and reopen offer; `mixer` the mixer and the part icons / instrument picker / stave
24
- * eyes; `zoom` the stave-scale slider, pinch zoom and the page arrows row; `drawer` the bottom drawer and its handle.
30
+ * eyes; `zoom` the stave-scale slider, pinch zoom and the page arrows row; `drawer` the bottom drawer and its handle;
31
+ * `tabToggle` the Notation / TAB / Both switch (header and drawer); `instrumentSwitch` the instrument switcher;
32
+ * `octaveTool` the octave tool. Each defaults to on for `full`, off otherwise.
25
33
  */
26
34
  export declare function resolveChrome(preset?: ChromePreset, controls?: PepperScoreControls): ResolvedChrome;
@@ -1,5 +1,10 @@
1
1
  import { type InstrumentId, type Score } from './score';
2
2
  import { type DeviceClass } from './stave-scale';
3
+ import { clampBpm, MAX_BPM, MIN_BPM, type AnnotationSize } from './prefs';
4
+ import type { TabView } from './tab/tab-parts';
5
+ import type { PartSound, PartSwitch } from './instrument-view';
6
+ import type { Shift } from './range-fit-core';
7
+ import { type RangeLevel } from './instrument-model';
3
8
  export type FileSource = {
4
9
  name: string;
5
10
  size: number;
@@ -22,17 +27,35 @@ export type FileSettings = {
22
27
  scorePitch: 'written' | 'concert';
23
28
  /** Spell the transposed view in the enharmonic key (F# major shown as Gb major); optional on read, default false. */
24
29
  keyFlip: boolean;
30
+ /** Text add-on sizes (prefs.ts ANNOTATION_SIZES); optional on read, default 'rg' (the size every earlier record showed). */
31
+ namesSize: AnnotationSize;
32
+ chordSymbolsSize: AnnotationSize;
33
+ countsSize: AnnotationSize;
34
+ subdivisionsSize: AnnotationSize;
35
+ /** TAB view, tuning (open-string MIDI, low to high) and capo; always written (see `SavedSettings` for reading). */
36
+ tabView: TabView;
37
+ tabTuning: number[];
38
+ capo: number;
25
39
  };
40
+ /** Settings added after version 2 shipped: a record without them (or with unusable ones) leaves the reader's own. */
41
+ export type LateSettingKey = 'tabView' | 'tabTuning' | 'capo';
42
+ /** Settings as read from a saved record. */
43
+ export type SavedSettings = Omit<FileSettings, LateSettingKey> & Partial<Pick<FileSettings, LateSettingKey>>;
26
44
  /** Saved view: an absolute stave scale and the device class it was chosen on (see stave-scale.ts). */
27
45
  export type ScoreView = {
28
46
  zoom: number;
29
47
  zoomDevice: DeviceClass;
30
48
  };
49
+ /**
50
+ * `switch`: the reader's instrument switch of this staff part (instrument-view.ts `PartSwitch`; `octaves` by
51
+ * `ScoreNote.id`). Added after version 2 shipped: optional, and an unusable one is dropped (the part opens as written).
52
+ */
31
53
  export type PartState = {
32
54
  visible: boolean;
33
55
  instrument: InstrumentId;
34
56
  gmSoundfont?: string;
35
57
  volume?: number;
58
+ switch?: SavedSwitch;
36
59
  };
37
60
  /** Per-score record (schema: file-state.schema.json). Only settings and hashes, never score content. */
38
61
  export type FileState = {
@@ -40,9 +63,15 @@ export type FileState = {
40
63
  source: FileSource;
41
64
  /** Epoch ms of the last time the score was opened; the store keeps the most recent records. */
42
65
  lastOpened: number;
43
- settings: FileSettings;
66
+ settings: SavedSettings;
44
67
  parts: Record<string, PartState>;
45
68
  view?: ScoreView;
69
+ /**
70
+ * The host's instrument settings when the record was saved (instrument-switch-state.ts `hostInstrumentKey`); a host
71
+ * whose settings differ on the next load applies them again over the saved switches. Added after version 2 shipped:
72
+ * optional, absent when the host gave none, dropped when unusable.
73
+ */
74
+ hostInstruments?: string;
46
75
  };
47
76
  /** The localStorage v1 record: zoom was a multiplier of the width fit and lived in settings. */
48
77
  export type FileStateV1 = {
@@ -53,9 +82,7 @@ export type FileStateV1 = {
53
82
  };
54
83
  parts: Record<string, PartState>;
55
84
  };
56
- export declare const MIN_BPM = 30, MAX_BPM = 240;
57
- /** The reader's BPM control range; saved and source tempos outside it are clamped. */
58
- export declare const clampBpm: (bpm: number) => number;
85
+ export { clampBpm, MAX_BPM, MIN_BPM };
59
86
  /** The localStorage key of a v1 record; `prefix` is the reader's persist namespace (score-db keyPrefix). */
60
87
  export declare const legacyStorageKey: (hash: string, prefix?: string) => string;
61
88
  /**
@@ -80,6 +107,19 @@ export declare function fileSource(file: Pick<File, 'name' | 'size'>, bytes: Uin
80
107
  export declare const fileSourceFor: (name: string, size: number, sha256: string) => FileSource;
81
108
  /** Source identity for text without a file (the sample, pasted ABC, the musicXml prop): a hash of the text with a format prefix. */
82
109
  export declare function textSource(format: string, text: string, name: string): FileSource;
110
+ /** A part's switch as saved in its record (`PartState.switch`); `sound` only with an `instrument`. */
111
+ export type SavedSwitch = {
112
+ instrument?: InstrumentId;
113
+ level: RangeLevel;
114
+ autoRange: boolean;
115
+ octaves?: Record<string, Shift>;
116
+ sound?: PartSound;
117
+ };
118
+ export declare const isLevel: (value: unknown) => value is RangeLevel;
119
+ /** A switch target: any model instrument but the drum kit (unpitched). */
120
+ export declare const isSwitchTarget: (value: unknown) => value is InstrumentId;
121
+ /** A part's switch as saved, or undefined for no entry. */
122
+ export declare function savedSwitch(entry: PartSwitch | undefined): SavedSwitch | undefined;
83
123
  /** Validate a stored v1 (legacy localStorage) record for `sha256`. */
84
124
  export declare function parseLegacyFileState(raw: unknown, sha256: string): FileStateV1 | null;
85
125
  /**
@@ -92,4 +132,4 @@ export declare function migrateFileState(legacy: FileStateV1, device: DeviceClas
92
132
  export declare function parseFileState(raw: unknown, sha256: string): FileState | null;
93
133
  /** Read the legacy localStorage v1 record for a source. */
94
134
  export declare function readFileState(source: FileSource): FileStateV1 | null;
95
- export declare function buildFileState(source: FileSource, settings: FileSettings, score: Score, hiddenIds: string[], volumes?: Record<string, number>, view?: ScoreView, lastOpened?: number): FileState;
135
+ export declare function buildFileState(source: FileSource, settings: FileSettings, score: Score, hiddenIds: string[], volumes?: Record<string, number>, view?: ScoreView, lastOpened?: number, switches?: Record<string, PartSwitch>, hostInstruments?: string): FileState;
@@ -6,9 +6,10 @@
6
6
  * the header; `secondary` controls sit inline above MOBILE_HEADER_BREAKPOINT
7
7
  * and move into the "More controls" dropdown at or below it. PepperScore.tsx maps
8
8
  * each id to its element; every control is always rendered (`bars` is disabled outside Vertical view, `keyFlip` when the key has no enharmonic) so the
9
- * header footprint never depends on client-only state.
9
+ * header footprint never depends on client-only state. `tab` (the Notation / TAB / Both switch) is the exception: it shows only for a score with a
10
+ * stave that can take TAB (any pitched part).
10
11
  */
11
- export type HeaderControlId = 'stop' | 'play' | 'openFile' | 'bpm' | 'transpose' | 'keyFlip' | 'mode' | 'bars' | 'staveScale' | 'mixer';
12
+ export type HeaderControlId = 'stop' | 'play' | 'openFile' | 'bpm' | 'transpose' | 'keyFlip' | 'mode' | 'tab' | 'bars' | 'staveScale' | 'mixer';
12
13
  export type HeaderControlTier = 'primary' | 'secondary';
13
14
  export type HeaderControlConfig = {
14
15
  id: HeaderControlId;
@@ -26,6 +27,9 @@ export declare const HEADER_CONTROLS: readonly [{
26
27
  }, {
27
28
  readonly id: "mode";
28
29
  readonly tier: "primary";
30
+ }, {
31
+ readonly id: "tab";
32
+ readonly tier: "secondary";
29
33
  }, {
30
34
  readonly id: "bpm";
31
35
  readonly tier: "secondary";
@@ -50,4 +54,4 @@ export declare const MOBILE_HEADER_BREAKPOINT = 760;
50
54
  /** Stave scale (zoom) bounds shared by the header slider and pinch zoom live with the defaults in stave-scale.ts. */
51
55
  export { clampStaveScale, STAVE_SCALE_MAX, STAVE_SCALE_MIN, STAVE_SCALE_STEP } from './stave-scale';
52
56
  /** Which `controls` key shows each header control (see chrome.ts). */
53
- export declare const HEADER_CONTROL_GROUP: Record<HeaderControlId, 'transport' | 'viewToggle' | 'openFile' | 'zoom' | 'mixer'>;
57
+ export declare const HEADER_CONTROL_GROUP: Record<HeaderControlId, 'transport' | 'viewToggle' | 'openFile' | 'zoom' | 'mixer' | 'tabToggle'>;
@@ -0,0 +1,18 @@
1
+ /** Click hit testing over the drawn notes (pure: the boxes are measured by the caller, in client px). */
2
+ export type HitRect = {
3
+ left: number;
4
+ top: number;
5
+ right: number;
6
+ bottom: number;
7
+ width: number;
8
+ height: number;
9
+ };
10
+ /** A drawn note or rest: its whole box (stem, flag, accidentals, dots, lyrics) and its notehead boxes (a rest: its glyph). */
11
+ export type DrawnNote = {
12
+ box: HitRect;
13
+ heads: HitRect[];
14
+ };
15
+ /** The note nearest (x, y): the notehead centre nearest in x, stacked noteheads split by the nearest in y. */
16
+ export declare function nearestNote<T extends DrawnNote>(items: T[], x: number, y: number): T | undefined;
17
+ /** The note whose drawn box (grown by `slop` px) holds (x, y); overlapping boxes go to the nearest notehead. */
18
+ export declare function noteInBox<T extends DrawnNote>(items: T[], x: number, y: number, slop: number): T | undefined;
@@ -0,0 +1,80 @@
1
+ /** The instrument model shared by the reader, the embeds and the editor (`pepperscore/instruments`); framework-free. */
2
+ /** The viewer's instrument ids. */
3
+ export declare const INSTRUMENT_IDS: readonly ["piano", "guitar", "bassguitar", "double-bass", "drum-kit", "alto-saxophone", "tenor-saxophone", "bari-saxophone", "flute", "clarinet", "trumpet", "trombone", "violin", "viola", "cello", "microphone"];
4
+ export declare const LEVELS: readonly ["beginner", "intermediate", "advanced"];
5
+ export type InstrumentId = typeof INSTRUMENT_IDS[number];
6
+ export type RangeLevel = typeof LEVELS[number];
7
+ /** An instrument a pitched part can be switched to: any but the drum kit (unpitched). */
8
+ export type SwitchInstrumentId = Exclude<InstrumentId, 'drum-kit'>;
9
+ export type Transposition = {
10
+ semitones: number;
11
+ diatonic: number;
12
+ };
13
+ export type InstrumentClef = 'treble' | 'bass' | 'alto' | 'tenor' | 'percussion' | 'treble8vb';
14
+ export type SpelledPitch = {
15
+ step: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G';
16
+ octave: number;
17
+ alter: number;
18
+ };
19
+ export type InstrumentInfo = {
20
+ id: InstrumentId;
21
+ label: string;
22
+ /** The name the editor's controls and status line use when shorter than `label` ("Alto Sax"). */
23
+ short?: string;
24
+ /** 1-based GM program (the viewer's voice program); the drum kit plays on channel 10, not a program. */
25
+ gmProgram: number;
26
+ clef: InstrumentClef;
27
+ /** Written minus sounding. */
28
+ transposition: Transposition;
29
+ /** Playable range per level in sounding MIDI, inclusive; absent for unpitched instruments. */
30
+ ranges?: Record<RangeLevel, {
31
+ low: number;
32
+ high: number;
33
+ }>;
34
+ };
35
+ /** The model's instruments by id (frozen). */
36
+ export declare const INSTRUMENTS: Readonly<Record<InstrumentId, Readonly<InstrumentInfo>>>;
37
+ /** The instrument's short name for controls and the status line (`short`, else `label`). */
38
+ export declare const instrumentName: (id: InstrumentId) => string;
39
+ /** The instrument nearest a 1-based GM program; piano when none is near. */
40
+ export declare function instrumentFor(gmProgram: number): Readonly<InstrumentInfo>;
41
+ /** The MIDI number of a pitch (C4 = 60). */
42
+ export declare const pitchMidi: (p: SpelledPitch) => number;
43
+ /** The alteration the key signature (`fifths`) gives `step`. */
44
+ export declare function keyAlter(step: SpelledPitch['step'], fifths: number): number;
45
+ /**
46
+ * The spelling of MIDI note `target`: the key signature's (`fifths`) spelling if it has one, else the
47
+ * natural, else a sharp going up (`direction` >= 0) or a flat going down, else the other single
48
+ * accidental, else a double.
49
+ */
50
+ export declare function spellMidi(target: number, fifths: number, direction: number): SpelledPitch | undefined;
51
+ /** The sounding MIDI number of a written pitch on an instrument with `transposition` (written minus sounding). */
52
+ export declare function spelledSoundingMidi(pitch: SpelledPitch, transposition: {
53
+ semitones: number;
54
+ }): number;
55
+ /**
56
+ * The written pitch of sounding MIDI `soundingMidi` for an instrument with `transposition`, spelled for the
57
+ * written key `keyFifths`: the key's own accidentals first, then naturals, then sharps for chromatic notes in
58
+ * sharp keys and C major, flats in flat keys. Only `semitones` is used: a MIDI number carries no spelling to
59
+ * move by the diatonic interval.
60
+ */
61
+ export declare function writtenPitch(soundingMidi: number, transposition: Transposition, keyFifths: number): SpelledPitch;
62
+ /**
63
+ * `pitch` moved by `t`, keeping its spelling: the step moves by `diatonic` (carrying the octave) and the alter
64
+ * is whatever makes the MIDI number move by `semitones`. When that alter would pass a double accidental the
65
+ * note is respelled with `writtenPitch` for the key `keyFifths` and `respelled` is true.
66
+ */
67
+ export declare function transposePitchSpelled(pitch: SpelledPitch, t: Transposition, keyFifths: number): {
68
+ pitch: SpelledPitch;
69
+ respelled: boolean;
70
+ };
71
+ /** `pitch` moved by `t`, keeping its spelling (see `transposePitchSpelled`; the fallback spells for C major). */
72
+ export declare const transposePitch: (pitch: SpelledPitch, t: Transposition) => SpelledPitch;
73
+ /**
74
+ * The written key (fifths) of music in concert key `concertFifths` shown with `transposition` (written minus
75
+ * sounding): the transposition as fifths, `(7 * semitones) mod 12` normalised to -6..6, added and wrapped
76
+ * back into -7..7. Alto sax (+9 semitones) is +3 fifths; a B-flat instrument +2.
77
+ */
78
+ export declare function writtenKeyFifths(concertFifths: number, transposition: Transposition): number;
79
+ /** The instruments the embeds offer, in switch order (frozen). */
80
+ export declare const EMBED_INSTRUMENTS: readonly InstrumentId[];
@@ -0,0 +1,169 @@
1
+ import { type PartState } from './file-state';
2
+ import { type InstrumentId, type RangeLevel, type SwitchInstrumentId } from './instrument-model';
3
+ import { type InstrumentViewResult, type PartSound, type PartSwitch } from './instrument-view';
4
+ import type { BaseTimeline } from './playback/timeline';
5
+ import type { Shift } from './range-fit-core';
6
+ import { type Part, type Score } from './score';
7
+ export declare const DEFAULT_RANGE_LEVEL: RangeLevel;
8
+ /** The instruments a pitched part can be switched to: the model's, in its order, without the drum kit. */
9
+ export declare const SWITCH_TARGETS: InstrumentId[];
10
+ /** The instrument keys of `PepperScoreSettings` (prefs.ts documents them). */
11
+ export type InstrumentSettings = {
12
+ instrument?: SwitchInstrumentId;
13
+ instruments?: Record<string, SwitchInstrumentId>;
14
+ rangeLevel?: RangeLevel;
15
+ autoRange?: boolean;
16
+ };
17
+ export type SwitchDefaults = {
18
+ level: RangeLevel;
19
+ autoRange: boolean;
20
+ };
21
+ type Switches = Record<string, PartSwitch>;
22
+ /** Source parts in score order, numbered from 1 by the host (TAB doublings excluded), with their staff parts. */
23
+ export declare function sourcePartsOf(score: Score): {
24
+ id: string;
25
+ parts: Part[];
26
+ switchable: boolean;
27
+ }[];
28
+ export declare const switchDefaults: (settings: InstrumentSettings) => SwitchDefaults;
29
+ /**
30
+ * The parts `onReady` lists (every staff, TAB doublings excluded), each with its 1-based source part number (the
31
+ * `settings.instruments` key; a grand staff's staves share one) and whether the switch applies to it.
32
+ */
33
+ export declare function readerPartsInfo(score: Score): {
34
+ id: string;
35
+ name: string;
36
+ sourcePart: number;
37
+ switchable: boolean;
38
+ }[];
39
+ /**
40
+ * Change one source part's switch on every staff part it has (a grand staff switches together). Choosing another
41
+ * instrument (or As written) drops a picked sound: the instrument's main sound plays again. Choosing the current one
42
+ * keeps it.
43
+ */
44
+ export declare function withSourceSwitch(score: Score, switches: Switches, sourcePart: string, patch: Partial<Omit<PartSwitch, 'octaves'>>, defaults: SwitchDefaults): Switches;
45
+ /** Set the reader's octave moves (by `ScoreNote.id`) of one staff part. */
46
+ export declare function withOctaves(switches: Switches, partId: string, octaves: Record<string, Shift>, defaults: SwitchDefaults): Switches;
47
+ /**
48
+ * Drop the reader's "keep as written" pins (an octave override of 0) that the auto-fit plan no longer overrides: after
49
+ * a level, instrument or fit change (or a host change) the plan of the view these switches give may not move that note
50
+ * any more, and a stale pin would keep its staff "non-plain" (a saved entry) and block auto-fit if the plan moves the
51
+ * note again later. A 0 on a note the plan still moves (8vb on an auto-raised phrase: as written) stays, as do 8va / 8vb
52
+ * moves. The view is the reader's: `scoreForView` at `semitones`, in the pitch view `readerPitch` gives these switches.
53
+ */
54
+ export declare function withoutStalePins(score: Score, switches: Switches, view: {
55
+ scorePitch: 'written' | 'concert';
56
+ semitones: number;
57
+ keyFlip?: boolean;
58
+ }, defaults: SwitchDefaults): Switches;
59
+ /**
60
+ * A staff part's label: its own name and, while it is switched to another instrument, that instrument's short name
61
+ * (the reader shows "Piano → Guitar"). No `switched` when as written, or switched to the instrument the part already is.
62
+ */
63
+ export declare function partLabel(part: Part, switches: Switches): {
64
+ name: string;
65
+ switched?: string;
66
+ };
67
+ /**
68
+ * The octave tool's selection on one staff part: `ids` (ScoreNote ids, score order, whole tie chains), the note the
69
+ * selection started from (`anchor`) and whether a plain click extends it to a passage (only after a single-note pick).
70
+ */
71
+ export type OctaveSelection = {
72
+ partId: string;
73
+ anchor: string;
74
+ ids: string[];
75
+ extendable: boolean;
76
+ };
77
+ /**
78
+ * A click on a note with the octave tool on. A note of a switchable part selects its tie chain; with `extend`
79
+ * (Shift-click), or a plain click after a single-note pick in the same part, it selects every note of that part whose
80
+ * (measure, start) lies between the anchor and this note, in all voices, each with its tie chain. A note the tool can't
81
+ * move (a rest, a drum or TAB staff) keeps the selection.
82
+ */
83
+ export declare function selectOctaveNote(score: Score, current: OctaveSelection | null, noteId: string, extend: boolean): OctaveSelection | null;
84
+ /** "Select phrase": the selection widened to the auto-fit phrases (`phrasesOf`) that contain any of its notes. */
85
+ export declare function selectOctavePhrase(score: Score, selection: OctaveSelection): OctaveSelection;
86
+ /**
87
+ * 8va / 8vb on the selected notes, from where each is shown (`shifts`: auto-fit and the reader's moves): the new shift
88
+ * (kept within ±12) is stored as the note's override, 0 included (a note auto-fit moved goes back as written); a
89
+ * shift auto-fit alone gives (`planned`) needs no override and drops it.
90
+ */
91
+ export declare function steppedOctaves(octaves: Record<string, Shift>, ids: string[], step: 12 | -12, shifts?: Record<string, Shift>, planned?: Record<string, Shift>): Record<string, Shift>;
92
+ /**
93
+ * A Sound pick on a staff part: while the part is switched it only overrides the instrument's main sound (the switch
94
+ * itself is kept); undefined when the part is not switched (the pick then changes the part's own sound).
95
+ */
96
+ export declare function withSound(switches: Switches, partId: string, sound: PartSound): Switches | undefined;
97
+ /** The host's settings as switches (a score without a saved record). */
98
+ export declare function switchesFromSettings(score: Score, settings: InstrumentSettings): Switches;
99
+ /** A host settings change: only the changed keys are applied again (the reader's other choices stay). */
100
+ export declare function applyHostSwitchChange(score: Score, current: Switches, previous: InstrumentSettings, next: InstrumentSettings): Switches;
101
+ /**
102
+ * A fingerprint of the host's instrument settings, saved with the record (`FileState.hostInstruments`): only the usable
103
+ * keys, `instruments` as [key, id] pairs sorted by key. Undefined when the host gives none.
104
+ */
105
+ export declare function hostInstrumentKey(settings: InstrumentSettings): string | undefined;
106
+ /**
107
+ * The host's instrument settings applied again over restored switches: every switchable source part takes the host's
108
+ * instrument (or As written), level and fit. A part whose instrument changes loses its octave moves (and its picked
109
+ * sound); a part whose instrument stays keeps them.
110
+ */
111
+ export declare function withHostSwitches(score: Score, switches: Switches, settings: InstrumentSettings): Switches;
112
+ /**
113
+ * The switches a score opens with. Without a saved record, the host's settings. With one, its switches, unless the
114
+ * host's instrument settings changed since it was saved (`hostInstrumentKey` differs from `hostInstruments`): then the
115
+ * host's are applied again over it (`withHostSwitches`), so a teacher's new instrument reaches returning readers. A host
116
+ * that gives no instrument settings, or the same ones, leaves the reader's saved choices.
117
+ */
118
+ export declare function switchesOnLoad(score: Score, saved: {
119
+ parts: Record<string, PartState>;
120
+ hostInstruments?: string;
121
+ } | null, settings: InstrumentSettings): Switches;
122
+ /**
123
+ * Switches restored from a saved record's parts (already validated by file-state.ts). The staves of a source part
124
+ * switch together, so they are normalised to its first staff with a saved switch (instrument, level, fit): a staff
125
+ * whose switch differs or was dropped takes it, keeping its own octave moves (and its sound when the instrument matches).
126
+ * A staff left as written with the defaults and no octave moves gets no entry, as `withSourceSwitch` would leave it.
127
+ */
128
+ export declare function switchesFromSaved(score: Score, parts: Record<string, PartState>, defaults?: SwitchDefaults): Switches;
129
+ /**
130
+ * The saved pitch view to restore. A concert-basis score used to be forced to (and saved as) concert pitch while no
131
+ * part was switched to a transposing instrument, so a saved 'concert' there was never the reader's choice: it opens as
132
+ * 'written', and a later switch to a B♭/E♭ instrument shows its written pitch.
133
+ */
134
+ export declare function restoredScorePitch(score: Score, switches: Switches, saved: 'written' | 'concert'): 'written' | 'concert';
135
+ /**
136
+ * The pitch view the reader shows. Verovio can only move written pitch to sounding, so a concert-basis score is shown
137
+ * at concert pitch, unless a part is switched to a transposing instrument (it shows its written pitch by default).
138
+ */
139
+ export declare function readerPitch(score: Score, switches: Switches, preferred: 'written' | 'concert'): {
140
+ scorePitch: "concert" | "written";
141
+ concertBasis: boolean;
142
+ forcedConcert: boolean;
143
+ switchedTransposing: boolean;
144
+ hasTransposingParts: boolean;
145
+ };
146
+ /**
147
+ * The Pitch panel's hint for the pitch view `readerPitch` gives. In a concert-basis score the Transposing view moves
148
+ * only the parts switched to a transposing instrument: every other part (a transposing one included) stays at concert pitch.
149
+ */
150
+ export declare function pitchHint(view: ReturnType<typeof readerPitch>): string;
151
+ /** The switched view of `scoreForView`'s result (the switch runs last); with no switches, that result itself. */
152
+ export declare function switchedView(viewed: Score, switches: Switches, scorePitch: 'written' | 'concert'): InstrumentViewResult;
153
+ /** Internal highlights of a switched view, applied before the host's. */
154
+ export declare function switchHighlights(view: InstrumentViewResult): {
155
+ ids: string[];
156
+ className: string;
157
+ }[];
158
+ /**
159
+ * The performance with the switched (or octave-moved) parts' events at the view's sounding pitch. The base timeline
160
+ * is the source score's, at 0 semitones (playback adds `semitones`), so every other part keeps its events as before;
161
+ * the view's pitches already include `semitones`, so they are taken back out here.
162
+ */
163
+ export declare function switchedTimeline(base: BaseTimeline, view: InstrumentViewResult, semitones: number): BaseTimeline;
164
+ /**
165
+ * Parts to play with: a part switched to an instrument plays the target's sound (the view part's), or the sound the
166
+ * reader picked for it while switched (`PartSwitch.sound`).
167
+ */
168
+ export declare function switchedSoundParts(parts: Part[], view: InstrumentViewResult, switches: Switches): Part[];
169
+ export {};
@@ -0,0 +1,92 @@
1
+ import { type InstrumentClef, type InstrumentId, type RangeLevel } from './instrument-model';
2
+ import { type Shift } from './range-fit-core';
3
+ import { type KeyMode, type Measure, type Part, type Score, type ScoreNote } from './score';
4
+ /** The part all staves of one source part share: the MusicXML <score-part id>, or the part's own id for ABC/MIDI. */
5
+ export declare const sourcePartOf: (part: Part) => string;
6
+ /**
7
+ * The playback sound a reader picked for a switched part (the mixer's Sound grid, like `Part.instrument` and
8
+ * `gmSoundfont`: the GM sound's icon id and FluidR3 name; no `gmSoundfont` with `instrument: 'drum-kit'` = the kit).
9
+ */
10
+ export type PartSound = {
11
+ instrument: InstrumentId;
12
+ gmSoundfont?: string;
13
+ };
14
+ /**
15
+ * A reader's switch for one staff part: `instrument` undefined = as written; `octaves` keyed by `ScoreNote.id` (a note's
16
+ * shift from its pitch in the view, overriding auto-fit along its tie chain; 0 keeps a note auto-fit would move);
17
+ * `sound` overrides the target's main sound (only while switched; cleared when the instrument changes).
18
+ */
19
+ export type PartSwitch = {
20
+ instrument?: InstrumentId;
21
+ level: RangeLevel;
22
+ autoRange: boolean;
23
+ octaves: Record<string, Shift>;
24
+ sound?: PartSound;
25
+ };
26
+ /**
27
+ * The MEI pass's view of one switched (or octave-moved) staff: `shift` = [diatonic, semitones] from the baseline MEI
28
+ * (the source's written pitch, after the global interval) to the pitch shown; `octaves` keyed by MEI note id.
29
+ */
30
+ export type StaffSwitch = {
31
+ shift: [number, number];
32
+ clef?: InstrumentClef;
33
+ octaves: Record<string, 12 | -12>;
34
+ };
35
+ /**
36
+ * What the MEI pass needs of one staff of a switched view (carried on `Score.instrumentView`): its `StaffSwitch` and,
37
+ * for a switched part, its keys [measure index, fifths, mode] before the global transposition (the source's written
38
+ * keys moved by `shift`), as `SpellingSpec` staves take them.
39
+ */
40
+ export type EngravedSwitch = StaffSwitch & {
41
+ keys?: [number, number, KeyMode][];
42
+ };
43
+ export type InstrumentViewResult = {
44
+ score: Score;
45
+ /** `ScoreNote.id`s moved by an octave (auto-fit or the reader's). */
46
+ moved: string[];
47
+ /** `ScoreNote.id`s still outside the level's range (switched parts), or moved outside it (unswitched parts). */
48
+ outOfRange: string[];
49
+ /** Each moved note's octave shift (auto-fit and the reader's), and what auto-fit alone would shift (nonzero only). */
50
+ shifts: Record<string, Shift>;
51
+ planned: Record<string, Shift>;
52
+ /** Keyed by part id. */
53
+ staves: Record<string, StaffSwitch>;
54
+ /**
55
+ * TAB parts doubling a switched or octave-moved part (their frets are stale). The renderer reads the same from
56
+ * `score.instrumentView` (tab/tab-parts.ts `planTab`): regenerated while the part is a guitar, else hidden.
57
+ */
58
+ hiddenTab: string[];
59
+ };
60
+ /**
61
+ * Whether the switch is offered for a part: never for the drum kit or percussion, nor for TAB staves. Judged on the
62
+ * part as imported: a Sound pick (`withPickedSound`) changes `instrument` but never what can be switched.
63
+ */
64
+ export declare const switchablePart: (part: Part) => boolean;
65
+ /**
66
+ * A part playing a picked sound (the mixer's Sound grid or a saved record): its icon id, GM program and FluidR3 name,
67
+ * with the instrument it was imported as kept in `importedInstrument`.
68
+ */
69
+ export declare const withPickedSound: (part: Part, sound: {
70
+ instrument: InstrumentId;
71
+ gmProgram?: number;
72
+ gmSoundfont?: string;
73
+ }) => Part;
74
+ export declare const pitched: (note: ScoreNote) => boolean;
75
+ export declare const byStart: (measure: Measure) => ScoreNote[];
76
+ /** Each pitched note's tie chain in a part, in score order (a note alone when untied). */
77
+ export declare function tieChainsOf(part: Part): (id: string) => string[];
78
+ /** Runs of pitched notes per voice per measure, broken at rests and after a slur end, joined across ties. */
79
+ export declare function phrasesOf(part: Part): string[][];
80
+ /**
81
+ * Switches parts of a Score view (`scoreForView`'s result) to other instruments and applies the reader's octave
82
+ * moves. Each part is treated on its own (a grand staff's callers set one `PartSwitch` on every staff). For every
83
+ * note of every part, `soundingMidi(outPart, measure, pitch, 0)` is its judged pitch (sounding, after the view's
84
+ * transposition) plus its octave shift: a concert view's transposing parts (already at sounding pitch) lose their
85
+ * transposition, switched or not. A switched part is written for the target instrument in the written view, at
86
+ * sounding pitch in the concert view. A target without a pitched clef or ranges (the drum kit) is treated as written.
87
+ *
88
+ * The view is the Score's own (`score.pitchView.view`); `scorePitch` only applies to a Score no view was made of.
89
+ * Throws when `score` already carries an instrument view: apply it once, to `scoreForView`'s result (a second pass
90
+ * would stack the shifts).
91
+ */
92
+ export declare function applyInstrumentView(score: Score, switches: Record<string, PartSwitch>, scorePitch: 'written' | 'concert'): InstrumentViewResult;
@@ -0,0 +1,82 @@
1
+ import { type SoundPackName } from './assets';
2
+ import { type AudioPlayer } from './sound-players';
3
+ import { type SoundPlan, type SoundRequest, type SoundSource } from './sounds';
4
+ import { type MetronomeVoice } from './metronome';
5
+ export type InstrumentOptions = {
6
+ /** Root of a pepperscore-sounds-<pack> layout (relative to the page); default: the jsDelivr copy of pepperscore-sounds-<soundPack>. */
7
+ assetBaseUrl?: string;
8
+ /** Picks the default assetBaseUrl. With your own assetBaseUrl, its pack.json decides which FluidR3 sounds exist. */
9
+ soundPack?: SoundPackName;
10
+ /** Your own sounds, claimed per GM program or for the drum kit (the PepperScore `soundSources` prop). */
11
+ sources?: readonly SoundSource[];
12
+ /** Default: the page's shared AudioContext (getSharedAudio). */
13
+ context?: AudioContext;
14
+ /** Where the instrument's output goes. Default: the shared master compressor (or `context.destination` for your own context). */
15
+ destination?: AudioNode;
16
+ /** Notes to load before the promise resolves. Default: 21-108 for an instrument, none for the drum kit. */
17
+ notes?: number[];
18
+ };
19
+ /** The public view of an instrument's sound plan. */
20
+ export type InstrumentPlan = {
21
+ readonly kind: SoundPlan['kind'];
22
+ readonly sourceId: string;
23
+ };
24
+ /** A loaded sound: an AudioPlayer plus its output gain (volume) and the plan it came from. */
25
+ export type Instrument = AudioPlayer & {
26
+ readonly context: AudioContext;
27
+ /** The instrument's volume; connected to `destination`. */
28
+ readonly output: GainNode;
29
+ /** Which kind of source plays it and its id (for example `'builtin:fluidr3'`). Read-only; the plan's URLs and presets are internal. */
30
+ readonly plan: InstrumentPlan;
31
+ /** Load (decode) these notes; a note starts silently until it is loaded. Drums load TimGM6mb's kit for notes the kit lacks. */
32
+ loadNotes(notes: number[]): Promise<void>;
33
+ /** Whether a loaded player can sound this note. */
34
+ covers(note: number): boolean;
35
+ };
36
+ /** A GM program (1-128), a FluidR3 sound name (`'acoustic_grand_piano'`), or `'drums'` for the GM drum kit (`'drum-kit'`, the score part id, is accepted as an alias). */
37
+ export type InstrumentName = number | string;
38
+ /** The sound request for a program number, a FluidR3 name or 'drums'/'drum-kit'. */
39
+ export declare function instrumentRequest(sound: InstrumentName): SoundRequest;
40
+ /**
41
+ * Load an instrument through pepperscore's sound resolution. `start()` returns a stop function for that one note;
42
+ * `stop()` stops every sounding note and cancels every scheduled one. A note's `destination` is honoured by sample-pack
43
+ * and note-file sounds; SF2 sounds always play through `output`.
44
+ */
45
+ export declare function loadInstrument(sound: InstrumentName, options?: InstrumentOptions): Promise<Instrument>;
46
+ /** The GM drum kit (`loadInstrument('drums', options)`): Perfect Drums note files, TimGM6mb's kit for notes they lack. */
47
+ export declare const loadDrumKit: (options?: InstrumentOptions) => Promise<Instrument>;
48
+ export type MetronomeOptions = Omit<InstrumentOptions, 'notes'> & {
49
+ /** Default 'woodblock'. */
50
+ voice?: MetronomeVoice;
51
+ /** Output volume, 0-1. Default 0.5 (gentle). */
52
+ gain?: number;
53
+ };
54
+ export type Metronome = {
55
+ readonly voice: MetronomeVoice;
56
+ readonly output: GainNode;
57
+ /**
58
+ * Schedule one beat at `time` (AudioContext seconds). `beat` counts from 0 within the bar; beat 0 is accented unless
59
+ * `accent` says otherwise. Returns a stop function for that beat.
60
+ */
61
+ tick(options: {
62
+ time: number;
63
+ beat: number;
64
+ accent?: boolean;
65
+ }): () => void;
66
+ /**
67
+ * Resolves once the drum kit and the notes of the voice given to `createMetronome` have loaded (or failed: then the
68
+ * synthesized sounds keep playing). `createMetronome` itself does not wait for it.
69
+ */
70
+ readonly ready: Promise<void>;
71
+ /** Switch voice; resolves when the drum kit notes the voice needs have loaded. Until then the voice plays its synthesized sound. A failed load is retried by the next call. */
72
+ setVoice(voice: MetronomeVoice): Promise<void>;
73
+ /** Stop every sounding and scheduled beat. */
74
+ stop(): void;
75
+ dispose(): void;
76
+ };
77
+ /**
78
+ * A metronome on the shared AudioContext; drum voices come from the same drum kit loadDrumKit plays. Resolves at once:
79
+ * the drum kit (a few MB for the woodblock's TimGM6mb kit) loads in the background, and beats play the synthesized
80
+ * sound until it arrives (`await metronome.ready` to wait).
81
+ */
82
+ export declare function createMetronome(options?: MetronomeOptions): Promise<Metronome>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What a metronome beat sounds like, per voice (public through pepperscore/audio's createMetronome). Pure: no Web Audio
3
+ * and no runtime imports (validation/audio-api.mjs runs it in Node).
4
+ * - woodblock: GM hi wood block (76) on beat 1, low wood block (77) on the others, when the drum kit has them; else a
5
+ * soft synthesized knock.
6
+ * - drum-machine: kick (36) on beat 1, closed hi-hat (42) on the others, from the drum kit.
7
+ * - click: a soft, short synthesized tick.
8
+ * Beat 1 (`beat` 0) is accented: a louder note, or a higher, louder synth sound.
9
+ */
10
+ export type MetronomeVoice = 'woodblock' | 'drum-machine' | 'click';
11
+ export declare const METRONOME_VOICES: readonly MetronomeVoice[];
12
+ export type MetronomeHit = {
13
+ kind: 'note';
14
+ note: number;
15
+ velocity: number;
16
+ } | {
17
+ kind: 'knock' | 'tick';
18
+ accent: boolean;
19
+ };
20
+ export declare const WOODBLOCK_HIGH = 76;
21
+ export declare const WOODBLOCK_LOW = 77;
22
+ export declare const KICK = 36;
23
+ export declare const CLOSED_HAT = 42;
24
+ /** Drum notes a voice plays (loaded up front); the synthesized click needs none. */
25
+ export declare const metronomeNotes: (voice: MetronomeVoice) => number[];
26
+ /**
27
+ * The hits for one beat. `beat` counts from 0 (beat 1 of the bar) and `accent` defaults to `beat === 0`. `kit` says
28
+ * whether the drum kit loaded and which notes it plays; without it, sample voices fall back to synthesized sounds.
29
+ */
30
+ export declare function metronomeHits(voice: MetronomeVoice, beat: number, { accent, covers }?: {
31
+ accent?: boolean;
32
+ covers?: (note: number) => boolean;
33
+ }): MetronomeHit[];