soeren

  • Dec 27, 2018
  • Joined Nov 6, 2017
  • 10 discussions
  • 31 posts
  • cmd + / on mac or ctrl + / elsewhere should work. However, it doesn't work on a spanish keyboard, not sure why.

  • Hello everyone,
    I hooked up a rotary encoder as shown in here
    http://farhek.com/jd/c1f1971/studio-rotary-encoders/31y79t/
    Then I compiled @bela_robert code ,but nothing was printed out. I temporally switched from digital to analog pins so I can use the scope example to visualise the incoming signal , it responded to push and rotation as expected, so i guess there is nothing wrong with the hardware . Am I missing something with the code? Shouuld i modify it?

    P.S. I used the pins P8_08 and P8_07 for the rotary.

  • soeren DIT: so this http://bela.io/belaDiagram/ is actually what I'm supposed to be looking at right? That was a bit misleading at first.

    yes, correct. Given that they are scattered all over the place, it would have not looked good or understandable in the brochure image. Also, that diagram is the same you have in the IDE on the board.

  • Then of course, there is the analog engineer's take on this stuff (this pretty much was inspired by analog circuits designed to implement an ADSR):
    https://github.com/transmogrifox/transmogriFX_bela/blob/master/src/adsr.cpp

    It looks like this:

    Below is the working part of the code with more comments added. Notice it uses first-order IIR filters instead of exp() or pow() functions. The exponential function occurs naturally due to recursion. All the setup code is related to scaling coefficients to the correct values so the recursion works properly.

    void
    adsr_tick_n(adsr* ad, float* output)
    {
    //The ad->NS comes from the block size set up in the creation of this struct.
    //When used in Bela, this is the "n" variable.
        for(int i = 0; i < ad->NS; i++)
        {
           //These next couple of blocks are for transitioning from one state to the next.
            if(ad->trig_timer < 1)
                ad->state = ADSR_STATE_RELEASE;
            else if (ad->trig_timeout > 0)
                ad->trig_timer--;
            //if trig_timeout < 0 then sustain forever
    
            if( (ad->state == ADSR_STATE_RELEASE) && (ad->trig_timer > 0) )
            {
                ad->state = ADSR_STATE_ATTACK;
            }
          //The switch statement below evaluates whether attack, decay, sustain, or release state should be computed
            switch(ad->state)
            {
                case ADSR_STATE_RELEASE:
                    ad->sv *= ad->rls; //Just multiply the current value of "sv" by a number less than one for every sample.
                break;
    
                case ADSR_STATE_ATTACK:
                    if(ad->sv >= ad->amplitude)  //we trigger state change when state variable "sv" gets to max amplitude
                    {
                        ad->sv = ad->amplitude;
                        ad->state = ADSR_STATE_DECAY;
                    }
                    else
                    	ad->sv = ad->sv + ad->atk * (ad->pk - ad->sv);  //This is a first-order IIR filter, behaves like an analog RC filter driven with a step input "pk"
                break;
                
                case ADSR_STATE_DECAY:
                    ad->sv = ad->sv + ad->dcy * (ad->sus - ad->sv);  //This also is a first-order IIR, driven with step input "sus"
                    if(ad->trig_timer == 0)
                        ad->state = ADSR_STATE_RELEASE;  //Redundant but paranoia safety (always return to a known state)
                break;
    
                default:
                    ad->state = ADSR_STATE_RELEASE;  //more paranoia
                break;
            }
            output[i] = ad->sv;  //each time through the loop add the current sample to the output buffer
        }
    }

    Then on every block you multiply the buffer you send in for "output" with your sine tone.

    Here is an example of where this is used in Bela for controlling a filter cut-off frequency:
    https://github.com/transmogrifox/transmogriFX_bela/blob/master/src/sample_hold_modulator.cpp

    It's complicated to sort through that whole blob, so below shows where the ADSR struct is used:

    sh_mod*
    make_sample_hold(sh_mod* sh, float fs, int N)
    {
    .
    .
    .
    sh->ad = make_adsr(sh->ad, fs, N);
    .
    .
    .
    sh->adsr_env = (float*) malloc(sizeof(float)*N);
    .
    .
    .
    for(int i = 0; i < N; i++)
        {
        	sh->adsr_env[i] = 0.0;
            .
            .
            .
    }
    .
    .
    .
    //Initialize ramp rates
        adsr_set_attack(sh->ad, Trate/8.0);
        adsr_set_decay(sh->ad, Trate/8.0);
        adsr_set_sustain(sh->ad, 0.36);
        adsr_set_release(sh->ad, Trate/4.0);
        adsr_set_trigger_timeout(sh->ad, 0.5/rate);
    .
    .
    .
    }
    .
    .
    .
    void
    run_sample_hold(sh_mod* sh, float* output)
    {
    .
    .
    .
    adsr_tick_n(sh->ad, sh->adsr_env);
    .
    .
    .
    if(sh->en_adsr)
    output[i] = sh->adsr_env[i];
    .
    .
    .
    }

    This doesn't back it up to the Bela render() function, but my code has to be tracked back to the render() function via the envelope filter code:
    https://github.com/transmogrifox/transmogriFX_bela/blob/master/src/envelope_filter.cpp

    void
    envf_tick_n(env_filter* envf, float* x, float* e)
    {
    .
    .
    .
    //TODO: move to env_filter struct
    run_sample_hold(envf->sh, envf->sh_buff);
    .
    .
    .

    Then finally envf->sh_buff gets applied to something (modulating the filter)

    		//limit to range 0 to 1 and apply to filter frequency setting
    		ei = clamp(ei*envf->sns*envf->ishmix + envf->sh_buff[i]*envf->shmix);
    envf->frq[i] = envf->depth + envf->width*ei;

    And envf->frq[] is a buffer to identify a time-varying cut-off frequency

       //Run the state variable filter
    svf_tick_fmod_soft_clip_n(envf->svf, envf->y, envf->frq, envf->N);

    So now where/how is this called from render()?
    https://github.com/transmogrifox/transmogriFX_bela/blob/master/src/render.cpp
    First you have to see what was done in setup:

    bool setup(BelaContext *context, void *userData)
    {
    	gAudioFramesPerAnalogFrame = context->audioFrames / context->analogFrames;
    	gifs = 1.0/context->audioSampleRate;
    	gNframes = context->audioFrames;
    	
    	ch0 = (float*) malloc(sizeof(float)*context->audioFrames);
           ch1 = (float*) malloc(sizeof(float)*context->audioFrames);
    .
    .
    .
    	//Envelope filter
    	ef = envf_make_filter(ef, context->audioSampleRate, gNframes);
    	gMaster_Envelope = (float*) malloc(sizeof(float)*gNframes);
    	for(int i = 0; i<gNframes; i++)
    gMaster_Envelope[i] = 0.0;
    .
    .
    .
    }

    Then here is render()

    void render(BelaContext *context, void *userData)
    {
    	format_audio_buffer(context, ch1, 1);
    	format_audio_buffer(context, ch0, 0);
    	
    envf_tick_n(ef, ch1, gMaster_Envelope);
    .
    .
    .
    }

    I may have gone overboard in explaining the connection back to render(), but the first step from adsr_tick_n() gives the algorithm. It's pretty simple for me because it thinks like I do, but putting myself in the place of trying to digest some other person's code...it's probably more horrible than the author you're referencing 🙂

    Anyway let me know if you want some more streamlined examples. The adsr code is a complete C-style struct that implements an ADSR the way an analog synthesizer does it. I haven't benchmarked it, but I expect the code is reasonably efficient. It certainly doesn't make a noticeable increase on CPU usage when I switch between sample/hold filter options. Pretty minimal compared to the double-sampled state-variable filter it's used in 🙂

  • You can print out from the render() function and from any other thread. Printing from render() requires to use rt_printf() to ensure real-time-safe operation. This actually prints to a circular buffer which is then copied to standard output by another thread. As a consequence of this:
    - if you are printing lots of stuff with rt_ptintf under heavy CPU load, you may lose part of your output, if the circular buffer is full
    - if you are mixing rt_printf and printf, the order in which they are printed to the console may not be the one you expect: the output of rt_printf will normally reach the standard output after the output of printf
    - using std::cout << behaves similarly to printf(), however be aware that each of these has its own buffered output, so if you mix the two you may get an unexpected ordering of the output lines.

  • The range makes no sense to me, so good idea to ignore it 🙂

    If you are looking for some good books on this sort of thing have a look at the two books by Will Pirkle.

    The "designing software synthesiser plug-ins in C++" book might be well worth a look.

  • Hi,

    Your render function is not quite right, remember the render function should process context->audioFrames samples, in the original code there was:

    for(unsigned int n = 0; n < context->audioFrames; n++)
    {
      // do stuff here
    }
    

    In your code you are processing 3 seconds worth of samples, which is quite a lot more.

    So make your k variable global and initialise it to 0 in setup()

    Then in the audio loop from the original example you can put your code in, don't use a loop for k, just increment k each time round the original loop.

    If you are still stuck after trying I can give you some more pointers...

    • soeren So a frame is the exact same thing as a sample, except it's looking at ALL channels at once

      We can phrase it in many different ways, all confusing in a different way. So let me try with a new one:
      a frame contains all the samples, one per each channel, corresponding at a given instant in time.

    • soeren how do you go about making an auxiliary task that takes argument(s

      AuxiliaryTask Bela_createAuxiliaryTask(void(*)(void *) callback, int priority, const char * name, void * arg )	

      The last argument (optional) passed to Bela_createAuxiliaryTask() is a void* that is then passed as the only argument to the function.
      See the docs.

      If you want to pass an argument other than a void* then you should pass it (or a pointer to it) and then cast it accordingly. For instance the below code:

      void myCallback(void* arg)
      {
        float param = *((float*)arg);
        print("param: %f\n", param); 
      }
      
      bool setup(BelaContext* context)
      {
        float param = 123;
        AuxiliaryTask task = Bela_createAuxiliaryTask(myCallback, myPriority, myName, (void*)&param);
        Bela_scheduleAuxiliaryTask(task);
      }

      should print 123 (untested).

      Just one follow up question: we do agree, that putting something in an aux task equals putting it into a different thread - right?

      Yes it does. An AuxiliaryTask is a different thread.

    • Assuming that your

      			//setFreq(500 * (time + 2), 0);

      is actually

      			setFreq(500 * (time + 2), 0);

      , here is your problem: you are doing an infrequent but expensive computation in the audio thread while your CPU is already pretty busy, thus causing glitches.

      Setting the frequency of a few hundred oscillators at once can be pretty expensive (especially considering it contains a call to random()). So let's say that on average your audio thread takes 70% of the CPU time: on that one audio callback where you are updating the frequency, you may be using more CPU time than the you have available ( > 100%) and therefore glitch. Note that you would not see such spike in the CPU usage from the CPU meter, because that shows the average CPU usage, but you are getting dropouts on a peak of usage.

      By updating the frequencies in a separate thread, you guarantee that no such glitch occurs, as the CPU time spent to update the frequencies can take the time needed without hanging a single audio callback.

      This is a very timely question: the talk I gave today at the ADC explains this problem in detail and I hope you will find it useful (starts at 5h32m23s):

      soeren EDIT: is best practice to just always put everything non-audio related into auxiliary tasks? If that is the case,

      In principle yes, it is good to put everything non-audio related into an aux task. However, some audio-related but non time-critical stuff (like in your case) can go in a separate thread, too. Hopefully the last part of the talk clarifies that.

      soeren how do you go about providing an auxiliary task with a BelaContent object ?

      You don't! A BelaContext* is only valid in those threads that call setup(), render() and cleanup(), and only for the lifetime of the function itself. This means that you should not make a copy of a BelaContext* to use later or to pass it to another thread. If you want to access elements of the BelaContext* from an AuxiliaryTask (e.g.: for getting an input value or writing an output value), then you should do so by reading/writing a shared resource (e.g.: a global variable) from one thread and reading it from the other. For instance, if you want to write to an analog out a value computed in an auxiliary task you would do:

      float gAnalogOutValue = 0;
      int gAnalogOutChannel = 3;
      void auxiliaryTaskCallback(void*)
      {
        gAnalogOutValue = random() / (float)RAND_MAX;
      }
      void render(BelaContext* context)
      {
      // ...
        analogWrite(context, n, gAnalogOutChannel, gAnalogOutValue);
      // ...
      }
      • As giuliomoro mentioned, mileage may very depending what you do. Stuff like resampling, pitch shifting, encoding/decoding on the fly, advanced synthesis algorithms -- might be limited in what types of thing you can implement or how many things you can run simultaneously.

        I don't have an OP-1, but from reading the description I would guess you will be limited by your creativity before Bela hardware limitations stop you.

      • 4 tracks, recording, playback is surely fine. Various FX is also most likely ok depending on what you are asking for. As in: each DSP algorithm implementation will have a very specific CPU cost and so here your mileage may vary depending on the algorithms you are trying to implement. I am fairly sure you will be ok.

        As for inputs, you get as stock 8 analog inputs (for pots/sliders) and 16 digital I/O (for buttons/LEDs). If you need more, you can expand the number of analog inputs with the multiplexer capelet, which gives you up to 64 analog inputs.

        • I have uploaded the most current revision of my work on this so far:
          http://cackleberrypines.net/transmogrifox/src/bela/15_MultiDelay_29_June_2017.zip

          At this point it's a full-featured guitar effects processor.
          My TODO list includes this:
          LCD display on front -- user feedback is still debug messages in the console.
          Implement save/recall presets.
          Implement 12-button foot controller like my old Digitech RP-20 (that unit was really slick, in my mind)

          Feature list:
          Flange
          Chorus
          Delay
          ~(3 above have assignable envelope control to most parameters)~
          Reverb (Zita Rev1)
          Wah Modeler (the inductor wah model)
          6-band parametric EQ
          Tremolo
          Sustainer (from Rakarrack) (a littler more "open" sounding than the standard compressor)
          Compressor
          Envelope Filter (Well, much much more -- includes sample-hold and ADSR modes)
          Phaser

        • swadhin I am a noob.

          A noob what? A noob programmer? A noob to Bela interface? A noob to audio DSP? My responses below assume you are familiar with C or C++ and guessing you just need a nudge to help putting together a system that will work on Bela.

          I also don't know what you mean by

          swadhin record audio using mics with different sampling rate

          Here's my best stab at it:
          If you mean you want to record at different sample rates, here's one hint:
          http://www.mega-nerd.com/SRC/api_misc.html

          One example I know for how to use it:
          https://sourceforge.net/p/rakarrack/git/ci/master/tree/src/Resample.h
          https://sourceforge.net/p/rakarrack/git/ci/master/tree/src/Resample.C

          Resample the audio you get from Bela to whatever output samplerate you want and write out to an audio file.

          The two rakarrack files consist of a complete class with a really simple interface to libsamplerate.

          As for the other part about recording to file, meganerd has helped us yet again:
          http://www.mega-nerd.com/libsndfile/api.html

          You will need to buffer the input and periodically send off blocks of audio to a non-rt thread to have it written to file in a background task.

          Look at
          http://docs.bela.io/group__auxtask.html