How to load a custom .wav to a wavetable buffer and play it back

Hello there,

I’m a huge fan of the iPlug2 framework and created a few basic plugins, testing out what I can create :slight_smile:

On my latest project, I got stuck trying to load a sample that I can then play back with an oscillator.

What I managed to do:

  • get the filepath from a sample that’s dragged onto an IControl
  • parsing the header and loading the actual sample data into a buffer
  • link a parameter to the IControl, so that SetParam() is executed in the IPlugInstrument_DSP.h file

I wanted to pass the buffer itself, but the params only include ints, doubles, percentage, etc.
So - I thought I could just use the pointer to the buffer data instead, and use that within the SetParam() function to ultimately pass the sample data to an oscillator.

This doesn’t work, however, because SetParam normalizes the value to [0.0,1.0], so even if I would convert the pointer to a “double” (shudders), pass that to the SetParam, and then convert it back to an int, it would never represent the original value.

Another option I thought of would be a global variable, that I could just access from within an oscillator… but that also feels terribly wrong, so I didn’t even attempt that so far.

I’d really like to do it in a proper, iPlug2-style way (admittedly, I’d even be happy if it’s just working at all)…

My question is - how can I pass the sample data that I loaded from a file via an IControl into an IOscillator? I’m confident that I can play back the samples from a buffer, once they are inside the oscillator, but I have no clue how to get them there.

I do not want to use a fixed resource folder, nor hard-code the sample buffer into the oscillator, since I really want to just drag/drop any sample from my disk into the plugin.

Any help would be appreciated :slight_smile:

Cheers,
Vaia

1 Like

My current workaround seems very wrong… but it gets the job done for now.
For reference, I’ve started from the IPlugInstrument example.

The control that overrides OnDrop() also receives a pointer to the “parent”, the plugin itself. When it is triggered, I can use the filepath from whatever was dropped to load/parse/handle the file at that path.

DragDropPanel::OnDrop(const char* str) {
  mParent->mDSP.setSampleFilePath(str);
}

the DSP itself again calls the ForEachVoice lambda

  void setSampleFilePath(const std::string& filePath) {
    mSynth.ForEachVoice([&filePath](SynthVoice& voice) {
      dynamic_cast<VaiaSamplerDSP::Voice&>(voice).mOSC.LoadSampleFromPath(filePath);
      });
  }

However, this seems incredibly clunky. It feels as if I’m totally bypassing the entirety of the IPlug architecture.
That, and, the sample buffer would be duplicated for each mOSC. The OSC doesn’t know the parent, so it can’t fetch the buffer from there. Maybe I need to pass a buffer pointer when creating a new OSC?

I’m leaving my “solution” here, in case anyone else encountered a similar challenge, but I can’t recommend it, even if it “works” =/

I’d appreciate any input on how to properly load a parsed file starting from the GUI’s OnDrop into a buffer that all voices can access. :musical_note:

Cheers.
Vaia

I don’t understand why do you want to play the wave file on an oscillator? if your goal if to play the wave you can just copy all the samples one by one to the channel data arrays in the ProcessBlock.

I don’t know how IOscillator works, but if you want to set its wave to your wavetable then maybe you have to do that in the oscillator’s initialisation, but not with the parametres which, as its name suggests, can only control single values for effects and that.

Hi Ric, thanks for your response!

Without knowing the implementation details, I’ve spent a lot of time making sounds in Serum.
When you import a file to Serum, you drag it into an Oscillator, so the concept of playing back a sample in an oscillator resonated with me (no pun intended).

IPlug2 IOscillator

The IPlug2 IOscillator looks up the sample value on a mathematical function based on its phase; and you get the sample by calling e.g. sin(phase).

And I felt like I could reuse that behaviour for samples, too - just that the lookup doesn’t happen on a mathematical function, but instead on a value inside an array, tracking the sample position, loading sampleBuffer[pos] for the result.

I was already able to pass the wave, and it works as intended - I press a note, and sound comes out - but the way from loading the file to playing it back seems like a very weird path in my solution.

In the example instrument, IPlugInstrument::ProcessBlock called mDSP.ProcessBlock internally, which itself called mSynth.ProcessBlock (which holds a number of Voices that are fed by an oscillator each).

I assumed that the example was the way to go, which is why I tried to use the same architecture.

Do I understand correctly that I should instead save the sample directly “into” the plugin class itself, and doing all the processing in there, omitting all additional classes, like MidiSynth or IOscillator? Would the plugin then still be MIDI-sensitive?

This is a bit of a complicated question to answer

You seem to be at the stage where you can read the sample into memory. You only need to load your sample into memory once. Ideally you should read that audio file on a separate thread so as not to block the audio thread (really bad) or the UI/main thread (pretty bad). When that is done you can then update all the voices with a pointer to the new waveform in memory.

You can communicate between the UI and DSP with messages, if you need to send a filePath or a small block of memory for a wavetable from one place to the other. This is only really a concern with WebAudioModules though. You can also “get a pointer” to your plug-in class from a UI Control, by casting the returned value of IControl::GetDelegate()

You should also remember that you need to serialize the waveform, or the path to the wav file in your plug-ins state, otherwise on preset recall etc, the waveform will be lost.

The IOscillator interface is just a base for an oscillator, so it would be appropriate to use in a wavetable synth (with wavetable reading added), although you might be better off writing something separate.

hope that is a bit helpful

Oh I get it now! My bad, I thought you were importing actual sounds, but apparently you just want to store one cycle on the oscillator and let it take care of frequencies, filters, &c. (I’ve done that myself in Alchemy now that I remember). Unfortunately I always code everything from scratch so I rarely dig too deep into IPlug2’s utilities so I can’t help you with IOscillator, but I suppose Oli’s reply should be what you need.

Thank you both for your insights!

IControl::GetDelegate() works like a charm and does exactly what I needed to do - giving me access to the plugin’s public data without any weird workarounds :slight_smile:

I’ve derived a WavetableOsc class from IOscillator which does its job of remembering the current play position, playback speed, etc.

I got far enough, thanks to you, to consider this topic resolved!

P.S.:
I did indeed run into a problem with losing the waveform information upon restart earlier, and I’ve opted to serialize the path in my previous version back then - but serializing the waveform would make it all more portable, so I’ll consider that, too.

Cheers!