Sending MIDI notes to external MIDI device and channel

Good morning,

I would like to send MIDI notes from the track to an external device but in the same time not blocking the audio workflow.

With this Track sending MIDI events to external device it seams that I send the notes but not earing anymore sound.

I tried to do it inside SamplerPlugin but not sure is the best idea. Should I create I plugin only for this scope?

I tried this in applyToBuffer of SamplerPlugin

 bool isRecording = false;
 if (auto* app = App::getInstance())
 {
     if (auto* edit = app->getEdit())
         isRecording = edit->getTransport().isRecording();
 }

 if (fc.bufferForMidiMessages != nullptr && isOnActiveTrack() && !isRecording)
 {
     auto& midiManager = MidiManager::getInstance();

     for (const auto& msg : *fc.bufferForMidiMessages)
     {
         if (msg.isNoteOnOrOff() || msg.isAllNotesOff() || msg.isAllSoundOff())
         {
             auto routed = msg;
             routed.setChannel(3);
             midiManager.sendToOutputs(routed);
         }
     }
 }

But seams not really accurate.

Thanks!

A track can’t have multiple outputs so the easiest thing to do is probably send the MIDI to another track which outputs to a MIDI device. You could do that with a Rack or an AuxSend/Return pair (I think they work with MIDI off the top of my head).

1 Like

Thanks @dave96 ,

I achieve this in Waveform 13 creating a AUX SEND to another track and in this track I need to set the correct midi output and put a MIDI Patchbay that convert channel 1 to 3.

Is this correct or there is a best way?

That’s probably the best way. MIDI output devices only pass through the MIDI so as you figured out, you need a patch bay if you want to change the channel.

1 Like

I’m trying to reproduce this inside TE:

I add a AuxSendPlugin in my first 8 tracks

       auto auxSendPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::AuxSendPlugin::xmlTypeName, {});
       if (auxSendPlugin != nullptr)
       {
           auxSendPlugin->state.setProperty(tracktion_engine::IDs::busNum, 0, nullptr);
           auxSendPlugin->setEnabled(false);
           track->pluginList.insertPlugin(std::move(auxSendPlugin), track->pluginList.size(), nullptr);
       }

In my 9th track I create this:

Channel 1 should go to channel 3

 auto auxReturnPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::AuxReturnPlugin::xmlTypeName, {});
 if (auxReturnPlugin != nullptr)
 {
     auxReturnPlugin->state.setProperty(tracktion_engine::IDs::busNum, 0, nullptr);
     midiReturnTrack->pluginList.insertPlugin(std::move(auxReturnPlugin), 0, nullptr);
 }

                        auto midiPatchbayPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::MidiPatchBayPlugin::xmlTypeName, {});
            if (midiPatchbayPlugin != nullptr)
            {
                if (auto* patchbay = dynamic_cast<tracktion::engine::MidiPatchBayPlugin*>(midiPatchbayPlugin.get()))
                    patchbay->makeConnection(1, 3);

                midiReturnTrack->pluginList.insertPlugin(std::move(midiPatchbayPlugin), midiReturnTrack->pluginList.size(), nullptr);
            }

With this if I open the edit with Waveform I see all correct and If I set output of track 8 to my midi controller i can see the note lighting up the buttons.

The problem appear when I’m trying to set the output to the track:

if (auto* midiReturnTrack = tracks[8])
{
    midiReturnTrack->setName("Midi Controller");

    auto& trackOutput = midiReturnTrack->getOutput();
    auto devices = juce::MidiOutput::getAvailableDevices();
    for (const auto& device : devices)
    {
        if (device.name.containsIgnoreCase("MyController"))
        {
            trackOutput.setOutputToDeviceID(device.identifier);
            trackOutput.updateOutput();
            break;
        }
    }
...

Doing this at the end:

for (int i = 0; i < tracks.size(); ++i)
{
    auto* track = tracks[i];
    auto& output = track->getOutput();

    DBG("[Track " + juce::String(i) + "] name=" + track->getName());
    DBG("  output device ID = " + output.getOutputDeviceID());
}

Show:

[Track 8] name=Midi Controller
  output device ID = \\?\usb#vid_239a&pid_cafe&mi_00#6&e3c153f&0&0000#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\global

So seams ok but no lights :smiley:

For sure when I changed track on my sequencer I do this I enable the auxSendPlugin on the track!

You have any ideas?

Thanks

I think you want to use te::DeviceManager::getNumMidiOutDevices()/getMidiOutDevice (int index) and get call getDeviceID() to get the ID to pass to the TrackOutput.

It works but I have a problem: initially I have this function:

std::unique_ptr<tracktion::engine::Edit> ProjectManager::createTempProject(tracktion::engine::Engine& engine)
{
resetTempProjectFolder();
auto folder = getTempProjectFolder();
getProjectSoundsFolder(“temp”).createDirectory();
getProjectExportsFolder(“temp”).createDirectory();

auto srcSounds = getProjectSoundsFolder("temp_backup"); // .../temp_backup/Sounds
auto dstSounds = getProjectSoundsFolder("temp");        // .../temp/Sounds

DBG("SRC: " + srcSounds.getFullPathName());
DBG("DST: " + dstSounds.getFullPathName());

dstSounds.createDirectory();

if (srcSounds.isDirectory())
{
    for (juce::DirectoryIterator it(srcSounds, true, "*", juce::File::findFilesAndDirectories);
        it.next();)
    {
        DBG("FILE: " + it.getFile().getFullPathName());
        auto file = it.getFile();
        auto relPath = file.getRelativePathFrom(srcSounds);
        auto destFile = dstSounds.getChildFile(relPath);

        if (file.isDirectory())
        {
            if (!destFile.createDirectory())
                DBG("Impossibile creare dir: " + destFile.getFullPathName());
        }
        else
        {
            // fix: assicurati che esista la cartella padre
            destFile.getParentDirectory().createDirectory();

            if (!file.copyFileTo(destFile))
                DBG("Copia fallita: " + file.getFullPathName() + " -> " + destFile.getFullPathName());
            else 
                DBG("File copiato: " + file.getFullPathName() + " -> " + destFile.getFullPathName());
        }
    }

    DBG("Cartella Sounds copiata da temp_backup a temp");
}
else
{
    DBG("Sorgente non trovata : " + srcSounds.getFullPathName());
}

DBG("Temp edit: " + getTempEditFile().getFullPathName());
auto edit = tracktion::engine::createEmptyEdit(engine, getTempEditFile());

if (edit == nullptr)
{
    DBG("Errore: impossibile caricare temp.tracktionedit");
    jassertfalse;
    return nullptr;
}
else {
    DBG("Edit caricato con successo!");
}
edit->tempoSequence.getTempos()[0]->setBpm(settingsManager.getValue("DefaultProjectBPM", 90.0));

int countInIndex = settingsManager.getValue<int>("CountInModeIndex", 0);
countInIndex = juce::jlimit(0, 4, countInIndex);

using CountIn = tracktion::engine::Edit::CountIn;
CountIn countInMode = CountIn::none;

switch (countInIndex)
{
case 0: countInMode = CountIn::none;    break;
case 1: countInMode = CountIn::oneBar;  break;
case 2: countInMode = CountIn::twoBar;  break;
case 3: countInMode = CountIn::twoBeat; break;
case 4: countInMode = CountIn::oneBeat; break;
default: countInMode = CountIn::none;   break;
}

edit->setCountInMode(countInMode);

edit->setClickTrackVolume(1.0f);
edit->clickTrackEnabled = false;
edit->clickTrackEmphasiseBars = false;
edit->ensureNumberOfAudioTracks(9);
auto& pluginManager = engine.getPluginManager();

registerPluginType<tracktion_engine::JPSamplerPlugin>(engine);
registerPluginType<tracktion_engine::JP12BitPlugin>(engine);
registerPluginType<tracktion_engine::JPAirwinWrapperPlugin>(engine);


auto tracks = tracktion::getAudioTracks(*edit);

if (tracks.isEmpty())
{
    DBG("Errore: nessuna traccia audio disponibile");
    jassertfalse;
    return nullptr;
}

for (int i = 0; i < 8; ++i)
{
    auto* track = tracks[i];

    auto jPSamplerPlugin = edit->getPluginCache().createNewPlugin(tracktion::JPSamplerPlugin::xmlTypeName, {});
    if (jPSamplerPlugin != nullptr)
        track->pluginList.insertPlugin(std::move(jPSamplerPlugin), 0, nullptr);
       
    auto pitchShiftPlugin = edit->getPluginCache().createNewPlugin(tracktion::PitchShiftPlugin::xmlTypeName, {});
    if (pitchShiftPlugin != nullptr)
    {
        /*track->pluginList.insertPlugin(std::move(pitchShiftPlugin), 1, nullptr);
        pitchShiftPlugin->setEnabled(false);*/
    }

    auto jp12BitPlugin = edit->getPluginCache().createNewPlugin(tracktion::JP12BitPlugin::xmlTypeName, {});
    if (jp12BitPlugin != nullptr)
    {
        jp12BitPlugin->setEnabled(false);
        track->pluginList.insertPlugin(std::move(jp12BitPlugin), 2, nullptr);
    }

    auto auxSendPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::AuxSendPlugin::xmlTypeName, {});
    if (auxSendPlugin != nullptr)
    {
        auxSendPlugin->state.setProperty(tracktion_engine::IDs::busNum, 0, nullptr);
        auxSendPlugin->setEnabled(false);
        track->pluginList.insertPlugin(std::move(auxSendPlugin), track->pluginList.size(), nullptr);
    }
   
    // Print plugin list for debug
    DBG("-- PLUGINS LIST FOR TRACK " + juce::String(i));
    for (auto* p : track->pluginList)
        DBG("   - " + p->getName() + " " + (p->isEnabled() ? "Enabled" : "Disabled"));
}

if (tracks.size() >= 9)
{
    if (auto* midiReturnTrack = tracks[8])
    {
        midiReturnTrack->setName("JP Midi Controller");

        auto auxReturnPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::AuxReturnPlugin::xmlTypeName, {});
        if (auxReturnPlugin != nullptr)
        {
            auxReturnPlugin->state.setProperty(tracktion_engine::IDs::busNum, 0, nullptr);
            midiReturnTrack->pluginList.insertPlugin(std::move(auxReturnPlugin), 0, nullptr);
        }

        auto midiPatchbayPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::MidiPatchBayPlugin::xmlTypeName, {});
        if (midiPatchbayPlugin != nullptr)
        {
            if (auto* patchbay = dynamic_cast<tracktion::engine::MidiPatchBayPlugin*>(midiPatchbayPlugin.get()))
                patchbay->makeConnection(1, 3);

            midiReturnTrack->pluginList.insertPlugin(std::move(midiPatchbayPlugin), midiReturnTrack->pluginList.size(), nullptr);
        }
    }
}

/*for (int i = 0; i < tracks.size(); ++i)
{
    auto* track = tracks[i];
    auto& output = track->getOutput();

    DBG("[Track " + juce::String(i) + "] name=" + track->getName());
    DBG("  output device ID = " + output.getOutputDeviceID());
}*/

auto& transport = edit->getTransport();
transport.play(false);
transport.stop(false, false);

tracktion::engine::EditFileOperations editOps(*edit);
if (editOps.writeToFile(getTempEditFile(), false))
{
    setCurrentProjectFolder(folder);
    DBG("Salvataggio completato");
}
else
{
    DBG("Errore nel salvataggio");
}

return edit;


}

i try to put the association to the output here: midiReturnTrack->setName(“JP Midi Controller”);

but seams that in this moment the midi output is not available.

If I call after some time it works. Which is the correct approach?

Thanks