changeset 539:b486344aa796 prerelease

merge
author Giulio Moro <giuliomoro@yahoo.it>
date Fri, 24 Jun 2016 01:43:53 +0100
parents 58652b93ef7e (current diff) bfcbeb437869 (diff)
children 67a746eea29e
files
diffstat 9 files changed, 196 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/core/PRU.cpp	Fri Jun 24 01:40:25 2016 +0100
+++ b/core/PRU.cpp	Fri Jun 24 01:43:53 2016 +0100
@@ -334,7 +334,7 @@
 		pru_buffer_spi_dac = (uint16_t *)&pruMem[PRU_MEM_DAC_OFFSET/sizeof(uint32_t)];
 
 		/* ADC memory starts after N(ch)*2(buffers)*bufsize samples */
-		pru_buffer_spi_adc = &pru_buffer_spi_dac[2 * context->analogChannels * context->analogFrames];
+		pru_buffer_spi_adc = &pru_buffer_spi_dac[2 * context->analogInChannels * context->analogFrames];
 	}
 	else {
 		pru_buffer_spi_dac = pru_buffer_spi_adc = 0;
@@ -376,7 +376,12 @@
     }
     if(analog_enabled) {
     	pru_buffer_comm[PRU_USE_SPI] = 1;
-    	pru_buffer_comm[PRU_SPI_NUM_CHANNELS] = context->analogChannels;
+    	if(context->analogInChannels != context->analogOutChannels){
+    		printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
+    		return 1;
+    	}
+    	unsigned int analogChannels = context->analogInChannels;
+    	pru_buffer_comm[PRU_SPI_NUM_CHANNELS] = analogChannels;
     }
     else {
     	pru_buffer_comm[PRU_USE_SPI] = 0;
@@ -460,9 +465,9 @@
 			return 1;
 		}		
 #else
-		context->analogIn = (float *)malloc(context->analogChannels * context->analogFrames * sizeof(float));
-		context->analogOut = (float *)malloc(context->analogChannels * context->analogFrames * sizeof(float));
-		last_analog_out_frame = (float *)malloc(context->analogChannels * sizeof(float));
+		context->analogIn = (float *)malloc(context->analogInChannels * context->analogFrames * sizeof(float));
+		context->analogOut = (float *)malloc(context->analogOutChannels * context->analogFrames * sizeof(float));
+		last_analog_out_frame = (float *)malloc(context->analogOutChannels * sizeof(float));
 
 		if(context->analogIn == 0 || context->analogOut == 0 || last_analog_out_frame == 0) {
 			rt_printf("Error: couldn't allocate analog buffers\n");
@@ -470,7 +475,7 @@
 		}
 #endif
 		
-		memset(last_analog_out_frame, 0, context->analogChannels * sizeof(float));
+		memset(last_analog_out_frame, 0, context->analogOutChannels * sizeof(float));
 	}
 
 	// Allocate digital buffers
@@ -528,7 +533,12 @@
 	RTIME irqTimeout = PRU_SAMPLE_INTERVAL_NS * 1024;	// Timeout for PRU interrupt: about 10ms, much longer than any expected period
 #else
 	// Polling interval is 1/4 of the period
-	RTIME sleepTime = PRU_SAMPLE_INTERVAL_NS * (context->analogChannels / 2) * context->analogFrames / 4;
+	if(context->analogInChannels != context->analogOutChannels){
+		printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
+		return;
+	}
+	unsigned int analogChannels = context->analogInChannels;
+	RTIME sleepTime = PRU_SAMPLE_INTERVAL_NS * (analogChannels / 2) * context->analogFrames / 4;
 #endif
 
 	uint32_t pru_audio_offset, pru_spi_offset;
@@ -540,8 +550,8 @@
 	if(analog_enabled) {
 		if(context->flags & BELA_FLAG_ANALOG_OUTPUTS_PERSIST) {
 			// Remember the content of the last_analog_out_frame
-			for(unsigned int ch = 0; ch < context->analogChannels; ch++){
-				last_analog_out_frame[ch] = context->analogOut[context->analogChannels * (context->analogFrames - 1) + ch];
+			for(unsigned int ch = 0; ch < context->analogOutChannels; ch++){
+				last_analog_out_frame[ch] = context->analogOut[context->analogOutChannels * (context->analogFrames - 1) + ch];
 			}
 		}
 	}
@@ -605,8 +615,18 @@
 		}
 		else {
 			// PRU is on buffer 0. We read and write to buffer 1
-			pru_audio_offset = context->audioFrames * 2;
-			pru_spi_offset = context->analogFrames * context->analogChannels;
+			if(context->audioInChannels != context->audioOutChannels){
+				printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
+				return;
+			}
+			unsigned int audioChannels = context->audioInChannels;
+			pru_audio_offset = context->audioFrames * audioChannels;
+			if(context->analogInChannels != context->analogOutChannels){
+				printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
+				return;
+			}
+	    	unsigned int analogChannels = context->analogInChannels;
+			pru_spi_offset = context->analogFrames * analogChannels;
 			if(digital_enabled)
 				context->digital = digital_buffer1;
 		}
@@ -644,22 +664,22 @@
 			int16_to_float_analog(context->analogChannels * context->analogFrames, 
 									&pru_buffer_spi_adc[pru_spi_offset], context->analogIn);
 #else	
-			for(unsigned int n = 0; n < context->analogChannels * context->analogFrames; n++) {
+			for(unsigned int n = 0; n < context->analogInChannels * context->analogFrames; n++) {
 				context->analogIn[n] = (float)pru_buffer_spi_adc[n + pru_spi_offset] / 65536.0f;
 			}
 #endif
 
 			if(context->flags & BELA_FLAG_ANALOG_OUTPUTS_PERSIST) {
 				// Initialize the output buffer with the values that were in the last frame of the previous output
-				for(unsigned int ch = 0; ch < context->analogChannels; ch++){
+				for(unsigned int ch = 0; ch < context->analogOutChannels; ch++){
 					for(unsigned int n = 0; n < context->analogFrames; n++){
-						context->analogOut[n * context->analogChannels + ch] = last_analog_out_frame[ch];
+						context->analogOut[n * context->analogOutChannels + ch] = last_analog_out_frame[ch];
 					}
 				}
 			}
 			else {
 				// Outputs are 0 unless set otherwise
-				memset(context->analogOut, 0, context->analogChannels * context->analogFrames * sizeof(float));
+				memset(context->analogOut, 0, context->analogOutChannels * context->analogFrames * sizeof(float));
 			}
 		}
 
@@ -688,8 +708,8 @@
 		if(analog_enabled) {
 			if(context->flags & BELA_FLAG_ANALOG_OUTPUTS_PERSIST) {
 				// Remember the content of the last_analog_out_frame
-				for(unsigned int ch = 0; ch < context->analogChannels; ch++){
-					last_analog_out_frame[ch] = context->analogOut[context->analogChannels * (context->analogFrames - 1) + ch];
+				for(unsigned int ch = 0; ch < context->analogOutChannels; ch++){
+					last_analog_out_frame[ch] = context->analogOut[context->analogOutChannels * (context->analogFrames - 1) + ch];
 				}
 			}
 
@@ -698,7 +718,7 @@
 			float_to_int16_analog(context->analogChannels * context->analogFrames, 
 								  context->analogOut, (uint16_t*)&pru_buffer_spi_dac[pru_spi_offset]);
 #else		
-			for(unsigned int n = 0; n < context->analogChannels * context->analogFrames; n++) {
+			for(unsigned int n = 0; n < context->analogOutChannels * context->analogFrames; n++) {
 				int out = context->analogOut[n] * 65536.0f;
 				if(out < 0) out = 0;
 				else if(out > 65535) out = 65535;
@@ -717,7 +737,7 @@
 #ifdef USE_NEON_FORMAT_CONVERSION
 		float_to_int16_audio(2 * context->audioFrames, context->audioOut, &pru_buffer_audio_dac[pru_audio_offset]);
 #else	
-		for(unsigned int n = 0; n < 2 * context->audioFrames; n++) {
+		for(unsigned int n = 0; n < context->audioOutChannels * context->audioFrames; n++) {
 			int out = context->audioOut[n] * 32768.0f;
 			if(out < -32768) out = -32768;
 			else if(out > 32767) out = 32767;
@@ -735,9 +755,6 @@
 		
 		Bela_autoScheduleAuxiliaryTasks();
 
-		// FIXME: TESTING!!
-		// if(testCount > 100000)
-		//	break;
 	}
 
 #ifdef BELA_USE_XENOMAI_INTERRUPTS
@@ -745,11 +762,6 @@
 	rt_intr_disable(pru_interrupt);
 #endif
 
-	// FIXME: TESTING
-	// RTIME endTime = rt_timer_read();
-	// RTIME diffTime = endTime - startTime;
-	// rt_printf("%d blocks elapsed in %f seconds, %f Hz block rate\n", testCount, ((float)diffTime / 1.0e9), (float)testCount / ((float)diffTime / 1.0e9));
-
 	// Tell PRU to stop
 	pru_buffer_comm[PRU_SHOULD_STOP] = 1;
 
--- a/core/RTAudio.cpp	Fri Jun 24 01:40:25 2016 +0100
+++ b/core/RTAudio.cpp	Fri Jun 24 01:43:53 2016 +0100
@@ -167,38 +167,55 @@
 		}
 	}
 
+	if(settings->numAnalogInChannels != settings->numAnalogOutChannels){
+		printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
+		return 1;
+	}
+	unsigned int numAnalogChannels = settings->numAnalogInChannels;
 	// Limit the analog channels to sane values
-	if(settings->numAnalogChannels != 2 
-		&& settings->numAnalogChannels != 4
-		&& settings->numAnalogChannels != 8) {
-			cout << "Invalid number of analog channels: " << settings->numAnalogChannels << ". Valid values are 2, 4, 8.\n";
+	if(numAnalogChannels != 2
+		&& numAnalogChannels != 4
+		&& numAnalogChannels != 8) {
+			cout << "Invalid number of analog channels: " << numAnalogChannels << ". Valid values are 2, 4, 8.\n";
 			return -1;
 	}
 
 	// Initialise the rendering environment: sample rates, frame counts, numbers of channels
 	gContext.audioSampleRate = 44100.0;
-	gContext.audioChannels = 2;
+
+	// TODO: settings a different number of channels for inputs and outputs is not yet supported
+	gContext.audioInChannels = 2;
+	gContext.audioOutChannels = 2;
 
 	if(settings->useAnalog) {
 		gContext.audioFrames = settings->periodSize;
 
-		gContext.analogFrames = gContext.audioFrames * 4 / settings->numAnalogChannels;
-		gContext.analogChannels = settings->numAnalogChannels;
-		gContext.analogSampleRate = gContext.audioSampleRate * 4.0 / (float)settings->numAnalogChannels;
+		// TODO: a different number of channels for inputs and outputs is not yet supported
+		gContext.analogFrames = gContext.audioFrames * 4 / settings->numAnalogInChannels;
+		gContext.analogInChannels = settings->numAnalogInChannels;
+		gContext.analogOutChannels = settings->numAnalogOutChannels;
+		unsigned int numAnalogChannelsForSampleRate = settings->numAnalogInChannels;
+		gContext.analogSampleRate = gContext.audioSampleRate * 4.0 / (float)numAnalogChannelsForSampleRate;
 	}
 	else {
 		gContext.audioFrames = settings->periodSize;
 
 		gContext.analogFrames = 0;
-		gContext.analogChannels = 0;
+		gContext.analogInChannels = 0;
+		gContext.analogOutChannels = 0;
 		gContext.analogSampleRate = 0;
 	}
 
+	if(gContext.analogInChannels != gContext.analogOutChannels){
+		printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
+		return -1;
+	}
+	unsigned int analogChannels = gContext.analogInChannels;
 	// Sanity check the combination of channels and period size
-	if( gContext.analogChannels != 0 && ((gContext.analogChannels <= 4 && gContext.analogFrames < 2) ||
-			(gContext.analogChannels <= 2 && gContext.analogFrames < 4)) )
+	if( analogChannels != 0 && ((analogChannels <= 4 && gContext.analogFrames < 2) ||
+			(analogChannels <= 2 && gContext.analogFrames < 4)) )
 	{
-		cout << "Error: " << gContext.analogChannels << " channels and period size of " << gContext.analogFrames << " not supported.\n";
+		cout << "Error: " << analogChannels << " channels and period size of " << gContext.analogFrames << " not supported.\n";
 		return 1;
 	}
 
@@ -231,7 +248,12 @@
 	}
 	
 	// Get the PRU memory buffers ready to go
-	if(gPRU->initialise(settings->pruNumber, gContext.analogFrames, gContext.analogChannels,
+	if(gContext.analogInChannels != gContext.analogOutChannels){
+		printf("Error: TODO: a different number of channels for inputs and outputs is not yet supported\n");
+		return 1;
+	}
+
+	if(gPRU->initialise(settings->pruNumber, gContext.analogFrames, analogChannels,
 		 				settings->numMuxChannels, true)) {
 		cout << "Error: unable to initialise PRU\n";
 		return 1;
--- a/core/RTAudioCommandLine.cpp	Fri Jun 24 01:40:25 2016 +0100
+++ b/core/RTAudioCommandLine.cpp	Fri Jun 24 01:43:53 2016 +0100
@@ -53,7 +53,11 @@
 	settings->periodSize = 16;
 	settings->useAnalog = 1;
 	settings->useDigital = 1;
-	settings->numAnalogChannels = 8;
+	settings->numAudioInChannels = 2;
+	settings->numAudioOutChannels = 2;
+
+	settings->numAnalogInChannels = 8;
+	settings->numAnalogOutChannels = 8;
 	settings->numDigitalChannels = 16;
 
 	settings->beginMuted = 0;
@@ -168,23 +172,35 @@
 				settings->numDigitalChannels = 0;
 			}
 			break;
-		case 'C':
-			settings->numAnalogChannels = atoi(optarg);
-			if(settings->numAnalogChannels >= 8) {
+		case 'C': {
+			// TODO: a different number of channels for inputs and outputs is not yet supported
+			unsigned int numAnalogChannels = atoi(optarg);
+			settings->numAnalogInChannels = numAnalogChannels;
+			settings->numAnalogOutChannels = numAnalogChannels;
+			if(numAnalogChannels >= 8) {
+				// TODO: a different number of channels for inputs and outputs is not yet supported
+
 				// Use multiplexer capelet to run larger numbers of channels
-				if(settings->numAnalogChannels >= 64)
+				if(settings->numAnalogInChannels >= 64)
 					settings->numMuxChannels = 8;
-				else if(settings->numAnalogChannels >= 32)
+				else if(settings->numAnalogInChannels >= 32)
 					settings->numMuxChannels = 4;
-				else if(settings->numAnalogChannels >= 16)
+				else if(settings->numAnalogInChannels >= 16)
 					settings->numMuxChannels = 2;
-				settings->numAnalogChannels = 8;
+				settings->numAnalogInChannels = 8;
 			}
-			else if(settings->numAnalogChannels >= 4)
-				settings->numAnalogChannels = 4;
-			else
-				settings->numAnalogChannels = 2;
+			else if(numAnalogChannels >= 4){
+				// TODO: a different number of channels for inputs and outputs is not yet supported
+				settings->numAnalogInChannels = 4;
+				settings->numAnalogOutChannels = 4;
+			}
+			else{
+				// TODO: a different number of channels for inputs and outputs is not yet supported
+				settings->numAnalogInChannels = 2;
+				settings->numAnalogOutChannels = 2;
+			}
 			break;
+		}
 		case 'B':
 			settings->numDigitalChannels = atoi(optarg);
 			if(settings->numDigitalChannels >= 16)
--- a/core/default_libpd_render.cpp	Fri Jun 24 01:40:25 2016 +0100
+++ b/core/default_libpd_render.cpp	Fri Jun 24 01:43:53 2016 +0100
@@ -136,9 +136,13 @@
 		printf("Error file %s/%s not found. The %s file should be your main patch.\n", folder, file, file);
 		return false;
 	}
-
+	if(context->analogInChannels != context->analogOutChannels ||
+			context->audioInChannels != context->audioOutChannels){
+		printf("This project requires the number of inputs and the number of outputs to be the same\n");
+		return false;
+	}
 	// analog setup
-	gAnalogChannelsInUse = context->analogChannels;
+	gAnalogChannelsInUse = context->analogInChannels;
 
 	// digital setup
 	dcm.setCallback(sendDigitalMessage);
@@ -305,27 +309,27 @@
 		float* p0;
 		float* p1;
 		for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
-			for (k = 0, p1 = p0; k < context->audioChannels; k++, p1 += gLibpdBlockSize) {
+			for (k = 0, p1 = p0; k < context->audioInChannels; k++, p1 += gLibpdBlockSize) {
 				*p1 = audioRead(context, audioFrameBase + j, k);
 			}
 		}
 		// then analogs
 		// this loop resamples by ZOH, as needed, using m
-		if(context->analogChannels == 8 ){ //hold the value for two frames
+		if(context->analogInChannels == 8 ){ //hold the value for two frames
 			for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
 				for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) {
 					unsigned int analogFrame = (audioFrameBase + j) / 2;
 					*p1 = analogRead(context, analogFrame, k);
 				}
 			}
-		} else if(context->analogChannels == 4){ //write every frame
+		} else if(context->analogInChannels == 4){ //write every frame
 			for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
 				for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) {
 					unsigned int analogFrame = audioFrameBase + j;
 					*p1 = analogRead(context, analogFrame, k);
 				}
 			}
-		} else if(context->analogChannels == 2){ //drop every other frame
+		} else if(context->analogInChannels == 2){ //drop every other frame
 			for (j = 0, p0 = gInBuf; j < gLibpdBlockSize; j++, p0++) {
 				for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; ++k, p1 += gLibpdBlockSize) {
 					unsigned int analogFrame = (audioFrameBase + j) * 2;
@@ -369,7 +373,7 @@
 
 		//audio
 		for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
-			for (k = 0, p1 = p0; k < context->audioChannels; k++, p1 += gLibpdBlockSize) {
+			for (k = 0, p1 = p0; k < context->audioOutChannels; k++, p1 += gLibpdBlockSize) {
 				audioWrite(context, audioFrameBase + j, k, *p1);
 			}
 		}
@@ -383,23 +387,23 @@
 
 
 		//analog
-		if(context->analogChannels == 8){
+		if(context->analogOutChannels == 8){
 			for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j += 2, p0 += 2) { //write every two frames
 				unsigned int analogFrame = (audioFrameBase + j) / 2;
 				for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
 					analogWriteOnce(context, analogFrame, k, *p1);
 				}
 			}
-		} else if(context->analogChannels == 4){ //write every frame
+		} else if(context->analogOutChannels == 4){ //write every frame
 			for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; ++j, ++p0) {
 				unsigned int analogFrame = (audioFrameBase + j);
-				for (k = 0, p1 = p0  + gLibpdBlockSize * context->audioChannels; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
+				for (k = 0, p1 = p0  + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
 					analogWriteOnce(context, analogFrame, k, *p1);
 				}
 			}
-		} else if(context->analogChannels == 2){ //write every frame twice
+		} else if(context->analogOutChannels == 2){ //write every frame twice
 			for (j = 0, p0 = gOutBuf; j < gLibpdBlockSize; j++, p0++) {
-				for (k = 0, p1 = p0 + gLibpdBlockSize * context->audioChannels; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
+				for (k = 0, p1 = p0 + gLibpdBlockSize * gFirstAnalogChannel; k < gAnalogChannelsInUse; k++, p1 += gLibpdBlockSize) {
 					int analogFrame = audioFrameBase * 2 + j * 2;
 					analogWriteOnce(context, analogFrame, k, *p1);
 					analogWriteOnce(context, analogFrame + 1, k, *p1);
--- a/examples/01-Basics/passthrough/render.cpp	Fri Jun 24 01:40:25 2016 +0100
+++ b/examples/01-Basics/passthrough/render.cpp	Fri Jun 24 01:43:53 2016 +0100
@@ -22,37 +22,43 @@
 */
 
 #include <Bela.h>
-#include <rtdk.h>
 
 bool setup(BelaContext *context, void *userData)
 {
 	// Nothing to do here...
+	if(context->audioInChannels != context->audioOutChannels ||
+			context->analogInChannels != context-> analogOutChannels){
+		printf("Error: for this project, you need the same number of input and output channels.\n");
+		return false;
+	}
 	return true;
 }
 
 void render(BelaContext *context, void *userData)
 {
+
 	// Simplest possible case: pass inputs through to outputs
 	for(unsigned int n = 0; n < context->audioFrames; n++) {
-		for(unsigned int ch = 0; ch < context->audioChannels; ch++){
+		for(unsigned int ch = 0; ch < context->audioInChannels; ch++){
 			// Two equivalent ways to write this code
 
 			// The long way, using the buffers directly:
-			// context->audioOut[n * context->audioChannels + ch] =
-			// 		context->audioIn[n * context->audioChannels + ch];
+			// context->audioOut[n * context->audioOutChannels + ch] =
+			// 		context->audioIn[n * context->audioInChannels + ch];
 
 			// Or using the macros:
 			audioWrite(context, n, ch, audioRead(context, n, ch));
 		}
 	}
 
-	// Same with analog channelss
+	// Same with analog channels
 	for(unsigned int n = 0; n < context->analogFrames; n++) {
-		for(unsigned int ch = 0; ch < context->analogChannels; ch++) {
+		for(unsigned int ch = 0; ch < context->analogInChannels; ch++) {
 			// Two equivalent ways to write this code
 
 			// The long way, using the buffers directly:
-			// context->analogOut[n * context->analogChannels + ch] = context->analogIn[n * context->analogChannels + ch];
+			// context->analogOut[n * context->analogOutChannels + ch] =
+			//  	context->analogIn[n * context->analogInChannels + ch];
 
 			// Or using the macros:
 			analogWrite(context, n, ch, analogRead(context, n, ch));
--- a/examples/03-Analog/analog-input/render.cpp	Fri Jun 24 01:40:25 2016 +0100
+++ b/examples/03-Analog/analog-input/render.cpp	Fri Jun 24 01:43:53 2016 +0100
@@ -68,7 +68,7 @@
 
 		float out = amplitude * sinf(gPhase);
 
-		for(unsigned int channel = 0; channel < context->audioChannels; channel++)
+		for(unsigned int channel = 0; channel < context->audioOutChannels; channel++)
 			context->audioOut[n * context->audioChannels + channel] = out;
 
 		gPhase += 2.0 * M_PI * frequency * gInverseSampleRate;
--- a/include/Bela.h	Fri Jun 24 01:40:25 2016 +0100
+++ b/include/Bela.h	Fri Jun 24 01:43:53 2016 +0100
@@ -112,8 +112,14 @@
 	int useAnalog;
 	/// Whether to use the 16 programmable GPIOs
 	int useDigital;
-	/// How many channels for the ADC and DAC
-	int numAnalogChannels;
+	/// How many audio input channels
+	int numAudioInChannels;
+	/// How many audio out channels
+	int numAudioOutChannels;
+	/// How many analog input channels
+	int numAnalogInChannels;
+	/// How many analog output channels
+	int numAnalogOutChannels;
 	/// How many channels for the GPIOs
 	int numDigitalChannels;
 
@@ -206,8 +212,10 @@
 
 	/// Number of audio frames per period
 	const uint32_t audioFrames;
-	/// Number of audio channels (currently always 2)
-	const uint32_t audioChannels;
+	/// Number of input audio channels
+	const uint32_t audioInChannels;
+	/// Number of output audio channels
+	const uint32_t audioOutChannels;
 	/// Audio sample rate in Hz (currently always 44100.0)
 	const float audioSampleRate;
 
@@ -216,10 +224,15 @@
 	/// This will be 0 if analog I/O is disabled.
 	const uint32_t analogFrames;
 
-	/// \brief Number of analog channels
+	/// \brief Number of input analog channels
 	///
-	/// This could take a value of 8, 4 or 2. This will be 0 if analog I/O is disabled.
-	const uint32_t analogChannels;
+	/// This will be 0 if analog I/O is disabled.
+	const uint32_t analogInChannels;
+
+	/// \brief Number of output analog channels
+	///
+	/// This will be 0 if analog I/O is disabled.
+	const uint32_t analogOutChannels;
 
 	/// \brief Analog sample rate in Hz
 	///
--- a/include/PRU.h	Fri Jun 24 01:40:25 2016 +0100
+++ b/include/PRU.h	Fri Jun 24 01:43:53 2016 +0100
@@ -55,8 +55,10 @@
 
 	/// Number of audio frames per period
 	uint32_t audioFrames;
-	/// Number of audio channels (currently always 2)
-	uint32_t audioChannels;
+	/// Number of input audio channels
+	uint32_t audioInChannels;
+	/// Number of output audio channels
+	uint32_t audioOutChannels;
 	/// Audio sample rate in Hz (currently always 44100.0)
 	float audioSampleRate;
 
@@ -65,10 +67,15 @@
 	/// This will be 0 if analog I/O is disabled.
 	uint32_t analogFrames;
 
-	/// \brief Number of analog channels
+	/// \brief Number of input analog channels
 	///
-	/// This could take a value of 8, 4 or 2. This will be 0 if analog I/O is disabled.
-	uint32_t analogChannels;
+	/// This will be 0 if analog I/O is disabled.
+	uint32_t analogInChannels;
+
+	/// \brief Number of output analog channels
+	///
+	/// This will be 0 if analog I/O is disabled.
+	uint32_t analogOutChannels;
 
 	/// \brief Analog sample rate in Hz
 	///
--- a/include/Utilities.h	Fri Jun 24 01:40:25 2016 +0100
+++ b/include/Utilities.h	Fri Jun 24 01:43:53 2016 +0100
@@ -42,7 +42,7 @@
 /** @} */
 
 /**
- * \ingroup wiring
+ * \cond BIT_FUNCTIONS
  *
  * @{
  */
@@ -59,7 +59,9 @@
 /// Set/clear the given bit in \c word to \c value.
 #define changeBit(word,bit,value) 	((clearBit((word),(bit))) | ((value) << (bit)))
 
-/** @} */
+/** @}
+ * \endcond
+ * */
 
 /**
  * \ingroup iofunctions
@@ -294,26 +296,40 @@
  */
 static inline float constrain(float x, float min_val, float max_val);
 
+/**
+ * \brief Returns the maximum of two numbers
+ *
+ * Returns the maximum of two numbers
+ */
+static inline float min(float x, float y);
+
+/**
+ * \brief Returns the minimum of two numbers
+ *
+ * Returns the minimum of two numbers
+ */
+static inline float max(float x, float y);
+
 /** @} */
 // audioRead()
 //
 // Returns the value of the given audio input at the given frame number.
 static inline float audioRead(BelaContext *context, int frame, int channel) {
-	return context->audioIn[frame * context->audioChannels + channel];
+	return context->audioIn[frame * context->audioInChannels + channel];
 }
 
 // audioWrite()
 //
 // Sets a given audio output channel to a value for the current frame
 static inline void audioWrite(BelaContext *context, int frame, int channel, float value) {
-	context->audioOut[frame * context->audioChannels + channel] = value;
+	context->audioOut[frame * context->audioOutChannels + channel] = value;
 }
 
 // analogRead()
 //
 // Returns the value of the given analog input at the given frame number.
 static inline float analogRead(BelaContext *context, int frame, int channel) {
-	return context->analogIn[frame * context->analogChannels + channel];
+	return context->analogIn[frame * context->analogInChannels + channel];
 }
 
 // analogWrite()
@@ -323,17 +339,17 @@
 static inline void analogWrite(BelaContext *context, int frame, int channel, float value) {
 	if(context->flags & BELA_FLAG_ANALOG_OUTPUTS_PERSIST) {
 		for(unsigned int f = frame; f < context->analogFrames; f++)
-			context->analogOut[frame * context->analogChannels + channel] = value;
+			context->analogOut[frame * context->analogOutChannels + channel] = value;
 	}
 	else
-		context->analogOut[frame * context->analogChannels + channel] = value;
+		context->analogOut[frame * context->analogOutChannels + channel] = value;
 }
 
 // analogWriteOnce()
 //
 // Sets a given channel to a value for only the current frame
 static inline void analogWriteOnce(BelaContext *context, int frame, int channel, float value) {
-	context->analogOut[frame * context->analogChannels + channel] = value;
+	context->analogOut[frame * context->analogOutChannels + channel] = value;
 }
 
 // digitalRead()
@@ -412,4 +428,12 @@
 	return x;
 }
 
+static inline float max(float x, float y){
+	return x > y ? x : y;
+}
+
+static inline float min(float x, float y){
+	return x < y ? x : y;
+}
+
 #endif /* UTILITIES_H_ */