1 /***************************************************/
2 /*
3   Simple realtime MIDI to SKINI parser.
4 
5   This object takes MIDI from the input stream
6   (via the RtMidi class), parses it, and turns it
7   into SKINI messages.
8 
9   by Perry R. Cook and Gary P. Scavone, 1995 - 2004.
10 */
11 /***************************************************/
12 
13 #include "RtMidi.h"
14 #include "SKINImsg.h"
15 #include <iostream>
16 #include <stdlib.h>
17 #include <stdio.h>
18 
19 void usage(void) {
20   std::cout << "\nuseage: Md2Skini <flag(s)>\n\n";
21   std::cout << "   With no arguments, Md2Skini converts MIDI input to SKINI\n";
22   std::cout << "   format and sends the output directly to stdout.\n";
23   std::cout << "   With flag = -f <filename>, the output stream is simultaneously\n";
24   std::cout << "   written to the file specified by the optional <filename>\n";
25   std::cout << "   (default = test.ski).\n";
26   std::cout << "   With flag = -c, MIDI control change messages will not be\n";
27   std::cout << "   converted to SKINI-specific named controls.\n";
28   std::cout << "   A MIDI input port can be specified with flag = -p portNumber.\n" << std::endl;
29   exit(0);
30 }
31 
32 #include <signal.h>
33 static void finish( int ignore ){ std::cout << "Type 'Exit' to quit." << std::endl; }
34 bool parseSkiniControl = true;
35 
36 void midiCallback( double deltatime, std::vector< unsigned char > *bytes, void *userData )
37 {
38   if ( bytes->size() < 2 ) return;
39 
40   // Parse the MIDI bytes ... only keep MIDI channel messages.
41   if ( bytes->at(0) > 239 ) return;
42 
43   register long type = bytes->at(0) & 0xF0;
44   register int channel = bytes->at(0) & 0x0F;
45   register long databyte1 = bytes->at(1);
46   register long databyte2 = 0;
47   if ( ( type != 0xC0 ) && ( type != 0xD0 ) ) {
48     if ( bytes->size() < 3 ) return;
49     databyte2 = bytes->at(2);
50   }
51 
52   std::string typeName;
53   switch( type ) {
54   case __SK_NoteOn_:
55     if ( databyte2 == 0 ) {
56       typeName = "NoteOff\t\t";
57       databyte2 = 64;
58     }
59     else typeName = "NoteOn\t\t";
60     break;
61 
62   case __SK_NoteOff_:
63     typeName = "NoteOff\t\t";
64     break;
65 
66   case __SK_PolyPressure_:
67     typeName = "PolyPressure\t";
68     break;
69 
70   case __SK_ProgramChange_:
71     typeName = "ProgramChange\t";
72     break;
73 
74   case __SK_ChannelPressure_:
75     typeName = "ChannelPressure\t";
76     break;
77 
78   case __SK_PitchBend_:
79     typeName = "PitchBend\t";
80     break;
81 
82   case __SK_ControlChange_:
83 
84     if ( parseSkiniControl != true ) {
85       typeName = "ControlChange\t";
86       goto output;
87     }
88 
89     switch( databyte1 ) {
90     case __SK_PitchChange_:
91       typeName = "PitchChange\t";
92       goto output;
93 
94     case __SK_Volume_:
95       typeName = "Volume\t";
96       goto output;
97 
98     case __SK_ModWheel_:
99       typeName = "ModWheel\t";
100       goto output;
101 
102     case __SK_Breath_:
103       typeName = "Breath\t\t";
104       goto output;
105 
106     case __SK_FootControl_:
107       typeName = "FootControl\t";
108       goto output;
109 
110     case __SK_Portamento_:
111       typeName = "Portamento\t";
112       goto output;
113 
114     case __SK_Balance_:
115       typeName = "Balance\t";
116       goto output;
117 
118     case __SK_Pan_:
119       typeName = "Pan\t\t";
120       goto output;
121 
122     case __SK_Sustain_:
123       typeName = "Sustain\t";
124       goto output;
125 
126     case __SK_Expression_:
127       typeName = "Expression\t";
128       goto output;
129 
130     default:
131       typeName = "ControlChange\t";
132       goto output;
133     }
134 
135   default:
136     typeName = "Unknown\t";
137   }
138 
139  output:
140 
141   FILE *file = (FILE *) userData;
142   if ( type == 0xC0 || type == 0xD0 || type == 0xE0 ) { // program change, channel pressure, or pitchbend
143     fprintf( stdout, "%s  %.3f  %d  %.1f\n", typeName.c_str(), 0.0, channel, (float)databyte1 );
144     if ( file != NULL )
145       fprintf( file, "%s  %.3f  %d  %.1f\n", typeName.c_str(), deltatime, channel, (float)databyte1 );
146   }
147   else if ( type == 0xB0 ) { // control change
148     if ( typeName == "ControlChange\t" ) {
149       fprintf( stdout, "%s  %.3f  %d  %.1f %.1f\n", typeName.c_str(), 0.0, channel, (float)databyte1, (float)databyte2 );
150       if ( file != NULL )
151         fprintf( file, "%s  %.3f  %d  %.1f %.1f\n", typeName.c_str(), deltatime, channel, (float)databyte1, (float)databyte2 );
152     }
153     else {
154     fprintf( stdout, "%s  %.3f  %d  %.1f\n", typeName.c_str(), 0.0, channel, (float)databyte2 );
155     if ( file != NULL )
156       fprintf( file, "%s  %.3f  %d  %.1f\n", typeName.c_str(), deltatime, channel, (float)databyte2 );
157     }
158   }
159   else { // noteon, noteoff, aftertouch, and unknown
160     fprintf( stdout, "%s  %.3f  %d  %.1f  %.1f\n", typeName.c_str(), 0.0, channel, (float)databyte1, (float)databyte2 );
161     if ( file != NULL )
162       fprintf( file, "%s  %.3f  %d  %.1f  %.1f\n", typeName.c_str(), deltatime, channel, (float)databyte1, (float)databyte2 );
163   }
164 
165   fflush( stdout );
166 }
167 
168 int main( int argc,char *argv[] )
169 {
170   FILE *file = NULL;
171   std::string fileName;
172   RtMidiIn *midiin = 0;
173   unsigned int port = 0;
174   std::string input;
175 
176   if ( argc > 5 ) usage();
177 
178   // Parse the command-line arguments.
179   int i = 1;
180   while ( i < argc ) {
181     if (argv[i][0] == '-') {
182       switch(argv[i][1]) {
183 
184       case 'f':
185         if ( (i+1 < argc) && argv[i+1][0] != '-' ) {
186           i++;
187           fileName = argv[i];
188           if ( fileName.find( ".ski" ) == std::string::npos ) fileName.append( ".ski" );
189         }
190         else fileName = "test.ski";
191         file = fopen( fileName.c_str(), "wb" );
192         break;
193 
194       case 'p':
195         if ( i++ >= argc) usage();
196         port = (unsigned int) atoi( argv[i] );
197         break;
198 
199       case 'c':
200         parseSkiniControl = false;
201         break;
202 
203       default:
204         usage();
205         break;
206       }
207     }
208     else usage();
209     i++;
210   }
211 
212   try {
213     midiin = new RtMidiIn();
214   }
215   catch (RtMidiError &error) {
216     error.printMessage();
217     if ( file != NULL ) fclose( file );
218     exit(EXIT_FAILURE);
219   }
220 
221   // Check available ports vs. specified.
222   unsigned int nPorts = midiin->getPortCount();
223   if ( nPorts == 0 ) {
224     std::cout << "No MIDI ports available!\n";
225     goto cleanup;
226   }
227   else if ( port >= nPorts ) {
228     std::cout << "Invalid port specifier!\n";
229     goto cleanup;
230   }
231 
232   // Open the port.
233   try {
234     midiin->openPort( port );
235   }
236   catch (RtMidiError &error) {
237     error.printMessage();
238     goto cleanup;
239   }
240 
241   // Set our callback function.  This should be done immediately after
242   // opening the port to avoid having incoming messages written to the
243   // queue instead of sent to the callback function.
244   midiin->setCallback( &midiCallback, file );
245 
246   // We'll ignore sysex, timing, and active sensing messages.
247   midiin->ignoreTypes( true, true, true );
248 
249   // Install an interrupt handler function.
250   (void) signal(SIGINT, finish);
251 
252   std::cout << "\nReading MIDI input ... type 'Exit' to quit.\n";
253   while ( input != "Exit" && input != "exit" ) {
254     input.erase();
255     std::cin >> input;
256     std::cout << input << std::endl;
257   }
258 
259  cleanup:
260   delete midiin;
261   if ( file != NULL ) fclose( file );
262 
263   std::cout << "Md2Skini finished ... bye!" << std::endl;
264   return 0;
265 }
266