Create audio connections in a for loop for large amounts of drums/recorders etc

Hello, my final goal is to be able to record audio at the same time that I play it back, in such a way that when the audio loops, the new recording is added on top of what is being played (basically standard looper pedal). However, this questions would be good to know for several projects outside of the the looper.

Imagine i had 100 drums, going to 25 mixers, going to 7 mixers, going to 2 mixers, then finally the combined output. Instead of laying out the patchchords manually on the gui(which would be fast enough) I was wondering if it would be possible to create an array of 100 drum objects, patch chord them all together with some coding, and end up with the final result. However, I am relatively new to c++, arduino, and coding in general.

Just in a test program before the actual one, I tried to have input from a mic, a regularly defined drum, and two drums which are in an array and patched in a for loop. However, I was running into issues with patching.

Relevant code:

(outside of setup)

AudioSynthSimpleDrum    drumsArr[2];
AudioConnection connectionArr[2];

(in setup)

  for(int i = 0; i < (sizeof(drumsArr)/sizeof(drumsArr[0])); i++){
    drumsArr[i].length(250);
    drumsArr[i].secondMix(0.0);
    drumsArr[i].pitchMod(0.55);
  
    connectionArr[i].connect(drumsArr[i],0,mixer1,i+2);
  }

  drumsArr[0].frequency(174);
  drumsArr[1].frequency(196);

  drum1.frequency(440);
  drum1.length(250);
  drum1.secondMix(0.0);
  drum1.pitchMod(0.55);

loop:
drum1.noteOn();
  for(int i = 0; i<2; i++){
    Serial.print("i: "); Serial.println(i);
    drumsArr[i].noteOn();
    delay(2000);
  }

error message:
Compilation error: no matching function for call to 'AudioConnection::AudioConnection()'
(at the line AudioConnection connectionArr[2];)
I also tried writing it as
AudioConnection connectionArr2 and
AudioConnection connectionArr()[2]
neither worked.

Full code:


#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S            input;           //xy=112.00001525878906,148.00000190734863
AudioSynthSimpleDrum     drum1;          //xy=216.40000915527344,203.39999771118164
AudioMixer4              inputMixer; //xy=244,154
AudioMixer4              mixer1;         //xy=448.00000762939453,233.4000129699707
AudioOutputI2S           i2s1;           //xy=632.0000076293945,171.00001335144043
AudioConnection          patchCord1(input, 0, inputMixer, 0);
AudioConnection          patchCord2(input, 1, inputMixer, 1);
AudioConnection          patchCord3(drum1, 0, mixer1, 1);
AudioConnection          patchCord4(inputMixer, 0, mixer1, 0);
AudioConnection          patchCord5(mixer1, 0, i2s1, 0);
AudioConnection          patchCord6(mixer1, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=394.6000061035156,352.4000015258789

AudioSynthSimpleDrum    drumsArr[2];
AudioConnection connectionArr[2];
void setup() {

  Serial.begin(9600);

  AudioMemory(20);

  sgtl5000_1.enable();
  sgtl5000_1.volume(.5);
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  sgtl5000_1.autoVolumeControl(1,3,0,-18,6,8);

  for(int i = 0; i < (sizeof(drumsArr)/sizeof(drumsArr[0])); i++){
    drumsArr[i].length(250);
    drumsArr[i].secondMix(0.0);
    drumsArr[i].pitchMod(0.55);
  
    connectionArr[i].connect(drumsArr[i],0,mixer1,i+2);
  }

  drumsArr[0].frequency(174);
  drumsArr[1].frequency(196);

  drum1.frequency(440);
  drum1.length(250);
  drum1.secondMix(0.0);
  drum1.pitchMod(0.55);
}

void loop() {
  drum1.noteOn();
  for(int i = 0; i<2; i++){
    Serial.print("i: "); Serial.println(i);
    drumsArr[i].noteOn();
    delay(2000);
  }
  
}

in the recorder code, im planning on saving each "layer" of the recording in a different recording object, and I don't know how long or how many layers there will be. Hence, the need to create new recording object, patchchords, and mixers whenver the max is used.

1 Like

Yes, now imagine what sort of processor that would take to do and you are way out of the league of any Arduino that has been built so far.

The closest you might come to that, and that is not very close, is to use a Teensy 4.1.

Yeah, im using the teensy 4.1 w/ rev d audio shield. Even if its not such an enormous number, how would you go about "dynamically" creating the patchchords in code? I want to test the limits on my own and see how i could apply them. The final goal is to use the recorder objects in this layout, and i want to see how far i can take it (max length of recording 1 layer vs max num recordings where length is 1 sec, etc)

Not too sure what you mean by patch chords? Do you mean the things you can make in the graphic sound interface?

If so basically you can't do this. The graphic interface produces code that you then include when you compiled the rest of the code. To do that on the fly you would need to play about with self modifying code. This is a technique that is almost impossible on controllers on a chip as the architecture keeps data and code in physically separate memory spaces with little or no access from one to another.

On the first computer I ever made way back in the mid 70's using a Signetics 2650 CPU I was able to write some code that used bytes, accessed from memory and use them to create a tone for each byte I read. Now the clever part was that the bytes it used to produce the tones were the bytes of the program itself. So in effect the program played itself. It could do this because the memory containing the code was the same as the memory containing data. That is sometimes called a Von Neumann machine or Princeton Architecture. With this you can write self modifying code. However, if you are modifying more than just a couple of machine code instructions then it is generally considered a bit of a taboo subject.

The all in one computer chips tend to use a slightly modified version of Harvard architecture which goes out of its way to avoid these self modifying techniques.

Note this only applies to actual machine code, not any higher level language like C. So you will have to use assembler to generate it. Self modifying code is also a nightmare to debug and often results in very unstable code.

Your best bet is to write code that can do things according to numbers you feed into it. Like the number of mixer channels being determined by a variable.

That depends on how much memory you have to play with. I am sure you know both the Teensy 4.1 and the audio shield can be fitted with extra static RAM or ROM chips, so a lot will depend on what you have fitted.

You might be able to get better help on the Teensy-specific forum. Here's the place:

Regarding how many drum synth and mixer instances you run on Teensy 4.1 running at 600 MHz, I would imagine 100 is probably well within the hardware's capability.

But AudioMemory(20) you have in your program won't be enough! You will need at least AudioMemory(100), because on every audio frame update you will have 100 synth instances which each need to allocate 1 audio block to create their sound.

The Teensy Audio Library was originally designed (I'm the guy who wrote it) with the intention for the C++ instances to be static. But recently support has been added, thanks largely to contributions Jonathan Oakley, to create connection instances dynamically with C++ "new" and if you wish, get rid of them with C++ "delete". If you wish to try this, best to install the latest beta version, because support for deleting audio connections is relatively new. If using Arduino IDE 2.0.x, in Boards Manager install Teensy version 0.58.3 (the betas are marked as version 0 so the IDE doesn't pester people to upgrade from stable versions).

To see how to use this stuff, in Arduino click File > Examples > Audio > Dynamic. Again, this is fairly new so make sure you have the latest beta if you don't see that example sub-menu.

The audio library web documentation was also recently updated to document the myConnection.connect() and myConnection.disconnect() functions which you will need to cause your dynamically created connections to actually route audio between the synth and mixer instances.

https://www.pjrc.com/teensy/td_libs_AudioConnection.html

But just because you can do a thing does not necessarily mean you should do it. Creating 100 synth instances and 34 mixer instances may seem like a lot of lines of text, but it is simple to do and probably only tedious to type (or copy-paste and edit) in Arduino IDE editor or draw in the design tool just once. Might be better to just suffer that work once and get it done the simple way. Doing things in complex ways can at first seem easier and in the end can result in fewer lines of code, but unless you have expert level programming skills, the process of the complex path can be a gift that keep on giving! Often the simplest way is best.

Either created static or by C++ new, to evaluate how well your Teensy 4.1 is handling 100 synth and 34 mixer instances, use the functions which report CPU usage. Also check the memory usage to learn how much of the AudioMemory() you created is actually used (it should be just over 100). Those resource monitoring functions are documented here:

https://www.pjrc.com/teensy/td_libs_AudioProcessorUsage.html

There's also a simple example in File > Examples > Audio > MemoryAndCpuUsage.

When you do get this running, and especially if you experiment, I'm curious to hear how many simultaneous synth instances you manage to run. With the basic waveforms Teensy 4.1 can run several hundred, but it's less if using the bandwidth limited variants, and if actively modulating their frequency with other waveforms, they use even more CPU, putting the limit probably under 100. As far as I know, nobody has tested (and said in public) how many of the sumple drum synth are able to run simultaneously. I hope you're successful and can share than result.

4 Likes

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.