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