mirror of
https://github.com/bvanroll/rpiRadio.git
synced 2025-09-03 14:32:46 +00:00
346 lines
8.2 KiB
JavaScript
346 lines
8.2 KiB
JavaScript
'use strict';
|
|
|
|
class MIDIStream {
|
|
constructor(buffer) {
|
|
this.data = new Uint8Array(buffer);
|
|
this.byteOffset = 0;
|
|
this.lastEventTypeByte = 0x00;
|
|
}
|
|
|
|
readString(byteLength) {
|
|
var byteOffset = this.byteOffset;
|
|
|
|
for (var i = 0, str = ''; i < byteLength; i++) {
|
|
str += String.fromCharCode(this.data[byteOffset + i]);
|
|
}
|
|
|
|
this.byteOffset += byteLength;
|
|
|
|
return str;
|
|
}
|
|
|
|
readUint32() {
|
|
var byteOffset = this.byteOffset;
|
|
var value = (
|
|
(this.data[byteOffset ] << 24) |
|
|
(this.data[byteOffset + 1] << 16) |
|
|
(this.data[byteOffset + 2] << 8) |
|
|
(this.data[byteOffset + 3] )
|
|
);
|
|
|
|
this.byteOffset += 4;
|
|
|
|
return value;
|
|
}
|
|
|
|
readUint24() {
|
|
var byteOffset = this.byteOffset;
|
|
var value = (
|
|
(this.data[byteOffset ] << 16) |
|
|
(this.data[byteOffset + 1] << 8) |
|
|
(this.data[byteOffset + 2] )
|
|
);
|
|
|
|
this.byteOffset += 3;
|
|
|
|
return value;
|
|
}
|
|
|
|
readUint16() {
|
|
var byteOffset = this.byteOffset;
|
|
var value = (
|
|
(this.data[byteOffset ] << 8) |
|
|
(this.data[byteOffset + 1] )
|
|
);
|
|
|
|
this.byteOffset += 2;
|
|
|
|
return value;
|
|
}
|
|
|
|
readUint8() {
|
|
var byteOffset = this.byteOffset;
|
|
var value = this.data[byteOffset];
|
|
|
|
this.byteOffset += 1;
|
|
|
|
return value;
|
|
}
|
|
|
|
readInt8() {
|
|
var byteOffset = this.byteOffset;
|
|
var value = this.data[byteOffset];
|
|
|
|
if (value & 0x80 === 0x80) {
|
|
value ^= 0xFFFFFF00;
|
|
}
|
|
|
|
this.byteOffset += 1;
|
|
|
|
return value;
|
|
}
|
|
|
|
readVarUint() {
|
|
var value = 0;
|
|
var uint8;
|
|
|
|
do {
|
|
uint8 = this.readUint8();
|
|
value = (value << 7) + (uint8 & 0x7F);
|
|
} while ((uint8 & 0x80) === 0x80);
|
|
|
|
return value;
|
|
}
|
|
|
|
skip(byteLength) {
|
|
this.byteOffset += byteLength;
|
|
}
|
|
|
|
readChunk() {
|
|
var id = this.readString(4);
|
|
var length = this.readUint32();
|
|
var byteOffset = this.byteOffset;
|
|
|
|
this.byteOffset += length;
|
|
|
|
var data = this.data.slice(byteOffset, this.byteOffset);
|
|
|
|
return {
|
|
id: id,
|
|
length: length,
|
|
data: data.buffer
|
|
};
|
|
}
|
|
|
|
readEvent() {
|
|
var event = {};
|
|
|
|
event.delta = this.readVarUint();
|
|
|
|
var eventTypeByte = this.readUint8();
|
|
|
|
// system event
|
|
if ((eventTypeByte & 0xF0) === 0xF0) {
|
|
switch (eventTypeByte) {
|
|
// meta event
|
|
case 0xFF:
|
|
event.type = 'meta';
|
|
|
|
var subTypeByte = this.readUint8();
|
|
var length = this.readVarUint();
|
|
|
|
switch (subTypeByte) {
|
|
case 0x00:
|
|
event.subType = 'sequenceNumber';
|
|
if (length === 2)
|
|
event.value = this.readUint16();
|
|
else
|
|
this.skip(length);
|
|
break;
|
|
case 0x01:
|
|
event.subType = 'text';
|
|
event.value = this.readString(length);
|
|
break;
|
|
case 0x02:
|
|
event.subType = 'copyrightNotice';
|
|
event.value = this.readString(length);
|
|
break;
|
|
case 0x03:
|
|
event.subType = 'trackName';
|
|
event.value = this.readString(length);
|
|
break;
|
|
case 0x04:
|
|
event.subType = 'instrumentName';
|
|
event.value = this.readString(length);
|
|
break;
|
|
case 0x05:
|
|
event.subType = 'lyrics';
|
|
event.value = this.readString(length);
|
|
break;
|
|
case 0x06:
|
|
event.subType = 'marker';
|
|
event.value = this.readString(length);
|
|
break;
|
|
case 0x07:
|
|
event.subType = 'cuePoint';
|
|
event.value = this.readString(length);
|
|
break;
|
|
case 0x20:
|
|
event.subType = 'midiChannelPrefix';
|
|
if (length === 1)
|
|
event.value = this.readUint8();
|
|
else
|
|
this.skip(length);
|
|
break;
|
|
case 0x2F:
|
|
event.subType = 'endOfTrack';
|
|
if (length > 0)
|
|
this.skip(length);
|
|
break;
|
|
case 0x51:
|
|
event.subType = 'setTempo';
|
|
if (length === 3)
|
|
event.value = this.readUint24();
|
|
else
|
|
this.skip(length)
|
|
break;
|
|
case 0x54:
|
|
event.subType = 'smpteOffset';
|
|
if (length === 5) {
|
|
var hourByte = this.readUint8();
|
|
event.value = {
|
|
frameRate: ({
|
|
0x00: 24,
|
|
0x01: 25,
|
|
0x02: 29.97,
|
|
0x03: 30
|
|
}[hourByte >>> 6]),
|
|
hour: (hourByte & 0x3F),
|
|
minute: this.readUint8(),
|
|
second: this.readUint8(),
|
|
frame: this.readUint8(),
|
|
subFrame: this.readUint8()
|
|
};
|
|
} else {
|
|
this.skip(length);
|
|
}
|
|
break;
|
|
case 0x58:
|
|
event.subType = 'timeSignature';
|
|
if (length === 4) {
|
|
event.value = {
|
|
numerator: this.readUint8(),
|
|
denominator: 1 << this.readUint8(),
|
|
metronome: this.readUint8(),
|
|
thirtyseconds: this.readUint8()
|
|
};
|
|
} else {
|
|
this.skip(length);
|
|
}
|
|
break;
|
|
case 0x59:
|
|
event.subType = 'keySignature';
|
|
if (length === 2) {
|
|
event.value = {
|
|
key: this.readInt8(),
|
|
scale: this.readUint8()
|
|
};
|
|
} else {
|
|
this.skip(length);
|
|
}
|
|
break;
|
|
case 0x7F:
|
|
event.subType = 'sequencerSpecific';
|
|
event.value = this.readString(length);
|
|
break;
|
|
default:
|
|
event.subType = 'unknown';
|
|
event.value = this.readString(length);
|
|
}
|
|
break;
|
|
// sysex event
|
|
case 0xF0:
|
|
event.type = 'sysEx';
|
|
|
|
var length = this.readVarUint();
|
|
|
|
event.value = this.readString(length);
|
|
|
|
break;
|
|
case 0xF7:
|
|
event.type = 'dividedSysEx';
|
|
|
|
var length = this.readVarUint();
|
|
|
|
event.value = this.readString(length);
|
|
|
|
break;
|
|
default:
|
|
event.type = 'unknown';
|
|
|
|
var length = this.readVarUint();
|
|
|
|
event.value = this.readString(length);
|
|
}
|
|
// channel event
|
|
} else {
|
|
var param;
|
|
|
|
// if the high bit is low
|
|
// use running event type mode
|
|
if ((eventTypeByte & 0x80) === 0x00) {
|
|
param = eventTypeByte;
|
|
eventTypeByte = this.lastEventTypeByte;
|
|
} else {
|
|
param = this.readUint8();
|
|
this.lastEventTypeByte = eventTypeByte;
|
|
}
|
|
|
|
var eventType = eventTypeByte >> 4;
|
|
|
|
event.channel = eventTypeByte & 0x0F;
|
|
event.type = 'channel';
|
|
|
|
switch (eventType) {
|
|
case 0x08:
|
|
event.subType = 'noteOff';
|
|
|
|
event.value = {
|
|
noteNumber: param,
|
|
velocity: this.readUint8()
|
|
};
|
|
break;
|
|
case 0x09:
|
|
event.value = {
|
|
noteNumber: param,
|
|
velocity: this.readUint8()
|
|
};
|
|
|
|
// some midi implementations use a noteOn
|
|
// event with 0 velocity to denote noteOff
|
|
if (event.value.velocity === 0) {
|
|
event.subType = 'noteOff';
|
|
} else {
|
|
event.subType = 'noteOn';
|
|
}
|
|
break;
|
|
case 0x0A:
|
|
event.subType = 'noteAftertouch';
|
|
|
|
event.value = {
|
|
noteNumber: param,
|
|
amount: this.readUint8()
|
|
};
|
|
break;
|
|
case 0x0B:
|
|
event.subType = 'controller';
|
|
|
|
event.value = {
|
|
controllerNumber: param,
|
|
controllerValue: this.readUint8()
|
|
};
|
|
break;
|
|
case 0x0C:
|
|
event.subType = 'programChange';
|
|
event.value = param;
|
|
break;
|
|
case 0x0D:
|
|
event.subType = 'channelAftertouch';
|
|
event.value = param;
|
|
break;
|
|
case 0x0E:
|
|
event.subType = 'pitchBend';
|
|
event.value = param + (this.readUint8() << 7);
|
|
break;
|
|
default:
|
|
event.subType = 'unknown';
|
|
event.value = (param << 8) + this.readUint8();
|
|
}
|
|
}
|
|
|
|
return event;
|
|
}
|
|
};
|
|
|
|
module.exports = MIDIStream;
|