AudioSynthKarplusStrong object with modulation/tuning input

Absolutely a noteOff() without a noteOn() should be handled properly. Previously if that happened I blindly put the sound into release mode, which fades it to nothing over the course of ~9ms to prevent clicks from the DC offset; now it does nothing if the sound isn’t playing. There’s also a check at the start of update() to abandon processing if sampleCount is zero.

I did start looking for occurrences of noteOff() but there were too many to make it worth my while trawling through them. The observation that led me to suspect it was that the big list of control settings (?) at startup never completed, so I thought maybe something there was ensuring all voices were silent.

Yes, the optimisation seems to be able to throw up some odd behaviour … and I’m having a heck of a time reproducing the TeensyDebug issue!
 
With the addition of SUSTAIN PEDAL processing (requested by my son who is an accomplished pianist, as he was putting an early version of my TMPS thru its paces), the "simple" logic tree for calling noteOff() grew many more branches (e.g. a key is released, but the pedal is active, so wait either until the note is needed for something else (stolen), or until the PEDAL is released AND the object envelope has ended, etc.) !!

There is an explicit call to handleNoteOn() followed by a call to killAllNotes() (which calls the noteOff() function on all objects) in the setup() function, and another call to killAllNotes() anytime the settings are restored (in the fully operational TMPS, the "preferred" saved settings are read from EEPROM on the display Teensy, then sent over the shared serial interface & activated in the audio Teensy) . . . maybe it's one of those killAllNotes() calls that you're referencing as silencing all voices (& yes, you are corect in what it does at startup) ??

As noted earlier, I'm seeing the problem even without any additional calls to noteOff(), so I'm not sure what might be at play. I'm not so convinced that it's directly related to calling noteOff() without a preceding call to noteOn(). Unfortunately, the opportunity to try out your latest updates will have to wait until we get back from "Sunday afternoon gracery shopping" (that honey-do list wins out again). ;)

Mark J Culross
KD5RXT
 
Just the one call to noteOff() would have done it, and you confirmed that it does happen, albeit fairly indirectly, during setup(). So I’m pretty certain I’ve fixed something, at least!

I did check several times using the example you posted, and could reproduce the issue before I made the change, and not afterwards. If the full TMPS still has problems, it’s probably something else … but let’s see how you get on when The Boss gives you some time off :)
 
@h4yn0nnym0u5e:

I am extremely pleased & ver much appreciative for what you have accomplished !!

Before testing your latest updates, I set out to build a much simpler example sketch to finally convince myself that calling noteOff() without a preceding call to noteOn() really was causing the misbehavior, as you had already determined (having to convince myself is definitely my problem, not yours !!). Finally, using that same simple(r) example sketch, I can confidently confirm that your latest updates have successfully resolved the apparent hang condition as experienced with my TeensyMIDIPolySynth (TMPS).

I am also very pleased that the following improvements are now observed/employed as part of my current TMPS operating conditions:

- I previously had to build the audio portion of my TMPS using "Smallest Code with LTO" optimization to keep from exceeding the amount of available RAM . . . now I can build using the standard "Fastest" optimization & still have plenty of stack space (& faster execution as a bonus), which works very well for both normal & extreme operations [ thanks to your changes now utilizing a fixed buffer, etc. ]

- I previously had to overclock the T4.0 audio processor in my TMPS at 1.008 GHz to allow the timely update of all of the audio objects without hanging (& even then, it would, at times, hover dangerously close to a 99% processor loading level when all notes & many of the associated audio objects were active) . . . now I can run the audio T4.0 only slightly overclocked at 816 MHz (I have retained heatsink & cooling fan, just in case) & after running continuously for several hours, I have not seen the audio processor loading ever exceed a maximum of 88%, even under the worst of conditions, leaving an amount of reserve headroom on the audio processor that I am perfectly comfortable with [ thanks to your optimization work & the potential use of the DSP capabilities of the processor ]

- I am happy that all of the TMPS strings objects are now tunable (& can be modulated as well, but that's much less important to my particular implementation than is the ability to tune the TMPS to a real-world analog instrument), just as the modulated waveform objects have always been capable of [ I sincerely appreciate your efforts that made this a reality ]

Thanks again & I certainly hope that @PaulStoffregen will consider incorporating your changes into the Audio Library at some time in the very near future.

Mark J Culross
KD5RXT
 
Great, glad to hear it’s now working for you. I guess you mean my “changes not utilizing a fixed buffer, etc.” :) I’d love to see / hear an example of it in action using the new Karplus-Strong object; maybe you could get your son to perform a short piece?

Given the increased CPU load, I think the best route will be to do what you’ve done and make it a separate object. I’ll package it up in that form and add a documentation section to the Design Tool. Not sure where @BenIP has got to with his filter changes, but if they arrive I can consider those - I plan on doing that in a way that doesn’t impact TMPS, so there will be a spectrum of capability vs CPU load for users to pick from.
 
I think that adding the modulated strings audio object to the existing synth_karplusstrong.* source files (with updated class names ending in "Mod") would be good. This would match the way the the original unmodulated waveform audio object & the newer modulated waveform audio object are both available within a single pair of source files. This should allow the use of either, modulated or unmodulated, creating no adverse impact on any current implementations where anyone makes use of the original unmodulated strings audio object.
 
@h4yn0nnym0u5e (any anyone else that may stumble upon this thread in the future):

Here are some measurements comparing the audio processor loading of the original unmodulated strings audio object vs. the updated modulated/tuneable strings object, as made use of in my TeensyMIDIPolySynth (TMPS). The absolute numbers by themselves are probably meaningless (only relevant to my particular 3-voice, 12-poly implementation, which incorporates 36 strings audio objects, be it good or bad), but can still give a relative comparison between the audio processor load imposed by the two different audio objects.

These numbers were gathered using my TMPS configured to play only the strings objects (all other waveform objects are inactive, all noise sources are inactive, all envelopes are inactive, all filters and/or effects are inactive, & the reverb is inactive). Note that all of the other objects are still initialized, configured, etc., they are just not being exercised by the incoming noteOn() / NoteOff() MIDI calls.

Using the original unmodulated strings audio object (T4.0 clocked at the standard 600 MHz, "Faster" optimization):
Idle (no notes playing): 26.61%
Maximum load (all notes variably playing via MIDI): bouncing between approximately 26% & 40%, topping out at 44.20% maximum

Using the updated modulated/tunable strings audio object (T4.0 clocked at the standard 600 MHz, "Faster" optimization):
Idle (no notes playing): 26.77%
Maximum load (all notes variably playing via MIDI): bouncing between approximately 26% & 51%, topping out at 62.30% maximum

Using the original unmodulated strings audio object (T4.0 overclocked at 816 MHz, "Faster" optimization):
Idle (no notes playing): 19.55%
Maximum load (all notes variably playing via MIDI): bouncing between approximately 19% & 28%, topping out at 32.41% maximum

Using the updated modulated/tunable strings audio object (T4.0 overclocked at 816 MHz, "Faster" optimization):
Idle (no notes playing): 19.65%
Maximum load (all notes variably playing via MIDI): bouncing between approximately 19% & 36%, topping out at 46.54% maximum

For me & my particular implementation/utilization of the strings audio objects, these numbers show that the addition of the modulated/tunable capability actually adds only slightly (per individual object) to the measured audio processor load. I am very pleased with this implementation of the modulated/tunable strings audio object.

Mark J Culross
KD5RXT
 
Here's a <link> to a folder on my Google Drive containing a few example recordings from my TeensyMIDIPolySynth (TMPS). Each of these recordings started as a MIDI file played by Aria Maestosa, with audio routed from the LINE OUT on the TMPS into a Pyle PAD43MXUBT USB soundcard mixer plugged into my laptop, and all recorded by Audacity on my laptop. All recordings are of an excerpt of approximately the last 2:00 of JSBach's Toccate Fugue (BWV 565). To fully appreciate the sound that has been generated & captured, these files really should be played on something other than PC speakers.

The different instrument sounds are generated by altering the settings in the TMPS, which supports up to 17 of what I call "SAVES" (essentially, a PATCH), and these recordings are each made using some of the default SAVEs. Note that there are no "sound samples" employed in the TMPS . . . all sounds were created by me, while twiddling the knobs until I got close to what I considered the desired sound, then saving the setup. Apologies in advance for any artistic liberties that I may have taken with this.

The title of each WAV file indicates which instrument was selected/activated, along with any additional effects (reverb, tremelo, etc.).

Enjoy !!

Mark J Culross
KD5RXT

P.S. I'd like to acknowledge primary credit to @PaulStoffregen for his excellent Audio Library. I'd like to also acknowledge credit for the SCreverb audio object to @Pio, & credit for the updated modulated/tunable strings audio object to @h4yn0nnym0u5e.

P.P.S @PaulStoffregen, you are free to use these audio recordings as examples of the variety, depth, & quality of sound that can be created with your Audio Library, in any way that you see fit. MJC
 
I've been looking at merging the old and new files, as discussed, and I think the new one still needs work to get the basic operation comparable to the old. The decay is significantly worse on the new one, and preliminary changes have fixed that but put the CPU usage back up :(

I'm going to see what I can do to sort that out - watch this space...
 
Here's a merged version with better decay characteristics - see what you think. The CPU load is pretty much as before, luckily.

I can report that this version works as well as the prior. I did, as before, include these files locally in my project source directory (in general, I prefer not to overwrite any core files, to avoid the case where an intentional and/or unintentional update then overwrites my custom files...with this in mind, I made only slight mods to avoid audio object clashes & re-definitions, but the changes are all inconsequential to the actual meat of the audio processing code), & everything works great, with maybe even a slight decrease in the processor loading of the updated modulated/tunable strings audio object.

Once again, thank-you for your continued work on this !!

Mark J Culross
KD5RXT
 
Thanks for checking it out, Mark. Unless something occurs to me I’ll park it for a bit, to give @BenIP a chance to redo his PR, which looked as if it had some desirable features but didn’t actually compile :unsure:

As and when progress resumes on Teensyduino 1.60 it may be appropriate to file a PR, but as that’s been going since last October it may be Paul will want to finish and release it, then start straight in with 1.61. Now he has so much spare time on his hands :ROFLMAO:
 
Hi there,

I'm very sorry for the late response, I had a bit of a crazy week at work. But today is worker's day so first thing is to update you :)
Of course, sorry for the mistake in the PR: I manually reported the change from the class I'm working on into your version as I'm having trouble managing different version of the same library in my setup.

In the meantime though I came to be quite unsatisfied with the high frequency decay characteristics. Even with the two pole feedback filter which allows to give more prominence to the high frequency, the decay was very fast (much faster that the original method). At first I thought that is was due to the systematic attenuating error when doing division with integer, but even added dithering did not solve the issue.

I then tried to implement something called "decay stretching" in which you basically choose at a random probability to skip the feedback filter entirely ( having `out= in;` as your feedback "filter"), but to my surprise to no effect. Even when skipping the filter all the time (using `out= in;` all the time), the decay was still there.

I tracked it down to the buffer operator [ ]. Basically, when the fractional sample selection has itself a low pass filtering effect. It makes sense if you imagine the frequency getting close to our integer indexes, of course taking the mean will attenuate it. If you have a low enough attenuation on the main feedback filter, it then becomes prevalent. I tried out a few things :
- First of course to validate the problem, I recorded a high pitch pluck with long decay and slow modulation with the standard logic. Event with the long decay setting, the sustain was quite low
- Then I simply modified the buffer operator [ ] to stick to the closest buffer (emulating the first version). Here the decay is very long, but of course you can hear the steps.
- Finally I tried to keep the buffer in integer position, but keep track of the error at each reading to add it to the next loop. While the result had slightly more sustain, the sound is quite unpleasant.

All in all none of the solution were really satisfying. This is not really an unknown issue, and it seems that some "allpass" methods have been designed, but I have to do some reading to see if those could be implemented. If not, I'll probably look into oversampling techniques.

Trouble is, without solving that issue, the two pole method only has some limited advantages of controlling the brightness but can't really overcome the sustain loss at high frequency.

Sorry again for the delay, and thanks for the incredible work on the matter. Karplus Strong with input signal can really make some cool sounds, and I'm sure this will greatly benefit the Teensy users !

 
My musician son was over for a visit today, so of course he wanted to tinker around with the latest version of my TeensyMIDIPolySynth (TMPS). He noticed something that I had not previously paid any attention to: if you press a key on the MIDI keyboard (generating a call to noteOn()) but never let go (so no corresponding call to noteOff()), the output from the strings audio object(s) will decay in amplitude as expected, but they will continue to ring forever, albeit at a very low amplitude, with a sort of buzzing sound. Unlike a real physical guitar string (which fades to zero amplitude), the sound from the strings object(s) will only terminate if/when the MIDI key is released. Is this a characteristic of the KarplusStrong algorithm, or is there something else at play here ??

Mark J Culross
KD5RXT
 
I think there always was some residual noise even after a long time, possibly due to rounding errors in the calculations, but this may be different. My use of DSP instructions has increased some rounding errors by one bit, I think, which may be the cause.

I’ll take a look later, I was planning to anyway to see if allowing a feedback level of slightly over 1.0 might provide some “make up gain” for higher notes, as per @BenIP’s comments. A better way would be quadratic interpolation for the fractional sample calculation, but that would blow the CPU load sky high!

@BenIP , thanks for looking into this, appreciate some people have day jobs (only just stopped myself…)! I’m guessing any improvement you achieve will come at a cost of CPU load, but that need not be a showstopper, as long as we can make it one of multiple options available to the user. The latest version actually uses the [ ] operator much less, as it turned out I was averaging the wrong samples which was part of the poor sustain problem.
 
OK folks, some improvements pushed up to the merged branch. It was the usual integer calculation loss of significance type thing, combined with a subtle difference between bit shift and division when it comes to negative numbers. It's a tiny bit higher CPU load, but not enough to notice, I reckon.

@kd5rxt-mark , you might want to wire a noise source into the drive input, if you haven't already done so, and experiment with the sounds you get when setDriveLevel() is non-zero. All manner of possibilities there!
 
@h4yn0nnym0u5e: I just finished adding a "distortion" slider (using your added strings.setDriveLevel() function) & and an associated enable/disable button to the (per-voice) strings menu in my TeensyMIDIPolySynth (TMPS), which (I agree) adds a very interesting effect to the strings. I do think that users might find this addition useful. Thanks !! Everything is working very well !!

Mark J Culross
KD5RXT
 
Back
Top