Hmm, it’s probably because you haven’t implemented UIBehaviour::getLastFocusedEdit()
juce::Array<AudioTrack*> MidiInputDevice::getDestinationTracks()
{
if (auto edit = engine.getUIBehaviour().getLastFocusedEdit())
if (auto in = edit->getCurrentInstanceForInputDevice (this))
return in->getTargetTracks();
return {};
}
You could do that but it highlights perhaps why this isn’t the most appropriate method for drawing thumbnails.
The main use for this method in Waveform is step-entry, when a keyboard key is pressed, we insert that note in to the piano roll and move the insert point forwards. Obviously you only want that to happen in the currently focused Edit hence the above behaviour.
It doesn’t lend itself to MIDI thumbnailing very well though.
Just to let you know, this is working as expected! In fact, it is working well enough that you may want to consider this technique for adding the feature yourself.
First you implement UIBehaviour::getLastFocusedEdit() to return the current edit.
Then, below is pseudo code that gives the general idea of what I am using;
class MidiRecordingClip : public Component, public te::MidiInputDevice::MidiKeyChangeDispatcher::Listener
{
public:
MidiRecordingClip(te::Track::Ptr t, EditViewState& evs) :
audioTrack(dynamic_cast<te::AudioTrack*> (t.get())),
editViewState(evs)
{
setAlwaysOnTop(true);
midiKeyChangeDispatcher->listeners.add(this);
}
~MidiRecordingClip()
{
midiKeyChangeDispatcher->listeners.remove(this);
}
void resized() override
{
width = getWidth();
height = getHeight();
// place here any needed calculations here
// ...
}
void paint(Graphics& g) override
{
// draw the notes
for (auto note : midiList.getNotes())
{
g.setColour(Colours::white.withAlpha(note->getVelocity() / 127.0f));
g.fillRect(getNoteBounds(note));
}
}
private:
void midiKeyStateChanged(te::AudioTrack* audTrk, const juce::Array<int>& notes, const juce::Array<int>& vels) override
{
if (audTrk == audioTrack)
{
midiList.clear(nullptr);
const auto transport{ &audioTrack->edit.getTransport() };
const auto leftEdge{ editViewState.timeToX(transport->getCurrentPosition(), width) };
for (auto i = 0; i < notes.size(); i++)
midiList.addNote(notes[i], editViewState.timeToBeats(transport->getCurrentPosition()), 1.0, vels[i], 0, nullptr);
repaint( leftEdge, 0, (width - leftEdge), height);
}
}
inline Rectangle<int> getNoteBounds(const te::MidiNote* note)
{
// calculate note bounds here
// ...
return { noteBounds };
}
te::AudioTrack* audioTrack;
EditViewState& editViewState;
SharedResourcePointer<te::MidiInputDevice::MidiKeyChangeDispatcher> midiKeyChangeDispatcher;
te::MidiList midiList;
int width{ getWidth() };
int height{ getHeight() };
};
The notes appear on-screen with a slight visual delay (as expected) but in surprisingly good agreement with what is being played. And time-stamping the notes with the transport position works quite well, with no need to compensate for any internal latency. The only thing missing is having note length.
My use case is to have a visual guide that midi is being recorded properly, so as to reassure the performer. This is more than good enough!
@bwall can you explain how to go about implementing the getLastFocusedEdit method? I have a subclass of UIBehavior, do I need to create a member variable for the edit inside this subclass and set this somehow? Or is there some other way to access the edit from within UIBehavior?
UIBehavior has no built-in way to get the edit pointer, so you must provide it yourself by whatever mechanism makes sense in your code. So, you create the pointer to your edit and return it from getLastFocusedEdit.