Skip to content

Commit 54ba908

Browse files
authored
Merge pull request #1513 from 0xfe/timesig_simplifications
Time Signature simplifications
2 parents 0c67d27 + 8066271 commit 54ba908

File tree

8 files changed

+139
-27
lines changed

8 files changed

+139
-27
lines changed

src/fonts/bravura_metrics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ export const BravuraMetrics = {
242242

243243
// These are for numeric digits, such as in time signatures
244244
digits: {
245-
// used by timesig
245+
// used by TimeSignature object
246246
shiftLine: -1,
247247
point: 34,
248248

src/fonts/leland_metrics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export const LelandMetrics = {
237237

238238
// These are for numeric digits, such as in time signatures
239239
digits: {
240-
// used by timesig
240+
// used by TimeSignature objects
241241
shiftLine: -1,
242242
point: 34,
243243

src/note.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ export abstract class Note extends Tickable {
488488

489489
/** Accessor to isDotted. */
490490
isDotted(): boolean {
491-
return this.getModifiersByType('Dot').length > 0;
491+
return this.getModifiersByType(Category.Dot).length > 0;
492492
}
493493

494494
/** Accessor to hasStem. */

src/stavemodifier.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ export class StaveModifier extends Element {
9090
return this;
9191
}
9292

93+
/**
94+
* Runs setYShift() for the Glyph object so that it matches the position of line for
95+
* the Stave provided. A `customShift` can also be given (measured in the same units
96+
* as `setYShift` not in lines) and this will be added after all other positions are
97+
* calculated from the Stave.
98+
*
99+
* Note that this routine only sets the yShift; it does not actually "place" (meaning
100+
* draw) the Glyph on the Stave. Call .draw() afterwards to do that.
101+
*/
93102
placeGlyphOnLine(glyph: Glyph, stave: Stave, line?: number, customShift = 0): void {
94103
glyph.setYShift(stave.getYForLine(line ?? 0) - stave.getYForGlyphs() + customShift);
95104
}

src/timesigglyph.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class TimeSignatureGlyph extends Glyph {
104104
y = stave.getYForLine(this.timeSignature.bottomLine);
105105
for (let i = 0; i < this.botGlyphs.length; ++i) {
106106
const glyph = this.botGlyphs[i];
107-
this.timeSignature.placeGlyphOnLine(glyph, stave, 0);
107+
this.timeSignature.placeGlyphOnLine(glyph, stave, this.timeSignature.getLine());
108108
Glyph.renderOutline(ctx, glyph.getMetrics().outline, this.scale, start_x + glyph.getMetrics().x_shift, y);
109109
start_x += defined(glyph.getMetrics().width);
110110
}

src/timesignature.ts

Lines changed: 107 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { defined, RuntimeError } from './util';
1414

1515
export interface TimeSignatureInfo {
1616
glyph: Glyph;
17-
line?: number;
17+
line: number;
1818
num: boolean;
1919
}
2020

@@ -36,6 +36,11 @@ const assertIsValidTimeSig = (timeSpec: string) => {
3636
});
3737
};
3838

39+
/**
40+
* A TimeSignature is a StaveModifier that can make its appropriate Glyphs directly from
41+
* a provided "timeSpec" such as "4/4", "C|" (cut time), or even something more advanced
42+
* such as "3/4(6/8)" or "2/4+5/8".
43+
*/
3944
export class TimeSignature extends StaveModifier {
4045
static get CATEGORY(): string {
4146
return Category.TimeSignature;
@@ -57,10 +62,13 @@ export class TimeSignature extends StaveModifier {
5762
}
5863

5964
point: number;
60-
bottomLine: number;
61-
topLine: number;
65+
bottomLine: number; // bottomLine and topLine are used to calculate the position of the
66+
topLine: number; // top row of digits in a numeric TimeSignature.
6267

63-
protected info: TimeSignatureInfo;
68+
protected timeSpec: string = '4/4';
69+
protected line: number = 0;
70+
protected glyph!: Glyph;
71+
protected is_numeric: boolean = true;
6472
protected validate_args: boolean;
6573

6674
constructor(timeSpec: string = '4/4', customPadding = 15, validate_args = true) {
@@ -69,17 +77,23 @@ export class TimeSignature extends StaveModifier {
6977

7078
const padding = customPadding;
7179

80+
// point must be defined before parsing spec.
7281
const musicFont = Tables.currentMusicFont();
7382
this.point = musicFont.lookupMetric('digits.point');
83+
7484
const fontLineShift = musicFont.lookupMetric('digits.shiftLine', 0);
7585
this.topLine = 2 + fontLineShift;
7686
this.bottomLine = 4 + fontLineShift;
7787
this.setPosition(StaveModifierPosition.BEGIN);
78-
this.info = this.parseTimeSpec(timeSpec);
79-
this.setWidth(defined(this.info.glyph.getMetrics().width));
88+
this.setTimeSig(timeSpec);
8089
this.setPadding(padding);
8190
}
8291

92+
/**
93+
* Return TimeSignatureInfo given a string, consisting of line (number),
94+
* num (boolean: same as TimeSignature.getIsNumeric()), and glyph (a Glyph or
95+
* TimeSignatureGlyph object).
96+
*/
8397
parseTimeSpec(timeSpec: string): TimeSignatureInfo {
8498
if (timeSpec === 'C' || timeSpec === 'C|') {
8599
const { line, code, point } = TimeSignature.glyphs[timeSpec];
@@ -97,35 +111,115 @@ export class TimeSignature extends StaveModifier {
97111
const parts = timeSpec.split('/');
98112

99113
return {
114+
line: 0,
100115
num: true,
101116
glyph: this.makeTimeSignatureGlyph(parts[0] ?? '', parts[1] ?? ''),
102117
};
103118
}
104119

105-
makeTimeSignatureGlyph(topDigits: string, botDigits: string): Glyph {
120+
/**
121+
* Returns a new TimeSignatureGlyph (a Glyph subclass that knows how to draw both
122+
* top and bottom digits along with plus signs etc.)
123+
*/
124+
makeTimeSignatureGlyph(topDigits: string, botDigits: string): TimeSignatureGlyph {
125+
// note that 'code' is ignored by TimeSignatureGlyph when rendering.
106126
return new TimeSignatureGlyph(this, topDigits, botDigits, 'timeSig0', this.point);
107127
}
108128

129+
/**
130+
* Returns {line, num (=getIsNumeric), glyph} --
131+
* but these can also be accessed directly w/ getters and setters.
132+
*/
109133
getInfo(): TimeSignatureInfo {
110-
return this.info;
134+
const { line, is_numeric, glyph } = this;
135+
return { line, num: is_numeric, glyph };
111136
}
112137

138+
/**
139+
* Set a new time signature specification without changing customPadding, etc.
140+
*
141+
* The getter for this is `getTimeSpec` not `getTimeSig`.
142+
*/
113143
setTimeSig(timeSpec: string): this {
114-
this.info = this.parseTimeSpec(timeSpec);
144+
this.timeSpec = timeSpec;
145+
const info = this.parseTimeSpec(timeSpec);
146+
this.setGlyph(info.glyph);
147+
this.is_numeric = info.num;
148+
this.line = info.line;
115149
return this;
116150
}
117151

152+
/**
153+
* Return the timeSpec (such as '4/4' or 'C|' or even '2/4+3/8') of the TimeSignature
154+
*/
155+
getTimeSpec(): string {
156+
return this.timeSpec;
157+
}
158+
159+
/**
160+
* Return the staff line that the TimeSignature sits on. Generally 0 for numerator/
161+
* denominator time signatures such as 3/4 and 2 for cut/common.
162+
*/
163+
getLine(): number {
164+
return this.line;
165+
}
166+
167+
/**
168+
* Set the line number that the TimeSignature sits on. Half-values are acceptable
169+
* for spaces, etc. Can be altered, for instance, for signatures that sit above the
170+
* staff in large orchestral scores.
171+
*/
172+
setLine(line: number) {
173+
this.line = line;
174+
}
175+
176+
/**
177+
* Get the Glyph object used to create the time signature. Numeric time signatures
178+
* such as 3/8 have a composite Glyph stored as a single Glyph object.
179+
*/
180+
getGlyph(): Glyph {
181+
return this.glyph;
182+
}
183+
184+
/**
185+
* Set the Glyph object used to draw the time signature, and update the width of the
186+
* TimeSignature to match. The Glyph must define width in its metrics.
187+
*/
188+
setGlyph(glyph: Glyph) {
189+
this.glyph = glyph;
190+
this.setWidth(defined(this.glyph.getMetrics().width));
191+
}
192+
193+
/**
194+
* Return a boolean on whether this TimeSignature is drawn with one or more numbers
195+
* (such as 4/4) or not (as in cut time).
196+
*/
197+
getIsNumeric(): boolean {
198+
return this.is_numeric;
199+
}
200+
201+
/**
202+
* Set whether this TimeSignature is drawn with one or more numbers.
203+
*/
204+
setIsNumeric(isNumeric: boolean) {
205+
this.is_numeric = isNumeric;
206+
}
207+
208+
/**
209+
* Draw the time signature on a Stave using its RenderContext. Both setStave
210+
* and setContext must already be run.
211+
*/
118212
draw(): void {
119213
const stave = this.checkStave();
120214
const ctx = stave.checkContext();
121215
this.setRendered();
122216

123217
this.applyStyle(ctx);
124218
ctx.openGroup('timesignature', this.getAttribute('id'));
125-
this.info.glyph.setStave(stave);
126-
this.info.glyph.setContext(ctx);
127-
this.placeGlyphOnLine(this.info.glyph, stave, this.info.line);
128-
this.info.glyph.renderToStave(this.x);
219+
this.glyph.setStave(stave);
220+
this.glyph.setContext(ctx);
221+
this.placeGlyphOnLine(this.glyph, stave, this.line);
222+
this.glyph.renderToStave(this.x);
129223
ctx.closeGroup();
130224
this.restoreStyle(ctx);
131225
}

src/timesignote.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,21 @@
33

44
import { ModifierContext } from './modifiercontext';
55
import { Note } from './note';
6-
import { TimeSignature, TimeSignatureInfo } from './timesignature';
6+
import { TimeSignature } from './timesignature';
77
import { Category } from './typeguard';
88

99
export class TimeSigNote extends Note {
1010
static get CATEGORY(): string {
1111
return Category.TimeSigNote;
1212
}
1313

14-
protected timeSigInfo: TimeSignatureInfo;
14+
protected timeSig: TimeSignature;
1515

1616
constructor(timeSpec: string, customPadding?: number) {
1717
super({ duration: 'b' });
1818

19-
const timeSignature = new TimeSignature(timeSpec, customPadding);
20-
this.timeSigInfo = timeSignature.getInfo();
21-
this.setWidth(this.timeSigInfo.glyph.getMetrics().width);
19+
this.timeSig = new TimeSignature(timeSpec, customPadding);
20+
this.setWidth(this.timeSig.getGlyph().getMetrics().width);
2221

2322
// Note properties
2423
this.ignore_ticks = true;
@@ -41,12 +40,13 @@ export class TimeSigNote extends Note {
4140
const ctx = this.checkContext();
4241
this.setRendered();
4342

44-
if (!this.timeSigInfo.glyph.getContext()) {
45-
this.timeSigInfo.glyph.setContext(ctx);
43+
const tsGlyph = this.timeSig.getGlyph();
44+
if (!tsGlyph.getContext()) {
45+
tsGlyph.setContext(ctx);
4646
}
4747

48-
this.timeSigInfo.glyph.setStave(stave);
49-
this.timeSigInfo.glyph.setYShift(stave.getYForLine(2) - stave.getYForGlyphs());
50-
this.timeSigInfo.glyph.renderToStave(this.getAbsoluteX());
48+
tsGlyph.setStave(stave);
49+
tsGlyph.setYShift(stave.getYForLine(2) - stave.getYForGlyphs());
50+
tsGlyph.renderToStave(this.getAbsoluteX());
5151
}
5252
}

tests/timesignature_tests.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const TimeSignatureTests = {
2929

3030
function parser(): void {
3131
const timeSig = new TimeSignature();
32+
equal(timeSig.getTimeSpec(), '4/4', 'default time signature is 4/4');
3233

3334
const mustFail = ['asdf', '123/', '/10', '/', '4567', 'C+', '1+', '+1', '(3+', '+3)', '()', '(+)'];
3435
mustFail.forEach((invalidString) => {
@@ -38,6 +39,14 @@ function parser(): void {
3839
const mustPass = ['4/4', '10/12', '1/8', '1234567890/1234567890', 'C', 'C|', '+'];
3940
mustPass.forEach((validString) => timeSig.parseTimeSpec(validString));
4041

42+
timeSig.setTimeSig('4/4');
43+
equal(timeSig.getIsNumeric(), true, '4/4 is numeric');
44+
equal(timeSig.getLine(), 0, 'digits are on line 0');
45+
timeSig.setTimeSig('C|');
46+
equal(timeSig.getTimeSpec(), 'C|', 'timeSpec changed to C|');
47+
equal(timeSig.getIsNumeric(), false, 'cut time is not numeric');
48+
equal(timeSig.getLine(), 2, 'cut/common are on line 2');
49+
4150
ok(true, 'all pass');
4251
}
4352

0 commit comments

Comments
 (0)