@scorelabs/core 1.0.10 → 1.0.11

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,4 @@
1
+ export declare const DEFAULT_WEBSITE_URL: string;
2
+ export declare const DEFAULT_VIEWER_URL: string;
3
+ export declare const DEFAULT_API_URL: string;
4
+ export declare const DEFAULT_DATABASE_URL: string;
@@ -0,0 +1,10 @@
1
+ const getEnv = (key, defaultValue) => {
2
+ if (typeof process !== 'undefined' && process.env && process.env[key]) {
3
+ return process.env[key];
4
+ }
5
+ return defaultValue;
6
+ };
7
+ export const DEFAULT_WEBSITE_URL = getEnv('WEBSITE_URL', 'http://localhost:5173');
8
+ export const DEFAULT_VIEWER_URL = getEnv('VIEWER_URL', 'http://localhost:5174');
9
+ export const DEFAULT_API_URL = getEnv('API_URL', 'http://localhost:3000');
10
+ export const DEFAULT_DATABASE_URL = getEnv('TURSO_DATABASE_URL', 'file:local.db');
@@ -1,4 +1,4 @@
1
- import { ScoreJSON } from '../models/Score';
1
+ import { ScoreJSON } from '../models/Score.js';
2
2
  /**
3
3
  * MusicXML Parser
4
4
  *
@@ -25,6 +25,7 @@ export declare class MusicXMLParser {
25
25
  private parseTitle;
26
26
  private parseCopyright;
27
27
  private parseComposer;
28
+ private parseLyricist;
28
29
  private parsePartList;
29
30
  private parseParts;
30
31
  private parsePart;
@@ -1,6 +1,7 @@
1
1
  import JSZip from 'jszip';
2
- import { getInstrumentByProgram, guessInstrumentByName } from '../models/Instrument';
3
- import { Accidental, Arpeggio, Articulation, BarlineStyle, Bowing, Clef, DURATION_VALUES, Duration, GlissandoType, HairpinType, NoteheadShape, Ornament, OttavaType, StemDirection, decomposeDuration, } from '../models/types';
2
+ import { getInstrumentByProgram, guessInstrumentByName } from '../models/Instrument.js';
3
+ import { Accidental, Arpeggio, Articulation, BarlineStyle, Bowing, Clef, Duration, GlissandoType, HairpinType, NoteheadShape, Ornament, OttavaType, StemDirection, calculateDurationValue, decomposeDuration, } from '../models/types.js';
4
+ // TODO: Write tests for MusicXMLParser. It is important to test it thoroughly.
4
5
  /**
5
6
  * MusicXML Parser
6
7
  *
@@ -81,7 +82,7 @@ export class MusicXMLParser {
81
82
  if (!_parser)
82
83
  throw new Error('No DOMParser available');
83
84
  const containerDoc = _parser.parseFromString(containerXml, 'application/xml');
84
- const rootfile = (new MusicXMLParser(_parser)).querySelector(containerDoc, 'rootfile');
85
+ const rootfile = new MusicXMLParser(_parser).querySelector(containerDoc, 'rootfile');
85
86
  const fullPath = rootfile?.getAttribute('full-path');
86
87
  if (!fullPath)
87
88
  throw new Error('Invalid MXL: Could not find main score file path');
@@ -120,6 +121,7 @@ export class MusicXMLParser {
120
121
  const title = this.parseTitle(doc);
121
122
  const subtitle = this.parseSubtitle(doc);
122
123
  const composer = this.parseComposer(doc);
124
+ const lyricist = this.parseLyricist(doc);
123
125
  const copyright = this.parseCopyright(doc);
124
126
  const partInfo = this.parsePartList(doc);
125
127
  const parts = this.parseParts(doc, partInfo);
@@ -127,6 +129,7 @@ export class MusicXMLParser {
127
129
  let globalTempoDuration = Duration.Quarter;
128
130
  let globalTempoIsDotted = false;
129
131
  let globalTempoText = '';
132
+ let globalTempoDotCount = 0;
130
133
  if (parts.length > 0 && parts[0].staves.length > 0 && parts[0].staves[0].measures.length > 0) {
131
134
  const firstMeasure = parts[0].staves[0].measures[0];
132
135
  if (firstMeasure.tempo) {
@@ -134,6 +137,7 @@ export class MusicXMLParser {
134
137
  globalTempoDuration = firstMeasure.tempo.duration;
135
138
  globalTempoIsDotted = firstMeasure.tempo.isDotted;
136
139
  globalTempoText = firstMeasure.tempo.text || '';
140
+ globalTempoDotCount = firstMeasure.tempo.dotCount || 0;
137
141
  }
138
142
  }
139
143
  const score = {
@@ -151,7 +155,9 @@ export class MusicXMLParser {
151
155
  tempoDuration: globalTempoDuration,
152
156
  tempoIsDotted: globalTempoIsDotted,
153
157
  tempoText: globalTempoText,
158
+ tempoDotCount: globalTempoDotCount,
154
159
  copyright,
160
+ lyricist,
155
161
  };
156
162
  return this.postProcess(score);
157
163
  }
@@ -210,7 +216,38 @@ export class MusicXMLParser {
210
216
  if (creator.getAttribute('type') === 'composer')
211
217
  return creator.textContent ?? 'Unknown';
212
218
  }
213
- return this.querySelector(doc, 'identification creator')?.textContent ?? 'Unknown';
219
+ // If no type="composer", but there is only one creator, assume it's the composer
220
+ if (creators.length === 1)
221
+ return (creators[0].textContent ?? 'Unknown');
222
+ // Check credits for composer type
223
+ const credits = this.querySelectorAll(doc, 'credit');
224
+ for (const credit of Array.from(credits)) {
225
+ const type = this.querySelector(credit, 'credit-type');
226
+ if (type?.textContent?.toLowerCase() === 'composer') {
227
+ const words = this.querySelector(credit, 'credit-words');
228
+ if (words?.textContent)
229
+ return words.textContent;
230
+ }
231
+ }
232
+ return creators.length > 0 ? creators[0].textContent ?? 'Unknown' : 'Unknown';
233
+ }
234
+ parseLyricist(doc) {
235
+ const creators = this.querySelectorAll(doc, 'identification creator');
236
+ for (const creator of Array.from(creators)) {
237
+ if (creator.getAttribute('type') === 'lyricist')
238
+ return creator.textContent ?? '';
239
+ }
240
+ // Check credits for lyricist type
241
+ const credits = this.querySelectorAll(doc, 'credit');
242
+ for (const credit of Array.from(credits)) {
243
+ const type = this.querySelector(credit, 'credit-type');
244
+ if (type?.textContent?.toLowerCase() === 'lyricist') {
245
+ const words = this.querySelector(credit, 'credit-words');
246
+ if (words?.textContent)
247
+ return words.textContent;
248
+ }
249
+ }
250
+ return '';
214
251
  }
215
252
  parsePartList(doc) {
216
253
  const partInfo = new Map();
@@ -347,7 +384,7 @@ export class MusicXMLParser {
347
384
  staffVoices.set(i, new Map());
348
385
  const children = this.getChildren(measureElement);
349
386
  let pendingChord = undefined;
350
- let currentNoteSetMap = new Map();
387
+ const currentNoteSetMap = new Map();
351
388
  const beamGroupIdMap = new Map();
352
389
  let globalBeamId = 1;
353
390
  let currentHairpin = undefined;
@@ -364,8 +401,9 @@ export class MusicXMLParser {
364
401
  if (metronome) {
365
402
  const beatUnit = this.getText(metronome, 'beat-unit') || 'quarter';
366
403
  const bpm = parseInt(this.getText(metronome, 'per-minute') || '120');
367
- const isDotted = this.querySelector(metronome, 'beat-unit-dot') !== null;
368
- measureTempo = { bpm, duration: this.mapDuration(beatUnit), isDotted };
404
+ const dotCount = this.querySelectorAll(metronome, 'beat-unit-dot').length;
405
+ const isDotted = dotCount > 0;
406
+ measureTempo = { bpm, duration: this.mapDuration(beatUnit), isDotted, dotCount };
369
407
  }
370
408
  const rehearsal = this.querySelector(child, 'rehearsal');
371
409
  if (rehearsal)
@@ -514,12 +552,13 @@ export class MusicXMLParser {
514
552
  const v1 = vMap0.get('1') || vMap0.get(Array.from(vMap0.keys())[0]);
515
553
  if (v1) {
516
554
  // Check duration of first voice in first staff
517
- const expectedDivisions = this.currentBeats * (divisions * 4 / this.currentBeatType);
555
+ const expectedDivisions = this.currentBeats * ((divisions * 4) / this.currentBeatType);
518
556
  let totalMeasureDivs = 0;
519
557
  const notes = this.querySelectorAll(measureElement, 'note');
520
558
  // Find first voice in first staff
521
559
  for (const n of notes) {
522
- if ((this.getText(n, 'staff') || '1') === '1' && (this.getText(n, 'voice') || '1') === (Array.from(vMap0.keys())[0] || '1')) {
560
+ if ((this.getText(n, 'staff') || '1') === '1' &&
561
+ (this.getText(n, 'voice') || '1') === (Array.from(vMap0.keys())[0] || '1')) {
523
562
  if (this.querySelector(n, 'chord'))
524
563
  continue;
525
564
  totalMeasureDivs += this.getNumber(n, 'duration') || 0;
@@ -535,7 +574,7 @@ export class MusicXMLParser {
535
574
  let voicesIndices = Array.from(vMap.keys()).sort();
536
575
  if (voicesIndices.length === 0)
537
576
  voicesIndices = ['1'];
538
- const voices = voicesIndices.map(id => vMap.get(id) || []);
577
+ const voices = voicesIndices.map((id) => vMap.get(id) || []);
539
578
  const measure = {
540
579
  voices: voices,
541
580
  timeSignature: measureTimeSignature,
@@ -553,8 +592,7 @@ export class MusicXMLParser {
553
592
  const targetDur = (this.currentBeats * 4) / this.currentBeatType;
554
593
  measure.voices = measure.voices.map((v) => {
555
594
  const currentDur = v.reduce((sum, ns) => {
556
- const base = DURATION_VALUES[ns.notes[0].duration];
557
- return sum + (ns.notes[0].isDotted ? base * 1.5 : base);
595
+ return sum + calculateDurationValue(ns.notes[0].duration, ns.notes[0].dotCount || (ns.notes[0].isDotted ? 1 : 0));
558
596
  }, 0);
559
597
  if (currentDur < targetDur - 0.001) {
560
598
  const gap = targetDur - currentDur;
@@ -583,9 +621,10 @@ export class MusicXMLParser {
583
621
  const isGrace = this.querySelector(noteElement, 'grace') !== null;
584
622
  const type = this.getText(noteElement, 'type');
585
623
  const duration = type ? this.mapDuration(type) : Duration.Quarter;
586
- const isDotted = this.querySelector(noteElement, 'dot') !== null;
624
+ const dotCount = this.querySelectorAll(noteElement, 'dot').length;
625
+ const isDotted = dotCount > 0;
587
626
  if (isRest)
588
- return { duration, isRest: true, isDotted };
627
+ return { duration, isRest: true, isDotted, dotCount };
589
628
  let step = 'C', octave = 4, alter = 0;
590
629
  const unpitched = this.querySelector(noteElement, 'unpitched');
591
630
  if (unpitched) {
@@ -751,7 +790,9 @@ export class MusicXMLParser {
751
790
  pullOff = pullOffEl.getAttribute('type') || 'start';
752
791
  }
753
792
  let glissando = undefined;
754
- const glissEl = notations ? (this.querySelector(notations, 'glissando') || this.querySelector(notations, 'slide')) : null;
793
+ const glissEl = notations
794
+ ? this.querySelector(notations, 'glissando') || this.querySelector(notations, 'slide')
795
+ : null;
755
796
  if (glissEl) {
756
797
  const type = glissEl.getAttribute('type');
757
798
  if (type === 'start' || type === 'stop') {
@@ -782,6 +823,7 @@ export class MusicXMLParser {
782
823
  duration,
783
824
  pitch: { midiNumber, step: this.stepToNumber(step), alter, octave },
784
825
  isDotted,
826
+ dotCount,
785
827
  accidental,
786
828
  slur,
787
829
  tie,
@@ -1 +1 @@
1
- export * from './MusicXMLParser';
1
+ export * from './MusicXMLParser.js';
@@ -1 +1 @@
1
- export * from './MusicXMLParser';
1
+ export * from './MusicXMLParser.js';
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './models/index.js';
2
2
  export * from './importers/index.js';
3
+ export * from './constants.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './models/index.js';
2
2
  export * from './importers/index.js';
3
+ export * from './constants.js';
@@ -1,6 +1,6 @@
1
- import { Note, NoteJSON } from './Note';
2
- import { NoteSet, NoteSetJSON } from './NoteSet';
3
- import { BarlineStyle, Clef, Duration, KeySignature, Repeat, Tempo, TimeSignature, Volta } from './types';
1
+ import { Note, NoteJSON } from './Note.js';
2
+ import { NoteSet, NoteSetJSON } from './NoteSet.js';
3
+ import { BarlineStyle, Clef, Duration, KeySignature, Repeat, Tempo, TimeSignature, Volta } from './types.js';
4
4
  /**
5
5
  * Represents a single measure containing note sets, potentially in multiple voices.
6
6
  */
@@ -20,9 +20,12 @@ export declare class Measure {
20
20
  readonly barlineStyle: BarlineStyle;
21
21
  constructor(voices: NoteSet[][], timeSignature?: TimeSignature | undefined, keySignature?: KeySignature | undefined, systemBreak?: boolean, pageBreak?: boolean, repeats?: Repeat[], volta?: Volta | undefined, isPickup?: boolean, clef?: Clef | undefined, tempo?: Tempo | undefined, rehearsalMark?: string | undefined, systemText?: string | undefined, barlineStyle?: BarlineStyle);
22
22
  get notes(): NoteSet[];
23
+ getNoteOffset(voiceIndex: number, noteIndex: number): number;
24
+ moveNoteToVoice(fromVoiceIndex: number, noteIndex: number, toVoiceIndex: number): Measure;
25
+ splitNote(voiceIndex: number, noteIndex: number, splitOffset: number): Measure;
23
26
  get hasStartRepeat(): boolean;
24
27
  get hasEndRepeat(): boolean;
25
- changeNoteDuration(noteIndex: number, newDuration: Duration, isDotted?: boolean, voiceIndex?: number): Measure;
28
+ changeNoteDuration(noteIndex: number, newDuration: Duration, dotCountOrIsDotted?: number | boolean, voiceIndex?: number): Measure;
26
29
  getTotalDuration(voiceIndex?: number): number;
27
30
  static fromJSON(data: MeasureJSON): Measure;
28
31
  transpose(semitones: number): Measure;
@@ -1,6 +1,6 @@
1
- import { Note } from './Note';
2
- import { NoteSet } from './NoteSet';
3
- import { BarlineStyle, DURATION_VALUES, Duration, decomposeDuration, } from './types';
1
+ import { Note } from './Note.js';
2
+ import { NoteSet } from './NoteSet.js';
3
+ import { BarlineStyle, Duration, calculateDurationValue, decomposeDuration, } from './types.js';
4
4
  /**
5
5
  * Represents a single measure containing note sets, potentially in multiple voices.
6
6
  */
@@ -39,26 +39,110 @@ export class Measure {
39
39
  get notes() {
40
40
  return this.voices[0] || [];
41
41
  }
42
+ getNoteOffset(voiceIndex, noteIndex) {
43
+ const voice = this.voices[voiceIndex];
44
+ if (!voice)
45
+ return 0;
46
+ let offset = 0;
47
+ for (let i = 0; i < noteIndex && i < voice.length; i++) {
48
+ offset += voice[i].getDurationValue();
49
+ }
50
+ return offset;
51
+ }
52
+ moveNoteToVoice(fromVoiceIndex, noteIndex, toVoiceIndex) {
53
+ if (fromVoiceIndex === toVoiceIndex)
54
+ return this;
55
+ const fromVoice = this.voices[fromVoiceIndex];
56
+ if (!fromVoice || noteIndex < 0 || noteIndex >= fromVoice.length)
57
+ return this;
58
+ const noteSetToMove = fromVoice[noteIndex];
59
+ if (noteSetToMove.isRest)
60
+ return this;
61
+ const onset = this.getNoteOffset(fromVoiceIndex, noteIndex);
62
+ // 1. Replace with rest in fromVoice first to get the source updated
63
+ let updatedMeasure = this.deleteNote(noteIndex, fromVoiceIndex);
64
+ // 2. Ensure toVoice exists and has rests up to total duration
65
+ const targetDur = this.timeSignature
66
+ ? (this.timeSignature.beats * 4) / this.timeSignature.beatType
67
+ : this.getTotalDuration(fromVoiceIndex);
68
+ updatedMeasure = updatedMeasure.fillVoiceWithRests(toVoiceIndex, targetDur);
69
+ // 3. Find the note/rest at 'onset' in toVoice and split if necessary
70
+ let toVoice = updatedMeasure.voices[toVoiceIndex];
71
+ let currentOffset = 0;
72
+ let targetNoteIndex = -1;
73
+ for (let i = 0; i < toVoice.length; i++) {
74
+ const dur = toVoice[i].getDurationValue();
75
+ if (Math.abs(currentOffset - onset) < 0.001) {
76
+ targetNoteIndex = i;
77
+ break;
78
+ }
79
+ if (onset > currentOffset + 0.001 && onset < currentOffset + dur - 0.001) {
80
+ // Need to split
81
+ updatedMeasure = updatedMeasure.splitNote(toVoiceIndex, i, onset - currentOffset);
82
+ toVoice = updatedMeasure.voices[toVoiceIndex]; // Update reference
83
+ // After split, the target should be at i + (number of segments added - 1)
84
+ // Let's just re-scan to be safe
85
+ currentOffset = 0;
86
+ for (let j = 0; j < toVoice.length; j++) {
87
+ if (Math.abs(currentOffset - onset) < 0.001) {
88
+ targetNoteIndex = j;
89
+ break;
90
+ }
91
+ currentOffset += toVoice[j].getDurationValue();
92
+ }
93
+ break;
94
+ }
95
+ currentOffset += dur;
96
+ }
97
+ if (targetNoteIndex !== -1) {
98
+ // We found the correct position in the prepared target voice
99
+ updatedMeasure = updatedMeasure.replaceNoteSet(targetNoteIndex, noteSetToMove, toVoiceIndex);
100
+ }
101
+ return updatedMeasure;
102
+ }
103
+ splitNote(voiceIndex, noteIndex, splitOffset) {
104
+ const voice = this.voices[voiceIndex];
105
+ if (!voice || noteIndex < 0 || noteIndex >= voice.length)
106
+ return this;
107
+ const target = voice[noteIndex];
108
+ const duration = target.getDurationValue();
109
+ if (splitOffset <= 0 || splitOffset >= duration - 0.001)
110
+ return this;
111
+ const leftParts = decomposeDuration(splitOffset);
112
+ const rightParts = decomposeDuration(duration - splitOffset);
113
+ const leftNotes = leftParts.map((p) => target.withDuration(p.duration, p.isDotted).withTie(target.isRest ? false : true));
114
+ const rightNotes = rightParts.map((p, idx) => {
115
+ const isLast = idx === rightParts.length - 1;
116
+ return target
117
+ .withDuration(p.duration, p.isDotted)
118
+ .withTie(isLast ? !!target.notes[0].tie : !target.isRest);
119
+ });
120
+ const newVoice = [...voice];
121
+ newVoice.splice(noteIndex, 1, ...leftNotes, ...rightNotes);
122
+ const newVoices = [...this.voices];
123
+ newVoices[voiceIndex] = newVoice;
124
+ return this.withVoices(newVoices);
125
+ }
42
126
  get hasStartRepeat() {
43
- return this.repeats.some((r) => r.type === "start");
127
+ return this.repeats.some((r) => r.type === 'start');
44
128
  }
45
129
  get hasEndRepeat() {
46
- return this.repeats.some((r) => r.type === "end");
130
+ return this.repeats.some((r) => r.type === 'end');
47
131
  }
48
- changeNoteDuration(noteIndex, newDuration, isDotted = false, voiceIndex = 0) {
132
+ changeNoteDuration(noteIndex, newDuration, dotCountOrIsDotted = 0, voiceIndex = 0) {
49
133
  const voice = this.voices[voiceIndex];
50
134
  if (!voice || noteIndex < 0 || noteIndex >= voice.length)
51
135
  return this;
52
136
  const targetNoteSet = voice[noteIndex];
53
137
  const oldVal = targetNoteSet.getDurationValue();
54
- const base = DURATION_VALUES[newDuration];
55
- const newVal = isDotted ? base * 1.5 : base;
138
+ const dotCount = typeof dotCountOrIsDotted === 'number' ? dotCountOrIsDotted : (dotCountOrIsDotted ? 1 : 0);
139
+ const newVal = calculateDurationValue(newDuration, dotCount);
56
140
  if (Math.abs(newVal - oldVal) < 0.001)
57
141
  return this;
58
142
  const updatedVoice = [...voice];
59
143
  if (newVal < oldVal) {
60
144
  const gap = oldVal - newVal;
61
- updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
145
+ updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, dotCountOrIsDotted);
62
146
  const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
63
147
  updatedVoice.splice(noteIndex + 1, 0, ...rests);
64
148
  }
@@ -70,7 +154,7 @@ export class Measure {
70
154
  }
71
155
  if (available < delta - 0.001)
72
156
  return this;
73
- updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
157
+ updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, dotCountOrIsDotted);
74
158
  let consumed = 0;
75
159
  let removeCount = 0;
76
160
  const replacements = [];
@@ -164,7 +248,7 @@ export class Measure {
164
248
  for (let i = index + 1; i < updatedVoice.length; i++) {
165
249
  available += updatedVoice[i].getDurationValue();
166
250
  }
167
- // If there's enough space to consume, do it.
251
+ // If there's enough space to consume, do it.
168
252
  // If not, we still replace it but it will overflow (which simplifyRests might fix or layout will handle)
169
253
  updatedVoice[index] = newNoteSet;
170
254
  let consumed = 0;
@@ -235,7 +319,7 @@ export class Measure {
235
319
  if (ts.beats === 4 || ts.beats === 2) {
236
320
  // In 4/4 or 2/4, eighth notes are often beamed in groups of 2 beats (length 2.0)
237
321
  // unless there are sixteenth notes which force 1-beat groupings.
238
- beamBeatLength = (lastGroupHasSixteenths || isSixteenth) ? 1.0 : 2.0;
322
+ beamBeatLength = lastGroupHasSixteenths || isSixteenth ? 1.0 : 2.0;
239
323
  }
240
324
  }
241
325
  else if (ts.beatType === 8) {
@@ -368,9 +452,11 @@ export class Measure {
368
452
  if (!voice || voice.length === 0)
369
453
  return this;
370
454
  const totalDur = this.getTotalDuration(voiceIndex);
371
- const expectedDur = this.timeSignature ? (this.timeSignature.beats * 4) / this.timeSignature.beatType : 4.0;
455
+ const expectedDur = this.timeSignature
456
+ ? (this.timeSignature.beats * 4) / this.timeSignature.beatType
457
+ : 4.0;
372
458
  // Special Case: If the whole measure is rests, just return a single whole rest (or appropriate duration)
373
- const allRests = voice.every(ns => ns.isRest);
459
+ const allRests = voice.every((ns) => ns.isRest);
374
460
  if (allRests && Math.abs(totalDur - expectedDur) < 0.001) {
375
461
  const newVoice = [new NoteSet([new Note(Duration.Whole, undefined, true)])];
376
462
  const newVoices = [...this.voices];
@@ -1,13 +1,9 @@
1
- import { Pitch } from './Pitch';
2
- import { Accidental, Arpeggio, Articulation, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, Hairpin, Lyric, NoteheadShape, Ornament, Ottava, Pedal, Slur, StemDirection, Tuplet } from './types';
3
- /**
4
- * Represents a single note or rest in a measure.
5
- */
1
+ import { Pitch } from './Pitch.js';
2
+ import { Accidental, Arpeggio, Articulation, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, Hairpin, Lyric, NoteheadShape, Ornament, Ottava, Pedal, Slur, StemDirection, Tuplet } from './types.js';
6
3
  export declare class Note {
7
4
  readonly duration: Duration;
8
5
  readonly pitch?: Pitch | undefined;
9
6
  readonly isRest: boolean;
10
- readonly isDotted: boolean;
11
7
  readonly accidental?: Accidental | undefined;
12
8
  readonly beamGroup?: number | undefined;
13
9
  readonly articulations: Articulation[];
@@ -39,8 +35,10 @@ export declare class Note {
39
35
  readonly hammerOn?: "start" | "stop" | undefined;
40
36
  readonly pullOff?: "start" | "stop" | undefined;
41
37
  constructor(duration: Duration, pitch?: Pitch | undefined, // undefined for rests
42
- isRest?: boolean, isDotted?: boolean, accidental?: Accidental | undefined, beamGroup?: number | undefined, // Group ID for beamed notes
38
+ isRest?: boolean, dotCountOrIsDotted?: number | boolean, accidental?: Accidental | undefined, beamGroup?: number | undefined, // Group ID for beamed notes
43
39
  articulations?: Articulation[], dynamic?: Dynamic | undefined, tie?: boolean | undefined, slur?: Slur | undefined, tuplet?: Tuplet | undefined, hairpin?: Hairpin | undefined, isGrace?: boolean, lyric?: Lyric | undefined, chord?: string | undefined, glissando?: Glissando | undefined, arpeggio?: Arpeggio | undefined, ottava?: Ottava | undefined, pedal?: Pedal | undefined, ornament?: Ornament | undefined, fret?: number | undefined, string?: number | undefined, fretboardDiagram?: FretboardDiagram | undefined, lyrics?: Lyric[] | undefined, staffText?: string | undefined, color?: string | undefined, notehead?: NoteheadShape | undefined, bowing?: Bowing | undefined, fingering?: number | undefined, stemDirection?: StemDirection | undefined, isStringCircled?: boolean | undefined, palmMute?: "start" | "stop" | undefined, hammerOn?: "start" | "stop" | undefined, pullOff?: "start" | "stop" | undefined);
40
+ readonly dotCount: number;
41
+ get isDotted(): boolean;
44
42
  /**
45
43
  * Get all lyrics as standardized objects
46
44
  */
@@ -74,7 +72,7 @@ export declare class Note {
74
72
  withLyrics(lyrics: Lyric[]): Note;
75
73
  withHairpin(hairpin?: Hairpin): Note;
76
74
  withAccidental(accidental?: Accidental): Note;
77
- withDuration(duration: Duration, isDotted?: boolean): Note;
75
+ withDuration(duration: Duration, dotCountOrIsDotted?: number | boolean): Note;
78
76
  withRest(isRest: boolean): Note;
79
77
  withFretboardDiagram(diagram?: FretboardDiagram): Note;
80
78
  withBeamGroup(beamGroup?: number): Note;
@@ -107,6 +105,7 @@ export interface NoteJSON {
107
105
  };
108
106
  isRest?: boolean;
109
107
  isDotted?: boolean;
108
+ dotCount?: number;
110
109
  accidental?: string;
111
110
  beamGroup?: number;
112
111
  articulation?: string;
@@ -1,13 +1,10 @@
1
- import { Pitch } from './Pitch';
2
- import { Accidental, DURATION_VALUES, Duration, } from './types';
3
- /**
4
- * Represents a single note or rest in a measure.
5
- */
1
+ import { Pitch } from './Pitch.js';
2
+ import { Accidental, Duration, calculateDurationValue, } from './types.js';
3
+ // TODO: replace dotCountOrIsDotted with dotCount: number and use it everywhere. Do not worry about legacy stuff.
6
4
  export class Note {
7
5
  duration;
8
6
  pitch;
9
7
  isRest;
10
- isDotted;
11
8
  accidental;
12
9
  beamGroup;
13
10
  articulations;
@@ -39,12 +36,11 @@ export class Note {
39
36
  hammerOn;
40
37
  pullOff;
41
38
  constructor(duration, pitch, // undefined for rests
42
- isRest = false, isDotted = false, accidental, beamGroup, // Group ID for beamed notes
39
+ isRest = false, dotCountOrIsDotted = 0, accidental, beamGroup, // Group ID for beamed notes
43
40
  articulations = [], dynamic, tie, slur, tuplet, hairpin, isGrace = false, lyric, chord, glissando, arpeggio, ottava, pedal, ornament, fret, string, fretboardDiagram, lyrics, staffText, color, notehead, bowing, fingering, stemDirection, isStringCircled, palmMute, hammerOn, pullOff) {
44
41
  this.duration = duration;
45
42
  this.pitch = pitch;
46
43
  this.isRest = isRest;
47
- this.isDotted = isDotted;
48
44
  this.accidental = accidental;
49
45
  this.beamGroup = beamGroup;
50
46
  this.articulations = articulations;
@@ -75,6 +71,12 @@ export class Note {
75
71
  this.palmMute = palmMute;
76
72
  this.hammerOn = hammerOn;
77
73
  this.pullOff = pullOff;
74
+ this.dotCount =
75
+ typeof dotCountOrIsDotted === 'number' ? dotCountOrIsDotted : dotCountOrIsDotted ? 1 : 0;
76
+ }
77
+ dotCount;
78
+ get isDotted() {
79
+ return this.dotCount > 0;
78
80
  }
79
81
  /**
80
82
  * Get all lyrics as standardized objects
@@ -100,8 +102,7 @@ export class Note {
100
102
  getDurationValue() {
101
103
  if (this.isGrace)
102
104
  return 0;
103
- const base = DURATION_VALUES[this.duration];
104
- let val = this.isDotted ? base * 1.5 : base;
105
+ let val = calculateDurationValue(this.duration, this.dotCount);
105
106
  if (this.tuplet) {
106
107
  val = val * (this.tuplet.normal / this.tuplet.actual);
107
108
  }
@@ -120,10 +121,10 @@ export class Note {
120
121
  this.duration === Duration.TwoHundredFiftySixth));
121
122
  }
122
123
  withGrace(isGrace) {
123
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection);
124
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
124
125
  }
125
126
  withChord(chord) {
126
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection);
127
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
127
128
  }
128
129
  transpose(semitones) {
129
130
  if (this.isRest || !this.pitch) {
@@ -133,7 +134,7 @@ export class Note {
133
134
  const chromaticIdx = newPitch.midiNumber % 12;
134
135
  const hasSharp = [1, 3, 6, 8, 10].includes(chromaticIdx);
135
136
  const newAccidental = hasSharp ? Accidental.Sharp : undefined;
136
- return new Note(this.duration, newPitch, this.isRest, this.isDotted, newAccidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
137
+ return new Note(this.duration, newPitch, this.isRest, this.dotCount, newAccidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
137
138
  }
138
139
  transposeOctave(octaves) {
139
140
  if (!this.pitch || this.isRest)
@@ -166,97 +167,97 @@ export class Note {
166
167
  return this.withPitch(newPitch).withAccidental(newAccidental);
167
168
  }
168
169
  withPitch(pitch) {
169
- return new Note(this.duration, pitch, false, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
170
+ return new Note(this.duration, pitch, false, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
170
171
  }
171
172
  withArticulation(articulation) {
172
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, articulation ? [articulation] : [], this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
173
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, articulation ? [articulation] : [], this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
173
174
  }
174
175
  withArticulations(articulations) {
175
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
176
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
176
177
  }
177
178
  withDynamic(dynamic) {
178
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
179
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
179
180
  }
180
181
  withTie(tie) {
181
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
182
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
182
183
  }
183
184
  withSlur(slur) {
184
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
185
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
185
186
  }
186
187
  withTuplet(tuplet) {
187
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
188
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
188
189
  }
189
190
  withLyric(lyric) {
190
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
191
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
191
192
  }
192
193
  withLyrics(lyrics) {
193
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
194
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
194
195
  }
195
196
  withHairpin(hairpin) {
196
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
197
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
197
198
  }
198
199
  withAccidental(accidental) {
199
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
200
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
200
201
  }
201
- withDuration(duration, isDotted = false) {
202
- return new Note(duration, this.pitch, this.isRest, isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
202
+ withDuration(duration, dotCountOrIsDotted = 0) {
203
+ return new Note(duration, this.pitch, this.isRest, dotCountOrIsDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
203
204
  }
204
205
  withRest(isRest) {
205
- return new Note(this.duration, this.pitch, isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
206
+ return new Note(this.duration, this.pitch, isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
206
207
  }
207
208
  withFretboardDiagram(diagram) {
208
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, diagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
209
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, diagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
209
210
  }
210
211
  withBeamGroup(beamGroup) {
211
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
212
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
212
213
  }
213
214
  withGlissando(glissando) {
214
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
215
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
215
216
  }
216
217
  withArpeggio(arpeggio) {
217
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
218
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
218
219
  }
219
220
  withOttava(ottava) {
220
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
221
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
221
222
  }
222
223
  withPedal(pedal) {
223
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
224
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
224
225
  }
225
226
  withOrnament(ornament) {
226
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
227
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
227
228
  }
228
229
  withTab(fret, string) {
229
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, fret, string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
230
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, fret, string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
230
231
  }
231
232
  withStaffText(text) {
232
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, text, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
233
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, text, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
233
234
  }
234
235
  withColor(color) {
235
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, color, this.notehead, this.bowing, this.fingering, this.stemDirection);
236
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
236
237
  }
237
238
  withNotehead(notehead) {
238
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
239
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
239
240
  }
240
241
  withBowing(bowing) {
241
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
242
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
242
243
  }
243
244
  withFingering(fingering) {
244
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
245
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
245
246
  }
246
247
  withStemDirection(stemDirection) {
247
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
248
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
248
249
  }
249
250
  withStringCircled(isStringCircled) {
250
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
251
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, isStringCircled, this.palmMute, this.hammerOn, this.pullOff);
251
252
  }
252
253
  withPalmMute(palmMute) {
253
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, palmMute, this.hammerOn, this.pullOff);
254
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, palmMute, this.hammerOn, this.pullOff);
254
255
  }
255
256
  withHammerOn(hammerOn) {
256
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, hammerOn, this.pullOff);
257
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, hammerOn, this.pullOff);
257
258
  }
258
259
  withPullOff(pullOff) {
259
- return new Note(this.duration, this.pitch, this.isRest, this.isDotted, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, pullOff);
260
+ return new Note(this.duration, this.pitch, this.isRest, this.dotCount, this.accidental, this.beamGroup, this.articulations, this.dynamic, this.tie, this.slur, this.tuplet, this.hairpin, this.isGrace, this.lyric, this.chord, this.glissando, this.arpeggio, this.ottava, this.pedal, this.ornament, this.fret, this.string, this.fretboardDiagram, this.lyrics, this.staffText, this.color, this.notehead, this.bowing, this.fingering, this.stemDirection, this.isStringCircled, this.palmMute, this.hammerOn, pullOff);
260
261
  }
261
262
  toJSON() {
262
263
  return {
@@ -273,6 +274,7 @@ export class Note {
273
274
  : undefined,
274
275
  isRest: this.isRest || undefined,
275
276
  isDotted: this.isDotted || undefined,
277
+ dotCount: this.dotCount || undefined,
276
278
  accidental: this.accidental,
277
279
  beamGroup: this.beamGroup,
278
280
  articulations: this.articulations.length > 0 ? this.articulations : undefined,
@@ -319,6 +321,6 @@ export class Note {
319
321
  else if (data.articulation) {
320
322
  articulations = [data.articulation];
321
323
  }
322
- return new Note(data.duration, pitch, data.isRest ?? false, data.isDotted ?? false, data.accidental, data.beamGroup, articulations, data.dynamic, data.tie, data.slur, data.tuplet, data.hairpin, data.isGrace ?? false, data.lyric, data.chord, data.glissando, data.arpeggio, data.ottava, data.pedal, data.ornament, data.fret, data.string, data.fretboardDiagram, data.lyrics, data.staffText, data.color, data.notehead, data.bowing, data.fingering, data.stemDirection, data.isStringCircled, data.palmMute, data.hammerOn, data.pullOff);
324
+ return new Note(data.duration, pitch, data.isRest ?? false, data.dotCount !== undefined ? data.dotCount : data.isDotted ? 1 : 0, data.accidental, data.beamGroup, articulations, data.dynamic, data.tie, data.slur, data.tuplet, data.hairpin, data.isGrace ?? false, data.lyric, data.chord, data.glissando, data.arpeggio, data.ottava, data.pedal, data.ornament, data.fret, data.string, data.fretboardDiagram, data.lyrics, data.staffText, data.color, data.notehead, data.bowing, data.fingering, data.stemDirection, data.isStringCircled, data.palmMute, data.hammerOn, data.pullOff);
323
325
  }
324
326
  }
@@ -1,17 +1,18 @@
1
- import { Note, NoteJSON } from './Note';
2
- import { Pitch } from './Pitch';
3
- import { Accidental, Arpeggio, Articulation, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, Hairpin, Lyric, NoteheadShape, Ornament, Ottava, Pedal, Slur, StemDirection, Tuplet } from './types';
1
+ import { Note, NoteJSON } from './Note.js';
2
+ import { Pitch } from './Pitch.js';
3
+ import { Accidental, Arpeggio, Articulation, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, Hairpin, Lyric, NoteheadShape, Ornament, Ottava, Pedal, Slur, StemDirection, Tuplet } from './types.js';
4
4
  export declare class NoteSet {
5
5
  readonly notes: Note[];
6
6
  constructor(notes: Note[]);
7
7
  get duration(): Duration;
8
8
  get isDotted(): boolean;
9
+ get dotCount(): number;
9
10
  get isRest(): boolean;
10
11
  get beamGroup(): number | undefined;
11
12
  get tuplet(): Tuplet | undefined;
12
13
  getDurationValue(): number;
13
14
  isBeamable(): boolean;
14
- withDuration(duration: Duration, isDotted?: boolean): NoteSet;
15
+ withDuration(duration: Duration, dotCountOrIsDotted?: number | boolean): NoteSet;
15
16
  withBeamGroup(group?: number): NoteSet;
16
17
  withNotes(notes: Note[]): NoteSet;
17
18
  withLyrics(lyrics: Lyric[]): NoteSet;
@@ -1,5 +1,5 @@
1
- import { Note } from './Note';
2
- import { Duration, } from './types';
1
+ import { Note } from './Note.js';
2
+ import { Duration, } from './types.js';
3
3
  export class NoteSet {
4
4
  notes;
5
5
  constructor(notes) {
@@ -14,6 +14,9 @@ export class NoteSet {
14
14
  get isDotted() {
15
15
  return this.notes[0].isDotted;
16
16
  }
17
+ get dotCount() {
18
+ return this.notes[0].dotCount;
19
+ }
17
20
  get isRest() {
18
21
  return this.notes[0].isRest;
19
22
  }
@@ -29,8 +32,8 @@ export class NoteSet {
29
32
  isBeamable() {
30
33
  return this.notes[0].isBeamable();
31
34
  }
32
- withDuration(duration, isDotted = false) {
33
- return new NoteSet(this.notes.map((n) => n.withDuration(duration, isDotted)));
35
+ withDuration(duration, dotCountOrIsDotted = 0) {
36
+ return new NoteSet(this.notes.map((n) => n.withDuration(duration, dotCountOrIsDotted)));
34
37
  }
35
38
  withBeamGroup(group) {
36
39
  return new NoteSet(this.notes.map((n) => n.withBeamGroup(group)));
@@ -1,8 +1,8 @@
1
- import { Instrument } from './Instrument';
2
- import { Measure } from './Measure';
3
- import { Note } from './Note';
4
- import { Staff, StaffJSON } from './Staff';
5
- import { Duration } from './types';
1
+ import { Instrument } from './Instrument.js';
2
+ import { Measure } from './Measure.js';
3
+ import { Note } from './Note.js';
4
+ import { Staff, StaffJSON } from './Staff.js';
5
+ import { Duration } from './types.js';
6
6
  /**
7
7
  * Represents a musical part (instrument/voice) which can have multiple staves.
8
8
  */
@@ -1,5 +1,5 @@
1
- import { PRESET_INSTRUMENTS } from './Instrument';
2
- import { Staff } from './Staff';
1
+ import { PRESET_INSTRUMENTS } from './Instrument.js';
2
+ import { Staff } from './Staff.js';
3
3
  /**
4
4
  * Represents a musical part (instrument/voice) which can have multiple staves.
5
5
  */
@@ -1,4 +1,4 @@
1
- import { Clef } from './types';
1
+ import { Clef } from './types.js';
2
2
  /**
3
3
  * Pitch representation using MIDI numbers and explicit spelling (step, alter, octave).
4
4
  */
@@ -1,4 +1,4 @@
1
- import { Clef } from './types';
1
+ import { Clef } from './types.js';
2
2
  /**
3
3
  * Pitch representation using MIDI numbers and explicit spelling (step, alter, octave).
4
4
  */
@@ -1,4 +1,4 @@
1
- import { Clef, KeySignature, TimeSignature } from './types';
1
+ import { Clef, KeySignature, TimeSignature } from './types.js';
2
2
  export interface PreMeasureJSON {
3
3
  clef?: string;
4
4
  keySignature?: KeySignature;
@@ -1,8 +1,8 @@
1
- import { Measure } from './Measure';
2
- import { NoteSet } from './NoteSet';
3
- import { Part, PartJSON } from './Part';
4
- import { Staff } from './Staff';
5
- import { Clef, Duration, Genre, KeySignature, TimeSignature } from './types';
1
+ import { Measure } from './Measure.js';
2
+ import { NoteSet } from './NoteSet.js';
3
+ import { Part, PartJSON } from './Part.js';
4
+ import { Staff } from './Staff.js';
5
+ import { Clef, Duration, Genre, KeySignature, TimeSignature } from './types.js';
6
6
  /**
7
7
  * Represents a complete musical score.
8
8
  */
@@ -21,11 +21,13 @@ export declare class Score {
21
21
  readonly subtitle: string;
22
22
  readonly genre: Genre;
23
23
  readonly tempoText: string;
24
- constructor(title: string, composer: string, timeSignature: TimeSignature, keySignature: KeySignature, parts: Part[], bpm?: number, tempoDuration?: Duration, tempoIsDotted?: boolean, copyright?: string, lyricist?: string, swing?: boolean, subtitle?: string, genre?: Genre, tempoText?: string);
24
+ readonly tempoDotCount: number;
25
+ constructor(title: string, composer: string, timeSignature: TimeSignature, keySignature: KeySignature, parts: Part[], bpm?: number, tempoDuration?: Duration, tempoIsDotted?: boolean, copyright?: string, lyricist?: string, swing?: boolean, subtitle?: string, genre?: Genre, tempoText?: string, tempoDotCount?: number);
25
26
  withTitle(title: string): Score;
26
27
  withComposer(composer: string): Score;
27
28
  withSubtitle(subtitle: string): Score;
28
29
  withGenre(genre: Genre): Score;
30
+ withBpm(bpm: number): Score;
29
31
  getMeasureCount(): number;
30
32
  getTimeSignatureAt(measureIndex: number): TimeSignature;
31
33
  getKeySignatureAt(measureIndex: number): KeySignature;
@@ -56,6 +58,7 @@ export declare class Score {
56
58
  replaceMeasure(partIndex: number, staffIndex: number, measureIndex: number, newMeasure: Measure, autoBeam?: boolean): Score;
57
59
  deleteNote(partIndex: number, staffIndex: number, measureIndex: number, noteIndex: number, voiceIndex?: number): Score;
58
60
  changeNoteDuration(partIndex: number, staffIndex: number, measureIndex: number, noteIndex: number, newDuration: Duration, isDotted?: boolean, voiceIndex?: number): Score;
61
+ moveNoteToVoice(partIndex: number, staffIndex: number, measureIndex: number, noteIndex: number, fromVoiceIndex: number, toVoiceIndex: number): Score;
59
62
  pasteNotes(partIndex: number, staffIndex: number, measureIndex: number, noteIndex: number, notesToPaste: NoteSet[]): Score;
60
63
  withTempo(bpm: number, duration: Duration, isDotted: boolean): Score;
61
64
  withTempoText(text: string): Score;
@@ -81,6 +84,7 @@ export interface ScoreJSON {
81
84
  bpm?: number;
82
85
  tempoDuration?: Duration;
83
86
  tempoIsDotted?: boolean;
87
+ tempoDotCount?: number;
84
88
  copyright?: string;
85
89
  lyricist?: string;
86
90
  swing?: boolean;
@@ -1,6 +1,6 @@
1
- import { Measure } from './Measure';
2
- import { Part } from './Part';
3
- import { Duration, Genre, decomposeDuration } from './types';
1
+ import { Measure } from './Measure.js';
2
+ import { Part } from './Part.js';
3
+ import { Duration, Genre, decomposeDuration } from './types.js';
4
4
  /**
5
5
  * Represents a complete musical score.
6
6
  */
@@ -19,7 +19,8 @@ export class Score {
19
19
  subtitle;
20
20
  genre;
21
21
  tempoText;
22
- constructor(title, composer, timeSignature, keySignature, parts, bpm = 120, tempoDuration = Duration.Quarter, tempoIsDotted = false, copyright = '', lyricist = '', swing = false, subtitle = '', genre = Genre.Unknown, tempoText = '') {
22
+ tempoDotCount;
23
+ constructor(title, composer, timeSignature, keySignature, parts, bpm = 120, tempoDuration = Duration.Quarter, tempoIsDotted = false, copyright = '', lyricist = '', swing = false, subtitle = '', genre = Genre.Unknown, tempoText = '', tempoDotCount = 0) {
23
24
  this.title = title;
24
25
  this.composer = composer;
25
26
  this.timeSignature = timeSignature;
@@ -34,18 +35,22 @@ export class Score {
34
35
  this.subtitle = subtitle;
35
36
  this.genre = genre;
36
37
  this.tempoText = tempoText;
38
+ this.tempoDotCount = tempoDotCount;
37
39
  }
38
40
  withTitle(title) {
39
- return new Score(title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
41
+ return new Score(title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
40
42
  }
41
43
  withComposer(composer) {
42
- return new Score(this.title, composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
44
+ return new Score(this.title, composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
43
45
  }
44
46
  withSubtitle(subtitle) {
45
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, subtitle, this.genre, this.tempoText);
47
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, subtitle, this.genre, this.tempoText, this.tempoDotCount);
46
48
  }
47
49
  withGenre(genre) {
48
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, genre, this.tempoText);
50
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, genre, this.tempoText, this.tempoDotCount);
51
+ }
52
+ withBpm(bpm) {
53
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
49
54
  }
50
55
  getMeasureCount() {
51
56
  return this.parts[0]?.getMeasureCount() ?? 0;
@@ -88,7 +93,7 @@ export class Score {
88
93
  }
89
94
  static fromJSON(data) {
90
95
  const parts = data.parts.map((p) => Part.fromJSON(p));
91
- return new Score(data.title, data.composer, data.timeSignature, data.keySignature, parts, data.bpm ?? 120, data.tempoDuration ?? Duration.Quarter, data.tempoIsDotted ?? false, data.copyright ?? '', data.lyricist ?? '', data.swing ?? false, data.subtitle ?? '', data.genre || Genre.Unknown, data.tempoText ?? '');
96
+ return new Score(data.title, data.composer, data.timeSignature, data.keySignature, parts, data.bpm ?? 120, data.tempoDuration ?? Duration.Quarter, data.tempoIsDotted ?? false, data.copyright ?? '', data.lyricist ?? '', data.swing ?? false, data.subtitle ?? '', data.genre || Genre.Unknown, data.tempoText ?? '', data.tempoDotCount ?? (data.tempoIsDotted ? 1 : 0));
92
97
  }
93
98
  transpose(semitones) {
94
99
  const semitoneToFifths = {
@@ -112,7 +117,7 @@ export class Score {
112
117
  newFifths -= 12;
113
118
  while (newFifths < -7)
114
119
  newFifths += 12;
115
- return new Score(this.title, this.composer, this.timeSignature, { fifths: newFifths }, this.parts.map((p) => p.transpose(semitones)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
120
+ return new Score(this.title, this.composer, this.timeSignature, { fifths: newFifths }, this.parts.map((p) => p.transpose(semitones)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
116
121
  }
117
122
  replaceNote(partIndex, staffIndex, measureIndex, noteIndex, newNote, voiceIndex = 0) {
118
123
  if (partIndex < 0 || partIndex >= this.parts.length)
@@ -122,10 +127,10 @@ export class Score {
122
127
  return this.replaceMeasure(partIndex, staffIndex, measureIndex, updatedMeasure, true);
123
128
  }
124
129
  withKeySignature(keySig) {
125
- return new Score(this.title, this.composer, this.timeSignature, keySig, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
130
+ return new Score(this.title, this.composer, this.timeSignature, keySig, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
126
131
  }
127
132
  withTimeSignature(timeSig) {
128
- return new Score(this.title, this.composer, timeSig, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
133
+ return new Score(this.title, this.composer, timeSig, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
129
134
  }
130
135
  updateMeasureSignatures(measureIndex, signatures, autoBeam = true) {
131
136
  let score = this;
@@ -170,7 +175,7 @@ export class Score {
170
175
  for (let sIdx = 0; sIdx < currentScore.parts[pIdx].staves.length; sIdx++) {
171
176
  const staff = currentScore.parts[pIdx].staves[sIdx];
172
177
  if (staff.measures.length < maxMeasures) {
173
- let updatedMsrs = [...staff.measures];
178
+ const updatedMsrs = [...staff.measures];
174
179
  for (let i = staff.measures.length; i < maxMeasures; i++) {
175
180
  const ts = currentScore.getTimeSignatureAt(i);
176
181
  const targetDur = ts.beats * (4 / ts.beatType);
@@ -201,10 +206,10 @@ export class Score {
201
206
  const staff = this.parts[pIdx].staves[sIdx];
202
207
  const measures = staff.measures;
203
208
  const maxVoices = Math.max(...measures.map((m) => m.voices.length), 1);
204
- let updatedMeasures = [...measures];
209
+ const updatedMeasures = [...measures];
205
210
  for (let vIdx = 0; vIdx < maxVoices; vIdx++) {
206
211
  // 1. Collect notes and merge tied notes into logical streams
207
- let stream = [];
212
+ const stream = [];
208
213
  for (let mIdx = fromMeasureIndex; mIdx < measures.length; mIdx++) {
209
214
  const msr = measures[mIdx];
210
215
  const voice = msr.voices[vIdx] || [];
@@ -295,7 +300,7 @@ export class Score {
295
300
  }
296
301
  const newParts = [...this.parts];
297
302
  newParts[partIndex] = newParts[partIndex].replaceMeasure(staffIndex, measureIndex, measureToUse);
298
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
303
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
299
304
  }
300
305
  deleteNote(partIndex, staffIndex, measureIndex, noteIndex, voiceIndex = 0) {
301
306
  if (partIndex < 0 || partIndex >= this.parts.length)
@@ -311,6 +316,15 @@ export class Score {
311
316
  const updatedMeasure = measure.changeNoteDuration(noteIndex, newDuration, isDotted, voiceIndex);
312
317
  return this.replaceMeasure(partIndex, staffIndex, measureIndex, updatedMeasure, true);
313
318
  }
319
+ moveNoteToVoice(partIndex, staffIndex, measureIndex, noteIndex, fromVoiceIndex, toVoiceIndex) {
320
+ if (partIndex < 0 || partIndex >= this.parts.length)
321
+ return this;
322
+ const measure = this.parts[partIndex].staves[staffIndex].measures[measureIndex];
323
+ if (!measure)
324
+ return this;
325
+ const updatedMeasure = measure.moveNoteToVoice(fromVoiceIndex, noteIndex, toVoiceIndex);
326
+ return this.replaceMeasure(partIndex, staffIndex, measureIndex, updatedMeasure, true);
327
+ }
314
328
  pasteNotes(partIndex, staffIndex, measureIndex, noteIndex, notesToPaste) {
315
329
  if (partIndex < 0 || partIndex >= this.parts.length)
316
330
  return this;
@@ -370,22 +384,22 @@ export class Score {
370
384
  return newScore;
371
385
  }
372
386
  withTempo(bpm, duration, isDotted) {
373
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, duration, isDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
387
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, duration, isDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
374
388
  }
375
389
  withTempoText(text) {
376
390
  return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, text);
377
391
  }
378
392
  withSwing(swing) {
379
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, swing, this.subtitle, this.genre, this.tempoText);
393
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
380
394
  }
381
395
  withLyricist(lyricist) {
382
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
396
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
383
397
  }
384
398
  withCopyright(copyright) {
385
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
399
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
386
400
  }
387
401
  withParts(parts) {
388
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
402
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
389
403
  }
390
404
  toJSON() {
391
405
  return {
@@ -403,6 +417,7 @@ export class Score {
403
417
  subtitle: this.subtitle,
404
418
  genre: this.genre,
405
419
  tempoText: this.tempoText,
420
+ tempoDotCount: this.tempoDotCount,
406
421
  };
407
422
  }
408
423
  replacePart(partIndex, newPart) {
@@ -410,29 +425,29 @@ export class Score {
410
425
  return this;
411
426
  const newParts = [...this.parts];
412
427
  newParts[partIndex] = newPart;
413
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
428
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
414
429
  }
415
430
  addPart(newPart) {
416
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, [...this.parts, newPart], this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
431
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, [...this.parts, newPart], this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
417
432
  }
418
433
  removePart(partIndex) {
419
434
  if (partIndex < 0 || partIndex >= this.parts.length)
420
435
  return this;
421
436
  const newParts = this.parts.filter((_, i) => i !== partIndex);
422
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
437
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
423
438
  }
424
439
  replaceStaff(partIndex, staffIndex, newStaff) {
425
440
  if (partIndex < 0 || partIndex >= this.parts.length)
426
441
  return this;
427
442
  const newParts = [...this.parts];
428
443
  newParts[partIndex] = newParts[partIndex].replaceStaff(staffIndex, newStaff);
429
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
444
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
430
445
  }
431
446
  addMeasure(index, measure) {
432
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.addMeasure(index, measure)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
447
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.addMeasure(index, measure)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
433
448
  }
434
449
  deleteMeasure(index) {
435
- return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.deleteMeasure(index)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
450
+ return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.deleteMeasure(index)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
436
451
  }
437
452
  getPlaybackSequence() {
438
453
  const measures = this.parts[0]?.staves[0]?.measures || [];
@@ -1,7 +1,7 @@
1
- import { Measure, MeasureJSON } from './Measure';
2
- import { Note } from './Note';
3
- import { Pitch } from './Pitch';
4
- import { Clef, Duration } from './types';
1
+ import { Measure, MeasureJSON } from './Measure.js';
2
+ import { Note } from './Note.js';
3
+ import { Pitch } from './Pitch.js';
4
+ import { Clef, Duration } from './types.js';
5
5
  /**
6
6
  * Represents a single staff (one set of 5 lines with a clef).
7
7
  */
@@ -1,5 +1,5 @@
1
- import { Measure } from './Measure';
2
- import { Pitch } from './Pitch';
1
+ import { Measure } from './Measure.js';
2
+ import { Pitch } from './Pitch.js';
3
3
  /**
4
4
  * Represents a single staff (one set of 5 lines with a clef).
5
5
  */
@@ -1,10 +1,10 @@
1
- export * from './types';
2
- export * from './Pitch';
3
- export * from './Note';
4
- export * from './NoteSet';
5
- export * from './Measure';
6
- export * from './Staff';
7
- export * from './Part';
8
- export * from './Score';
9
- export * from './Instrument';
10
- export * from './PreMeasure';
1
+ export * from './types.js';
2
+ export * from './Pitch.js';
3
+ export * from './Note.js';
4
+ export * from './NoteSet.js';
5
+ export * from './Measure.js';
6
+ export * from './Staff.js';
7
+ export * from './Part.js';
8
+ export * from './Score.js';
9
+ export * from './Instrument.js';
10
+ export * from './PreMeasure.js';
@@ -1,10 +1,10 @@
1
- export * from './types';
2
- export * from './Pitch';
3
- export * from './Note';
4
- export * from './NoteSet';
5
- export * from './Measure';
6
- export * from './Staff';
7
- export * from './Part';
8
- export * from './Score';
9
- export * from './Instrument';
10
- export * from './PreMeasure';
1
+ export * from './types.js';
2
+ export * from './Pitch.js';
3
+ export * from './Note.js';
4
+ export * from './NoteSet.js';
5
+ export * from './Measure.js';
6
+ export * from './Staff.js';
7
+ export * from './Part.js';
8
+ export * from './Score.js';
9
+ export * from './Instrument.js';
10
+ export * from './PreMeasure.js';
@@ -10,6 +10,10 @@ export declare enum Duration {
10
10
  TwoHundredFiftySixth = "two-hundred-fifty-sixth"
11
11
  }
12
12
  export declare const DURATION_VALUES: Record<Duration, number>;
13
+ /**
14
+ * Calculates the numeric duration value (quarter = 1) for a given duration and dot count.
15
+ */
16
+ export declare function calculateDurationValue(duration: Duration, dotCount?: number): number;
13
17
  /**
14
18
  * Decomposes a duration value (quarter = 1) into a list of duration/dot pairs.
15
19
  */
@@ -22,6 +22,19 @@ export const DURATION_VALUES = {
22
22
  [Duration.OneHundredTwentyEighth]: 0.03125,
23
23
  [Duration.TwoHundredFiftySixth]: 0.015625,
24
24
  };
25
+ /**
26
+ * Calculates the numeric duration value (quarter = 1) for a given duration and dot count.
27
+ */
28
+ export function calculateDurationValue(duration, dotCount = 0) {
29
+ const base = DURATION_VALUES[duration];
30
+ let multiplier = 1;
31
+ let dotValue = 0.5;
32
+ for (let i = 0; i < dotCount; i++) {
33
+ multiplier += dotValue;
34
+ dotValue /= 2;
35
+ }
36
+ return base * multiplier;
37
+ }
25
38
  /**
26
39
  * Decomposes a duration value (quarter = 1) into a list of duration/dot pairs.
27
40
  */
@@ -3,6 +3,7 @@ export interface Tempo {
3
3
  bpm: number;
4
4
  duration: Duration;
5
5
  isDotted: boolean;
6
+ dotCount?: number;
6
7
  text?: string;
7
8
  gradual?: 'ritardando' | 'accelerando';
8
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scorelabs/core",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Core logic and models for ScoreLabs music notation",
5
5
  "type": "module",
6
6
  "files": [