1 //
2 // Programmer: Craig Stuart Sapp <craig@ccrma.stanford.edu>
3 // Creation Date: Mon Apr 6 13:54:09 PDT 2009
4 // Last Modified: Mon Apr 6 13:54:12 PDT 2009
5 // Filename: peep2midi.cpp
6 // Syntax: C++; museinfo
7 //
8 // Description: Convert Performance Expression Extraction Program
9 // output data into MIDI data.
10 //
11
12 #include "MidiFile.h"
13 #include "humdrum.h"
14 #include "Options.h"
15
16 #include <ctype.h>
17 #include <string.h>
18 #include <stdio.h>
19
20 using namespace std;
21 using namespace smf;
22
23 // user interface variables
24 Options options;
25 int debugQ = 0; // use with --debug option
26 int maxcount = 100000;
27 int mindyn = 30; // use with -r option
28 int maxdyn = 120; // use with -r option
29 double duration = 0.1; // use with -d option
30 const char* filename = ""; // use with -o option
31
32
33 // function declarations:
34 void checkOptions (Options& opts, int argc, char** argv);
35 void createMidiFile (MidiFile& midifile, HumdrumFile& infile);
36 int getMIDIKeyNum (const char* string);
37 int getTrackNumber (const char* string);
38 void example (void);
39 void usage (const char* command);
40
41 //////////////////////////////////////////////////////////////////////////
42
main(int argc,char * argv[])43 int main(int argc, char* argv[]) {
44 checkOptions(options, argc, argv);
45 HumdrumFile infile;
46 infile.read(options.getArg(1));
47 MidiFile midifile;
48 createMidiFile(midifile, infile);
49
50 if (strcmp(filename, "") == 0) {
51 cout << midifile;
52 } else {
53 midifile.write(filename);
54 }
55
56 return 0;
57 }
58
59 //////////////////////////////////////////////////////////////////////////
60
61
62 //////////////////////////////
63 //
64 // createMidiFile --
65 // input humdrum file data should be in simple columns in this order:
66 //
67 // **data **data **data **data **data
68 // 1.667 18.8 F3 -2 1
69 // 1.666 19 C4 -1 1
70 // 1.667 10.7 a4 -2 1
71 // 1.667 19.2 c5 -2 1
72 // 2.009 24 F3 +10 1
73 // 2.01 24.2 C4 +10 1
74 //
75 // Column one is the time in seconds at which the note is started
76 // Column two is a dynamic value (the range of which will be normalized)
77 // Column three contains the pitch information.
78 //
79
createMidiFile(MidiFile & midifile,HumdrumFile & infile)80 void createMidiFile(MidiFile& midifile, HumdrumFile& infile) {
81
82 Array<int> millitimes;
83 Array<double> velocities;
84 Array<int> keynum;
85 Array<int> track;
86
87 millitimes.setSize(infile.getNumLines());
88 velocities.setSize(infile.getNumLines());
89 keynum.setSize(infile.getNumLines());
90 track.setSize(infile.getNumLines());
91
92 millitimes.setSize(0);
93 velocities.setSize(0);
94 keynum.setSize(0);
95 track.setSize(0);
96
97 int intval;
98 double floatval;
99 double dmax = -100000;
100 double dmin = +100000;
101
102 int i;
103 for (i=0; i<infile.getNumLines(); i++) {
104 if (!infile[i].isData()) {
105 continue;
106 }
107 sscanf(infile[i][0], "%lf", &floatval);
108 intval = int(floatval * 1000.0 + 0.5);
109 millitimes.append(intval);
110
111 sscanf(infile[i][1], "%lf", &floatval);
112 velocities.append(floatval);
113 if (floatval < dmin) { dmin = floatval; }
114 if (floatval > dmax) { dmax = floatval; }
115
116 intval = getMIDIKeyNum(infile[i][2]);
117 keynum.append(intval);
118
119 intval = getTrackNumber(infile[i][2]);
120 track.append(intval);
121 }
122 millitimes.allowGrowth(0);
123 velocities.allowGrowth(0);
124 keynum.allowGrowth(0);
125 track.allowGrowth(0);
126
127 // normalize the dynamics data into the range from 0 to 1
128 double diff = dmax - dmin;
129 for (i=0; i<velocities.getSize(); i++) {
130 if (diff > 0.0) {
131 velocities[i] = (velocities[i] - dmin) / diff;
132 } else {
133 velocities[i] = 0.5;
134 }
135 }
136
137
138 // now ready to write the data to the MIDI file:
139
140 midifile.setMillisecondDelta(); // SMPTE 25 frames & 40 subframes
141 midifile.absoluteTime(); // Time values inserted are absolute
142 midifile.addTrack(2); // Right and Left hands
143
144 Array<uchar> event;
145 event.setSize(3);
146
147 int intdur = int(duration * 1000.0 + 0.5);
148 int lasttime = 0;
149 int dyndiff = maxdyn - mindyn;
150 int vel;
151 for (i=0; i<millitimes.getSize(); i++) {
152 if ((keynum[i] <= 10) || (keynum[i] > 127)) {
153 continue;
154 }
155 vel = int(velocities[i] * dyndiff + mindyn + 0.5);
156 if (vel < 1) { vel = 1; }
157 if (vel > 127) { vel = 127; }
158
159 event[0] = 0x90; // note-on
160 event[1] = keynum[i];
161 event[2] = vel;
162 midifile.addEvent(track[i], millitimes[i], event);
163 event[2] = 0;
164 lasttime = millitimes[i] + intdur;
165 midifile.addEvent(track[i], lasttime, event);
166 }
167
168 // write the end of track marker
169 event[0] = 0xff;
170 event[1] = 0x2f;
171 event[2] = 0;
172 for (i=0; i<midifile.getTrackCount(); i++) {
173
174 if (i>0) {
175 // have to lengthen the last note in track due to bugs
176 // in various MIDI playback programs which clip
177 // the last chord of a file
178 midifile.getEvent(i, midifile.getNumEvents(i)-1).time += 1500;
179 }
180 midifile.addEvent(i, lasttime+2000, event);
181 }
182
183 // add comments from header
184 for (i=0; i<infile.getNumLines() && i<lasttime; i++) {
185 if (infile[i].isBibliographic() || infile[i].isGlobalComment()) {
186 // 0x01 is a text event
187 midifile.addMetaEvent(0, i, 0x01, infile[i].getLine());
188 }
189 }
190
191 // sort the ontimes and offtimes so they are in correct time order:
192 midifile.sortTracks();
193
194 }
195
196
197
198 //////////////////////////////
199 //
200 // getTrackNumber -- lowercase pitch name = right hand; uppercase = left hand
201 //
202
getTrackNumber(const char * string)203 int getTrackNumber(const char* string) {
204 if (islower(string[0])) {
205 return 1;
206 } else {
207 return 2;
208 }
209 }
210
211
212
213 //////////////////////////////
214 //
215 // getMIDIKeyNum --
216 //
217
getMIDIKeyNum(const char * string)218 int getMIDIKeyNum(const char* string) {
219 int accid = 0;
220 int octave = -1;
221 int len = strlen(string);
222 int diatonic = -1;
223
224 switch (tolower(string[0])) {
225 case 'c': diatonic = 0; break;
226 case 'd': diatonic = 2; break;
227 case 'e': diatonic = 4; break;
228 case 'f': diatonic = 5; break;
229 case 'g': diatonic = 7; break;
230 case 'a': diatonic = 9; break;
231 case 'b': diatonic = 11; break;
232 }
233
234 if (diatonic < 0) {
235 return -1;
236 }
237
238 int i;
239 for (i=0; i<len; i++) {
240 if (string[i] == '#') {
241 accid++;
242 } else if (string[i] == '-') {
243 accid--;
244 }
245 if (isdigit(string[i]) && (octave < 0)) {
246 sscanf(&(string[i]), "%d", &octave);
247 }
248 }
249
250 if (octave < 0) {
251 return -1;
252 }
253
254 return diatonic + octave * 12 + accid;
255 }
256
257
258
259 //////////////////////////////
260 //
261 // checkOptions --
262 //
263
checkOptions(Options & opts,int argc,char * argv[])264 void checkOptions(Options& opts, int argc, char* argv[]) {
265 opts.define("o|output=s", "output midi file name");
266 opts.define("r|range=s", "dynamics range");
267
268 opts.define("author=b", "author of program");
269 opts.define("version=b", "compilation info");
270 opts.define("example=b", "example usages");
271 opts.define("h|help=b", "short description");
272
273 opts.process(argc, argv);
274
275 // handle basic options:
276 if (opts.getBoolean("author")) {
277 cout << "Written by Craig Stuart Sapp, "
278 << "craig@ccrma.stanford.edu, 22 Jan 2002" << endl;
279 exit(0);
280 } else if (opts.getBoolean("version")) {
281 cout << argv[0] << ", version: 22 Jan 2002" << endl;
282 cout << "compiled: " << __DATE__ << endl;
283 exit(0);
284 } else if (opts.getBoolean("help")) {
285 usage(opts.getCommand());
286 exit(0);
287 } else if (opts.getBoolean("example")) {
288 example();
289 exit(0);
290 }
291
292 if (opts.getArgCount() != 1) {
293 usage(opts.getCommand());
294 exit(1);
295 }
296
297 filename = opts.getString("output");
298 if (opts.getBoolean("range")) {
299 int count = sscanf(opts.getString("range"), "%d-%d", &mindyn, &maxdyn);
300 if (count != 2) {
301 count = sscanf(opts.getString("range"), "%d:%d", &mindyn, &maxdyn);
302 }
303 if (count != 2) {
304 count = sscanf(opts.getString("range"), "%d,%d", &mindyn, &maxdyn);
305 }
306 if (count != 2) {
307 count = sscanf(opts.getString("range"), "%d, %d", &mindyn, &maxdyn);
308 }
309 if (count != 2) {
310 /* count = */ sscanf(opts.getString("range"), "%d %d", &mindyn, &maxdyn);
311 }
312 }
313
314 if (mindyn > maxdyn) {
315 int temp = mindyn;
316 mindyn = maxdyn;
317 maxdyn = temp;
318 }
319
320 if (mindyn == maxdyn) {
321 mindyn = 20;
322 maxdyn = 120;
323 }
324
325 }
326
327
328
329 //////////////////////////////
330 //
331 // example --
332 //
333
example(void)334 void example(void) {
335
336 }
337
338
339
340 //////////////////////////////
341 //
342 // usage --
343 //
344
usage(const char * command)345 void usage(const char* command) {
346 cout << "Usage: " << command << " midifile" << endl;
347 }
348
349
350
351