@real-music-packages/web-core 0.50.0 → 0.51.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.
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-55J6XMHW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { Hand, HandNotePair, HarmonyEvent, Pitch, RhythmEvent, SingleNoteEvent } from './types.js';
2
+
1
3
  declare const NOTE_NAMES: readonly ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
2
4
  declare const NOTE_NAMES_FLAT: readonly ["C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"];
3
5
  /** Note name (e.g. "C", "C#", "Db", "Cb") → pitch class 0..11. */
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./chunk-55J6XMHW.js";
1
2
  import {
2
3
  INTERVALS,
3
4
  MAX_FRET,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/frequency.ts","../src/chords.ts","../src/shareCard.ts"],"sourcesContent":["/** Equal-temperament MIDI → frequency (A4 = MIDI 69 = 440 Hz). */\nexport function midiToFrequency(midi: number): number {\n return 440 * Math.pow(2, (midi - 69) / 12);\n}\n","export interface ChordTemplate { name: string; intervals: number[] }\n\n/** Chord-quality templates, richer qualities first so naming matches the\n * most specific quality (e.g. maj7 before plain major). */\nexport const CHORD_TEMPLATES: ChordTemplate[] = [\n { name: 'maj7', intervals: [0, 4, 7, 11] },\n { name: '7', intervals: [0, 4, 7, 10] },\n { name: 'm7', intervals: [0, 3, 7, 10] },\n { name: 'dim7', intervals: [0, 3, 6, 9] },\n { name: 'm7b5', intervals: [0, 3, 6, 10] },\n { name: '', intervals: [0, 4, 7] },\n { name: 'm', intervals: [0, 3, 7] },\n { name: 'dim', intervals: [0, 3, 6] },\n { name: 'aug', intervals: [0, 4, 8] },\n { name: 'sus4', intervals: [0, 5, 7] },\n { name: 'sus2', intervals: [0, 2, 7] },\n];\n","// Post-round score-share card: branded 1080×1350 PNG rendered client-side.\n// Pure-canvas, no font/image fetches — safe on any origin.\n\nexport interface ShareCardOptions {\n appName: string; // \"Real Ear Trainer\"\n accentColor: string; // app theme accent, e.g. \"#5492D4\"\n drillTitle: string; // \"Name the Interval\"\n score: number;\n total: number;\n streakBest: number;\n url: string; // canonical drill URL printed on the card\n logoText?: string; // defaults to appName\n}\n\nexport function shareFilename(appName: string, drillTitle: string): string {\n const slug = (s: string) =>\n s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n return `${slug(appName)}-${slug(drillTitle)}-score.png`;\n}\n\nexport function shareCardLines(r: { score: number; total: number; streakBest: number }) {\n return {\n scoreLine: `${r.score} / ${r.total}`,\n percentLine: `${Math.round((r.score / r.total) * 100)}%`,\n streakLine: `Best streak: ${r.streakBest}`,\n };\n}\n\nconst W = 1080;\nconst H = 1350;\nconst FONT = \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\";\n\nexport async function renderShareCard(opts: ShareCardOptions): Promise<Blob> {\n const canvas = document.createElement('canvas');\n canvas.width = W;\n canvas.height = H;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('canvas 2d unavailable');\n const { scoreLine, percentLine, streakLine } = shareCardLines(opts);\n\n // Background: near-black with a soft accent glow top-right.\n ctx.fillStyle = '#0b0e14';\n ctx.fillRect(0, 0, W, H);\n const glow = ctx.createRadialGradient(W * 0.8, H * 0.15, 0, W * 0.8, H * 0.15, W * 0.9);\n glow.addColorStop(0, opts.accentColor + '55');\n glow.addColorStop(1, 'transparent');\n ctx.fillStyle = glow;\n ctx.fillRect(0, 0, W, H);\n\n ctx.textAlign = 'center';\n ctx.fillStyle = '#9aa3b2';\n ctx.font = `600 44px ${FONT}`;\n ctx.fillText((opts.logoText ?? opts.appName).toUpperCase(), W / 2, 140);\n\n ctx.fillStyle = '#e8ecf3';\n ctx.font = `800 72px ${FONT}`;\n ctx.fillText(opts.drillTitle, W / 2, 320);\n\n ctx.fillStyle = opts.accentColor;\n ctx.font = `800 260px ${FONT}`;\n ctx.fillText(percentLine, W / 2, 680);\n\n ctx.fillStyle = '#e8ecf3';\n ctx.font = `600 56px ${FONT}`;\n ctx.fillText(`${scoreLine} correct`, W / 2, 790);\n ctx.fillText(streakLine, W / 2, 880);\n\n ctx.fillStyle = '#9aa3b2';\n ctx.font = `600 44px ${FONT}`;\n ctx.fillText('Can you beat it?', W / 2, 1120);\n ctx.fillStyle = opts.accentColor;\n ctx.font = `700 48px ${FONT}`;\n ctx.fillText(opts.url.replace(/^https?:\\/\\//, ''), W / 2, 1200);\n\n return await new Promise<Blob>((resolve, reject) =>\n canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png')\n );\n}\n\n/** Share via navigator.share(files) → clipboard image → download. Never throws. */\nexport async function shareOrDownload(\n blob: Blob,\n filename: string,\n shareText: string\n): Promise<'shared' | 'copied' | 'downloaded'> {\n const file = new File([blob], filename, { type: 'image/png' });\n try {\n if (typeof navigator !== 'undefined' && navigator.canShare?.({ files: [file] })) {\n await navigator.share({ files: [file], text: shareText });\n return 'shared';\n }\n } catch {\n /* user cancelled or share failed — fall through */\n }\n try {\n if (typeof ClipboardItem !== 'undefined' && navigator.clipboard?.write) {\n await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);\n return 'copied';\n }\n } catch {\n /* clipboard blocked — fall through */\n }\n const a = document.createElement('a');\n a.href = URL.createObjectURL(blob);\n a.download = filename;\n a.click();\n URL.revokeObjectURL(a.href);\n return 'downloaded';\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/frequency.ts","../src/chords.ts","../src/shareCard.ts"],"sourcesContent":["/** Equal-temperament MIDI → frequency (A4 = MIDI 69 = 440 Hz). */\nexport function midiToFrequency(midi: number): number {\n return 440 * Math.pow(2, (midi - 69) / 12);\n}\n","export interface ChordTemplate { name: string; intervals: number[] }\n\n/** Chord-quality templates, richer qualities first so naming matches the\n * most specific quality (e.g. maj7 before plain major). */\nexport const CHORD_TEMPLATES: ChordTemplate[] = [\n { name: 'maj7', intervals: [0, 4, 7, 11] },\n { name: '7', intervals: [0, 4, 7, 10] },\n { name: 'm7', intervals: [0, 3, 7, 10] },\n { name: 'dim7', intervals: [0, 3, 6, 9] },\n { name: 'm7b5', intervals: [0, 3, 6, 10] },\n { name: '', intervals: [0, 4, 7] },\n { name: 'm', intervals: [0, 3, 7] },\n { name: 'dim', intervals: [0, 3, 6] },\n { name: 'aug', intervals: [0, 4, 8] },\n { name: 'sus4', intervals: [0, 5, 7] },\n { name: 'sus2', intervals: [0, 2, 7] },\n];\n","// Post-round score-share card: branded 1080×1350 PNG rendered client-side.\n// Pure-canvas, no font/image fetches — safe on any origin.\n\nexport interface ShareCardOptions {\n appName: string; // \"Real Ear Trainer\"\n accentColor: string; // app theme accent, e.g. \"#5492D4\"\n drillTitle: string; // \"Name the Interval\"\n score: number;\n total: number;\n streakBest: number;\n url: string; // canonical drill URL printed on the card\n logoText?: string; // defaults to appName\n}\n\nexport function shareFilename(appName: string, drillTitle: string): string {\n const slug = (s: string) =>\n s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n return `${slug(appName)}-${slug(drillTitle)}-score.png`;\n}\n\nexport function shareCardLines(r: { score: number; total: number; streakBest: number }) {\n return {\n scoreLine: `${r.score} / ${r.total}`,\n percentLine: `${Math.round((r.score / r.total) * 100)}%`,\n streakLine: `Best streak: ${r.streakBest}`,\n };\n}\n\nconst W = 1080;\nconst H = 1350;\nconst FONT = \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\";\n\nexport async function renderShareCard(opts: ShareCardOptions): Promise<Blob> {\n const canvas = document.createElement('canvas');\n canvas.width = W;\n canvas.height = H;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('canvas 2d unavailable');\n const { scoreLine, percentLine, streakLine } = shareCardLines(opts);\n\n // Background: near-black with a soft accent glow top-right.\n ctx.fillStyle = '#0b0e14';\n ctx.fillRect(0, 0, W, H);\n const glow = ctx.createRadialGradient(W * 0.8, H * 0.15, 0, W * 0.8, H * 0.15, W * 0.9);\n glow.addColorStop(0, opts.accentColor + '55');\n glow.addColorStop(1, 'transparent');\n ctx.fillStyle = glow;\n ctx.fillRect(0, 0, W, H);\n\n ctx.textAlign = 'center';\n ctx.fillStyle = '#9aa3b2';\n ctx.font = `600 44px ${FONT}`;\n ctx.fillText((opts.logoText ?? opts.appName).toUpperCase(), W / 2, 140);\n\n ctx.fillStyle = '#e8ecf3';\n ctx.font = `800 72px ${FONT}`;\n ctx.fillText(opts.drillTitle, W / 2, 320);\n\n ctx.fillStyle = opts.accentColor;\n ctx.font = `800 260px ${FONT}`;\n ctx.fillText(percentLine, W / 2, 680);\n\n ctx.fillStyle = '#e8ecf3';\n ctx.font = `600 56px ${FONT}`;\n ctx.fillText(`${scoreLine} correct`, W / 2, 790);\n ctx.fillText(streakLine, W / 2, 880);\n\n ctx.fillStyle = '#9aa3b2';\n ctx.font = `600 44px ${FONT}`;\n ctx.fillText('Can you beat it?', W / 2, 1120);\n ctx.fillStyle = opts.accentColor;\n ctx.font = `700 48px ${FONT}`;\n ctx.fillText(opts.url.replace(/^https?:\\/\\//, ''), W / 2, 1200);\n\n return await new Promise<Blob>((resolve, reject) =>\n canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png')\n );\n}\n\n/** Share via navigator.share(files) → clipboard image → download. Never throws. */\nexport async function shareOrDownload(\n blob: Blob,\n filename: string,\n shareText: string\n): Promise<'shared' | 'copied' | 'downloaded'> {\n const file = new File([blob], filename, { type: 'image/png' });\n try {\n if (typeof navigator !== 'undefined' && navigator.canShare?.({ files: [file] })) {\n await navigator.share({ files: [file], text: shareText });\n return 'shared';\n }\n } catch {\n /* user cancelled or share failed — fall through */\n }\n try {\n if (typeof ClipboardItem !== 'undefined' && navigator.clipboard?.write) {\n await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);\n return 'copied';\n }\n } catch {\n /* clipboard blocked — fall through */\n }\n const a = document.createElement('a');\n a.href = URL.createObjectURL(blob);\n a.download = filename;\n a.click();\n URL.revokeObjectURL(a.href);\n return 'downloaded';\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared note-domain types, used by the notation UI and the app layer alike.
3
+ * Moved here from stave's `$lib/types` so a Svelte component package
4
+ * (@real-music-packages/web-ui) and the app can reference one definition
5
+ * (2026-08-27). Pure types — no imports, no cycle.
6
+ */
7
+ interface Pitch {
8
+ midi: number;
9
+ letter: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G';
10
+ accidental: -1 | 0 | 1;
11
+ octave: number;
12
+ }
13
+ type Hand = 'left' | 'right';
14
+ interface HandNotePair {
15
+ rh: Pitch;
16
+ lh: Pitch;
17
+ measureNumber: number;
18
+ }
19
+ interface SingleNoteEvent {
20
+ pitch: Pitch;
21
+ hand: Hand;
22
+ measureNumber: number;
23
+ /** Shared by notes in the same vertical score onset. Optional for synthetic
24
+ * one-note drills, where each event remains sequential. */
25
+ onsetId?: number;
26
+ /** Where this onset falls, in quarter notes from the start of the EXCERPT
27
+ * (not the piece) — the engraved rhythm, so rests and unequal durations are
28
+ * already accounted for. What in-tempo reading times itself against.
29
+ * Optional for synthetic drills, which have no engraved rhythm. */
30
+ onsetQuarters?: number;
31
+ /** Ornament flag from the source score, so scoring/display can treat a
32
+ * decorated note as an ornament instead of an ordinary single onset. OSMD's
33
+ * Mordent/InvertedMordent and Turn/InvertedTurn/Delayed* variants collapse
34
+ * to 'mordent'/'turn' — direction isn't preserved. */
35
+ ornament?: 'trill' | 'mordent' | 'turn' | 'grace';
36
+ }
37
+ interface HarmonyEvent {
38
+ /** All pitched notes sounding at this onset, sorted by MIDI ascending. */
39
+ pitches: Pitch[];
40
+ measureNumber: number;
41
+ }
42
+ interface RhythmEvent {
43
+ measureNumber: number;
44
+ beatsPerMeasure: number;
45
+ beatUnit: number;
46
+ /** Onset positions in beats from measure start, sorted ascending. */
47
+ onsets: number[];
48
+ /** Actual measure length in beats (same unit as `onsets`). Equals
49
+ * `beatsPerMeasure` for a full bar; smaller for a pickup/anacrusis bar.
50
+ * Optional for back-compat; consumers default to `beatsPerMeasure`. */
51
+ actualBeats?: number;
52
+ /** Right-hand-staff onsets (staff index 0), beats from measure start. */
53
+ onsetsRH?: number[];
54
+ /** Left-hand-staff onsets (staff index 1+), beats from measure start. */
55
+ onsetsLH?: number[];
56
+ }
57
+
58
+ export type { Hand, HandNotePair, HarmonyEvent, Pitch, RhythmEvent, SingleNoteEvent };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ import "./chunk-55J6XMHW.js";
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-music-packages/web-core",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "Shared music-theory + audio primitives for the music-suite web apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -66,6 +66,10 @@
66
66
  "./scene/headless": {
67
67
  "types": "./dist/scene/headless.d.ts",
68
68
  "import": "./dist/scene/headless.js"
69
+ },
70
+ "./types": {
71
+ "types": "./dist/types.d.ts",
72
+ "import": "./dist/types.js"
69
73
  }
70
74
  },
71
75
  "files": [