Skip to content

Commit

Permalink
Fix state persistance and implement sound slots
Browse files Browse the repository at this point in the history
  • Loading branch information
topisani committed Jan 13, 2023
1 parent 2c6eb4a commit d49bff2
Show file tree
Hide file tree
Showing 22 changed files with 311 additions and 151 deletions.
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,3 @@
The OTTO is an open source digital hardware synth, groovebox and FX processor. For information and documentation see [our website](https://bitfieldaudio.com)!

We would like to invite you to our **[discord server](https://discord.gg/4cV9Ucz)** where we hang out and discuss all things OTTO.

# Support the project


<a href="https://www.patreon.com/bitfieldaudio" data-patreon-widget-type="become-patron-button"><img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dbitfieldaudio%26type%3Dpatrons&style=for-the-badge"></a>

If you want to help support the development of OTTO, you can support us on Patreon. With Patreon, you can send us a small monthly amount of money, or make a one-time donation by cancelling the monthly subscription after the first payment (Note that this usually happens on the first of the month, not immediately.)

17 changes: 8 additions & 9 deletions src/app/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,16 @@ namespace otto {

// Context
itc::Context ctx;
stateman.add("Context", std::ref(ctx));
itc::PersistanceProvider persistance(ctx);
stateman.add("Context", std::ref(persistance));

// Sound slots
auto& soundslots_ctx = ctx["slots"];
auto sound_slots = engines::slots::SoundSlots::make(soundslots_ctx);
nav_km.bind_nav_key(Key::slots, sound_slots.overlay_screen);

// Synth Dispatcher
auto synth = otto::make_synthdispatcher(ctx["synth"]);
auto synth = otto::make_synthdispatcher(sound_slots.logic->managed_ctx());
synth.logic->register_engine(std::move(engines::ottofm::factory));
synth.logic->register_engine(std::move(engines::nuke::factory));

Expand All @@ -153,13 +159,6 @@ namespace otto {
auto master = engines::master::Master::make(ctx["master"], audio.driver().mixer());
nav_km.bind_nav_key(Key::master, master.screen);

// Sound slots
itc::Context soundslots_ctx;
auto sound_slots = engines::slots::SoundSlots::make(soundslots_ctx);
sound_slots.logic->set_managed(std::ref(ctx));
nav_km.bind_nav_key(Key::slots, sound_slots.overlay_screen);
stateman.add("Sound Slots", std::ref(soundslots_ctx));

// Drivers
RtMidiDriver rt_midi_driver(audio.midi());
LedManager ledman(controller.port_writer());
Expand Down
1 change: 1 addition & 0 deletions src/app/engines/midi-fx/arp/arp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ namespace otto::engines::arp {
case OctaveMode::octavedownup: return octavedownup; break;
case OctaveMode::multiply: return multiply; break;
}
OTTO_UNREACHABLE();
}
} // namespace octave_modes

Expand Down
25 changes: 18 additions & 7 deletions src/app/engines/slots/overlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace otto::engines::slots {
}

struct SlotsWidget : graphics::Widget<SlotsWidget> {
int selected_index = 0;
float selected_color_f = 0.f; // [0,1]
skia::Color selected_color = skia::Color(SK_ColorRED);

Expand All @@ -31,22 +32,29 @@ namespace otto::engines::slots {
float height = bounding_box.height();
skia::Point center = {width / 2.f, width / 2.f};
// Background
ctx.drawRRect(skia::RRect::MakeRectXY(bounding_box, 8, 8), paints::fill(colors::grey70));
constexpr float rect_radius = 4;
ctx.drawRRect(skia::RRect::MakeRectXY(bounding_box, rect_radius, rect_radius), paints::fill(colors::black));
ctx.drawRRect(skia::RRect::MakeRectXY(bounding_box, rect_radius, rect_radius),
paints::stroke(colors::yellow, 3.f));

skia::place_text(ctx, fmt::format("SLOT {}", selected_index + 1), fonts::medium(22), colors::white,
{width / 2, 16}, anchors::top_center);

constexpr float outer_radius = 20;
constexpr float middle_radius = 12;
constexpr float inner_radius = 10;
constexpr SkScalar text_offset = outer_radius + 6;
const float btn_center = height * 0.55f;
// Blue Copy/Paste
skia::Point copy_location = {width * 0.25f, static_cast<SkScalar>(height * 0.382)};
skia::Point copy_location = {width * 0.25f, btn_center};
ctx.drawCircle(copy_location, middle_radius, paints::fill(colors::blue.fade(0.5f)));
ctx.drawCircle(copy_location, inner_radius, paints::fill(colors::blue));
skia::place_text(ctx, "COPY/PASTE", fonts::medium(14), paints::fill(colors::white),
skia::place_text(ctx, "CPY/PSTE", fonts::medium(14), paints::fill(colors::white),
{copy_location.x(), copy_location.y() + text_offset}, anchors::top_center);


// Color wheel
skia::Point color_wheel_location = {width * 0.75f, static_cast<SkScalar>(height * 0.382)};
skia::Point color_wheel_location = {width * 0.75f, btn_center};
skia::Paint paint;
paint.setAntiAlias(true);
paint.setShader(skia::GradientShader::MakeSweep(color_wheel_location.x(), color_wheel_location.y(), wheel_colors,
Expand Down Expand Up @@ -102,21 +110,23 @@ namespace otto::engines::slots {
{
return led_groups::channel;
}

void leds(LEDColorSet& led_color) noexcept override
{
for (const auto& led : util::enum_values<Led>()) {
if (led_groups::channel.test(led)) {
auto key = *key_from(led); // This is certain to contain a value
led_color[led] =
LEDColor::from_skia(f_to_color(state().slot_states[*channel_number_for(key)].selected_color_f));
auto channel_idx = *channel_number_for(key);
led_color[led] = LEDColor::from_skia(f_to_color(state().slot_states[channel_idx].selected_color_f)
.dim(channel_idx == state().active_idx ? 0 : 0.5));
}
}
}

SoundSlotsScreen(itc::Context& c) : Consumer(c)
{
// Init Screen
widget.bounding_box = {skia::Box({50, 80}, {220, 80})};
widget.bounding_box = {skia::Box({50, 60}, {220, 120})};
widget.selected_color_f = state().slot_states[state().active_idx].selected_color_f;
// Init LEDs
}
Expand All @@ -129,6 +139,7 @@ namespace otto::engines::slots {

// Content
widget.selected_color_f = state().slot_states[state().active_idx].selected_color_f;
widget.selected_index = state().active_idx;
widget.draw(ctx);
}

Expand Down
9 changes: 2 additions & 7 deletions src/app/engines/slots/slots.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,9 @@ namespace otto::engines::slots {
tl::optional old = idx.old_val();
if (idx.check_changed(state.active_idx)) {
// Serialize current
if (old) data_.json_objects[*old] = util::serialize(_ctx);
if (old) data_.json_objects[*old] = util::serialize(persistance_provider);
// Deserialize new
util::deserialize_from(data_.json_objects[state.active_idx], _ctx);
util::deserialize_from(data_.json_objects[state.active_idx], persistance_provider);
}
}

void Logic::set_managed(util::DynSerializable&& ctx)
{
_ctx = std::move(ctx);
}
} // namespace otto::engines::slots
28 changes: 22 additions & 6 deletions src/app/engines/slots/slots.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,34 @@ namespace otto::engines::slots {
// Put in a separate struct because it is not needed for the screen and contains a lot of data.
struct SlotData {
std::array<json::value, 10> json_objects;
DECL_VISIT(json_objects);
};

struct Logic : ILogic, itc::Producer<SoundSlotsState, SlotState> {
Logic(itc::Context& ctx) : Producer(ctx) {}
struct Logic : ILogic, itc::Producer<SoundSlotsState>, itc::Persistant {
Logic(itc::Context& ctx) : Producer(ctx), itc::Persistant(ctx, "slots"), persistance_provider(ctx["slots"]) {}

void on_state_change(const SoundSlotsState& state) noexcept override;
void on_state_change(const SlotState& state) noexcept override {}
void set_managed(util::DynSerializable&& ctx);

/// Any objects registered on this context will be serialized and deserialized when switching slots
itc::Context& managed_ctx()
{
return persistance_provider.context();
}

void serialize_into(json::value& json) const override
{
util::serialize_into(json["slots"], data_.json_objects);
json["slots"][state().active_idx] = util::serialize(persistance_provider);
}

void deserialize_from(const json::value& json) override
{
auto slots = json::get_or_null(json, "slots");
util::deserialize_from(slots, data_.json_objects);
util::deserialize_from(json::get_or_null(json, state().active_idx), persistance_provider);
}

private:
util::DynSerializable _ctx;
itc::PersistanceProvider persistance_provider;
SlotData data_;
util::change_checker<int> idx;
};
Expand Down
33 changes: 33 additions & 0 deletions src/app/layers/navigator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ namespace otto {
return current_screen_;
}

ScreenWithHandlerPtr Navigator::prev_screen() noexcept
{
return prev_screen_;
}

// NAV KEYMAP

NavKeyMap::NavKeyMap(Conf conf, util::smart_ptr<Navigator> n) : conf(conf), nav_(std::move(n)) {}
Expand Down Expand Up @@ -178,4 +183,32 @@ namespace otto {
{
return nav_->draw(ctx);
}

void NavKeyMap::deserialize_from(const json::value& json)
{
if (json["previous"].is_string()) {
auto prev = util::deserialize<Key>(json["previous"]);
auto found = binds_.find(prev);
if (found == binds_.end()) return;
nav().navigate_to(found->second);
}

if (json["current"].is_string()) {
auto cur = util::deserialize<Key>(json["current"]);
auto found = binds_.find(cur);
if (found == binds_.end()) return;
nav().navigate_to(found->second);
}
}

void NavKeyMap::serialize_into(json::value& json) const
{
auto cur = nav_->current_screen();
auto prev = nav_->prev_screen();
json = json::object();
for (auto&& [k, v] : binds_) {
if (v == cur) util::serialize_into(json["current"], k);
if (v == prev) util::serialize_into(json["previous"], k);
}
}
} // namespace otto
23 changes: 6 additions & 17 deletions src/app/layers/navigator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ namespace otto {

/// Access the currently selected screen/handler pair
ScreenWithHandlerPtr current_screen() noexcept;
/// Access the previously selected screen/handler pair
ScreenWithHandlerPtr prev_screen() noexcept;

[[nodiscard]] KeySet key_mask() const noexcept override;
[[nodiscard]] LedSet led_mask() const noexcept override;
Expand All @@ -52,7 +54,7 @@ namespace otto {
/// if it becomes useful in other places
struct NavKeyMap final : IInputLayer, IScreen, util::ISerializable {
struct Conf : Config<Conf> {
static constexpr auto name = "Navigation Keys";
static constexpr auto name = "NavKeyMap";
chrono::duration peek_timeout = 500ms;
LEDColor deselected_color = {0x08, 0x08, 0x08};
LEDColor selected_color = {0xFF, 0xFF, 0xFF};
Expand Down Expand Up @@ -83,22 +85,9 @@ namespace otto {
[[nodiscard]] KeySet key_mask() const noexcept override;
[[nodiscard]] LedSet led_mask() const noexcept override;

void serialize_into(json::value& json) const override
{
// TODO: Do this better
auto cur = nav_->current_screen();
for (auto&& [k, v] : binds_) {
if (v == cur) return util::serialize_into(json, k);
}
}

void deserialize_from(const json::value& json) override
{
auto key = util::deserialize<Key>(json);
auto found = binds_.find(key);
if (found == binds_.end()) return;
nav().navigate_to(found->second);
}
void serialize_into(json::value& json) const override;

void deserialize_from(const json::value& json) override;

private:
Conf conf;
Expand Down
1 change: 1 addition & 0 deletions src/app/services/audio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace otto::services {
using Callback = drivers::IAudioDriver::Callback;

Audio(util::smart_ptr<drivers::IAudioDriver>&& d);
Audio(ConfigManager& confman) : Audio(drivers::IAudioDriver::make_default(confman) ) {}

util::at_exit set_process_callback(Callback&& cb) noexcept;
util::at_exit set_midi_handler(util::smart_ptr<midi::IMidiHandler> h) noexcept;
Expand Down
1 change: 0 additions & 1 deletion src/app/services/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <memory_resource>
#include <unordered_map>

#include "lib/util/crtp.hpp"
#include "lib/util/name_of.hpp"
#include "lib/util/serialization.hpp"
#include "lib/util/string_ref.hpp"
Expand Down
59 changes: 53 additions & 6 deletions src/lib/engines/synthdispatcher/synthdispatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <functional>
#include <memory>

#include "lib/util/change_checker.hpp"
#include "lib/util/math.hpp"

#include "lib/audio.hpp"
Expand Down Expand Up @@ -72,10 +73,15 @@ namespace otto {
}
};

struct SynthDispatcherLogic : ILogic, itc::Producer<SynthDispatcherState>, itc::Receiver<SynthDispatcherCommand> {
struct SynthDispatcherLogic : ILogic,
itc::Producer<SynthDispatcherState>,
itc::Receiver<SynthDispatcherCommand>,
itc::Persistant {
using Producer::Producer;

SynthDispatcherLogic(itc::Context& ctx) : Producer(ctx), Receiver(ctx) {}
SynthDispatcherLogic(itc::Context& ctx)
: Producer(ctx), Receiver(ctx), Persistant(ctx, "synth_dispatcher"), persistance_provider(ctx["engine"])
{}

void register_engine(SynthEngineFactory&& factory)
{
Expand Down Expand Up @@ -108,7 +114,37 @@ namespace otto {
using namespace synth_dispatcher_cmd;
util::match(cmd, //
[&](SelectEngine e) { select_engine(std::clamp(e.index, 0, (int) _factories.size() - 1)); });
;
}

void on_state_change(const SynthDispatcherState& state) noexcept override
{
if (_factories.empty() && active_index.check_changed(state.active_engine)) {
select_engine(state.active_engine);
}
}

void serialize_into(json::value& json) const override
{
if (!_factories.empty()) {
json["active_engine"] = _factories[state().active_engine]._metadata.name;
util::serialize_into(json["state"], persistance_provider);
}
}

void deserialize_from(const json::value& json) override
{
deactivate_engine();
if (auto active = json::get_or_null(json, "active_engine"); !active.is_null()) {
int idx = std::ranges::find_if(_factories, [&](SynthEngineFactory& x) { return x._metadata.name == active; }) -
_factories.begin();
if (idx == _factories.size()) {
idx = 0;
}
select_engine(idx);
util::deserialize_from(json::get_or_null(json, "state"), persistance_provider);
} else if (!_factories.empty()) {
select_engine(0);
}
}

private:
Expand All @@ -123,15 +159,26 @@ namespace otto {

void activate_engine(std::size_t idx)
{
LOGT("Activating engine {}", idx);
// Constructs the engine
if (idx < _factories.size()) {
_active = _factories[idx].make_all(Producer::context());
_active = _factories[idx].make_all(engine_ctx());
}
update_state(idx);
AudioDomain::get_static_executor()->sync();
GraphicsDomain::get_static_executor()->sync();
}

[[nodiscard]] itc::Context& engine_ctx()
{
return Producer::context()["engine"];
}

[[nodiscard]] const itc::Context& engine_ctx() const
{
return Producer::context()["engine"];
}

void wipe_state()
{
commit([&](auto& state) {
Expand All @@ -154,9 +201,9 @@ namespace otto {
});
}

itc::PersistanceProvider persistance_provider;
std::vector<SynthEngineFactory> _factories;
// TODO: Refactor this?
// To make sure these don't come out of sync, we only allow activation through the index.
util::change_checker<int> active_index;
SynthEngineInstance _active = {};
};

Expand Down
Loading

0 comments on commit d49bff2

Please sign in to comment.