robert@464: /* robert@464: * RTAudio.cpp robert@464: * robert@464: * Central control code for hard real-time audio on BeagleBone Black robert@464: * using PRU and Xenomai Linux extensions. This code began as part robert@464: * of the Hackable Instruments project (EPSRC) at Queen Mary University robert@464: * of London, 2013-14. robert@464: * robert@464: * (c) 2014 Victor Zappi and Andrew McPherson robert@464: * Queen Mary University of London robert@464: */ robert@464: robert@464: robert@464: #include robert@464: #include robert@464: #include robert@464: #include robert@464: #include robert@464: #include robert@464: #include // interrupt handler robert@464: #include robert@464: #include robert@464: #include // to handle files in dirs robert@464: #include // to check if device is mounted robert@464: #include // mount() robert@464: #include // elapsed time robert@464: #include // neon library robert@464: robert@464: // thread priority robert@464: #include robert@464: #include robert@464: robert@464: // get_opt_long robert@464: #include robert@464: robert@464: #include robert@464: #include "config.h" robert@464: #include "sensors.h" robert@464: #include "OscillatorBank.h" robert@464: #include "StatusLED.h" robert@464: #include "logger.h" robert@464: robert@464: using namespace std; robert@464: robert@464: //---------------------------------------- robert@464: // main variables robert@464: //---------------------------------------- robert@464: vector gOscBanks; robert@464: int gCurrentOscBank = 0; robert@464: int gNextOscBank = 0; robert@464: int oscBnkOversampling = 1; // oscillator bank frame oversampling robert@464: robert@464: const int kStatusLEDPin = 30; // P9-11 controls status LED robert@464: StatusLED gStatusLED; robert@464: robert@464: pthread_t keyboardThread; robert@464: pthread_t logThread; robert@464: robert@464: // general settings robert@464: int gVerbose = 0; // verbose flag robert@464: bool forceKeyboard = true; // activate/deactivate keyboard control robert@464: bool forceSensors = false; // activate/deactivate sensor control robert@464: bool forceLog = true; // activate/deactivate log on boot partition robert@464: bool useSD = true; // activate/deactivate file loading from SD [as opposed to emmc] robert@464: bool useAudioTest = false; // activate/deactivate sensors and test audio only robert@464: robert@464: // audio settings robert@464: unsigned int gPeriodSize = 8; // period size for audio robert@464: char* gPartialFilename = 0; // name of the partials file to load robert@464: bool gAudioIn = false; // stereo audio in status robert@464: robert@464: int touchSensor0Address = 0x0C; // I2C addresses of touch sensors robert@464: int touchSensor1Address = 0x0B; robert@464: int sensorType = 0; robert@464: robert@464: char sdPath[256] = "/dev/mmcblk0p2"; // system path of the SD, partition 2 robert@464: char mountPath[256] = "/root/d-box/usersounds"; // mount point of SD partition 2 [where user files are] robert@464: char gUserDirName[256] = "usersounds"; // Directory in which user analysis files can be found [dir of mountPath] robert@464: char gDefaultDirName[256] = "/root/d-box/sounds"; // Directory in which built in analysis files can be found robert@464: char *gDirName; robert@464: bool gIsLoading = false; robert@464: int fileCnt = 0; robert@464: std::vector files; robert@464: robert@464: char gId = 'f'; // from 0 to 14, hexadecimal [0-d]! f means not set robert@464: char gGroup = '2'; // 0 is no info, 1 info. 2 is not set robert@464: robert@464: // audio in filter robert@464: extern ne10_float32_t *filterState[2]; robert@464: extern ne10_float32_t *filterIn[2]; robert@464: extern ne10_float32_t *filterOut[2]; robert@464: robert@464: struct arg_data robert@464: { robert@464: int argc; robert@464: char **argv; robert@464: }; robert@464: robert@464: arg_data args; robert@464: robert@464: robert@464: int readFiles() robert@464: { robert@464: if(useSD) robert@464: gDirName = gUserDirName; robert@464: else robert@464: gDirName = gDefaultDirName; robert@464: DIR *dir; robert@464: struct dirent *ent; robert@464: robert@464: // From http://stackoverflow.com/questions/612097/how-can-i-get-a-list-of-files-in-a-directory-using-c-or-c robert@464: if ((dir = opendir (gDirName)) != NULL) { robert@464: /* print all the files and directories within directory */ robert@464: while ((ent = readdir (dir)) != NULL) { robert@464: // Ignore dotfiles and . and .. paths robert@464: if(!strncmp(ent->d_name, ".", 1)) robert@464: continue; robert@464: robert@464: //dbox_printf("%s\n", ent->d_name); robert@464: robert@464: // take only .dbx and .txt files robert@464: string name = string(ent->d_name); robert@464: int len = name.length(); robert@464: robert@464: bool dboxFile = false; robert@464: robert@464: if( (name[len-4]=='.') && (name[len-3]=='d') && (name[len-2]=='b') && (name[len-1]=='x') ) robert@464: dboxFile = true; robert@464: if( (name[len-4]=='.') && (name[len-3]=='t') && (name[len-2]=='x') && (name[len-1]=='t') ) robert@464: dboxFile = true; robert@464: robert@464: if(dboxFile) robert@464: { robert@464: fileCnt++; robert@464: //dbox_printf("%s\n", ent->d_name); robert@464: files.push_back( std::string( ent->d_name ) ); robert@464: } robert@464: } robert@464: closedir (dir); robert@464: } else { robert@464: /* could not open directory */ robert@464: printf("Could not open directory %s\n", gDirName); robert@464: return 1; robert@464: } robert@464: robert@464: // order by name robert@464: std::sort( files.begin(), files.end() ); robert@464: robert@464: if(fileCnt==0) robert@464: { robert@464: printf("No .dbx or .txt files in %s!\n", gDirName); robert@464: return 1; robert@464: } robert@464: robert@464: return 0; robert@464: } robert@464: robert@464: /* Load sounds from the directory */ robert@464: void loadAudioFiles(bool loadFirstFile) robert@464: { robert@464: char fullFileName[256]; robert@464: robert@464: if(loadFirstFile) { robert@464: strcpy(fullFileName, gDirName); robert@464: strcat(fullFileName, "/"); robert@464: strncat(fullFileName, files[0].c_str(), 255 - strlen(gDirName)); robert@464: dbox_printf("Loading first file %s...\n", fullFileName); robert@464: OscillatorBank *bank = new OscillatorBank(fullFileName); robert@464: if(bank->initBank(oscBnkOversampling)) { robert@464: bank->setLoopHops(100, bank->getLastHop()); robert@464: gOscBanks.push_back(bank); robert@464: } robert@464: } robert@464: robert@464: else { robert@464: for(int i=1; iinitBank(oscBnkOversampling)) { robert@464: bank->setLoopHops(100, bank->getLastHop()); robert@464: gOscBanks.push_back(bank); robert@464: } robert@464: } robert@464: } robert@464: } robert@464: robert@464: // adapted from http://program-nix.blogspot.co.uk/2008/08/c-language-check-filesystem-is-mounted.html robert@464: int checkIfMounted (char * dev_path) robert@464: { robert@464: FILE * mtab = NULL; robert@464: struct mntent * part = NULL; robert@464: int is_mounted = 0; robert@464: robert@464: if ( ( mtab = setmntent ("/etc/mtab", "r") ) != NULL) robert@464: { robert@464: while ( ( part = getmntent ( mtab) ) != NULL) robert@464: { robert@464: if ( ( part->mnt_fsname != NULL ) && ( strcmp ( part->mnt_fsname, dev_path ) ) == 0 ) robert@464: is_mounted = 1; robert@464: } robert@464: endmntent(mtab); robert@464: } robert@464: return is_mounted; robert@464: } robert@464: robert@464: int mountSDuserPartition() robert@464: { robert@464: if(checkIfMounted(sdPath)) robert@464: { robert@464: printf("device %s already mounted, fair enough, let's move on\n", sdPath); robert@464: return 0; robert@464: } robert@464: // if mount rootfs from SD [rootfs eMMC not used] or from eMMC via properly formatted SD [SD rootfs used as storage volume] robert@464: // we always use rootfs on SD as storage volume ----> "/dev/mmcblk0p2" robert@464: int ret = mount(sdPath, "/root/d-box/usersounds", "vfat", 0, NULL); robert@464: if (ret!=0) robert@464: { robert@464: printf("Error in mount...%s\n", strerror(ret)); robert@464: return 1; robert@464: } robert@464: return 0; robert@464: } robert@464: robert@464: int initSoundFiles() robert@464: { robert@464: if(gVerbose==1) robert@464: cout << "---------------->Init Audio Thread" << endl; robert@464: robert@464: if(useSD) robert@464: { robert@464: // mount the SD partition where user sounds are located robert@464: // [this is p2, p1 is already mounted and we will log data there] robert@464: if(mountSDuserPartition()!=0) robert@464: return -1; robert@464: } robert@464: robert@464: gIsLoading = true; robert@464: robert@464: // read files from SD and order them alphabetically robert@464: if(readFiles()!=0) robert@464: return 1; robert@464: robert@464: // load first file into oscBank robert@464: loadAudioFiles(true); robert@464: robert@464: return 0; robert@464: } robert@464: robert@464: //--------------------------------------------------------------------------------------------------------- robert@464: robert@464: // Handle Ctrl-C robert@464: void interrupt_handler(int var) robert@464: { robert@464: // kill keyboard thread mercilessly robert@464: if(forceKeyboard) robert@464: pthread_cancel(keyboardThread); robert@464: robert@464: gShouldStop = true; robert@464: } robert@464: robert@464: robert@464: void parseArguments(arg_data args, BelaInitSettings *settings) robert@464: { robert@464: // Default filename; robert@464: gPartialFilename = strdup("D-Box_sound_250_60_40_h88_2.txt"); robert@464: robert@464: const int kOptionAudioTest = 1000; robert@464: robert@464: // TODO: complete this robert@464: struct option long_option[] = robert@464: { robert@464: {"help", 0, NULL, 'h'}, robert@464: {"audioin", 1, NULL, 'i'}, robert@464: {"file", 1, NULL, 'f'}, robert@464: {"keyboard", 1, NULL, 'k'}, robert@464: {"audio-test", 0, NULL, kOptionAudioTest}, robert@464: {"sensor-type", 1, NULL, 't'}, robert@464: {"sensor0", 1, NULL, 'q'}, robert@464: {"sensor1", 1, NULL, 'r'}, robert@464: {"log", 1, NULL, 'l'}, robert@464: {"usesd", 1, NULL, 'u'}, robert@464: {"oversamp", 1, NULL, 'o'}, robert@464: {"boxnumber", 1, NULL, 'n'}, robert@464: {"group", 1, NULL, 'g'}, robert@464: {NULL, 0, NULL, 0}, robert@464: }; robert@464: int morehelp = 0; robert@464: int tmp = -1; robert@464: robert@464: Bela_defaultSettings(settings); robert@464: robert@464: while (1) robert@464: { robert@464: int c; robert@464: if ((c = Bela_getopt_long(args.argc, args.argv, "hf:ki:sq:r:t:l:u:o:n:g:", long_option, settings)) < 0) robert@464: break; robert@464: switch (c) robert@464: { robert@464: case 'h': robert@464: morehelp++; robert@464: break; robert@464: case 'f': robert@464: free(gPartialFilename); robert@464: gPartialFilename = strdup(optarg); robert@464: break; robert@464: case 'k': robert@464: forceKeyboard = true; robert@464: break; robert@464: case 'i': robert@464: gAudioIn = (atoi(optarg)==0) ? false : true; robert@464: break; robert@464: case 's': robert@464: forceSensors = true; robert@464: break; robert@464: case kOptionAudioTest: robert@464: useAudioTest = true; robert@464: break; robert@464: case 't': robert@464: sensorType = atoi(optarg); robert@464: break; robert@464: case 'q': robert@464: touchSensor0Address = atoi(optarg); robert@464: break; robert@464: case 'r': robert@464: touchSensor1Address = atoi(optarg); robert@464: break; robert@464: case 'l': robert@464: tmp = atoi(optarg); robert@464: if(tmp==0) robert@464: forceLog = false; robert@464: else if(tmp>0) robert@464: forceLog = true; robert@464: break; robert@464: case 'u': robert@464: tmp = atoi(optarg); robert@464: if(tmp==0) robert@464: useSD = false; robert@464: else if(tmp>0) robert@464: useSD = true; robert@464: break; robert@464: case 'o': robert@464: oscBnkOversampling = atoi(optarg); robert@464: break; robert@464: case 'n': robert@464: gId = *optarg; robert@464: cout << "-set box number to: " << gId << endl; robert@464: break; robert@464: case 'g': robert@464: gGroup = *optarg; robert@464: cout << "-set group to: " << gId << endl; robert@464: break; robert@464: default: robert@464: break; robert@464: } robert@464: } robert@464: robert@464: gPeriodSize = settings->periodSize; robert@464: gVerbose = settings->verbose; robert@464: } robert@464: robert@464: int main(int argc, char *argv[]) robert@464: { robert@464: BelaInitSettings settings; // Standard audio settings robert@464: RT_TASK rtSensorThread; robert@464: const char rtSensorThreadName[] = "dbox-sensor"; robert@464: int oscBankHopSize; robert@464: robert@464: // Parse command-line arguments robert@464: args.argc = argc; robert@464: args.argv = argv; robert@464: parseArguments(args, &settings); robert@464: robert@464: Bela_setVerboseLevel(gVerbose); robert@464: if(gVerbose == 1 && useAudioTest) robert@464: cout << "main() : running in audio test mode" << endl; robert@464: robert@464: // Load sound files from directory robert@464: if(initSoundFiles() != 0) robert@464: return -1; robert@464: robert@464: oscBankHopSize = gOscBanks[gCurrentOscBank]->getHopSize()/gOscBanks[gCurrentOscBank]->getMinSpeed(); robert@464: robert@464: // Initialise the audio device robert@464: if(Bela_initAudio(&settings, &oscBankHopSize) != 0) robert@464: return -1; robert@464: robert@464: // Initialise the status LED robert@464: if(!gStatusLED.init(kStatusLEDPin)) { robert@464: if(gVerbose) robert@464: cout << "Couldn't initialise status LED pin\n"; robert@464: } robert@464: robert@464: // Free file name string which is no longer needed robert@464: if(gPartialFilename != 0) robert@464: free(gPartialFilename); robert@464: robert@464: if(!useAudioTest) { robert@464: if(initSensorLoop(touchSensor0Address, touchSensor1Address, sensorType) != 0) robert@464: return -1; robert@464: } robert@464: robert@464: if(gVerbose == 1) robert@464: cout << "main() : creating audio thread" << endl; robert@464: robert@464: if(Bela_startAudio()) { robert@464: cout << "Error: unable to start real-time audio" << endl; robert@464: return -1; robert@464: } robert@464: robert@464: // LED on... robert@464: gStatusLED.on(); robert@464: robert@464: if(forceSensors && !useAudioTest) { robert@464: if(gVerbose==1) robert@464: cout << "main() : creating control thread" << endl; robert@464: robert@464: if(rt_task_create(&rtSensorThread, rtSensorThreadName, 0, BELA_AUDIO_PRIORITY - 5, T_JOINABLE | T_FPU)) { robert@464: cout << "Error:unable to create Xenomai control thread" << endl; robert@464: return -1; robert@464: } robert@464: if(rt_task_start(&rtSensorThread, &sensorLoop, 0)) { robert@464: cout << "Error:unable to start Xenomai control thread" << endl; robert@464: return -1; robert@464: } robert@464: } robert@464: robert@464: if(forceKeyboard) { robert@464: if(gVerbose==1) robert@464: cout << "main() : creating keyboard thread" << endl; robert@464: robert@464: if ( pthread_create(&keyboardThread, NULL, keyboardLoop, NULL) ) { robert@464: cout << "Error:unable to create keyboard thread" << endl; robert@464: return -1; robert@464: } robert@464: } robert@464: robert@464: if(forceLog) { robert@464: if(gVerbose==1) robert@464: cout << "main() : creating log thread" << endl; robert@464: robert@464: if(initLogLoop()!=0) { robert@464: cout << "Error:unable to create log thread" << endl; robert@464: return -1; robert@464: } robert@464: robert@464: if ( pthread_create(&logThread, NULL, logLoop, NULL) ) { robert@464: cout << "Error:unable to create keyboard thread" << endl; robert@464: return -1; robert@464: } robert@464: } robert@464: robert@464: // Set up interrupt handler to catch Control-C and SIGTERM robert@464: signal(SIGINT, interrupt_handler); robert@464: signal(SIGTERM, interrupt_handler); robert@464: robert@464: // load all other files into oscBanks robert@464: loadAudioFiles(false); robert@464: cout << "Finished loading analysis files\n"; robert@464: gIsLoading = false; robert@464: robert@464: // Run until told to stop robert@464: while(!gShouldStop) { robert@464: usleep(100000); robert@464: } robert@464: robert@464: Bela_stopAudio(); robert@464: robert@464: if(!useAudioTest) robert@464: rt_task_join(&rtSensorThread); robert@464: robert@464: Bela_cleanupAudio(); robert@464: robert@464: pthread_join( keyboardThread, NULL); robert@464: pthread_join( logThread, NULL); robert@464: robert@464: for(unsigned int i = 0; i < gOscBanks.size(); i++) robert@464: delete gOscBanks[i]; robert@464: robert@464: NE10_FREE(filterState[0]); robert@464: NE10_FREE(filterState[1]); robert@464: NE10_FREE(filterIn[0]); robert@464: NE10_FREE(filterIn[1]); robert@464: NE10_FREE(filterOut[0]); robert@464: NE10_FREE(filterOut[1]); robert@464: robert@464: printf("Program ended\nBye bye\n"); robert@464: return 0; robert@464: }