-
Notifications
You must be signed in to change notification settings - Fork 1
/
smf2seq.js
152 lines (132 loc) · 3.81 KB
/
smf2seq.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
* SMF to SEQ
*
* Convert Standard MIDI file to event array
* Output file as sequence.h: to be compiled with ino files
*
* This program uses part of jasmid to decode and parse .mid files
* Please refer to jasmid/LICENSE for the jasmid license
*
* Run this file with node.js
* $ node smf2seq.js [SMF filename]
*
* 2016 by ilufang
*/
Midifile = require("./jasmid/midifile.js");
fs = require("fs");
// # Read and parse SMF
var filename = process.argv[2]?process.argv[2]:"song.mid";
var midi_blob = fs.readFileSync(filename);
var t = "";
for (var i=0; i<midi_blob.length; i++) {
t += String.fromCharCode(midi_blob[i] & 255);
}
try {
midi = Midifile(t);
} catch(e) {
console.error("Could not parse midi.");
process.exit(1);
}
if (midi.header.formatType != 1) {
console.error("MIDI Type not supported. Expect 1, got "+midi.header.formatType);
process.exit(1);
}
// fs.writeFileSync("midi.json",JSON.stringify(midi,null,'\t'));
// process.exit();
// # Process events
var ptr = []; // iteration pointer per track
seq = []; // merged result
// convert delta time to accumulative time
for (var t in midi.tracks) {
ptr.push(0); // Irrelevant to the calculation, just initialize ptr
var tick = 0;
var width = 0, maxwidth = 0;
for (var i in midi.tracks[t]) {
tick += midi.tracks[t][i].deltaTime;
midi.tracks[t][i].time = tick;
midi.tracks[t][i].track = parseInt(t); // Still need to keep track of the track
}
}
// Merge the tracks
var time = 0;
while(true) {
// 'Pop' front
var minTrack = 0;
for (var t in midi.tracks) {
if (!midi.tracks[t][ptr[t]]) {
if (minTrack == t) {
// In case a prior tracks are shorter, assign the default minTrack to a latter one
minTrack++;
}
continue;
}
if (midi.tracks[t][ptr[t]].time < midi.tracks[minTrack][ptr[minTrack]].time) { minTrack = t; }
}
if (minTrack==midi.tracks.length) {
// No tracks have events left
break;
}
seq.push(midi.tracks[minTrack][ptr[minTrack]]);
ptr[minTrack]++;
}
// Scan for consecutive notes
/*
time = 0;
var notes_off = {};
for (var i = 0; i < seq.length; i++) {
if (seq[i].time != time) {
time = seq[i].time;
notes_off = {};
}
if (seq[i].type=="channel") {
if (seq[i].subtype=="noteOff") {
notes_off[seq[i].noteNumber] = i;
} else if (seq[i].subtype=="noteOn") {
if (notes_off[seq[i].noteNumber]) {
seq[notes_off[seq[i].noteNumber]].time -= 20;
}
}
}
}
*/
// Regenerate deltatime
var tempo = 0;
var notes = [], note_params = [];
for (var i=0; i<seq.length; i++) {
if (seq[i+1]) {
seq[i].deltaTime = seq[i+1].time - seq[i].time;
} else {
seq[i].deltatime = 0;
}
prevTime = seq[i].time;
if (seq[i].type=="meta" && seq[i].subtype =="setTempo") {
tempo = 1.0*seq[i].microsecondsPerBeat/1000/midi.header.ticksPerBeat;
} else if (seq[i].type=="channel") {
if (seq[i].subtype=="noteOff") {
seq[i].velocity = 0;
}
if (seq[i].subtype=="noteOn" || seq[i].subtype=="noteOff") {
seq[i].velocity*=128/96;
if (seq[i].velocity > 127)
seq[i].velocity = 127;
notes.push(seq[i].noteNumber);
note_params.push((seq[i].deltaTime<<4)+(seq[i].velocity>>3));
}
}
}
fs.writeFileSync("midi.json", JSON.stringify(seq, null, '\t'));
// # Generate sin table
var file = "// MIDI events\n// Generated by smf2seq.js\n";
var sine_sample = [], sine_sample_size = 256;
for (var i = 0; i < sine_sample_size; i++) {
sine_sample.push(128+Math.round(128*Math.sin(2*Math.PI*i/sine_sample_size)));
}
file += "const unsigned char sine[] = {"+sine_sample.join(",")+"};\n";
file += "#define TEMPO "+tempo+"\n";
file += "#define SONG_LEN "+notes.length+"\n";
file += "PROGMEM const unsigned char notes[] = {"+notes.join(",")+"};\n";
file += "PROGMEM const int params[] = {"+note_params.join(",")+"};\n";
console.log("Using memory: "+(notes.length*3));
console.log("Total memory: 32256");
// # Write to file
fs.writeFileSync("sequence.h",file);