UNPKG

midifile

Version:
360 lines (338 loc) 10.5 kB
'use strict'; // MIDIFile : Read (and soon edit) a MIDI file in a given ArrayBuffer // Dependencies var MIDIFileHeader = require('./MIDIFileHeader'); var MIDIFileTrack = require('./MIDIFileTrack'); var MIDIEvents = require('midievents'); var UTF8 = require('utf-8'); function ensureArrayBuffer(buf) { if (buf) { if (buf instanceof ArrayBuffer) { return buf; } if (buf instanceof Uint8Array) { // Copy/convert to standard Uint8Array, because derived classes like // node.js Buffers might have unexpected data in the .buffer property. return new Uint8Array(buf).buffer; } } throw new Error('Unsupported buffer type, need ArrayBuffer or Uint8Array'); } // Constructor function MIDIFile(buffer, strictMode) { var track; var curIndex; var i; var j; // If not buffer given, creating a new MIDI file if (!buffer) { // Creating the content this.header = new MIDIFileHeader(); this.tracks = [new MIDIFileTrack()]; // if a buffer is provided, parsing him } else { buffer = ensureArrayBuffer(buffer); // Minimum MIDI file size is a headerChunk size (14bytes) // and an empty track (8+3bytes) if (25 > buffer.byteLength) { throw new Error( 'A buffer of a valid MIDI file must have, at least, a' + ' size of 25bytes.' ); } // Reading header this.header = new MIDIFileHeader(buffer, strictMode); this.tracks = []; curIndex = MIDIFileHeader.HEADER_LENGTH; // Reading tracks for (i = 0, j = this.header.getTracksCount(); i < j; i++) { // Testing the buffer length if (strictMode && curIndex >= buffer.byteLength - 1) { throw new Error( "Couldn't find datas corresponding to the track #" + i + '.' ); } // Creating the track object track = new MIDIFileTrack(buffer, curIndex, strictMode); this.tracks.push(track); // Updating index to the track end curIndex += track.getTrackLength() + 8; } // Testing integrity : curIndex should be at the end of the buffer if (strictMode && curIndex !== buffer.byteLength) { throw new Error('It seems that the buffer contains too much datas.'); } } } // Events reading helpers MIDIFile.prototype.getEvents = function(type, subtype) { var events; var event; var playTime = 0; var filteredEvents = []; var format = this.header.getFormat(); var tickResolution = this.header.getTickResolution(); var i; var j; var trackParsers; var smallestDelta; // Reading events // if the read is sequential if (1 !== format || 1 === this.tracks.length) { for (i = 0, j = this.tracks.length; i < j; i++) { // reset playtime if format is 2 playTime = 2 === format && playTime ? playTime : 0; events = MIDIEvents.createParser( this.tracks[i].getTrackContent(), 0, false ); // loooping through events event = events.next(); while (event) { playTime += event.delta ? event.delta * tickResolution / 1000 : 0; if (event.type === MIDIEvents.EVENT_META) { // tempo change events if (event.subtype === MIDIEvents.EVENT_META_SET_TEMPO) { tickResolution = this.header.getTickResolution(event.tempo); } } // push the asked events if ( (!type || event.type === type) && (!subtype || (event.subtype && event.subtype === subtype)) ) { event.playTime = playTime; filteredEvents.push(event); } event = events.next(); } } // the read is concurrent } else { trackParsers = []; smallestDelta = -1; // Creating parsers for (i = 0, j = this.tracks.length; i < j; i++) { trackParsers[i] = {}; trackParsers[i].parser = MIDIEvents.createParser( this.tracks[i].getTrackContent(), 0, false ); trackParsers[i].curEvent = trackParsers[i].parser.next(); } // Filling events do { smallestDelta = -1; // finding the smallest event for (i = 0, j = trackParsers.length; i < j; i++) { if (trackParsers[i].curEvent) { if ( -1 === smallestDelta || trackParsers[i].curEvent.delta < trackParsers[smallestDelta].curEvent.delta ) { smallestDelta = i; } } } if (-1 !== smallestDelta) { // removing the delta of previous events for (i = 0, j = trackParsers.length; i < j; i++) { if (i !== smallestDelta && trackParsers[i].curEvent) { trackParsers[i].curEvent.delta -= trackParsers[smallestDelta].curEvent.delta; } } // filling values event = trackParsers[smallestDelta].curEvent; playTime += event.delta ? event.delta * tickResolution / 1000 : 0; if (event.type === MIDIEvents.EVENT_META) { // tempo change events if (event.subtype === MIDIEvents.EVENT_META_SET_TEMPO) { tickResolution = this.header.getTickResolution(event.tempo); } } // push midi events if ( (!type || event.type === type) && (!subtype || (event.subtype && event.subtype === subtype)) ) { event.playTime = playTime; event.track = smallestDelta; filteredEvents.push(event); } // getting next event trackParsers[smallestDelta].curEvent = trackParsers[ smallestDelta ].parser.next(); } } while (-1 !== smallestDelta); } return filteredEvents; }; MIDIFile.prototype.getMidiEvents = function() { return this.getEvents(MIDIEvents.EVENT_MIDI); }; MIDIFile.prototype.getLyrics = function() { var events = this.getEvents(MIDIEvents.EVENT_META); var texts = []; var lyrics = []; var event; var i; var j; for (i = 0, j = events.length; i < j; i++) { event = events[i]; // Lyrics if (event.subtype === MIDIEvents.EVENT_META_LYRICS) { lyrics.push(event); // Texts } else if (event.subtype === MIDIEvents.EVENT_META_TEXT) { // Ignore special texts if ('@' === String.fromCharCode(event.data[0])) { if ('T' === String.fromCharCode(event.data[1])) { // console.log('Title : ' + event.text.substring(2)); } else if ('I' === String.fromCharCode(event.data[1])) { // console.log('Info : ' + event.text.substring(2)); } else if ('L' === String.fromCharCode(event.data[1])) { // console.log('Lang : ' + event.text.substring(2)); } // karaoke text follows, remove all previous text } else if ( 0 === String.fromCharCode.apply(String, event.data).indexOf('words') ) { texts.length = 0; // console.log('Word marker found'); // Karaoke texts // If playtime is greater than 0 } else if (0 !== event.playTime) { texts.push(event); } } } // Choosing the right lyrics if (2 < lyrics.length) { texts = lyrics; } else if (!texts.length) { texts = []; } // Convert texts and detect encoding try { texts.forEach(function(event) { event.text = UTF8.getStringFromBytes(event.data, 0, event.length, true); }); } catch (e) { texts.forEach(function(event) { event.text = event.data .map(function(c) { return String.fromCharCode(c); }) .join(''); }); } return texts; }; // Basic events reading MIDIFile.prototype.getTrackEvents = function(index) { var event; var events = []; var parser; if (index > this.tracks.length || 0 > index) { throw Error('Invalid track index (' + index + ')'); } parser = MIDIEvents.createParser( this.tracks[index].getTrackContent(), 0, false ); event = parser.next(); do { events.push(event); event = parser.next(); } while (event); return events; }; // Basic events writting MIDIFile.prototype.setTrackEvents = function(index, events) { var bufferLength; var destination; if (index > this.tracks.length || 0 > index) { throw Error('Invalid track index (' + index + ')'); } if (!events || !events.length) { throw Error('A track must contain at least one event, none given.'); } bufferLength = MIDIEvents.getRequiredBufferLength(events); destination = new Uint8Array(bufferLength); MIDIEvents.writeToTrack(events, destination); this.tracks[index].setTrackContent(destination); }; // Remove a track MIDIFile.prototype.deleteTrack = function(index) { if (index > this.tracks.length || 0 > index) { throw Error('Invalid track index (' + index + ')'); } this.tracks.splice(index, 1); this.header.setTracksCount(this.tracks.length); }; // Add a track MIDIFile.prototype.addTrack = function(index) { var track; if (index > this.tracks.length || 0 > index) { throw Error('Invalid track index (' + index + ')'); } track = new MIDIFileTrack(); if (index === this.tracks.length) { this.tracks.push(track); } else { this.tracks.splice(index, 0, track); } this.header.setTracksCount(this.tracks.length); }; // Retrieve the content in a buffer MIDIFile.prototype.getContent = function() { var bufferLength; var destination; var origin; var i; var j; var k; var l; var m; var n; // Calculating the buffer content // - initialize with the header length bufferLength = MIDIFileHeader.HEADER_LENGTH; // - add tracks length for (i = 0, j = this.tracks.length; i < j; i++) { bufferLength += this.tracks[i].getTrackLength() + 8; } // Creating the destination buffer destination = new Uint8Array(bufferLength); // Adding header origin = new Uint8Array( this.header.datas.buffer, this.header.datas.byteOffset, MIDIFileHeader.HEADER_LENGTH ); for (i = 0, j = MIDIFileHeader.HEADER_LENGTH; i < j; i++) { destination[i] = origin[i]; } // Adding tracks for (k = 0, l = this.tracks.length; k < l; k++) { origin = new Uint8Array( this.tracks[k].datas.buffer, this.tracks[k].datas.byteOffset, this.tracks[k].datas.byteLength ); for (m = 0, n = this.tracks[k].datas.byteLength; m < n; m++) { destination[i++] = origin[m]; } } return destination.buffer; }; // Exports Track/Header constructors MIDIFile.Header = MIDIFileHeader; MIDIFile.Track = MIDIFileTrack; module.exports = MIDIFile;