diff --git a/package.json b/package.json index e3b983d..79650fb 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,8 @@ "sinon-chai": "^2.8.0" }, "dependencies": { - "babel-runtime": "^6.9.2" + "babel-runtime": "^6.9.2", + "safe-buffer": "^5.0.1" }, "release": { "verifyRelease": { diff --git a/src/format.js b/src/format.js index 8cd8fa0..5b305ca 100644 --- a/src/format.js +++ b/src/format.js @@ -1,7 +1,9 @@ import rolandChecksum from './rolandChecksum'; +import isBufferEquivalent from './utils/isBufferEquivalent'; +import asBuffer from './utils/asBuffer'; function requiredSize (message) { - if (Buffer.isBuffer(message)) return message.length; + if (isBufferEquivalent(message)) return message.byteLength || message.length; if (typeof message === 'number') return 1; // make it simple if (!message) return 0; switch (message.type) { @@ -35,15 +37,15 @@ export default function format (message) { if (Array.isArray(message)) { return Buffer.concat(message.map(format)); } - if (Buffer.isBuffer(message)) return message; + if (isBufferEquivalent(message)) return asBuffer(message); if (!message || typeof message !== 'object') throw new Error('Message must be an Object or a Buffer'); const size = requiredSize(message); const buf = new Buffer(size); buf.fill(0); let i = 0; const write = data => { - if (Buffer.isBuffer(data)) { - i += data.copy(buf, i); + if (isBufferEquivalent(data)) { + if (data.byteLength || data.length) i += asBuffer(data).copy(buf, i); } else { buf[i++] = data; } @@ -52,13 +54,13 @@ export default function format (message) { case 'sysex': write(0xF0); if (message.data) { - if (Buffer.isBuffer(message.data)) { + if (isBufferEquivalent(message.data)) { write(message.data); } else if (message.data.vendor === 'Roland') { write(0x41); write(message.data.deviceId); write(message.data.modelId); - if (Buffer.isBuffer(message.data.command)) { + if (isBufferEquivalent(message.data.command)) { write(message.data.command); } else { switch (message.data.command.type) { diff --git a/src/parse.js b/src/parse.js index 04f26d7..8ffba87 100644 --- a/src/parse.js +++ b/src/parse.js @@ -1,11 +1,13 @@ import rolandHexParser from './peg/roland'; import assert from 'assert'; +import isBufferEquivalent from './utils/isBufferEquivalent'; +import asBuffer from './utils/asBuffer'; /** - * @param {Buffer} buf A buffer containing MIDI messages. + * @param {Buffer|Uint8Array} buf A buffer containing MIDI messages. * @returns {Array} - Contains SysEx messages parsed from the buffer. */ export default function parse (buf) { - assert(Buffer.isBuffer(buf), 'Argument should be a Buffer'); - return rolandHexParser.parse(buf.toString('hex')); + assert(isBufferEquivalent(buf), 'buf must be a Buffer or Uint8Array'); + return rolandHexParser.parse(asBuffer(buf).toString('hex')); } diff --git a/src/utils/asBuffer.js b/src/utils/asBuffer.js new file mode 100644 index 0000000..f58c0b9 --- /dev/null +++ b/src/utils/asBuffer.js @@ -0,0 +1,15 @@ +import SafeBuffer from 'safe-buffer'; + +/** + * @private + * Ensure that {@link buf} is a @{link Buffer} + * @param {Buffer|TypedArray|ArrayBuffer} buf A buffer or typed array. + * @returns {Buffer} + */ +export default function asBuffer (buf) { + if (Buffer.isBuffer(buf)) return buf; + if (buf.buffer instanceof ArrayBuffer) { + return SafeBuffer.Buffer.from(buf.buffer); + } + return SafeBuffer.Buffer.from(buf); +} diff --git a/src/utils/isBufferEquivalent.js b/src/utils/isBufferEquivalent.js new file mode 100644 index 0000000..63fd164 --- /dev/null +++ b/src/utils/isBufferEquivalent.js @@ -0,0 +1,9 @@ +/** + * @private + * Check that {@link buf} is a {@link Buffer} equivalent (e.g. a typed array) + * @param buf + * @returns {boolean} + */ +export default function isBufferEquivalent (buf) { + return Buffer.isBuffer(buf) || (buf.buffer instanceof ArrayBuffer) || (buf instanceof ArrayBuffer); +} diff --git a/test/specs/format.js b/test/specs/format.js index 16166ec..25d6ca6 100644 --- a/test/specs/format.js +++ b/test/specs/format.js @@ -96,4 +96,20 @@ describe('format()', () => { } }).should.deep.equal(hex `F0 41 10 6A 12 00 00 00 01 7F F7`); }); + it('should accept Uint8Array for embedded data as well as Buffer', () => { + format({ + type: 'sysex', + data: { + vendor: 'Roland', + deviceId: 0x10, + modelId: 0x6A, + command: { + type: 'DT1', + address: 0x00000001, + body: new Uint8Array([]), + checksum: 0x7f + } + } + }).should.deep.equal(hex `F0 41 10 6A 12 00 00 00 01 7F F7`); + }); }); diff --git a/test/specs/parse.js b/test/specs/parse.js index 9f7125f..096d56d 100644 --- a/test/specs/parse.js +++ b/test/specs/parse.js @@ -50,4 +50,21 @@ describe('parse()', () => { (() => parse(hex `F0 41 10 6A 12 00 00 00 01 11 F7`)).should.throw(/checksum/i); (() => parse(hex `F0 41 10 6A 11 01 02 03 04 00 00 00 00 12 F7`)).should.throw(/checksum/i); }); + it('should parse DT1 from Uint8Array', () => { + parse(new Uint8Array([0xF0, 0x41, 0x10, 0x6A, 0x12, 0x00, 0x00, 0x00, 0x01, 0x7F, 0xF7])) + .should.deep.equal([{ + type: 'sysex', + data: { + vendor: 'Roland', + deviceId: 0x10, + modelId: 0x6A, + command: { + type: 'DT1', + address: 0x000000001, + body: new Buffer([]), + checksum: 0x7f + } + } + }]); + }); });