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

The design intent of this branch was to preserve the original functionality, while adding abilities to bend pitch and get to lower notes. (There’s even a TODO comment in Paul’s original code about the latter.) A side-effect of bending the pitch was better intonation, because it needed the fractional sample capability.

As such, the feedback filter was intentionally left as the same minimal average of two samples, which in itself turned out to be much harder than I expected. The code you’ve worked with previously wasn’t acceptable for some reason, probably due to poor sustain given the sequence of commit comments; I believe I was averaging the wrong two samples.

Either way, I believe there has to be some sort of filter in the delay / feedback loop, otherwise the algorithm simply won’t work. I did briefly toy with putting a more versatile filter in the loop, by re-using some of the other Audio library code, but quickly got bogged down and abandoned the effort. I don’t recall why now, but it was likely due to having to deal with notes higher than ~344Hz, i.e. with a period of less than one audio block.

It may be useful if you were to fork my code, make changes that suit your usage, and let us know when you’re happy - maybe you already are? Then we can try your code out. I would caution against using bit shifts like you have in post #16. I think that’s remnants of my code, and it turns out there’s subtle differences compared to a true division when it comes to negative numbers, which IIRC resulted in the infinite low-level sustain mentioned before. You’ll see the use of the lvMul constant introduced in this commit - it seems to work OK without drastically increasing CPU usage.
 
I'm not sure a filter is 100% required, though you at least need some attenuation if you don't want the note to go on forever. However, I suspect that a filter of some kind is needed to get good results. Running it through a biquad filter or something sounds like a good idea. I've also heard of comb filters being used to get Karplus-Strong-like sounds, but I don't understand the details. (https://pichenettes.github.io/mutable-instruments-documentation/modules/rings/manual/ see section on "polyphony and synthesis method".)

Playing around with your latest branch, I reverted a couple lines where we get the values of "prior" and "in" to do the way it was done in the old branch I've been working off of. With that change, the "weight" control works as expected and actually makes a big difference.

C++:
        uint32_t perData[AUDIO_BLOCK_SAMPLES];

        computeBendData(perData,bend->data); // compute look-back amount for each sample
        release(bend);
        for (int i=0; i < AUDIO_BLOCK_SAMPLES; i++)
        {
            //int16_t in, prior;
            //theBuffer.read2samples(bufferIndex - perData[i], prior, in);

            int16_t prior = theBuffer[bufferIndex - increment];
            int16_t in = theBuffer[bufferIndex - perData[i]]; // frequency modulated by input

            // using DSP - slight loss of precision but faster execution
            int32_t out = signed_multiply_32x16b(fbkIn,in);
            out = signed_multiply_accumulate_32x16b(out,fbkPrior,prior);

            if (nullptr != drive)
                out = signed_multiply_accumulate_32x16b(out, _driveLevel, *drive++);
            *data++ = out/lvMul;
            theBuffer[bufferIndex] = out/lvMul; // store feedback data for next cycle
            bufferIndex += increment;
        }

I don't know why one method of reading those values seems to result in filtering and the other one doesn't.

I've got a version that basically works the way I like in the Mosaichord firmware, based on your old branch. (Not sure if it sounds any different than the version based on your latest branch.)

Code is here, in case anyone wants to compare: https://github.com/jimsnow/microtonal-controller/tree/main/src/microtonal-controller

In a way I think it's fortunate that you sent me to the old branch, because it was relatively straightforward to add a control to make the filter less aggressive. If I had started from your latest branch, it wouldn't have been as obvious that there's a primitive filter hiding in there somewhere that could be re-enabled.
 
Back
Top