Skip to content

Commit

Permalink
Load a preset using a midi button (#104)
Browse files Browse the repository at this point in the history
* Update frontend to support midi button

* Create new class for handling mapped MIDI messages

* Implement notifications on MIDI preset change

* Implement preset dropdown for midi mapping page

* Allow switching between parameter and toggle presets using dropdown

* Implement midi mapping edit dialog
  • Loading branch information
Barabas5532 authored Feb 2, 2024
1 parent 1ae7b44 commit ba1a7a0
Show file tree
Hide file tree
Showing 33 changed files with 1,156 additions and 342 deletions.
1 change: 1 addition & 0 deletions firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ if (ESP_PLATFORM)
add_subdirectory(components/i2c)
add_subdirectory(components/i2s)
add_subdirectory(components/messages)
add_subdirectory(components/midi_handling)
add_subdirectory(components/midi_mapping)
add_subdirectory(components/midi_protocol)
add_subdirectory(components/os)
Expand Down
1 change: 1 addition & 0 deletions firmware/components/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ add_subdirectory(float_convert)
add_subdirectory(hardware)
add_subdirectory(heap_tracing)
add_subdirectory(messages)
add_subdirectory(midi_handling)
add_subdirectory(midi_mapping)
add_subdirectory(midi_protocol)
add_subdirectory(os)
Expand Down
25 changes: 10 additions & 15 deletions firmware/components/audio_param/test/test_audio_param.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,53 +56,48 @@ TEST_F(AudioParams, DefaultValueIsCorrect)

TEST_F(AudioParams, UpdateToMinimum)
{
uut.update("test", 0);

ASSERT_EQ(0, uut.update("test", 0));
ASSERT_EQ(0, *uut.get_raw_parameter("test"));
}

TEST_F(AudioParams, UpdateToMaximum)
{
uut.update("test", 1);

ASSERT_EQ(0, uut.update("test", 1));
ASSERT_EQ(10, *uut.get_raw_parameter("test"));
}

TEST_F(AudioParams, UpdateToHalfWithNonTrivialRange)
{
(void)uut.create_and_add_parameter("non-trivial", -1, 1, -1);
uut.update("non-trivial", 0.5);

ASSERT_EQ(0, uut.create_and_add_parameter("non-trivial", -1, 1, -1));
ASSERT_EQ(0, uut.update("non-trivial", 0.5));
ASSERT_EQ(0, *uut.get_raw_parameter("non-trivial"));
}

TEST_F(AudioParams, UpdateTooHighIsNoop)
{
uut.update("test", 1.1);

ASSERT_EQ(0, uut.update("test", 1.1));
ASSERT_EQ(5, *uut.get_raw_parameter("test"));
}

TEST_F(AudioParams, UpdateTooLowIsNoop)
{
uut.update("test", -0.1);

ASSERT_EQ(0, uut.update("test", -0.1));
ASSERT_EQ(5, *uut.get_raw_parameter("test"));
}

TEST_F(AudioParams, UpdateLimitEdgeCases)
{
uut.update("test", 0);
ASSERT_EQ(0, uut.update("test", 0));
ASSERT_EQ(0, *uut.get_raw_parameter("test"));

uut.update("test", 1);
ASSERT_EQ(0, uut.update("test", 1));
ASSERT_EQ(10, *uut.get_raw_parameter("test"));
}

TEST_F(AudioParams, Iterate)
{
(void)uut.create_and_add_parameter("test1", 0, 10, 1);
(void)uut.create_and_add_parameter("test2", 0, 10, 2);
ASSERT_EQ(0, uut.create_and_add_parameter("test1", 0, 10, 1));
ASSERT_EQ(0, uut.create_and_add_parameter("test2", 0, 10, 2));

std::map<parameters::id_t, float> expected{
{"test", 5}, // defined in test fixture
Expand Down
3 changes: 2 additions & 1 deletion firmware/components/cmd_handling/test/test_cmd_handling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class EventSendAdapter final
void send(const shrapnel::parameters::ApiMessage &message,
std::optional<int> fd)
{

event.send(message, fd);
}

Expand Down Expand Up @@ -157,6 +158,6 @@ TEST_F(CmdHandling, InitialiseParameters)
EXPECT_CALL(event, send({expected}, testing::Eq(std::nullopt))).Times(1);

dispatch(shrapnel::parameters::Initialise{}, 0);
}
} // namespace

} // namespace
25 changes: 25 additions & 0 deletions firmware/components/midi_handling/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
add_library(shrapnel_midi_handling INTERFACE)
add_library(shrapnel::midi_handling ALIAS shrapnel_midi_handling)

target_include_directories(shrapnel_midi_handling INTERFACE include)

target_link_libraries(shrapnel_midi_handling INTERFACE
shrapnel::etl
shrapnel::messages
shrapnel::presets
)

if(DEFINED TESTING)
add_executable(midi_handling_test
test/test_midi_handling.cpp)

target_link_libraries(midi_handling_test
PRIVATE
GTest::gmock
GTest::gtest_main
shrapnel::audio_param
shrapnel::compiler_warning_flags
shrapnel::midi_handling)

gtest_discover_tests(midi_handling_test)
endif()
108 changes: 108 additions & 0 deletions firmware/components/midi_handling/include/midi_handling.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2022 Barabas Raffai
*
* This file is part of ShrapnelDSP.
*
* ShrapnelDSP is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* ShrapnelDSP is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* ShrapnelDSP. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include "etl/delegate.h"
#include "messages.h"
#include "midi_mapping.h"
#include "midi_protocol.h"
#include "presets_manager.h"
#include "selected_preset_manager.h"

namespace shrapnel {

template <typename AudioParametersT,
typename MappingManagerT,
typename PresetLoaderT>
class MidiMessageHandler
{
public:
MidiMessageHandler(std::shared_ptr<AudioParametersT> a_parameters,
std::shared_ptr<MappingManagerT> a_mapping_manager,
std::shared_ptr<PresetLoaderT> a_preset_loader)
: parameters{std::move(a_parameters)},
mapping_manager{std::move(a_mapping_manager)},
preset_loader{std::move(a_preset_loader)} {};

/** React to a MIDI message by updating an audio parameter if there is a
* mapping registered
*/
void process_message(midi::Message message) const
{
auto cc_params =
get_if<midi::Message::ControlChange>(&message.parameters);
if(!cc_params)
return;

for(const auto &[_, mapping] : *mapping_manager)
{
if(mapping.midi_channel != message.channel)
{
continue;
}

if(mapping.cc_number != cc_params->control)
{
continue;
}

switch(mapping.mode)
{
case midi::Mapping::Mode::PARAMETER:
parameters->update(mapping.parameter_name,
cc_params->value /
float(midi::CC_VALUE_MAX));
break;
case midi::Mapping::Mode::TOGGLE:
{
if(cc_params->value == 0)
{
continue;
}

auto old_value = parameters->get(mapping.parameter_name);

parameters->update(mapping.parameter_name,
old_value < 0.5f ? 1.f : 0.f);
break;
}
case midi::Mapping::Mode::BUTTON:
{
// A button sends the maximum value when it becomes pressed.
if(cc_params->value != midi::CC_VALUE_MAX)
{
continue;
}

auto id = mapping.preset_id;
preset_loader->load_preset(id);
break;
}
}
}
}

private:
std::shared_ptr<AudioParametersT> parameters;
std::shared_ptr<MappingManagerT> mapping_manager;
std::shared_ptr<PresetLoaderT> preset_loader;
};

} // namespace shrapnel
Loading

0 comments on commit ba1a7a0

Please sign in to comment.