-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsmf2seq.js
167 lines (145 loc) · 4.26 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/*
* 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));
}
}
}
// convert notes into note and octave
// 0xF0 = octave
// 0x0F = note
var singlenote = 0, signleoctave = 0;
for (var i in notes) {
singlenote = ((notes[i]+4)%12)&0x0F;
signleoctave = ((notes[i]+4)/12)&0x0F;
notes[i] = (signleoctave<<4) | singlenote;
}
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; // UNO
//var sine_sample = [], sine_sample_size = 4096; // ATMEGA2560
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 uint8_t sine[] = {"+sine_sample.join(",")+"};\n";
file += "#define SINE_SAMPLE_SIZE "+sine_sample_size+"\n";
file += "#define TRACKS "+minTrack+"\n";
file += "#define TEMPO "+tempo+"\n";
file += "#define SONG_LEN "+notes.length+"\n";
file += "PROGMEM const uint8_t notes[] = {"+notes.join(",")+"};\n";
file += "PROGMEM const uint16_t params[] = {"+note_params.join(",")+"};\n";
console.log("Using tracks: "+(minTrack));
console.log("Using memory: "+(notes.length*3));
console.log("Total memory: 32256");
// # Write to file
fs.writeFileSync("sequence.h",file);