I’m working on a VST3 midi plugin that generates midi data on the fly.
The plugin is able to successfully generate midi in Ableton Live and FLStudio, but in Waveform, I’m having a weird issue. Waveform seems to catch the first midi output at time zero, but nothing after that, so the initial midi on message just continues indefinitely.
I tried to create the simplest example, in hopes that someone can see what I might be doing wrong here.
The code below should create a note on message once per second, each followed by a note off message 1/2 second later.
In Live and FLStudio, I can see/hear the midi and I can see the note messages being output to the log.
In Waveform, only the note on message at “position” 0 is ever realized in the DAW. If hit play from any other transport position, I see/hear no messages at all.
I’m certain I’m missing something simple here, but I’ve been trying for days to figure out what it might be. Any insight is appreciated.
PluginProcessor.h
#include <JuceHeader.h>
class MyVSTAudioProcessor : public juce::AudioProcessor {
public:
//==============================================================================
//BOILER PLATE CODE OMITTED
//==============================================================================
private:
int bufferSize;
double sampleRate;
double halfSampleRate;
juce::MidiMessage onMsg{ juce::MidiMessage::noteOn(1, 60, (juce::uint8)90) };
juce::MidiMessage offMsg{ juce::MidiMessage::noteOff(1, 60, (juce::uint8)90) };
juce::AudioPlayHead* transportHead;
juce::Optional<juce::AudioPlayHead::PositionInfo> transportInfo{ juce::AudioPlayHead::PositionInfo() };
bool isPlaying();
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MyVSTAudioProcessor);
};
PluginProcessor.cpp
#include "PluginProcessor.h"
#include "PluginEditor.h"
//==============================================================================
//BOILER PLATE CODE OMITTED
//==============================================================================
bool MyVSTAudioProcessor::isPlaying() {
return transportInfo->getIsPlaying();
}
void MyVSTAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) {
this->bufferSize = samplesPerBlock;
this->sampleRate = sampleRate;
this->halfSampleRate = sampleRate / 2;
this->transportHead = getPlayHead();
}
void MyVSTAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) {
// update transport
transportInfo = transportHead->getPosition();
// prepare to write to the midi buffer
midiMessages.clear();
if (isPlaying()) {
// set the sample position to the current transport position
int samplePosition = (int)transportInfo->getTimeInSamples().orFallback(0);
int start = samplePosition; // start of the buffer
int end = samplePosition + bufferSize - 1; // end of the buffer
if (samplePosition % (int)sampleRate < bufferSize) {
// once per second, send a note on message
midiMessages.addEvent(onMsg, sampleRate * (int)(samplePosition / sampleRate));
} else if (samplePosition % (int)(halfSampleRate) < bufferSize) {
// every 1/2 second after a note on message, send a note off message
midiMessages.addEvent(offMsg, halfSampleRate * (int)(samplePosition / halfSampleRate));
}
// log the midi messages
for (const juce::MidiMessageMetadata metadata : midiMessages) {
if (metadata.numBytes == 3) {
juce::Logger::writeToLog(juce::String(metadata.samplePosition) + ": " + metadata.getMessage().getDescription());
}
}
}
}
For reference, I created a VST3 project with the following relevant settings:
Plugin Formats: VST3
Plugin Characteristics: Plugin MIDI Input, Plugin MIDI Output
Plugin VST3 Category: Fx, Instrument, Generator