Making a Matrix Mixer - issues with release()?

Status
Not open for further replies.

john-mike

Well-known member
Hello,
I would like to create a matrix mixer with X out and X inputs. Using the mixer4 for this is possible but will be very complicated.
The code I have so far is not working properly.

"matrix_mixer.cpp"
Code:
//matrix_mixer.cpp

#include <Arduino.h>
#include "matrix_mixer.h"
#include "utility/dspinst.h"


#define MULTI_UNITYGAIN 65536

static void MMapplyGain(int16_t *data, int32_t mult)
{
	uint32_t *p = (uint32_t *)data;
	const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);

	do {
		uint32_t tmp32 = *p; // read 2 samples from *data
		int32_t val1 = signed_multiply_32x16b(mult, tmp32);
		int32_t val2 = signed_multiply_32x16t(mult, tmp32);
		val1 = signed_saturate_rshift(val1, 16, 0);
		val2 = signed_saturate_rshift(val2, 16, 0);
		*p++ = pack_16b_16b(val2, val1);
	} while (p < end);
}

static void MMapplyGainThenAdd(int16_t *data, const int16_t *in, int32_t mult)
{
	uint32_t *dst = (uint32_t *)data;
	const uint32_t *src = (uint32_t *)in;
	const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);

	if (mult == MULTI_UNITYGAIN) {
		do {
			uint32_t tmp32 = *dst;
			*dst++ = signed_add_16_and_16(tmp32, *src++);
			tmp32 = *dst;
			*dst++ = signed_add_16_and_16(tmp32, *src++);
		} while (dst < end);
	} else {
		do {
			uint32_t tmp32 = *src++; // read 2 samples from *data
			int32_t val1 = signed_multiply_32x16b(mult, tmp32);
			int32_t val2 = signed_multiply_32x16t(mult, tmp32);
			val1 = signed_saturate_rshift(val1, 16, 0);
			val2 = signed_saturate_rshift(val2, 16, 0);
			tmp32 = pack_16b_16b(val2, val1);
			uint32_t tmp32b = *dst;
			*dst++ = signed_add_16_and_16(tmp32, tmp32b);
		} while (dst < end);
	}
}



void AudioMatrixMixer::update(void)
{
	audio_block_t *in, *out = NULL;
	unsigned int channel;
	byte out_sel = 0;
	byte started = 0;
	for (out_sel = 0; out_sel < 4; out_sel++) {
		started = 0;
		for (channel = 0; channel < 4; channel++) {
			if (started == 1) {
				in = receiveReadOnly(channel);
				if (in) {
					MMapplyGainThenAdd(out->data, in->data, multiplier[out_sel][channel]);
					release(in);
				}
			}

			if (started == 0) {
				out = receiveWritable(channel);
				if (out) {
					int32_t mult = multiplier[out_sel][channel];
					if (mult != MULTI_UNITYGAIN) MMapplyGain(out->data, mult);
					started = 1;
				}
			}
		}

		if (out) {
			transmit(out, out_sel);
			release(out);
		}
	}

}

"matrix_mixer.h"
Code:
// matrix_mixer.h

#ifndef matrix_mixer_h_
#define matrix_mixer_h_

#include "Arduino.h"
#include "AudioStream.h"

class AudioMatrixMixer : public AudioStream
{

public:
	AudioMatrixMixer(void) : AudioStream(4, inputQueueArray) {
/*		for (int j = 0; j < 4; j++) {
			for (int i = 0; i < 4; i++) {
				multiplier[j][i] = 65536;
			}*/
		}
		virtual void update(void);

		void gain(unsigned int out_sel, unsigned int channel, float gain) {
			if (channel >= 4) return;
			if (out_sel >= 4) return;
			if (gain > 32767.0f) gain = 32767.0f;
			else if (gain < -32767.0f) gain = -32767.0f;
			multiplier[out_sel][channel] = gain * 65536.0f; // TODO: proper roundoff?
		}
private:
		int32_t multiplier[4][4];
		audio_block_t *inputQueueArray[4];
	};
#endif

Arduino code
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioMatrixMixer               mm1;
AudioSynthWaveform       wf1, wf2, wf3, wf4;
AudioOutputI2S           i2sout;
AudioConnection          patchCord1(wf1, 0, mm1, 0);
AudioConnection          patchCord2(wf2, 0, mm1, 1);
AudioConnection          patchCord3(wf3, 0, mm1, 2);
AudioConnection          patchCord5(mm1, 0, i2sout, 1);
AudioConnection          patchCord6(mm1, 1, i2sout, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=854,187
// GUItool: end automatically generated code

uint32_t cm, prev[4], dm, cu, pu;
float r1, f1, f2;


void setup() {
  AudioNoInterrupts();
  AudioMemory(90);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.75);

  wf1.begin(1, 100 , WAVEFORM_SINE );
  wf2.begin(1, 200 , WAVEFORM_SINE );
  wf3.begin(1, 300 , WAVEFORM_SINE );
  wf4.begin(1, 400 , WAVEFORM_SINE);

  mm1.gain(0, 0, .2);
  mm1.gain(0, 1, .5);
  mm1.gain(0, 2, .4);
  mm1.gain(0, 3, 0);

  mm1.gain(1, 0, .3);
  mm1.gain(1, 1, .3);
  mm1.gain(1, 2, 0);
  mm1.gain(1, 3, 0);

  analogReadResolution(12);
  analogReadAveraging(64);
  AudioInterrupts();
}

void loop() {

   cm = millis();
  if (cm - prev[2] > 250) {
    prev[2] = cm;
    Serial.print(AudioProcessorUsageMax()); 
    Serial.print("  ");
    Serial.println(AudioMemoryUsageMax()); 
    Serial.println();

    AudioProcessorUsageMaxReset();
    AudioMemoryUsageMaxReset();
  }
}

This is a 4x4 matrix test using the normal mixer code.

This only outputs the first channel BUT if I change the update in the cpp file to this...
Code:
void AudioMatrixMixer::update(void)
{
	audio_block_t *in, *out = NULL;
	unsigned int channel;
	byte out_sel = 1;
	byte started = 0;
		started = 0;
		for (channel = 0; channel < 4; channel++) {
			if (started == 1) {
				in = receiveReadOnly(channel);
				if (in) {
					MMapplyGainThenAdd(out->data, in->data, multiplier[out_sel][channel]);
					release(in);
				}
			}

			if (started == 0) {
				out = receiveWritable(channel);
				if (out) {
					int32_t mult = multiplier[out_sel][channel];
					if (mult != MULTI_UNITYGAIN) MMapplyGain(out->data, mult);
					started = 1;
				}
			}
		}

		if (out) {
			transmit(out, out_sel);
			release(out);
		
	}

}
...I can change the active output with "out_sel" and get the intend output on whichever out I want. But the others are silent. So my other code with the multiplier array works.

This must have something to do with my misunderstanding of how the whole receive and release thing works but when I tried to first read all the inputs or put them in a 2d array so each of the 16 individual mixers in this 4x4 array had it's own reading to start with I'd have all kind of issues with freezing and crazy memory reporting.

Here's an example of one thing I've tried that did not work
Code:
void AudioMatrixMixer::update(void)
{
	const byte printo = 0;
	audio_block_t *in = NULL, *out = NULL;
	audio_block_t *mmin[4], *mmo[4];
	if (printo == 1) {Serial.println(" - ");}
	for (byte jj = 0; jj < 4; jj++) {
		mmin[jj] = NULL;
		mmo[jj] = NULL;

	}
	//for (byte out_sel = 0; out_sel < 4; out_sel++) {}

	for (byte jj = 0; jj < 4; jj++) {
		mmin[jj] = receiveWritable(jj);
		//mmo[jj] = mmin[jj];
	}

////////////////
	
		byte started = 0;
		byte out_sel = 0;

		for (byte channel = 0; channel < 4; channel++) {

			if (mmin[channel]) {
				if (started == 1) {
					MMapplyGainThenAdd(mmo[out_sel]->data, mmin[channel]->data, multiplier[out_sel][channel]);
				}
				if (started == 0) {
					mmo[out_sel] = mmin[channel];
					int32_t mult = multiplier[out_sel][channel];
					if (mult != MULTI_UNITYGAIN) MMapplyGain(mmo[out_sel]->data, mult);
					started = 1;
				}

			}
		}
	

	for (byte hh = 0; hh < 4; hh++) {
		if (mmo[hh])	{
			transmit(mmo[hh], hh);
			release(mmo[hh]);
		}

		if (mmin[hh]) {
			release(mmin[hh]);
		}
	}

}


Thanks!
 
For this matrix mixer, I'd recommend using a simpler but less memory efficient approach. Do 4 receiveReadOnly() calls to get the 4 inputs. Don't do any receiveWritable() at all. Check which ones are NULL. If all 4 are NULL, just release them all and transmit nothing.

If 1, 2, 3 or 4 have data, then you'll call allocate() 4 times, to get the 4 output buffers. Remember allocate() can return NULL too. While you could try to create some of the outputs, if the audio lib is running out of memory, usually the best thing you can do is immediately release everything and return without transmitting anything.

Once you have all 4 outputs, and 1 to 4 inputs, then do the mixing work. The normal mixer code is a bit complicated, because it picks one of the 4 to become overwritten. For the sake of getting this working, my main suggestion is to abandon that optimization. Writing this code will be much simpler if you have 4 buffers that are always the outputs, and 1 to 4 which are input only and never are overwritten.

When you're done copying and adding, release the 1-4 input buffers. Then transmit all 4 outputs, and release all 4.

The main thing to avoid is modifying the input buffers. With the normal mixer, this makes things a bit more complicated, but it's still fairly simple because the first non-NULL buffer is simply used as-is. But for this matrix mixer, you'll need to use each input buffer 4 times. If you overwrite the data, then it'll be wrong for the other 3 outputs. It also just makes everything much more complicated. The key to success is keeping things simple. Ditch this optimization. Zero the 4 output buffers, then add each of the inputs into them. Get that working first. Then maybe *after* it's all working flawlessly, maybe you'll see some opportunities for optimizations. But do it the simple way first!
 
Thanks so much for your quick and thorough response!

I've tried a few this but it always ends up with first channel working but the others distorted. It seems like the increment is messed up since the wave is chopped at the regular interval of AUDIO_BLOCK_SAMPLES.

All the other code is the same as before except multiplier[out][in] is a byte so I'm just multiplying and shifting by 8 for now.
Code:
void AudioMatrixMixer::update(void)
{
	audio_block_t  *ins[4], *outs[4];
	int16_t *p[4], ti[4];
	int16_t *po, *end;
	int16_t sum = 0;

	for (byte i = 0; i < 4; i++) {
		ins[i] =  receiveReadOnly(i);
		outs[i] = allocate();
		p[i] = (ins[i]->data);
	}

	for (byte out_sel = 0; out_sel < 4; out_sel++) {

		po = (outs[out_sel]->data);
		end = po + AUDIO_BLOCK_SAMPLES;

		while (po < end) {
			sum = 0;
			for (byte j = 0; j < 4; j++) {
				ti[j] = *p[j]++;
				sum += (ti[j] * (multiplier[out_sel][j])) >> 8; //multiplier value is 0-255

			}
			*po++ = sum;
		}
	}

	for (byte i = 0; i < 4; i++) {
		if (ins[i]) {
			release(ins[i]);
		}
		if (outs[i]) {
			transmit(outs[i], i);
			release(outs[i]);
		}
	}
}
 
Yeah unfortunately it doesn't. Here's a picture of the output. The first is on the top and the second on the bottom. Outs 2 and 3 look similar. There are 128 samples between the distortion which is what I have AUDIO_BLOCK_SAMPLES at.
Changing it to 512 makes large gaps on the bad channel and distorts the first one as seen here. (For some reason it's not letting me upload images)

There are connections to each of the inputs to rule that out as an error.

Here are the other files just to be sure I don't have anything stupid somewhere else.

"matrix_mixer.h"
Code:
// matrix_mixer.h

#ifndef matrix_mixer_h_
#define matrix_mixer_h_

#include "Arduino.h"
#include "AudioStream.h"

class AudioMatrixMixer : public AudioStream
{

public:
	AudioMatrixMixer(void) : AudioStream(4, inputQueueArray) {
		/*		for (int j = 0; j < 4; j++) {
					for (int i = 0; i < 4; i++) {
						multiplier[j][i] = 65536;
					}*/
	}
	virtual void update(void);

	void gain(unsigned int out_sel, unsigned int channel, float gain) {
		if (channel >= 4) return;
		if (out_sel >= 4) return;
		if (gain > 32767.0f) gain = 32767.0f;
		else if (gain < -32767.0f) gain = -32767.0f;
		multiplier[out_sel][channel] = gain * 255.0f; // TODO: proper roundoff?
	}
private:
#define mmax 4

	byte multiplier[mmax][mmax];
	audio_block_t *inputQueueArray[mmax];
};
#endif

Arduino sketch
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioMatrixMixer               mm1;
AudioMixer4                    mixer1;
AudioSynthWaveform       wf1, wf2, wf3, wf4;
AudioOutputI2S           i2sout;
AudioConnection          patchCord1(wf1, 0, mm1, 0);
AudioConnection          patchCord2(wf2, 0, mm1, 1);
AudioConnection          patchCord3(wf3, 0, mm1, 2);
AudioConnection          patchCord4(wf4, 0, mm1, 3);
AudioConnection          patchCord5(mm1, 0, i2sout, 1);
AudioConnection          patchCord6(mm1, 1, i2sout, 0);
AudioConnection          patchCord7(mm1, 2, mixer1, 1);
AudioConnection          patchCord8(mm1, 3, mixer1, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=854,187
// GUItool: end automatically generated code

uint32_t cm, prev[4], dm, cu, pu;
float r1, f1, f2;


void setup() {
  AudioNoInterrupts();
  AudioMemory(90);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.75);

  wf1.begin(1, 100 , WAVEFORM_SINE );//
  wf2.begin(1, 200 , WAVEFORM_SINE );
  wf3.begin(1, 300 , WAVEFORM_SINE );
  wf4.begin(1, 400 , WAVEFORM_SINE);

  mm1.gain(0, 0, 0);
  mm1.gain(0, 1, .5);
  mm1.gain(0, 2, .5);
  mm1.gain(0, 3, 0);

  mm1.gain(1, 0, .5);
  mm1.gain(1, 1, 0);
  mm1.gain(1, 2, 0);
  mm1.gain(1, 3, .5);

  mm1.gain(2, 0, 0);
  mm1.gain(2, 1, 0);
  mm1.gain(2, 2, 0);
  mm1.gain(2, 3, 1);

  mm1.gain(3, 0, 1);
  mm1.gain(3, 1, 0);
  mm1.gain(3, 2, 0);
  mm1.gain(3, 3, 0);

  analogReadResolution(12);
  analogReadAveraging(64);
  AudioInterrupts();
}

void loop() {

   cm = millis();
  if (cm - prev[2] > 250 && 1==1) {
    prev[2] = cm;
    Serial.print(AudioProcessorUsageMax()); 
    Serial.print("  ");
    Serial.println(AudioMemoryUsageMax()); 
    Serial.println();

    AudioProcessorUsageMaxReset();
    AudioMemoryUsageMaxReset();
  }
}
 
Status
Not open for further replies.
Back
Top