Table of contents
- Description
- Plugin Name and Description
- Modifying the GUI
- Passing
HostEvent
andMidiFileEvent
toPPP
thread - Modifying
ITP
Thread - Modifying
MDL
Thread - Loading the Model in
PPP
Thread
Tutorial 5 - Single Thread Implementation
In this tutorial, we will be using the same model as the previous tutorials, so make sure you have completed the previous tutorials.
the source code for this tutorial is available in the tutorials branch of the repository.
Description
If you find the three thread implementation too complicated for a given model you’re using, you can modify the wrapper to use a single thread implementation instead
In this tutorial, we will show you how to do this by modifying the wrapper as follows:
- Modifying
ITP
: Send incomingMidiFileEvent
andEventFromHost
to the model using theModelInput
struct - Modifying
MDL
: Get theModelInput
struct from theITP
and send it to the model as is - Modifying
PPP
: Get theModelOutput
containing all necessary events and Implement the processing here
Once finished with this tutorial, you can always use this as a template for your own models.
We’ll use this single thread implementation method for the next tutorial
Plugin Name and Description
As mentioned here, we need to specify the name of the plugin as well as some descriptions for it.
To do this, we will modify the NeuralMidiFXPlugin/NeuralMidiFXPlugin/CMakeLists.txt file as follows:
project(Tutorial5NMFx VERSION 0.0.1)
set (BaseTargetName Tutorial5NMFx)
....
juce_add_plugin("${BaseTargetName}"
COMPANY_NAME "AIMCTutorials"
...
PLUGIN_CODE AZCO # a unique 4 character code for your plugin
...
PRODUCT_NAME "Tutorial5NMFx") # Replace with your plugin title
Once you re-build the cmake project, and re-build the plugin, you should see the name of the plugin change in the DAW:
Now we are ready to move on to the next step.
Modifying the GUI
For this tutorial, we don’t need the GUI we will make sure there are no components in Configs_GUI.h
file.
// Configs_GUI.h
// rest of the code ...
namespace Tabs {
const bool show_grid = false;
const bool draw_borders_for_components = false;
const std::vector<tab_tuple> tabList{
tab_tuple
{
"Tab",
slider_list
{
},
rotary_list
{
},
button_list
{
}
},
};
}
namespace MidiInVisualizer {
const bool enable = true;
const bool allowToDragInMidi = true;
const bool visualizeIncomingMidiFromHost = true;
const bool deletePreviousIncomingMidiMessagesOnBackwardPlayhead = false;
const bool deletePreviousIncomingMidiMessagesOnRestart = false;
}
namespace GeneratedContentVisualizer
{
const bool enable = true;
const bool allowToDragOutAsMidi = true;
}
Passing HostEvent
and MidiFileEvent
to PPP
thread
We will start with modifying the CustomStructs.h
file as follows:
First, make sure ITPData
and MDLData
are empty structs. If not, this won’t cause any issues, but we’ll keep them empty as in this case, we will not be doing any processing in these threads. Rather, we will just be using these threads to pass the data directly from ITP
to PPP
via MDL
thread.
// CustomStructs.h
// Any Extra Variables You need in ITP can be defined here
// An instance called 'ITPdata' will be provided to you in Deploy() method
struct ITPData {
};
// Any Extra Variables You need in MDL can be defined here
// An instance called 'MDLdata' will be provided to you in Deploy() method
struct MDLData {
};
Then, we will be modifying the ModelInput
and ModelOutput
structs as follows:
// CustomStructs.h
struct ModelInput {
std::optional<MidiFileEvent> new_midi_event_dragdrop;
std::optional<EventFromHost> new_event_from_host;
// ==============================================
// Don't Change Anything in the following section
// ==============================================
chrono_timer timer{};
};
struct ModelOutput {
std::optional<MidiFileEvent> new_midi_event_dragdrop;
std::optional<EventFromHost> new_event_from_host;
// ==============================================
// Don't Change Anything in the following section
// ==============================================
chrono_timer timer{};
};
Note that we have added two optional variables to the ModelInput
and ModelOutput
structs. These are exactly the same as the inputs passed on to the deploy()
method of ITP
(see ITP_Deploy.cpp
).
Now, we are ready to move on to the next step.
Modifying ITP
Thread
In here, we will modify the ITP_Deploy.cpp
file.
The objective here is to first make sure if the deploy()
method was called because of an Event (rather than a GUI change). If so, then we will place the received events in the ModelInput
struct and send it to the MDL
thread.
// ITP_Deploy.cpp
bool InputTensorPreparatorThread::deploy(
std::optional<MidiFileEvent> & new_midi_event_dragdrop,
std::optional<EventFromHost> & new_event_from_host,
bool gui_params_changed_since_last_call) {
// we only pass the data if the method was called because of a new event
// and not because of a gui parameter change
if (new_midi_event_dragdrop.has_value() || new_event_from_host.has_value()) {
model_input.new_event_from_host = new_event_from_host;
model_input.new_midi_event_dragdrop = new_midi_event_dragdrop;
return true;
}
return false;
}
Modifying MDL
Thread
In here, we will modify the MDL_Deploy.cpp
file.
Similar to the ITP
thread, we will first make sure if the deploy()
method was called because of a new received ModelInput
and not because of a GUI change. If so, we will pass the received ModelInput
to the PPP
thread.
// MDL_Deploy.cpp
bool ModelThread::deploy(bool new_model_input_received,
bool did_any_gui_params_change) {
// we only pass the data if the method was called because of a new model_input
if (new_model_input_received) {
model_output.new_event_from_host = model_input.new_event_from_host;
model_output.new_midi_event_dragdrop = model_input.new_midi_event_dragdrop;
return true;
}
return false;
}
After these modifications are done, we can test whether the required data is correctly passed from ITP
to PPP
thread. To do this, we’ll modify PPP_Deploy.cpp
file as follows:
// PPP_Deploy.cpp
std::pair<bool, bool> PlaybackPreparatorThread::deploy(bool new_model_output_received, bool did_any_gui_params_change) {
bool newPlaybackPolicyShouldBeSent = false;
bool newPlaybackSequenceGeneratedAndShouldBeSent = false;
if (new_model_output_received)
{
if (model_output.new_event_from_host) {
PrintMessage("Received new event from host");
}
if (model_output.new_midi_event_dragdrop) {
PrintMessage("Received new midi event from dragdrop");
}
}
return {newPlaybackPolicyShouldBeSent, newPlaybackSequenceGeneratedAndShouldBeSent};
}
Loading the Model in PPP
Thread
Loading the model in PPP
thread is slightly different from the previous tutorials. The reason is that, there is no dedicated loader nor a dedicated place holder for the model in PPP
thread. That said, we have the ability to load the model using the PPPData
struct in CustomStructs.h
. as follows:
// CustomStructs.h
// assuming that the model is called 'drumLoopVAE.pt' and is available somewhere int the TorchScript folder
struct PPPData {
torch::jit::script::Module model = load_processing_script("drumLoopVAE.pt");
};
To use this model in the PPP_Deploy.cpp
file, we can simply use the local instance of PPPData
(called PPPdata
) as follows:
// PPP_Deploy.cpp
// some code ...
PPPdata.model.forward(....);