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