That's great! Thanks Giulio. Tested the code and it works really well.
My intention is to use the Bela as a mini recorder for a microphone array. I have an audio expander capelet that I would like to utilise, so that I can record more than one microphone. I know I can activate the audio expander and its inputs in the settings within the IDE, but I assume that the code requires some modification to accommodate the additional inputs for recording?

I was also wondering when external power (such as a power bank supplying 5V) is connected to the Bela, am I right in thinking that after it boots up it automatically begins running the project? Or can it only run from the IDE (connected to laptop)? I only ask as I essentially would like the Bela (acting as a mini recorder) to be portable.

    mpc I was also wondering when external power (such as a power bank supplying 5V) is connected to the Bela, am I right in thinking that after it boots up it automatically begins running the project?

    That can be done by selecting the project you want to start at boot from the top menu in the Settings tab of the IDE.

    mpc but I assume that the code requires some modification to accommodate the additional inputs for recording?

    you have to use analogRead() instead of audioRead(), and be mindful of the difference in sampling rate (and hence the number of frames per block) between audio and analog channels as explained here.

    Last, be aware that there will be a significant difference in quality between the analog in channels and the audio channels, see here for more.

    • mpc replied to this.

      That's great, thank you Giulio. I'll have a look through the extra info that you sent over and approach it from there. I'll be in touch if I have any more queries regarding the audio expander.

      a month later

      giuliomoro

      Hi Guilio,

      Is it possible to utilise more than one pipe so that I can record both the audio and analogue inputs. So I adapted the code (which was originally for recording the audio input) to work with the analogue inputs to use the 8 channels (audio expander capelet) but would like to also be able to record the audio input. As using the same pipe causes issues but attempting to use two pipes (one for audio, one for analogue) causes errors when the code is running.

      I assume it is probably best to record to two separate files (one for recording analogue, one for recording audio and fairly straightforward to implement the code) because of the different sampling rates, due to the analogue channels being sampled half as often as the audio channels etc.

      The current setup is that I’m recording four lapel microphones plugged into the audio expander capelet but would be great to record a fifth lapel that is plugged into the audio input.

      I’ve pasted the current code for reference, most of the audio input related code is commented out. So this is working for analogue at the moment. The code also works for audio input when the analogue related code is commented out.

      #include <Bela.h>
      #include <libraries/Pipe/Pipe.h>
      #include <libraries/sndfile/sndfile.h>
      
      const char* path = "./recording.wav";
      
      AuxiliaryTask gFillBufferTask;
      SNDFILE * outfile;
      unsigned int gAudioFrames;
      unsigned int gAudioInChannels;
      float gAudioSampleRate;
      Pipe gPipe;
      
      //analog 
      unsigned int numAnalogFrames;
      unsigned int numAnalogChannels;
      float numAnalogSampleRate;
      
      void openFile() {
          SF_INFO sfinfo;
          //sfinfo.channels = gAudioInChannels;
          //sfinfo.samplerate = gAudioSampleRate;
          sfinfo.channels = numAnalogChannels;
          sfinfo.samplerate = numAnalogSampleRate;
          sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
          outfile = sf_open(path, SFM_WRITE, &sfinfo);
      }
      
      /*void openFileAnalog() {
      	SF_INFO sfinfo;
          sfinfo.channels = numAnalogChannels;
          sfinfo.samplerate = numAnalogSampleRate;
          sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
          outfile = sf_open(path, SFM_WRITE, &sfinfo);
      }*/
      
      void closeFile() {
          sf_write_sync(outfile);
          sf_close(outfile);
          printf(".wav file written and closed\n");
      }
      
      void writeBuffer(void*) {
        
      /*
        unsigned int numItems = gAudioFrames * gAudioInChannels; 
        float buf[numItems];
        int ret;
        while((ret = gPipe.readNonRt(buf, numItems) ) > 0) 
        {
          sf_write_float(outfile, &buf[0], ret);
        }*/
        
        unsigned int numItemsAn = numAnalogFrames * numAnalogChannels;
        float bufAnalog[numItemsAn];
        int retAn;
        while ((retAn = gPipe.readNonRt(bufAnalog, numItemsAn)) > 0)
        {
        	sf_write_float(outfile, &bufAnalog[0], retAn);
        }
        
      }
      
      bool setup(BelaContext* context, void* arg)
      {
      
        //setup audio frames and channels
        gAudioSampleRate = context->audioSampleRate;
        gAudioFrames = context->audioFrames;
        gAudioInChannels = context->audioInChannels;
        
        //setup analog frames and channels 
        numAnalogSampleRate = context->analogSampleRate;
        numAnalogFrames = context->analogFrames;
        numAnalogChannels = context->analogInChannels;
        
        gPipe.setup("sndfile-write", 65536, false, false);
        openFile();
        
        if((gFillBufferTask = Bela_createAuxiliaryTask(&writeBuffer, 90, "writeBuffer")) == 0) {
          return false;
        }
        return true;
      }
      
      void render(BelaContext* context, void* arg)
      {
        // context->audioIn is the float* that points to all the input samples, stored as interleaved channels)
        // audioIn is an array of 4 frames * 2 channels = 8 audio input samples.
        //gPipe.writeRt(context->audioIn, context->audioFrames * context->audioInChannels);
        
        // analogIn is an array of 2 frames * 8 channels = 16 analog input samples. 
        // (2 frames because analog I/O runs at half the sample rate of audio I/O.) 
        gPipe.writeRt(context->analogIn, context->analogFrames * context->analogInChannels);
        
        Bela_scheduleAuxiliaryTask(gFillBufferTask);
        
      }
      
      void cleanup(BelaContext* context, void* arg)
      {
          closeFile();
      }

        mpc As using the same pipe causes issues but attempting to use two pipes (one for audio, one for analogue) causes errors when the code is running.

        what error would you get? Two Pipes should work just fine, as long as you use two separate AuxiliaryTasks.

        Using one Pipe is also fine, you should just make sure that you are sending data in exactly the same format and order as you are receiving them. In the example below I send audio first and analog second, and I receive them the same way. The while(1) with break is to make sure that if the thread didn't get a chance to run between invocations of render(), it gets a chance to run and empty the content of gPipe.

        For instance:

        void writeBuffer(void*) {
         while(1) {
          unsigned int numItems = gAudioFrames * gAudioInChannels; 
          float buf[numItems];
          int ret;
          if((ret = gPipe.readNonRt(buf, numItems) ) > 0) 
          {
            sf_write_float(outfile, &buf[0], ret);
          } else {
            break;
          }
          
          unsigned int numItemsAn = numAnalogFrames * numAnalogChannels;
          float bufAnalog[numItemsAn];
          int retAn;
          if ((retAn = gPipe.readNonRt(bufAnalog, numItemsAn)) > 0)
          {
          	sf_write_float(outfile, &bufAnalog[0], retAn);
          }
         }
        }
        
        
        void render(BelaContext* context, void*)
        {
        ...
        gPipe.writeRt(context->audioIn, context->audioFrames * context->audioInChannels);
        gPipe.writeRt(context->analogIn, context->analogFrames * context->analogInChannels);
        ...
        }

        A few notes:

        • the code above is thread-safe only as long as it runs on one CPU. If you were to port it to an architecture that uses multiple CPUs, you'd have to be a bit more clever
        • if you can accept a (probably minor or insignificant) performance penalty, you could pack all the relevant analog and audio samples in a single buffer and send them with a single gPIpe.writeRt() call, which would simplify the writeBuffer() function and be thread-safe. If currently you are sending more data than you need (e.g.: you are sending all the analog channels, but you are only using 4), then this solution would actually save some CPU time, instead of causing a performance penalty, as you would reduce the memory that gets copied to the pipe.
        • mpc replied to this.

          giuliomoro I realised just before your response that I had forgot to use another AuxiliaryTask for the two pipe approach. Thank you for the example, this was very helpful and everything is working as intended. The notes are also really helpful as I'll keep this in mind for future reference.

          4 days later

          giuliomoro

          Hi Giulio,

          Can you access the files from the SD card after recording without using the IDE? Basically directly off the SD card. As when the external power is plugged into the Bela, the project runs on boot and it starts recording. I can stop the currently running program by pressing the power button on the BBB for a small interval (I assume 1 second?). The problem would be that when plugging into the computer to review recordings (if using the IDE), the project will run (on boot) and begin overwriting those recordings. So I would like to avoid this.

          If the SD card is formatted with a filesystem that your computer can read, then you can. If this is the regular Bela SD card you are talking about, it is formatted as ext4, which can only be read natively from Linux, though I remember there were some free drivers for Windows. There was also a solution for macos, but that would be a commercial ($$$) one. However, if you have a USB SD card reader, you should be able to read it from a Linux Virtual Machine running on macos, but that is a bulky option I'd say. One possibility is to store data to an external medium, e.g.: a USB stick.

          The safest option is probably to never overwrite existing files. You use WriteFile::generateUniqueFilename(const char* original) to get a unique filename and make sure you don't overwrite the existing one. See here for details on usage.

          Alternatively, what you could do is modifying your program so that it only creates the file and starts recording if a certain GPIO is in a certain state. So for instance, you could put a wire between the GPIO and 3.3V or GND when it has to be recording and then removing it before you boot the board in "read only" mode. Given how you are calling openFile() (which will truncate the previous file) from setup(), you wouldn't be able to use digitalRead(), but you'd have to use an arbitrary GPIO pin.

          Ultimately, what should be implemented is this, so that you'd be able to write a file to the BELABOOT partition (which you can read/write from your host without drivers, given how it's FAT32) to prevent the file from running at boot. Or even better, repurpose The Bela Button so that if it is pressed before the program starts it disables running at boot for the current boot.

          • mpc replied to this.
            4 days later

            giuliomoro

            Thanks for the reply Giulio, I think potentially re-formatting the SD card (back-up everything prior) to a recognisable file system might be the way to go. I may potentially look at using a USB stick for additional storage for the future. With regards to avoiding overwriting existing files, original in the following line static char* generateUniqueFilename(const char* original); refers to the path/filename of the original file which would look something like this: char* generateUniqueFilename(const char* path = “audioCh.wav”);. Although this compiles fine, it doesn’t work at the moment and I’ve tried a couple of other different versions of writing it using just the filename. I read the details on usage (I understand what each part is doing) but not 100% sure on how to implement it.

              mpc I think potentially re-formatting the SD card (back-up everything prior) to a recognisable file system might be the way to go

              it's not easy: Linux has to run from an ext4 partition, but it can access many other filesystems. You could instead add a partition on the SD card with a different filesystem.

              mpc char generateUniqueFilename(const char path = “audioCh.wav”);.

              what is this?

              You'd use it like this:

              char originalFilename[] = "my-file.wav";
              char* uniqueFilename = WriteFile::generateUniqueFilename(originalFilename);

              and then subsequently use uniqueFilename (and free() it when you are done).

              • mpc replied to this.

                giuliomoro

                Ah I see I'm using mac operating system and was thinking of using FAT32 for the filesystem for the SD card. I did reformat the SD card to this filesystem and then flashed the Bela image but unfortunately that didn't seem to work. I'll try adding a partition instead and see how it goes.

                char originalFilename[] = "my-file.wav";
                char* uniqueFilename = WriteFile::generateUniqueFilename(originalFilename);
                my apologies this code makes a lot more sense, as it wasn't quite clear with regards to the implementation when reading it in the Writefile.h on Bela github. When you say 'subsequently use uniqueFilename' would you be able to elaborate abit on this? When calling free() on uniqueFilename would I be correct in saying that this is done within void cleanup. Similar to how we close the file when creating wav files with sndfile.

                  mpc When you say 'subsequently use uniqueFilename' would you be able to elaborate abit on this?

                  I mean, in the reminder of your code use uniqueFilename and not originalFilename. You can free() it as soon as you no longer need it. Usually that would be as soon as you opened the file, as henceforth you'd be referring to it via the file descriptor. However, cleaning it in cleanup() is also just fine.

                  mpc I did reformat the SD card to this filesystem and then flashed the Bela image but unfortunately that didn't seem to work.

                  Flashing the Bela image on it will override any partitioning/formatting you may have done before. You should start with a flashed image and then add a FAT32 partition to it. I am not familiar with macos's disk utility, but it should be able to do this. Alternatively you can use fdisk on the board.

                  • mpc replied to this.
                  • mpc likes this.

                    giuliomoro

                    Ah ok I see what you mean. I do get the following come up in the console:
                    File audioCh.wav exists, writing to audioCh1.wav instead
                    File analogueCh.wav exists, writing to analogueCh1.wav instead

                    The files are showing up in resources in the IDE, as I open, write buffer and then close them. At present when the new files for example audioCh1.wav or audioCh2.wav are created and its recording, the original file is overwritten at the same time. Once it gets beyond audioCh1.wav that file is fine. audioCh2.wav is created and the original file is overwritten and so on for each new audioCh.wav file that gets created. I believe that this is the section of code that is causing it:

                    void writeBuffer(void*) {
                    	 while(1) {
                    	  unsigned int numItems = gAudioFrames * gAudioInChannels; 
                    	  float buf[numItems];
                    	  int ret;
                    	  if((ret = gPipe.readNonRt(buf, numItems) ) > 0) 
                    	  {
                    	    sf_write_float(outfile, &buf[0], ret);
                    	    sf_write_float(outfile2, &buf[0], ret);
                    	  } else {
                    	    break;
                    	  }
                    	  
                    	  unsigned int numItemsAn = numAnalogFrames * numAnalogChannels;
                    	  float bufAnalog[numItemsAn];
                    	  int retAn;
                    	  if ((retAn = gPipe.readNonRt(bufAnalog, numItemsAn)) > 0)
                    	  {
                    	  	sf_write_float(outfileAnalog, &bufAnalog[0], retAn);
                    	  	sf_write_float(outfileAnalog2, &bufAnalog[0], retAn);
                    	  }
                    	  }
                    	}

                    as I've got two sf_write_float()in each of the if statements but I'm not quite sure how to amend it to work as intended. I've tried a couple of different approaches (with other conditional statements) but been unsuccessful.

                    With regards to the SD card unfortunately after the Bela image is flashed onto the card it registers as Master Boot Record format, which can’t be partitioned using disk utility through the application or terminal on Mac. I was wondering if it would be easier to use a USB stick (formatted as FAT32 and mounted using instructions on the wiki) for storage and then I assume specify the path from what I have currently in my code const char* path = "./audioCh.wav”; to the USB path with something like const char* path = “./root/Bela/projects/project_name/usb/audioCh.wav”?

                    4 years later

                    I want to be able to record audio signals without knowing beforehand for how long I will be recording. The recording time could be in the range of 10s of seconds to hours (assuming enough disk space exists). Ideally if the program where to crash I would want the recorded material to still be on the disk.

                    I saw this recording example but that writes into a single vector and then writes that to disk. For recording large amounts of time that would require a whole lot of allocation.

                    Then I found this code you wrote earlier: giuliomoro

                    Does sf_write_float actually write data to the disk or does that happen when sf_write_sync or sf_close is called? It seems like it does when looking at the libsndfile source but I'm not 100% sure. Assuming it does directly write to disk and disk space is infinite, I can use this code to record until infinity?