Skip to content

Commit

Permalink
Merge pull request #20 from SLM-Audio/syl/tutorial-cleanup
Browse files Browse the repository at this point in the history
Updated FXPluginTutorial with reference to Contexts, added to docs page
  • Loading branch information
MeijisIrlnd authored Nov 21, 2024
2 parents 940a3ae + 117bc1f commit 9b0b97e
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 22 deletions.
9 changes: 5 additions & 4 deletions docs/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
find_package(Doxygen)
if(Doxygen_FOUND)
if (Doxygen_FOUND)
set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_STATIC YES)
set(DOXYGEN_EXTRACT_LOCAL_METHODS YES)
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ${CMAKE_SOURCE_DIR}/README.md)
if(NOT APPLE)
if (NOT APPLE)
# include the platform specific stuff anyway..
set(MOSTLYHARMLESS_EXTRA_DOC_HEADERS
${CMAKE_SOURCE_DIR}/include/mostly_harmless/gui/platform/mostlyharmless_GuiHelpersMacOS.h)
endif()
endif ()
message(STATUS ${CMAKE_CURRENT_SOURCE_DIR})

add_subdirectory(${CMAKE_SOURCE_DIR}/modules/marvin/include ${CMAKE_CURRENT_BINARY_DIR}/marvin)
doxygen_add_docs(
mostly_harmless_docs
${CMAKE_SOURCE_DIR}/README.md
${CMAKE_CURRENT_SOURCE_DIR}/FXPluginTutorial.md
${MARVIN_HEADERS}
${CMAKE_SOURCE_DIR}/modules/marvin/docs/marvin_NamespaceDocs.h
${MOSTLYHARMLESS_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/mostlyharmless_NamespaceDocs.h
${MOSTLYHARMLESS_EXTRA_DOC_HEADERS}
USE_STAMP_FILE
)
endif()
endif ()
49 changes: 31 additions & 18 deletions docs/FXPluginTutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ namespace myplugin {
class IEngine final : public mostly_harmless::core::IEngine { // [1]
public:
explicit Engine(SharedState* sharedState); // [2]
void initialise(double sampleRate, std::uint32_t minBlockSize, std::uint32_t maxBlockSize) override; // [3]
void process(marvin::containers::BufferView<float> buffer, std::optional<mostly_harmless::TransportState> transport) override; // [4]
void reset() override; // [5]
void initialise(mostly_harmless::core::InitContext context) noexcept override; // [3]
void process(mostly_harmless::core::ProcessContext context) noexcept override; // [4]
void reset() noexcept override; // [5]
private:
SharedState* m_sharedState{ nullptr }; // [2]
};
Expand Down Expand Up @@ -173,27 +173,29 @@ namespace myplugin {
}
void Engine::initialise(double sampleRate, std::uint32_t minBlockSize, std::uint32_t maxBlockSize) { // [2]
void Engine::initialise(mostly_harmless::core::InitContext context) noexcept { // [2]
}
void Engine::process(marvin::container::BufferView<float> buffer, std::optional<mostly_harmless::TransportState> transport) { // [3]
void Engine::process(mostly_harmless::core::ProcessContext context) noexcept { // [3]
}
void Engine::reset() { // [4]
void Engine::reset() noexcept { // [4]
}
}
```

[1] Here we store the passed `SharedState` instance in our `m_sharedState` member, for access later on.

[2] We implement `initialise`, which takes the host's sample rate (`sampleRate`), the smallest block size the host can
[2] We implement `initialise`, which takes an `InitContext`, which is a convenience wrapper for making the args
extensible, and contains the host's sample rate (`sampleRate`), the smallest block size the host can
pass (`minBlockSize`),
and the largest block size the host can pass (`maxBlockSize`).
[3] We implement `process`, which takes a non-owning-view into the buffer passed by the host (`buffer`), and an optional
[3] We implement `process`, which takes a `ProcessContext` - again, a convenience wrapper for making the args
extensible, which contains a non-owning-view into the buffer passed by the host (`buffer`), and an optional
representing the transport state, if it is available (`transport`).
[4] We implement `reset`, as detailed above.
Expand Down Expand Up @@ -300,9 +302,10 @@ As discussed earlier, `IPluginEntry` is our interface for doing so.
namespace myplugin {
struct PluginEntry final : public mostly_harmless::core::IPluginEntry {
public:
std::unique_ptr<mostly_harmless::core::ISharedState> createState(mostly_harmless::core::SharedStateContext&& context) override;
std::unique_ptr<mostly_harmless::Core::IEngine> createEngine(mostly_harmless::core::ISharedState* sharedState) override;
std::unique_ptr<mostly_harmless::core::IEditor> createEditor(mostly_harmless::core::ISharedState* sharedState) override;
[[nodiscard]] std::unique_ptr<mostly_harmless::core::ISharedState> createState(mostly_harmless::core::SharedStateContext&& context) override;
[[nodiscard]] std::unique_ptr<mostly_harmless::Core::IEngine> createEngine(mostly_harmless::core::ISharedState* sharedState) override;
[[nodiscard]] bool hasGui() const noexcept override;
[[nodiscard]] std::unique_ptr<mostly_harmless::core::IEditor> createEditor(mostly_harmless::core::ISharedState* sharedState) override;
};
}

Expand All @@ -326,12 +329,16 @@ namespace myplugin {
return std::make_unique<Engine>(asUserState(sharedState)); // [3]
}
bool PluginEntry::hasGui() const noexcept { // [4]
return true;
}
std::unique_ptr<mostly_harmless::core::IEditor> PluginEntry::createEditor(mostly_harmless::core::ISharedState* sharedState) {
return std::make_unique<Editor>(asUserState(sharedState)); // [4]
return std::make_unique<Editor>(asUserState(sharedState)); // [5]
}
}
MH_REGISTER_PLUGIN_ENTRY(myplugin::PluginEntry); // [5]
MH_REGISTER_PLUGIN_ENTRY(myplugin::PluginEntry); // [6]
```

[1] We declare a TU scoped helper function to avoid verbose static casts, for downcasting our `ISharedEditor` points to
Expand All @@ -341,9 +348,12 @@ our user `SharedState` class.

[3] We create our `Engine` class, passing it a downcast-to-user-state of `sharedState`.

[4] We create our `Editor` class, passing it a downcast-to-user-state of `sharedState`.
[4] We return true from `hasGui()`. In the case of a headless plugin, we can return false here, and return a nullptr
from `createEditor`.

[5] We create our `Editor` class, passing it a downcast-to-user-state of `sharedState`.

[5] Finally, we call a macro to register this `PluginEntry` class with the framework. Internally this defines a free
[6] Finally, we call a macro to register this `PluginEntry` class with the framework. Internally this defines a free
function, `createPluginEntry`, which returns our user `PluginEntry` type.
The internal framework class then uses its hooks to create `SharedState`, `Engine` and `Editor` classes, and forwards
relevant function calls to the appropriate places within there classes.
Expand Down Expand Up @@ -411,12 +421,14 @@ Let's start simple - multiply the audio input by 0.5. Jumping back to our `Engin
place to do so.
```cpp
void Engine::process(marvin::containers::BufferView<float> buffer, std::optional<mostly_harmless::TransportState> /*transportState*/) {
void Engine::process(mostly_harmless::core::ProcessContext context) noexcept {
auto buffer = context.buffer;
}
```
You can take a look at the docs for `BufferView`, but it can pretty much be thought of a `std::span` but for an audio
Firstly, we retrieve the buffer (a `marvin::containers::BufferView<float>`) from the `context`. You can take a look at
the docs for `BufferView`, but it can pretty much be thought of a `std::span` but for an audio
buffer - that is, a non owning view into the passed in audio buffer.
Internally in the framework, for sample accurate automation, this buffer view will be from index_of_last_event to
index_of_current_event - essentially it's interrupted whenever there's a new param/midi event.
Expand Down Expand Up @@ -566,7 +578,8 @@ ParameterView SharedState::getParamView() const noexcept {
We can finally grab the parameter in our Engine now:

```cpp
void Engine::process(marvin::containers::BufferView<float> buffer, std::optional<mostly_harmless::TransportState> transport) {
void Engine::process(mostly_harmless::core::ProcessContext context) noexcept {
auto buffer = context.buffer;
auto paramView = m_sharedState->getParamView();
const auto gain = paramView.gainParam->value;
const auto* const* read = buffer.getArrayOfReadPointers();
Expand Down

0 comments on commit 9b0b97e

Please sign in to comment.