-
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add looping support in midi-player; Add baiao demo
- Loading branch information
1 parent
cc88fed
commit 5e4350c
Showing
12 changed files
with
1,556 additions
and
367 deletions.
There are no files selected for viewing
Binary file not shown.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{"measure":0,"timestamp":0,"duration":1224},{"measure":1,"timestamp":1224.488,"duration":1224},{"measure":2,"timestamp":2448.976,"duration":1224},{"measure":3,"timestamp":3673.464,"duration":1224}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
/*! | ||
* musicxml-player v0.17.2 | ||
* musicxml-player v0.18.0 | ||
* (c) Karim Ratib <[email protected]> (https://github.com/infojunkie) | ||
* Released under the GPL-3.0-only License. | ||
*/ | ||
|
@@ -696,6 +696,7 @@ class MidiPlayer { | |
this._startScheduler = startScheduler; | ||
this._state = null; | ||
this._velocity = 1; | ||
this._repeat = 1; | ||
this._latest = MidiPlayer._getMaxTimestamp(json); | ||
} | ||
get position() { | ||
|
@@ -799,24 +800,30 @@ class MidiPlayer { | |
this._clear(); | ||
this._pause(this._state); | ||
} | ||
play(velocity) { | ||
play(velocity, repeat) { | ||
if (this.state !== PlayerState.Stopped) { | ||
throw new Error('The player is not currently stopped.'); | ||
} | ||
// Here, we set the internal variable because we're already stopped and no further state adjustment is needed. | ||
if (typeof velocity !== 'undefined') { | ||
this._velocity = velocity; | ||
} | ||
if (typeof repeat !== 'undefined') { | ||
this._repeat = repeat; | ||
} | ||
return this._promise(); | ||
} | ||
resume(velocity) { | ||
resume(velocity, repeat) { | ||
if (this.state !== PlayerState.Paused) { | ||
throw new Error('The player is not currently paused.'); | ||
} | ||
// Here, we set the public variable to adjust internal state. | ||
if (typeof velocity !== 'undefined') { | ||
this.velocity = velocity; | ||
} | ||
if (typeof repeat !== 'undefined') { | ||
this._repeat = repeat; | ||
} | ||
return this._promise(); | ||
} | ||
stop() { | ||
|
@@ -844,11 +851,25 @@ class MidiPlayer { | |
return new Promise((resolve) => { | ||
const { stop: stopScheduler, reset: resetScheduler, now: nowScheduler } = this._startScheduler(({ end, start }) => { | ||
if (this._state === null) { | ||
this._state = { offset: start, resolve, stopScheduler: null, resetScheduler: null, nowScheduler: null, paused: null }; | ||
this._state = { | ||
next: null, | ||
nowScheduler: null, | ||
offset: start, | ||
paused: null, | ||
repeat: this._repeat, | ||
resetScheduler: null, | ||
resolve, | ||
stopScheduler: null, | ||
}; | ||
} | ||
if (this._state.paused !== null) { | ||
this._state.offset = start - this._state.paused; | ||
this._state.paused = null; | ||
this._state.resolve = resolve; | ||
} | ||
if (this._state.next !== null) { | ||
this._state.offset = this._state.next; | ||
this._state.next = null; | ||
} | ||
this._schedule(start, end, this._state); | ||
}); | ||
|
@@ -867,8 +888,28 @@ class MidiPlayer { | |
events | ||
.filter(({ event }) => this._filterMidiMessage(event)) | ||
.forEach(({ event, time }) => this._midiOutput.send(this._encodeMidiMessage(event), start + time / this._velocity)); | ||
if ((start - state.offset) * this._velocity >= this._latest) { | ||
this._stop(state); | ||
// Check if we're at the end of the file. | ||
const wrapping = (end - state.offset) * this._velocity - this._latest; | ||
if (state.repeat === 0) { | ||
// If we're no longer looping, stop the player. | ||
if (state.nowScheduler !== null && (state.nowScheduler() - state.offset) * this._velocity >= this._latest) { | ||
this._stop(state); | ||
} | ||
} | ||
else if (wrapping >= 0) { | ||
// Decrement the loop counter. | ||
if (state.repeat > 0) { | ||
state.repeat -= 1; | ||
} | ||
// If we're still looping, schedule the starting events from the next cycle. | ||
// Otherwise, wait until next cycle to stop the player. | ||
if (state.repeat !== 0) { | ||
const events2 = this._midiFileSlicer.slice(0, wrapping); | ||
events2 | ||
.filter(({ event }) => this._filterMidiMessage(event)) | ||
.forEach(({ event, time }) => this._midiOutput.send(this._encodeMidiMessage(event), end + (time - wrapping) / this._velocity)); | ||
state.next = state.offset + this._latest / this._velocity; | ||
} | ||
} | ||
} | ||
_stop(state) { | ||
|
@@ -933,7 +974,7 @@ const createMidiPlayerFactory = (createMidiFileSlicer, startScheduler) => { | |
const INTERVAL = 500; | ||
const createStartScheduler = (clearInterval, performance, setInterval) => (next) => { | ||
const start = performance.now(); | ||
let nextTick = start + INTERVAL; | ||
let nextTick = start; | ||
let end = nextTick + INTERVAL; | ||
const intervalId = setInterval(() => { | ||
if (performance.now() >= nextTick) { | ||
|
@@ -12547,7 +12588,7 @@ const timingObjectConstructor = createTimingObjectConstructor(createCalculateTim | |
// @todo Expose an isSupported flag which checks for performance.now() support. | ||
|
||
var name = "musicxml-player"; | ||
var version = "0.17.2"; | ||
var version = "0.18.0"; | ||
var description = "A simple JavaScript component that loads and plays MusicXML files in the browser using Web Audio and Web MIDI."; | ||
var main = "dist/musicxml-player.esm.js"; | ||
var type = "module"; | ||
|
@@ -12695,7 +12736,6 @@ class Player { | |
} | ||
}); | ||
} | ||
//private _timingObjectUpdating: boolean; | ||
constructor(_options, _sheet, _parseResult, _musicXml) { | ||
var _a, _b, _c, _d, _e, _f; | ||
this._options = _options; | ||
|
@@ -12751,8 +12791,12 @@ class Player { | |
}, | ||
}); | ||
// Initialize the playback options. | ||
this._repeat = this._repeatCounter = (_d = this._options.repeat) !== null && _d !== void 0 ? _d : 1; | ||
this._repeat = (_d = this._options.repeat) !== null && _d !== void 0 ? _d : 1; | ||
this._mute = (_e = this._options.mute) !== null && _e !== void 0 ? _e : false; | ||
this._velocity = (_f = this._options.velocity) !== null && _f !== void 0 ? _f : 1; | ||
this._duration = | ||
this._options.converter.timemap.last().timestamp + | ||
this._options.converter.timemap.last().duration; | ||
// Set up resize handling. | ||
// Throttle the resize event https://stackoverflow.com/a/5490021/209184 | ||
let timeout = undefined; | ||
|
@@ -12764,17 +12808,14 @@ class Player { | |
}); | ||
this._observer.observe(this._sheet); | ||
// Create the TimingObject. | ||
this._timingObject = new timingObjectConstructor({ velocity: (_f = this._options.velocity) !== null && _f !== void 0 ? _f : 1, position: 0 }, 0, this._options.converter.timemap.last().timestamp + | ||
this._options.converter.timemap.last().duration); | ||
this._timingObject = new timingObjectConstructor({ velocity: this._velocity, position: 0 }, 0, this.duration); | ||
this._timingObjectListener = (event) => this._handleTimingObjectChange(event); | ||
//this._timingObjectUpdating = false; | ||
this._timingObject.addEventListener('change', this._timingObjectListener); | ||
} | ||
/** | ||
* Destroy the instance by freeing all resources and disconnecting observers. | ||
*/ | ||
destroy() { | ||
this._repeatCounter = 0; | ||
this._timingObject.removeEventListener('change', this._timingObjectListener); | ||
this._sheet.remove(); | ||
this._observer.disconnect(); | ||
|
@@ -12796,7 +12837,7 @@ class Player { | |
} | ||
// Set the playback position. | ||
// Find the closest instance of the measure based on current playback position. | ||
const position = this._midiPlayer.position - measureOffset; | ||
const position = this.position - measureOffset; | ||
const entry = this._options.converter.timemap | ||
.filter((e) => e.measure == measureIndex) | ||
.sort((a, b) => { | ||
|
@@ -12824,7 +12865,6 @@ class Player { | |
if (this._output instanceof WebAudioFontOutput) { | ||
yield this._output.initialize(); | ||
} | ||
this._repeatCounter = this._repeat; | ||
yield this._play(); | ||
}); | ||
} | ||
|
@@ -12835,16 +12875,15 @@ class Player { | |
if (this._midiPlayer.state !== PlayerState.Playing) | ||
return; | ||
this._midiPlayer.pause(); | ||
this._timingObjectUpdate({ velocity: 0 }); | ||
this._timingObject.update({ velocity: 0 }); | ||
} | ||
/** | ||
* Stop playback and rewind to start. | ||
*/ | ||
rewind() { | ||
this._repeatCounter = 0; | ||
this._midiPlayer.stop(); | ||
this._options.renderer.moveTo(0, 0, 0); | ||
this._timingObjectUpdate({ velocity: 0, position: 0 }); | ||
this._timingObject.update({ velocity: 0, position: 0 }); | ||
} | ||
/** | ||
* The version numbers of the player components. | ||
|
@@ -12886,10 +12925,17 @@ class Player { | |
} | ||
/** | ||
* The duration of the score/MIDI file (ms). | ||
* Precomputed in the constructor. | ||
*/ | ||
get duration() { | ||
return (this._options.converter.timemap.last().timestamp + | ||
this._options.converter.timemap.last().duration); | ||
return this._duration; | ||
} | ||
/** | ||
* Current position of the player (ms). | ||
*/ | ||
get position() { | ||
var _a; | ||
return Math.max(0, Math.min((_a = this._midiPlayer.position) !== null && _a !== void 0 ? _a : 0, this._duration - 1)); | ||
} | ||
/** | ||
* The TimingObject attached to the player. | ||
|
@@ -12901,7 +12947,7 @@ class Player { | |
* Repeat count. A value of -1 means loop forever. | ||
*/ | ||
set repeat(value) { | ||
this._repeat = value < 0 ? -1 : Math.max(1, Math.trunc(value)); | ||
this._repeat = value; | ||
} | ||
/** | ||
* A flag to mute the player's MIDI output. | ||
|
@@ -12912,6 +12958,17 @@ class Player { | |
this.clear(); | ||
} | ||
} | ||
/** | ||
* Playback speed. A value of 1 means normal speed. | ||
*/ | ||
set velocity(value) { | ||
this._velocity = value; | ||
if (this._midiPlayer.state === PlayerState.Playing) { | ||
this._midiPlayer.pause(); | ||
this._timingObject.update({ velocity: this._velocity }); | ||
this._play(); | ||
} | ||
} | ||
/** | ||
* Implementation of IMidiOutput.send(). | ||
* | ||
|
@@ -12943,7 +13000,8 @@ class Player { | |
if (this._midiPlayer.state !== PlayerState.Playing) | ||
return; | ||
// Lookup the current measure number by binary-searching the timemap. | ||
const timestamp = this._midiPlayer.position; | ||
// TODO Optimize search by starting at current measure. | ||
const timestamp = this.position; | ||
const index = binarySearch(this._options.converter.timemap, { | ||
measure: 0, | ||
timestamp, | ||
|
@@ -12957,32 +13015,19 @@ class Player { | |
// Update the cursors and listeners. | ||
const entry = this._options.converter.timemap[index >= 0 ? index : Math.max(0, -index - 2)]; | ||
this._options.renderer.moveTo(entry.measure, entry.timestamp, Math.max(0, timestamp - entry.timestamp), entry.duration); | ||
this._timingObjectUpdate({ position: timestamp }); | ||
this._timingObject.update({ position: timestamp }); | ||
// Schedule next cursor movement. | ||
requestAnimationFrame(synchronizeMidi); | ||
}; | ||
// Schedule first cursor movement. | ||
requestAnimationFrame(synchronizeMidi); | ||
// Activate the MIDI player. | ||
const { velocity } = this.timingObject.query(); | ||
if (this._midiPlayer.state === PlayerState.Paused) { | ||
yield this._midiPlayer.resume(velocity); | ||
yield this._midiPlayer.resume(this._velocity, this._repeat); | ||
} | ||
else { | ||
yield this._midiPlayer.play(velocity); | ||
yield this._midiPlayer.play(this._velocity, this._repeat); | ||
} | ||
// Repeat if needed. | ||
if (this._midiPlayer.state === PlayerState.Stopped) { | ||
if (this._repeatCounter < 0 || Math.max(0, --this._repeatCounter) > 0) { | ||
this._play(); | ||
} | ||
} | ||
}); | ||
} | ||
_timingObjectUpdate(newVector) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
//this._timingObjectUpdating = true; | ||
this._timingObject.update(newVector); | ||
}); | ||
} | ||
_handleTimingObjectChange(_event) { | ||
|
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.