Clicking envelope with Audio Library?

@danixdj.
yes I know and I addressed the termination of a note earlier
I got triggered by comments of oddson and pensive and tried to see a bigger picture which I tried to understand

But, I may have misunderstood
 
If yes, why does one needs in SW to steal a voice in the first place?
Is SW not ideal to allocate for all notes a voice circuit (aka voice object)
Is it then not only an issue on how to mix voices together?

you could then simply design a table driven mixer that combines all voices together.
Or I'm completely wrong with that?

All notes? in all scales? That's literally hundreds and hundreds of voices, and you would run out of memory and processing power. That is why. :)

Just western music is 12 notes in at least 8 octaves which gets us to 96 voices before we even look at other types of music from other parts of the world....

Also worth mentioning that midi supports 256 notes in it's note byte.
 
Last edited:
@danixdj.
yes I know and I addressed the termination of a note earlier
I got triggered by comments of oddson and pensive and tried to see a bigger picture which I tried to understand

But, I may have misunderstood

This problem can be solved by Paul only in the audio library code or by someone who can update the code ... so everything we can try it's just a "workaround" but the problem stay on...
 
we aren't speaking about synth theory but we are speaking about a DIGITAL ARTIFACT CLICK (repeat: digital artifact clicking) when you repeat a note under the same OSC and envelope...

You are talking about your desire for the software to magically prevent any artifacts, no matter how you use it.

Everyone else is talking about how unrealistic your expectations are.
 
You are talking about your desire for the software to magically prevent any artifacts, no matter how you use it.

Everyone else is talking about how unrealistic your expectations are.

Sorry? Unrealistic?

All the others monophonic commercial synth haven't click (digital or analog it's the same)... so for teensy this is an unrealistic expectations? Why?

If there is someone that it's able to make a sketch for a monophonic synth that an pianist can play without clicking using audio library please let me know... i'm here...

It's totally unusable for now!
 
The question is... why they are able to do it and teensy audio library can't do it ?

teensy_commercial.jpg
 
Your image is unrealistic, because it shows the original waveform beginning to decay **before** the note-on event.

Earlier you said 8 ms extra latency (on top of the existing audio latency) was acceptable to you. You also posted an example using a 1 kHz test tone. So at the very least, this should look like the decay spread over 8 cycles. This image shows the decay occurring over only half of a cycle! But most importantly, the software can not begin decrease the level before you tell it to do so.

I'm considering adding code to implement a short release phase. But it can't possibly look the same as this impossible waveform.
 
The same parameters for teensy and a commercial synth have this images! TOTALLY the same (1 voice, sinusoidal 400hz, decay 200, sustain 1, release 200, attack 30)

I don't know how they can do it but they do it (if they do it isn't impossible i think).

so we must to work about a function that can make the same wave form of a commercial synth in this situation (it makes a little bit kick not a digital click)...
 
i'm totally able to help you if you need but you must to know that's a problem... i have a lot of analog synths (mono or polyphonic) and digital synths, software and hardware... i can test what you need but we can't say that there aren't problems... please...
 
I've added code to implement a special quick release phase.

https://github.com/PaulStoffregen/Audio/commit/6aab072831048f8260bc3aed79619d4a1f1f6efc

There's a new releaseNoteOn() function to tune it. The default is 5ms. This does add latency in the case where a new noteOn is called while the previous AHDSR cycle is still in progress.

This is somewhat a kludge, but it should at least lessen the audible artifact when someone implements a very simple synthesis with the envelope without the complexity of many paths for different voices and code to allocate among them.

Here's how the waveform looks (using the new envelope defaults):

file.png
 
I've added code to implement a special quick release phase.

There's a new releaseNoteOn() function to tune it. The default is 5ms. This does add latency in the case where a new noteOn is called while the previous AHDSR cycle is still in progress.


Thank you Paul - a reasonable compromise and it will make the audio library even more accessible.
 
Either this:

https://www.baldengineer.com/installing-arduino-library-from-github.html

Or just wait for the 1.38-beta3 and run the installer.

If you do install from github, that copy will forever (until deleted) override whatever Teensyduino provides. So if you're not good with details like this, best to just want for the next beta installer.

Paul thank you so much ... it's simply fantastic! It works very fine, almost better than a commercial synth ... no words ... compliments! :eek:
 
In this code using drum object there is the same click that Paul have just solved in Envelop function.

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

// GUItool: begin automatically generated code
AudioSynthSimpleDrum     drum1;          //xy=226,95
AudioOutputI2S           i2s2;           //xy=955,101
AudioConnection          patchCord1(drum1, 0, i2s2, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=253,204
// GUItool: end automatically generated code



elapsedMillis tempo;

void setup() {

/////////////////////////   SETUP AUDIO    ////////////////////////////////////

sgtl5000_1.enable();
AudioMemory(30);
sgtl5000_1.lineOutLevel(13);

drum1.length(100);
drum1.frequency(250);
drum1.pitchMod(0);

}
void loop() {if (tempo > 200) {drum1.noteOn(); tempo=0;}
}

durm_click.jpg
 
Last edited:
Another possible issue is what to do it noteOn is called during any of the phases while the gain is more than zero.

Today, the gain is immediately set to zero, then the delay or attach phase begins. The code has a simple design, where attack always transitions from 0 to 1.0, regardless of the prior state. That's probably also far from the best behavior.

(Hi Paul, thank you for this awesome audio library. I tried JUCE for a few weeks and could barely get it working, Teensy + Audio has made me able to start coding my own Synth.)

It looks like the digital artifact popping issue was resolved in this thread releaseNoteOn().

Another topic that came up in this thread was voice stealing behavior. I'm working on my "Hello World" monophonic synth. I'm using the envelope make a simple Attack Sustain Release envelope. releaseNoteOn() is good for getting rid of clicks between notes but I'm trying to find a way to avoid "breathing". If I'm holding one note then play a second, envelop.noteOn() will bring the envelope down to zero then attack which sounds like a duck in the volume. I sort of solved this by using some boolean logic to only call noteOn() for the first note in a group of notes.

However, I can't think of a good solution for what to do when the first note is in the release phase. If I play a second note while the first note is releasing I still get a similar ducking effect. One solution discussed on this thread is to add voices but I actually want to perform a monophic legato style instrument.

My preference is for the release phase of the first note to hand off its value to the attack phase of the second note. The attack phase will start at that value then complete the attack. The attack phase would take less time than if the attack started from 0.

I think the best direction is to implement my own envelope in code and use that to control the gain of an amp object. Is there any way for me to achieve this with the audio library? Thanks everyone!
 
multi-note envelope?

My preference is for the release phase of the first note to hand off its value to the attack phase of the second note. The attack phase will start at that value then complete the attack. The attack phase would take less time than if the attack started from 0.

I think the best direction is to implement my own envelope in code and use that to control the gain of an amp object. Is there any way for me to achieve this with the audio library? Thanks everyone!

Maybe you could assemble the kind of envelope you're looking out of two standard Envelopes. If your MIDI-or-whatever handler can alternate between triggering the two envelopes with each new note, you can feed both of the envelopes a simple +1 constant instead of the tone signal; then at any time the larger of the two envelopes' outputs would be the level you want. So then you choose the greater level, feed that level signal into an Amp module, and the whole thing is your envelope; you just feed the tone signal into the other input of the Amp.

Almost all of that seems to exist as standalone Audio Library modules. The one part I don't see is some way to choose the larger of two signals. The Combine module will do various other kinds of math, and there's Multiply ... Maybe I'm not seeing the complete solution. One just needs a module to output the greater of its two inputs. Surely the Teensy Audio Library already has that functionality somewhere?
 
I have a preliminary modified version of the envelope object up and running that doesn't reset the envelope to zero at NoteOn, and just continues from the last value instead. I've removed the delay and forced states to keep it simple for now. I get occasional small clicks a the moment, but I think that''s only because I'm not checking the attack env level on every sample. If I can fix that, I'll post the code, which is really only minimally altered from Paul's original object.
 
Clicks appear to be gone now that I check on every sample for overflow. I'm sure Paul can code this more efficiently than what I'm doing here, but the principle works fine. Below is the cpp code of the modified envelope object. If you want to use it, you will also need to create an "effect_reenvelope.h" file that is just a copy of the original .h file with the class name changed to AudioEffectReEnvelope.

/* Audio Library for Teensy 3.X
* Copyright (c) 2017, Paul Stoffregen, paul@pjrc.com
*
* Development of this audio library was funded by PJRC.COM, LLC by sales of
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop
* open source software by purchasing Teensy or other PJRC products.
*
* 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, development funding 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.
*/

#include <Arduino.h>
#include "effect_reenvelope.h"

#define STATE_IDLE 0
#define STATE_DELAY 1
#define STATE_ATTACK 2
#define STATE_HOLD 3
#define STATE_DECAY 4
#define STATE_SUSTAIN 5
#define STATE_RELEASE 6
#define STATE_FORCED 7

void AudioEffectReEnvelope::noteOn(void)
{
__disable_irq();
state = STATE_ATTACK;
count = attack_count;
inc_hires = 0x40000000 / (int32_t)count;
__enable_irq();
}

void AudioEffectReEnvelope::noteOff(void)
{
__disable_irq();
if (state != STATE_IDLE && state != STATE_FORCED) {
state = STATE_RELEASE;
count = release_count;
inc_hires = (-mult_hires) / (int32_t)count;
}
__enable_irq();
}

void AudioEffectReEnvelope::update(void)
{
audio_block_t *block;
uint32_t *p, *end;
uint32_t sample12, sample34, sample56, sample78, tmp1, tmp2;

block = receiveWritable();
if (!block) return;
if (state == STATE_IDLE) {
release(block);
return;
}
p = (uint32_t *)(block->data);
end = p + AUDIO_BLOCK_SAMPLES/2;

while (p < end) {

// we only care about the state when completing a region
if (count == 0) {
if (state == STATE_ATTACK) {
count = hold_count;
if (count > 0) {
state = STATE_HOLD;
mult_hires = 0x40000000;
inc_hires = 0;
} else {
state = STATE_DECAY;
count = decay_count;
inc_hires = (sustain_mult - 0x40000000) / (int32_t)count;
}
continue;
} else if (state == STATE_HOLD) {
state = STATE_DECAY;
count = decay_count;
inc_hires = (sustain_mult - 0x40000000) / (int32_t)count;
continue;
} else if (state == STATE_DECAY) {
state = STATE_SUSTAIN;
count = 0xFFFF;
mult_hires = sustain_mult;
inc_hires = 0;
} else if (state == STATE_SUSTAIN) {
count = 0xFFFF;
} else if (state == STATE_RELEASE) {
state = STATE_IDLE;
while (p < end) {
*p++ = 0;
*p++ = 0;
*p++ = 0;
*p++ = 0;
}
break;
}
}

int32_t mult = mult_hires >> 14;
int32_t inc = inc_hires >> 17;
// process 8 samples, using only mult and inc (16 bit resolution)
sample12 = *p++;
sample34 = *p++;
sample56 = *p++;
sample78 = *p++;
p -= 4;
mult += inc;

if (state == STATE_ATTACK)
{
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp1 = signed_multiply_32x16b(mult, sample12);
mult += inc;
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp2 = signed_multiply_32x16t(mult, sample12);
sample12 = pack_16b_16b(tmp2, tmp1);
mult += inc;
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp1 = signed_multiply_32x16b(mult, sample34);
mult += inc;
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp2 = signed_multiply_32x16t(mult, sample34);
sample34 = pack_16b_16b(tmp2, tmp1);
mult += inc;
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp1 = signed_multiply_32x16b(mult, sample56);
mult += inc;
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp2 = signed_multiply_32x16t(mult, sample56);
sample56 = pack_16b_16b(tmp2, tmp1);
mult += inc;
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp1 = signed_multiply_32x16b(mult, sample78);
mult += inc;
if(mult >= 0x40000000>>14) mult = 0x40000000>>14 ;
tmp2 = signed_multiply_32x16t(mult, sample78);
sample78 = pack_16b_16b(tmp2, tmp1);

if(mult >= 0x40000000>>14 ){
mult_hires = 0x40000000;
inc_hires = 0;
count=1;
}
}
else
{
tmp1 = signed_multiply_32x16b(mult, sample12);
mult += inc;
tmp2 = signed_multiply_32x16t(mult, sample12);
sample12 = pack_16b_16b(tmp2, tmp1);
mult += inc;
tmp1 = signed_multiply_32x16b(mult, sample34);
mult += inc;
tmp2 = signed_multiply_32x16t(mult, sample34);
sample34 = pack_16b_16b(tmp2, tmp1);
mult += inc;
tmp1 = signed_multiply_32x16b(mult, sample56);
mult += inc;
tmp2 = signed_multiply_32x16t(mult, sample56);
sample56 = pack_16b_16b(tmp2, tmp1);
mult += inc;
tmp1 = signed_multiply_32x16b(mult, sample78);
mult += inc;
tmp2 = signed_multiply_32x16t(mult, sample78);
sample78 = pack_16b_16b(tmp2, tmp1);
}
*p++ = sample12;
*p++ = sample34;
*p++ = sample56;
*p++ = sample78;
// adjust the long-term gain using 30 bit resolution (fix #102)
// https://github.com/PaulStoffregen/Audio/issues/102
mult_hires += inc_hires;
count--;

}
transmit(block);
release(block);
}

bool AudioEffectReEnvelope::isActive()
{
uint8_t current_state = *(volatile uint8_t *)&state;
if (current_state == STATE_IDLE) return false;
return true;
}

bool AudioEffectReEnvelope::isSustain()
{
uint8_t current_state = *(volatile uint8_t *)&state;
if (current_state == STATE_SUSTAIN) return true;
return false;
}
 
Back
Top