UNPKG

qambi

Version:

MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio

296 lines (252 loc) 8.33 kB
import {getPosition2} from './position.js' import {dispatchEvent} from './eventlistener.js' import {sortEvents} from './util.js' const range = 10 // milliseconds or ticks let instanceIndex = 0 export class Playhead{ constructor(song, type = 'all'){ this.id = `${this.constructor.name}_${instanceIndex++}_${new Date().getTime()}` this.song = song this.type = type this.lastEvent = null this.data = {} this.activeParts = [] this.activeNotes = [] this.activeEvents = [] } // unit can be 'millis' or 'ticks' set(unit, value){ this.unit = unit this.currentValue = value this.eventIndex = 0 this.noteIndex = 0 this.partIndex = 0 this.calculate() return this.data } get(){ return this.data } update(unit, diff){ if(diff === 0){ return this.data } this.unit = unit this.currentValue += diff this.calculate() return this.data } updateSong(){ this.events = [...this.song._events, ...this.song._timeEvents] sortEvents(this.events) //console.log('events %O', this.events) this.notes = this.song._notes this.parts = this.song._parts this.numEvents = this.events.length this.numNotes = this.notes.length this.numParts = this.parts.length this.set('millis', this.song._currentMillis) } calculate(){ let i let value let event let note let part let position let stillActiveNotes = [] let stillActiveParts = [] let collectedParts = new Set() let collectedNotes = new Set() this.data = {} this.activeEvents = [] let sustainpedalEvents = [] for(i = this.eventIndex; i < this.numEvents; i++){ event = this.events[i] value = event[this.unit] if(value <= this.currentValue){ // if the playhead is set to a position of say 3000 millis, we don't want to add events more that 10 units before the playhead if(value === 0 || value > this.currentValue - range){ this.activeEvents.push(event) // this doesn't work too well if(event.type === 176){ //console.log(event.type, event.data1, event.data2) if(event.data1 === 64){ dispatchEvent({ type: 'sustainpedal2', data: event.data2 === 127 ? 'down' : 'up' }) sustainpedalEvents.push(event) } // }else{ // dispatchEvent({ // type: 'event', // data: event // }) } dispatchEvent({ type: 'event', data: event }) } this.lastEvent = event this.eventIndex++ }else{ break } } // let num = sustainpedalEvents.length // if(num > 0){ // console.log(this.currentValue, num, sustainpedalEvents[num - 1].data2, sustainpedalEvents) // } //console.log('-----------------') this.data.activeEvents = this.activeEvents // if a song has no events yet, use the first time event as reference if(this.lastEvent === null){ this.lastEvent = this.song._timeEvents[0] } position = getPosition2(this.song, this.unit, this.currentValue, 'all', this.lastEvent) this.data.eventIndex = this.eventIndex this.data.millis = position.millis this.data.ticks = position.ticks this.data.position = position if(this.type.indexOf('all') !== -1){ var data = this.data for(let key of Object.keys(position)){ data[key] = position[key] } }else if(this.type.indexOf('barsbeats') !== -1){ this.data.bar = position.bar this.data.beat = position.beat this.data.sixteenth = position.sixteenth this.data.tick = position.tick this.data.barsAsString = position.barsAsString this.data.ticksPerBar = position.ticksPerBar this.data.ticksPerBeat = position.ticksPerBeat this.data.ticksPerSixteenth = position.ticksPerSixteenth this.data.numSixteenth = position.numSixteenth }else if(this.type.indexOf('time') !== -1){ this.data.hour = position.hour this.data.minute = position.minute this.data.second = position.second this.data.millisecond = position.millisecond this.data.timeAsString = position.timeAsString }else if(this.type.indexOf('percentage') !== -1){ this.data.percentage = position.percentage } // get active notes if(this.type.indexOf('notes') !== -1 || this.type.indexOf('all') !== -1){ // get all notes between the noteIndex and the current playhead position for(i = this.noteIndex; i < this.numNotes; i++){ note = this.notes[i] value = note.noteOn[this.unit] if(value <= this.currentValue){ this.noteIndex++ if(typeof note.noteOff === 'undefined'){ continue; } // if the playhead is set to a position of say 3000 millis, we don't want to add notes before the playhead if(this.currentValue === 0 || note.noteOff[this.unit] > this.currentValue){ collectedNotes.add(note) dispatchEvent({ type: 'noteOn', data: note.noteOn }) } }else{ break; } } // filter notes that are no longer active for(i = this.activeNotes.length - 1; i >= 0; i--){ note = this.activeNotes[i]; //if(note.noteOn.state.indexOf('removed') === 0 || this.song._notesById.get(note.id) === false){ if(this.song._notesById.get(note.id) === false){ //console.log('skipping removed note', note.id); continue; } if(typeof note.noteOff === 'undefined'){ console.warn('note with id', note.id, 'has no noteOff event'); continue; } //if(note.noteOff[this.unit] > this.currentValue && collectedNotes.has(note) === false){ if(note.noteOff[this.unit] > this.currentValue){ stillActiveNotes.push(note); }else{ dispatchEvent({ type: 'noteOff', data: note.noteOff }) } } // add the still active notes and the newly active events to the active notes array this.activeNotes = [...collectedNotes.values(), ...stillActiveNotes] this.data.activeNotes = this.activeNotes } // get active parts if(this.type.indexOf('parts') !== -1 || this.type.indexOf('all') !== -1){ for(i = this.partIndex; i < this.numParts; i++){ part = this.parts[i] //console.log(part, this.unit, this.currentValue); if(part._start[this.unit] <= this.currentValue){ collectedParts.add(part) dispatchEvent({ type: 'partOn', data: part }) this.partIndex++ }else{ break } } // filter parts that are no longer active for(i = this.activeParts.length - 1; i >= 0; i--){ part = this.activeParts[i]; //if(part.state.indexOf('removed') === 0 || this.song._partsById.get(part.id) === false){ if(this.song._partsById.get(part.id) === false){ //console.log('skipping removed part', part.id); continue; } //if(part._end[this.unit] > this.currentValue && collectedParts.has(part) === false){ if(part._end[this.unit] > this.currentValue){ stillActiveParts.push(note); }else{ dispatchEvent({ type: 'partOff', data: part }) } } this.activeParts = [...collectedParts.values(), ...stillActiveParts] this.data.activeParts = this.activeParts } dispatchEvent({ type: 'position', data: this.data }) } /* setType(t){ this.type = t; this.set(this.unit, this.currentValue); //console.log(type,activeParts); } addType(t){ this.type += ' ' + t; this.set(this.unit, this.currentValue); //console.log(type,activeParts); } removeType(t){ var arr = this.type.split(' '); this.type = ''; arr.forEach(function(type){ if(type !== t){ this.type += t + ' '; } }); this.type.trim(); this.set(this.currentValue); //console.log(type,activeParts); } */ }