Best approach for writing plugins

I’ve been making a few hardware audio effects using the Teensy dev board, and have shared some of the results here. Due to lack of debugging tools on Teensy I found I spent ages trying to track down issues that would have been trivial with proper dev tools, so I’m considering doing the prototyping by writing a plugin using real dev tools, and then move to the Teensy when I know my DSP code is sound.

Can anyone recommend a good approach? I’ve been considering using either JUCE or writing a native Audio Unit. Ideally I want something as low-level, and close to the Teensy audio library as possible.

3 Likes

i think JUCE is a fine choice. does kinda the same job as arduino + teensy audio lib in setting up everything for you except the process loop.

one decision you have to make is whether your audio process will use fixed-point or float math. i’d assume the latter; new teensys have hardware FPU…

so, you make your class implement a generic processing loop and param setters, and then wrap it in a teensy AudioStream:

#include <stdint.h>

static inline float from_short(int16_t x) { return (float)x / (float)0x8000; }
static inline int16_t to_short(float x) { return (int16_t) (x * (float)0x8000); }

class MyEffect {
  float process(float x) {
	float y = /* awesome stuff here */ x * param_; 
	return y;
  }

  float param_;
  set_param(float val) { param_ = val; }
  
}
  
class MyAudioStream: public AudioStream {
  // PJRC override
  virtual void update(void);
  MyEffect effect;

};

void MyAudioStream::update(void) {
  
  audio_block_t* audio;
  audio_block_t* control;
  
  audio = receiveWritable(0);
  control = receiveReadOnly(0);

  if(!audio) { return; }
  if(!control) { return; }

  int16_t* samp = audio->data;
  // say you just want one ctl value per block
  int16_t ctl = control->data[0];
  effect.set_param(from_short(ctl));	   
  
  for(int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
	float fsamp = from_short(*samp);
	fsamp = effect.process(fsamp);
	*samp = to_short(fsamp);
	samp++;  
  }
  
  transmit(audio);  
  return;
}

similarly in JUCE, use the exact same MyEffect and wrap in in an AudioProcessor instead of AudioStream.

the override of
AudioStream::update()

becomes
AudioProcessor::processBlock(AudioBuffer<float> &buffer, MidiBuffer &midiMessages)

you use
const float* AudioBuffer::getReadPointer()
and
float* AudioBuffer::getWritePointer()
to manipulate the sample data

an hm, i guess the new recommenation for parameters is to use a AudioProcessorParameter member in your AudioProcessor.

HTH!

2 Likes

oh also, the new teensys have SWD/JTAG debug pins i think. pretty pumped about that (and the 3.6 in general.)

1 Like

Thank so much for this @zebra! JUCE it is! I’ve never done any fixed point maths in Teensy, only standard floats, so unless the libs hide this from you (which seems unlikely) I think both the 3.2 and the 3.6 have floating point units?

I’m quite excited about it, as now I’ll get a software version and a hardware version…

I think both the 3.2 and the 3.6 have floating point units?

negatory. had to look it up to confirm my memory, but indeed: teensy 3.2 uses freescale MK20DX256VLH7. the ‘D’ means that it lacks a hardware FPU. the float version is MK20F… something. this is not at all easy to discover, its buried in section 2.3 of the datasheet.

teensy 3.6 has got an upgrade to K66FX1M… something, which does have a hardware FPU (datasheet). hooray!

yes indeed, you can still perform floating point computations without dedicated hardware, it is just much slower. maybe by a factor of 10x or 20x. i found it is better to stick to fixed point on teensy 3.0-3.2, otherwise your power is a bit limited. if you do want to go with (soft) float math you might want to play with the related arm-gcc options to speed things up.

Ha - that is interesting! Fixed point math sure is a lot of hard work.

Another thing I’d mention (again) is that faust is an incredibly fast language to work in for prototyping DSP algorithms, once you get your head round ‘functional’ DSP. So that might be worth looking at. You can write all faust code under linux/jack - greatly speeds algorithm-level debugging. Then once you’ve got the faust code for you algorithm cooking, you can link the faust objects into the rest of your C/C++ program…

Well, this approach has been highly productive for me anyway!

1 Like

speaking of optimization, i should say that my example is not so good, in that you probably want to define your process function to operate on a whole block of samples, with frame count as argument. i omitted this for space/clarity with the fixed/float casting.

Yeah, that makes sense. Mainly I wanted to know I was setting off on the right path, which it seems I am. Really appreciate your help.

this is kind of exciting; FFT float benchmark is like 100x faster on K66…

1 Like

another advantage of the faust approach - you can ‘think’ in sample-by-sample like the way aleph DSP code reads, whereas the code compiles down to highly efficient, (completely unreadable) block-processing…

1 Like

Ah good to know, thanks. It’s hard to go back to the 3.2 now I’ve tried the 3.6 anyway! So much more memory.

It’s really convenient how similar the interfaces seem to be between JUCE and Teensy Audio. Should make this project fairly straight-forward.

I finally got around to doing this, and it works a treat. I now have my Teensy effect running as a VST/Audio Unit using the Juce API. I can highly recommend Juce, very easy to get started with, and the forums were very helpful. https://github.com/cutlasses/TeensyJuce/

2 Likes