UNPKG

midijs

Version:

Read and write Standard MIDI files and enable communication with MIDI devices!

386 lines (324 loc) 13 kB
'use strict'; var assert = require('assert'); var path = require('path'); var buffer = require('buffer'); var bufferEqual = require('buffer-equal'); var fs = require('fs'); var MIDI = require('../index'); var File = MIDI.File; var error = MIDI.error; var MetaEvent = File.MetaEvent; var SysexEvent = File.SysexEvent; var ChannelEvent = File.ChannelEvent; var fixtures = path.join(__dirname, 'fixtures'); var filePath = path.join(fixtures, 'file.mid'); describe('File as a writer', function () { describe('creation API', function () { var file, header, tracks; before(function () { file = new File(); header = file.getHeader(); tracks = file.getTracks(); }); it('should create a new file', function () { assert.strictEqual(header._trackCount, 0); assert.ok(Array.isArray(tracks)); assert.strictEqual(tracks.length, header._trackCount); }); it('should have header methods', function () { assert.strictEqual(typeof header.getFileType(), 'number'); assert.strictEqual(typeof header.getTicksPerBeat(), 'number'); assert.strictEqual( header .setFileType(File.Header.FILE_TYPE.SINGLE_TRACK) .setFileType(File.Header.FILE_TYPE.SYNC_TRACKS) .setFileType(File.Header.FILE_TYPE.ASYNC_TRACKS), header ); assert.strictEqual( header .setTicksPerBeat(120) .setTicksPerBeat(60), header ); assert.strictEqual( header.getFileType(), File.Header.FILE_TYPE.ASYNC_TRACKS ); assert.strictEqual(header.getTicksPerBeat(), 60); }); it('should throw with wrong values', function () { assert.throws(function () { header.setFileType(-1); }, error.MIDIInvalidArgument); assert.throws(function () { header.setTicksPerBeat(0); }, error.MIDIInvalidArgument); assert.throws(function () { header.setTicksPerBeat(65536); }, error.MIDIInvalidArgument); }); it('should add tracks', function () { file.addTrack(0, new MetaEvent(MetaEvent.TYPE.END_OF_TRACK)); file.addTrack(new MetaEvent(MetaEvent.TYPE.SET_TEMPO)); file.addTrack(2, []); file.addTrack([ new ChannelEvent(ChannelEvent.TYPE.CHANNEL_AFTERTOUCH) ]); }); it('should add events', function () { var track = file.getTrack(2), events, expectedEvents = [ new MetaEvent(MetaEvent.TYPE.SEQUENCE_NUMBER, { number: 2 }), new MetaEvent(MetaEvent.TYPE.SEQUENCE_NAME, { text: 'Optional' }), new MetaEvent(MetaEvent.TYPE.END_OF_TRACK) ]; track.addEvent(expectedEvents[2]); track.addEvent(0, expectedEvents[0]); track.addEvent(1, expectedEvents[1]); events = track.getEvents(); events.forEach(function (event, i) { assert.deepEqual(event, expectedEvents[i]); }); expectedEvents.forEach(function (event, i) { assert.deepEqual(event, track.getEvent(i)); }); }); it('should remove events', function () { var track = file.getTrack(2); track.removeEvent(0); assert.strictEqual(track.getEvents().length, 2); track.removeEvent(); assert.strictEqual(track.getEvents().length, 1); assert.strictEqual( track.getEvent(0).type, MetaEvent.TYPE.SEQUENCE_NAME ); }); it('should remove tracks', function () { file.removeTrack(2); assert.strictEqual(file.getTracks().length, 3); file.removeTrack(2); assert.strictEqual(file.getTracks().length, 2); file.removeTrack(); assert.strictEqual(file.getTracks().length, 1); assert.strictEqual( file.getTrack(0).getEvent(0).type, MetaEvent.TYPE.END_OF_TRACK ); }); it('should throw with unknown events', function () { assert.throws(function () { return new ChannelEvent('unknown type!!!'); }, error.MIDIInvalidEventError); assert.throws(function () { return new MetaEvent('unknown type!!!'); }, error.MIDIInvalidEventError); }); }); describe('encoding APIs', function () { var file, encodedBuffers, encodedStreams; before(function () { file = new File(); file.addTrack( new MetaEvent(MetaEvent.TYPE.SEQUENCE_NUMBER, { number: 0 }), new MetaEvent(MetaEvent.TYPE.SEQUENCE_NAME, { text: 'Meta track' }), new MetaEvent(MetaEvent.TYPE.COPYRIGHT_NOTICE, { text: '© 2015, Mattéo DELABRE' }), new MetaEvent(MetaEvent.TYPE.TEXT, { text: 'A fixture for testing all types ' + 'of MIDI events in one file' }), new MetaEvent(MetaEvent.TYPE.TIME_SIGNATURE, { numerator: 2, denominator: 4, metronome: 24, clockSignalsPerBeat: 8 }), new MetaEvent(MetaEvent.TYPE.KEY_SIGNATURE, { major: false, note: 3 }), new MetaEvent(MetaEvent.TYPE.SEQUENCER_SPECIFIC, { data: new buffer.Buffer('just testing out') }), new MetaEvent(MetaEvent.TYPE.SET_TEMPO, { tempo: 240 }), new MetaEvent(MetaEvent.TYPE.SET_TEMPO, { tempo: 480 }, 600), new MetaEvent(MetaEvent.TYPE.END_OF_TRACK) ); file.addTrack([ new MetaEvent(MetaEvent.TYPE.SEQUENCE_NUMBER, { number: 1 }), new MetaEvent(MetaEvent.TYPE.SEQUENCE_NAME, { text: 'Test song' }), new MetaEvent(MetaEvent.TYPE.MIDI_CHANNEL, { channel: 0 }), new MetaEvent(MetaEvent.TYPE.MIDI_PORT, { port: 42 }), new MetaEvent(MetaEvent.TYPE.DEVICE_NAME, { text: 'test device' }), new MetaEvent(MetaEvent.TYPE.INSTRUMENT_NAME, { text: 'Church organ' }), new MetaEvent(MetaEvent.TYPE.PROGRAM_NAME, { text: 'program name test' }), new ChannelEvent(ChannelEvent.TYPE.PROGRAM_CHANGE, { program: MIDI.gm.getProgram('Church organ') }, 0), new MetaEvent(MetaEvent.TYPE.SMPTE_OFFSET, { rate: 25, hours: 15, minutes: 53, seconds: 10, frames: 20, subframes: 50 }), new ChannelEvent(ChannelEvent.TYPE.NOTE_ON, { note: 75, velocity: 127 }), new ChannelEvent(ChannelEvent.TYPE.NOTE_ON, { note: 60, velocity: 127 }, 0, 120), new ChannelEvent(ChannelEvent.TYPE.NOTE_ON, { note: 60, velocity: 127 }), new MetaEvent(MetaEvent.TYPE.LYRICS, { text: 'test' }), new MetaEvent(MetaEvent.TYPE.SET_TEMPO, { tempo: 60 }), new ChannelEvent(ChannelEvent.TYPE.NOTE_AFTERTOUCH, { note: 75, pressure: 50 }, 0, 480), new ChannelEvent(ChannelEvent.TYPE.NOTE_AFTERTOUCH, { note: 60, pressure: 50 }), new ChannelEvent(ChannelEvent.TYPE.NOTE_AFTERTOUCH, { note: 60, pressure: 50 }), new ChannelEvent(ChannelEvent.TYPE.CHANNEL_AFTERTOUCH, { pressure: 127 }, 0, 480), new MetaEvent(MetaEvent.TYPE.MARKER, { text: 'Pitch bend' }, 480), new ChannelEvent(ChannelEvent.TYPE.PITCH_BEND, { value: -6000 }), new ChannelEvent(ChannelEvent.TYPE.NOTE_OFF, { note: 75, velocity: 127 }, 0, 480), new ChannelEvent(ChannelEvent.TYPE.CONTROLLER, { controller: 123, value: 0 }, 0, 480), new SysexEvent(0, new buffer.Buffer('test')), new MetaEvent(MetaEvent.TYPE.CUE_POINT, { text: 'All sounds are stopped' }, 480), new ChannelEvent(ChannelEvent.TYPE.PROGRAM_CHANGE, { program: 0 }), new MetaEvent(MetaEvent.TYPE.END_OF_TRACK) ]); }); it('should encode with buffers', function (done) { file.getData(function (err, data) { if (err) { done(err); return; } encodedBuffers = data; done(); }); }); it('should encode with streams', function (done) { var data = []; file.on('data', function (chunk) { data.push(chunk); }); file.on('error', function (err) { done(err); }); file.on('end', function () { encodedStreams = buffer.Buffer.concat(data); done(); }); }); it('should give the same results', function () { assert.ok(bufferEqual(encodedBuffers, encodedStreams)); }); it('should equal reference file', function (done) { fs.readFile(filePath, function (err, data) { if (err) { done(err); return; } assert.ok(bufferEqual(encodedBuffers, data)); assert.ok(bufferEqual(encodedStreams, data)); done(); }); }); }); describe('invalid files', function () { var file; beforeEach(function () { file = new File(); file.addTrack(); }); it('should throw with unknown meta events', function (done) { var event = new MetaEvent(MetaEvent.TYPE.MIDI_CHANNEL); event.type = 'unknown type!!!'; // dirty hack file.getTrack(0).addEvent(event); file.getData(function (err) { assert.ok(err); assert.ok(err instanceof error.MIDIEncoderError); done(); }); }); it('should throw with unknown channel events', function (done) { var event = new ChannelEvent(ChannelEvent.TYPE.NOTE_AFTERTOUCH); event.type = 'unknown type!!!'; // dirty hack file.getTrack(0).addEvent(event); file.getData(function (err) { assert.ok(err); assert.ok(err instanceof error.MIDIEncoderError); done(); }); }); it('should throw with unknown events', function (done) { file.getTrack(0).addEvent({}); // add a generic object as an event file.getData(function (err) { assert.ok(err); assert.ok(err instanceof error.MIDIEncoderError); done(); }); }); }); });