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 <stdio.h>
robert@464: #include <stdlib.h>
robert@464: #include <string.h>
robert@464: #include <strings.h>
robert@464: #include <math.h>
robert@464: #include <iostream>
robert@464: #include <signal.h>		// interrupt handler
robert@464: #include <assert.h>
robert@464: #include <vector>
robert@464: #include <dirent.h>		// to handle files in dirs
robert@464: #include <mntent.h>		// to check if device is mounted
robert@464: #include <sys/mount.h>	// mount()
robert@464: #include <sys/time.h>	// elapsed time
robert@464: #include <ne10/NE10.h>	// neon library
robert@464: 
robert@464: // thread priority
robert@464: #include <pthread.h>
robert@464: #include <sched.h>
robert@464: 
robert@464: // get_opt_long
robert@464: #include <getopt.h>
robert@464: 
robert@464: #include <Bela.h>
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<OscillatorBank*> 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 <std::string> 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; i<fileCnt; i++){
robert@464: 			strcpy(fullFileName, gDirName);
robert@464: 			strcat(fullFileName, "/");
robert@464: 			strncat(fullFileName, files[i].c_str(), 255 - strlen(gDirName));
robert@464: 			dbox_printf("Loading 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: }
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: }