Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 2 1 2 LastLast
Results 1 to 25 of 27

Thread: getting started with library writing - samples vs blocks and AUDIO_BLOCK_SAMPLES

  1. #1

    getting started with library writing - samples vs blocks and AUDIO_BLOCK_SAMPLES

    I'd like to translate my Arduino Due 'string ensemble chorus' effect to an Audio Library-compatible library, but I could use a few pointers.

    First, my Due code deals with samples as they come in via a 44.1 KHz interrupt, but in the Audio Library it's the 'update' method getting called somehow. Does that happen at the samplerate? I'll need to know that also since I'm targeting a Teensy 3.6 and will probably calculate my LFO on the fly (should be fast enough) and that depends on samples per second, or however often 'update' gets called. I need three phases of LFO separated by 2/3*pi radians, each a sum of two sine functions, will that be too much math per update?

    Second, on the Due I process a sample at a time, but in the Audio Library it's blocks. What size are blocks? Is it 'AUDIO_BLOCK_SAMPLES'? I need a certain amount of buffer space to wiggle around my delay tap pointers, and I suppose it should be calculated based on the samplerate used.

    Finally, I'd like to make the effect stereo, so I need to figure out how that's handled in the Audio Library. I guess it'd be stereo based on a phase adjustment of the LFO for one of the channels, to give a fake stereo spread.

    If anyone skilled in the writing on Audio Library libraries finds the trivial and wants to take it on, that'd be awesome. I'd frankly just love to get going on using it to code my 'string machine' synth.

    My Due code is here: https://github.com/quarterturn/due_ensenble_chorus
    The root of the repo has the python code to generate the LFO wavetable.

  2. #2
    AUDIO_BLOCK_SAMPLES is 128, see here: https://github.com/PaulStoffregen/co.../AudioStream.h

    Audio is processed in blocks, 44100 / 128 = 344 times per second, or about 2.9 milliseconds.

    For stereo, you'll need to allocate and process one audio_block_t per channel. If you can do mono, stereo isn't really any harder to implement. Take a look at the stereo reverb implementation to see: https://github.com/PaulStoffregen/Au...t_freeverb.cpp and look for AudioEffectFreeverbStereo::update.

  3. #3
    Quote Originally Posted by wcalvert View Post
    AUDIO_BLOCK_SAMPLES is 128, see here: https://github.com/PaulStoffregen/co.../AudioStream.h

    Audio is processed in blocks, 44100 / 128 = 344 times per second, or about 2.9 milliseconds.

    For stereo, you'll need to allocate and process one audio_block_t per channel. If you can do mono, stereo isn't really any harder to implement. Take a look at the stereo reverb implementation to see: https://github.com/PaulStoffregen/Au...t_freeverb.cpp and look for AudioEffectFreeverbStereo::update.
    Thanks!

    So looks like each block I just run 128 iterations of the per-interrupt code and then pass on the completed block. Got it. I was updating my wavetable pointer every 100 interrupts so that'll work OK inside that loop.

    My first version will probably just be non-adjustable. There's pretty much a sweet spot to the effect anyway which gives the 'nailed it' sound.

  4. #4
    Sounds good, unfortunately I don't have time to actually help write or debug the port, but good luck none the less

  5. #5
    I'm looking over effect_freeverb.cpp in section void AudioEffectFreeverbStereo::update()

    I'm confused that there's only one block pointer for the input:

    Code:
    #if defined(__ARM_ARCH_7EM__)
    	const audio_block_t *block;
    	audio_block_t *outblockL;
    	audio_block_t *outblockR;
    	int i;
    	int16_t input, bufout, outputL, outputR;
    	int32_t sum;
    Does this example take two channels as its input? It doesn't appear to. How would I get the second channel for my input?

  6. #6
    Take a look at the mixer code, starting around line 113. There's a for loop that receives multiple blocks of data. https://github.com/PaulStoffregen/Au...ster/mixer.cpp

  7. #7
    Quote Originally Posted by wcalvert View Post
    Take a look at the mixer code, starting around line 113. There's a for loop that receives multiple blocks of data. https://github.com/PaulStoffregen/Au...ster/mixer.cpp
    Aha in the freeverb code I see:

    Code:
    block = receiveReadOnly(0);
    I guess I'd just allocate another block for channel 1. Is there a way to know how many channels are available?

  8. #8
    Perhaps a good example would be the filter code: https://github.com/PaulStoffregen/Au...r_variable.cpp

    Take a look on line 176. He acquires two different blocks, and checks for input by simply doing "if(!control_block)" or "if(!input_block)" and processing the blocks accordingly.

  9. #9
    This has been very helpful. I should have a working library soon!

  10. #10
    Where should my LFO table go? I was using something like this with the due in lfo.h:

    Code:
     const PROGMEM int LFO_TABLE[]=
        {
            0,
            1,
            2,
            3,
            ...
        }
    I've tried putting that in the private section of the class constructor for my class AudioEffectEnsemble in file effect_ensemble.h, but it gcc doesn't like it. I get the following:

    Code:
    In file included from /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/Audio/Audio.h:82:0,
                     from /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/Audio/examples/Synthesis/pulseWidth/pulseWidth.ino:4:
    /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/Audio/effect_ensemble.h:1552:5: error: expected ';' at end of member declaration
         }
         ^
    /Applications/Arduino.app/Contents/Java/hardware/teensy/avr/libraries/Audio/effect_ensemble.h:1552:5: error: too many initializers for 'const int [0]'
    Of course there is a ';' at the end:
    Code:
    };
    #endif
    My guess based on the last line of the errors is there's something it does not like about PROGMEM in either a header file or a class constructor.

  11. #11
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    Code:
    const PROGMEM int LFO_TABLE[]=
    I think the PROGMEM has to go at the end of the declaration, like this:
    Code:
    const int LFO_TABLE[] PROGMEM =
    because it is defined as a storage attribute:
    Code:
    #define PROGMEM __attribute__((section(".progmem")))
    Pete

  12. #12
    Quote Originally Posted by el_supremo View Post
    Code:
    const PROGMEM int LFO_TABLE[]=
    I think the PROGMEM has to go at the end of the declaration, like this:
    Code:
    const int LFO_TABLE[] PROGMEM =
    because it is defined as a storage attribute:
    Code:
    #define PROGMEM __attribute__((section(".progmem")))
    Pete
    Same error, unfortunately.

  13. #13
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,761
    other uses I see are of the form :: const int PROGMEM LFO_TABLE[]=

  14. #14
    Try the following:

    static const unsigned char PROGMEM your_stuff_here[] = {
    0xff,0xff,0xfe, ....
    };

  15. #15
    Welp here's my first attempt: https://github.com/quarterturn/teensy3-ensemble-chorus

    It does not work. The freeverbs code checks that the input block got something from receiveReadOnly(), so I use that too but fail on both channels. If I comment out the test it just kills the envelope used in the .ino file. If I pass back the input blocks, there's nothing but silence, reinforcing the validity of the test in the freeverbs library. It's possible there's mistake in my code, but it's more or less the exact same code that worked on the Due.

    I'm am probably complicating things for myself since I have a Blackaddr Audio interface vs the PJRC one. If I take the pulsewidth example and just swap the correct codec name in, it does work, so my hardware is good. I used the web tool to get code by hooking up an oscillator to the freeverb module and then to the i2s output. Who knows maybe I have my connections wrong somehow.

    Finally, I got around the PROGMEM issue by just not storing the LFO in EEPROM. Instead, I just write it to an array in RAM using the orignal trig function in the constructor.

    I'll need to sleep on this for a while.

  16. #16
    Quick suggestion is I think you need to use receiveWritable, though I have not dug into the receiveReadOnly and receiveWritable code to know what's going on under the hood.

    Actually, on second thought, I'm probably wrong. I will have to look into it more.

  17. #17
    Senior Member
    Join Date
    Nov 2012
    Posts
    1,155
    Why do you have three delay buffers? You store the same sample in each one and otherwise never change their content. Therefore all three always have the same content.
    I think you can use just one buffer and use this to compute the left channel output:
    Code:
                outputL = 16384 + (delayBuffer1L[offsetIndex1L] >> 2) + (delayBuffer1L[offsetIndex2L] >> 2) + (delayBuffer1L[offsetIndex3L] >> 2);
    and similar code for the right channel.

    Pete

  18. #18
    Quote Originally Posted by el_supremo View Post
    Why do you have three delay buffers? You store the same sample in each one and otherwise never change their content. Therefore all three always have the same content.
    I think you can use just one buffer and use this to compute the left channel output:
    Code:
                outputL = 16384 + (delayBuffer1L[offsetIndex1L] >> 2) + (delayBuffer1L[offsetIndex2L] >> 2) + (delayBuffer1L[offsetIndex3L] >> 2);
    and similar code for the right channel.

    Pete
    Yeah it's an artifact of initially doing it backwards ie. doing the modulation on the input pointer and keeping the output steady. Thanks for pointing it out, I will fix it.

  19. #19
    I don't think my code is right for block processing. I need to study effect_chorus.cpp more.

  20. #20
    I've re-written my code and it should be making sense for processing blocks in and out of a ring buffer. Anyhow, not working - I'm going out to lunch when I try to load the combined delay offset data from the buffer into the block. Code's on github but here it is as well:

    Code:
    void AudioEffectEnsemble::update(void)
    {
    	audio_block_t *block;
    	uint16_t i;
    
    	block = receiveWritable(0);
    
        // buffer the incoming block
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++)
        {
    
            // wrap the input index
            inIndex++;
            if (inIndex > (BUFFER_SIZE - 1))
                inIndex = 0;
    
            delayBuffer[inIndex] = block->data[i];
    
        }
        // re-load the block with the delayed data
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++)
        {
            // advance the wavetable indexes every COUNTS_PER_LFO
            // so the LFO modulates at the correct rate
            lfoCount++;
            if (lfoCount > COUNTS_PER_LFO)
            {
                // wrap the lfo index
                lfoIndex1++;
                if (lfoIndex1 > (LFO_SIZE - 1))
                    lfoIndex1 = 0;
                lfoIndex2++;
                if (lfoIndex2 > (LFO_SIZE - 1))
                    lfoIndex2 = 0;
                lfoIndex3++;
                if (lfoIndex3 > (LFO_SIZE - 1))
                    lfoIndex3 = 0;
    
                // reset the counter
                lfoCount = 0;
            }
    
            // wrap the output index
            outIndex1++;
            if (outIndex1 > (BUFFER_SIZE - 1))
                outIndex1 = 0;
    
            outIndex2++;
            if (outIndex2 > (BUFFER_SIZE - 1))
                outIndex2 = 0;
    
            outIndex3++;
            if (outIndex3 > (BUFFER_SIZE - 1))
                outIndex3 = 0;
    
            // get the delay from the wavetable
            offset1 = lfoTable[lfoIndex1];
            offset2 = lfoTable[lfoIndex2];
            offset3 = lfoTable[lfoIndex3];
    
            // add the delay to the buffer index to get the delay index
            offsetIndex1 = outIndex1 + offset1;
            offsetIndex2 = outIndex2 + offset2;
            offsetIndex3 = outIndex3 + offset3;
    
    
            // wrap the index if it goes past the end of the buffer
            if (offsetIndex1 > (BUFFER_SIZE - 1))
                offsetIndex1 = offsetIndex1 - BUFFER_SIZE;
            if (offsetIndex2 > (BUFFER_SIZE - 1))
                offsetIndex2 = offsetIndex2 - BUFFER_SIZE;
            if (offsetIndex3 > (BUFFER_SIZE - 1))
                offsetIndex3 = offsetIndex3 - BUFFER_SIZE;
    
            // wrap the index if it goes past the buffer the other way
            if (offsetIndex1 < 0)
                offsetIndex1 = BUFFER_SIZE + offsetIndex1;
            if (offsetIndex2 < 0)
                offsetIndex2 = BUFFER_SIZE + offsetIndex2;
            if (offsetIndex3 < 0)
                offsetIndex3 = BUFFER_SIZE + offsetIndex3;
    
            // combine delayed samples into output
            // add the delayed and scaled samples
            block->data[i] = 16384 + (delayBuffer[offsetIndex1] >> 2) + (delayBuffer[offsetIndex2] >> 2) + (delayBuffer[offsetIndex3] >> 2);
    
        }
        Serial.println("next is transmit");
        transmit(block, 0);
        Serial.println("transmit done");
    	release(block);
        
        return;
    
    }
    I hope it's something obvious. I'm not great with C++ syntax.

  21. #21
    When you say "going out to lunch" you mean the teensy hard locks? If so you might have gone out of bounds on an array, but it appears you're checking array bounds correctly.

    What's the last serial print before it locks up?

  22. #22
    It crashes right at the block->data[i] = ... line. I had it surrounded with Serial.print statements and it only printed the one before.

  23. #23
    Maybe as a sanity check, after you do "block = receiveWritable(0);" could you check that the block has been received (make sure it's not null)?

    The teensy doesn't lock up if you set block->data[i] to some static value (rather than access the arrays)?

  24. #24
    Ah yep, it's null. I can take a working example like the one below, substitute 'ensemble' for 'reverb' and get a null

    Code:
    /*************************************************************************
     * This demo uses the BALibrary library to provide enhanced control of
     * the TGA Pro board.
     * 
     * The latest copy of the BA Guitar library can be obtained from
     * https://github.com/Blackaddr/BALibrary
     * 
     * This demo provides an example guitar tone consisting of some slap-back delay,
     * followed by a reverb and a low-pass cabinet filter.
     * 
     */
    #include <Wire.h>
    #include <Audio.h>
    #include <MIDI.h>
    #include "BALibrary.h"
    
    using namespace BALibrary;
    
    BAAudioControlWM8731      codecControl;
    
    AudioInputI2S            i2sIn;
    AudioOutputI2S           i2sOut;
    
    AudioMixer4              gainModule; // This will be used simply to reduce the gain before the reverb
    AudioEffectDelay         delayModule; // we'll add a little slapback echo
    AudioEffectEnsemble      ensemble;
    AudioEffectReverb        reverb; // Add a bit of 'verb to our tone
    AudioMixer4              mixer; // Used to mix the original dry with the wet (effects) path.
    AudioFilterBiquad        cabFilter; // We'll want something to cut out the highs and smooth the tone, just like a guitar cab.
    
    
    // Audio Connections
    AudioConnection      patchIn(i2sIn,0, delayModule, 0); // route the input to the delay
    
    AudioConnection      patch2(delayModule,0, gainModule, 0); // send the delay to the gain module
    // AudioConnection      patch2b(gainModule, 0, reverb, 0); // then to the reverb
    AudioConnection      patch2b(gainModule, 0, ensemble, 0); // then to the reverb
    
    //AudioConnection      patch1(i2sIn,0, mixer,0); // mixer input 0 is our original dry signal
    // AudioConnection      patch3(reverb, 0, mixer, 1); // mixer input 1 is our wet
    AudioConnection      patch3(ensemble, 0, mixer, 1); // mixer input 1 is our wet
    
    AudioConnection      patch4(mixer, 0, cabFilter, 0); // mixer outpt to the cabinet filter
    
    
    AudioConnection      patch5(cabFilter, 0, i2sOut, 0); // connect the cab filter to the output.
    //AudioConnection      patch5b(cabFilter, 0, i2sOut, 1); // connect the cab filter to the output.
    
    void setup() {
    
      delay(5); // wait a few ms to make sure the GTA Pro is fully powered up
      AudioMemory(48);
    
      // If the codec was already powered up (due to reboot) power itd own first
      codecControl.disable();
      delay(100);
      codecControl.enable();
      delay(100);
    
      // Configure our effects
      delayModule.delay(0, 50.0f); // 50 ms slapback delay
      gainModule.gain(0, 0.25); // the reverb unit clips easily if the input is too high
      mixer.gain(0, 1.0f); // unity gain on the dry
      mixer.gain(1, 1.0f); // unity gain on the wet
    
      // Setup 2-stages of LPF, cutoff 4500 Hz, Q-factor 0.7071 (a 'normal' Q-factor)
      cabFilter.setLowpass(0, 4500, .7071);
      cabFilter.setLowpass(1, 4500, .7071);
      
      
    }
    
    void loop() {  
    
      // The audio flows automatically through the Teensy Audio Library
    
    }

  25. #25
    Well, I am left grasping at straws. Instead of receiveWritable, you could try receiveReadOnly, and then do all of your write operations into another block: "outBlock = allocate();" and then you'd actually transmit outBlock... making sure to check both blocks for not null

    It seems like the issue is a lack of my/our understanding of what's going on with receiveWritable and receiveReadOnly.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •