On adding an internal synthesizer engine to the The Mosaichord MIDI controller using the Teensy audio library.

jsnow

Member
A few months ago, I added an on-board synthesizer to the Mosaichord, my Teensy-based microtonal MIDI controller. I thought I'd post some notes here about how that went, what worked and what didn't, it case it's useful to anyone else doing the same thing, or to anyone working on improving the Teensy audio library.

mosaichord-front.jpg


Here's more information on the Mosaichord: https://desideratasystems.com/mosaichord.html

(Basically, it's a 4-octave keyboard using a 28 note scale. The notes are taken from 7-limit just intonation. The keys themselves are pressure sensitive, and use force-sensitive resistors. I can scan the whole keybed about 500 times per second.)

Here's what the end result sounds like:

Sounce code and user manual can be found here: https://github.com/jimsnow/microtonal-controller

The Mosaichord is something I've been working on for awhile. For the last few years I've been using it as a MIDI controller with a variety of external synthesizers. This works well enough, but I also wanted an internal sound engine so a) I wouldn't have to plug it into some other external device just to get sound and b) I can configure the sound engine exactly in a way that works best with the capabilities of the Mosaichord.

I was able to get a pretty good result without spending a lot of time on it. It sounds like what you'd expect a simple analog-modelling synthesizer to sound like.

Some of the details about how I did it follow:

The DAC

The output device I'm using is the PCM5102a DAC from Texas Instruments, which uses the i2s bus. One nice thing about the 5102a is that it can generate the SCK clock signal itself using a PLL, which frees up a pin on the Teensy. It's just a line output; it's not supposed to be able to drive headphones, but I've found that in practice it actually can.

For a long time, the Mosaichord firmware just used the DAC to output a sine wave of the middle note (1/1) on the keyboard. This was a pretty easy to do using the Teensy audio system design tool. Basically just connect a "sine" object to both channels of an "i2s2" object, and add some code to set the frequency on startup and anytime it transposes to a different key. (The Mosaichord has transpose buttons for +/- an octave and +/- a 100-cent semitone.) This verified that the DAC worked as expected and is occasionally useful for tuning analog oscillators if I'm using the Mosaichord to control a eurorack setup. (The first PCB rev with the DAC didn't work because I neglected to connect one of the pins to ground that was supposed to be grounded, but it's worked fine ever since I fixed that.)

Polyphony and the Audio Design Tool

The Mosaichord is intended to be a polyphonic instrument, so what I want is a certain "voice circuit" and then replicate it a bunch of times, and feed the outputs into a mixer. The audio system design tool doesn't really have any notion of "duplicate this thing N times", so I used the design tool to make one voice, then refactored the resulting code so that I was creating arrays of objects rather than a single one, and putting initialization in a loop.

Waveforms

The basic architecture I'm using is subtractive synthesis. Basically, start with a basic waveform (saw, triangle, square, sine, etc...), modulate the oscillator's volume by key pressure, and run the output of that into a resonant low-pass filter.

I initially used the regular variants of the oscillators (e.g. WAVEFORM_SAWTOOTH), but they sounded very "ring-mody" in high octaves. Switching over to the bandwidth limited versions (e.g. WAVEFORM_BANDLIMIT_SAWTOOTH) fixed that.

Volume slew

I noticed that if I use an oscillator without a lot of harmonics like sine wave, there's an audible white noise sound when I module the volume. (Volume is modulated by key pressure.) I'd run into the same problem recently using a MIDI-to-CV device with the Mosaichord, and in both cases the cause is the same: making abrupt changes to volume causes a stair-stepping effect that's audible as noise.

For the Mosaichord I decided to control the volume at the oscillator rather than the mixer. I don't think it matters a great deal which I chose, but the issue in either case is that I need to apply some smoothing to the volume. And I can't do it without editing the internals of either the oscillator or the mixer because controlling the volume externally can only be done once per "block" (I don't remember how big a block is, but I think it's somewhere in the neighborhood of 32 samples or so) whereas I want it to be smoothed at a per-sample granularity.

I ended up just making a copy of AudioSynthWaveform and adding a slew limit to the volume that I called TaperedAudioSynthWaveform. I would rather have just derived a new class from AudioSynthWaveform, but there was some technical reason why that didn't work. (I don't remember what it was exactly, but probably along the lines of I needed to override the "amplitude" method but it isn't virtual, and I needed access to some of the private variables.)

My slew limit algorithm could probably be better, but after a few tries I got something that made the noise far less noticeable and I figured it was good enough for now.

The audio library could probably benefit from having configurable slew limits on all kinds of settings, but especially oscillator volume, mixer channel volume, filter cutoff frequency and resonance.

Filters

For my resonant low-pass filter I'm using AudioFilterBiquad. So far it's working pretty well. I'm just using stage 0, and use a frequency value between 20.0f and 20000.0f, and a resonance value from 0.1f to 5.0f.

Mixers

I give each voice its own dedicated mixer (so I can easily add sub-oscillators or whatever later). The Teensy audio library has a limit of 4 inputs per mixer and I'm using 16 voices, so the voice outputs get sent to 4 mixers that get mixed down into a single output by another mixer. If I wanted more voices, I would have to add another layer to the tree.

It would be very nice if there were a mixer object with more inputs so we wouldn't have to construct a tree anytime we need more than 4.

Reverb

After that last mixer, the output gets sent into a reverb. I initially used freeverb, but it was kind of underwhelming. I eventually switched over to using the plate reverb from Piotr Zapart, which sounds reasonably good. It has a bunch of parameters that I haven't spent much time tweaking.

I added a final mixer pair (one each for right and left), and feed into those the (mono) dry signal and the wet signals from the right and left reverb channels, respectively. One of the Mosaichord knobs fades from full dry to full wet by adjusting those final mixer input gain settings. The reverb output is not very loud, so I give it an extra 3x gain.

Karplus-Strong

I tried using the AudioSynthKarplusStrong oscillator, but gave up on that for now because the tuning is not very accurate. (Basically, its wavelength is determined by the number of samples at 44.1khz, and that has to be an integer.) It also has no way to pitch-bend a note after it begins, which isn't a total deal-breaker but it is a significant limitation.

I really like Karplus Strong oscillators in general, so I hope we get a better implementation in the Teensy audio library eventually. I made an attempt at improving the code myself, but I didn't really get anywhere with it.

Other non-sound-library details

Having an integrated sound engine gives me more of a reason to care about saving settings that can persist across reboots. I haven't finished implementing it yet, but my plan is to use LittleFS and the built-in flash memory.

Ideally one should be able to export settings to a computer, and the LittleFS library does support MTP, but unfortunately there isn't an option in the Arduino IDE (under Tools->USB Type) for "MIDI + Serial + MTP". I understand I can add that setting myself by editing the Arduino IDE's configuration, but for this project I want end users to be able to recompile the source code with just a stock installation of Arduino and Teensyduino. It would be nice if that were one of the built-in options.

Another random suggestion I have for improving the MIDI library is to make running status a setting you can toggle on and off at runtime. So far I haven't run into any synths that are confused by running status, but if I did it would be a shame to basically have to turn it off for all synths to get that one synth to work.

C++:
struct MidiLocalSettings : public MIDI_NAMESPACE::DefaultSettings {
  static const bool UseRunningStatus = true;  /* avoid re-sending status byte when it hasn't changed */
  static const unsigned SysExMaxSize = 10;     /* we don't expect to receive sysex */
};

MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial5, dinMidi, MidiLocalSettings);
 
@jsnow: Nice project & great sound. If you'd like to investigate a Karplus-Strong object that allows modulation/tuning, take a look at <this> thread. You can find the updated source (specifically synth_karplusstrong.cpp & synth_karplusstrong.h) in <this> repo by @h4yn0nnym0u5e (he did all the real work . . . & we get to enjoy / employ it !!).

Mark J Culross
KD5RXT
 
@kd5rxt-mark - you beat me to it!

Love the looks, though it appears to be quite challenging to play - is that a standard keyboard layout for the scale?

On slewing things, yes, it's a bit of a design flaw that so many audio objects react in a stepwise fashion when updated from code. The default block is 128 samples, giving you an update interval of about 2.9ms at 44.1kHz, by the way. Much may be done by use of the AudioSynthWaveformDc element, which does have a slew option when you set its amplitude. For example, volume changes can use this plus an AudioEffectMultiply; it does make your design more complex, but stays within the available Audio library capabilities.

On designing polyphony, you might want to take a look at the Design Tool++. The export also caters for a templated mixer with any channel count you care to design (I think the technical limit might be 256 channels...). A few minor changes to the shipped Design Tool would remove some of the effort in re-factoring from the exported code; I thought I'd done a PR for that, but it appears I haven't - I'll have to look into that, though at the current rate of progress it'll be 2028 before it's included.

On non-sound-library stuff, based on this thread I've messed about a bit with adding custom USB options, and got to the point of implementing Audio+MTP+Serial (CDC) and Audio+MIDI+MTP+Serial (emulated); MIDI+MTP+Serial (CDC) would definitely be a possibility. If you read that thread you will note that there's a hard limit on 6 available USB endpoints, so the combinations are fairly limited. That's why I had to use emulated Serial for the second option, which in turn has its own problems in that it requires a "gateway" application to translate the data flow, which is not open source, is undocumented and is limited in its capabilities - for one thing it operates only on a limited number of USB product IDs, most of which are already in use, and some of which won't work with MTP under Linux.
 
@jsnow: Nice project & great sound. If you'd like to investigate a Karplus-Strong object that allows modulation/tuning, take a look at <this> thread. You can find the updated source (specifically synth_karplusstrong.cpp & synth_karplusstrong.h) in <this> repo by @h4yn0nnym0u5e (he did all the real work . . . & we get to enjoy / employ it !!).

Mark J Culross
KD5RXT

I might have to give that a try. One of the comments in the thread mentioned they'd substantially improved the tuning accuracy...
 
Indeed, the period resolution is now about 88.6ns compared to the original's 22.7µs, though TBH I'm not sure exactly what that means for super-high notes. There isn't a user control, but in theory one could improve it by increasing the value of fracShift - see this line of code. I have fairly cloth ears, though even I could tell the original tuning wasn't great, but if you think even more resolution is needed we can give it a try.

EDIT: just out of curiosity I did the calculation. Assuming that's error-free, for MIDI note 119 = B8 = 7902.133Hz, the pitch error should be about 0.8 cents. I believe that will suffice, but of course that does assume I've got the code entirely correct.
 
Last edited:
Love the looks, though it appears to be quite challenging to play - is that a standard keyboard layout for the scale?

The layout is my own invention. It's not terribly difficult to play, aside from some of the inherent complexities of using just intonation -- basically, you have to be aware of distinctions between notes that are only slightly different in pitch and if you use the wrong one it sounds off.

Unlike a piano, the keyboard is isomorphic, so that helps. Any given chord voicing always has the same shape.

Much may be done by use of the AudioSynthWaveformDc element, which does have a slew option when you set its amplitude. For example, volume changes can use this plus an AudioEffectMultiply; it does make your design more complex, but stays within the available Audio library capabilities.

Huh, I didn't know about that one or I would probably have used it.
 
Indeed, the period resolution is now about 88.6ns compared to the original's 22.7µs, though TBH I'm not sure exactly what that means for super-high notes. There isn't a user control, but in theory one could improve it by increasing the value of fracShift - see this line of code. I have fairly cloth ears, though even I could tell the original tuning wasn't great, but if you think even more resolution is needed we can give it a try.

EDIT: just out of curiosity I did the calculation. Assuming that's error-free, for MIDI note 119 = B8 = 7902.133Hz, the pitch error should be about 0.8 cents. I believe that will suffice, but of course that does assume I've got the code entirely correct.

My math (which I'm not 100% sure is correct either) says the accuracy is about 1.21 cents at B8. Anyways, that should be well within reasonable tolerances considering it's near the upper end of human hearing anyways. Even in the mid-ranges that would be pretty decent.

Haskell code:

("cents" is a function that converts a musical interval expressed as a floating point ratio into cents)
Code:
*Main> cents 1.0       
0.0
*Main> cents 2.0
1200.0
*Main> cents 0.5
-1200.0
*Main> cents (2**(1/12))
100.00000000000007
*Main> let b8wavelength = 1.0 / 7902.133
*Main> let resolution = 88.6/1000000000.0
*Main> b8wavelength
1.2654811049118004e-4
*Main> resolution
8.86e-8
*Main> cents (1.0 + (resolution / b8wavelength))
1.2116630247691178
 
Moment of panic, couldn’t fault your 1.21 cents … but then realised I’d worked out that 7902.133Hz is 1428.68 “microsamples” of 88.6ns, so the error from rounding down is only 0.68, not 1, hence the pitch error being 0.82 cents.

The updated Karplus-Strong code is currently only in my repo linked in post #2. I’ll do a PR fairly soon, but as Teensyduino 1.60 has stalled and PRs are being merged at a glacial pace, you’re probably best off grabbing the two relevant files and adding them to your modified Audio library.

You might want to rename your new class, by the way. The general convention seems to be naming them Audio<type><function><detail>, with the notable exception of AsyncAudioInputSPDIF3 which somehow got through the net.
 
You might want to rename your new class, by the way.
Yeah, I named it "AudioSynthKarplusStronger" after your feature branch. Seemed like a good name.

Tuning precision seems to be good. I haven't measured it, but it sounds like it's probably correct.

I ended up needing to increase the audio memory blocks by a lot. (Was 40, now it's 400.) That seems to be able to handle 16x polyphony in the neighborhood of C0.

I've tried playing around with the feedback levels; it seems like the high notes fade out too quick whereas the low notes tend to linger a long time. I figured that was probably due to the high notes decaying faster because they go through more decay iterations than the low notes in the same time span, but I eventually realized even if I set feedback to 1.0, the high notes still fade quickly. So maybe there's some low-pass filtering that's a bit too aggressive? Or maybe it's just a limitation of the algorithm (e.g. fewer samples per note, so it takes less time to decay because there's not much information to begin with)?

Is there any way to pitch bend yet? It looks like it was implemented with pitch bend in mind, but I didn't see any obvious API call to bend a note after note-on.

I've also experimented a bit with "drive". Right now I'm just sending white noise in, which gets an interesting result but it's really fizzy. Perhaps I'll try sending the output of my subtractive synth into the Karplus Strong input.

Karplus Strong seems to go well with Piotr Zapart's plate reverb model.

Anyways, your branch of Karplus Strong is a big improvement over the old one.
 
Yeah, I named it "AudioSynthKarplusStronger" after your feature branch. Seemed like a good name.
Wah? I meant your TaperedAudioSynthWaveform needed renaming...
Is there any way to pitch bend yet? It looks like it was implemented with pitch bend in mind, but I didn't see any obvious API call to bend a note after note-on.
There's no API call, because that's a poor way to implement pitch bend. There are two inputs to the AudioSynthKarplusStrongModulated object: you've clearly found the Drive one, the other is a frequency modulation input using a similar approach to the AudioSynthWaveformModulated object. You should not change the available bend range using the frequencyModulation() API call while a note is active, because the number of blocks needed is calculated at noteOn(). To bend via an API call, connect a DC source to the bend input, and change its value, ideally with a suitable slew time - you can of course mix that with an LFO etc. for full flexibility.

Yes, the sustain can be disappointing - that was kind of the intention of the drive input. There was a contributor who thought he might be able to improve that, but he vanished quite quickly. His (unfinished) code got a bit expensive on CPU, and as the intent was to get something fairly economical for the TMPSynth I never pulled it in.

As you say, it can chew audio blocks something fierce, though playing 16x C0 notes is probably ... a niche use-case?
 
I thought you just meant that I needed to rename things so I don't have name collisions with the older versions of those classes that exist in the audio library, in which case it wouldn't really matter whether I abide by official naming conventions. If I come up with something that's worth doing a PR to the audio library, I'd probably just make changes to the existing classes.

I rigged up pitch bend to set a DC value connected into the FM input on Karplus Strong, and it's working. Thanks. 1ms of slew seems to be enough to prevent scratchy sounds when changing frequency.

16x C0's is a niche use case, but 16x polyphony isn't than unreasonable with Karplus-Strong, especially if notes are sustained. I'm currently invoking noteOff when the keys are released, but I could defer noteOff to let them linger a bit. I'd rather not have things glitch out if some user decides to mash all the keys at once in a low bass range.

It would be nice if noteOff didn't kill the note right away, but rather just applied a more aggressive (configurable) damping factor and ends the note whenever it decays effectively to nothing. An application could sort of do this itself, but it would be hard for the application to know when it's safe to send the noteOff.
 
100% agree you need to keep the synth robust, and if having a bonkers amount of audio memory available is the price then so be it! There are other ways, like monitoring usage and refusing to play a note that would tip the system over the edge, but there’s no point doing that if you don’t have to.

It’s worth being aware that the library imposes a limit of 896 blocks - I don’t believe that’s well documented…

With the new code, noteOff actually doesn’t kill the sound immediately, but fades it over the course of 3 block times (8.7ms with stock values). This was just a glitch-avoidance strategy, it’s not intended as a sensible bit of physical modelling.

There’s a couple of approaches to ending a note. The first is to do what is needed anyway for oscillator-based voices, using an envelope and its noteOff method, allowing re-use of the voice when isActive goes false.

For a Karplus-Strong voice, you could try lowering the feedback level, and using a peak or RMS object to monitor the output level. That would need a bit of testing to be sure the glitch that will occur when the feedback level is changed isn’t too audible. If it is nasty, I’ll consider adding a third input to control feedback level on a sample-by-sample basis - I don’t think it’d add much to the CPU load.
 
I figured out how to make the high-pitched notes sustain a lot longer, and it's a simple change.

The current algorithm uses a very crude form of low-pass filtering. Whenever computes the new audio samples, it takes the average of the value from the previous time it looped through the buffer and the value of the immediately adjacent previous sample. This causes high-frequency stuff to get smoothed out quickly.

The minor tweak is to take a weighted average. If each new sample comes from the previous loop with weight 7/8 and the adjacent sample with 1/8 weight, then it takes a lot longer for high frequencies to decay. It should be pretty easy to make this a configurable parameter...

C++:
  [B]int32_t fbk_prior = (_feedbackLevel *0x2000) >> 15;
  int32_t fbk_in = (_feedbackLevel * 0xe000) >> 15;[/B]
    ...

    // finally, create new audio data
    if (nullptr == bend) // no bend, just compute it
    {
        for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++)
        {
            int16_t prior = theBuffer[bufferIndex - increment]; // frequency fixed at "baseLen" samples
            int16_t in = theBuffer[bufferIndex - baseLen];
            [B]int16_t out = (in * fbk_in + prior * fbk_prior) >> 16;[/B]
            if (nullptr != drive)
                out += (*drive++ * _driveLevel) >> 16;
            *data++ = out;
            theBuffer[bufferIndex] = out; // store feedback data for next cycle
            bufferIndex += increment;
        }
    }

Changes to the pitch-bend case are similar.
 
Last edited:
That looks very promising, thanks. I’ll take a look / listen, and push an update if no serious issues become apparent. @kd5rxt-mark, if you’d be so kind as to stand ready to try it on TMPS, that would be great!

For backwards compatibility I think I’ll leave setFeedbackLevel(float) as-is, and add an overload that takes two floats which the user can weight as they see fit. It’ll probably need an example to demonstrate the difference.

@jsnow, looking at the changed code, and back to post #2, you’re actually not using the latest version: you should start from this branch. Apologies to all, I do tend to let my branches get a bit overgrown…
 
It turns out there is actually a bit of a complication: if you change the feedback balance between "in" and "prior" it changes the tuning.

Since the wave is always being smooshed in one direction, the low-pass filter has the effect of altering the pitch. The current code seems to be properly in tune with its default 50/50 balance, but if you change that to make it brighter it goes sharp, and if you make it less bright it goes flat.

So, it seems there needs to be some pitch compensation factor applied at some point.

For backwards compatibility I think I’ll leave setFeedbackLevel(float) as-is, and add an overload that takes two floats which the user can weight as they see fit. It’ll probably need an example to demonstrate the difference.

I think setFeedbackLevel can stay as it is, and there could be an additional brightness control:

C++:
void brightness(float level) {
    if (level > 1.0f) {
      level = 1.0f;
    } else if (level < 0.0f) {
      level = 0.0f;
    }

    /* feedback_in and feedback_prior are int32_t */
    feedback_in = level * 0x10000;
    feedback_prior = (1.0f - level) * 0x10000;

    /* we should update the pitch correction factor here once we figure out how to do that */
  }

It would be a little weird to give users direct access to feedback_in and feedback_prior, as it would make them responsible for making sure that together they never exceed 1.0f.

In "update" we could do something like this:

C++:
int32_t fbk_prior = (_feedbackLevel * feedback_prior) >> 15;
  int32_t fbk_in = (_feedbackLevel * feedback_in) >> 15;

...and then use fbk_prior and fbk_in in the computations to generate new samples.

I think it's probably most intuitive to give users a "feedback" control that affects sustain, and a "brightness" control that sets how aggressive the low-pass filter is.
 
It turns out there is actually a bit of a complication: if you change the feedback balance between "in" and "prior" it changes the tuning.
That's interesting - I'll look out for it and see what can be done to keep the tuning accurate. Not intuitive why that occurs...

I'm working on this right now; currently I'm looking at making the method look like setFeedbackLevel(level, weight = 0.5f), so the existing default behaviour is preserved, and a higher value of weight will result in a longer decay. I'll put in a range check for that, too - good point. For best effect I suspect it'll need setting on a per-note basis, possibly also being velocity sensitive, so a single method seems to make sense to me.
 
I tried a crude fix for the tuning and just applying a correction function before I send the frequency in to noteOn():

C++:
  float brightnessCorrection(float frequency) {
    float sharpness = stringSynthBrightness - 0.5f;
    float sensitivity = frequency/middleCFrequency;
    return 1.0f - (sharpness * 0.012 * sensitivity);
}

This seems to get things in the general ballpark.

I think the "why it happens" thing has a reasonable explanation. The low-pass filter works by copying some of the amplitude from the previous sample and adding it in to the current sample. So, naturally that's going to cause the pitch to go flat, because you're stretching the wave. The pitch isn't just determined by how often the buffer repeats, but by how the contents of the buffer move over time. If you make the low-pass filter less aggressive, then it won't go as flat.

I suppose the problem would go away if we made the low-pass filter act in a bi-directional way. Like, you take some amplitude from the previous sample if it's higher, but you go the other way if it's lower. But then the order you scan through the array would matter too...

What's weird is that the default 50/50 split is perfectly in tune, and there's no obvious pitch correction factor.
However, if you look at noteOn:

C++:
// actual number of samples for requested note
  baseLen = AUDIO_SAMPLE_RATE_EXACT*increment / noteFreq - increment;

Pitch is determined by baseLen. Everything looks normal here except, what's with that "- increment" at the end? It looks like the code is deliberately making the buffer too short by one sample, which one would think would make the note go sharp... And if every sample, half the amplitude gets pushed forward by by one sample, and then half of that gets pushed forward by another sample, and a quarter of that gets pushed forward by a 3rd sample... maybe that works out as making things go flat by exactly one sample and it all balances out?

I'm not sure if that math works out, but anyways that's my theory.

For best effect I suspect it'll need setting on a per-note basis, possibly also being velocity sensitive, so a single method seems to make sense to me.
NoteOn is a reasonable time to apply feedback/brightness settings, but I can think of a couple use-cases for changing those settings while a note is playing -- which might mean having to automatically bend the note to keep it properly in tune.
 
OK, I've made some changes on a new branch you can find here.

As noted above, I've just added an optional weight parameter to setFeedbackLevel(). It defaults to 0.5f, but can be set between 0.0f and 1.0f. I believe the resulting code conforms fairly closely to the suggestion in post #16, with the proviso that that post was referencing an out-of-date branch.

My observations are that I can see the pitch shift, but not the improved sustain we were aiming for! Also, we may be hearing the return of the "loss of significance" problems, in that with a weight other than 0.5 the occasional note does sustain forever, but at a very low level and the wrong pitch.

I tried looking up the Karplus Strong theory, and most websites either echo Wikipedia, which doesn't quite have the required depth, or go into abstruse maths involving Z-transforms, which I don't understand. "I think I saw somewhere" that the simple 0.5+0.5 filter used by Paul's original code, and copied fairly faithfully by me, results in an extra 0.5-sample delay being added to the N samples you'd intuitively expect. I think that's the reason for the ... - increment noted in post #20, which became ... - increment/2 in the previous branch. Possibly it should now be ... - increment * weight, but I'm not going to chase that down until such time as we do have better sustain and no other unpleasant artefacts.
 
My observations are that I can see the pitch shift, but not the improved sustain we were aiming for!

That's strange. Do different values of "weight" have any noticeable effect on timbre at all? And did you set feedback level close to 1.0 at the same time?

Maybe there's some major difference between your old branch I've been working off of and your latest branch...
 
Well, a couple of the commits after the old branch do mention better sustain and tuning, plus fixing an incorrect infinite low-level “sustain” due to loss of precision. So I would 100% recommend starting from those, or my new branch, which should behave the same if you don’t set the weight.

Plus, you can try the weight parameter to see if you think it does anything :), and inspect my changes to see if they’re equivalent to the ones you posted. They won’t be identical due to the fixes for loss of precision, but they look very similar.

Yes, I did set the feedback level close to 1.0. There may be some minor timbre differences, but nothing significant compared to the usual variation due to the starting stimulus being pretty random.

I may see if I can get some consistent objective measurements tomorrow, depending on what you may or may not find and report on. I’m guessing we’re in somewhat different time zones (UK here), which always slows things down a bit.
 
Okay, I gave your new version a try. You're right, the "weight" value doesn't noticeably affect timbre. Your version sounds like how my old version sounds if you crank the brightness all the way up (no low-pass filtering).

My guess is that the "better sustain" commit probably disabled the low-pass filter entirely. Which does give you better sustain, but you miss out on a whole range of interesting sounds between "very aggressive low-pass" to "no low-pass at all".
 
Back
Top