Show Midi Data As It Is Recorded

In the Tracktion Engine demos you show how to display audio as it is recorded. This works nicely to show the audio as it comes in to the clip…

What would be the technique to do the same with incoming midi to have it show as it arrives in the clip?

It’s trickier in MIDI recordings but you can get notified of any recorded messages using AudioTrack::Listener::recordedMidiMessageSentToPlugins.

You should be able to show the MIDI from that.

I was thinking the midi events are collected somewhere for adding to the clip. And it might be possible to access this list for showing the notes prior to the clip appearing after recording is stopped. Is this not the the case?

They are, but they’re contained inside MidiInputDeviceInstanceBase::InputAudioNode and there’s no easy way to get at them in a thread-safe way. This class is also internal and not meant for public use (in fact it will be going away completely and replaced by MidiInputDeviceNode when we switch to using the tracktion_graph module for playback).

All these messages will get sent to the above listener on the message thread which handles that for you so it’s basically the same thing if you collect those yourself to display.

1 Like

Ah, OK, that makes sense. Thank you!

I finally got the opportunity to experiment with this, but I am having no luck with the callback. I am sure I am naively overlooking something.

I have stripped my code down to the bare essentials.

class MidiRecordingClip : public Component, public te::AudioTrack::Listener
{
public:
	MidiRecordingClip(te::Track::Ptr t)
	{
		audioTrack = dynamic_cast<te::AudioTrack*>(t.get());
		audioTrack->addListener(this);
	}

	~MidiRecordingClip()
	{
		audioTrack->removeListener(this);
	}

private:

	void te::AudioTrack::Listener::recordedMidiMessageSentToPlugins(te::AudioTrack&, const juce::MidiMessage&)
	{
			AlertWindow::showMessageBoxAsync(AlertWindow::InfoIcon,
				"MIDIRecordingClip",
				"We Reached the Callback!");
	}

	te::AudioTrack* audioTrack;
};

The callback never fires when midi is played, even though a normal midi clip is created when “Stop” is pressed.

What am I overlooking?

The problem could be the weird namespace prefix you’ve stuck on the front of the overridden method name. I’m not actually sure what the compiler would do with that syntax, whether it’ll ignore it or do something unexpected, but this is why you need the override keyword on there, so at least you’d know whether or not it’s overriding the method that you think it is.

The weird namespace was added as part of trying ti get this to work. The original version simply had the function name. However, removing the prefix does not allow the callback to fire. Nor does adding override.

	void recordedMidiMessageSentToPlugins(te::AudioTrack&, const juce::MidiMessage&) override
	{
			AlertWindow::showMessageBoxAsync(AlertWindow::InfoIcon,
				"MIDIRecordingClip",
				"We Reached the Callback!");
	}

The callback is pure virtual I believe.

Any ideas here will be appreciated! I am sure I am missing something obvious. One of the downsides to working alone is not having others to spot missing things in your code.

I am stumped right now as to why the callback does not fire?! Is there another step to prepare it?

Hmm. If you look at the AudioTrack:: Listener class, it has two pure virtual methods. You’re only implementing one of them. So if you try to actually create an instance of your class, you’d get an error. So if your program actually compiles, I’d say that means you’re never even creating an instance of that class.

Can I check what branch you’re using? I only added the second pure virtual method a few days ago.

We don’t actually have any code that uses this callback so I can’t step through it directly.
Can you check the following please?

  1. Line 991 of tracktion_AudioTrack.cpp check that the LiveMidiOutputAudioNode is actually getting created
  2. If it is, check that the LiveMidiOutputAudioNode::renderAdding method is adding the MIDI to the pendingMessages buffer
  3. Check that in LiveMidiOutputAudioNode::handleAsyncUpdate the recordedMidiMessageSentToPlugins listener callback is called

If step 1 isn’t happening, check that the audio graph is actually getting rebuilt between adding your listener and trying to display the MIDI. If it doesn’t, try calling TransportControl::editHasChanged() to trigger a rebuild.

The version I have only has the one virtual method. And the class is created and the constructor executes. If there is now another virtual method, perhaps the problem is that the version I am using is incomplete?

My version has only one virtual method, so I downloaded the most recent “Develop” version, and it also has only one pure virtual method.

What version should I be using?..the “tracktion_graph” version, perhaps?

You probably don’t want to use tracktion_graph just yet. It’s still in a lot of flux.
Develop will only have the single virtual method but the one I added is for another purpose, not displaying MIDI so shouldn’t matter here.

Did you try the other steps that I mentioned?

Yes, the “tracktion_graph” branch added several errors to my build, so I went back to “Develop”.

For the moment, I have only tried adding TransportControl::editHasChanged right after the listener is created. Unfortunately the callback still not fire.

Later today I will work on the other suggestions you made.

This has to be something subtle, because the midi is processed just fine as far as the recorded midi clip is concerned. It is only the listener that is not responding.

I will let you know what I find. Thank you!

LiveMidiOutputAudioNode is not being created because there are no registered listeners! listeners.isEmpty(). returns true.

So the audioTrack->addListener(this) is not happening.

Looking at my code above, could it be related to the dynamic cast?

audioTrack = dynamic_cast<te::AudioTrack*>(t.get());

I appreciate your help!
Thank you!

It shouldn’t be… Are you sure that it’s not empty for a different track?
I’d put a breakpoint in AudioTrack::addListener and see if it’s triggered, or step through your code and ensure removeListener isn’t called before the audio graph is rebuilt.

When debugging this kind of stuff, it’s much easier to simplify your tracks so you only have one. Otherwise you can end up debugging tracks you’re not really interested in.

I am trying to figure this out, so please bare with me.

I create an edit with one track.
I select a midi input and arm the track for recording.
I enable “Record” mode for the DAW.
I press “Play” to begin recording Midi.
The MidiRecordingClip is constructed right away.
I play some notes on the keyboard.
Then, press “Stop”.
listeners.isEmpty() returns true.
Then I see the MidiRecordingClip destructor run.

At no point is there any indication that the listener is added. And the constructor currently is as follows.

	MidiRecordingClip(te::Track::Ptr t) : audioTrack(dynamic_cast<te::AudioTrack*> (t.get()))
	{
		audioTrack->addListener(this);
		audioTrack->edit.getTransport().editHasChanged();
	}

I am trying to model everything after the way the audioThumnail is generated as audio is recorded. The thumbnail displays as the data comes in. But, for Midi, it is acting like nothing is triggered until “Stop” is pressed. In other words, there is no activity through AudioTrack::addListener until stop is pressed, which is, of course, too late.

I appreciate your patience on this. If you can think of any more things to check, it will be most appreciated!

Thank you!

I think because your MidiRecordingClip is created after recording starts, the playback graph can’t get rebuilt so the listener doesn’t get called from the playback graph.

If you create the MidiRecordingClip before starting to record, or better still, have your track register itself as as the listener and then pass the messages on to the clip?

I was thinking along the same lines.

I will work on this today and report back when I get something working.

Thank you!