1 //
2 // Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
3 // Creation Date: Thu Jul 22 18:59:27 PDT 2010
4 // Last Modified: Thu Jul 22 18:59:30 PDT 2010
5 // Filename:      ...sig/doc/examples/all/midiexcerpt/midiexcerpt.cpp
6 // Syntax:        C++
7 //
8 // Description:   Extracts a time region from a MIDI file.  Notes
9 //                starting before the start time will be ignored.
10 //                Notes not ending before the end time of the file
11 //                will be turned off at the given end time.
12 //
13 
14 #include "Options.h"
15 #include "MidiFile.h"
16 #include "PerlRegularExpression.h"
17 #include <stdlib.h>
18 
19 using namespace std;
20 using namespace smf;
21 
22 // function declarations:
23 void   checkOptions        (Options& opts);
24 void   example             (void);
25 void   usage               (const char* command);
26 double getTimeInSeconds    (const char* timestring);
27 void   extractMidi         (MidiFile& outputfile, MidiFile& inputfile,
28                             double starttime, double endtime);
29 int    getStartIndex       (MidiFile& midifile, int starttick);
30 int    getStopIndex        (MidiFile& midifile, int startindex, int stoptick);
31 
32 // user interface variables:
33 double starttime = 0.0;    // used with -s option
34 double endtime   = 0.0;    // used with -e option
35 
36 ///////////////////////////////////////////////////////////////////////////
37 
main(int argc,char ** argv)38 int main(int argc, char** argv) {
39    int       status;
40    MidiFile  inputfile;
41    MidiFile  outputfile;
42    Options   options(argc, argv);
43 
44    checkOptions(options);
45 
46    status = inputfile.read(options.getArg(1));
47    if (status == 0) {
48       cout << "Syntax error in file: " << options.getArg(1) << "\n";
49    }
50 
51    extractMidi(outputfile, inputfile, starttime, endtime);
52    outputfile.write(options.getArg(2));
53 
54    return 0;
55 }
56 
57 ///////////////////////////////////////////////////////////////////////////
58 
59 
60 //////////////////////////////
61 //
62 // extractMidi -- Extract a time range from a MIDI file.  If the
63 //      endtime is negative, then that means through the end of the
64 //      original MIDI file.
65 //
66 //
67 
extractMidi(MidiFile & outputfile,MidiFile & inputfile,double starttime,double endtime)68 void extractMidi(MidiFile& outputfile, MidiFile& inputfile, double starttime,
69      double endtime) {
70 
71    outputfile.absoluteTime();
72    outputfile.setTicksPerQuarterNote(inputfile.getTicksPerQuarterNote());
73    if (inputfile.getTrackCount() > 1) {
74       outputfile.addTrack(inputfile.getTrackCount()-1);
75    }
76    // outputfile.joinTracks();
77 
78    int i, j;
79 
80 
81    Array<Array<Array<int> > > notestates;
82    notestates.setSize(inputfile.getTrackCountAsType1());
83    for (i=0; i<notestates.getSize(); i++) {
84       notestates[i].setSize(16);
85       for (j=0; j<16; j++) {
86          notestates[i][j].setSize(128);
87          notestates[i][j].allowGrowth(0);
88          notestates[i][j].setAll(0);
89       }
90    }
91 
92    int offtype80 = 0;
93    int offtype90 = 0;
94 
95    int starttick = inputfile.getAbsoluteTickTime(starttime);
96    int stoptick  = inputfile.getAbsoluteTickTime(endtime);
97 
98    int startindex = getStartIndex(inputfile, starttick);
99    int stopindex  = getStopIndex(inputfile, startindex, stoptick);
100 
101    MFEvent eventcopy;
102    int track;
103    int pitch;
104    int channel = 0;
105 
106    // insert active tempo setting, if any
107    MFEvent *tempoptr = NULL;
108    for (i=0; i<startindex; i++) {
109       if (inputfile.getEvent(0, i).isTempo()) {
110          tempoptr = &inputfile.getEvent(0, i);
111       }
112    }
113    if (tempoptr != NULL) {
114       eventcopy = *tempoptr;
115       eventcopy.time = 0;
116       outputfile.addEvent(eventcopy);
117    }
118 
119    // insert active timbre settings, if any
120    Array<Array<int> > timbres;
121    timbres.setSize(notestates.getSize());
122    for (i=0; i<timbres.getSize(); i++) {
123       timbres[i].setSize(16);
124       timbres[i].setAll(-1);
125    }
126    for (i=0; i<startindex; i++) {
127       if (inputfile.isTimbre(0, i)) {
128          int tam = inputfile.getEvent(0, i).data[1];
129          track = inputfile.getTrack(0, i);
130          channel = inputfile.getChannelNibble(0, i);
131          timbres[track][channel] = tam;
132       }
133    }
134    eventcopy.data.setSize(2);
135    eventcopy.time = 0;
136    for (track=0; track<timbres.getSize(); track++) {
137       for (channel=0; channel<timbres.getSize(); channel++) {
138          if (timbres[track][channel] >= 0) {
139             eventcopy.track = track;
140             eventcopy.data[0] = 0xc0 | channel;
141             eventcopy.data[1] = timbres[track][channel];
142             outputfile.addEvent(eventcopy);
143          }
144       }
145    }
146 
147    MFEvent *ptr;
148    for (i=startindex; i<stopindex; i++) {
149       ptr = &inputfile.getEvent(0,i);
150       if (ptr->isNoteOff()) {
151          if (ptr->getCommandNibble() == 0x90) {
152             offtype90++;
153          } else if (ptr->getCommandNibble() == 0x80) {
154             offtype80++;
155          }
156 
157          track = ptr->track;
158          channel = ptr->getChannelNibble();
159          pitch = ptr->data[1];
160          if (notestates[track][channel][pitch] > 0) {
161             notestates[track][channel][pitch]--;
162          } else {
163             // ignore the note off, since it is from a note
164             // which was turned on before the selected time region.
165             continue;
166          }
167       } else if (ptr->isNoteOn()) {
168          track = ptr->track;
169          pitch = ptr->data[1];
170          notestates[track][channel][pitch]++;
171       }
172       eventcopy = *ptr;
173       eventcopy.time -= starttick;
174       outputfile.addEvent(eventcopy);
175    }
176 
177    // Turn off any notes which are still on...
178 
179    int k;
180    eventcopy.data.setSize(3);
181    eventcopy.time = stoptick - starttick;
182    for (track=0; track<notestates.getSize(); track++) {
183       for (channel=0; channel<16; channel++) {
184          for (pitch=0; pitch<128; pitch++) {
185             for (k=0; k<notestates[track][channel][pitch]; k++) {
186                eventcopy.track = track;
187                eventcopy.data[1] = pitch;
188                if (offtype90 > offtype80) {
189                   eventcopy.data[0] = (uchar)(0x90 | channel);
190                   eventcopy.data[0] = 0;
191                } else {
192                   eventcopy.data[0] = (uchar)(0x80 | channel);
193                   eventcopy.data[2] = 64;
194                }
195                outputfile.addEvent(eventcopy);
196             }
197          }
198       }
199    }
200 
201    outputfile.sortTracks();
202 }
203 
204 
205 
206 //////////////////////////////
207 //
208 // getStartIndex --
209 //
210 
getStartIndex(MidiFile & midifile,int starttick)211 int getStartIndex(MidiFile& midifile, int starttick) {
212    int i;
213    for (i=0; i<midifile.getNumEvents(0); i++) {
214       if (starttick <= midifile.getEvent(0,i).time) {
215          return i;
216       }
217    }
218 
219    // something bad happened
220    cerr << "ERROR in getStartIndex" << endl;
221    exit(1);
222 }
223 
224 
225 
226 //////////////////////////////
227 //
228 // getStopIndex --
229 //
230 
getStopIndex(MidiFile & midifile,int startindex,int stoptick)231 int getStopIndex(MidiFile& midifile, int startindex, int stoptick) {
232    int i;
233    for (i=startindex; i<midifile.getNumEvents(0); i++) {
234       if (stoptick <= midifile.getEvent(0,i).time) {
235          return i-1;
236       }
237    }
238 
239    // something bad happened
240    cerr << "ERROR in getStartIndex" << endl;
241    exit(1);
242 }
243 
244 
245 
246 //////////////////////////////
247 //
248 // checkOptions -- handle command-line options.
249 //
250 
checkOptions(Options & opts)251 void checkOptions(Options& opts) {
252    opts.define("begin|start|b|s=s:0", "Excerpt start time in sec or min:sec");
253    opts.define("duration|d=s:0", "Duration of the excerpt in sec or min:sec");
254    opts.define("end|e=s:-1", "Ending time of the excerpt in sec or min:sec");
255 
256    opts.define("author=b");
257    opts.define("version=b");
258    opts.define("example=b");
259    opts.define("help=b");
260    opts.process();
261 
262    if (opts.getBoolean("author")) {
263       cout << "Written by Craig Stuart Sapp, "
264            << "craig@ccrma.stanford.edu, July 2010" << endl;
265       exit(0);
266    }
267    if (opts.getBoolean("version")) {
268       cout << "midiextract version 1.0" << endl;
269       cout << "compiled: " << __DATE__ << endl;
270    }
271    if (opts.getBoolean("help")) {
272       usage(opts.getCommand());
273       exit(0);
274    }
275    if (opts.getBoolean("example")) {
276       example();
277       exit(0);
278    }
279 
280    // can only have one output filename
281    if (opts.getArgCount() != 2) {
282       cout << "Error: need one input MIDI file and an output filename.";
283       cout << endl;
284       usage(opts.getCommand());
285       exit(1);
286    }
287 
288    starttime = getTimeInSeconds(opts.getString("begin"));
289    if (opts.getBoolean("duration")) {
290       double duration = getTimeInSeconds(opts.getString("duration"));
291       if (duration <= 0.0) {
292           cerr << "ERROR: duration must be positive" << endl;
293           exit(1);
294       }
295       endtime = starttime + duration;
296    } else {
297       endtime = getTimeInSeconds(opts.getString("end"));
298    }
299 }
300 
301 
302 
303 //////////////////////////////
304 //
305 // getTimeInSeconds -- return the numeric value found in the string.
306 //    if the string contains a colon (:), then treate the number
307 //    on the left side of the colon as being in minutes, and the value
308 //    on the right as time in seconds.  Fractional values are allowed
309 //    on seconds.  Also allowed on minutes, but probably should not
310 //    be used...
311 //
312 
getTimeInSeconds(const char * timestring)313 double getTimeInSeconds(const char* timestring) {
314    PerlRegularExpression pre;
315    if (pre.search(timestring, ":", "")) {
316       double minutes = 0.0;
317       double seconds = 0.0;
318       if (pre.search(timestring, "([\\d\\.\\+-]+):", "")) {
319          minutes = strtod(pre.getSubmatch(1), NULL);
320       }
321       if (pre.search(timestring, ":([\\d\\.\\+-]+)", "")) {
322          seconds = strtod(pre.getSubmatch(1), NULL);
323       }
324       return minutes * 60.0 + seconds;
325    } else {
326       if (pre.search(timestring, "([\\d+.+-]+)", "")) {
327          return strtod(pre.getSubmatch(1), NULL);
328       } else {
329          return 0.0;
330       }
331    }
332 }
333 
334 
335 
336 //////////////////////////////
337 //
338 // example -- gives example calls to the midiexcerpt program.
339 //
340 
example(void)341 void example(void) {
342    cout <<
343    "# textmidi examples:                                                     \n"
344    << endl;
345 }
346 
347 
348 //////////////////////////////
349 //
350 // usage -- how to run the midiexcerpt program on the command line.
351 //
352 
usage(const char * command)353 void usage(const char* command) {
354    cout <<
355    "                                                                         \n"
356    << endl;
357 }
358 
359 
360 
361