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