qambi
Version:
MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio
249 lines (199 loc) • 6.73 kB
JavaScript
// called by song
import {parseTimeEvents, parseEvents} from './parse_events'
import {sortEvents} from './util'
import {MIDIEventTypes} from './constants'
import {calculatePosition} from './position'
import {MIDIEvent} from './midi_event'
import {dispatchEvent} from './eventlistener'
export function update():void{
if(this.playing === false){
_update.call(this)
}else{
this._performUpdate = true
}
}
export function _update():void{
if(this._updateTimeEvents === false
&& this._removedTracks.length === 0
&& this._removedEvents.length === 0
&& this._newEvents.length === 0
&& this._movedEvents.length === 0
&& this._newParts.length === 0
&& this._removedParts.length === 0
&& this._resized === false
){
return
}
//debug
//this.isPlaying = true
//console.groupCollapsed('update song')
console.time('updating song took')
// TIME EVENTS
// check if time events are updated
if(this._updateTimeEvents === true){
//console.log('updateTimeEvents', this._timeEvents.length)
parseTimeEvents(this, this._timeEvents, this.isPlaying)
//console.log('time events %O', this._timeEvents)
}
// only parse new and moved events
let tobeParsed = []
// but parse all events if the time events have been updated
if(this._updateTimeEvents === true){
tobeParsed = [...this._events]
}
// TRACKS
// removed tracks
if(this._removedTracks.length > 0){
this._removedTracks.forEach(track => {
this._tracksById.delete(track.id)
track.removeParts(track.getParts())
track._song = null
track._gainNode.disconnect()
track._songGainNode = null
})
}
// PARTS
// removed parts
//console.log('removed parts %O', this._changedParts)
if(this._removedParts.length > 0){
this._removedParts.forEach((part) => {
this._partsById.delete(part.id)
})
this._parts = Array.from(this._partsById.values())
}
// add new parts
//console.log('new parts %O', this._newParts)
this._newParts.forEach((part) => {
part._song = this
this._partsById.set(part.id, part)
part.update()
})
// update changed parts
//console.log('changed parts %O', this._changedParts)
this._changedParts.forEach((part) => {
part.update()
})
// EVENTS
// filter removed events
//console.log('removed events %O', this._removedEvents)
this._removedEvents.forEach((event) => {
let track = event.midiNote._track
// unschedule all removed events that already have been scheduled
if(event.time >= this._currentMillis){
track.unschedule(event)
}
this._notesById.delete(event.midiNote.id)
this._eventsById.delete(event.id)
})
// add new events
//console.log('new events %O', this._newEvents)
this._newEvents.forEach((event) => {
this._eventsById.set(event.id, event)
this._events.push(event)
tobeParsed.push(event)
})
// moved events need to be parsed
//console.log('moved %O', this._movedEvents)
this._movedEvents.forEach((event) => {
// don't add moved events if the time events have been updated -> they have already been added to the tobeParsed array
if(this._updateTimeEvents === false){
tobeParsed.push(event)
}
})
// parse all new and moved events
if(tobeParsed.length > 0){
//console.time('parse')
//console.log('tobeParsed %O', tobeParsed)
//console.log('parseEvents', tobeParsed.length)
tobeParsed = [...tobeParsed, ...this._timeEvents]
parseEvents(tobeParsed, this.isPlaying)
// add MIDI notes to song
tobeParsed.forEach(event => {
//console.log(event.id, event.type, event.midiNote)
if(event.type === MIDIEventTypes.NOTE_ON){
if(event.midiNote){
this._notesById.set(event.midiNoteId, event.midiNote)
//console.log(event.midiNoteId, event.type)
//this._notes.push(event.midiNote)
}
}
})
//console.timeEnd('parse')
}
if(tobeParsed.length > 0 || this._removedEvents.length > 0){
//console.time('to array')
this._events = Array.from(this._eventsById.values())
this._notes = Array.from(this._notesById.values())
//console.timeEnd('to array')
}
//console.time(`sorting ${this._events.length} events`)
sortEvents(this._events)
this._notes.sort(function(a, b){
return a.noteOn.ticks - b.noteOn.ticks
})
//console.timeEnd(`sorting ${this._events.length} events`)
//console.log('notes %O', this._notes)
console.timeEnd('updating song took')
// SONG DURATION
// get the last event of this song
let lastEvent = this._events[this._events.length - 1]
let lastTimeEvent = this._timeEvents[this._timeEvents.length - 1]
//console.log(lastEvent, lastTimeEvent)
// check if song has already any events
if(lastEvent instanceof MIDIEvent === false){
lastEvent = lastTimeEvent
}else if(lastTimeEvent.ticks > lastEvent.ticks){
lastEvent = lastTimeEvent
}
//console.log(lastEvent, this.bars)
// get the position data of the first beat in the bar after the last bar
this.bars = Math.max(lastEvent.bar, this.bars)
let ticks = calculatePosition(this, {
type: 'barsbeats',
target: [this.bars + 1],
result: 'ticks'
}).ticks
// we want to put the END_OF_TRACK event at the very last tick of the last bar, so we calculate that position
let millis = calculatePosition(this, {
type: 'ticks',
target: ticks - 1,
result: 'millis'
}).millis
this._lastEvent.ticks = ticks - 1
this._lastEvent.millis = millis
//console.log('length', this._lastEvent.ticks, this._lastEvent.millis, this.bars)
this._durationTicks = this._lastEvent.ticks
this._durationMillis = this._lastEvent.millis
// METRONOME
// add metronome events
if(this._updateMetronomeEvents || this._metronome.bars !== this.bars || this._updateTimeEvents === true){
this._metronomeEvents = parseEvents([...this._timeEvents, ...this._metronome.getEvents()])
}
this._allEvents = [...this._metronomeEvents, ...this._events]
sortEvents(this._allEvents)
//console.log('all events %O', this._allEvents)
/*
this._metronome.getEvents()
this._allEvents = [...this._events]
sortEvents(this._allEvents)
*/
//console.log('current millis', this._currentMillis)
this._playhead.updateSong()
this._scheduler.updateSong()
if(this.playing === false){
this._playhead.set('millis', this._currentMillis)
dispatchEvent({
type: 'position',
data: this._playhead.get().position
})
}
// reset
this._newParts = []
this._removedParts = []
this._newEvents = []
this._movedEvents = []
this._removedEvents = []
this._resized = false
this._updateTimeEvents = false
//console.groupEnd('update song')
}