My audio.h fork - Tape delay, FM waveform input, granular effect and more

john-mike

Well-known member
Howdy!
I made all these a while back for a product I put out but haven't gotten a chance to clean up and put on git. Seeing this post reminded me.

https://github.com/BleepLabs/Audio

I've added:
FM input in waveform synth
Bandlimited waveforms with some examples on their use.
Digital combine for simple digital distortion
Lazy mixer setup options
Variable delay
Sample and hold for smooth bitcrushing
Simple granular effect

Let me know if you have any questions.
You can see most all of these in use in this code.

I'd like to add a sync input to the waveform oscillators but am having issues with the second input. Here's the thread regarding it.
 
Last edited:
These look like some great additions @john-mike, can't wait to give them a go. Thanks for sharing.
 
I'm trying to find how to FM modulate a waveform object with other waveform object and reading the description this library update should help me :)

But i can't find how to use/call/patch the FM input, can someone give me some guidance for this?

thanks in advance!
 
Since you can't do it in the GUI you'll need to code it yourself. It's just like the sineFM object though:
Code:
AudioConnection          patchCord1(waveform1, waveform2);
AudioConnection          patchCord2(waveform2, waveform3);
 
Hi John,

Thanks a lot for this work. Your crazy instrument does sound very good ! (great video btw !)

I'd love to use all that new objects you gave us but...sorry, I'm still asking some noob question (I wish I could just solve it by myself) but it's been hours that I'm searching for how to use your library...

I just added the zip file though the "include librarie .zip" tool in arduino but when I'm trying to modulate one waveform to another, nothing's happening.

I used this code :

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



// GUItool: begin automatically generated code
AudioSynthWaveform       waveform1;      //xy=79,121
AudioEffectEnvelope      envelope1;      //xy=219,121
AudioSynthWaveform       waveform2;      //xy=234,220
AudioEffectEnvelope      envelope2;      //xy=380,219
AudioOutputI2S           i2s1;           //xy=668,153
AudioConnection          patchCord1(waveform1, envelope1);
AudioConnection          patchCord2(waveform2, envelope2);
AudioConnection          patchCord3(envelope2, 0, i2s1, 0);
AudioConnection          patchCord4(envelope2, 0, i2s1, 1);
AudioConnection          patchCord5(envelope1, 0, waveform2, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=668,211
// GUItool: end automatically generated code

int randomValue;
int NoteOffTrig = 0 ;
uint32_t noteTime;
uint32_t resetNoteOffTime = 0;
bool NoteOffStateTime = 0;
bool NoteOffStateTimePrevious = 0;

void setup() {
  Serial.begin(9600);
  randomSeed(analogRead(A9));  //passer la fonction random en réelle fonction aléatoire.

  AudioMemory(70);             // Audio connections require memory to work.  For more detailed information, see the MemoryAndCpuUsage example
  // setup audio board

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.7);

  waveform1.begin(1, 15, WAVEFORM_SQUARE);
  waveform2.begin(1, 80, WAVEFORM_SAWTOOTH);

  envelope1.attack(2000);
  envelope1.decay(300);
  envelope1.sustain(0.5);
  envelope1.release(4000);

  envelope2.attack(50);
  envelope2.decay(50);
  envelope2.sustain(0.4);
  envelope2.release(500);
}

void loop() {
  randomValue = random(0, 100);

  // TRIG RANDOM SOUNDS

  if (randomValue > 92) {
    Serial.println(">>>>>>>> NOTE ON !! <<<<<<<<");
    
    AudioNoInterrupts() ;
    envelope1.noteOn();
    envelope2.noteOn();
    resetNoteOffTime = millis();
    NoteOffTrig = random(0, 500);
    AudioInterrupts();
  }

  NoteOffStateTime = millis() - resetNoteOffTime > NoteOffTrig;

  // TRIG ENVELOPPES NOTE OFF

  if (NoteOffStateTime) {
    if (NoteOffStateTimePrevious == 0) {
      Serial.println(">>>>>>>> NOTE OFF !! <<<<<<<<");

      envelope1.noteOff();
      envelope2.noteOff();

      NoteOffStateTimePrevious = 1;
    }
  }
  else {
    NoteOffStateTimePrevious = 0;
  }

  Serial.println();
  
  Serial.print(AudioProcessorUsageMax());
  Serial.print("  ");
  Serial.println(AudioMemoryUsageMax());
  AudioProcessorUsageMaxReset();
  AudioMemoryUsageMaxReset();
  delay(50);


}

Any suggestion ? Is there something I'm doing totally wrong ?
 
@john-mike - Looking at merging your code and I have a quick question. Most of your added files have no license header. Is MIT license (same as the rest of the library) ok to add? Should the header be attributed to BleepLabs, or your name, or anyone else?

This isn't meant to be all lawyerly. Just need a quick okay on MIT and about 10-60 chars of who to attribute.
 
Oh sorry yes the standard license is fine.

Code:
/*
 * Copyright (c) 2018 John-Michael Reed
 * bleeplabs.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
 
@MadMind
You might be having issues due to using delay. It should almost always be avoided.

First try this simple sketch:

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

// GUItool: begin automatically generated code
AudioSynthWaveform       waveform1;      //xy=79,121
AudioSynthWaveform       waveform2;      //xy=234,220
AudioOutputI2S           i2s1;           //xy=668,153
AudioConnection          patchCord1(waveform1, waveform2);
AudioConnection          patchCord2(waveform2, envelope2);
AudioConnection          patchCord3(waveform2, 0, i2s1, 0);
AudioConnection          patchCord4(waveform2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=668,211
// GUItool: end automatically generated code

uint32_t current_time, prev_time;

void setup() {
  Serial.begin(9600);

  AudioMemory(70);             // Audio connections require memory to work.  For more detailed information, see the MemoryAndCpuUsage example
  // setup audio board

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.7);

  waveform1.begin(.5, 1, WAVEFORM_TRIANGLE);
  waveform2.begin(1, 220, WAVEFORM_SAWTOOTH);

}

void loop() {

  current_time = millis();

  if (current_time - prev_time > 500) {
    prev_time = current_time;
    Serial.print(AudioProcessorUsageMax());
    Serial.print("  ");
    Serial.println(AudioMemoryUsageMax());
    AudioProcessorUsageMaxReset();
    AudioMemoryUsageMaxReset();
  }

}

If it works you'll need to carefully redo your code. Remove the delay and add the type of timing code you see in this and the old "blinkwithoutdelay" example.
Changing at only 50 milliseconds might be too fast.

If it doesn't work then the library isn't installed.
What kind of system are you using?
 
I started looking first at AudioEffectSH. I'm guess "SH" means Sample and Hold? The only info I can see what what it's supposed to do is in the readme "Sample and Hold - Allows for smooth bitcrushing at arbitrary rates and classic s&h modulators."

Several things about this code don't make sense to me.

The 2nd input is read to a variable named "mod_in". But then all other references to it are in code that's commented out. As nearly as I can tell, the 2nd input does nothing?

I see what looks like code which captures the current sample at regular intervals to "audio_out_c" (which is a local static, so shared among all instances of this object) and then keeps repeating it until the interval occurs again.

There are 2 public functions smooth() and manual_hold() which affect variables that aren't ever used.

So far this is the only file I've examine in detail. I'll look at another one later today. But at least for AudioEffectSH, I hope you can understand how I'm reluctant to merge this code without a clear idea of what it's supposed to do, and especially why local static state variable are used.
 
Yes that's what I was talking about a while back. A lot of this needs cleaning up which I am working on these next few weeks.
AudioEffectSH is definitely the roughest.
If you could give me some notes on effect_var_dly and effect_granular that would be great.

I'll get back to you one everything is ready for you to look at and I have all the serial flash stuff implemented.

Thanks!
 
AudioEffectSH is definitely the roughest.
If you could give me some notes on effect_var_dly and effect_granular that would be great.

Oh, looks like I picked the wrong one by chance. ;)

I actually started looking at the granular effect. First impression is the API exposes all 16 bit numbers rather than the float 0-1.0 API convention. I can adapt stuff. I also want to make sure the public functions follow the API conventions. Some of this may be a bit picky on my part, but please understand I've been burned a few times by hastily accepting contributions. I will put more time into this tonight...

If you have a little time today, the thing that would help me the most is just a short description of what each object is supposed to do. All I see for the granular effect is "Simple pitch shifting and freezing". 10-100 words about what it actually does and how it does that would be incredibly helpful, and likewise about 10-40 words about what each public function is meant to control would be perfect. Not only will that help me review the code faster, but it'll also be a great start on the design tool docs.

Will do more later today / tonight, but out of time right now.

My goal is to get as much of this merged as I can in the next day or two, and then publish 1.42-beta4 and update the website with the new objects in the design tool, so people can easily start playing with these features.
 
..but please understand I've been burned a few times by hastily accepting contributions.
Oh of course! I want to get cleaned up to your standards.
Is there a style guide I've been missing all these years?

Yes I'll do a quick run through and have descriptions by tonight.
 
I'm working with the granular effect code right now. Let's talk about what the public functions are meant to do? I'm pretty sure I understand begin(), but these 4 aren't so clear to me.

freeze(activate, playpack_rate, grain_length);

shift(activate, playpack_rate, grain_length);

length(max_len)

rate(playpack_rate)

My guess is freeze() and shift() are meant to turn on/off the 2 ways this effect is supposed to work. As nearly as I can guess from the code, playpack_rate at 512 means to play the grains at their original speed. Am I getting close?

Are length() and rate() meant to be called while the effect is running?

From an API point of view, the "activate" parameter is the most troubling. Obviously 1 is meant to turn on this effect. But what is 0 supposed to do? Turn it off? The code looks like it just passes the audio through, and turning off freeze mode looks like it captures audio into the buffer in prep for another freeze to begin. But what's supposed to happen if a user calls freeze with activate=1 without first calling it with activate=0, or calling with with activate=1 while they were in shift mode?
 
This example for the granular library explains it all.
We could easily change it to a noteOn type thing. It just needs to be called in setup then so it's always capturing audio.

I've fixed some big issues for the other modules and added examples
There is also an example on bandlimited wavetables


Digital combine - Combine analog signals with bitwise expressions like OXR. Combining two simple oscillators results in interesting new waveforms, Combining white noise or dynamic incoming audio results in aggressive digital distortion.

Variable delay now named Tape Delay - Interpolated delay time gives delay effect with a tape like response. Control of this interpolation speed and delay sample rate is now possible.
Eventually I'd like to have another one that interpolates between samples rather than just time.

Granular effect - This is the classic granular effect that uses a variable speed buffer to shift the pitch and freeze incoming audio.

synthWaveform:
FM input in waveform synth - second input modulates frequency of the oscillator just line SineFM
Arbitrary wavetable voice length
arbitraryWaveform(sample array, maxFreq (unused still), length of array (2047 max))

Variable triangle wave. WAVEFORM_VARIABLE_TRIANGLE
waveform1.varible_triangle(0); or waveform1.pulseWidth(0) would be a saw
waveform1.varible_triangle(.75); would be halfway between a regular triangle and a ramp

Control input for pulse width and varible triangle. For example:
AudioConnection patchCord1(sine1, 0, waveform1, 0);
AudioConnection patchCord2(sine2, 0, waveform1, 1);
Would be fm from the sine wone and shpe modulation from sine2


I didn't get to sample and hold yet. It needs work. The version on git is not at all correct and I cant find a working copy.
But it should be:
amount(0-1.0) controls sample rate reductions "bitchusging, at 65536 level
smooth() this was to simple average the last and current output but is not useful
second input controls sample rate with an input of 0 being no change and -1 or 1 being a sample rate of 0
 
I still need to finish up my record to serial flash code but the variable speed playback from flash code works well.
I used the waveform object so my students could use something they had experience with but it should obviously be it's own thing
 
Well, I ended up changing the granular public functions quite a bit. The grain lengths are now in milliseconds and the speed is a ratio (1.0 = play at original speed). Here's the design tool documentation.

https://www.pjrc.com/teensy/gui/?info=AudioEffectGranular

I also rewrote most of the freeze mode. Entering freeze mode depended on previously being in a special sampling mode. I changed it to sample when the mode starts, much like the pitch shift mode.
 
I've added the variable triangle feature, at least without modulation.

Turns out the waveform object had a lot of broken code from prior contributions. In the past I've been a bit sloppy about accepting contributions without checking carefully. Many of them were completely broken, and none had the phase adjustment implemented. Over the last couple days I ended up completely rewriting the whole thing and carefully checking with my oscilloscope. I also made a new variable triangle which uses the full numerical range. It's on github now.

I've been thinking about the modulation stuff. My current plan is to add another object with the modulation, since it comes with a significant cost. I want to keep the basic waveform object lightweight.
 
Back
Top