Skip to content

Instantly share code, notes, and snippets.

@myu314
Last active March 12, 2018 09:54
Show Gist options
  • Save myu314/a3a374a7ad49d555adcb91b908c63661 to your computer and use it in GitHub Desktop.
Save myu314/a3a374a7ad49d555adcb91b908c63661 to your computer and use it in GitHub Desktop.
SMF読み込み
const TinySMF = (() => {
const Group = {
ChannelVoiceMessage : 0,
ChannelModeMessage : 1,
SystemCommonMessage : 2,
SystemRealtimeMessage : 3,
MetaEventMessage : 4
};
const Type = {
// Channel Voice Message
NoteOff : 0,
NoteOn : 1,
PolyKeyPressure : 2,
ControlChange : 3,
ProgramChange : 4,
ChannelPressure : 5,
PitchBend : 6,
// System Common Message
SystemExclusive : 7,
MTCQuaterFrame : 8,
SongPositionPointer : 9,
SongSelect : 10,
TuneRequest : 11,
SystemExclusive2 : 12,
// System Realtime Message
TimingClock : 13,
Play : 14,
Continue : 15,
Stop : 16,
ActiveSensisng : 17,
SystemReset : 18,
// Meta Event Message
SequenceNumber : 19,
TextEvent : 20,
CopyrightNotice : 21,
SequenceTrackName : 22,
InstrumentName : 23,
Lyric : 24,
Marker : 25,
CuePoint : 26,
MIDIChannelPrefix : 27,
MIDIPortPrefix : 28,
EndOfTrack : 29,
SetTempo : 30,
SMPTEOffset : 31,
TimeSignature : 32,
KeySignature : 33,
SequencerSpecificMetaEvent : 34,
UnknownMetaEvent : 35,
};
if (Object.freeze) {
Object.freeze(Group);
Object.freeze(Type);
}
function readSMF(source) {
"use strict";
const reader = new Reader(source);
let header;
let tracks = [];
while (reader.remains() >= 8) {
switch (reader.char(4)) {
case 'MThd':
header = readHeader(reader);
break;
case 'MTrk':
tracks.push(readTrack(reader));
break;
default:
reader.skip(reader.uint32());
break;
}
}
if (header === undefined) {
throw "Can't find header chunk.";
}
return { header, tracks };
}
function readHeader(reader) {
"use strict";
const chunkSize = reader.uint32();
if (chunkSize != 6) {
throw "Lenght of header chunk must be 6.";
}
const format = reader.uint16();
const numTracks = reader.uint16();
const division = reader.uint16();
if (division & 0x8000) {
const framerate = 256 - (division >> 8);
const ticksPerFrame = division & 0xff;
return { format, numTracks, division, framerate, ticksPerFrame };
}
return { format, numTracks, division };
}
function readTrack(reader) {
"use strict";
const chunkSize = reader.uint32();
const last = reader.index() + chunkSize;
const events = [];
let currentTime = 0;
let runningStatus = 0;
while (reader.index() < last) {
currentTime += reader.varlen();
const event = { time: currentTime };
events.push(event);
let status = reader.uint8();
if (status < 0x80) {
if (runningStatus < 0x80) {
throw "Can't find status byte.";
}
status = runningStatus;
reader.undo();
}
// Channel Voice/Mode Message
if (status <= 0xef) {
runningStatus = status;
event.group = Group.ChannelVoiceMessage
event.channel = status & 0x0f;
if (status <= 0x8f) {
event.type = Type.NoteOff;
event.key = reader.uint8();
event.velocity = reader.uint8();
} else if (status <= 0x9f) {
event.key = reader.uint8();
event.velocity = reader.uint8();
event.type = (event.velocity > 0) ? Type.NoteOn : Type.NoteOff;
} else if (status <= 0xaf) {
event.type = Type.PolyKeyPressure;
event.key = reader.uint8();
event.pressure = reader.uint8();
} else if (status <= 0xbf) {
event.type = Type.ControlChange;
event.number = reader.uint8();
event.value = reader.uint8();
if (event.number >= 0x78) {
event.group = Group.ChannelModeMessage;
}
} else if (status <= 0xcf) {
event.type = Type.ProgramChange;
event.program = reader.uint8();
} else if (status <= 0xdf) {
event.type = Type.ChannelPressure;
event.pressure = reader.uint8();
} else {
event.type = Type.PitchBend;
event.value = reader.uint14() - 8192;
}
continue;
}
// System Common Message
if (status <= 0xf7) {
runningStatus = 0;
event.group = Group.SystemCommonMessage;
if (status == 0xf0) {
event.type = Type.SystemExclusive;
event.data = reader.bytes(reader.varlen());
} else if (status == 0xf1) {
let v = reader.uint8();
event.type = Type.MTCQuaterFrame;
event.msgtype = v >> 4;
event.value = v & 0x0f;
} else if (status == 0xf2) {
event.type = Type.SongPositionPointer;
event.position = reader.uint14();
} else if (status == 0xf3) {
event.type = Type.SongSelect;
event.number = reader.uint8();
} else if (status <= 0xf5) {
throw "Undfined status found: 0x" + status.toString(16);
} else if (status == 0xf6) {
event.type = Type.TuneRequest;
} else {
event.type = Type.SystemExclusive2;
event.data = reader.bytes(reader.varlen());
}
continue;
}
// System Realtime Message
if (status <= 0xfe) {
event.group = Group.SystemRealtimeMessage;
switch (status) {
case 0xf8:
event.type = Type.TimingClock;
break;
case 0xfa:
event.type = Type.Play;
break;
case 0xfb:
event.type = Type.Continue;
break;
case 0xfc:
event.type = Type.Stop;
break;
case 0xfe:
event.type = Type.ActiveSensisng;
break;
case 0xff:
// never reached here.
event.type = Type.SystemReset;
break;
default:
throw "Undefined status found: 0x" + status.toString(16);
}
continue;
}
// Meta Event Message
const type = reader.uint8();
const raw = new Uint8Array(reader.bytes(reader.varlen()));
event.group = Group.MetaEventMessage;
event.data = raw.buffer;
switch (type) {
case 0x00:
if (raw.byteLength != 2) {
throw "Length of 'Sequence Number' must be 2";
}
event.type = Type.SequenceNumber;
event.number = raw[0];
break;
case 0x01:
event.type = Type.TextEvent;
break;
case 0x02:
event.type = Type.CopyrightNotice;
break;
case 0x03:
event.type = Type.SequenceTrackName;
break;
case 0x04:
event.type = Type.InstrumentName;
break;
case 0x05:
event.type = Type.Lyric;
break;
case 0x06:
event.type = Type.Marker;
break;
case 0x07:
event.type = Type.CuePoint;
break;
case 0x20:
if (raw.byteLength != 1) {
throw "Length of 'MIDI Channel Prefix' must be 1";
}
event.type = Type.MIDIChannelPrefix;
event.channel = raw[0];
break;
case 0x21:
if (raw.byteLength != 1) {
throw "Length of 'MIDI Port Prefix' must be 1";
}
event.type = Type.MIDIPortPrefix;
event.port = raw[0];
break;
case 0x2f:
if (raw.byteLength != 0) {
throw "Length of 'End of Track' must be 0";
}
event.type = Type.EndOfTrack;
break;
case 0x51:
if (raw.byteLength != 3) {
throw "Length of 'Set Tempo' must be 0";
}
event.type = Type.SetTempo;
event.microsec = (raw[0] << 16) | (raw[1] << 8) | raw[2];
event.bpm = 60000000 / event.microsec;
break;
case 0x54:
if (raw.byteLength != 5) {
throw "Length of 'SMPTE Offset' must be 0";
}
event.type = Type.SMPTEOffset;
event.hours = raw[0];
event.minutes = raw[1];
event.seconds = raw[2];
event.frames = raw[3];
event.subframes = raw[4];
break;
case 0x58:
if (raw.byteLength != 4) {
throw "Length of 'Time Signature' must be 4";
}
event.type = Type.TimeSignature;
event.numerator = raw[0];
event.denominator = 2 ** raw[1];
event.metronome = raw[2];
event.num32notes = raw[3];
break;
case 0x59:
if (raw.byteLength != 2) {
throw "Length of 'Key Signature' must be 4";
}
event.type = Type.KeySignature;
event.key = (raw[0] << 24) >> 24;
event.scale = raw[1];
break;
case 0x7f:
event.type = Type.SequencerSpecificMetaEvent;
break;
default:
event.type = Type.UnknownMetaEvent;
break;
}
}
return events;
}
class Reader {
constructor(buffer) {
this._buffer = buffer;
this._dataview = new DataView(buffer);
this._index = 0;
this._prev_index = null;
}
index() {
return this._index;
}
remains() {
return this._buffer.byteLength - this._index;
}
uint8() {
this._prev_index = this._index;
return this._dataview.getUint8(this._index++);
}
uint16() {
const v = this._dataview.getUint16(this._index);
this._prev_index = this._index;
this._index += 2;
return v;
}
uint32() {
const v = this._dataview.getUint32(this._index);
this._prev_index = this._index;
this._index += 4;
return v;
}
uint14() {
const v = this._dataview.getUint16(this._index, true);
this._prev_index = this._index;
this._index += 2;
return ((v & 0x7f00) >> 1) | (v & 0x7f);
}
varlen() {
this._prev = this._index;
let value = 0;
let t;
do {
t = this._dataview.getUint8(this._index++);
value = (value << 7) | (t & 0x7f);
} while (t > 0x7f);
return value;
}
bytes(len) {
const v = this._buffer.slice(this._index, this._index + len);
this._prev_index = this._index;
this._index += len;
return v;
}
char(len) {
const b = new Uint8Array(this._buffer, this._index, len)
this._prev_index = this._index;
this._index += len;
return b.reduce((t, c) => t + String.fromCharCode(c), '');
}
skip(len) {
this._prev_index = this._index;
this._index += len;
}
undo() {
if (this._prev_index === null) {
return;
}
this._index = this._prev_index;
this._prev = null;
}
};
return {
Group,
Type,
read: readSMF
}
})();
function eventToStr(evt) {
"use strict";
function fmtKey(k) {
const kt = [
'C ','C#','D ','D#','E ','F ','F#','G ','G#','A ','A#','B '
];
return kt[k % 12] + (' ' + ((k / 12 |0) -1)).slice(-2);
}
function fmtV3(v) {
return (' ' + v).slice(-3);
}
function fmtData(x) {
const u8 = new Uint8Array(x);
let s = '';
for (let i = 0, e = Math.min(u8.byteLength, 8); i < e; i++) {
s += ' ' + ('0' + u8[i]).slice(-2);
}
if (u8.byteLength > 8)
s += ' ...';
return s.slice(1);
}
function fmtStr(x) {
const u8 = new Uint8Array(x);
let s = u8.reduce((s, c) => s + String.fromCharCode(c), '');
if (s.length > 24)
s = s.slice(0, 24) + ' ...';
return s;
}
const Group = TinySMF.Group;
const Type = TinySMF.Type;
let tm = '' + evt.time;
if (tm.length < 6) tm = (' ' + tm).slice(-6);
let gr;
switch (evt.group) {
case Group.ChannelVoiceMessage:
gr = 'Chan.Voice'; break;
case Group.ChannelModeMessage:
gr = 'Chan.Mode '; break;
case Group.SystemCommonMessage:
gr = 'Sys.Common'; break;
case Group.SystemRealtimeMessage:
gr = 'Sys.RTime '; break;
case Group.MetaEventMessage:
gr = 'Meta Event'; break;
default:
gr = 'UNKNOWN '; break;
}
let ch = (evt.channel !== undefined) ?
(`Ch:${(' ' + evt.channel).slice(-2)}`) : '';
let tp, vl = '';
switch (evt.type) {
case Type.NoteOff:
tp = 'Note Off';
vl = `${ch}, Key:${fmtKey(evt.key)}, Vel:${fmtV3(evt.velocity)}`;
break;
case Type.NoteOn:
tp = 'Note On ';
vl = `${ch}, Key:${fmtKey(evt.key)}, Vel:${fmtV3(evt.velocity)}`;
break;
case Type.PolyKeyPressure:
tp = 'PolyPres';
vl = `${ch}, Key:${fmtKey(evt.key)}, Prs:${fmtV3(evt.pressure)}`;
break;
case Type.ControlChange:
tp = 'Control ';
vl = `${ch}, Num:${fmtV3(evt.number)}, Val:${fmtV3(evt.value)}`;
break;
case Type.ProgramChange:
tp = 'Program ';
vl = `${ch}, Prg:${fmtV3(evt.program)}`;
break;
case Type.ChannelPressure:
tp = 'ChanPres';
vl = `${ch}, Prs:${fmtV3(evt.pressure)}`;
break;
case Type.PitchBend:
tp = 'P.Bend ';
vl = `${ch}, Val:${evt.value}`;
break;
case Type.SystemExclusive:
tp = 'SysEx ';
vl = `${fmtData(evt.data)}`;
break;
case Type.MTCQuaterFrame:
tp = 'MTCQFrm ';
vl = `Type:${evt.msgtype}, val:${evt.value}`
break
case Type.SongPositionPointer:
tp = 'SongPos ';
vl = `Pos:${evt.position}`;
break
case Type.SongSelect:
tp = 'SongSel ';
vl = `Num:${evt.number}`;
break
case Type.TuneRequest:
tp = 'TuneReq ';
break
case Type.SystemExclusive2:
tp = 'SysEx2 ';
vl = `${fmtData(evt.data)}`;
break;
case Type.TimingClock:
tp = 'Timing.C';
break;
case Type.Play:
tp = 'Play ';
break;
case Type.Continue:
tp = 'Continue';
break;
case Type.Stop:
tp = 'Stop';
break;
case Type.ActiveSensisng:
tp = 'Active.S';
break;
case Type.SystemReset:
tp = 'SysReset';
break;
case Type.SequenceNumber:
tp = 'SeqNum ';
vl = `Num:${evt.number}`;
break;
case Type.TextEvent:
tp = 'TxtEvent';
vl = `${fmtStr(evt.data)}`;
break;
case Type.CopyrightNotice:
tp = 'Copyrite';
vl = `${fmtStr(evt.data)}`;
break;
case Type.SequenceTrackName:
tp = 'Trk.Name';
vl = `${fmtStr(evt.data)}`;
break;
case Type.InstrumentName:
tp = 'InstName';
vl = `${fmtStr(evt.data)}`;
break;
case Type.Lyric:
tp = 'Lyric ';
vl = `${fmtStr(evt.data)}`;
break;
case Type.Marker:
tp = 'Marker ';
vl = `${fmtStr(evt.data)}`;
break;
case Type.CuePoint:
tp = 'CuePoint';
vl = `${fmtStr(evt.data)}`;
break;
case Type.MIDIChannelPrefix:
tp = 'Chan.Pre';
vl = `Ch:${evt.channel}`;
break;
case Type.MIDIPortPrefix:
tp = 'Port.Pre';
vl = `Port:${evt.channel}`;
break;
case Type.EndOfTrack:
tp = 'EndOfTrk';
break;
case Type.SetTempo:
tp = 'SetTempo';
vl = `BPM:${evt.bpm |0}, ${evt.microsec} usec/QNote`;
break;
case Type.SMPTEOffset:
tp = 'SMPTEOfs';
vl = `${fmtData(evt.data)}`;
break;
case Type.TimeSignature:
tp = 'TimeSig.';
vl = `${evt.numerator} / ${evt.denominator}, Met:${evt.metronome}, n32:${evt.num32notes}`;
break;
case Type.KeySignature:
tp = 'KeySig. ';
vl = `${(evt.key < 0) ? 'b' : '#'}${Math.abs(evt.key)} ${evt.scale ? 'minor' : 'major'}`;
break;
case Type.SequencerSpecificMetaEvent:
tp = 'Seq.Meta';
vl = `${fmtData(evt.data)}`;
break;
case Type.UnknownMetaEvent:
tp = '?MetaEvt';
vl = `${fmtData(evt.data)}`;
break;
default:
tp = ' ';
}
return `time:${tm} / ${gr} / [${tp}] ${vl}`;
}
function disp(header, tracks) {
let s = `*** ${filename} ***
format : ${header.format}
tracks : ${header.numTracks}
division : ${header.division}\n`;
if (header.division & 0x8000) {
s += `framerate : ${header.framerate}
ticks/frm : ${header.ticksPerFrame}\n`;
}
for (let [i, tr] of tracks.entries()) {
s += `\n[ channel: ${i} ]\n`;
for (let e of tr) s += eventToStr(e) + '\n';
s += '\n';
}
console.log(s);
}
const fs = require('fs');
const filename = process.argv[2];
const bufU8 = new Uint8Array(fs.readFileSync(filename));
const { header, tracks } = TinySMF.read(bufU8.buffer);
disp(header, tracks);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment