abcjs 6.5.2 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abcjs",
3
- "version": "6.5.2",
3
+ "version": "6.6.0",
4
4
  "description": "Renderer for abc music notation",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -12,6 +12,7 @@ var TimingCallbacks = function(target, params) {
12
12
  self.lineEndCallback = params.lineEndCallback; // This is called when the end of a line is approaching.
13
13
  self.lineEndAnticipation = params.lineEndAnticipation ? parseInt(params.lineEndAnticipation, 10) : 0; // How many milliseconds before the end should the call happen.
14
14
  self.beatSubdivisions = params.beatSubdivisions ? parseInt(params.beatSubdivisions, 10) : 1; // how many callbacks per beat is desired.
15
+ if (!self.beatSubdivisions) self.beatSubdivisions = 1
15
16
  self.joggerTimer = null;
16
17
 
17
18
  self.replaceTarget = function(newTarget) {
@@ -38,7 +39,73 @@ var TimingCallbacks = function(target, params) {
38
39
  // noteTimings contains an array of events sorted by time. Events that happen at the same time are in the same element of the array.
39
40
  self.millisecondsPerBeat = 1000 / (self.qpm / 60) / self.beatSubdivisions;
40
41
  self.lastMoment = self.noteTimings[self.noteTimings.length-1].milliseconds;
41
- self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat);
42
+
43
+ // For irregular time sigs that specify the beat divisions (that is, something like `M: 2+3/8`)
44
+ // Then the beat is not a regular pulse. To keep most of this logic easy, the beat will be specified
45
+ // as half as long, but the callback will happen according to the pattern. That is,
46
+ // for bpm=60 at the above time signature, internally the beat callback will happen at 120 bpm, but
47
+ // the callback function will be called at 2 sub beats, then 3 sub beats, etc.
48
+ // The beat number will be an integer for all of them and count up by one each time.
49
+ var meter = newTarget.getMeter()
50
+ var irregularMeter = ''
51
+ if (meter && meter.type === "specified" && meter.value && meter.value.length > 0 && meter.value[0].num.indexOf('+') > 0)
52
+ irregularMeter = meter.value[0].num
53
+ // for subdivisions = 1, then this should contain only whole numbers and the callbacks are irregular
54
+ // for subdivisions = 2, then this should be 1/8 notes. The beats should be something like: 0, 0.5, 1, 1.33, 1.66 2 (For M:2+3/8)
55
+ // etc for more subdivisions - they are just multiplied
56
+ self.beatStarts = []
57
+ if (irregularMeter) {
58
+ var measureLength = self.noteTimings[self.noteTimings.length-1].millisecondsPerMeasure
59
+ var numMeasures = self.lastMoment / measureLength
60
+ var parts = irregularMeter.split("+")
61
+ for (var i = 0; i < parts.length; i++)
62
+ parts[i] = parseInt(parts[i],10) / 2 // since we count a beat as a quarter note, but these numbers refer to 1/8 notes, we convert the beat length
63
+ var currentTs = 0
64
+ var beatNumber = 0
65
+ // For the input: parts = [ 1, 1.5 ] and beatSubdivisions = 2
66
+ // beatNumbers = 0 0.5 1 1.33 1.67 2 2.5 3 3.33 3.67 ...
67
+ // when part=1 then there is 1 extra sub beat and it is 0.5
68
+ // when part=1.5 then there are 2 extra sub beats at 0.33 and 0.67
69
+ //
70
+ // beatSubdivision | numFor1 | numFor1.5
71
+ // 2 | 2 | 3
72
+ // 3 | 3 | 4.5
73
+ // 4 | 4 | 6
74
+ for (var measureNumber = 0; measureNumber < numMeasures; measureNumber++) {
75
+ var measureStartTs = measureNumber * measureLength
76
+ var subBeatCounter = 0
77
+ for (var kk = 0; kk < parts.length; kk++) {
78
+ var beatLength = parts[kk] // This is either 1 or 1.5 (how many quarter notes in this beat)
79
+ if (self.beatSubdivisions === 1) {
80
+ if (self.beatSubdivisions === 1)
81
+ if (currentTs < self.lastMoment) {
82
+ self.beatStarts.push({b: beatNumber, ts: currentTs})
83
+ }
84
+ currentTs += beatLength * self.millisecondsPerBeat
85
+ } else {
86
+ var numDivisions = beatLength * self.beatSubdivisions
87
+ for (var k = 0; k < Math.floor(numDivisions); k++) {
88
+ var subBeat = k / numDivisions
89
+ var ts = Math.round(measureStartTs + subBeatCounter * self.millisecondsPerBeat)
90
+ if (ts < self.lastMoment) {
91
+ self.beatStarts.push({b: beatNumber + subBeat, ts: ts})
92
+ }
93
+ subBeatCounter++
94
+ }
95
+ }
96
+ beatNumber++
97
+ }
98
+ }
99
+ self.beatStarts.push({b: numMeasures * parts.length, ts: self.lastMoment})
100
+ self.totalBeats = self.beatStarts.length
101
+ } else {
102
+ self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat);
103
+ // Add one so the last beat is the last moment
104
+ for (var j = 0; j < self.totalBeats+1; j++) {
105
+ self.beatStarts.push({b: j/self.beatSubdivisions, ts: Math.round(j * self.millisecondsPerBeat)})
106
+ }
107
+ }
108
+ //console.log({lastMoment: self.lastMoment, beatStarts: self.beatStarts})
42
109
  };
43
110
 
44
111
  self.replaceTarget(target);
@@ -49,11 +116,11 @@ var TimingCallbacks = function(target, params) {
49
116
  if (self.lastTimestamp === timestamp)
50
117
  return; // If there are multiple seeks or other calls, then we can easily get multiple callbacks for the same instant.
51
118
  self.lastTimestamp = timestamp;
52
- if (!self.startTime) {
53
- self.startTime = timestamp;
54
- }
55
119
 
56
120
  if (!self.isPaused && self.isRunning) {
121
+ if (!self.startTime) {
122
+ self.startTime = timestamp;
123
+ }
57
124
  self.currentTime = timestamp - self.startTime;
58
125
  self.currentTime += 16; // Add a little slop because this function isn't called exactly.
59
126
  while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
@@ -73,8 +140,9 @@ var TimingCallbacks = function(target, params) {
73
140
  }
74
141
  if (self.currentTime < self.lastMoment) {
75
142
  requestAnimationFrame(self.doTiming);
76
- if (self.currentBeat * self.millisecondsPerBeat < self.currentTime) {
143
+ if (self.currentBeat < self.beatStarts.length && self.beatStarts[self.currentBeat].ts <= self.currentTime) {
77
144
  var ret = self.doBeatCallback(timestamp);
145
+ self.currentBeat++
78
146
  if (ret !== null)
79
147
  self.currentTime = ret;
80
148
  }
@@ -82,6 +150,7 @@ var TimingCallbacks = function(target, params) {
82
150
  // Because of timing issues (for instance, if the browser tab isn't active), the beat callbacks might not have happened when they are supposed to. To keep the client programs from having to deal with that, this will keep calling the loop until all of them have been sent.
83
151
  if (self.beatCallback) {
84
152
  var ret2 = self.doBeatCallback(timestamp);
153
+ self.currentBeat++
85
154
  if (ret2 !== null)
86
155
  self.currentTime = ret2;
87
156
  requestAnimationFrame(self.doTiming);
@@ -178,15 +247,15 @@ var TimingCallbacks = function(target, params) {
178
247
 
179
248
  var thisStartTime = self.startTime; // the beat callback can call seek and change the position from beneath us.
180
249
  self.beatCallback(
181
- self.currentBeat / self.beatSubdivisions,
250
+ self.beatStarts[self.currentBeat].b,
182
251
  self.totalBeats / self.beatSubdivisions,
183
252
  self.lastMoment,
184
253
  position,
185
254
  debugInfo);
186
255
  if (thisStartTime !== self.startTime) {
187
256
  return timestamp - self.startTime;
188
- } else
189
- self.currentBeat++;
257
+ } // else
258
+ // self.currentBeat++;
190
259
  }
191
260
  return null;
192
261
  };
@@ -285,7 +354,6 @@ var TimingCallbacks = function(target, params) {
285
354
  var now = performance.now();
286
355
  self.startTime = now - self.currentTime;
287
356
 
288
- var oldEvent = self.currentEvent;
289
357
  self.currentEvent = 0;
290
358
  while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) {
291
359
  self.currentEvent++;
@@ -298,10 +366,18 @@ var TimingCallbacks = function(target, params) {
298
366
  }
299
367
  }
300
368
 
369
+ //console.log({jump:self.currentTime})
301
370
  var oldBeat = self.currentBeat;
302
- self.currentBeat = Math.floor(self.currentTime / self.millisecondsPerBeat);
303
- if (self.beatCallback && oldBeat !== self.currentBeat) // If the movement caused the beat to change, then immediately report it to the client.
304
- self.doBeatCallback(self.startTime+self.currentTime);
371
+ for (self.currentBeat = 0; self.currentBeat < self.beatStarts.length; self.currentBeat++) {
372
+ if (self.beatStarts[self.currentBeat].ts > self.currentTime)
373
+ break
374
+ }
375
+ self.currentBeat--
376
+ if (self.beatCallback && oldBeat !== self.currentBeat) {
377
+ // If the movement caused the beat to change, then immediately report it to the client.
378
+ self.doBeatCallback(self.startTime + self.currentTime);
379
+ self.currentBeat++
380
+ }
305
381
 
306
382
  if (self.eventCallback && self.currentEvent >= 0 && self.noteTimings[self.currentEvent].type === 'event')
307
383
  self.eventCallback(self.noteTimings[self.currentEvent]);
@@ -328,4 +404,3 @@ function getLineEndTimings(timings, anticipation) {
328
404
  }
329
405
 
330
406
  module.exports = TimingCallbacks;
331
-
@@ -1,21 +1,26 @@
1
1
  // All these keys have the same number of accidentals
2
2
  var keys = {
3
- 'C': { modes: ['CMaj', 'Amin', 'Am', 'GMix', 'DDor', 'EPhr', 'FLyd', 'BLoc'], stepsFromC: 0 },
4
- 'Db': { modes: ['DbMaj', 'Bbmin', 'Bbm', 'AbMix', 'EbDor', 'FPhr', 'GbLyd', 'CLoc'], stepsFromC: 1 },
5
- 'D': { modes: ['DMaj', 'Bmin', 'Bm', 'AMix', 'EDor', 'F#Phr', 'GLyd', 'C#Loc'], stepsFromC: 2 },
6
- 'Eb': { modes: ['EbMaj', 'Cmin', 'Cm', 'BbMix', 'FDor', 'GPhr', 'AbLyd', 'DLoc'], stepsFromC: 3 },
7
- 'E': { modes: ['EMaj', 'C#min', 'C#m', 'BMix', 'F#Dor', 'G#Phr', 'ALyd', 'D#Loc'], stepsFromC: 4 },
8
- 'F': { modes: ['FMaj', 'Dmin', 'Dm', 'CMix', 'GDor', 'APhr', 'BbLyd', 'ELoc'], stepsFromC: 5 },
9
- 'Gb': { modes: ['GbMaj', 'Ebmin', 'Ebm', 'DbMix', 'AbDor', 'BbPhr', 'CbLyd', 'FLoc'], stepsFromC: 6 },
10
- 'G': { modes: ['GMaj', 'Emin', 'Em', 'DMix', 'ADor', 'BPhr', 'CLyd', 'F#Loc'], stepsFromC: 7 },
11
- 'Ab': { modes: ['AbMaj', 'Fmin', 'Fm', 'EbMix', 'BbDor', 'CPhr', 'DbLyd', 'GLoc'], stepsFromC: 8 },
12
- 'A': { modes: ['AMaj', 'F#min', 'F#m', 'EMix', 'BDor', 'C#Phr', 'DLyd', 'G#Loc'], stepsFromC: 9 },
13
- 'Bb': { modes: ['BbMaj', 'Gmin', 'Gm', 'FMix', 'CDor', 'DPhr', 'EbLyd', 'ALoc'], stepsFromC: 10 },
14
- 'B': { modes: ['BMaj', 'G#min', 'G#m', 'F#Mix', 'C#Dor', 'D#Phr', 'ELyd', 'A#Loc'], stepsFromC: 11 },
3
+ 'C': { modes: ['CMaj', 'CIon', 'Amin', 'AAeo', 'Am', 'GMix', 'DDor', 'EPhr', 'FLyd', 'BLoc'], stepsFromC: 0 },
4
+ 'Db': { modes: ['DbMaj', 'DbIon', 'Bbmin', 'BbAeo', 'Bbm', 'AbMix', 'EbDor', 'FPhr', 'GbLyd', 'CLoc'], stepsFromC: 1 },
5
+ 'D': { modes: ['DMaj', 'DIon', 'Bmin', 'BAeo', 'Bm', 'AMix', 'EDor', 'F#Phr', 'GLyd', 'C#Loc'], stepsFromC: 2 },
6
+ 'Eb': { modes: ['EbMaj', 'EbIon', 'Cmin', 'CAeo', 'Cm', 'BbMix', 'FDor', 'GPhr', 'AbLyd', 'DLoc'], stepsFromC: 3 },
7
+ 'E': { modes: ['EMaj', 'EIon', 'C#min', 'C#Aeo', 'C#m', 'BMix', 'F#Dor', 'G#Phr', 'ALyd', 'D#Loc'], stepsFromC: 4 },
8
+ 'F': { modes: ['FMaj', 'FIon', 'Dmin', 'DAeo', 'Dm', 'CMix', 'GDor', 'APhr', 'BbLyd', 'ELoc'], stepsFromC: 5 },
9
+ 'Gb': { modes: ['GbMaj', 'GbIon', 'Ebmin', 'EbAeo', 'Ebm', 'DbMix', 'AbDor', 'BbPhr', 'CbLyd', 'FLoc'], stepsFromC: 6 },
10
+ 'G': { modes: ['GMaj', 'GIon', 'Emin', 'EAeo', 'Em', 'DMix', 'ADor', 'BPhr', 'CLyd', 'F#Loc'], stepsFromC: 7 },
11
+ 'Ab': { modes: ['AbMaj', 'AbIon', 'Fmin', 'FAeo', 'Fm', 'EbMix', 'BbDor', 'CPhr', 'DbLyd', 'GLoc'], stepsFromC: 8 },
12
+ 'A': { modes: ['AMaj', 'AIon', 'F#min', 'F#Aeo', 'F#m', 'EMix', 'BDor', 'C#Phr', 'DLyd', 'G#Loc'], stepsFromC: 9 },
13
+ 'Bb': { modes: ['BbMaj', 'BbIon', 'Gmin', 'GAeo', 'Gm', 'FMix', 'CDor', 'DPhr', 'EbLyd', 'ALoc'], stepsFromC: 10 },
14
+ 'B': { modes: ['BMaj', 'BIon', 'G#min', 'G#Aeo', 'G#m', 'F#Mix', 'C#Dor', 'D#Phr', 'ELyd', 'A#Loc'], stepsFromC: 11 },
15
15
  // Enharmonic keys
16
- 'C#': { modes: ['C#Maj', 'A#min', 'A#m', 'G#Mix', 'D#Dor', 'E#Phr', 'F#Lyd', 'B#Loc'], stepsFromC: 1 },
17
- 'F#': { modes: ['F#Maj', 'D#min', 'D#m', 'C#Mix', 'G#Dor', 'A#Phr', 'BLyd', 'E#Loc'], stepsFromC: 6 },
18
- 'Cb': { modes: ['CbMaj', 'Abmin', 'Abm', 'GbMix', 'DbDor', 'EbPhr', 'FbLyd', 'BbLoc'], stepsFromC: 11 },
16
+ 'C#': { modes: ['C#Maj', 'C#Ion', 'A#min', 'A#Aeo', 'A#m', 'G#Mix', 'D#Dor', 'E#Phr', 'F#Lyd', 'B#Loc'], stepsFromC: 1 },
17
+ 'F#': { modes: ['F#Maj', 'F#Ion', 'D#min', 'D#Aeo', 'D#m', 'C#Mix', 'G#Dor', 'A#Phr', 'BLyd', 'E#Loc'], stepsFromC: 6 },
18
+ 'Cb': { modes: ['CbMaj', 'CbIon', 'Abmin', 'AbAeo', 'Abm', 'GbMix', 'DbDor', 'EbPhr', 'FbLyd', 'BbLoc'], stepsFromC: 11 },
19
+ }
20
+
21
+ var modeNames = ['maj', 'ion', 'min', 'aeo', 'm', 'mix', 'dor', 'phr', 'lyd', 'loc']
22
+ function isLegalMode(mode) {
23
+ return modeNames.indexOf(mode.toLowerCase()) >= 0
19
24
  }
20
25
 
21
26
  var keyReverse = null
@@ -42,7 +47,7 @@ function relativeMajor(key) {
42
47
  createKeyReverse()
43
48
  }
44
49
  // get the key portion itself - there might be other stuff, like extra sharps and flats, or the mode written out.
45
- var mode = key.toLowerCase().match(/([a-g][b#]?)(maj|min|mix|dor|phr|lyd|loc|m)?/)
50
+ var mode = key.toLowerCase().match(/([a-g][b#]?)(maj|ion|min|aeo|mix|dor|phr|lyd|loc|m)?/);
46
51
  if (!mode || !mode[2])
47
52
  return key;
48
53
  mode = mode[1] + mode[2]
@@ -60,10 +65,10 @@ function relativeMode(majorKey, mode) {
60
65
  return majorKey;
61
66
  if (mode === '')
62
67
  return majorKey;
63
- var match = mode.toLowerCase().match(/^(maj|min|mix|dor|phr|lyd|loc|m)/)
68
+ var match = mode.toLowerCase().match(/^(maj|ion|min|aeo|mix|dor|phr|lyd|loc|m)/);
64
69
  if (!match)
65
70
  return majorKey
66
- var regMode = match[1]
71
+ var regMode = match[1]
67
72
  for (var i = 0; i < group.modes.length; i++) {
68
73
  var thisMode = group.modes[i]
69
74
  var ind = thisMode.toLowerCase().indexOf(regMode)
@@ -89,4 +94,4 @@ function transposeKey(key, steps) {
89
94
  return key;
90
95
  }
91
96
 
92
- module.exports = {relativeMajor: relativeMajor, relativeMode: relativeMode, transposeKey: transposeKey};
97
+ module.exports = {relativeMajor: relativeMajor, relativeMode: relativeMode, transposeKey: transposeKey, isLegalMode:isLegalMode};
@@ -90,12 +90,17 @@ var Tune = function() {
90
90
  this.getBeatLength = function() {
91
91
  // This returns a fraction: for instance 1/4 for a quarter
92
92
  // There are two types of meters: compound and regular. Compound meter has 3 beats counted as one.
93
+
94
+ // Irregular meters have the beat as an 1/8 note but the tempo as a 1/4.
95
+ // That keeps it a similar tempo to 4/4 but that may or may not be generally intuitive, so that might need to change.
93
96
  var meter = this.getMeterFraction();
94
97
  var multiplier = 1;
95
98
  if (meter.num === 6 || meter.num === 9 || meter.num === 12)
96
99
  multiplier = 3;
97
100
  else if (meter.num === 3 && meter.den === 8)
98
101
  multiplier = 3;
102
+ else if (meter.den === 8 && (meter.num === 5 || meter.num === 7))
103
+ multiplier = 2
99
104
 
100
105
  return multiplier / meter.den;
101
106
  };
@@ -194,7 +199,13 @@ var Tune = function() {
194
199
  var den = 4;
195
200
  if (meter) {
196
201
  if (meter.type === 'specified') {
197
- num = parseInt(meter.value[0].num, 10);
202
+ if (meter.value && meter.value.length > 0 && meter.value[0].num.indexOf('+') > 0) {
203
+ var parts = meter.value[0].num.split('+')
204
+ num = 0;
205
+ for (var i = 0; i < parts.length; i++)
206
+ num += parseInt(parts[i],10)
207
+ } else
208
+ num = parseInt(meter.value[0].num, 10);
198
209
  den = parseInt(meter.value[0].den,10);
199
210
  } else if (meter.type === 'cut_time') {
200
211
  num = 2;
@@ -11,7 +11,7 @@ function delineTune(inputLines, options) {
11
11
  var currentTripletFont = [];
12
12
  var currentAnnotationFont = [];
13
13
  for (var i = 0; i < inputLines.length; i++) {
14
- var inputLine = inputLines[i];
14
+ var inputLine = cloneLine(inputLines[i]);
15
15
  if (inputLine.staff) {
16
16
  if (inMusicLine && !inputLine.vskip) {
17
17
  var outputLine = outputLines[outputLines.length-1];
@@ -23,8 +23,8 @@ var create;
23
23
 
24
24
  var beatsPerSecond = tempo / 60;
25
25
 
26
- // Fix tempo for */8 meters
27
- if (time.den == 8){
26
+ // Fix tempo for compound meters
27
+ if (time.den === 8 && time.num !== 5 && time.num !== 7){
28
28
 
29
29
  // Compute the tempo based on the actual milliseconds per measure, scaled by the number of eight notes and halved to get tempo in bpm.
30
30
  var msPerMeasure = abcTune.millisecondsPerMeasure();
@@ -6,6 +6,7 @@ var ParseHeader = require('./abc_parse_header');
6
6
  var ParseMusic = require('./abc_parse_music');
7
7
  var Tokenizer = require('./abc_tokenizer');
8
8
  var wrap = require('./wrap_lines');
9
+ var chordGrid = require('./chord-grid');
9
10
 
10
11
  var Tune = require('../data/abc_tune');
11
12
  var TuneBuilder = require('../parse/tune-builder');
@@ -53,6 +54,8 @@ var Parse = function() {
53
54
  t.lineBreaks = tune.lineBreaks;
54
55
  if (tune.visualTranspose)
55
56
  t.visualTranspose = tune.visualTranspose;
57
+ if (tune.chordGrid)
58
+ t.chordGrid = tune.chordGrid
56
59
  return t;
57
60
  };
58
61
 
@@ -593,6 +596,23 @@ var Parse = function() {
593
596
  }
594
597
 
595
598
  wrap.wrapLines(tune, multilineVars.lineBreaks, multilineVars.barNumbers);
599
+ if (switches.chordGrid) {
600
+ try {
601
+ tune.chordGrid = chordGrid(tune)
602
+ } catch(err) {
603
+ switch (err.message) {
604
+ case "notCommonTime":
605
+ warn("Chord grid only works for 2/2 and 4/4 time.", 0,0)
606
+ break;
607
+ case "noChords":
608
+ warn("No chords are found in the tune.", 0,0)
609
+ break;
610
+ default:
611
+ warn(err.message, 0,0)
612
+ }
613
+
614
+ }
615
+ }
596
616
  };
597
617
  };
598
618