/* CMP.c containing the main() for the standalone ComParser program with only a simple commandline interface. June 17, 2004. Version number CMP.h. Main() has no arguments yet. It will try to read a file called 'startup' in the current directory. The 3 basic object classes used are: PREP Our audio preprocessor object. Reads in chunks of audio and yields feature vectors. AVALANCHE Avalanche object for spatiotemporal recognition. It processes feature vectors and may trigger state transitions in the parenting NETWORK during score-following. NETWORK Larger structure containing one or more AVALANCHE structures in sequential but also parallel order. Internal network structure is limited to 'railroad tracks' for now. Then, we need to get dirty by making physical connections to the world: PORTAUDIO To get those lovely audio samples into our computer program on all those lovely platforms. AIFF To read (and record) audiofiles from and to the filesystem. JPEG To visualise neural weights (i.e. make audio-fingerprints). MIDI To drive other stuff after recognition. CONSOLE Very ugly time-slicing still.... going POSIX soon to do proper multithreading. Latest version available at: http://kmt.hku.nl/~pieter/SOFT/ Copyright (c) 2001-2004 Schreck Ensemble - Pieter Suurmond Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is requested to send the modifications to the original developer so that they can be incorporated into the canonical version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include /* Standard headers. */ #include #include #include /* For fabs(). */ #include /* For generating filenames. */ #include /* For usleep() during sending midi messages. */ #include "CMP.h" /* For version number. */ #include "CMP_console.h" /* Include CMP.console.c in proj. or makefile, */ /* along with 1 of the platform-specific files */ /* CMP_mac_console.c or CMP_sgi_console.c. */ #include "CMP_midi.h" /* Include CMP.midi.c in project or makefile, */ /* along with one of the platform-dependant */ /* files: sgi_midi.c, or mac_midi_oms.c, or... */ #include "CMP_prep.h" #include "portaudio.h" /* CMP_portaudio.h needs it. */ #include "CMP_portaudio.h" #include "CMP_avalanche.h" #include "CMP_network.h" #include "CMP_aiff.h" #include "CMP_audiorecord.h" /*--------------------------------- SOME GLOBALS: --------------------------*/ static NETWORK gNetwork = (NETWORK)NULL; /* Loaded network object. */ /* Real-time analysis object, 0 if not running. */ static RUNNING_DATA* gAnaRun = (RUNNING_DATA*)NULL; /* NULL when MIDI is off, see CMP.commands.c. */ static CMP_MIDI_IO* gMidi = (CMP_MIDI_IO*)NULL; struct { /* For audiorecording while following. */ long readIndex; /* >=0 when running. */ aiff_file* audiofile; /* NULL means not recording now. */ } gAudioRecord = { -1L, NULL }; /*----------------------------------------------------------------------------*/ /* Tests whether network of avalanches and realtime parameters are compatible. The number of feature vectors per second must match. One should consult this test function before repeatedly calling follow_NETWORK(). Returns 0 or 1. */ static short ProcessDisagree_NETWORK(NETWORK network, RUNNING_DATA* running, /* Must be there! */ FILE* msg) { float realtime_fps; realtime_fps = (float)(kCMP_analysis_overlap * running->samplerate) / (float)running->chunksize; if (fabsf(realtime_fps - network->fps) > 0.1) /* Allow some deviation. */ { if (msg) printf("Number of feature vectors per second (%.2f) in network\ '%s' disagrees with realtime fps (%.2f)!\n", network->fps, network->filename, realtime_fps); return 1; } return 0; /* We should also test basefreq and num scalars and such(?). */ } /*----------------------------------------------------------------------------*/ /* MENU CHOICES: */ /*----------------------------------------------------------------------------*/ /* The following routines are called from the processCommand() function which is in CMP.c. They all return 0 on success (or when error not fatal). */ /*----------------------------------------------------------------------------*/ short scanForAudioInputDevices(const char* name_version, char* t) { (void)t; /* Surpress warning t not used. */ if (audio_devices(stdout)) /* Argument may even be NULL (no error). */ printf("%s ERROR: scanForAudioInputDevices() failed.\n", name_version); return 0; } /*-----------------------------------------------*/ short testMyFFT(const char* name_version, char* t) { short e = 0; (void)t; /* Surpress warning t not used. */ if (gAnaRun) /* When I'm not mistaken, this is not strictly necessary(?). */ { /* More than 1 analysis-object may be running? */ printf("Please stop realtime analysis before running this test.\n"); printf("(Benchmarks look nicer when no other processes are running.)\n"); return 0; } printf("%s running PREP_test() to tests audio preprocessor:\n", name_version); e = PREP_test(1024, stdout); /* Fftsize=1024 */ printf("%s now running separate FFT tests:\n", name_version); e |= PREP_test_fft(1024, stdout); /* Last arg may even be NULL (no error). */ e |= PREP_test_fft(2048, stdout); e |= PREP_test_fft(4096, stdout); return e; } /*---------------------------------------------------------------------------------*/ /* STARTING, STOPPING, VIEWING AND SAVING THE PREPROCESSING LAYER: */ /*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/ /* Try to startup realtime audio analysis, using the lowsest samplerate possible when samplerate not explicitly requested. Also use rather low number of feature vectors per second (fps) when not explicitly requested by 'args'. */ short startAna(const char* name_version, char* args) { char *arg_fps, *arg_sr = NULL; int e, chunksize; double fftSize, fps = 21.53; /* Take this when no argument supplied and no network loaded. */ long sr = 11025L; /* Take this when no argument is supplied. */ (void)name_version; if (gAnaRun) /* Prevent from allocating/starting more than 1 realtime object */ { /* which IS possible because of object-oriented architecture. */ printf("REALTIME INPUT ANALYSIS ALREADY RUNNING!\n"); return 0; } if (gNetwork) /* Inherit fps from network if loaded. */ fps = gNetwork->fps; if (args) /* Prevent from bizarre rates here? */ { if (!(arg_sr = strstr(args, "sr="))) arg_sr = strstr(args, "SR="); if (!(arg_fps = strstr(args, "fps="))) arg_fps = strstr(args, "FPS="); if (arg_sr) *arg_sr = 0; if (arg_fps) *arg_fps = 0; if (arg_sr) sr = atol(arg_sr + 3); if (arg_fps) fps = atof(arg_fps + 4); } if ((sr < 1000L) || (sr > 96000L)) { printf("Invalid samplerate (%ld)!\n", sr); return 0; } if ((fps < kCMP_min_fps) || (fps > kCMP_max_fps)) { printf("FPS out of range (%.2f)!\n", fps); return 0; } tryDoubleSr: fftSize = (double)(sr * kCMP_analysis_overlap) / fps; if (fabs(fftSize - 1024.0) < 8.0) chunksize = 1024; else if (fabs(fftSize - 2048.0) < 16.0) chunksize = 2048; else if (fabs(fftSize - 4096.0) < 32.0) chunksize = 4096; else /*FIXXXXED @1.32.*/ { if ((!arg_sr) && (sr < 44100L)) /* Not requested by command, set to minimum. */ { sr <<= 1; goto tryDoubleSr; } /* Search lowest sr before giving up. */ printf("Unhappy combination of sr (%ld) and fps (%.4f)!\n", sr, fps); return 0; } fps = (double)(sr * kCMP_analysis_overlap) / (double)chunksize; /* More accurate fps. */ printf("\ Trying audio input samplerate of %ld Hz, %.4f feature vectors per second\n\ (%d input-point FFT, overlap %d).\n", sr, fps, chunksize, kCMP_analysis_overlap); if (chunksize > 1024) printf("Hint: using a lower input samplerate reduces CPU load.\n"); e = start_analysis(&gAnaRun, 2, sr, chunksize, stdout); /* Take 2 channels for now. */ if (e) { printf("Sorry, start_analysis()=%d!\n", e); return 0; /* Was "return 1;" before, which would quit ComParser. */ } printf("REALTIME INPUT ANALYSIS RUNNING NOW.\n"); Pa_Sleep(250); /* Wait a little while to cool down the operating system. */ return 0; /* (Prevent possible MIDI startup immediately hereafter */ } /* which sometimes may give a coredump under IRIX 6.2.) */ /*------------------------------------------------*/ short stopAna(const char* name_version, char* dummy) { (void)name_version; (void)dummy; if (!gAnaRun) { printf("Realtime input analysis already stopped!\n"); return 0; } /* Pass handle so stopAnalysis() can write */ stop_analysis(&gAnaRun, stdout); /* a NULL there _after_ cleaning up. */ printf("REALTIME INPUT ANALYSIS STOPPED.\n"); if (gAudioRecord.audiofile) /* Then audio recording also stops! */ { stop_recordAudio(&gAudioRecord.readIndex, &gAudioRecord.audiofile); } /* stop_recordAudio() reports to stdout. */ return 0; } /*--------------------------------------------------------------------*/ /* Dumps the last numFrames to the standard output. */ short viewAna(const char* name_version, char* numFrames) { long frames; (void)name_version; if (!gAnaRun) { printf("Realtime analysis is not running now, sorry.\n"); return 0; } if (!numFrames) { numFrames = "21"; printf("No argument given (default = %s frames):\n", numFrames); } frames = atol(numFrames); if ((frames <= 0) || (frames >= gAnaRun->cbdata.varidistFrames)) { printf("Too many or not enough frames (%ld), sorry.\n", frames); return 0; } view_analysis(gAnaRun, frames, stdout); /* Last arg may even be NULL. */ return 0; } #if (0) /* We don't directly save avalanche files any longer? */ /*---------------------------------------------------------------------------------------*/ short recAvalanche(const char* name_version, char* filename) /* This command is discouraged. Gerard van Wolferen */ { /* learned me it is a bad idea not to keep original */ int e; /* material analalysed. Better work with audiofiles! */ double fps, bfq; long startIndex, stopIndex, num = 0L; char *consoleInput, *noname = "untitled", *onoff[2] = { "OFF", "ON" }; short vectorClusteringEnabled = 1, silenceCroppingEnabled = 1; /*------------------------------------------------*/ short maxCluster = 42, /* DEFAULT SETTINGS: maxCluster, silenceCropping. */ silenceCropping = 2000; /*------------------------------------------------*/ /* (2560 is highest occuring silence-measure.) */ if (!gAnaRun) /* (See vector clutering treshold below.) */ { printf("Sorry, realtime analysis must be started first.\n"); return 0; } if (filename) { while (*filename == '-') /* Read argument options. */ { filename++; if (*filename == 'c') vectorClusteringEnabled = 0; else if (*filename == 's') silenceCroppingEnabled = 0; else if (*filename == 'p') /* -p option saves previously recorded material. */ { /* Try to find the numer of frames specified: */ do { filename++; } while (*filename&&(*filename<=' ')); /* Skip spaces.*/ num = atol(filename); /* Mab be "" --> illigal 0.*/ if ((num < 1L) || (num > gAnaRun->cbdata.varidistFrames)) { printf("Wrong number of frames with -p option.\n"); return 0; } do { filename++; } while (*filename&&(*filename>' ')); /* Find space.*/ } else { printf("Unknown option (only -c and -s are allowed).\n"); return 0; } do { *filename++; } while (*filename && (*filename <= ' ')); /* Skip tabs, spaces, etc. */ } if (!*filename) filename = (char*)NULL; } if (!filename) /* Read or invent a filename. */ filename = noname; printf(" (over)writing file: '%s'\n", filename); printf(" vector clustering: %s\n", onoff[vectorClusteringEnabled]); printf(" silence cropping: %s\n", onoff[silenceCroppingEnabled]); if (num) { /*---------------------- PREVIOUSLY RECORDED: ---------------------*/ startIndex = gAnaRun->cbdata.writtenIndex - (num-1); if (startIndex < 0) startIndex += gAnaRun->cbdata.varidistFrames; printf(" Saving %ld previously recorded frames to disk...\n", num); } else { /*---------------------- REALTIME RECORDING: ---------------------*/ printf(" press ENTER to start recording (or c to cancel): "); fflush(stdout); do { consoleInput = cmpGetConsoleInputString(); } while (!consoleInput); startIndex = gAnaRun->cbdata.writtenIndex - 1; /* Start 1 frame earlier. */ if (startIndex < 0) startIndex += gAnaRun->cbdata.varidistFrames; if ((*consoleInput == 'c') || (*consoleInput == 'C')) goto cancel; printf(" RECORDING NOW... ENTER to stop again (c to cancel): "); fflush(stdout); do { consoleInput = cmpGetConsoleInputString(); } while (!consoleInput); if ((*consoleInput == 'c') || (*consoleInput == 'C')) { cancel: printf(" '%s' not saved!\n", filename); return 0; } stopIndex = gAnaRun->cbdata.writtenIndex; num = stopIndex - startIndex; if (num < 0) num += gAnaRun->cbdata.varidistFrames; num++; } /*-------------------------------- ACTUALLY GO SAVING: -------------------*/ if (!vectorClusteringEnabled) maxCluster = 0; /* Technically, 1 should result the same! */ if (!silenceCroppingEnabled) silenceCropping = 32000; /* Higher than 2560 means OFF (CMP 0.61). */ fps = (double)(gAnaRun->samplerate * kCMP_analysis_overlap) / (double)gAnaRun->chunksize; bfq = (double)(gAnaRun->samplerate * kCMP_varidist_fftStartBin) / (double)gAnaRun->chunksize; e = write_AVALANCHE(filename, /* Command line argument. */ fps, /* Feature vectors per second. */ bfq, /* Base frequency of FFT. */ &gAnaRun->cbdata, /* Realtime callback-data. */ startIndex, /* long first frame index. */ num, /* long number of frames. */ 1, /* hurry=1: check realtime overwrite. */ 6200000L, /* long clustering treshold. */ maxCluster, /* short maxCluster. */ silenceCropping); /* short silenceCropping. */ if (e) { printf(" writeIncantationQ('%s',..) = %d!\n", filename, e); if (e < 0) return 1; /* Quit on NEGATIVE saving-errors. */ return 0; /* POSITIVE errors are not fatal. */ } printf(" Saved file as '%s' (%ld unclustered frames).\n", filename, num); return 0; } #endif /*----------------------------------------------------------------------------*/ short infoNetwork(const char* name_version, char* gen_jpg) { if (gen_jpg) /* Arg must be "jpg" or "JPG", etc. */ { if (strcasecmp(gen_jpg, "jpg")) gen_jpg = NULL; } NETWORK_info(gNetwork, name_version, gen_jpg, stdout); return 0; } /*----------------------------------------------------------------------------*/ short loadNetwork(const char* name_version, char* filename) { short e; long linesRead; if (gNetwork) { printf("Sorry, currently loaded network '%s'.\n", gNetwork->filename); return 0; } if (!filename) { /* DEFAULT NETWORK TO LOAD: */ filename = "data/poemi.network"; printf("No filename given, trying to read '%s'.\n", filename); } e = NETWORK_load(&gNetwork, /* Pass global. */ name_version, filename, &linesRead, /* Receive number of lines read here. */ stdout); /* May be NULL, stderr, stdout or some textfile. */ switch (e) { case 0: printf("Loaded network '%s'", filename); break; case 1: case 2: case 3: printf("Not enough memory to build network"); break; /* Quit. */ case 4: printf("Could not open file '%s'!\n", filename); e=0; break; /* Don't quit. */ case 6: case 7: printf("Error in headerline"); e=0; break; case 8: printf("No filename found in avalanche"); e=0; break; case 11: printf("Endtime below or equal to starttime"); e=0; break; case 14: printf("Empty node name"); e=0; break; case 17: printf("Illegal character in node"); e=0; break; case 18: printf("No avalanches allowed before first node"); e=0; break; case 19: printf("Found no avalanches between nodes"); e=0; break; case 21: printf("No finishing node found in network file"); e=0; break; default: printf("Error: load_NETWORK(%s)=%d", filename, (int)e); } printf(" (read %ld lines).\n", linesRead); return e; } /*----------------------------------------------------------------------------*/ short freeNetwork(const char* name_version, char* arg) { short e = 0; char* name; (void)name_version; (void)arg; if (gNetwork) { /* gReadIndex = -1L; If no network left, process STOPS! */ name = strdup(gNetwork->filename); e = NETWORK_destruct(&gNetwork); if (e) printf("Error: release_NETWORK()=%d!\n", (int)e); /* Quit. */ if (name) { if (!e) printf("Deleted network %s from memory.\n", name); free(name); } else e = 999; /* Quit. */ } else printf("Network was already released.\n"); /* Don't quit. */ return e; } /*-------------------------------------------------------------------------------------*/ short startNetwork(const char* name_version, char* args) /* Makes follow_NETWORK() to be called regularly. */ { /* User command 'follow [r=N] [d[d]] [a]'. */ long repeat = 0L; int debugF = 0; char *arg_repeat, *arg_debugf, *dot, audiofilename[256]; time_t t; struct tm* locTime; (void)name_version; if (!gAnaRun) { printf("Realtime audio analysis must be started up first.\n"); return 0; } if (!gNetwork) { printf("A network must be loaded first.\n"); return 0; } if (gAnaRun->cbdata.readIndex >= 0) { printf("Already following.\n"); return 0; } /* Test whether sample- */ if (ProcessDisagree_NETWORK(gNetwork, gAnaRun, stdout)) /* rates agree, before */ { /* incantationProcess(). */ printf("Sorry, load other files, or change the realtime samplerate.\n"); return 0; } if (args) { /* BACKGROUND AUDIO RECORDING WHILE FOLLOWING: */ if (strchr(args, 'a') || strchr(args, 'A')) { /* Invent a filename for audio: network name, up to the first dot, */ dot = gNetwork->filename; /* but without prefixed path. */ while (strchr("./\\:", *dot)) dot++; /* Skip leading dots, slashes, colons. */ strcpy(audiofilename, dot); /* Skip directory-names (inner slashes). */ while ((dot = strchr(audiofilename, '/'))) strcpy(audiofilename, dot + 1); /* Unix. */ while ((dot = strchr(audiofilename, '\\'))) strcpy(audiofilename, dot + 1); /* Dos. */ while ((dot = strchr(audiofilename, ':'))) strcpy(audiofilename, dot + 1); /* Mac. */ dot = strchr(audiofilename, '.'); if (dot) *dot = (char)0; if (!*audiofilename) strcpy(audiofilename, "noname"); time(&t); locTime = localtime(&t); /* Reentrant 'r_' version available on Mac? */ sprintf(audiofilename + strlen(audiofilename), "_%04d%02d%02d_%02dh%02dm%02ds.aiff", locTime->tm_year + 1900, locTime->tm_mon + 1, locTime->tm_mday, locTime->tm_hour, locTime->tm_min, locTime->tm_sec); if (start_recordAudio(gAnaRun, /* Realtime audio input. */ &gAudioRecord.readIndex, /* Index in circular audio buffer. */ audiofilename, /* Overwrite. */ &gAudioRecord.audiofile)) /* Receive audiofile object here. */ { printf("Sorry, start_recordAudio() failed.\n"); return 1; } else printf("Started recording '%s'.\n", audiofilename); } if (!(arg_repeat = strstr(args, "r="))) arg_repeat = strstr(args, "R="); if (!(arg_debugf = strchr(args, 'd'))) arg_debugf = strchr(args, 'D'); if (arg_repeat) *arg_repeat = 0; if (arg_debugf) *arg_debugf = 0; if (arg_repeat) repeat = atol(arg_repeat + 2); if (arg_debugf) /* 0 */ { arg_debugf++; debugF++; /* 1 */ if ((*arg_debugf == 'd') || (*arg_debugf == 'D')) debugF++; /* 2 */ } } NETWORK_reset(gNetwork, repeat, debugF); /* Must clear before calling follow_NETWORK(). */ gAnaRun->cbdata.readIndex = gAnaRun->cbdata.writtenIndex - 1; /* Set to previous, so current */ if (gAnaRun->cbdata.readIndex < 0) /* frame will be processed. */ gAnaRun->cbdata.readIndex += gAnaRun->cbdata.varidistFrames; printf("Following '%s'", gNetwork->filename); /* Check struct: */ if (gNetwork->rt_repeat) printf(", repeating %ld times", gNetwork->rt_repeat); if (gNetwork->rt_debug_avalanches) printf(", debugging level %d", gNetwork->rt_debug_avalanches); printf(".\n"); return 0; } /*---------------------------------------------------------------------------------*/ short stopAudioRec(const char* name_version, char* dummy) { (void)name_version; /* Surpress unused parameter warnings. */ (void)dummy; stop_recordAudio(&gAudioRecord.readIndex, &gAudioRecord.audiofile); return 0; /* Disregard return value, file closed and memory released, cleared object reference. */ } /*---------------------------------------------------------------------------------*/ short hopNetwork(const char* name_version, char* dummy) { (void)name_version; (void)dummy; if ((!(gAnaRun && gNetwork)) || (gAnaRun->cbdata.readIndex < 0)) printf("Cannot hop now.\n"); else if (gNetwork->rt_follow_node) /* Are we already at end? */ { gNetwork->rt_follow_node->rt_activity = 1; /* The easiest way to hop. */ printf(""); } else printf("Cannot hop beyond end.\n"); return 0; } /*----------------------------------------------------------------------------*/ short stopNetwork(const char* name_version, char* arg) { (void)name_version; (void)arg; if (gAudioRecord.audiofile) /* Disregard gAudioRecord.readIndex. */ { stop_recordAudio(&gAudioRecord.readIndex, &gAudioRecord.audiofile); } /* stop_recordAudio() reports to stdout. */ if (!gAnaRun) printf("Warning: realtime audio input already stopped.\n"); if (gAnaRun->cbdata.readIndex < 0L) printf("Following already stopped.\n"); else printf("Following process stopped.\n"); gAnaRun->cbdata.readIndex = -1L; return 0; } /*----------------------------------------------------------------------------*/ /* STARTING AND STOPPING MIDI SEND AND RECEIVE: */ /*----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/ static char gRep255[256]; /* For mon()/moff() only, sorry for global. */ /*----------------------------------------------------------------------*/ short mon(const char* name_version, char* arg) { int r; const char *qMis = "Quotation mark missing %sput name!\n", *offOn[2] = { "off", "on" }; unsigned char recChan = 12, /* Default receive channel 13. */ recOmni = 1; /* Default setting OMNI ON. */ char *fnd, *arg_in = NULL, *arg_r = NULL, *arg_out = NULL; (void)name_version; if (gMidi) { printf("MIDI was already switched on.\n"); return 0; } if (arg) { if (!(arg_in = strstr(arg, "in=" ))) arg_in = strstr(arg, "IN=" ); if (!(arg_out = strstr(arg, "out="))) arg_out = strstr(arg, "OUT="); if (!(arg_r = strstr(arg, "r=" ))) arg_r = strstr(arg, "R=" ); if (arg_in ) { *arg_in = 0; arg_in += 3; } if (arg_out) { *arg_out = 0; arg_out += 4; } if (arg_r ) { *arg_r = 0; arg_r += 2; } if (arg_in) { if (*arg_in++ != '\'') { arg_in = NULL; printf(qMis, "before in"); } else if ((fnd = strchr(arg_in, '\''))) *fnd = 0; else { arg_in = NULL; printf(qMis, "after in"); } } if (arg_out) { if (*arg_out++ != '\'') { arg_out = NULL; printf(qMis, "before out"); } if ((fnd = strchr(arg_out, '\''))) *fnd = 0; else { arg_out = NULL; printf(qMis, "after out"); } } if (arg_r) { r = atoi(arg_r); if ((1 <= r) && (r <= 16)) /* If correct recieve channel */ { /* supplied as argument, omni */ recChan = (unsigned char)(r - 1); /* off. */ recOmni = 0; } else printf("Invalid MIDI receive channel (%d)!\n ", r); } } printf("Selecting MIDI receive channel %d, omni %s.\n", (int)recChan + 1, offOn[recOmni]); gMidi = initializeMidiIO(arg_in, arg_out, /* NEW: input and output names or NULL. */ recOmni, /* recOmni: OFF ro ON. */ recChan, /* recChan: 0 to 15 !!! */ 0, /* recThru: OFF. */ 98, /* recSysexID: 98 is mine?! */ gRep255); /* buffer 256 chars. */ printf("%s", gRep255); Pa_Sleep(100); /* Just a small pause to let the system cool down. */ return 0; /* Don't exit Comparser when MIDI login fails or wrong args were given. */ } /*----------------------------------------------------------------------*/ short mlist(const char* name_version, char* arg) { (void)name_version; (void)arg; if (!gMidi) printf("Sorry, first switch on MIDI.\n"); else listNamesMIDI(gMidi, stdout); return 0; } /*----------------------------------------------------------------------*/ short moff(const char* name_version, char* arg) { (void)name_version; (void)arg; if (!gMidi) printf("MIDI already switched off.\n"); else { releaseMidiIO(&gMidi, gRep255); printf("%s", gRep255); } return 0; /* Don't exit Comparser if already logged out MIDI. */ } /*---------------------------------------------------------------------------*/ short stopLoop(const char* name_version, char* t) { (void)t; printf("Quitting %s...\n", name_version); return 1; } /*-------------------------------------------------------------------------------*/ /* The following eight 'recMidi'-functions are prototyped in midi/CMP.midi.h. They are executed by the receiveMIDI() function after MIDI has been received. The receiveMIDI() function must be called regularly to poll for MIDI data. BUT IS IT REALLY NECESSARY FOR COMPARSER TO BE ABLE TO RECEIVE MIDI ??? */ /*-------------------------------------------------------------------------------*/ void recMidiNoteOff (unsigned char ch, unsigned char note, unsigned char vel) { printf("NOTE OFF: ch %d, note %d, vel %d.\n", (int)ch, (int)note, (int)vel); } void recMidiNoteOn (unsigned char ch, unsigned char note, unsigned char vel) { printf("NOTE ON: ch %d, note %d, vel %d.\n", (int)ch, (int)note, (int)vel); } void recMidiPolyPressure (unsigned char ch, unsigned char d0, unsigned char d1) { printf("POLY PRESS: ch %d, d0 %d, d1 %d.\n", (int)ch, (int)d0, (int)d1); } /*-------------------------------------------------------------------------------*/ void recMidiController (unsigned char ch, unsigned char num, unsigned char val) { printf("CONTROLLER: ch %d, ctr %d, val %d.\n", (int)ch, (int)num, (int)val); /* if (num == 64) { if (val) startAna((char*)NULL); // Sustain pedal starts/stops analysis? else stopAna((char*)NULL); } */ } /*-------------------------------------------------------------------------------*/ void recMidiProgramChange (unsigned char ch, unsigned char d0) { printf("PROGRAM CHANGE: ch %d, program %d.\n", (int)ch, (int)d0); } void recMidiChannelPressure (unsigned char ch, unsigned char d0) { printf("CHANNEL PRESS: ch %d, pressure %d.\n", (int)ch, (int)d0); } void recMidiPitchBend (unsigned char ch, int d14) { printf("PITCH BEND: ch %d, bend %d.\n", (int)ch, d14); } void recMidiSystemExclusive (unsigned char* rd, unsigned int len) { printf("SYSEX: %d bytes:", len); while (len--) printf(" %3d", (int)*rd++); printf("\n"); } /* End of MIDI-commands (not used yet). */ /*--------------------------------------*/ /*--------------------------------------------------------------------------------------*/ typedef struct /* COMPARSER USER COMMANDS: */ { const char* name; const char* explain; short (*fun)(const char*, char*); /* Pointer to one of the functions. */ } COMMAND; static const COMMAND gUserCommands[] = { /* COMMAND NAMES: DESCRIPTIONS: FUNCTION POINTERS: */ { "audio scan"," Scan for realtime audio inputs.", scanForAudioInputDevices }, { "audio on","[sr=SR][fps=FPS] Start realtime audio analysis.", startAna }, { "audio view","[N] View realtime audio analysis.", viewAna }, { "audio off"," Stop realtime audio analysis.", stopAna }, /* DISCOURAGE THIS (DEPRECATED) COMMAND: { "audio avalanche","[-c][-s][-p N] Record audio directly to avalanche textfile\n\ [FILENAME] (no need for this when using audiofiles).", recAvalanche }, */ { "audio rec off"," Close down audio recording.", stopAudioRec }, { "audio tfft"," Test FFT and bin-regrouping.", testMyFFT }, { "midi on","[in='NAME'][r=CHAN] Switch on MIDI send and receive, optional\n\ [out='NAME'] input and output names and receive channel.", mon }, { "midi scan"," List names of MIDI interfaces available.", mlist }, { "midi off"," Switch off MIDI.", moff }, { "net load","[FILENAME] Load network file with name FILENAME.", loadNetwork }, { "net info","[jpg] Show loaded nodes and avalanches.", infoNetwork }, { "net follow","[r=N] [d[d]] [a] Follow loaded network, optional repeat N\n\ times, debug avalanches, record to audiofile.", startNetwork }, { "h"," Hop to the next node in the network.", hopNetwork }, { "net halt"," Stop following network.", stopNetwork }, { "net free"," Release loaded network from memory.", freeNetwork }, { "q"," Quit ComParser.", stopLoop }, { (char*)NULL, (char*)NULL, (void*)NULL } }; /* Always terminate with 0 0 0. */ /*---------------------------------------------------------------------------------------*/ static void printCommandInfo(void) /* State-aware menu. */ { short u = 0, pted = 0; while (gUserCommands[u].name) /* Exclude certain menu choices. */ { if ( (gUserCommands[u].fun == stopLoop) || /* Always available. */ ((gUserCommands[u].fun == scanForAudioInputDevices) && (!gAnaRun)) || /* Not while running. */ ((gUserCommands[u].fun == startAna) && (!gAnaRun)) || ((gUserCommands[u].fun == testMyFFT) && (!gAnaRun)) || /* ((gUserCommands[u].fun == recAvalanche) && gAnaRun) || */ ((gUserCommands[u].fun == stopAna) && gAnaRun) || ((gUserCommands[u].fun == viewAna) && gAnaRun) || ((gUserCommands[u].fun == mon) && (!gMidi)) || ((gUserCommands[u].fun == mlist) && gMidi) || ((gUserCommands[u].fun == moff) && gMidi) || ((gUserCommands[u].fun == loadNetwork) && (!gNetwork)) || ((gUserCommands[u].fun == infoNetwork) && gNetwork) || ((gUserCommands[u].fun == freeNetwork) && gNetwork) || ((gUserCommands[u].fun == startNetwork) && gAnaRun && gNetwork && (gAnaRun->cbdata.readIndex < 0)) || ((gUserCommands[u].fun == stopNetwork) && gAnaRun && gNetwork && (gAnaRun->cbdata.readIndex >= 0)) || ((gUserCommands[u].fun == hopNetwork) && gAnaRun && gNetwork && (gAnaRun->cbdata.readIndex >= 0)) || ((gUserCommands[u].fun == stopAudioRec) && gAudioRecord.audiofile) ) { if (pted) printf(" "); else { printf("\nMenu:"); pted = 1; } printf(" %s %s\n", gUserCommands[u].name, gUserCommands[u].explain); } u++; } } /*------------------------------------------------------------------------------*/ /* Processes command, if any. Returns nonzero when it's time to quit ComParser. Called (repeatedly) by processStartupCommands() and processConsoleCommand(). Arguments may never be NULL. Memory space that com points to may be altered! */ /*------------------------------------------------------------------------------*/ static short processCommand(const char* name_version, char* com) { short u = 0; int n; cmpCutSpace(&com); /* Destructive removal of excessive spaces, tabs, etc. */ if (com) /* Transforms empty strings ("") to NULL pointers. See */ { /* console/CMP_handy.c. */ while (gUserCommands[u].name) /* Search for it in array of commands. */ { n = strlen(gUserCommands[u].name); /* Case SENSITIVE */ if (!strncmp(com, gUserCommands[u].name, n)) /* search. */ { com += n; if (*com && (*com <= ' ')) /* Cut space preceeding the arg(s). */ com++; /* Sure it'll never be more than 1. */ if (!*com) com = (char*)NULL; /* Empty strings ("") become NULL. */ return gUserCommands[u].fun(name_version, com); /* Pass arg(s) as string. */ } u++; } printf("Unknown CMP command!\n"); } return 0; } /*------------------------------------------------------------------------------------*/ /* Execute commands from a textfile. */ static short processStartupCommands(const char* name_version, char* filename) { FILE* fp; char* lineBuff; const short startupMaxLineLength = 128; /* Including the \0 character. */ short count = 1, e = 0; fp = fopen(filename, "rt"); if (fp) { lineBuff = (char*)malloc(startupMaxLineLength); if (lineBuff) { while (fgets(lineBuff, startupMaxLineLength, fp) && (!e)) { /* Ignore lines staring with */ if (!strchr("#\n\r", *lineBuff)) /* a '#' and empty lines. */ { /* No problem if empty ("")? */ printf("%s line %d: %s", filename, (int)count, lineBuff); e = processCommand(name_version, lineBuff); /* Echo to stdout. */ } count++; } free(lineBuff); } else { printf("Not enough memory to read startup file '%s'!\n", filename); e = -100; /* Quit Comparser if there's such lack of memory. */ } fclose(fp); } /* else Who cares: printf("Could not open startup file '%s'!\n", filename); */ return e; } /*----------------------------------------------------------------------------------*/ static short processConsoleCommand(const char* name_version) { short e = 0; char* line; line = cmpGetConsoleInputString(); /* Platform dependant implementations. */ if (line) { e = processCommand(name_version, line); if ((!e) && (*line != 'h') && (*line != 'H')) printCommandInfo(); /* Do not show commands just before quitting. */ } /* And not during manual hops. */ return e; } /*----------------------------------------------------------------------------------*/ /* Dirty trick to prevent blocking of audiorecording during long pauses between the midibytes. Called by send_midi_messages_and_perform_pauses() in CMP_network.c. Whenever we change to real multithreading, we'll change this. */ short keepRecording(void) /* We 'll get rid of this shit whenever we switch to POSIX MT. */ { return(recordAudio(gAnaRun, &gAudioRecord.readIndex, &gAudioRecord.audiofile)); } /*-------------------------------------------------------------------*/ /* Sends up to 255 MIDI bytes, which must be structured according to the MIDI-protocol, but may form more than one midi message. Calls sendMIDI() zero or one or more times. First arg may be NULL. FIX: Implicitly addresses 'interface 0'. BUG: Running status is NOT YET supported! Called by follow_NETWORK() during node-transitions. */ static short send_midi_messages_and_perform_pauses(CMP_MIDI_IO* mio, short* data, FILE* msg) { short i, len, e; unsigned char miniMidiBuff[32]; /* Expect messages not to be longer than 31 bytes! */ useconds_t s; len = *data++; while (len > 0) /* 0 midi bytes and/or pauses is ok. */ { if (*data < 0) /* Negative numbers are pauses. */ { if (keepRecording()) /* See CMP.c. */ { if (msg) fprintf(msg, "keepRecording() failed!\n"); return 1; } s = 0-(useconds_t)(*data); if (msg) { fprintf(msg, ","); fflush(msg); } s *= (useconds_t)1000; usleep(s); /* Pause microseconds. */ data++; len--; } else { i = 0; if (data[i] < 128) { if (msg) fprintf(msg, "First midibyte should be a statusbyte!\n"); return 2; } do { /* Is there more than one message? */ miniMidiBuff[i] = (unsigned char)data[i]; i++; } while ((0 <= data[i]) && (data[i] <= 127) && (i < len)); if ((e = sendMIDI(mio, /* May be NULL! */ miniMidiBuff, /* Pointer to bytes. */ i, /* Number of bytes. */ 0))) /* 0 is the number of the interface! *** FIX ***/ { if (mio) /* Don't report absence of mio object, we perform pauses here also. */ { if (msg) fprintf(msg, "sendMIDI()=%d\n%s", e, mio->rep255); return 3; } } data += i; len -= i; } } if (len) /* Should not end negative. */ /* Shouldn't we write to 'msg' instead? */ { sprintf(mio->rep255, "Error in parsing multiple midi messages!\n"); return 4; } return 0; } /*----------------------------------------------------------------------------*/ /* All arguments may be NULL. */ static int score_follow(NETWORK network, RUNNING_DATA* running, CMP_MIDI_IO* midi_io, FILE* msg) { int e; NODE node; long togo; if ((!(network && running)) || (running->cbdata.readIndex < 0L)) /* If first args are not ok. */ return 0; /* we simply don't run, no err. */ /* See whether new feature vectors arrived in our circular buffer. */ togo = running->cbdata.writtenIndex - running->cbdata.readIndex; if (togo < 0L) togo += running->cbdata.varidistFrames; /* Read- or write-pointer wrapped. */ if (togo > 20L) { if (msg) fprintf(msg, "Score-follower is not keeping up too well, %ld feature vectors behind!\n", togo); if (togo > 127L) /* 128 frames behind --> 6 s latency @ 11025 Hz. */ return 1; /* -1 causes CMP to STOP, for debugging purposes. */ } /* printf("togo=%ld\n", togo); */ while (togo--) /* Process all new feature-frames that arrived. */ { if (++(running->cbdata.readIndex) == running->cbdata.varidistFrames) /* Inc and wrap. */ running->cbdata.readIndex = 0L; /* printf("readIndex=%ld, writtenIndex=%ld\n", running->cbdata.readIndex, running->cbdata.writtenIndex); */ e = NETWORK_follow(network, /* May not be NULL. */ running->cbdata.varidistOutput + /* Startaddress of realtime input vector. */ (running->cbdata.readIndex * running->cbdata.audioprep->varidistScalars), &node, /* Receive node here on transition. */ msg); /* May be NULL, stdout or some fileptr. */ if (e) { if (msg) fprintf(msg, "follow_NETWORK()=%d\n", (int)e); return 2; } if (node) { /* This func may block! */ e = send_midi_messages_and_perform_pauses(midi_io, /* midiOut may be NULL. */ node->action, msg); /* Can send multiple messages. */ if (e) /* Nonzero means error-string is there. */ { if (msg) fprintf(msg, "send_midi_messages_and_perform_pauses()=%d\n", (int)e); return 3; /* CMP quits. */ } /* Set readIndex so that we are not behind. For we might have paused with one or more usleeps. */ /* Don't process any remaining input vectors in the circular buffer (skip some amount of time). */ running->cbdata.readIndex = running->cbdata.writtenIndex; /* Even skip the most recent one. */ break; } } return 0; } /*----------------------------------------------------------------------------------*/ int main(void) /* No starup-args yet (for scripting control). */ { static const char* name_version = kCMP_NAME_VERSION; /* Take it from CMP.h. */ long mp; short loop = 1; cmpInitialize(name_version); /* Platform specific startup code (not too much). */ printf("\n----- %s (c) 2001-2004 Schreck Ensemble - Pieter Suurmond -----\n\n", name_version); /*------------------------------------------------------------------------------*/ if (processStartupCommands(name_version, /* Execute startup file (if any). */ "startup")) /* Supply rel. or abs. pathname. */ loop = 0; /*------------------------------------------------------------------------------*/ /* MAIN EVENT LOOP (I'd like to change this to multithread-model): */ /*------------------------------------------------------------------------------*/ printCommandInfo(); while (loop) { /* Poll for MIDI events and */ mp = receiveMIDI(gMidi); /* process them. */ if (mp < -1L) /* Results -1 when gMidi ptr */ printf("%s", gMidi->rep255); /* was NULL, MIDI wasn't here. */ if (score_follow(gNetwork, gAnaRun, gMidi, stdout)) loop = 0; /* Exit if we don't keep up or */ /* when errors occured. */ else if (recordAudio(gAnaRun, &gAudioRecord.readIndex, &gAudioRecord.audiofile)) /* May switch off itself. */ loop = 0; else if (processConsoleCommand(name_version)) loop = 0; } /*------------------------------------------------ Global cleanup before exit: */ if (gAnaRun) stopAna(name_version, (char*)NULL); /* STOP REAL-TIME SUB-PROCESS. */ if (gAudioRecord.audiofile) stop_recordAudio(&gAudioRecord.readIndex, &gAudioRecord.audiofile); if (gNetwork) /* Release all avalance avalanches. */ freeNetwork(name_version, (char*)NULL); if (gMidi) { Pa_Sleep(250); moff(name_version, (char*)NULL); /* LOGOUT MIDI BEFORE QUIT. */ } printf("Bye.\n"); Pa_Sleep(500); return 0; /* Unix likes an exit-value. */ }