diff --git a/software/platformio.ini b/software/platformio.ini index 56f148ca0..751821527 100644 --- a/software/platformio.ini +++ b/software/platformio.ini @@ -69,10 +69,10 @@ build_flags = -DENABLE_APP_SCENES ; -DENABLE_APP_ENIGMA -DENABLE_APP_MIDI - -DENABLE_APP_PONG - -DENABLE_APP_PIQUED - -DENABLE_APP_POLYLFO - -DENABLE_APP_H1200 + ; -DENABLE_APP_PONG + ; -DENABLE_APP_PIQUED + ; -DENABLE_APP_POLYLFO + ; -DENABLE_APP_H1200 -DENABLE_APP_BYTEBEATGEN ; -DENABLE_APP_NEURAL_NETWORK ; -DENABLE_APP_DARKEST_TIMELINE diff --git a/software/res/progname.py b/software/res/progname.py index 5ae9e8ddf..e2a8c9ebd 100644 --- a/software/res/progname.py +++ b/software/res/progname.py @@ -20,3 +20,6 @@ extras += "_flipped" env.Replace(PROGNAME=f"o_C-phazerville-{version}{extras}-{git_rev}") + +# include toolchain paths in compiledb for clangd lsp +env.Replace(COMPILATIONDB_INCLUDE_TOOLCHAIN=True) diff --git a/software/src/APP_HEMISPHERE.h b/software/src/APP_HEMISPHERE.h index bb47b5b96..acfb0ab1f 100644 --- a/software/src/APP_HEMISPHERE.h +++ b/software/src/APP_HEMISPHERE.h @@ -41,6 +41,11 @@ #endif #include "hemisphere_config.h" +#include +#ifdef ARDUINO_TEENSY41 +#include "AudioSetup.h" +#include "AudioPassthrough.h" +#endif // The settings specify the selected applets, and 64 bits of data for each applet, // plus 64 bits of data for the ClockSetup applet (which includes some misc config). @@ -85,9 +90,9 @@ class HemispherePreset : public SystemExclusiveHandler, return (h == LEFT_HEMISPHERE) ? values_[HEMISPHERE_SELECTED_LEFT_ID] : values_[HEMISPHERE_SELECTED_RIGHT_ID]; } - HemisphereApplet* GetApplet(int h) { + HemisphereApplet& GetApplet(int h) { int idx = HS::get_applet_index_by_id( GetAppletId(h) ); - return HS::available_applets[idx].instance[h]; + return HS::available_applets[idx]->GetInstance(h); } void SetAppletId(int h, int id) { apply_value(h, id); @@ -242,6 +247,9 @@ class HemisphereManager : public HSApplication { SetApplet(LEFT_HEMISPHERE, HS::get_applet_index_by_id(18)); // DualTM SetApplet(RIGHT_HEMISPHERE, HS::get_applet_index_by_id(15)); // EuclidX + #ifdef ARDUINO_TEENSY41 + UpdateAudioPipeline(); + #endif } void Resume() { @@ -267,11 +275,11 @@ class HemisphereManager : public HSApplication { for (int h = 0; h < 2; h++) { int index = my_applet[h]; - if (hem_active_preset->GetAppletId(HEM_SIDE(h)) != HS::available_applets[index].id) + if (hem_active_preset->GetAppletId(HEM_SIDE(h)) != HS::available_applets[index]->id) doSave = 1; - hem_active_preset->SetAppletId(HEM_SIDE(h), HS::available_applets[index].id); + hem_active_preset->SetAppletId(HEM_SIDE(h), HS::available_applets[index]->id); - uint64_t data = HS::available_applets[index].instance[h]->OnDataRequest(); + uint64_t data = HS::available_applets[index]->GetInstance(h).OnDataRequest(); if (data != applet_data[h]) doSave = 1; applet_data[h] = data; hem_active_preset->SetData(HEM_SIDE(h), data); @@ -319,7 +327,7 @@ class HemisphereManager : public HSApplication { int index = HS::get_applet_index_by_id( hem_active_preset->GetAppletId(h) ); applet_data[h] = hem_active_preset->GetData(HEM_SIDE(h)); SetApplet(HEM_SIDE(h), index); - HS::available_applets[index].instance[h]->OnDataReceive(applet_data[h]); + HS::available_applets[index]->GetInstance(h).OnDataReceive(applet_data[h]); } @@ -331,13 +339,88 @@ class HemisphereManager : public HSApplication { LoadFromPreset(queued_preset); } + HemisphereApplet& GetApplet(HEM_SIDE hemisphere) { + return HS::available_applets[my_applet[hemisphere]]->GetInstance(hemisphere); + } + + #ifdef ARDUINO_TEENSY41 + void UpdateAudioPipeline() { + AudioNoInterrupts(); + HemisphereApplet& left_applet = GetApplet(HEM_SIDE::LEFT_HEMISPHERE); + HemisphereApplet& right_applet = GetApplet(HEM_SIDE::RIGHT_HEMISPHERE); + + for (int i=0; i < 3; i++) for (int j=0; j < 2; j++) conns[i][j].disconnect(); + + AudioStream& in = OC::AudioDSP::input_stream(); + AudioStream& out = OC::AudioDSP::output_stream(); + + auto lin = left_applet.InputStream(); + auto lout = left_applet.OutputStream(); + + auto rin = right_applet.InputStream(); + auto rout = right_applet.OutputStream(); + + // Rules: + // - If both sides are mono, left applet controls left pipeline, right controls right + // - If one side is mono and the other stereo, the stereo effectively becomes mono + // - If one side is stereo and the other none, the stereo applet controls both channels + // - If both are stereo, left pipeline feeds into right + + // Wire up left channel + if (left_applet.NumAudioChannels() > 0) { + conns[0][0].connect(in, 0, lin.value(), 0); + if (right_applet.NumAudioChannels() == 2 && left_applet.NumAudioChannels() == 2) { + conns[1][0].connect(lout.value(), 0, rin.value(), 0); + conns[2][0].connect(rout.value(), 0, out, 0); + } else { + conns[1][0].connect(lout.value(), 0, out, 0); + } + } else if (right_applet.NumAudioChannels() == 2) { + conns[0][0].connect(in, 0, rin.value(), 0); + conns[1][0].connect(rout.value(), 0, out, 0); + } else { + conns[0][0]. connect(in, 0, out, 0); + } + + // Wire up right channel + if (right_applet.NumAudioChannels() == 1) { + conns[0][1].connect(in, 1, rin.value(), 0); + conns[1][1].connect(rout.value(), 0, out, 1); + } else if (left_applet.NumAudioChannels() == 2) { + conns[0][1].connect(in, 1, lin.value(), 1); + if (right_applet.NumAudioChannels() == 2) { + conns[1][1].connect(lout.value(), 1, rin.value(), 1); + conns[2][1].connect(rout.value(), 1, out, 1); + } else { + conns[1][1].connect(lout.value(), 1, out, 1); + } + } else if (right_applet.NumAudioChannels() == 2) { + conns[0][1].connect(in, 1, rin.value(), 1); + conns[1][1].connect(rout.value(), 1, out, 1); + } else { + conns[0][1].connect(in, 1, out, 1); + } + AudioInterrupts(); + } + #endif + // does not modify the preset, only the manager void SetApplet(HEM_SIDE hemisphere, int index) { //if (my_applet[hemisphere]) // TODO: special case for first load? - HS::available_applets[my_applet[hemisphere]].instance[hemisphere]->Unload(); + GetApplet(hemisphere).Unload(); + #ifdef ARDUINO_TEENSY41 + const uint8_t last_channels = GetApplet(hemisphere).NumAudioChannels(); + #endif next_applet[hemisphere] = my_applet[hemisphere] = index; - HS::available_applets[index].instance[hemisphere]->BaseStart(hemisphere); + HemisphereApplet& selected_applet = HS::available_applets[index]->GetInstance(hemisphere); + selected_applet.BaseStart(hemisphere); + + #ifdef ARDUINO_TEENSY41 + if (selected_applet.NumAudioChannels() > 0 || selected_applet.NumAudioChannels() != last_channels) + UpdateAudioPipeline(); + #endif } + void ChangeApplet(HEM_SIDE h, int dir) { int index = HS::get_next_applet_index(next_applet[h], dir); next_applet[h] = index; @@ -423,7 +506,7 @@ class HemisphereManager : public HSApplication { int index = my_applet[h]; // MIDI signals mixed with inputs to applets - if (HS::available_applets[index].id != 150) // not MIDI In + if (HS::available_applets[index]->id != 150) // not MIDI In { ForEachChannel(ch) { int chan = h*2 + ch; @@ -448,7 +531,7 @@ class HemisphereManager : public HSApplication { } } } - HS::available_applets[index].instance[h]->BaseController(); + HS::available_applets[index]->GetInstance(h).BaseController(); } #ifdef ARDUINO_TEENSY41 @@ -500,7 +583,7 @@ class HemisphereManager : public HSApplication { #ifdef ARDUINO_TEENSY41 if (view_state == AUDIO_SETUP) { gfxHeader("Audio DSP Setup"); - OC::AudioDSP::DrawAudioSetup(); + // OC::AudioDSP::DrawAudioSetup(); draw_applets = false; } #endif @@ -514,8 +597,7 @@ class HemisphereManager : public HSApplication { draw_applets = false; } else if (help_hemisphere > -1) { - int index = my_applet[help_hemisphere]; - HS::available_applets[index].instance[help_hemisphere]->BaseView(true); + GetApplet(static_cast(help_hemisphere)).BaseView(true); draw_applets = false; } } @@ -524,7 +606,7 @@ class HemisphereManager : public HSApplication { for (int h = 0; h < 2; h++) { int index = my_applet[h]; - HS::available_applets[index].instance[h]->BaseView(); + HS::available_applets[index]->GetInstance(h).BaseView(); } if (HS::clock_m.IsRunning()) { @@ -558,7 +640,7 @@ class HemisphereManager : public HSApplication { } #ifdef ARDUINO_TEENSY41 if (view_state == AUDIO_SETUP) { - if (!down) OC::AudioDSP::AudioSetupButtonAction(h); + // if (!down) OC::AudioDSP::AudioSetupButtonAction(h); return; } #endif @@ -577,7 +659,7 @@ class HemisphereManager : public HSApplication { } else if (!clock_setup) { // regular applets get button release int index = my_applet[h]; - HS::available_applets[index].instance[h]->OnButtonPress(); + HS::available_applets[index]->GetInstance(h).OnButtonPress(); } } @@ -679,11 +761,11 @@ class HemisphereManager : public HSApplication { // -- button release if (!clock_setup) { const int index = my_applet[hemisphere]; - HemisphereApplet* applet = HS::available_applets[index].instance[hemisphere]; + HemisphereApplet& applet = HS::available_applets[index]->GetInstance(hemisphere); - if (applet->EditMode()) { + if (applet.EditMode()) { // select button becomes aux button while editing a param - applet->AuxButton(); + applet.AuxButton(); } else { // Select Mode if (hemisphere == select_mode) select_mode = -1; // Exit Select Mode if same button is pressed @@ -711,7 +793,7 @@ class HemisphereManager : public HSApplication { } #ifdef ARDUINO_TEENSY41 if (view_state == AUDIO_SETUP) { - OC::AudioDSP::AudioMenuAdjust(h, event.value); + // OC::AudioDSP::AudioMenuAdjust(h, event.value); return; } #endif @@ -725,7 +807,7 @@ class HemisphereManager : public HSApplication { ChangeApplet(HEM_SIDE(h), event.value); } else { int index = my_applet[h]; - HS::available_applets[index].instance[h]->OnEncoderMove(event.value); + HS::available_applets[index]->GetInstance(h).OnEncoderMove(event.value); } } @@ -803,6 +885,13 @@ class HemisphereManager : public HSApplication { int config_page = 0; int dummy_count = 0; + #ifdef ARDUINO_TEENSY41 + // default audio streams for applets so non-audio applets don't need their + // own. + AudioPassthrough<2> passthroughs[2]; + AudioConnection conns[3][2]; // input_to_left, left_to_right, right_to_output + #endif + OC::menu::ScreenCursor<5> showhide_cursor; int select_mode = -1; @@ -1088,7 +1177,7 @@ class HemisphereManager : public HSApplication { ++current, y += LineH) { gfxIcon(1, y + 1, HS::applet_is_hidden(current) ? CHECK_OFF_ICON : CHECK_ON_ICON); - gfxPrint( 11, y + 2, HS::available_applets[current].instance[0]->applet_name()); + gfxPrint( 11, y + 2, HS::available_applets[current]->GetInstance(0).applet_name()); if (current == showhide_cursor.cursor_pos()) gfxInvert(0, y, 127, LineH - 1); @@ -1166,9 +1255,9 @@ class HemisphereManager : public HSApplication { if (!hem_presets[i].is_valid()) gfxPrint(18, y, "(empty)"); else { - gfxPrint(18, y, hem_presets[i].GetApplet(0)->applet_name()); + gfxPrint(18, y, hem_presets[i].GetApplet(0).applet_name()); gfxPrint(", "); - gfxPrint(hem_presets[i].GetApplet(1)->applet_name()); + gfxPrint(hem_presets[i].GetApplet(1).applet_name()); } y += 10; @@ -1217,6 +1306,12 @@ void HEMISPHERE_init() { manager.BaseStart(); } +// #ifdef ARDUINO_TEENSY41 +// void HEMISPHERE_initAudio(AudioStream &input, AudioStream &output) { +// manager.SetIOStreams(input, output); +// } +// #endif + static constexpr size_t HEMISPHERE_storageSize() { return HemispherePreset::storageSize() * HEM_NR_OF_PRESETS; } diff --git a/software/src/APP_QUADRANTS.h b/software/src/APP_QUADRANTS.h index abef1488e..debc194a1 100644 --- a/software/src/APP_QUADRANTS.h +++ b/software/src/APP_QUADRANTS.h @@ -87,9 +87,9 @@ class QuadrantsPreset : public SystemExclusiveHandler, int GetAppletId(HEM_SIDE h) { return values_[QUADRANTS_SELECTED_LEFT_ID + h]; } - HemisphereApplet* GetApplet(HEM_SIDE h) { + HemisphereApplet& GetApplet(HEM_SIDE h) { int idx = HS::get_applet_index_by_id( GetAppletId(h) ); - return HS::available_applets[idx].instance[h]; + return HS::available_applets[idx]->GetInstance(h); } void SetAppletId(HEM_SIDE h, int id) { apply_value(h, id); @@ -260,11 +260,11 @@ class QuadAppletManager : public HSApplication { for (int h = 0; h < APPLET_SLOTS; h++) { int index = active_applet_index[h]; - if (quad_active_preset->GetAppletId(HEM_SIDE(h)) != HS::available_applets[index].id) + if (quad_active_preset->GetAppletId(HEM_SIDE(h)) != HS::available_applets[index]->id) doSave = 1; - quad_active_preset->SetAppletId(HEM_SIDE(h), HS::available_applets[index].id); + quad_active_preset->SetAppletId(HEM_SIDE(h), HS::available_applets[index]->id); - uint64_t data = HS::available_applets[index].instance[h]->OnDataRequest(); + uint64_t data = HS::available_applets[index]->GetInstance(h).OnDataRequest(); if (data != applet_data[h]) doSave = 1; applet_data[h] = data; quad_active_preset->SetData(HEM_SIDE(h), data); @@ -315,7 +315,7 @@ class QuadAppletManager : public HSApplication { int index = HS::get_applet_index_by_id( quad_active_preset->GetAppletId(HEM_SIDE(h)) ); applet_data[h] = quad_active_preset->GetData(HEM_SIDE(h)); SetApplet(HEM_SIDE(h), index); - HS::available_applets[index].instance[h]->OnDataReceive(applet_data[h]); + HS::available_applets[index]->GetInstance(h).OnDataReceive(applet_data[h]); } } preset_id = id; @@ -339,7 +339,7 @@ class QuadAppletManager : public HSApplication { active_applet[hemisphere]->Unload(); next_applet_index[hemisphere] = active_applet_index[hemisphere] = index; - active_applet[hemisphere] = HS::available_applets[index].instance[hemisphere]; + active_applet[hemisphere] = &HS::available_applets[index]->GetInstance(hemisphere); active_applet[hemisphere]->BaseStart(hemisphere); } void ChangeApplet(HEM_SIDE h, int dir) { @@ -395,7 +395,7 @@ class QuadAppletManager : public HSApplication { } // MIDI signals mixed with inputs to applets - if (HS::available_applets[ active_applet_index[h] ].id != 150) // not MIDI In + if (HS::available_applets[ active_applet_index[h] ]->id != 150) // not MIDI In { ForEachChannel(ch) { int chan = h*2 + ch; @@ -463,7 +463,7 @@ class QuadAppletManager : public HSApplication { if (draw_applets) { if (view_state == AUDIO_SETUP) { gfxHeader("Audio DSP Setup"); - OC::AudioDSP::DrawAudioSetup(); + // OC::AudioDSP::DrawAudioSetup(); draw_applets = false; } else if (view_state == CLOCK_SETUP) { @@ -519,7 +519,7 @@ class QuadAppletManager : public HSApplication { return; } if (view_state == AUDIO_SETUP) { - OC::AudioDSP::AudioSetupButtonAction(h); + // OC::AudioDSP::AudioSetupButtonAction(h); return; } @@ -642,7 +642,7 @@ class QuadAppletManager : public HSApplication { return; } if (view_state == AUDIO_SETUP) { - OC::AudioDSP::AudioMenuAdjust(h, event.value); + // OC::AudioDSP::AudioMenuAdjust(h, event.value); return; } @@ -1095,9 +1095,9 @@ class QuadAppletManager : public HSApplication { if (!quad_presets[i].is_valid()) gfxPrint(18, y, "(empty)"); else { - gfxPrint(18, y, quad_presets[i].GetApplet(LEFT_HEMISPHERE)->applet_name()); + gfxPrint(18, y, quad_presets[i].GetApplet(LEFT_HEMISPHERE).applet_name()); gfxPrint(", "); - gfxPrint(quad_presets[i].GetApplet(RIGHT_HEMISPHERE)->applet_name()); + gfxPrint(quad_presets[i].GetApplet(RIGHT_HEMISPHERE).applet_name()); } y += 10; diff --git a/software/src/AudioPassthrough.h b/software/src/AudioPassthrough.h new file mode 100644 index 000000000..755b534e9 --- /dev/null +++ b/software/src/AudioPassthrough.h @@ -0,0 +1,22 @@ +#pragma once +#include + +template +class AudioPassthrough : public AudioStream +{ +public: + AudioPassthrough() : AudioStream(NumChannels, inputQueueArray) {} + virtual void update(void) { + audio_block_t *block; + for(int i=0; i #include - // --- Audio-related functions accessible from elsewhere in the O_C codebase namespace OC { namespace AudioDSP { - enum ParamTarget { - AMP_LEVEL, - FILTER_CUTOFF, - FILTER_RESONANCE, - WAVEFOLD_MOD, - REVERB_LEVEL, - REVERB_SIZE, - REVERB_DAMP, - - TARGET_COUNT - }; - - enum ChannelMode { - PASSTHRU, - VCA_MODE, - LPG_MODE, - VCF_MODE, - WAVEFOLDER, - - MODE_COUNT - }; - extern const char * const mode_names[]; - - const int MAX_CV = 9216; // 6V - - extern uint8_t mods_enabled; // DAC outputs bitmask - extern ChannelMode mode[2]; // mode for each channel - extern int mod_map[2][TARGET_COUNT]; // CV modulation sources (as channel indexes for [inputs..outputs]) - extern float bias[2][TARGET_COUNT]; // baseline settings - extern uint8_t audio_cursor[2]; - static constexpr int CURSOR_MAX = 2; + // enum ParamTarget { + // AMP_LEVEL, + // FILTER_CUTOFF, + // FILTER_RESONANCE, + // WAVEFOLD_MOD, + // REVERB_LEVEL, + // REVERB_SIZE, + // REVERB_DAMP, + // + // TARGET_COUNT + // }; + // + // enum ChannelMode { + // PASSTHRU, + // VCA_MODE, + // LPG_MODE, + // VCF_MODE, + // WAVEFOLDER, + // + // MODE_COUNT + // }; + // extern const char * const mode_names[]; + // + // const int MAX_CV = 9216; // 6V + // + // extern uint8_t mods_enabled; // DAC outputs bitmask + // extern ChannelMode mode[2]; // mode for each channel + // extern int mod_map[2][TARGET_COUNT]; // CV modulation sources (as channel indexes for [inputs..outputs]) + // extern float bias[2][TARGET_COUNT]; // baseline settings + // extern uint8_t audio_cursor[2]; + // static constexpr int CURSOR_MAX = 2; + // + // void Init(); + // void Process(const int *values); + // void SwitchMode(int ch, ChannelMode newmode); + // void AudioMenuAdjust(int ch, int direction); + // void DrawAudioSetup(); + // + // static inline void AudioSetupButtonAction(int ch) { + // ++audio_cursor[ch] %= CURSOR_MAX; + // } + // const int MAX_CV = 9216; // 6V + + // uint8_t mods_enabled = 0xff; // bitmask for DAC outputs + + AudioStream &input_stream(); + AudioStream &output_stream(); void Init(); + void Process(const int *values); - void SwitchMode(int ch, ChannelMode newmode); - void AudioMenuAdjust(int ch, int direction); - void DrawAudioSetup(); - static inline void AudioSetupButtonAction(int ch) { - ++audio_cursor[ch] %= CURSOR_MAX; - } } // AudioDSP namespace } // OC namespace diff --git a/software/src/HSIOFrame.h b/software/src/HSIOFrame.h index fe451619a..d6b9cc64d 100644 --- a/software/src/HSIOFrame.h +++ b/software/src/HSIOFrame.h @@ -371,11 +371,6 @@ typedef struct IOFrame { OC::DAC::set_pitch_scaled(DAC_CHANNEL(i), outputs[i], 0); } if (autoMIDIOut) MIDIState.Send(outputs); - -#ifdef ARDUINO_TEENSY41 - // this relies on the inputs and outputs arrays being contiguous... - OC::AudioDSP::Process(inputs); -#endif } } IOFrame; diff --git a/software/src/HSUtils.cpp b/software/src/HSUtils.cpp index b64187dbc..de1ccd0f2 100644 --- a/software/src/HSUtils.cpp +++ b/software/src/HSUtils.cpp @@ -1,4 +1,6 @@ +#ifdef ARDUINO_TEENSY41 #include +#endif #include "OC_core.h" #include "HemisphereApplet.h" #include "HSUtils.h" @@ -209,41 +211,41 @@ namespace HS { } // namespace HS -#ifdef ARDUINO_TEENSY41 -void OC::AudioDSP::DrawAudioSetup() { - for (int ch = 0; ch < 2; ++ch) - { - int mod_target = AMP_LEVEL; - switch (mode[ch]) { - default: - case PASSTHRU: - case VCA_MODE: - case LPG_MODE: - break; - case VCF_MODE: - mod_target = FILTER_CUTOFF; - break; - case WAVEFOLDER: - mod_target = WAVEFOLD_MOD; - break; - } - - // Channel mode - gfxPrint(8 + 82*ch, 15, "Mode"); - gfxPrint(8 + 82*ch, 25, mode_names[ mode[ch] ]); - - // Modulation assignment - gfxPrint(8 + 82*ch, 35, "Map"); - gfxPrint(8 + 82*ch, 45, OC::Strings::cv_input_names_none[ mod_map[ch][mod_target] + 1 ] ); - - // cursor - gfxIcon(120*ch, 25 + audio_cursor[ch]*20, ch ? LEFT_ICON : RIGHT_ICON); - } - - // Reverb params (size, damping, level?) - // careful, because level is also feedback... -} -#endif +// #ifdef ARDUINO_TEENSY41 +// void OC::AudioDSP::DrawAudioSetup() { +// for (int ch = 0; ch < 2; ++ch) +// { +// int mod_target = AMP_LEVEL; +// switch (mode[ch]) { +// default: +// case PASSTHRU: +// case VCA_MODE: +// case LPG_MODE: +// break; +// case VCF_MODE: +// mod_target = FILTER_CUTOFF; +// break; +// case WAVEFOLDER: +// mod_target = WAVEFOLD_MOD; +// break; +// } +// +// // Channel mode +// gfxPrint(8 + 82*ch, 15, "Mode"); +// gfxPrint(8 + 82*ch, 25, mode_names[ mode[ch] ]); +// +// // Modulation assignment +// gfxPrint(8 + 82*ch, 35, "Map"); +// gfxPrint(8 + 82*ch, 45, OC::Strings::cv_input_names_none[ mod_map[ch][mod_target] + 1 ] ); +// +// // cursor +// gfxIcon(120*ch, 25 + audio_cursor[ch]*20, ch ? LEFT_ICON : RIGHT_ICON); +// } +// +// // Reverb params (size, damping, level?) +// // careful, because level is also feedback... +// } +// #endif //////////////// Hemisphere-like graphics methods for easy porting diff --git a/software/src/HemisphereApplet.h b/software/src/HemisphereApplet.h index 029314abd..2593eaec4 100644 --- a/software/src/HemisphereApplet.h +++ b/software/src/HemisphereApplet.h @@ -26,6 +26,7 @@ //////////////////////////////////////////////////////////////////////////////// #pragma once +#include #ifndef _HEM_APPLET_H_ #define _HEM_APPLET_H_ @@ -44,15 +45,32 @@ #include "HSUtils.h" #include "HSIOFrame.h" +#ifdef ARDUINO_TEENSY41 +#include +#endif + class HemisphereApplet; namespace HS { -typedef struct Applet { - const int id; - const uint8_t categories; - HemisphereApplet* instance[APPLET_SLOTS]; -} Applet; +class BaseApplet { +public: + BaseApplet(const int id, const uint8_t categories) : id(id), categories(categories) {} + const int id; + const uint8_t categories; + virtual HemisphereApplet & GetInstance(int slot) = 0; +}; + +template +class StaticApplet : public BaseApplet { +public: + StaticApplet() : BaseApplet(Id, Categories) {} + A instance[APPLET_SLOTS]; + + HemisphereApplet & GetInstance(int slot) override { + return instance[slot]; + } +}; extern IOFrame frame; @@ -60,6 +78,12 @@ extern IOFrame frame; using namespace HS; +enum AudioChannels { + None = 0, + Mono = 1, + Stereo = 2, +}; + class HemisphereApplet { public: static int cursor_countdown[APPLET_SLOTS]; @@ -77,6 +101,13 @@ class HemisphereApplet { virtual void OnButtonPress() { CursorToggle(); }; virtual void OnEncoderMove(int direction) = 0; + #ifdef ARDUINO_TEENSY41 + template using optional_ref = std::optional>; + virtual optional_ref InputStream() { return std::nullopt; } + virtual optional_ref OutputStream() { return std::nullopt; } + virtual AudioChannels NumAudioChannels() { return None; } + #endif + //void BaseStart(const HEM_SIDE hemisphere_); void BaseController(); void BaseView(bool full_screen = false); @@ -189,6 +220,10 @@ class HemisphereApplet { return (c <= ADC_CHANNEL_LAST) ? frame.inputs[c - 1] : frame.outputs[c - 1 - ADC_CHANNEL_LAST]; } + float InF(int ch) { + return static_cast(In(ch)) / HEMISPHERE_MAX_INPUT_CV; + } + // Apply small center detent to input, so it reads zero before a threshold int DetentedIn(int ch) { return (In(ch) > (HEMISPHERE_CENTER_CV + HEMISPHERE_CENTER_DETENT) || In(ch) < (HEMISPHERE_CENTER_CV - HEMISPHERE_CENTER_DETENT)) @@ -316,6 +351,25 @@ class HemisphereApplet { gfxPrint(num); } + void gfxPrint(int x, int y, float num, int digits) { + graphics.setPrintPos(x + gfx_offset, y); + gfxPrint(num, digits); + } + + void gfxPrint(float num, int digits) { + int i = static_cast(num); + float dec = num - i; + gfxPrint(i); + gfxPrint("."); + while (digits--) { + dec *= 10; + i = static_cast(dec); + gfxPrint(i); + dec -= i; + } + } + + template void gfxPrintfn(int x, int y, int n, const char *format, Args ...args) { graphics.setPrintPos(x + gfx_offset, y); @@ -405,6 +459,10 @@ class HemisphereApplet { }; virtual void SetHelp() = 0; + #ifdef ARDUINO_TEENSY41 + AudioConnection passThru{}; + #endif + /* Forces applet's Start() method to run the next time the applet is selected. This * allows an applet to start up the same way every time, regardless of previous state. */ diff --git a/software/src/HemisphereAudioApplet.h b/software/src/HemisphereAudioApplet.h new file mode 100644 index 000000000..f59d8a861 --- /dev/null +++ b/software/src/HemisphereAudioApplet.h @@ -0,0 +1,17 @@ +#pragma once + +#include "HemisphereApplet.h" +#include "AudioPassthrough.h" + +template +class HemisphereAudioApplet : public HemisphereApplet { +public: + optional_ref InputStream() override { return input_stream; } + optional_ref OutputStream() override { return output_stream; } + AudioChannels NumAudioChannels() override { return N; } + +protected: + AudioPassthrough input_stream; + AudioPassthrough output_stream; +}; + diff --git a/software/src/Main.cpp b/software/src/Main.cpp index f9cd8eab2..856d0d2f2 100644 --- a/software/src/Main.cpp +++ b/software/src/Main.cpp @@ -50,8 +50,9 @@ USBHub hub1(thisUSB); MIDIDevice usbHostMIDI(thisUSB); #if defined(ARDUINO_TEENSY41) -MIDI_CREATE_INSTANCE(HardwareSerial, Serial8, MIDI1); #include "AudioSetup.h" + +MIDI_CREATE_INSTANCE(HardwareSerial, Serial8, MIDI1); #endif #endif // __IMXRT1062__ diff --git a/software/src/applets/Delay.h b/software/src/applets/Delay.h new file mode 100644 index 000000000..62621af33 --- /dev/null +++ b/software/src/applets/Delay.h @@ -0,0 +1,58 @@ +#include "HemisphereAudioApplet.h" +#include + +class Delay : public HemisphereAudioApplet { +public: + const char *applet_name() { return "Delay"; } + void Start() { } + + void Controller() { + float newDelay = InF(0) * 1000.0f; + if (fabsf(newDelay - delayMs) > 2.0f) { + delayMs = newDelay; + delay.delay(0, delayMs); + } + feedback = InF(1); + wet = InF(2); + feedback_mixer.gain(FB_WET_CH, feedback); + wet_dry_mixer.gain(WD_WET_CH, wet); + wet_dry_mixer.gain(WD_DRY_CH, 1.0f - wet); + } + void View() { + gfxPrint(0, 15, "Time: "); + gfxPrint(delayMs, 3); + gfxPrint("ms"); + gfxPrint(0, 25, "FB: "); + gfxPrint(feedback, 3); + gfxPrint(0, 35, "Wet: "); + gfxPrint(wet, 3); + } + void OnButtonPress() {CursorToggle();} + void OnEncoderMove(int direction) {} + uint64_t OnDataRequest() {return 0;} + void OnDataReceive(uint64_t data) {} + +protected: + void SetHelp() {} + +private: + float delayMs = 100.0f; + float wet = 0.5f; + float feedback = 0.5f; + + const uint8_t FB_DRY_CH = 0; + const uint8_t FB_WET_CH = 1; + const uint8_t WD_DRY_CH = 0; + const uint8_t WD_WET_CH = 1; + + AudioEffectDelay delay; + AudioMixer4 feedback_mixer; + AudioConnection input_conn {input_stream, 0, feedback_mixer, FB_DRY_CH}; + AudioConnection delay_in{feedback_mixer, 0, delay, 0}; + AudioConnection feedback_out{delay, 0, feedback_mixer, FB_WET_CH}; + + AudioMixer4 wet_dry_mixer; + AudioConnection wet_conn{delay, 0, wet_dry_mixer, WD_WET_CH}; + AudioConnection dry_conn{input_stream, 0, wet_dry_mixer, WD_DRY_CH}; + AudioConnection output_conn{wet_dry_mixer, output_stream}; +}; diff --git a/software/src/applets/Freeverb.h b/software/src/applets/Freeverb.h new file mode 100644 index 000000000..7c06858ba --- /dev/null +++ b/software/src/applets/Freeverb.h @@ -0,0 +1,34 @@ +#include "HemisphereAudioApplet.h" +#include + +class Freeverb : public HemisphereAudioApplet { +public: + const char *applet_name() { return "Freeverb"; } + void Start() { } + void Controller() { + roomsize = InF(0); + damping = InF(1); + freeverb.roomsize(roomsize); + freeverb.damping(damping); + } + void View() { + gfxPrint(0, 15, "Roomsize"); + gfxPrint(0, 25, roomsize, 3); + gfxPrint(0, 35, "Damping"); + gfxPrint(0, 45, damping, 3); + } + void OnButtonPress() {CursorToggle();} + void OnEncoderMove(int direction) {} + uint64_t OnDataRequest() {return 0;} + void OnDataReceive(uint64_t data) {} +protected: + void SetHelp() {} +private: + int r = 0; + int d = 0; + float roomsize = 0.5f; + float damping = 0.5f; + AudioEffectFreeverb freeverb {}; + AudioConnection input_conn{input_stream, freeverb}; + AudioConnection output_conn{freeverb, output_stream}; +}; diff --git a/software/src/hemisphere_config.h b/software/src/hemisphere_config.h index 7ad07d2e6..bee82fc93 100644 --- a/software/src/hemisphere_config.h +++ b/software/src/hemisphere_config.h @@ -13,6 +13,7 @@ // * Category filtering is deprecated at 1.8, but I'm leaving the per-applet categorization // alone to avoid breaking forked codebases by other developers. +#include #ifdef ARDUINO_TEENSY41 // Teensy 4.1 can run four applets in Quadrasphere #define CREATE_APPLET(class_name) \ @@ -111,170 +112,118 @@ class_name class_name ## _instance[2] #include "applets/Voltage.h" #include "applets/hMIDIIn.h" #include "applets/hMIDIOut.h" - - -CREATE_APPLET(Cumulus); -CREATE_APPLET(ADSREG); -CREATE_APPLET(ADEG); -CREATE_APPLET(AttenuateOffset); -CREATE_APPLET(BugCrack); -CREATE_APPLET(DrumMap); -CREATE_APPLET(DualTM); -CREATE_APPLET(EbbAndLfo); -CREATE_APPLET(EuclidX); -CREATE_APPLET(hMIDIIn); -CREATE_APPLET(hMIDIOut); -CREATE_APPLET(Pigeons); -CREATE_APPLET(Stairs); -CREATE_APPLET(TB_3PO); -CREATE_APPLET(Voltage); - -CREATE_APPLET(Calibr8); -CREATE_APPLET(DivSeq); -CREATE_APPLET(PolyDiv); -CREATE_APPLET(Strum); - -CREATE_APPLET(ASR); -CREATE_APPLET(Binary); -CREATE_APPLET(BootsNCat); -CREATE_APPLET(Brancher); -CREATE_APPLET(Burst); -CREATE_APPLET(Button); -CREATE_APPLET(Calculate); -CREATE_APPLET(Carpeggio); -CREATE_APPLET(Chordinator); -CREATE_APPLET(ClockDivider); -CREATE_APPLET(ClockSkip); -CREATE_APPLET(Compare); -CREATE_APPLET(CVRecV2); -CREATE_APPLET(DualQuant); -CREATE_APPLET(EnigmaJr); -CREATE_APPLET(EnsOscKey); -CREATE_APPLET(EnvFollow); -CREATE_APPLET(GameOfLife); -CREATE_APPLET(GateDelay); -CREATE_APPLET(GatedVCA); -CREATE_APPLET(DrLoFi); -CREATE_APPLET(Logic); -CREATE_APPLET(LowerRenz); -CREATE_APPLET(Metronome); -CREATE_APPLET(MixerBal); -CREATE_APPLET(MultiScale); -CREATE_APPLET(Palimpsest); -CREATE_APPLET(ProbabilityDivider); -CREATE_APPLET(ProbabilityMelody); -CREATE_APPLET(ResetClock); -CREATE_APPLET(RndWalk); -CREATE_APPLET(RunglBook); -CREATE_APPLET(ScaleDuet); -CREATE_APPLET(Schmitt); -CREATE_APPLET(Scope); -CREATE_APPLET(SequenceX); -CREATE_APPLET(Seq32); -CREATE_APPLET(SeqPlay7); -CREATE_APPLET(ShiftGate); -CREATE_APPLET(ShiftReg); -CREATE_APPLET(Shredder); -CREATE_APPLET(Shuffle); -CREATE_APPLET(Slew); -CREATE_APPLET(Squanch); -CREATE_APPLET(Switch); -CREATE_APPLET(SwitchSeq); -CREATE_APPLET(TLNeuron); -CREATE_APPLET(Trending); -CREATE_APPLET(TrigSeq); -CREATE_APPLET(TrigSeq16); -CREATE_APPLET(Tuner); -CREATE_APPLET(VectorEG); -CREATE_APPLET(VectorLFO); -CREATE_APPLET(VectorMod); -CREATE_APPLET(VectorMorph); +#ifdef ARDUINO_TEENSY41 +#include "applets/Freeverb.h" +#include "applets/Delay.h" +#endif ////////////////// id cat class name -#define HEMISPHERE_APPLETS { \ - DECLARE_APPLET( 8, 0x01, ADSREG), \ - DECLARE_APPLET( 34, 0x01, ADEG), \ - DECLARE_APPLET( 47, 0x09, ASR), \ - DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ - DECLARE_APPLET( 41, 0x41, Binary), \ - DECLARE_APPLET( 55, 0x80, BootsNCat), \ - DECLARE_APPLET( 4, 0x14, Brancher), \ - DECLARE_APPLET( 51, 0x80, BugCrack), \ - DECLARE_APPLET( 31, 0x04, Burst), \ - DECLARE_APPLET( 65, 0x10, Button), \ - DECLARE_APPLET( 12, 0x10, Calculate),\ - DECLARE_APPLET( 88, 0x10, Calibr8), \ - DECLARE_APPLET( 32, 0x0a, Carpeggio), \ - DECLARE_APPLET( 64, 0x08, Chordinator), \ - DECLARE_APPLET( 6, 0x04, ClockDivider), \ - DECLARE_APPLET( 28, 0x04, ClockSkip), \ - DECLARE_APPLET( 30, 0x10, Compare), \ - DECLARE_APPLET( 74, 0x40, Cumulus), \ - DECLARE_APPLET( 24, 0x02, CVRecV2), \ - DECLARE_APPLET( 68, 0x06, DivSeq), \ - DECLARE_APPLET( 16, 0x80, DrLoFi), \ - DECLARE_APPLET( 57, 0x02, DrumMap), \ - DECLARE_APPLET( 9, 0x08, DualQuant), \ - DECLARE_APPLET( 18, 0x02, DualTM), \ - DECLARE_APPLET( 7, 0x01, EbbAndLfo), \ - DECLARE_APPLET( 45, 0x02, EnigmaJr), \ - DECLARE_APPLET( 35, 0x08, EnsOscKey), \ - DECLARE_APPLET( 42, 0x11, EnvFollow), \ - DECLARE_APPLET( 15, 0x02, EuclidX), \ - DECLARE_APPLET( 22, 0x01, GameOfLife), \ - DECLARE_APPLET( 29, 0x04, GateDelay), \ - DECLARE_APPLET( 17, 0x50, GatedVCA), \ - DECLARE_APPLET( 10, 0x44, Logic), \ - DECLARE_APPLET( 21, 0x01, LowerRenz), \ - DECLARE_APPLET( 50, 0x04, Metronome), \ - DECLARE_APPLET(150, 0x20, hMIDIIn), \ - DECLARE_APPLET( 27, 0x20, hMIDIOut), \ - DECLARE_APPLET( 33, 0x10, MixerBal), \ - DECLARE_APPLET( 73, 0x08, MultiScale), \ - DECLARE_APPLET( 20, 0x02, Palimpsest), \ - DECLARE_APPLET( 71, 0x02, Pigeons), \ - DECLARE_APPLET( 72, 0x06, PolyDiv), \ - DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ - DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ - DECLARE_APPLET( 70, 0x14, ResetClock), \ - DECLARE_APPLET( 69, 0x01, RndWalk), \ - DECLARE_APPLET( 44, 0x01, RunglBook), \ - DECLARE_APPLET( 26, 0x08, ScaleDuet), \ - DECLARE_APPLET( 40, 0x40, Schmitt), \ - DECLARE_APPLET( 23, 0x80, Scope), \ - DECLARE_APPLET( 75, 0x02, Seq32), \ - DECLARE_APPLET( 76, 0x02, SeqPlay7), \ - DECLARE_APPLET( 14, 0x02, SequenceX), \ - DECLARE_APPLET( 48, 0x45, ShiftGate), \ - DECLARE_APPLET( 77, 0x45, ShiftReg), \ - DECLARE_APPLET( 58, 0x01, Shredder), \ - DECLARE_APPLET( 36, 0x04, Shuffle), \ - DECLARE_APPLET( 19, 0x01, Slew), \ - DECLARE_APPLET( 46, 0x08, Squanch), \ - DECLARE_APPLET( 61, 0x01, Stairs), \ - DECLARE_APPLET( 74, 0x08, Strum), \ - DECLARE_APPLET( 3, 0x10, Switch), \ - DECLARE_APPLET( 38, 0x10, SwitchSeq), \ - DECLARE_APPLET( 60, 0x02, TB_3PO), \ - DECLARE_APPLET( 13, 0x40, TLNeuron), \ - DECLARE_APPLET( 37, 0x40, Trending), \ - DECLARE_APPLET( 11, 0x06, TrigSeq), \ - DECLARE_APPLET( 25, 0x06, TrigSeq16), \ - DECLARE_APPLET( 39, 0x80, Tuner), \ - DECLARE_APPLET( 52, 0x01, VectorEG), \ - DECLARE_APPLET( 49, 0x01, VectorLFO), \ - DECLARE_APPLET( 53, 0x01, VectorMod), \ - DECLARE_APPLET( 54, 0x01, VectorMorph), \ - DECLARE_APPLET( 43, 0x10, Voltage), \ -} /* DECLARE_APPLET(127, 0x80, DIAGNOSTIC), \ */ +// +// template +// Applet CreateApplet(int id, uint8_t categories) { +// HemisphereApplet* instances[APPLET_SLOTS]; +// for (int i = 0; i < APPLET_SLOTS; i++) { +// instances[i] = new A(); +// } +// return HS::Applet{ id, categories, instances }; +// } +static std::tuple< + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet, + StaticApplet + #ifdef ARDUINO_TEENSY41 + , StaticApplet + , StaticApplet + #endif +> applet_tuple; +template +std::array::value> tuple_to_array(Tuple &tuple, std::integer_sequence) { + return std::array::value>{ &std::get(tuple)... }; +} + +template +std::array::value> tuple_to_array(Tuple &tuple) { + return tuple_to_array(tuple, std::make_index_sequence::value>()); +} namespace HS { - static constexpr Applet available_applets[] = HEMISPHERE_APPLETS; - static constexpr int HEMISPHERE_AVAILABLE_APPLETS = ARRAY_SIZE(available_applets); + constexpr static int HEMISPHERE_AVAILABLE_APPLETS = std::tuple_size(); + static auto available_applets = tuple_to_array(applet_tuple); // TODO: needs to be larger than 64 bits... // TODO: also figure out where to store this @@ -286,11 +235,11 @@ namespace HS { hidden_applets = hidden_applets ^ (uint64_t(1) << index); } - constexpr int get_applet_index_by_id(const int& id) { + int get_applet_index_by_id(const int& id) { int index = 0; for (int i = 0; i < HEMISPHERE_AVAILABLE_APPLETS; i++) { - if (available_applets[i].id == id) index = i; + if (available_applets[i]->id == id) index = i; } return index; }