From c269b0a68acb76a35ea6456b49dffb055cf05218 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Wed, 15 Jun 2022 19:20:51 -0700 Subject: [PATCH 001/417] Add Chordinator applet --- software/o_c_REV/HEM_Chordinator.ino | 238 +++++++++++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 3 +- 2 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 software/o_c_REV/HEM_Chordinator.ino diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino new file mode 100644 index 000000000..8d624cc08 --- /dev/null +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -0,0 +1,238 @@ +// Copyright (c) 2021, Bryan Head +// +// Based on Braids Quantizer, Copyright 2015 Émilie Gillet. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "braids_quantizer.h" + +class Chordinator : public HemisphereApplet { +public: + const char *applet_name() { return "Chordnate"; } + + void Start() { + scale = 5; + root_quantizer.Init(); + chord_quantizer.Init(); + continuous[0] = 1; + continuous[1] = 1; + set_scale(scale); + update_chord_quantizer(); + } + + void Controller() { + ForEachChannel(ch) { + if (Clock(ch)) { + continuous[ch] = 0; + StartADCLag(ch); + } + } + + if (continuous[0] || EndOfADCLag(0)) { + chord_root_raw = In(0); + int32_t new_root_pitch = + root_quantizer.Process(chord_root_raw, root << 7, 0); + if (new_root_pitch != chord_root_pitch) { + update_chord_quantizer(); + chord_root_pitch = new_root_pitch; + } + Out(0, chord_root_pitch); + } + + if (continuous[1] || EndOfADCLag(1)) { + harm_pitch = + chord_quantizer.Process(In(1) + chord_root_pitch, root << 7, 0); + Out(1, harm_pitch); + } + } + + void View() { + gfxHeader(applet_name()); + + gfxPrint(0, 15, OC::scale_names_short[scale]); + if (cursor == 0) + gfxCursor(0, 23, 30); + gfxPrint(36, 15, OC::Strings::note_names_unpadded[root]); + if (cursor == 1) + gfxCursor(36, 23, 12); + + uint16_t mask = chord_mask; + for (size_t i = 0; i < active_scale.num_notes; i++) { + if (mask & 1) { + gfxRect(5 * i, 25, 4, 4); + } else { + gfxFrame(5 * i, 25, 4, 4); + } + if (cursor - 2 == i) { + gfxCursor(5 * i, 31, 4); + } + + mask >>= 1; + } + + size_t root_ix = note_ix(chord_root_pitch); + gfxBitmap(5 * root_ix, 35, 8, NOTE4_ICON); + gfxBitmap(5 * note_ix(harm_pitch), 45, 8, NOTE4_ICON); + } + + void OnButtonPress() { + if (cursor <= 2) { + selected = !selected; + } else { + chord_mask ^= 1 << (cursor - 2); + update_chord_quantizer(); + } + } + + void OnEncoderMove(int direction) { + if (selected) { + switch (cursor) { + case 0: + set_scale(scale + direction); + break; + case 1: + root = constrain(root + direction, 0, 11); + break; + default: + // shouldn't happen... + break; + } + update_chord_quantizer(); + } else { + cursor++; + if (cursor >= 2 + active_scale.num_notes) { + cursor = 0; + } + } + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation{0, 8}, scale); + Pack(data, PackLocation{8, 4}, root); + Pack(data, PackLocation{12, 16}, chord_mask); + return data; + } + + void OnDataReceive(uint64_t data) { + set_scale(Unpack(data, PackLocation{0, 8})); + root = Unpack(data, PackLocation{8, 4}); + chord_mask = Unpack(data, PackLocation{12, 16}); + update_chord_quantizer(); + } + +protected: + void SetHelp() { + help[HEMISPHERE_HELP_DIGITALS] = "Sample, Lrn/Sync"; + help[HEMISPHERE_HELP_CVS] = "Weight, Signal"; + help[HEMISPHERE_HELP_OUTS] = "Sampled out, Thru"; + help[HEMISPHERE_HELP_ENCODER] = "Scl,root,wght,lrn"; + } + +private: + braids::Quantizer root_quantizer; + braids::Quantizer chord_quantizer; + + size_t scale; // SEMI + int16_t root; + bool continuous[2]; + braids::Scale active_scale; + + size_t cursor = 0; + bool selected = false; + + // Leftmost is root, second to left is 2, etc. Defaulting here to basic triad. + uint16_t chord_mask = 0b10101; + + int16_t chord_root_raw = 0; + int16_t chord_root_pitch = 0; + + int16_t harm_pitch = 0; + + void update_chord_quantizer() { + size_t num_notes = active_scale.num_notes; + chord_root_pitch = root_quantizer.Process(chord_root_raw, root, 0); + size_t chord_root = note_ix(chord_root_pitch); + uint16_t mask = rotl32(chord_mask, num_notes, chord_root); + chord_quantizer.Configure(active_scale, mask); + chord_quantizer.Requantize(); + } + + size_t note_ix(int pitch) { + int rel_pitch = pitch % active_scale.span; + int d = active_scale.span; + size_t p = 0; + for (size_t i = 0; i < active_scale.num_notes; i++) { + int e = abs(rel_pitch - active_scale.notes[i]); + if (e < d) { + p = i; + d = e; + } + } + return p; + } + + void set_scale(size_t value) { + scale = value; + active_scale = OC::Scales::GetScale(scale); + root_quantizer.Configure(active_scale); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to Chordinator, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +Chordinator Chordinator_instance[2]; + +void Chordinator_Start(bool hemisphere) { + Chordinator_instance[hemisphere].BaseStart(hemisphere); +} + +void Chordinator_Controller(bool hemisphere, bool forwarding) { + Chordinator_instance[hemisphere].BaseController(forwarding); +} + +void Chordinator_View(bool hemisphere) { + Chordinator_instance[hemisphere].BaseView(); +} + +void Chordinator_OnButtonPress(bool hemisphere) { + Chordinator_instance[hemisphere].OnButtonPress(); +} + +void Chordinator_OnEncoderMove(bool hemisphere, int direction) { + Chordinator_instance[hemisphere].OnEncoderMove(direction); +} + +void Chordinator_ToggleHelpScreen(bool hemisphere) { + Chordinator_instance[hemisphere].HelpScreen(); +} + +uint64_t Chordinator_OnDataRequest(bool hemisphere) { + return Chordinator_instance[hemisphere].OnDataRequest(); +} + +void Chordinator_OnDataReceive(bool hemisphere, uint64_t data) { + Chordinator_instance[hemisphere].OnDataReceive(data); +} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 4001bf19d..230fd3d3e 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 54 +#define HEMISPHERE_AVAILABLE_APPLETS 55 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ @@ -28,6 +28,7 @@ DECLARE_APPLET( 32, 0x0a, Carpeggio), \ DECLARE_APPLET( 6, 0x04, ClockDivider), \ DECLARE_APPLET( 28, 0x04, ClockSkip), \ + DECLARE_APPLET( 62, 0x08, Chordinator), \ DECLARE_APPLET( 30, 0x10, Compare), \ DECLARE_APPLET( 24, 0x02, CVRecV2), \ DECLARE_APPLET( 55, 0x80, DrCrusher), \ From 8eee077244a49e844992a4737d40d9ebd3e4f634 Mon Sep 17 00:00:00 2001 From: mattlongest Date: Sun, 27 Mar 2022 10:33:52 -0700 Subject: [PATCH 002/417] AttenOff: Mix A & B * ADDED: mix param to optionally sum the A & B signals into the B output --- software/o_c_REV/HEM_AttenuateOffset.ino | 67 +++++++++++++++++------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index 0a847fdac..0922d3e4d 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -32,9 +32,19 @@ public: } void Controller() { + mix_final = mix || Gate(0); + int prevSignal = 0; + ForEachChannel(ch) { int signal = Proportion(level[ch], 63, In(ch)) + (offset[ch] * ATTENOFF_INCREMENTS); + if (ch == 1 && mix_final) { + signal = signal + prevSignal; + } + + // use the unconstrained signal for mixing + prevSignal = signal; + signal = constrain(signal, -HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV); Out(ch, signal); } @@ -46,44 +56,50 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + if (++cursor > 4) cursor = 0; ResetCursor(); } void OnEncoderMove(int direction) { - uint8_t ch = cursor / 2; - if (cursor == 0 || cursor == 2) { - // Change offset voltage - int min = -HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; - int max = HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; - offset[ch] = constrain(offset[ch] + direction, min, max); - } else { - // Change level percentage - level[ch] = constrain(level[ch] + direction, 0, 63); + if (cursor == 4) { + mix = (direction > 0); + } else + { + uint8_t ch = cursor / 2; + if (cursor == 0 || cursor == 2) { + // Change offset voltage + int min = -HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; + int max = HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; + offset[ch] = constrain(offset[ch] + direction, min, max); + } else { + // Change level percentage + level[ch] = constrain(level[ch] + direction, -63, 63); + } } - } uint64_t OnDataRequest() { uint64_t data = 0; Pack(data, PackLocation {0,9}, offset[0] + 256); Pack(data, PackLocation {10,9}, offset[1] + 256); - Pack(data, PackLocation {19,6}, level[0]); - Pack(data, PackLocation {25,6}, level[1]); + Pack(data, PackLocation {19,7}, level[0] + 64); + Pack(data, PackLocation {26,7}, level[1] + 64); + Pack(data, PackLocation {34,1}, mix); return data; } void OnDataReceive(uint64_t data) { offset[0] = Unpack(data, PackLocation {0,9}) - 256; offset[1] = Unpack(data, PackLocation {10,9}) - 256; - level[0] = Unpack(data, PackLocation {19,6}); - level[1] = Unpack(data, PackLocation {25,6}); + level[0] = Unpack(data, PackLocation {19,7}) - 64; + level[1] = Unpack(data, PackLocation {26,7}) - 64; + mix = Unpack(data, PackLocation {34,1}); } protected: void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = ""; + help[HEMISPHERE_HELP_DIGITALS] = "1=Mix A&B"; help[HEMISPHERE_HELP_CVS] = "CV Inputs 1,2"; help[HEMISPHERE_HELP_OUTS] = "Outputs A,B"; help[HEMISPHERE_HELP_ENCODER] = "Offset V / Level %"; @@ -94,6 +110,8 @@ private: int cursor; int level[2]; int offset[2]; + bool mix = false; + bool mix_final = false; void DrawInterface() { ForEachChannel(ch) @@ -105,9 +123,20 @@ private: gfxPrint("%"); } - int ch = cursor / 2; - if (cursor == 0 or cursor == 2) gfxCursor(13, 23 + (ch * 20), 36); - else gfxCursor(13, 33 + (ch * 20), 36); + if (mix_final) { + gfxLine(3, 24, 3, 31); + gfxIcon(0, 25, DOWN_BTN_ICON); + } + + if (cursor == 4) { + if (CursorBlink()) { + gfxFrame(0, 24, 9, 10); + } + } else{ + int ch = cursor / 2; + if (cursor == 0 or cursor == 2) gfxCursor(13, 23 + (ch * 20), 36); + else gfxCursor(13, 33 + (ch * 20), 36); + } } }; From 5c84c055f36206fb0444c4b9f92ba89f498d38c6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 11 Jul 2022 08:17:42 -0400 Subject: [PATCH 003/417] Fix off-by-one regression to re-enable Voltage --- software/o_c_REV/hemisphere_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 8337b6cc9..8d6dedfe0 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 54 +#define HEMISPHERE_AVAILABLE_APPLETS 55 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ From 05f5c1316969d9a4f38ed309f56ecc627a7076b3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 11 Jul 2022 09:07:26 -0400 Subject: [PATCH 004/417] Update Button applet to use 64-bit types --- software/o_c_REV/HEM_Button.ino | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/HEM_Button.ino b/software/o_c_REV/HEM_Button.ino index 0d25bb031..e36a0ca15 100644 --- a/software/o_c_REV/HEM_Button.ino +++ b/software/o_c_REV/HEM_Button.ino @@ -66,14 +66,14 @@ public: /* No state data for this applet */ - uint32_t OnDataRequest() { - uint32_t data = 0; + uint64_t OnDataRequest() { + uint64_t data = 0; return data; } /* No state data for this applet */ - void OnDataReceive(uint32_t data) { + void OnDataReceive(uint64_t data) { return; } @@ -118,5 +118,5 @@ void Button_View(bool hemisphere) {Button_instance[hemisphere].BaseView();} void Button_OnButtonPress(bool hemisphere) {Button_instance[hemisphere].OnButtonPress();} void Button_OnEncoderMove(bool hemisphere, int direction) {Button_instance[hemisphere].OnEncoderMove(direction);} void Button_ToggleHelpScreen(bool hemisphere) {Button_instance[hemisphere].HelpScreen();} -uint32_t Button_OnDataRequest(bool hemisphere) {return Button_instance[hemisphere].OnDataRequest();} -void Button_OnDataReceive(bool hemisphere, uint32_t data) {Button_instance[hemisphere].OnDataReceive(data);} +uint64_t Button_OnDataRequest(bool hemisphere) {return Button_instance[hemisphere].OnDataRequest();} +void Button_OnDataReceive(bool hemisphere, uint64_t data) {Button_instance[hemisphere].OnDataReceive(data);} From a24f0c3d9c4566c5301b41c71cdb94e1c3d0eb8e Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Sun, 7 Aug 2022 06:59:28 -0700 Subject: [PATCH 005/417] TrigSeq: Add reset offset --- software/o_c_REV/HEM_TrigSeq.ino | 33 ++++++++++++++++++++++++++---- software/o_c_REV/HEM_TrigSeq16.ino | 33 ++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HEM_TrigSeq.ino b/software/o_c_REV/HEM_TrigSeq.ino index 06738f852..f51a8b8f3 100644 --- a/software/o_c_REV/HEM_TrigSeq.ino +++ b/software/o_c_REV/HEM_TrigSeq.ino @@ -38,11 +38,13 @@ public: void Controller() { if (Clock(0) || Clock(1)) { bool swap = In(0) >= HEMISPHERE_3V_CV; + ForEachChannel(ch) { if (Clock(1) || step[ch] >= end_step[ch]) step[ch] = -1; step[ch]++; - if ((pattern[ch] >> step[ch]) & 0x01) ClockOut(swap ? (1 - ch) : ch); + active_step[ch] = Step(ch); + if ((pattern[ch] >> active_step[ch]) & 0x01) ClockOut(swap ? (1 - ch) : ch); } } @@ -106,18 +108,37 @@ protected: help[HEMISPHERE_HELP_ENCODER] = "T=Set P=Select"; // "------------------" <-- Size Guide } - + private: int step[2]; // Current step of each channel + int active_step[2]; uint8_t pattern[2]; int end_step[2]; int cursor; // 0=ch1 low, 1=ch1 hi, 2=c1 end_step, 3=ch2 low, 4=ch3 hi, 5=ch2 end_step - + + inline int Length(int ch) const { + return end_step[ch] + 1; + } + + int Step(int ch) { + int s = step[ch] + Offset(ch); + s %= Length(ch); + return s; + } + + int Offset(int ch) { + int offset = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, end_step[ch]); + if (offset < 0) offset += Length(ch); + offset %= Length(ch); + return offset; + } + void DrawDisplay() { ForEachChannel(ch) { int this_cursor = cursor - (3 * ch); int x = 10 + (31 * ch); + int offset = Offset(ch); // Draw the steps for this channel bool stop = 0; // Stop displaying when end_step is reached @@ -135,10 +156,14 @@ private: } } - if (s == step[ch]) { + if (s == active_step[ch]) { gfxLine(x + 4, y, x + 10, y); } + if (s == offset) { + gfxFrame(x - 4, y - 4, 9, 9); + } + // Draw the end_step cursor if (s == end_step[ch]) { if (this_cursor == 2 && CursorBlink()) { diff --git a/software/o_c_REV/HEM_TrigSeq16.ino b/software/o_c_REV/HEM_TrigSeq16.ino index 6f59b06d0..c6bda2bb0 100644 --- a/software/o_c_REV/HEM_TrigSeq16.ino +++ b/software/o_c_REV/HEM_TrigSeq16.ino @@ -39,12 +39,13 @@ public: if (Clock(0) || Clock(1)) { if (Clock(1) || step >= end_step) step = -1; step++; + active_step = Step(); bool swap = In(0) >= HEMISPHERE_3V_CV; - if (step < 8) { - if ((pattern[0] >> step) & 0x01) ClockOut(swap ? 1 : 0); + if (active_step < 8) { + if ((pattern[0] >> active_step) & 0x01) ClockOut(swap ? 1 : 0); else ClockOut(swap ? 0 : 1); } else { - if ((pattern[1] >> (step - 8)) & 0x01) ClockOut(swap ? 1 : 0); + if ((pattern[1] >> (active_step - 8)) & 0x01) ClockOut(swap ? 1 : 0); else ClockOut(swap ? 0 : 1); } } @@ -109,13 +110,33 @@ protected: private: int step; // Current step + int active_step; uint8_t pattern[2]; int end_step; int cursor; // 0=ch1 low, 1=ch1 hi, 2=ch2 low, 3=ch3 hi, 4=end_step - + + int Offset() { + int offset = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, end_step); + if (offset < 0) offset += Length(); + offset %= Length(); + return offset; + } + + inline int Length() const { + return end_step + 1; + } + + int Step() { + int s = step + Offset(); + s %= Length(); + return s; + } + void DrawDisplay() { bool stop = 0; // Stop displaying when end_step is reached + int offset = Offset(); + ForEachChannel(ch) { int x = 10 + (31 * ch); @@ -139,6 +160,10 @@ private: gfxLine(x + 4, y, x + 10, y); } + if (s == offset) { + gfxFrame(x - 4, y - 4, 9, 9); + } + // Draw the end_step cursor if (s + (ch * 8) == end_step) { if (cursor == 4) { From d7a3550b1fe063aa49d78b15c9f52c69dbc1a517 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Sun, 7 Aug 2022 07:00:38 -0700 Subject: [PATCH 006/417] wip EbbAndLfo --- software/o_c_REV/HEM_EbbAndLfo.ino | 80 ++++++++++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 3 +- software/o_c_REV/resources/tideslite.h | 52 +++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 software/o_c_REV/HEM_EbbAndLfo.ino create mode 100644 software/o_c_REV/resources/tideslite.h diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino new file mode 100644 index 000000000..11f02c6ef --- /dev/null +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -0,0 +1,80 @@ +#include "resources/tideslite.h" + +class EbbAndLfo : public HemisphereApplet { +public: + const char *applet_name() { return "Ebb & LFO"; } + + void Start() { phase = 0; } + + void Controller() { + uint32_t phase_increment = ComputePhaseIncrement(In(0)); + phase += phase_increment; + Out(0, Proportion(phase >> 16, 1 << 16, HEMISPHERE_MAX_CV)); + } + + void View() { + + } + + void SetHelp() { + + } + + void OnButtonPress() {} + + void OnEncoderMove(int direction) {} + + uint64_t OnDataRequest() { return 0; } + + void OnDataReceive(uint64_t data) {} + +private: + int16_t pitch; + int slope; + int att_shape; + int dec_shape; + int smoothness; + + int phase; +}; +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to EbbAndLfo, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +EbbAndLfo EbbAndLfo_instance[2]; + +void EbbAndLfo_Start(bool hemisphere) { + EbbAndLfo_instance[hemisphere].BaseStart(hemisphere); +} + +void EbbAndLfo_Controller(bool hemisphere, bool forwarding) { + EbbAndLfo_instance[hemisphere].BaseController(forwarding); +} + +void EbbAndLfo_View(bool hemisphere) { + EbbAndLfo_instance[hemisphere].BaseView(); +} + +void EbbAndLfo_OnButtonPress(bool hemisphere) { + EbbAndLfo_instance[hemisphere].OnButtonPress(); +} + +void EbbAndLfo_OnEncoderMove(bool hemisphere, int direction) { + EbbAndLfo_instance[hemisphere].OnEncoderMove(direction); +} + +void EbbAndLfo_ToggleHelpScreen(bool hemisphere) { + EbbAndLfo_instance[hemisphere].HelpScreen(); +} + +uint64_t EbbAndLfo_OnDataRequest(bool hemisphere) { + return EbbAndLfo_instance[hemisphere].OnDataRequest(); +} + +void EbbAndLfo_OnDataReceive(bool hemisphere, uint64_t data) { + EbbAndLfo_instance[hemisphere].OnDataReceive(data); +} \ No newline at end of file diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 230fd3d3e..99bcf7e9c 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 55 +#define HEMISPHERE_AVAILABLE_APPLETS 57 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ @@ -36,6 +36,7 @@ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ + DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ DECLARE_APPLET( 29, 0x04, GateDelay), \ DECLARE_APPLET( 17, 0x50, GatedVCA), \ DECLARE_APPLET( 16, 0x80, LoFiPCM), \ diff --git a/software/o_c_REV/resources/tideslite.h b/software/o_c_REV/resources/tideslite.h new file mode 100644 index 000000000..b9afb58d8 --- /dev/null +++ b/software/o_c_REV/resources/tideslite.h @@ -0,0 +1,52 @@ + +const int16_t kOctave = 12 * 128; + +/* +import numpy as np + +sample_rate = 16666 + +excursion = float(1 << 32) + +a4_midi = 69 +a4_pitch = 440.0 +notes = np.arange(0 * 128.0, 12 * 128.0 + 16, 16) / 128.0 +pitches = a4_pitch * 2 **((notes - a4_midi) / 12) +increments = excursion / sample_rate * pitches + +increments.astype(int) +*/ +const uint32_t lut_increments[] = { + 2106971, 2122239, 2137618, 2153108, 2168710, 2184425, 2200255, 2216199, + 2232258, 2248434, 2264727, 2281138, 2297668, 2314318, 2331089, 2347981, + 2364995, 2382133, 2399395, 2416782, 2434295, 2451935, 2469702, 2487599, + 2505625, 2523782, 2542070, 2560491, 2579046, 2597734, 2616559, 2635519, + 2654617, 2673854, 2693230, 2712746, 2732404, 2752204, 2772147, 2792235, + 2812469, 2832850, 2853377, 2874054, 2894881, 2915858, 2936988, 2958270, + 2979707, 3001300, 3023048, 3044954, 3067019, 3089244, 3111630, 3134178, + 3156890, 3179766, 3202808, 3226017, 3249394, 3272940, 3296657, 3320546, + 3344608, 3368845, 3393257, 3417846, 3442613, 3467560, 3492687, 3517996, + 3543489, 3569167, 3595031, 3621082, 3647321, 3673751, 3700373, 3727187, + 3754196, 3781401, 3808802, 3836402, 3864202, 3892204, 3920409, 3948818, + 3977432, 4006254, 4035285, 4064527, 4093980, 4123647, 4153528, 4183626, + 4213943 +}; + +uint32_t ComputePhaseIncrement(int16_t pitch) { + int16_t num_shifts = 0; + while (pitch < 0) { + pitch += kOctave; + --num_shifts; + } + while (pitch >= kOctave) { + pitch -= kOctave; + ++num_shifts; + } + // Lookup phase increment + uint32_t a = lut_increments[pitch >> 4]; + uint32_t b = lut_increments[(pitch >> 4) + 1]; + uint32_t phase_increment = a + ((b - a) * (pitch & 0xf) >> 4); + // Compensate for downsampling + return num_shifts >= 0 ? phase_increment << num_shifts + : phase_increment >> -num_shifts; +} From 968fa3b130cf701c594e765276880d1379a4093a Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Sun, 7 Aug 2022 14:50:53 -0700 Subject: [PATCH 007/417] wip EbbAndLfo: Add voct tracking and freq display --- software/o_c_REV/HEM_EbbAndLfo.ino | 69 +++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 11f02c6ef..99a7d6433 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -7,22 +7,33 @@ public: void Start() { phase = 0; } void Controller() { - uint32_t phase_increment = ComputePhaseIncrement(In(0)); + uint32_t phase_increment = ComputePhaseIncrement(pitch + In(0)); phase += phase_increment; - Out(0, Proportion(phase >> 16, 1 << 16, HEMISPHERE_MAX_CV)); + Out(0, Proportion(phase >> 16, 0xffff, HEMISPHERE_MAX_CV)); + + if (knob_accel > (1 << 8)) knob_accel--; } - - void View() { + void View() { + gfxHeader(applet_name()); + gfxPrintFreq(0, 18, pitch); + gfxPrint(0, 36, knob_accel); } - + void SetHelp() { } void OnButtonPress() {} - void OnEncoderMove(int direction) {} + void OnEncoderMove(int direction) { + uint32_t old_pi = ComputePhaseIncrement(pitch); + pitch += (knob_accel >> 8) * direction; + while (ComputePhaseIncrement(pitch) == old_pi) { + pitch += direction; + } + if (knob_accel < (1 << 12)) knob_accel <<= 1; + } uint64_t OnDataRequest() { return 0; } @@ -35,7 +46,51 @@ private: int dec_shape; int smoothness; - int phase; + int knob_accel = 1 << 8; + + uint32_t phase; + + void gfxPrintFreq(int x, int y, int16_t pitch) { + uint32_t num = ComputePhaseIncrement(pitch); + uint32_t denom = 0xffffffff / 16666; + bool swap = num < denom; + if (swap) { + uint32_t t = num; + num = denom; + denom = t; + } + int int_part = num / denom; + int digits = 0; + if (int_part < 10) digits = 1; + else if (int_part < 100) digits = 2; + else if (int_part < 1000) digits = 3; + else digits = 4; + + gfxPos(x, y); + gfxPrint(int_part); + gfxPrint("."); + + num %= denom; + while (digits < 4) { + num *= 10; + gfxPrint(num / denom); + num %= denom; + digits++; + } + if (swap) { + gfxPrint("s"); + } else { + gfxPrint("Hz"); + } + // int oom = 1; + // int i = -3; + // while (oom * phase_inc < 1000 * (uint64_t) 0xffffffff) { + // oom *= 10; + // i++; + // } + // gfxCursor(x, y); + // gfxPrint(x, y, phase_inc * oom / 0xffffffff); + } }; //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Applet Functions From fac01b0e202f342a968ec552c5b5614ba21b8120 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Wed, 10 Aug 2022 08:49:04 -0700 Subject: [PATCH 008/417] EbbAndLfo: Get slope and shape working --- software/o_c_REV/HEM_EbbAndLfo.ino | 85 +++++++++++++++----- software/o_c_REV/resources/tideslite.h | 107 ++++++++++++++++++++++++- 2 files changed, 170 insertions(+), 22 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 99a7d6433..4a80630f0 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -9,30 +9,71 @@ public: void Controller() { uint32_t phase_increment = ComputePhaseIncrement(pitch + In(0)); phase += phase_increment; - Out(0, Proportion(phase >> 16, 0xffff, HEMISPHERE_MAX_CV)); + int slope_cv = In(1) * 32768 / HEMISPHERE_3V_CV; + int s = constrain(slope * 65535 / 127 + slope_cv, 0, 65535); + GenerateSample(s, att_shape * 65535 / 127, dec_shape * 65535 / 127, phase, + sample); - if (knob_accel > (1 << 8)) knob_accel--; + Out(0, Proportion(sample.unipolar, 65535, HEMISPHERE_MAX_CV)); + Out(1, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); + + if (knob_accel > (1 << 8)) + knob_accel--; } void View() { gfxHeader(applet_name()); - gfxPrintFreq(0, 18, pitch); - gfxPrint(0, 36, knob_accel); - } - void SetHelp() { + gfxPrintFreq(0, 15, pitch); + if (cursor == 0) + gfxCursor(0, 23, 32); + + gfxPrint(0, 25, slope); + if (cursor == 1) + gfxCursor(0, 33, 32); + gfxPrint(0, 35, att_shape); + if (cursor == 2) + gfxCursor(0, 43, 32); + + gfxPrint(0, 45, dec_shape); + if (cursor == 3) + gfxCursor(0, 53, 32); } - void OnButtonPress() {} + void SetHelp() {} + + void OnButtonPress() { + cursor++; + cursor %= 4; + } void OnEncoderMove(int direction) { - uint32_t old_pi = ComputePhaseIncrement(pitch); - pitch += (knob_accel >> 8) * direction; - while (ComputePhaseIncrement(pitch) == old_pi) { - pitch += direction; + switch (cursor) { + case 0: { + uint32_t old_pi = ComputePhaseIncrement(pitch); + pitch += (knob_accel >> 8) * direction; + while (ComputePhaseIncrement(pitch) == old_pi) { + pitch += direction; + } + break; + } + case 1: { + // slope += (knob_accel >> 4) * direction; + slope = constrain(slope + direction, 0, 127); + break; + } + case 2: { + att_shape = constrain(att_shape + direction, 0, 127); + break; + } + case 3: { + dec_shape = constrain(dec_shape + direction, 0, 127); + break; + } } - if (knob_accel < (1 << 12)) knob_accel <<= 1; + if (knob_accel < (1 << 13)) + knob_accel <<= 1; } uint64_t OnDataRequest() { return 0; } @@ -40,11 +81,13 @@ public: void OnDataReceive(uint64_t data) {} private: + int cursor; int16_t pitch; - int slope; - int att_shape; - int dec_shape; + int slope = 64; + int att_shape = 64; + int dec_shape = 64; int smoothness; + TidesLiteSample sample; int knob_accel = 1 << 8; @@ -61,10 +104,14 @@ private: } int int_part = num / denom; int digits = 0; - if (int_part < 10) digits = 1; - else if (int_part < 100) digits = 2; - else if (int_part < 1000) digits = 3; - else digits = 4; + if (int_part < 10) + digits = 1; + else if (int_part < 100) + digits = 2; + else if (int_part < 1000) + digits = 3; + else + digits = 4; gfxPos(x, y); gfxPrint(int_part); diff --git a/software/o_c_REV/resources/tideslite.h b/software/o_c_REV/resources/tideslite.h index b9afb58d8..c6d2d8707 100644 --- a/software/o_c_REV/resources/tideslite.h +++ b/software/o_c_REV/resources/tideslite.h @@ -1,5 +1,17 @@ +#include + +#define LUT_INCREMENTS_SIZE 97 const int16_t kOctave = 12 * 128; +const uint16_t kSlopeBits = 12; + +enum TidesLiteFlagBitMask { FLAG_EOA = 1, FLAG_EOR = 2 }; + +struct TidesLiteSample { + uint16_t unipolar; + int16_t bipolar; + uint8_t flags; +}; /* import numpy as np @@ -29,8 +41,46 @@ const uint32_t lut_increments[] = { 3543489, 3569167, 3595031, 3621082, 3647321, 3673751, 3700373, 3727187, 3754196, 3781401, 3808802, 3836402, 3864202, 3892204, 3920409, 3948818, 3977432, 4006254, 4035285, 4064527, 4093980, 4123647, 4153528, 4183626, - 4213943 -}; + 4213943}; + +/* +size_t lower_bound(uint32_t* first, uint32_t* last, const uint32_t target)) { + size_t len = last - first; + + if (first == last) { + return &first; + } + uint32_t* mid = first + (last - first) / 2; + if (mid == &target) { + return mid; + } + if (target < mid) +} + +int16_t ComputePitch(uint32_t phase_increment) { + uint32_t first = lut_increments[0]; + uint32_t last = lut_increments[LUT_INCREMENTS_SIZE - 2]; + int16_t pitch = 0; + + if (phase_increment == 0) { + phase_increment = 1; + } + + while (phase_increment > last) { + phase_increment >>= 1; + pitch += kOctave; + } + while (phase_increment < first) { + phase_increment <<= 1; + pitch -= kOctave; + } + pitch += (std::lower_bound( + lut_increments, + lut_increments + LUT_INCREMENTS_SIZE, + phase_increment) - lut_increments) << 4; + return pitch; +} +*/ uint32_t ComputePhaseIncrement(int16_t pitch) { int16_t num_shifts = 0; @@ -48,5 +98,56 @@ uint32_t ComputePhaseIncrement(int16_t pitch) { uint32_t phase_increment = a + ((b - a) * (pitch & 0xf) >> 4); // Compensate for downsampling return num_shifts >= 0 ? phase_increment << num_shifts - : phase_increment >> -num_shifts; + : phase_increment >> -num_shifts; +} + +const uint64_t max_phase = 0xffffffff; +const uint64_t max_16 = 0xffff; +uint32_t WarpPhase(uint32_t phase, uint16_t curve) { + int32_t c = curve - 32767; + bool flip = c < 0; + if (flip) + phase = max_phase - phase; + uint64_t a = (uint64_t) 128 * c * c; + phase = (max_16 + a / max_16) * phase / ( (max_phase + a / max_16 * phase / max_16) / max_16); + if (flip) + phase = max_phase - phase; + return phase; +} + +uint16_t ShapePhase(uint16_t phase, uint16_t attack_curve, + uint16_t decay_curve) { + return phase < (1UL << 15) + ? WarpPhase(phase << 17, attack_curve) >> 16 + : WarpPhase((0xffff - phase) << 17, decay_curve) >> 16; +} + +void GenerateSample(uint16_t slope, uint16_t att_shape, uint16_t dec_shape, + uint32_t phase, TidesLiteSample &sample) { + uint32_t eoa = slope << 16; + // uint32_t skewed_phase = phase; + slope = slope ? slope : 1; + uint32_t decay_factor = (32768 << kSlopeBits) / slope; + uint32_t attack_factor = (32768 << kSlopeBits) / (65536 - slope); + + uint32_t skewed_phase = phase; + if (phase <= eoa) { + skewed_phase = (phase >> kSlopeBits) * decay_factor; + } else { + skewed_phase = ((phase - eoa) >> kSlopeBits) * attack_factor; + skewed_phase += 1L << 31; + } + + sample.unipolar = ShapePhase(skewed_phase >> 16, att_shape, dec_shape); + sample.bipolar = ShapePhase(skewed_phase >> 15, att_shape, dec_shape) >> 1; + if (skewed_phase >= (1UL << 31)) { + sample.bipolar = -sample.bipolar; + } + + sample.flags = 0; + if (phase >= eoa) { + sample.flags |= FLAG_EOA; + } else { + sample.flags |= FLAG_EOR; + } } From 7c0118e94f46e817fcc2c09c0884266f8a1ce7d4 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Sat, 13 Aug 2022 08:18:02 -0700 Subject: [PATCH 009/417] EbbAndLfo: Add fold and output params --- software/o_c_REV/HEM_EbbAndLfo.ino | 118 ++++++++++++++++++---- software/o_c_REV/resources/plot.py | 9 ++ software/o_c_REV/resources/tideslite.cpp | 24 +++++ software/o_c_REV/resources/tideslite.h | 122 +++++++++++++++++++++-- 4 files changed, 243 insertions(+), 30 deletions(-) create mode 100644 software/o_c_REV/resources/plot.py create mode 100644 software/o_c_REV/resources/tideslite.cpp diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 4a80630f0..a9cd158a2 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -11,11 +11,11 @@ public: phase += phase_increment; int slope_cv = In(1) * 32768 / HEMISPHERE_3V_CV; int s = constrain(slope * 65535 / 127 + slope_cv, 0, 65535); - GenerateSample(s, att_shape * 65535 / 127, dec_shape * 65535 / 127, phase, - sample); + ProcessSample(s, att_shape * 65535 / 127, dec_shape * 65535 / 127, + fold * 32767 / 127, phase, sample); - Out(0, Proportion(sample.unipolar, 65535, HEMISPHERE_MAX_CV)); - Out(1, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); + output(0, (Output) out_a); + output(1, (Output) out_b); if (knob_accel > (1 << 8)) knob_accel--; @@ -23,29 +23,60 @@ public: void View() { gfxHeader(applet_name()); + /* - gfxPrintFreq(0, 15, pitch); - if (cursor == 0) - gfxCursor(0, 23, 32); - - gfxPrint(0, 25, slope); - if (cursor == 1) - gfxCursor(0, 33, 32); - - gfxPrint(0, 35, att_shape); - if (cursor == 2) - gfxCursor(0, 43, 32); + int last = 50; + for (int i = 1; i < 64; i++) { + ProcessSample(slope * 65535 / 127, att_shape * 65535 / 127, + dec_shape * 65535 / 127, fold * 32767 / 127, + 0xffffffff / 64 * i, disp_sample); + int next = 50 - disp_sample.unipolar * 35 / 65535; + gfxLine(i - 1, last, i, next); + last = next; + // gfxPixel(i, 50 - disp_sample.unipolar * 35 / 65536); + } + uint32_t p = phase / (0xffffffff / 64); + gfxLine(p, 15, p, 50); + */ - gfxPrint(0, 45, dec_shape); - if (cursor == 3) - gfxCursor(0, 53, 32); + switch (cursor) { + case 0: + // gfxPrint(0, 55, "Frq:"); + gfxPos(0, 55); + gfxPrintFreq(pitch); + break; + case 1: + gfxPrint(0, 55, "Slope: "); + gfxPrint(slope); + break; + case 2: + gfxPrint(0, 55, "Att: "); + gfxPrint(att_shape); + break; + case 3: + gfxPrint(0, 55, "Dec: "); + gfxPrint(dec_shape); + break; + case 4: + gfxPrint(0, 55, "Fold: "); + gfxPrint(fold); + break; + case 5: + gfxPrint(0, 55, "OutA: "); + gfxPrint(out_labels[out_a]); + break; + case 6: + gfxPrint(0, 55, "OutB: "); + gfxPrint(out_labels[out_b]); + break; + } } void SetHelp() {} void OnButtonPress() { cursor++; - cursor %= 4; + cursor %= 7; } void OnEncoderMove(int direction) { @@ -71,6 +102,18 @@ public: dec_shape = constrain(dec_shape + direction, 0, 127); break; } + case 4: { + fold = constrain(fold + direction, 0, 127); + break; + } + case 5: { + out_a += direction; + out_a %= 4; + } + case 6: { + out_b += direction; + out_b %= 4; + } } if (knob_accel < (1 << 13)) knob_accel <<= 1; @@ -81,19 +124,51 @@ public: void OnDataReceive(uint64_t data) {} private: + + enum Output { + UNIPOLAR, + BIPOLAR, + EOA, + EOR, + }; + int cursor; int16_t pitch; int slope = 64; int att_shape = 64; int dec_shape = 64; - int smoothness; + int fold = 0; + const char* out_labels[4] = {"Uni", "Bi", "High", "Low"}; + uint8_t out_a = UNIPOLAR; + uint8_t out_b = BIPOLAR; + TidesLiteSample disp_sample; TidesLiteSample sample; int knob_accel = 1 << 8; uint32_t phase; - void gfxPrintFreq(int x, int y, int16_t pitch) { + void output(int ch, Output out) { + switch (out) { + case UNIPOLAR: + Out(ch, Proportion(sample.unipolar, 65535, HEMISPHERE_MAX_CV)); + break; + case BIPOLAR: + Out(ch, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); + break; + case EOA: + //GateOut(ch, sample.flags & FLAG_EOA); + GateOut(ch, phase <= 0x7fffffff); + break; + case EOR: + //GateOut(ch, sample.flags & FLAG_EOR); + GateOut(ch, phase > 0x7fffffff); + break; + } + } + + + void gfxPrintFreq(int16_t pitch) { uint32_t num = ComputePhaseIncrement(pitch); uint32_t denom = 0xffffffff / 16666; bool swap = num < denom; @@ -113,7 +188,6 @@ private: else digits = 4; - gfxPos(x, y); gfxPrint(int_part); gfxPrint("."); diff --git a/software/o_c_REV/resources/plot.py b/software/o_c_REV/resources/plot.py new file mode 100644 index 000000000..12769cc3c --- /dev/null +++ b/software/o_c_REV/resources/plot.py @@ -0,0 +1,9 @@ +import sys +import pandas as pd +import matplotlib.pyplot as plt + +if __name__ == "__main__": + df = pd.read_csv(sys.stdin) + df.plot() + plt.show() + diff --git a/software/o_c_REV/resources/tideslite.cpp b/software/o_c_REV/resources/tideslite.cpp new file mode 100644 index 000000000..953c7f2c1 --- /dev/null +++ b/software/o_c_REV/resources/tideslite.cpp @@ -0,0 +1,24 @@ +#include "tideslite.h" +#include +#include + +int main(int argc, char **argv) { + TidesLiteSample sample; + uint64_t samples = atol(argv[1]); + int16_t pitch = atoi(argv[2]); + uint32_t phase_inc = ComputePhaseIncrement(pitch); + uint16_t slope = atoi(argv[3]); + uint16_t shape = atoi(argv[4]); + int16_t fold = atoi(argv[5]); + + uint32_t phase = 0; + printf("unipolar,bipolar,high,low\n"); + for (uint64_t i = 0; i < samples; i++) { + ProcessSample(slope, shape, shape, fold, phase, sample); + printf("%d,%d,%d,%d\n", sample.unipolar, sample.bipolar, + (sample.flags & FLAG_EOA) ? 65535 : 0, + (sample.flags & FLAG_EOR) ? 65535 : 0); + phase += phase_inc; + } + return 0; +} \ No newline at end of file diff --git a/software/o_c_REV/resources/tideslite.h b/software/o_c_REV/resources/tideslite.h index c6d2d8707..af5c888db 100644 --- a/software/o_c_REV/resources/tideslite.h +++ b/software/o_c_REV/resources/tideslite.h @@ -43,6 +43,85 @@ const uint32_t lut_increments[] = { 3977432, 4006254, 4035285, 4064527, 4093980, 4123647, 4153528, 4183626, 4213943}; +/* +import numpy + +WAVESHAPER_SIZE = 256 + +x = numpy.arange(0, WAVESHAPER_SIZE + 1) / (WAVESHAPER_SIZE / 2.0) - 1.0 +x[-1] = x[-2] +sine = numpy.sin(8 * numpy.pi * x) +window = numpy.exp(-x * x * 4) ** 2 +bipolar_fold = sine * window + numpy.arctan(3 * x) * (1 - window) +bipolar_fold /= numpy.abs(bipolar_fold).max() +(numpy.round(32767) * bipolar_fold).astype(int) + +x = numpy.arange(0, WAVESHAPER_SIZE + 1) / float(WAVESHAPER_SIZE) +x[-1] = x[-2] +sine = numpy.sin(8 * numpy.pi * x) +window = numpy.exp(-x * x * 4) ** 2 +unipolar_fold = (0.5 * sine + 2 * x) * window + numpy.arctan(4 * x) * (1 - +window) unipolar_fold /= numpy.abs(unipolar_fold).max() (numpy.round(32767 * +unipolar_fold)).astype(int) +*/ + +const int16_t wav_bipolar_fold[] = { + -32767, -32701, -32634, -32566, -32496, -32425, -32353, -32279, -32204, + -32129, -32053, -31977, -31901, -31825, -31749, -31674, -31600, -31526, + -31453, -31379, -31305, -31229, -31150, -31068, -30980, -30886, -30783, + -30671, -30546, -30409, -30258, -30091, -29910, -29713, -29503, -29281, + -29049, -28812, -28572, -28335, -28106, -27890, -27693, -27520, -27374, + -27261, -27180, -27133, -27117, -27129, -27160, -27203, -27246, -27274, + -27271, -27222, -27108, -26911, -26617, -26212, -25683, -25026, -24239, + -23327, -22300, -21176, -19978, -18738, -17489, -16273, -15131, -14106, + -13240, -12572, -12135, -11953, -12041, -12403, -13028, -13893, -14960, + -16179, -17485, -18805, -20059, -21161, -22027, -22574, -22730, -22433, + -21639, -20321, -18479, -16132, -13326, -10133, -6644, -2973, 752, + 4393, 7806, 10850, 13391, 15310, 16512, 16925, 16512, 15267, + 13223, 10446, 7040, 3136, -1106, -5510, -9885, -14039, -17784, + -20947, -23377, -24951, -25584, -25230, -23886, -21592, -18430, -14523, + -10025, -5117, 0, 5117, 10025, 14523, 18430, 21592, 23886, + 25230, 25584, 24951, 23377, 20947, 17784, 14039, 9885, 5510, + 1106, -3136, -7040, -10446, -13223, -15267, -16512, -16925, -16512, + -15310, -13391, -10850, -7806, -4393, -752, 2973, 6644, 10133, + 13326, 16132, 18479, 20321, 21639, 22433, 22730, 22574, 22027, + 21161, 20059, 18805, 17485, 16179, 14960, 13893, 13028, 12403, + 12041, 11953, 12135, 12572, 13240, 14106, 15131, 16273, 17489, + 18738, 19978, 21176, 22300, 23327, 24239, 25026, 25683, 26212, + 26617, 26911, 27108, 27222, 27271, 27274, 27246, 27203, 27160, + 27129, 27117, 27133, 27180, 27261, 27374, 27520, 27693, 27890, + 28106, 28335, 28572, 28812, 29049, 29281, 29503, 29713, 29910, + 30091, 30258, 30409, 30546, 30671, 30783, 30886, 30980, 31068, + 31150, 31229, 31305, 31379, 31453, 31526, 31600, 31674, 31749, + 31825, 31901, 31977, 32053, 32129, 32204, 32279, 32353, 32425, + 32496, 32566, 32634, 32701, 32701}; + +const int16_t wav_unipolar_fold[] = { + 0, 1405, 2797, 4165, 5496, 6779, 8003, 9157, 10232, 11219, 12110, + 12900, 13581, 14151, 14606, 14944, 15166, 15271, 15261, 15141, 14915, 14588, + 14166, 13659, 13073, 12419, 11706, 10944, 10146, 9321, 8481, 7638, 6803, + 5986, 5198, 4451, 3752, 3111, 2537, 2036, 1616, 1281, 1035, 883, + 827, 868, 1006, 1240, 1568, 1988, 2495, 3085, 3753, 4493, 5297, + 6158, 7069, 8021, 9007, 10017, 11043, 12077, 13110, 14133, 15141, 16124, + 17076, 17990, 18861, 19683, 20453, 21166, 21818, 22408, 22935, 23396, 23793, + 24125, 24394, 24601, 24749, 24841, 24881, 24871, 24816, 24722, 24592, 24431, + 24245, 24039, 23817, 23585, 23347, 23109, 22874, 22648, 22433, 22234, 22054, + 21896, 21762, 21654, 21574, 21523, 21502, 21511, 21551, 21621, 21720, 21847, + 22002, 22182, 22386, 22612, 22858, 23120, 23398, 23688, 23988, 24296, 24608, + 24923, 25238, 25552, 25861, 26164, 26459, 26744, 27019, 27281, 27529, 27764, + 27983, 28187, 28375, 28547, 28703, 28843, 28968, 29079, 29174, 29256, 29326, + 29383, 29430, 29466, 29494, 29514, 29527, 29534, 29536, 29535, 29531, 29525, + 29518, 29511, 29505, 29500, 29497, 29496, 29498, 29503, 29512, 29524, 29541, + 29561, 29586, 29614, 29646, 29682, 29722, 29765, 29811, 29860, 29912, 29966, + 30022, 30080, 30139, 30199, 30260, 30321, 30382, 30444, 30505, 30565, 30624, + 30683, 30740, 30796, 30850, 30903, 30955, 31005, 31053, 31099, 31144, 31187, + 31228, 31268, 31307, 31343, 31379, 31413, 31446, 31478, 31509, 31539, 31568, + 31597, 31624, 31652, 31678, 31705, 31731, 31757, 31782, 31808, 31833, 31858, + 31884, 31909, 31934, 31960, 31985, 32011, 32036, 32062, 32088, 32114, 32140, + 32166, 32192, 32218, 32244, 32270, 32296, 32322, 32348, 32374, 32399, 32425, + 32451, 32476, 32501, 32526, 32551, 32576, 32601, 32625, 32649, 32673, 32697, + 32720, 32744, 32767, 32767}; + /* size_t lower_bound(uint32_t* first, uint32_t* last, const uint32_t target)) { size_t len = last - first; @@ -82,6 +161,18 @@ int16_t ComputePitch(uint32_t phase_increment) { } */ +inline int16_t Interpolate1022(const int16_t *table, uint32_t phase) { + int32_t a = table[phase >> 22]; + int32_t b = table[(phase >> 22) + 1]; + return a + ((b - a) * static_cast((phase >> 6) & 0xffff) >> 16); +} + +inline int16_t Interpolate824(const int16_t *table, uint32_t phase) { + int32_t a = table[phase >> 24]; + int32_t b = table[(phase >> 24) + 1]; + return a + ((b - a) * static_cast((phase >> 8) & 0xffff) >> 16); +} + uint32_t ComputePhaseIncrement(int16_t pitch) { int16_t num_shifts = 0; while (pitch < 0) { @@ -107,9 +198,10 @@ uint32_t WarpPhase(uint32_t phase, uint16_t curve) { int32_t c = curve - 32767; bool flip = c < 0; if (flip) - phase = max_phase - phase; - uint64_t a = (uint64_t) 128 * c * c; - phase = (max_16 + a / max_16) * phase / ( (max_phase + a / max_16 * phase / max_16) / max_16); + phase = max_phase - phase; + uint64_t a = (uint64_t)128 * c * c; + phase = (max_16 + a / max_16) * phase / + ((max_phase + a / max_16 * phase / max_16) / max_16); if (flip) phase = max_phase - phase; return phase; @@ -122,8 +214,8 @@ uint16_t ShapePhase(uint16_t phase, uint16_t attack_curve, : WarpPhase((0xffff - phase) << 17, decay_curve) >> 16; } -void GenerateSample(uint16_t slope, uint16_t att_shape, uint16_t dec_shape, - uint32_t phase, TidesLiteSample &sample) { +void ProcessSample(uint16_t slope, uint16_t att_shape, uint16_t dec_shape, + int16_t fold, uint32_t phase, TidesLiteSample &sample) { uint32_t eoa = slope << 16; // uint32_t skewed_phase = phase; slope = slope ? slope : 1; @@ -145,9 +237,23 @@ void GenerateSample(uint16_t slope, uint16_t att_shape, uint16_t dec_shape, } sample.flags = 0; - if (phase >= eoa) { - sample.flags |= FLAG_EOA; - } else { + if (phase <= eoa) { sample.flags |= FLAG_EOR; + } else { + sample.flags |= FLAG_EOA; + } + + if (fold > 0) { + int32_t wf_gain = 2048; + wf_gain += fold * (32767 - 1024) >> 14; + int32_t wf_balance = fold; + + int32_t original = sample.unipolar; + int32_t folded = Interpolate824(wav_unipolar_fold, original * wf_gain) << 1; + sample.unipolar = original + ((folded - original) * wf_balance >> 15); + + original = sample.bipolar; + folded = Interpolate824(wav_bipolar_fold, original * wf_gain + (1UL << 31)); + sample.bipolar = original + ((folded - original) * wf_balance >> 15); } } From 68e81b154ac28eff244d64f621d73b8e3582cfda Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Sat, 13 Aug 2022 10:34:11 -0700 Subject: [PATCH 010/417] EbbAndLfo: Don't retrig hi and lo during cycle --- software/o_c_REV/HEM_EbbAndLfo.ino | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index a9cd158a2..d58f8da34 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -13,6 +13,11 @@ public: int s = constrain(slope * 65535 / 127 + slope_cv, 0, 65535); ProcessSample(s, att_shape * 65535 / 127, dec_shape * 65535 / 127, fold * 32767 / 127, phase, sample); + if (phase < phase_increment) { + eoa_reached = false; + } else { + eoa_reached = eoa_reached || (sample.flags & FLAG_EOA); + } output(0, (Output) out_a); output(1, (Output) out_b); @@ -23,7 +28,6 @@ public: void View() { gfxHeader(applet_name()); - /* int last = 50; for (int i = 1; i < 64; i++) { @@ -37,7 +41,6 @@ public: } uint32_t p = phase / (0xffffffff / 64); gfxLine(p, 15, p, 50); - */ switch (cursor) { case 0: @@ -143,6 +146,7 @@ private: uint8_t out_b = BIPOLAR; TidesLiteSample disp_sample; TidesLiteSample sample; + bool eoa_reached = false; int knob_accel = 1 << 8; @@ -157,12 +161,10 @@ private: Out(ch, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); break; case EOA: - //GateOut(ch, sample.flags & FLAG_EOA); - GateOut(ch, phase <= 0x7fffffff); + GateOut(ch, eoa_reached); break; case EOR: - //GateOut(ch, sample.flags & FLAG_EOR); - GateOut(ch, phase > 0x7fffffff); + GateOut(ch, !eoa_reached); break; } } From e96f0e4f60d330ca1860e265c45364a5b0e1aa93 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 21 Aug 2022 23:32:26 -0400 Subject: [PATCH 011/417] Fix reset offset display for TrigSeq16 --- software/o_c_REV/HEM_TrigSeq16.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_TrigSeq16.ino b/software/o_c_REV/HEM_TrigSeq16.ino index c6bda2bb0..5f3500a1e 100644 --- a/software/o_c_REV/HEM_TrigSeq16.ino +++ b/software/o_c_REV/HEM_TrigSeq16.ino @@ -160,7 +160,7 @@ private: gfxLine(x + 4, y, x + 10, y); } - if (s == offset) { + if (s + (ch * 8) == offset) { gfxFrame(x - 4, y - 4, 9, 9); } From e782b4daf0b47904bd8df04a53ee9cc5241e16f2 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 22 Aug 2022 08:21:45 -0400 Subject: [PATCH 012/417] Replace AnnularFusion with Euclid, ported from adegani Disabled a few applets I'm not using to make it all fit --- software/o_c_REV/HEM_Euclid.ino | 280 +++++++++++++++++++++++++++ software/o_c_REV/HSicons.h | 4 + software/o_c_REV/hemisphere_config.h | 28 +-- 3 files changed, 299 insertions(+), 13 deletions(-) create mode 100644 software/o_c_REV/HEM_Euclid.ino diff --git a/software/o_c_REV/HEM_Euclid.ino b/software/o_c_REV/HEM_Euclid.ino new file mode 100644 index 000000000..788b0ff92 --- /dev/null +++ b/software/o_c_REV/HEM_Euclid.ino @@ -0,0 +1,280 @@ +// Copyright (c) 2022, Alessio Degani +// Copyright (c) 2018, Jason Justian +// +// Bjorklund pattern filter, Copyright (c) 2016 Tim Churches +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include +#include "OC_core.h" +#include "bjorklund.h" + +class Euclid : public HemisphereApplet { +public: + + const char* applet_name() { + return "Euclid"; + } + + void Start() { + + ForEachChannel(ch) + { + length[ch] = 4; + beats[ch] = 1; + pattern[ch] = EuclideanPattern(length[ch] - 1, beats[ch], 0);; + } + step = 0; + } + + void Controller() { + if (Clock(1)) step = 0; // Reset + + // Advance both rings + if (Clock(0) && !Gate(1)) { + + int cv_data[2]; + + cv_data[0] = DetentedIn(0); + cv_data[1] = DetentedIn(1); + ForEachChannel(ch) + { + actual_length[ch] = length[ch]; + actual_rotation[ch] = rotation[ch]; + actual_beats[ch] = beats[ch]; + for (uint8_t ch_src = 0; ch_src <= 1; ch_src++) { + if (cv_dest[ch_src] == 0+3*ch) { + actual_length[ch] = (uint8_t)constrain(actual_length[ch] + Proportion(cv_data[ch_src], HEMISPHERE_MAX_CV, 29), 3, 32); + // if (actual_beats[ch] > actual_length[ch]) actual_beats[ch] = actual_length[ch]; + // if (actual_rotation[ch] > actual_length[ch]-1) actual_rotation[ch] = actual_length[ch]-1; + } + if (cv_dest[ch_src] == 1+3*ch) { + actual_beats[ch] = (uint8_t)constrain(actual_beats[ch] + Proportion(cv_data[ch_src], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); + } + if (cv_dest[ch_src] == 2+3*ch) { + actual_rotation[ch] = (uint8_t)constrain(actual_rotation[ch] + Proportion(cv_data[ch_src], HEMISPHERE_MAX_CV, actual_length[ch]), 0, actual_length[ch]-1); + } + } + + // Store the pattern for display + pattern[ch] = EuclideanPattern(actual_length[ch]-1, actual_beats[ch], actual_rotation[ch]); + int sb = step % actual_length[ch]; + if ((pattern[ch] >> sb) & 0x01) { + ClockOut(ch); + } + } + + // Plan for the thing to run forever and ever + if (++step >= actual_length[0] * actual_length[1]) step = 0; + } + } + + void View() { + gfxHeader(applet_name()); + DrawEditor(); + } + + void OnButtonPress() { + if (++cursor > 7) cursor = 0; + ResetCursor(); + } + + void OnEncoderMove(int direction) { + int ch = cursor < 4 ? 0 : 1; + int f = cursor - (ch * 4); // Cursor function + switch(f) { + case 0: + length[ch] = constrain(length[ch] + direction, 3, 32); + if (beats[ch] > length[ch]) beats[ch] = length[ch]; + if (rotation[ch] > length[ch]-1) rotation[ch] = length[ch]-1; + // SetDisplayPositions(ch, 24 - (8 * ch)); + break; + case 1: + beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); + break; + case 2: + rotation[ch] = constrain(rotation[ch] + direction, 0, length[ch]-1); + break; + case 3: + cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, 5); + break; + default: + break; + } + actual_length[ch] = length[ch]; + actual_rotation[ch] = rotation[ch]; + actual_beats[ch] = beats[ch]; + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation {0,4}, length[0] - 1); + Pack(data, PackLocation {4,4}, beats[0] - 1); + Pack(data, PackLocation {8,4}, rotation[0]); + Pack(data, PackLocation {12,4}, length[1] - 1); + Pack(data, PackLocation {16,4}, beats[1] - 1); + Pack(data, PackLocation {20,4}, rotation[1]); + Pack(data, PackLocation {24,4}, cv_dest[0]); + Pack(data, PackLocation {28,4}, cv_dest[1]); + return data; + } + + void OnDataReceive(uint64_t data) { + ForEachChannel(ch) { + length[ch] = Unpack(data, PackLocation {0+12*ch,4}) + 1; + beats[ch] = Unpack(data, PackLocation {4+12*ch,4}) + 1; + rotation[ch] = Unpack(data, PackLocation {8+12*ch,4}); + cv_dest[ch] = Unpack(data, PackLocation {24+4*ch,4}); + actual_length[ch] = length[ch]; + actual_beats[ch] = beats[ch]; + actual_rotation[ch] = rotation[ch]; + } + // length[0] = Unpack(data, PackLocation {0,4}) + 1; + // beats[0] = Unpack(data, PackLocation {4,4}) + 1; + // rotation[0] = Unpack(data, PackLocation {8,4}); + // length[1] = Unpack(data, PackLocation {12,4}) + 1; + // beats[1] = Unpack(data, PackLocation {16,4}) + 1; + // rotation[1] = Unpack(data, PackLocation {20,4}); + // cv_dest[0] = Unpack(data, PackLocation {24,4}); + // cv_dest[1] = Unpack(data, PackLocation {28,4}); + // actual_length[0] = length[0]; + // actual_beats[0] = beats[0]; + // actual_rotation[0] = rotation[0]; + // actual_length[1] = length[1]; + // actual_beats[1] = beats[1]; + // actual_rotation[1] = rotation[1]; + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; + help[HEMISPHERE_HELP_CVS] = "Rotate 1=Ch1 2=Ch2"; + help[HEMISPHERE_HELP_OUTS] = "Clock A=Ch1 B=Ch2"; + help[HEMISPHERE_HELP_ENCODER] = "Length/Hits Ch1,2"; + // "------------------" <-- Size Guide + } + +private: + int step; + uint8_t cursor = 0; // Ch1: 0=Length, 1=Hits, 2=Rotation; Ch2: 3=Length, 4=Hits, 5=Rotation + // AFStepCoord disp_coord[2][32]; + uint32_t pattern[2]; + + // Settings + uint8_t length[2]; + uint8_t beats[2]; + uint8_t rotation[2]; + uint8_t actual_length[2]; + uint8_t actual_beats[2]; + uint8_t actual_rotation[2]; + uint8_t cv_dest[2]; + int cv_data[2]; + + void DrawEditor() { + int f = 0; + + ForEachChannel(ch) + { + f = cursor - (ch * 4); // Cursor function + + // Length cursor + gfxPrint(12 + 24*ch + pad(10, actual_length[ch]), 15, actual_length[ch]); + if (f == 0) gfxCursor(13 + 24*ch, 23, 12); + for (int ch_dest = 0; ch_dest < 2; ch_dest++){ + if (cv_dest[ch_dest] == 0+3*ch) gfxBitmap(26 + 24*ch, 14+ch, 3, ch_dest?SUB_TWO:SUP_ONE); + } + + // Beats cursor + gfxPrint(12 + 24*ch + pad(10, actual_beats[ch]), 25, actual_beats[ch]); + if (f == 1) gfxCursor(13 + 24*ch, 33, 12); + for (int ch_dest = 0; ch_dest < 2; ch_dest++){ + if (cv_dest[ch_dest] == 1+3*ch) gfxBitmap(26 + 24*ch, 24+ch, 3, ch_dest?SUB_TWO:SUP_ONE); + } + + // Rotation cursor + gfxPrint(12 + 24*ch + pad(10, actual_rotation[ch]), 35, actual_rotation[ch]); + if (f == 2) gfxCursor(13 + 24*ch, 43, 12); + for (int ch_dest = 0; ch_dest < 2; ch_dest++){ + if (cv_dest[ch_dest] == 2+3*ch) gfxBitmap(26 + 24*ch, 34+ch, 3, ch_dest?SUB_TWO:SUP_ONE); + } + + // CV destination + gfxPrint(12 + 24*ch + pad(10, cv_dest[ch]), 45, cv_dest[ch]); + if (f == 3) gfxCursor(13 + 24*ch, 53, 12); + + // int curr_cv = 0; + // for (int ch_src = 0; ch_src < 2; ch_src++) { + // if (cv_dest[ch] == 0+3*ch_src) curr_cv = length[ch_src] + cv_data[ch]; + // if (cv_dest[ch] == 1+3*ch_src) curr_cv = beats[ch_src] + cv_data[ch]; + // if (cv_dest[ch] == 2+3*ch_src) curr_cv = rotation[ch_src] + cv_data[ch]; + // } + // gfxPrint(12 + 24*ch + pad(10, pattern[ch]), 55, pattern[ch]); + } + + gfxBitmap(1, 15, 8, LEFT_RIGHT_ICON); + gfxBitmap(1, 25, 8, X_NOTE_ICON); + gfxBitmap(1, 35, 8, LOOP_ICON); + gfxBitmap(1, 45, 8, CV_ICON); + gfxLine(34, 15, 34, 55); + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to Euclid, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +Euclid Euclid_instance[2]; + +void Euclid_Start(bool hemisphere) { + Euclid_instance[hemisphere].BaseStart(hemisphere); +} + +void Euclid_Controller(bool hemisphere, bool forwarding) { + Euclid_instance[hemisphere].BaseController(forwarding); +} + +void Euclid_View(bool hemisphere) { + Euclid_instance[hemisphere].BaseView(); +} + +void Euclid_OnButtonPress(bool hemisphere) { + Euclid_instance[hemisphere].OnButtonPress(); +} + +void Euclid_OnEncoderMove(bool hemisphere, int direction) { + Euclid_instance[hemisphere].OnEncoderMove(direction); +} + +void Euclid_ToggleHelpScreen(bool hemisphere) { + Euclid_instance[hemisphere].HelpScreen(); +} + +uint64_t Euclid_OnDataRequest(bool hemisphere) { + return Euclid_instance[hemisphere].OnDataRequest(); +} + +void Euclid_OnDataReceive(bool hemisphere, uint64_t data) { + Euclid_instance[hemisphere].OnDataReceive(data); +} diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index 5242609f9..826171cb6 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -59,6 +59,10 @@ const uint8_t LEFT_RIGHT_ICON[8] = {0x10,0x38,0x7c,0x10,0x10,0x7c,0x38,0x10}; const uint8_t SEGMENT_ICON[8] = {0xc0,0xc0,0x20,0x10,0x08,0x06,0x06,0x00}; const uint8_t WAVEFORM_ICON[8] = {0x10,0x08,0x04,0x08,0x10,0x20,0x10,0x08}; +// Superscript and subscript 1 and 2 +const uint8_t SUP_ONE[3] = {0x0a,0x0f,0x08}; +const uint8_t SUB_TWO[3] = {0x90,0xd0,0xa0}; + // Units const uint8_t HERTZ_ICON[8] = {0xfe,0x10,0x10,0xfe,0x00,0xc8,0xa8,0x98}; diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 060ae8269..d8c90f20f 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,13 +11,13 @@ // * Category filtering is deprecated at 1.8, but I'm leaving the per-applet categorization // alone to avoid breaking forked codebases by other developers. -#define HEMISPHERE_AVAILABLE_APPLETS 57 +#define HEMISPHERE_AVAILABLE_APPLETS 49 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ DECLARE_APPLET( 8, 0x01, ADSREG), \ DECLARE_APPLET( 34, 0x01, ADEG), \ - DECLARE_APPLET( 15, 0x02, AnnularFusion), \ + DECLARE_APPLET( 15, 0x02, Euclid), \ DECLARE_APPLET( 47, 0x09, ASR), \ DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ DECLARE_APPLET( 51, 0x80, BugCrack), \ @@ -28,30 +28,22 @@ DECLARE_APPLET( 32, 0x0a, Carpeggio), \ DECLARE_APPLET( 6, 0x04, ClockDivider), \ DECLARE_APPLET( 28, 0x04, ClockSkip), \ - DECLARE_APPLET( 62, 0x08, Chordinator), \ DECLARE_APPLET( 30, 0x10, Compare), \ DECLARE_APPLET( 24, 0x02, CVRecV2), \ DECLARE_APPLET( 55, 0x80, DrCrusher), \ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ - DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ DECLARE_APPLET( 29, 0x04, GateDelay), \ - DECLARE_APPLET( 17, 0x50, GatedVCA), \ DECLARE_APPLET( 16, 0x80, LoFiPCM), \ 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( 20, 0x02, Palimpsest), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ - DECLARE_APPLET( 44, 0x01, RunglBook), \ DECLARE_APPLET( 26, 0x08, ScaleDuet), \ - DECLARE_APPLET( 40, 0x40, Schmitt), \ DECLARE_APPLET( 23, 0x80, Scope), \ DECLARE_APPLET( 14, 0x02, Sequence5), \ DECLARE_APPLET( 48, 0x45, ShiftGate), \ @@ -60,12 +52,10 @@ DECLARE_APPLET( 36, 0x04, Shuffle), \ DECLARE_APPLET( 7, 0x01, SkewedLFO), \ DECLARE_APPLET( 19, 0x01, Slew), \ - DECLARE_APPLET( 46, 0x08, Squanch), \ DECLARE_APPLET( 61, 0x01, Stairs), \ DECLARE_APPLET( 3, 0x10, Switch), \ 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), \ @@ -75,4 +65,16 @@ DECLARE_APPLET( 54, 0x01, VectorMorph), \ DECLARE_APPLET( 43, 0x10, Voltage), \ } -/* DECLARE_APPLET(127, 0x80, DIAGNOSTIC), \ */ +/* + DECLARE_APPLET(127, 0x80, DIAGNOSTIC), \ + DECLARE_APPLET(150, 0x20, hMIDIIn), \ + DECLARE_APPLET( 27, 0x20, hMIDIOut), \ + DECLARE_APPLET( 62, 0x08, Chordinator), \ + DECLARE_APPLET( 45, 0x02, EnigmaJr), \ + DECLARE_APPLET( 17, 0x50, GatedVCA), \ + DECLARE_APPLET( 20, 0x02, Palimpsest), \ + DECLARE_APPLET( 44, 0x01, RunglBook), \ + DECLARE_APPLET( 40, 0x40, Schmitt), \ + DECLARE_APPLET( 46, 0x08, Squanch), \ + DECLARE_APPLET( 37, 0x40, Trending), \ +*/ From d711f8c379b0969f3c911a125a9faf52c25c4aa0 Mon Sep 17 00:00:00 2001 From: Alessio Degani Date: Sun, 21 Aug 2022 00:13:50 +0200 Subject: [PATCH 013/417] fix: Bjorklund algo fails rotation when num_steps == num_beats --- software/o_c_REV/bjorklund.cpp | 503 +++++++++++++++++---------------- 1 file changed, 252 insertions(+), 251 deletions(-) diff --git a/software/o_c_REV/bjorklund.cpp b/software/o_c_REV/bjorklund.cpp index 4fd3d12f6..af7383b80 100644 --- a/software/o_c_REV/bjorklund.cpp +++ b/software/o_c_REV/bjorklund.cpp @@ -57,14 +57,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00 // 31 beats 00 // 32 beats 00 -0 , 1 , 3 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 3 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 3 step patterns // 0 beats 000 // 1 beats 100 @@ -99,14 +99,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000 // 31 beats 000 // 32 beats 000 -0 , 1 , 3 , 7 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 3 , 7 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 4 step patterns // 0 beats 0000 // 1 beats 1000 @@ -141,14 +141,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000 // 31 beats 0000 // 32 beats 0000 -0 , 1 , 5 , 7 , -15 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 5 , 7 , +15 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 5 step patterns // 0 beats 00000 // 1 beats 10000 @@ -183,14 +183,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000 // 31 beats 00000 // 32 beats 00000 -0 , 1 , 5 , 21 , -15 , 31 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 5 , 21 , +15 , 31 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 6 step patterns // 0 beats 000000 // 1 beats 100000 @@ -225,14 +225,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000 // 31 beats 000000 // 32 beats 000000 -0 , 1 , 9 , 21 , -27 , 31 , 63 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 9 , 21 , +27 , 31 , 63 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 7 step patterns // 0 beats 0000000 // 1 beats 1000000 @@ -267,14 +267,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000 // 31 beats 0000000 // 32 beats 0000000 -0 , 1 , 9 , 21 , -85 , 91 , 63 , 127 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 9 , 21 , +85 , 91 , 63 , 127 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 8 step patterns // 0 beats 00000000 // 1 beats 10000000 @@ -309,14 +309,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000 // 31 beats 00000000 // 32 beats 00000000 -0 , 1 , 17 , 73 , -85 , 109 , 119 , 127 , -255 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 17 , 73 , +85 , 109 , 119 , 127 , +255 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 9 step patterns // 0 beats 000000000 // 1 beats 100000000 @@ -351,14 +351,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000000 // 31 beats 000000000 // 32 beats 000000000 -0 , 1 , 17 , 73 , -85 , 341 , 219 , 375 , -255 , 511 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 17 , 73 , +85 , 341 , 219 , 375 , +255 , 511 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 10 step patterns // 0 beats 0000000000 // 1 beats 1000000000 @@ -393,14 +393,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000000 // 31 beats 0000000000 // 32 beats 0000000000 -0 , 1 , 33 , 73 , -165 , 341 , 693 , 731 , -495 , 511 , 1023 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 33 , 73 , +165 , 341 , 693 , 731 , +495 , 511 , 1023 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 11 step patterns // 0 beats 00000000000 // 1 beats 10000000000 @@ -435,14 +435,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000000 // 31 beats 00000000000 // 32 beats 00000000000 -0 , 1 , 33 , 273 , -585 , 341 , 1365 , 877 , -955 , 1519 , 1023 , 2047 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 33 , 273 , +585 , 341 , 1365 , 877 , +955 , 1519 , 1023 , 2047 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 12 step patterns // 0 beats 000000000000 // 1 beats 100000000000 @@ -477,14 +477,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000000000 // 31 beats 000000000000 // 32 beats 000000000000 -0 , 1 , 65 , 273 , -585 , 1189 , 1365 , 1717 , -1755 , 1911 , 2015 , 2047 , -4095 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 65 , 273 , +585 , 1189 , 1365 , 1717 , +1755 , 1911 , 2015 , 2047 , +4095 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 13 step patterns // 0 beats 0000000000000 // 1 beats 1000000000000 @@ -519,14 +519,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000000000 // 31 beats 0000000000000 // 32 beats 0000000000000 -0 , 1 , 65 , 273 , -585 , 1321 , 1365 , 5461 , -5549 , 5851 , 6007 , 6111 , -4095 , 8191 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 65 , 273 , +585 , 1321 , 1365 , 5461 , +5549 , 5851 , 6007 , 6111 , +4095 , 8191 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 14 step patterns // 0 beats 00000000000000 // 1 beats 10000000000000 @@ -561,14 +561,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000000000 // 31 beats 00000000000000 // 32 beats 00000000000000 -0 , 1 , 129 , 1057 , -1161 , 4681 , 2709 , 5461 , -10965 , 7021 , 11739 , 7927 , -8127 , 8191 , 16383 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 129 , 1057 , +1161 , 4681 , 2709 , 5461 , +10965 , 7021 , 11739 , 7927 , +8127 , 8191 , 16383 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 15 step patterns // 0 beats 000000000000000 // 1 beats 100000000000000 @@ -603,14 +603,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000000000000 // 31 beats 000000000000000 // 32 beats 000000000000000 -0 , 1 , 129 , 1057 , -4369 , 4681 , 5285 , 5461 , -21845 , 22197 , 14043 , 15291 , -15855 , 24511 , 16383 , 32767 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 129 , 1057 , +4369 , 4681 , 5285 , 5461 , +21845 , 22197 , 14043 , 15291 , +15855 , 24511 , 16383 , 32767 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 16 step patterns // 0 beats 0000000000000000 // 1 beats 1000000000000000 @@ -645,14 +645,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000000000000 // 31 beats 0000000000000000 // 32 beats 0000000000000000 -0 , 1 , 257 , 1057 , -4369 , 4681 , 18761 , 19093 , -21845 , 27349 , 28013 , 46811 , -30583 , 48623 , 32639 , 32767 , -65535 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 257 , 1057 , +4369 , 4681 , 18761 , 19093 , +21845 , 27349 , 28013 , 46811 , +30583 , 48623 , 32639 , 32767 , +65535 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 17 step patterns // 0 beats 00000000000000000 // 1 beats 10000000000000000 @@ -687,14 +687,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000000000000 // 31 beats 00000000000000000 // 32 beats 00000000000000000 -0 , 1 , 257 , 4161 , -4369 , 17545 , 37449 , 38053 , -21845 , 87381 , 54965 , 56173 , -60891 , 96119 , 64495 , 98175 , -65535 , 131071 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 257 , 4161 , +4369 , 17545 , 37449 , 38053 , +21845 , 87381 , 54965 , 56173 , +60891 , 96119 , 64495 , 98175 , +65535 , 131071 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 18 step patterns // 0 beats 000000000000000000 // 1 beats 100000000000000000 @@ -729,14 +729,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000000000000000 // 31 beats 000000000000000000 // 32 beats 000000000000000000 -0 , 1 , 513 , 4161 , -8721 , 18577 , 37449 , 42281 , -43605 , 87381 , 174933 , 177581 , -112347 , 187835 , 192375 , 128991 , -130815 , 131071 , 262143 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 513 , 4161 , +8721 , 18577 , 37449 , 42281 , +43605 , 87381 , 174933 , 177581 , +112347 , 187835 , 192375 , 128991 , +130815 , 131071 , 262143 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 19 step patterns // 0 beats 0000000000000000000 // 1 beats 1000000000000000000 @@ -771,14 +771,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000000000000000 // 31 beats 0000000000000000000 // 32 beats 0000000000000000000 -0 , 1 , 513 , 4161 , -33825 , 69905 , 37449 , 84297 , -86693 , 87381 , 349525 , 350901 , -355693 , 374491 , 244667 , 253687 , -391135 , 392959 , 262143 , 524287 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 513 , 4161 , +33825 , 69905 , 37449 , 84297 , +86693 , 87381 , 349525 , 350901 , +355693 , 374491 , 244667 , 253687 , +391135 , 392959 , 262143 , 524287 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 20 step patterns // 0 beats 00000000000000000000 // 1 beats 10000000000000000000 @@ -813,14 +813,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000000000000000 // 31 beats 00000000000000000000 // 32 beats 00000000000000000000 -0 , 1 , 1025 , 16513 , -33825 , 69905 , 74825 , 299593 , -169125 , 305749 , 349525 , 437077 , -710325 , 449389 , 749275 , 489335 , -507375 , 520159 , 523775 , 524287 , -1048575 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 1025 , 16513 , +33825 , 69905 , 74825 , 299593 , +169125 , 305749 , 349525 , 437077 , +710325 , 449389 , 749275 , 489335 , +507375 , 520159 , 523775 , 524287 , +1048575 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 21 step patterns // 0 beats 000000000000000000000 // 1 beats 100000000000000000000 @@ -855,14 +855,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000000000000000000 // 31 beats 000000000000000000000 // 32 beats 000000000000000000000 -0 , 1 , 1025 , 16513 , -33825 , 69905 , 148617 , 299593 , -600361 , 346773 , 349525 , 1398101 , -1403605 , 896429 , 898779 , 1502683 , -1537911 , 1555951 , 1040319 , 1572351 , -1048575 , 2097151 , 0 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 1025 , 16513 , +33825 , 69905 , 148617 , 299593 , +600361 , 346773 , 349525 , 1398101 , +1403605 , 896429 , 898779 , 1502683 , +1537911 , 1555951 , 1040319 , 1572351 , +1048575 , 2097151 , 0 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 22 step patterns // 0 beats 0000000000000000000000 // 1 beats 1000000000000000000000 @@ -897,14 +897,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000000000000000000 // 31 beats 0000000000000000000000 // 32 beats 0000000000000000000000 -0 , 1 , 2049 , 16513 , -67617 , 270865 , 559377 , 299593 , -1198665 , 1217701 , 698709 , 1398101 , -2796885 , 1758901 , 1796973 , 2995931 , -1956795 , 2027383 , 3112431 , 3137471 , -2096127 , 2097151 , 4194303 , 0 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 2049 , 16513 , +67617 , 270865 , 559377 , 299593 , +1198665 , 1217701 , 698709 , 1398101 , +2796885 , 1758901 , 1796973 , 2995931 , +1956795 , 2027383 , 3112431 , 3137471 , +2096127 , 2097151 , 4194303 , 0 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 23 step patterns // 0 beats 00000000000000000000000 // 1 beats 10000000000000000000000 @@ -939,14 +939,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000000000000000000 // 31 beats 00000000000000000000000 // 32 beats 00000000000000000000000 -0 , 1 , 2049 , 65793 , -266305 , 279073 , 1118481 , 1123401 , -2396745 , 1353001 , 2443925 , 1398101 , -5592405 , 3500757 , 5682605 , 3595117 , -3895003 , 3914683 , 6156023 , 4127727 , -4177855 , 6290431 , 4194303 , 8388607 , -0 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 2049 , 65793 , +266305 , 279073 , 1118481 , 1123401 , +2396745 , 1353001 , 2443925 , 1398101 , +5592405 , 3500757 , 5682605 , 3595117 , +3895003 , 3914683 , 6156023 , 4127727 , +4177855 , 6290431 , 4194303 , 8388607 , +0 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 24 step patterns // 0 beats 000000000000000000000000 // 1 beats 100000000000000000000000 @@ -981,14 +981,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000000000000000000000 // 31 beats 000000000000000000000000 // 32 beats 000000000000000000000000 -0 , 1 , 4097 , 65793 , -266305 , 1082401 , 1118481 , 2245769 , -2396745 , 4802889 , 4871333 , 4893013 , -5592405 , 6991189 , 7034549 , 7171437 , -7190235 , 7794139 , 7829367 , 8118007 , -8255455 , 8355711 , 8386559 , 8388607 , -16777215 , 0 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 4097 , 65793 , +266305 , 1082401 , 1118481 , 2245769 , +2396745 , 4802889 , 4871333 , 4893013 , +5592405 , 6991189 , 7034549 , 7171437 , +7190235 , 7794139 , 7829367 , 8118007 , +8255455 , 8355711 , 8386559 , 8388607 , +16777215 , 0 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 25 step patterns // 0 beats 0000000000000000000000000 // 1 beats 1000000000000000000000000 @@ -1023,14 +1023,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000000000000000000000 // 31 beats 0000000000000000000000000 // 32 beats 0000000000000000000000000 -0 , 1 , 4097 , 65793 , -266305 , 1082401 , 1118481 , 2377873 , -2396745 , 5392969 , 5412005 , 5581461 , -5592405 , 22369621 , 22391509 , 22730421 , -22768493 , 23967451 , 24042939 , 24606583 , -16236015 , 25032671 , 25132927 , 25163775 , -16777215 , 33554431 , 0 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 4097 , 65793 , +266305 , 1082401 , 1118481 , 2377873 , +2396745 , 5392969 , 5412005 , 5581461 , +5592405 , 22369621 , 22391509 , 22730421 , +22768493 , 23967451 , 24042939 , 24606583 , +16236015 , 25032671 , 25132927 , 25163775 , +16777215 , 33554431 , 0 , 0 , +0 , 0 , 0 , 0 , 0 , // 26 step patterns // 0 beats 00000000000000000000000000 // 1 beats 10000000000000000000000000 @@ -1065,14 +1065,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000000000000000000000 // 31 beats 00000000000000000000000000 // 32 beats 00000000000000000000000000 -0 , 1 , 8193 , 262657 , -532545 , 1082401 , 2236689 , 4753681 , -4792905 , 19173961 , 10822953 , 11096741 , -11183445 , 22369621 , 44741973 , 44915381 , -45462957 , 28760941 , 47937243 , 48094139 , -49215351 , 49790447 , 50067423 , 33488767 , -33550335 , 33554431 , 67108863 , 0 , -0 , 0 , 0 , 0 , +0 , 1 , 8193 , 262657 , +532545 , 1082401 , 2236689 , 4753681 , +4792905 , 19173961 , 10822953 , 11096741 , +11183445 , 22369621 , 44741973 , 44915381 , +45462957 , 28760941 , 47937243 , 48094139 , +49215351 , 49790447 , 50067423 , 33488767 , +33550335 , 33554431 , 67108863 , 0 , +0 , 0 , 0 , 0 , 0 , // 27 step patterns // 0 beats 000000000000000000000000000 // 1 beats 100000000000000000000000000 @@ -1107,14 +1107,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 000000000000000000000000000 // 31 beats 000000000000000000000000000 // 32 beats 000000000000000000000000000 -0 , 1 , 8193 , 262657 , -2113665 , 4261921 , 4465169 , 17895697 , -9577609 , 19173961 , 21580105 , 38966437 , -22325845 , 22369621 , 89478485 , 89566037 , -56284853 , 91057517 , 57521883 , 95907291 , -62634939 , 98496375 , 66026991 , 66580447 , -66977535 , 100659199 , 67108863 , 134217727 , -0 , 0 , 0 , 0 , +0 , 1 , 8193 , 262657 , +2113665 , 4261921 , 4465169 , 17895697 , +9577609 , 19173961 , 21580105 , 38966437 , +22325845 , 22369621 , 89478485 , 89566037 , +56284853 , 91057517 , 57521883 , 95907291 , +62634939 , 98496375 , 66026991 , 66580447 , +66977535 , 100659199 , 67108863 , 134217727 , +0 , 0 , 0 , 0 , 0 , // 28 step patterns // 0 beats 0000000000000000000000000000 // 1 beats 1000000000000000000000000000 @@ -1149,14 +1149,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 0000000000000000000000000000 // 31 beats 0000000000000000000000000000 // 32 beats 0000000000000000000000000000 -0 , 1 , 16385 , 262657 , -2113665 , 4327489 , 17318945 , 17895697 , -19022985 , 19173961 , 76698185 , 43296041 , -44386965 , 78292309 , 89478485 , 111850837 , -179661525 , 181843373 , 115039085 , 191739611 , -192343515 , 125269879 , 129883895 , 199195631 , -133160895 , 201195263 , 134209535 , 134217727 , -268435455 , 0 , 0 , 0 , +0 , 1 , 16385 , 262657 , +2113665 , 4327489 , 17318945 , 17895697 , +19022985 , 19173961 , 76698185 , 43296041 , +44386965 , 78292309 , 89478485 , 111850837 , +179661525 , 181843373 , 115039085 , 191739611 , +192343515 , 125269879 , 129883895 , 199195631 , +133160895 , 201195263 , 134209535 , 134217727 , +268435455 , 0 , 0 , 0 , 0 , // 29 step patterns // 0 beats 00000000000000000000000000000 // 1 beats 10000000000000000000000000000 @@ -1191,14 +1191,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 00000000000000000000000000000 // 31 beats 00000000000000000000000000000 // 32 beats 00000000000000000000000000000 -0 , 1 , 16385 , 1049601 , -2113665 , 17043521 , 34636833 , 17895697 , -71600273 , 71901769 , 153391689 , 153692457 , -88757413 , 156543573 , 89478485 , 357913941 , -223783765 , 359356085 , 229485997 , 230087533 , -249263835 , 250469819 , 393705335 , 259776247 , -264174575 , 401596351 , 268173055 , 402644991 , -268435455 , 536870911 , 0 , 0 , +0 , 1 , 16385 , 1049601 , +2113665 , 17043521 , 34636833 , 17895697 , +71600273 , 71901769 , 153391689 , 153692457 , +88757413 , 156543573 , 89478485 , 357913941 , +223783765 , 359356085 , 229485997 , 230087533 , +249263835 , 250469819 , 393705335 , 259776247 , +264174575 , 401596351 , 268173055 , 402644991 , +268435455 , 536870911 , 0 , 0 , 0 , // 30 step patterns // 0 beats 000000000000000000000000000000 // 1 beats 100000000000000000000000000000 @@ -1233,14 +1233,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 111111111111111111111111111111 // 31 beats 000000000000000000000000000000 // 32 beats 000000000000000000000000000000 -0 , 1 , 32769 , 1049601 , -4227201 , 17043521 , 34636833 , 69345553 , -143167761 , 76620873 , 153391689 , 306858313 , -173184165 , 312822421 , 178951509 , 357913941 , -715838805 , 448096981 , 727373493 , 460025197 , -460175067 , 767258331 , 501070779 , 518977399 , -519552495 , 528349151 , 803200959 , 536346111 , -536854527 , 536870911 , 1073741823 , 0 , +0 , 1 , 32769 , 1049601 , +4227201 , 17043521 , 34636833 , 69345553 , +143167761 , 76620873 , 153391689 , 306858313 , +173184165 , 312822421 , 178951509 , 357913941 , +715838805 , 448096981 , 727373493 , 460025197 , +460175067 , 767258331 , 501070779 , 518977399 , +519552495 , 528349151 , 803200959 , 536346111 , +536854527 , 536870911 , 1073741823 , 0 , 0 , // 31 step patterns // 0 beats 0000000000000000000000000000000 // 1 beats 1000000000000000000000000000000 @@ -1275,14 +1275,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 1111111111111111111111111111110 // 31 beats 1111111111111111111111111111111 // 32 beats 0000000000000000000000000000000 -0 , 1 , 32769 , 1049601 , -16843009 , 17043521 , 34636833 , 138682897 , -286331153 , 287458441 , 153391689 , 345133641 , -614802729 , 623530661 , 357739093 , 357913941 , -1431655765 , 1432005461 , 900422325 , 917878189 , -1457216365 , 1533916891 , 997649883 , 1002159035 , -1038020471 , 1593294319 , 1602090975 , 1069531071 , -1610087935 , 1610596351 , 1073741823 , 2147483647 , +0 , 1 , 32769 , 1049601 , +16843009 , 17043521 , 34636833 , 138682897 , +286331153 , 287458441 , 153391689 , 345133641 , +614802729 , 623530661 , 357739093 , 357913941 , +1431655765 , 1432005461 , 900422325 , 917878189 , +1457216365 , 1533916891 , 997649883 , 1002159035 , +1038020471 , 1593294319 , 1602090975 , 1069531071 , +1610087935 , 1610596351 , 1073741823 , 2147483647 , 0 , // 32 step patterns // 0 beats 00000000000000000000000000000000 // 1 beats 10000000000000000000000000000000 @@ -1317,14 +1317,14 @@ const uint32_t bjorklund_patterns[] = { // 30 beats 11111111111111101111111111111110 // 31 beats 11111111111111111111111111111110 // 32 beats 11111111111111111111111111111111 -0 , 1 , 65537 , 4196353 , -16843009 , 67641409 , 69272609 , 142885409 , -286331153 , 304367761 , 306778697 , 1227133513 , -1229539657 , 1246925989 , 1251297941 , 1252693333 , -1431655765 , 1789580629 , 1792371413 , 1801115317 , -1835887981 , 1840700269 , 3067852507 , 3077496251 , -2004318071 , 3151884023 , 3186605551 , 2130442207 , -2139062143 , 2146434559 , 2147450879 , 2147483647 , +0 , 1 , 65537 , 4196353 , +16843009 , 67641409 , 69272609 , 142885409 , +286331153 , 304367761 , 306778697 , 1227133513 , +1229539657 , 1246925989 , 1251297941 , 1252693333 , +1431655765 , 1789580629 , 1792371413 , 1801115317 , +1835887981 , 1840700269 , 3067852507 , 3077496251 , +2004318071 , 3151884023 , 3186605551 , 2130442207 , +2139062143 , 2146434559 , 2147450879 , 2147483647 , 4294967295 , }; @@ -1332,7 +1332,7 @@ bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uin if (num_beats > (num_steps + 1)) { num_beats = num_steps + 1; } - uint32_t pattern = bjorklund_patterns[((num_steps - 1) * 33) + num_beats]; + uint32_t pattern = bjorklund_patterns[((num_steps - 1) * 33) + num_beats]; if (rotation) { // Serial.print(pattern); // Serial.print("\n"); @@ -1349,10 +1349,11 @@ uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation if (num_beats > (num_steps + 1)) { num_beats = num_steps + 1; } - uint32_t pattern = bjorklund_patterns[((num_steps - 1) * 33) + num_beats]; - if (rotation) { + uint32_t pattern = bjorklund_patterns[((num_steps - 1) * 33) + num_beats]; + if ((rotation) && ((num_steps + 1) != num_beats)) { rotation = rotation % (num_steps + 1); pattern = rotl32(pattern, num_steps, rotation) ; } + return pattern; } From 0109248a304db6d8eb8d2f5178ab693ae79e98f6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 24 Aug 2022 14:51:40 -0400 Subject: [PATCH 014/417] EbbAndLfo: Set Help text --- software/o_c_REV/HEM_EbbAndLfo.ino | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index d58f8da34..840e7989d 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -75,8 +75,6 @@ public: } } - void SetHelp() {} - void OnButtonPress() { cursor++; cursor %= 7; @@ -126,6 +124,14 @@ public: void OnDataReceive(uint64_t data) {} +protected: + void SetHelp() { + help[HEMISPHERE_HELP_DIGITALS] = ""; + help[HEMISPHERE_HELP_CVS] = "1=V/Oct 2=Slope"; + help[HEMISPHERE_HELP_OUTS] = "A=OutA B=OutB"; + help[HEMISPHERE_HELP_ENCODER] = "P=param T=adjust"; + } + private: enum Output { @@ -255,4 +261,4 @@ uint64_t EbbAndLfo_OnDataRequest(bool hemisphere) { void EbbAndLfo_OnDataReceive(bool hemisphere, uint64_t data) { EbbAndLfo_instance[hemisphere].OnDataReceive(data); -} \ No newline at end of file +} From 9eeeb9cfc6f7a974e2af4cc97e737d2c41e6a4d0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 24 Aug 2022 14:48:42 -0400 Subject: [PATCH 015/417] TrigSeq16: fix offset display for step cursor, too --- software/o_c_REV/HEM_TrigSeq16.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_TrigSeq16.ino b/software/o_c_REV/HEM_TrigSeq16.ino index 5f3500a1e..45073b6fe 100644 --- a/software/o_c_REV/HEM_TrigSeq16.ino +++ b/software/o_c_REV/HEM_TrigSeq16.ino @@ -156,7 +156,7 @@ private: } } - if (s + (ch * 8) == step) { + if (s + (ch * 8) == active_step) { gfxLine(x + 4, y, x + 10, y); } From 1a31ba3fd67c48c9e0dbe7970f06995d94f65100 Mon Sep 17 00:00:00 2001 From: recliq Date: Sun, 10 Jul 2022 17:08:03 +0200 Subject: [PATCH 016/417] added saving of octave_offset to TB3PO --- software/o_c_REV/HEM_TB3PO.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 83cc25a49..b9bd2ad98 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -379,6 +379,7 @@ class TB_3PO : public HemisphereApplet Pack(data, PackLocation {8,4}, root); Pack(data, PackLocation {12,4}, density_encoder); Pack(data, PackLocation {16,16}, seed); + Pack(data, PackLocation {32,8}, octave_offset); return data; } @@ -388,6 +389,7 @@ class TB_3PO : public HemisphereApplet root = Unpack(data, PackLocation {8,4}); density_encoder = Unpack(data, PackLocation {12,4}); seed = Unpack(data, PackLocation {16,16}); + octave_offset = Unpack(data, PackLocation {32,8}); //const braids::Scale & quant_scale = OC::Scales::GetScale(scale); set_quantizer_scale(scale); @@ -396,6 +398,7 @@ class TB_3PO : public HemisphereApplet root = constrain(root, 0, 11); density_encoder = constrain(density_encoder, 0, 14); // Internally just positive density = density_encoder; + octave_offset = constrain(octave_offset,-3,3); // Restore all seed-derived settings! regenerate_all(); From bcb6469a1e2c71792966bec28fceac78f61c41b5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 31 Aug 2022 16:37:20 -0400 Subject: [PATCH 017/417] HEM_TM.ino: Tweaks Change edit cursor behavior (toggle-edit) Pack saved parameters tighter --- software/o_c_REV/HEM_TM.ino | 79 +++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/software/o_c_REV/HEM_TM.ino b/software/o_c_REV/HEM_TM.ino index 774c21fdf..49715f6c0 100644 --- a/software/o_c_REV/HEM_TM.ino +++ b/software/o_c_REV/HEM_TM.ino @@ -67,8 +67,9 @@ public: // CV 2 bi-polar modulation of probability int pCv = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100); + bool clk = Clock(0); - if (Clock(0)) { + if (clk) { // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same int prob = (cursor == 1 || Gate(1)) ? p + pCv : 0; prob = constrain(prob, 0, 100); @@ -98,15 +99,14 @@ public: if (cv2 == 0) { // Send 8-bit proportioned CV - int cv = Proportion(reg & 0x00ff, 255, HEMISPHERE_MAX_CV); - Out(1, cv); + Out(1, Proportion(reg & 0x00ff, 255, HEMISPHERE_MAX_CV) ); } else if (cv2 == 1) { - if (Clock(0)) { + if (clk) { ClockOut(1); } } else if (cv2 == 2) { // only trigger if 1st bit is high - if (Clock(0) && (reg & 0x01) == 1) { + if (clk && (reg & 0x01) == 1) { ClockOut(1); } } @@ -119,29 +119,35 @@ public: } void OnButtonPress() { - if (++cursor > 4) cursor = 0; + isEditing = !isEditing; } void OnEncoderMove(int direction) { - if (cursor == 0) length = constrain(length += direction, TM_MIN_LENGTH, TM_MAX_LENGTH); - if (cursor == 1) p = constrain(p += direction, 0, 100); - if (cursor == 2) { - scale += direction; - if (scale >= TM_MAX_SCALE) scale = 0; - if (scale < 0) scale = TM_MAX_SCALE - 1; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); - } - if(cursor == 3){ - quant_range = constrain(quant_range += direction, 1, 32); - } - if(cursor == 4){ - cv2 += direction; - if (cv2 > 2) { - cv2 = 0; - } - if (cv2 < 0) { - cv2 = 2; - } + if (!isEditing) { + cursor += direction; + if (cursor < 0) cursor = 4; + if (cursor > 4) cursor = 0; + } else { + switch (cursor) { + case 0: + length = constrain(length + direction, TM_MIN_LENGTH, TM_MAX_LENGTH); + break; + case 1: + p = constrain(p + direction, 0, 100); + break; + case 2: + scale += direction; + if (scale >= TM_MAX_SCALE) scale = 0; + if (scale < 0) scale = TM_MAX_SCALE - 1; + quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + break; + case 3: + quant_range = constrain(quant_range + direction, 1, 32); + break; + case 4: + cv2 = constrain(cv2 + direction, 0, 2); + break; + } } } @@ -150,13 +156,13 @@ public: Pack(data, PackLocation {0,16}, reg); Pack(data, PackLocation {16,7}, p); Pack(data, PackLocation {23,4}, length - 1); - Pack(data, PackLocation {28,6}, quant_range); - Pack(data, PackLocation {34,4}, cv2); + Pack(data, PackLocation {27,5}, quant_range - 1); + Pack(data, PackLocation {32,4}, cv2); // Logarhythm mod: Since scale can exceed 6 bits now, clamp mathematically rather than surprising the user with a roll over of larger numbers //Pack(data, PackLocation {27,6}, scale); - Pack(data, PackLocation {38,6}, constrain(scale, 0, 63)); - + Pack(data, PackLocation {36,6}, constrain(scale, 0, 63)); + return data; } @@ -164,11 +170,10 @@ public: reg = Unpack(data, PackLocation {0,16}); p = Unpack(data, PackLocation {16,7}); length = Unpack(data, PackLocation {23,4}) + 1; - quant_range = Unpack(data, PackLocation{28,6}); - cv2 = Unpack(data, PackLocation {34,4}); - scale = Unpack(data, PackLocation {38,6}); + quant_range = Unpack(data, PackLocation{27,5}) + 1; + cv2 = Unpack(data, PackLocation {32,4}); + scale = Unpack(data, PackLocation {36,6}); quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); - } protected: @@ -184,6 +189,7 @@ protected: private: int length; // Sequence length int cursor; // 0 = length, 1 = p, 2 = scale + bool isEditing = false; braids::Quantizer quantizer; // Settings @@ -226,6 +232,13 @@ private: if (cursor == 2) gfxCursor(13, 33, 30); // Scale Cursor if (cursor == 3) gfxCursor(49, 33, 14); // Quant Range Cursor // APD if (cursor == 4) gfxCursor(27, 43, (cv2 == 2) ? 18 : 10); // cv2 mode + if (isEditing) { + if (cursor == 0) gfxInvert(13, 14, 12, 9); + if (cursor == 1) gfxInvert(45, 14, 18, 9); + if (cursor == 2) gfxInvert(13, 24, 30, 9); + if (cursor == 3) gfxInvert(49, 24, 14, 9); + if (cursor == 4) gfxInvert(27, 34, (cv2 == 2) ? 18 : 10, 9); // cv2 mode + } } void DrawIndicator() { From 1eb46f576ced288eec457bf2063fd0c920067250 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 31 Aug 2022 22:12:09 -0400 Subject: [PATCH 018/417] TB-3PO: push to toggle editing of params Also use switch-case in place of multiple if statements in ShiftReg and TB-3PO. Tweaked graphics by a few pixels in some places. --- software/o_c_REV/HEM_TB3PO.ino | 248 +++++++++++++++++---------------- software/o_c_REV/HEM_TM.ino | 30 ++-- 2 files changed, 144 insertions(+), 134 deletions(-) diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index b9bd2ad98..2cc02874b 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -262,115 +262,113 @@ class TB_3PO : public HemisphereApplet DrawGraphics(); } - void OnButtonPress() - { - if(cursor == 0) - { - cursor = lock_seed ? 1 : 5; - } - else if (++cursor > 8) - { - cursor = 0; - } - - ResetCursor(); // Reset blink so it's immediately visible when moved + void OnButtonPress() { + isEditing = !isEditing; } void OnEncoderMove(int direction) { - if(cursor == 0) - { - // Toggle the seed between auto (randomized every reset input pulse) - // or Manual (seed becomes locked, cursor can be moved to edit each digit) - lock_seed += direction; - - // See if the turn would move beyond the random die to the left or the lock to the right - // If so, take this as a manual input just like receiving a reset pulse (handled in Controller()) - // regenerate_all() will honor the random or locked icon shown (seed will be randomized or not) - manual_reset_flag = (lock_seed > 1 || lock_seed < 0) ? 1 : 0; - - // constrain to legal values before regeneration - lock_seed = constrain(lock_seed, 0, 1); - } - else if (cursor <= 4) - { - // Editing one of the 4 hex digits of the seed - - // cursor==1 is at the most significant byte, - // cursor==4 is at least significant byte - int byte_offs = 4-cursor; - int shift_amt = byte_offs*4; - - uint32_t nib = (seed >> shift_amt)& 0xf; // Abduct the nibble - uint8_t c = nib; - c = constrain(c+direction, 0, 0xF); // Edit the nibble - nib = c; - uint32_t mask = 0xf; - seed &= ~(mask << shift_amt); // Clear bits where this nibble lives - seed |= (nib << shift_amt); // Move the nibble to its home - } - else if (cursor == 5) - { - density_encoder = constrain(density_encoder + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice - density_encoder_display = 400; // How long to show the encoder version of density in the number display for - - //density = constrain(density + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice - - // Disabled: Let this occur when detected on the next step - //regenerate_density_if_changed(); - } - else if(cursor == 6) - { - // Scale selection - scale += direction; - if (scale >= OC::Scales::NUM_SCALES) scale = 0; - if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - // Apply to the quantizer - set_quantizer_scale(scale); - -// New: Constrain root to scale size (leave oct offset where it is) - int max_root = scale_size > 12 ? 12 : scale_size; - if(max_root > 0) - { - root = constrain(root, 0, max_root-1); + if (!isEditing) { // move cursor + cursor += direction; + if (cursor < 0) cursor = 8; + if (cursor > 8) cursor = 0; + if (!lock_seed && cursor == 1) cursor = 5; // skip from 1 to 5 if not locked + if (!lock_seed && cursor == 4) cursor = 0; // skip from 4 to 0 if not locked + + ResetCursor(); // Reset blink so it's immediately visible when moved + } else { // edit param + switch(cursor) { + case 0: + // Toggle the seed between auto (randomized every reset input pulse) + // or Manual (seed becomes locked, cursor can be moved to edit each digit) + lock_seed += direction; + + // See if the turn would move beyond the random die to the left or the lock to the right + // If so, take this as a manual input just like receiving a reset pulse (handled in Controller()) + // regenerate_all() will honor the random or locked icon shown (seed will be randomized or not) + manual_reset_flag = (lock_seed > 1 || lock_seed < 0) ? 1 : 0; + + // constrain to legal values before regeneration + lock_seed = constrain(lock_seed, 0, 1); + break; + case 1: + case 2: + case 3: + case 4: { // Editing one of the 4 hex digits of the seed + // cursor==1 is at the most significant byte, + // cursor==4 is at least significant byte + int byte_offs = 4-cursor; + int shift_amt = byte_offs*4; + + uint32_t nib = (seed >> shift_amt)& 0xf; // Abduct the nibble + uint8_t c = nib; + c = constrain(c+direction, 0, 0xF); // Edit the nibble + nib = c; + uint32_t mask = 0xf; + seed &= ~(mask << shift_amt); // Clear bits where this nibble lives + seed |= (nib << shift_amt); // Move the nibble to its home + break; } - } - else if(cursor == 7) - { - // Root note selection - - // No oct version - //root = constrain(root + direction, 0, 11); + case 5: // density + density_encoder = constrain(density_encoder + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice + density_encoder_display = 400; // How long to show the encoder version of density in the number display for + + //density = constrain(density + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice + + // Disabled: Let this occur when detected on the next step + //regenerate_density_if_changed(); + break; + case 6: { // Scale selection + scale += direction; + if (scale >= OC::Scales::NUM_SCALES) scale = 0; + if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; + // Apply to the quantizer + set_quantizer_scale(scale); + + // New: Constrain root to scale size (leave oct offset where it is) + int max_root = scale_size > 12 ? 12 : scale_size; + if(max_root > 0) + { + root = constrain(root, 0, max_root-1); + } + break; + } + case 7: { // Root note selection + + // No oct version + //root = constrain(root + direction, 0, 11); - // Add in handling for octave settings without affecting root's range - int r = root + direction; + // Add in handling for octave settings without affecting root's range + int r = root + direction; - int max_root = scale_size > 12 ? 12 : scale_size; - - //if(direction > 0 && r > 11 && octave_offset < 3) - if(direction > 0 && r >= max_root && octave_offset < 3) - { - ++octave_offset; // Go up to next octave - r = 0; // Roll around root note - } - else if(direction < 0 && r < 0 && octave_offset > -3) - { - --octave_offset; + int max_root = scale_size > 12 ? 12 : scale_size; + + //if(direction > 0 && r > 11 && octave_offset < 3) + if(direction > 0 && r >= max_root && octave_offset < 3) + { + ++octave_offset; // Go up to next octave + r = 0; // Roll around root note + } + else if(direction < 0 && r < 0 && octave_offset > -3) + { + --octave_offset; - r = max_root-1; - //r = 11; // Roll around root note - } + r = max_root-1; + //r = 11; // Roll around root note + } - // Limit root value - //root = constrain(r, 0, 11); - root = constrain(r, 0, max_root-1); + // Limit root value + //root = constrain(r, 0, 11); + root = constrain(r, 0, max_root-1); + break; + } + case 8: // pattern length + num_steps = constrain(num_steps + direction, 1, 32); + break; + } //switch } - else - { - num_steps = constrain(num_steps + direction, 1, 32); - } - } + } //OnEncoderMove uint64_t OnDataRequest() { uint64_t data = 0; @@ -419,6 +417,7 @@ class TB_3PO : public HemisphereApplet private: int cursor = 0; + bool isEditing = false; braids::Quantizer quantizer; // Helper for note index --> pitch cv braids::Quantizer display_semi_quantizer; // Quantizer to interpret the current note for display on a keyboard @@ -957,30 +956,35 @@ class TB_3PO : public HemisphereApplet } // Draw edit cursor - if (cursor == 0) - { - // Set length to indicate length - gfxCursor(14, 23, lock_seed ? 12 : 35); // Seed = auto-randomize / locked-manual - } - else if (cursor <= 4) // seed, 4 positions (1-4) - { - gfxCursor(24 + 6*(cursor-1), 23, 8); - } - else if(cursor == 5) - { - gfxCursor(9, 34, 14); // density - } - else if(cursor == 6) - { - gfxCursor(38, 34, 26); // scale - } - else if(cursor == 7) - { - gfxCursor(42, 43, 16); // root note - } - else if(cursor == 8) - { - gfxCursor(20, 54, 12); // step + switch (cursor) { + case 0: + // Set length to indicate length + gfxCursor(14, 23, lock_seed ? 11 : 36); // Seed = auto-randomize / locked-manual + if (isEditing) gfxInvert(14, 14, lock_seed ? 11 : 36, 9); + break; + case 1: + case 2: + case 3: + case 4: // seed, 4 positions (1-4) + gfxCursor(25 + 6*(cursor-1), 23, 7); + if (isEditing) gfxInvert(25 + 6*(cursor-1), 14, 7, 9); + break; + case 5: + gfxCursor(9, 45, 14); // density + if (isEditing) gfxInvert(9, 36, 14, 9); + break; + case 6: + gfxCursor(39, 34, 25); // scale + if (isEditing) gfxInvert(39, 26, 25, 8); + break; + case 7: + gfxCursor(39, 44, 24); // root note + if (isEditing) gfxInvert(39, 35, 24, 9); + break; + case 8: + gfxCursor(20, 54, 12); // step + if (isEditing) gfxInvert(20, 46, 12, 8); + break; } } diff --git a/software/o_c_REV/HEM_TM.ino b/software/o_c_REV/HEM_TM.ino index 49715f6c0..89ae1c45d 100644 --- a/software/o_c_REV/HEM_TM.ino +++ b/software/o_c_REV/HEM_TM.ino @@ -127,6 +127,8 @@ public: cursor += direction; if (cursor < 0) cursor = 4; if (cursor > 4) cursor = 0; + + ResetCursor(); // Reset blink so it's immediately visible when moved } else { switch (cursor) { case 0: @@ -205,12 +207,11 @@ private: gfxBitmap(1, 14, 8, LOOP_ICON); gfxPrint(12 + pad(10, length), 15, length); gfxPrint(32, 15, "p="); - if (cursor == 1 || Gate(1)) { + if (cursor == 1 || Gate(1)) { // p unlocked int pCv = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100); int prob = constrain(p + pCv, 0, 100); - if (cursor == 1) gfxCursor(45, 23, 18); // Probability Cursor gfxPrint(pad(100, prob), prob); - } else { + } else { // p is disabled gfxBitmap(49, 14, 8, LOCK_ICON); } gfxBitmap(1, 24, 8, SCALE_ICON); @@ -228,16 +229,21 @@ private: } //gfxPrint(1, 35, tmp); - if (cursor == 0) gfxCursor(13, 23, 12); // Length Cursor - if (cursor == 2) gfxCursor(13, 33, 30); // Scale Cursor - if (cursor == 3) gfxCursor(49, 33, 14); // Quant Range Cursor // APD - if (cursor == 4) gfxCursor(27, 43, (cv2 == 2) ? 18 : 10); // cv2 mode + switch (cursor) { + case 0: gfxCursor(13, 23, 12); break; // Length Cursor + case 1: gfxCursor(45, 23, 18); break; // Probability Cursor + case 2: gfxCursor(12, 33, 25); break; // Scale Cursor + case 3: gfxCursor(49, 33, 14); break; // Quant Range Cursor // APD + case 4: gfxCursor(27, 43, (cv2 == 2) ? 18 : 10); // cv2 mode + } if (isEditing) { - if (cursor == 0) gfxInvert(13, 14, 12, 9); - if (cursor == 1) gfxInvert(45, 14, 18, 9); - if (cursor == 2) gfxInvert(13, 24, 30, 9); - if (cursor == 3) gfxInvert(49, 24, 14, 9); - if (cursor == 4) gfxInvert(27, 34, (cv2 == 2) ? 18 : 10, 9); // cv2 mode + switch (cursor) { + case 0: gfxInvert(13, 14, 12, 9); break; + case 1: gfxInvert(45, 14, 18, 9); break; + case 2: gfxInvert(12, 24, 25, 9); break; + case 3: gfxInvert(49, 24, 14, 9); break; + case 4: gfxInvert(27, 34, (cv2 == 2) ? 18 : 10, 9); // cv2 mode + } } } From 8b51b1bf64ec51a282b9c53c250bc080c081bb04 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 7 Sep 2022 00:14:11 -0400 Subject: [PATCH 019/417] Version/About screen text is now defined in one place --- software/o_c_REV/APP_SETTINGS.ino | 4 ++-- software/o_c_REV/OC_version.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/APP_SETTINGS.ino b/software/o_c_REV/APP_SETTINGS.ino index 72e750fe2..2d90e57d8 100644 --- a/software/o_c_REV/APP_SETTINGS.ino +++ b/software/o_c_REV/APP_SETTINGS.ino @@ -71,9 +71,9 @@ public: void View() { gfxHeader("Setup / About"); - gfxPrint(0, 15, "Benisphere Suite"); + gfxPrint(0, 15, OC_VERSION_TITLE); gfxPrint(0, 25, OC_VERSION); - gfxPrint(0, 35, "github.com/benirose"); + gfxPrint(0, 35, OC_VERSION_URL); gfxPrint(0, 55, "[CALIBRATE] [RESET]"); #ifdef BUCHLA_4U diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 2c098a2c5..aafddde48 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,4 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ -#define OC_VERSION "v1.3-phz" +#define OC_VERSION_TITLE "Phazerville Suite" +#define OC_VERSION "v1.4-phz" +#define OC_VERSION_URL "github.com/djphazer" #endif From 6017a8a10a4ac821befc49bb43315a6f76ae1db6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 7 Sep 2022 00:18:48 -0400 Subject: [PATCH 020/417] Disable ENIGMA to free up space --- software/o_c_REV/OC_apps.ino | 1 + software/o_c_REV/OC_options.h | 2 +- software/o_c_REV/o_c_REV.ino | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 9e5462453..ef0cfc0e6 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -23,6 +23,7 @@ #include "OC_apps.h" #include "OC_digital_inputs.h" #include "OC_autotune.h" +#include "enigma/TuringMachine.h" #define DECLARE_APP(a, b, name, prefix) \ { TWOCC::value, name, \ diff --git a/software/o_c_REV/OC_options.h b/software/o_c_REV/OC_options.h index 9c730a27a..98c8cdfcc 100644 --- a/software/o_c_REV/OC_options.h +++ b/software/o_c_REV/OC_options.h @@ -26,7 +26,7 @@ /* Flags for the full-width apps, these enable/disable them in OC_apps.ino but also zero out the app */ /* files to prevent them from taking up space. Only Enigma is enabled by default. */ -#define ENABLE_APP_ENIGMA +//#define ENABLE_APP_ENIGMA //#define ENABLE_APP_MIDI //#define ENABLE_APP_NEURAL_NETWORK //#define ENABLE_APP_PONG diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 8b2401937..ab1b95ed6 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -26,6 +26,7 @@ #include #include "OC_apps.h" +#include "OC_strings.h" #include "OC_core.h" #include "OC_DAC.h" #include "OC_debug.h" @@ -200,4 +201,4 @@ void FASTRUN loop() { } } - + From 870f0532c0e3fea6059638cfdb79f3d1dcf1ea86 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 7 Sep 2022 00:32:56 -0400 Subject: [PATCH 021/417] EbbAndLfo: fix OutA / OutB config --- software/o_c_REV/HEM_EbbAndLfo.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 840e7989d..f0e0348d0 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -110,10 +110,12 @@ public: case 5: { out_a += direction; out_a %= 4; + break; } case 6: { out_b += direction; out_b %= 4; + break; } } if (knob_accel < (1 << 13)) From c79973f6d7c927791603ffd365b36680bbb1e8a1 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 7 Sep 2022 03:05:06 -0400 Subject: [PATCH 022/417] Re-enable some HEM applets --- software/o_c_REV/hemisphere_config.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index d8c90f20f..08512e7b5 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 49 +#define HEMISPHERE_AVAILABLE_APPLETS 54 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ @@ -23,9 +23,10 @@ DECLARE_APPLET( 51, 0x80, BugCrack), \ DECLARE_APPLET( 4, 0x14, Brancher), \ DECLARE_APPLET( 31, 0x04, Burst), \ - DECLARE_APPLET( 57, 0x10, Button), \ + DECLARE_APPLET( 65, 0x10, Button), \ DECLARE_APPLET( 12, 0x10, Calculate),\ 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), \ @@ -41,9 +42,12 @@ DECLARE_APPLET( 21, 0x01, LowerRenz), \ DECLARE_APPLET( 50, 0x04, Metronome), \ DECLARE_APPLET( 33, 0x10, MixerBal), \ + DECLARE_APPLET( 20, 0x02, Palimpsest), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ + DECLARE_APPLET( 44, 0x01, RunglBook), \ DECLARE_APPLET( 26, 0x08, ScaleDuet), \ + DECLARE_APPLET( 40, 0x40, Schmitt), \ DECLARE_APPLET( 23, 0x80, Scope), \ DECLARE_APPLET( 14, 0x02, Sequence5), \ DECLARE_APPLET( 48, 0x45, ShiftGate), \ @@ -69,12 +73,8 @@ DECLARE_APPLET(127, 0x80, DIAGNOSTIC), \ DECLARE_APPLET(150, 0x20, hMIDIIn), \ DECLARE_APPLET( 27, 0x20, hMIDIOut), \ - DECLARE_APPLET( 62, 0x08, Chordinator), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 17, 0x50, GatedVCA), \ - DECLARE_APPLET( 20, 0x02, Palimpsest), \ - DECLARE_APPLET( 44, 0x01, RunglBook), \ - DECLARE_APPLET( 40, 0x40, Schmitt), \ DECLARE_APPLET( 46, 0x08, Squanch), \ DECLARE_APPLET( 37, 0x40, Trending), \ */ From cd416faa4c40b82895732b0ebe7b5a098d721ccf Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 24 Sep 2022 07:23:06 -0400 Subject: [PATCH 023/417] ShiftReg: tweaks, added 2 more cv2 modes - duplicate of A - alternate 6-bit pitch (experimental) - simplified/rewrote some code, hope it doesn't break anything lol --- software/o_c_REV/HEM_TM.ino | 85 ++++++++++++++-------------- software/o_c_REV/hemisphere_config.h | 2 +- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/software/o_c_REV/HEM_TM.ino b/software/o_c_REV/HEM_TM.ino index 89ae1c45d..2dc7ee50b 100644 --- a/software/o_c_REV/HEM_TM.ino +++ b/software/o_c_REV/HEM_TM.ino @@ -31,8 +31,6 @@ #include "braids_quantizer_scales.h" #include "OC_scales.h" -// Logarhythm mod: Allow all scales, even though only the first 64 serialize correctly -//#define TM_MAX_SCALE 63 #define TM_MAX_SCALE OC::Scales::NUM_SCALES #define TM_MIN_LENGTH 2 @@ -72,43 +70,37 @@ public: if (clk) { // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same int prob = (cursor == 1 || Gate(1)) ? p + pCv : 0; - prob = constrain(prob, 0, 100); - // Grab the bit that's about to be shifted away - int last = (reg >> (length - 1)) & 0x01; - - // Does it change? - if (random(0, 99) < prob) last = 1 - last; - - // Shift left, then potentially add the bit from the other side - reg = (reg << 1) + last; + AdvanceRegister( constrain(prob, 0, 100) ); } // Send 5-bit quantized CV - int32_t note = reg & 0x1f; - // APD: Scale this to the range of notes allowed by quant_range: 32 should be all // This defies the faithful Turing Machine sim aspect of this code but gives a useful addition that the Disting adds to the concept - // scaled = note * quant_range / 0x1f - note *= quant_range; - simfloat x = int2simfloat(note) / (int32_t)0x1f; - note = simfloat2int(x); - //tmp = note; - + int32_t note = Proportion(reg & 0x1f, 0x1f, quant_range); Out(0, quantizer.Lookup(note + 64)); - if (cv2 == 0) { + switch (cv2) { + case 0: // Send 8-bit proportioned CV Out(1, Proportion(reg & 0x00ff, 255, HEMISPHERE_MAX_CV) ); - } else if (cv2 == 1) { - if (clk) { - ClockOut(1); - } - } else if (cv2 == 2) { + break; + case 1: + if (clk) + ClockOut(1); + break; + case 2: // only trigger if 1st bit is high - if (clk && (reg & 0x01) == 1) { + if (clk && (reg & 0x01) == 1) ClockOut(1); - } + break; + case 3: // duplicate of Out A + Out(1, quantizer.Lookup(note + 64)); + break; + case 4: // alternative 6-bit pitch + note = Proportion( (reg >> 8 & 0x3f), 0x3f, quant_range); + Out(1, quantizer.Lookup(note + 64)); + break; } } @@ -147,7 +139,7 @@ public: quant_range = constrain(quant_range + direction, 1, 32); break; case 4: - cv2 = constrain(cv2 + direction, 0, 2); + cv2 = constrain(cv2 + direction, 0, 4); break; } } @@ -160,10 +152,7 @@ public: Pack(data, PackLocation {23,4}, length - 1); Pack(data, PackLocation {27,5}, quant_range - 1); Pack(data, PackLocation {32,4}, cv2); - - // Logarhythm mod: Since scale can exceed 6 bits now, clamp mathematically rather than surprising the user with a roll over of larger numbers - //Pack(data, PackLocation {27,6}, scale); - Pack(data, PackLocation {36,6}, constrain(scale, 0, 63)); + Pack(data, PackLocation {36,8}, constrain(scale, 0, 255)); return data; } @@ -174,7 +163,7 @@ public: length = Unpack(data, PackLocation {23,4}) + 1; quant_range = Unpack(data, PackLocation{27,5}) + 1; cv2 = Unpack(data, PackLocation {32,4}); - scale = Unpack(data, PackLocation {36,6}); + scale = Unpack(data, PackLocation {36,8}); quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); } @@ -183,7 +172,7 @@ protected: // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=p Gate"; help[HEMISPHERE_HELP_CVS] = "1=Length 2=p Mod"; - help[HEMISPHERE_HELP_OUTS] = "A=Quant5-bit B=CV8"; + help[HEMISPHERE_HELP_OUTS] = "A=Quant5-bit B=CV2"; help[HEMISPHERE_HELP_ENCODER] = "Len/Prob/Scl/Range"; // "------------------" <-- Size Guide } @@ -200,8 +189,8 @@ private: //int8_t scale; // Scale used for quantized output int scale; // Logarhythm: hold larger values //int tmp = 0; - int quant_range; // APD - int cv2 = 0; + uint8_t quant_range; // APD + uint8_t cv2 = 0; // 2nd output mode: 0=mod; 1=trig; 2=trig-on-msb; 3=duplicate of A; 4=alternate pitch void DrawSelector() { gfxBitmap(1, 14, 8, LOOP_ICON); @@ -219,13 +208,21 @@ private: gfxBitmap(41, 24, 8, NOTE4_ICON); gfxPrint(49, 25, quant_range); // APD gfxPrint(1, 35, "CV2:"); - if (cv2 == 0) { - gfxBitmap(28, 35, 8, WAVEFORM_ICON); - } else { - gfxBitmap(28, 35, 8, CLOCK_ICON); - if (cv2 == 2) { + switch (cv2) { + case 0: // modulation output + gfxBitmap(28, 35, 8, WAVEFORM_ICON); + break; + case 2: // clock out only on msb gfxPrint(36, 35, "1"); - } + case 1: // clock out icon + gfxBitmap(28, 35, 8, CLOCK_ICON); + break; + case 3: // double output A + gfxBitmap(28, 35, 8, LINK_ICON); + break; + case 4: // alternate 6-bit pitch + gfxBitmap(28, 35, 8, CV_ICON); + break; } //gfxPrint(1, 35, tmp); @@ -259,7 +256,7 @@ private: void AdvanceRegister(int prob) { // Before shifting, determine the fate of the last bit - int last = (reg >> 15) & 0x01; + int last = (reg >> (length - 1)) & 0x01; if (random(0, 99) < prob) last = 1 - last; // Shift left, then potentially add the bit from the other side @@ -309,4 +306,4 @@ uint64_t TM_OnDataRequest(bool hemisphere) { void TM_OnDataReceive(bool hemisphere, uint64_t data) { TM_instance[hemisphere].OnDataReceive(data); -} +} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 08512e7b5..8930f2cee 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 54 +#define HEMISPHERE_AVAILABLE_APPLETS 53 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ From 10387e5157f7a145675f0197ed87f71acbf09cb6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 24 Sep 2022 07:26:41 -0400 Subject: [PATCH 024/417] Fix loading saved data --- software/o_c_REV/APP_HEMISPHERE.ino | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 33282d198..39537ffc1 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -96,7 +96,11 @@ public: { int index = get_applet_index_by_id(values_[h]); SetApplet(h, index); - uint64_t data = (uint64_t(values_[8 + h]) << 48) + (uint64_t(values_[6 + h]) << 32) + (values_[4 + h] << 16) + values_[2 + h]; + uint64_t data = + (uint64_t(values_[8 + h]) << 48) | + (uint64_t(values_[6 + h]) << 32) | + (uint64_t(values_[4 + h]) << 16) | + (uint64_t(values_[2 + h])); available_applets[index].OnDataReceive(h, data); } ClockSetup.OnDataReceive(0, uint64_t(values_[HEMISPHERE_CLOCK_DATA])); From 9fb0cfe2f0c312562bb824abe3edfcb4c644fb5d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 9 Oct 2022 09:28:44 -0400 Subject: [PATCH 025/417] from qiemem: revamped AnnularFusion and Euclidean functions --- software/o_c_REV/HEM_AnnularFusion.ino | 193 +++++++++---------------- software/o_c_REV/bjorklund.cpp | 28 +--- software/o_c_REV/bjorklund.h | 2 +- software/o_c_REV/hemisphere_config.h | 2 +- 4 files changed, 77 insertions(+), 148 deletions(-) diff --git a/software/o_c_REV/HEM_AnnularFusion.ino b/software/o_c_REV/HEM_AnnularFusion.ino index f1980f846..81b1c70c2 100644 --- a/software/o_c_REV/HEM_AnnularFusion.ino +++ b/software/o_c_REV/HEM_AnnularFusion.ino @@ -21,12 +21,11 @@ // SOFTWARE. #include "bjorklund.h" -#define AF_DISPLAY_TIMEOUT 330000 -struct AFStepCoord { - uint8_t x; - uint8_t y; -}; +const int NUM_PARAMS = 3; +const int PARAM_SIZE = 5; +const int OUTER_RADIUS = 24; +const int INNER_RADIUS = 16; class AnnularFusion : public HemisphereApplet { public: @@ -36,31 +35,32 @@ public: } void Start() { - display_timeout = AF_DISPLAY_TIMEOUT; ForEachChannel(ch) { length[ch] = 16; beats[ch] = 4 + (ch * 4); - pattern[ch] = EuclideanPattern(length[ch] - 1, beats[ch], 0);; + pattern[ch] = EuclideanPattern(length[ch], beats[ch], 0);; } step = 0; - SetDisplayPositions(0, 24); - SetDisplayPositions(1, 16); last_clock = OC::CORE::ticks; } void Controller() { if (Clock(1)) step = 0; // Reset + ForEachChannel(ch) { + int rotation = Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, length[ch]); + // Store the pattern for display + pattern[ch] = EuclideanPattern(length[ch], beats[ch], rotation + offset[ch]); + } + + // Advance both rings - if (Clock(0) && !Gate(1)) { + if (Clock(0)) { last_clock = OC::CORE::ticks; ForEachChannel(ch) { - int rotation = Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, length[ch]); - // Store the pattern for display - pattern[ch] = EuclideanPattern(length[ch] - 1, beats[ch], rotation); int sb = step % length[ch]; if ((pattern[ch] >> sb) & 0x01) { ClockOut(ch); @@ -70,51 +70,53 @@ public: // Plan for the thing to run forever and ever if (++step >= length[0] * length[1]) step = 0; } - if (display_timeout > 0) --display_timeout; } void View() { gfxHeader(applet_name()); DrawSteps(); - if (display_timeout > 0) DrawEditor(); + DrawEditor(); } void OnButtonPress() { - display_timeout = AF_DISPLAY_TIMEOUT; - if (++cursor > 3) cursor = 0; + if (++cursor > 5) cursor = 0; ResetCursor(); } void OnEncoderMove(int direction) { - display_timeout = AF_DISPLAY_TIMEOUT; - int ch = cursor < 2 ? 0 : 1; - int f = cursor - (ch * 2); // Cursor function + int ch = cursor < NUM_PARAMS ? 0 : 1; + int f = cursor - (ch * NUM_PARAMS); // Cursor function if (f == 0) { length[ch] = constrain(length[ch] + direction, 3, 32); if (beats[ch] > length[ch]) beats[ch] = length[ch]; - SetDisplayPositions(ch, 24 - (8 * ch)); + if (offset[ch] > length[ch]) offset[ch] = length[ch]; } if (f == 1) { beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); } + if (f == 2) { + offset[ch] = constrain(offset[ch] + direction, 0, length[ch] - 1); + } } - + uint64_t OnDataRequest() { uint64_t data = 0; - Pack(data, PackLocation {0,4}, length[0] - 1); - Pack(data, PackLocation {4,4}, beats[0] - 1); - Pack(data, PackLocation {8,4}, length[1] - 1); - Pack(data, PackLocation {12,4}, beats[1] - 1); + Pack(data, PackLocation {0 * PARAM_SIZE, PARAM_SIZE}, length[0] - 1); + Pack(data, PackLocation {1 * PARAM_SIZE, PARAM_SIZE}, beats[0] - 1); + Pack(data, PackLocation {2 * PARAM_SIZE, PARAM_SIZE}, length[1] - 1); + Pack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}, beats[1] - 1); + Pack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}, offset[0]); + Pack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}, offset[1]); return data; } void OnDataReceive(uint64_t data) { - length[0] = Unpack(data, PackLocation {0,4}) + 1; - beats[0] = Unpack(data, PackLocation {4,4}) + 1; - length[1] = Unpack(data, PackLocation {8,4}) + 1; - beats[1] = Unpack(data, PackLocation {12,4}) + 1; - SetDisplayPositions(0, 24); - SetDisplayPositions(1, 16); + length[0] = Unpack(data, PackLocation {0 * PARAM_SIZE, PARAM_SIZE}) + 1; + beats[0] = Unpack(data, PackLocation {1 * PARAM_SIZE, PARAM_SIZE}) + 1; + length[1] = Unpack(data, PackLocation {2 * PARAM_SIZE, PARAM_SIZE}) + 1; + beats[1] = Unpack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}) + 1; + offset[0] = Unpack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}); + offset[1] = Unpack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}); } protected: @@ -123,123 +125,64 @@ protected: help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; help[HEMISPHERE_HELP_CVS] = "Rotate 1=Ch1 2=Ch2"; help[HEMISPHERE_HELP_OUTS] = "Clock A=Ch1 B=Ch2"; - help[HEMISPHERE_HELP_ENCODER] = "Length/Hits Ch1,2"; + help[HEMISPHERE_HELP_ENCODER] = "Len/Hits/Rot Ch1,2"; // "------------------" <-- Size Guide } - + private: int step; int cursor = 0; // Ch1: 0=Length, 1=Hits; Ch2: 2=Length 3=Hits - AFStepCoord disp_coord[2][32]; uint32_t pattern[2]; int last_clock; - uint32_t display_timeout; - + // Settings int length[2]; int beats[2]; + int offset[2]; void DrawSteps() { - ForEachChannel(ch) - { - DrawActiveSegment(ch); - DrawPatternPoints(ch); - } - } - - void DrawActiveSegment(int ch) { - if (last_clock && OC::CORE::ticks - last_clock < 166666) { - int s1 = step % length[ch]; - int s2 = s1 + 1 == length[ch] ? 0 : s1 + 1; - - AFStepCoord s1_c = disp_coord[ch][s1]; - AFStepCoord s2_c = disp_coord[ch][s2]; - gfxLine(s1_c.x, s1_c.y, s2_c.x, s2_c.y); - } - } + //int spacing = 1; + gfxLine(0, 45, 63, 45); + gfxLine(0, 62, 63, 62); + gfxLine(0, 53, 63, 53); + gfxLine(0, 54, 63, 54); + ForEachChannel(ch) { + for (int i = 0; i < 16; i++) { + if ((pattern[ch] >> ((i + step) % length[ch])) & 0x1) { + gfxRect(4 * i + 1, 48 + 9 * ch, 3, 3); + //gfxLine(4 * i + 2, 47 + 9 * ch, 4 * i + 2, 47 + 9 * ch + 4); + } else { + gfxPixel(4 * i + 2, 47 + 9 * ch + 2); + } - void DrawPatternPoints(int ch) { - for (int p = 0; p < length[ch]; p++) - { - if ((pattern[ch] >> p) & 0x01) { - gfxPixel(disp_coord[ch][p].x, disp_coord[ch][p].y); - gfxPixel(disp_coord[ch][p].x + 1, disp_coord[ch][p].y); + if ((i + step) % length[ch] == 0) { + //gfxLine(4 * i, 46 + 9 * ch, 4 * i, 52 + 9 * ch); + gfxLine(4 * i, 46 + 9 * ch, 4 * i, 46 + 9 * ch + 1); + gfxLine(4 * i, 52 + 9 * ch - 1, 4 * i, 52 + 9 * ch); + } } } } void DrawEditor() { - int ch = cursor < 2 ? 0 : 1; // Cursor channel - int f = cursor - (ch * 2); // Cursor function + int spacing = 25; - // Length cursor - gfxBitmap(1, 15, 8, LOOP_ICON); - gfxPrint(12 + pad(10, length[ch]), 15, length[ch]); - if (f == 0) gfxCursor(13, 23, 12); + gfxBitmap(1 + 0 * spacing, 15, 8, LOOP_ICON); + gfxBitmap(1 + 1 * spacing, 15, 8, X_NOTE_ICON); + gfxBitmap(1 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); - // Beats cursor - gfxBitmap(1, 25, 8, X_NOTE_ICON); - gfxPrint(12 + pad(10, beats[ch]), 25, beats[ch]); - if (f == 1) gfxCursor(13, 33, 12); + ForEachChannel (ch) { + int y = 15 + 10 * (ch + 1); + gfxPrint(1 + 0 * spacing, y, length[ch]); + if (cursor == 0 + ch * NUM_PARAMS) gfxCursor(1 + 0 * spacing, y + 7, 12); - // Ring indicator - gfxCircle(8, 52, 8); - gfxCircle(8, 52, 4); + gfxPrint(1 + 1 * spacing, y, beats[ch]); + if (cursor == 1 + ch * NUM_PARAMS) gfxCursor(1 + 1 * spacing, y + 7, 12); - if (ch == 0) gfxCircle(8, 52, 7); - else gfxCircle(8, 52, 5); - } - - /* Get coordinates of circle in two halves, from the top and from the bottom */ - void SetDisplayPositions(int ch, int r) { - int cx = 31; // Center coordinates - int cy = 39; - int di = 0; // Display index (positions actually used in the display) - int c_count = 0; // Count of pixels along the circumference - int x_per_step = (r * 4) / 32; - uint32_t pattern = EuclideanPattern(31, length[ch], 0); - - // Sweep across the top of the circle looking for positions within the - // radius of the circle. Left to right: - for (uint8_t x = 0; x < 63; x++) - { - // Top down - for (uint8_t y = 0; y < 63; y++) - { - int rx = cx - x; // Positions relative to center - int ry = cy - y; - - // Is this point within the radius? - if (rx * rx + ry * ry < r * r + 1) { - if (c_count++ % x_per_step == 0) { - if (pattern & 0x01) disp_coord[ch][di++] = AFStepCoord {x, y}; - pattern = pattern >> 0x01; - } - break; // Only use the first point - } - } + gfxPrint(1 + 2 * spacing, y, offset[ch]); + if (cursor == 2 + ch * NUM_PARAMS) gfxCursor(1 + 2 * spacing, y + 7, 12); } - // Sweep across the top of the circle looking for positions within the - // radius of the circle. Right to left: - for (uint8_t x = 63; x > 0; x--) - { - // Bottom up - for (uint8_t y = 63; y > 0; y--) - { - int rx = cx - x; // Positions relative to center - int ry = cy - y; - - // Is this point within the radius? - if (rx * rx + ry * ry < r * r + 1) { - if (c_count++ % x_per_step == 0) { - if (pattern & 0x01) disp_coord[ch][di++] = AFStepCoord {x, y}; - pattern = pattern >> 0x01; - } - break; // Only use the first point - } - } - } } }; diff --git a/software/o_c_REV/bjorklund.cpp b/software/o_c_REV/bjorklund.cpp index af7383b80..f4f66e1af 100644 --- a/software/o_c_REV/bjorklund.cpp +++ b/software/o_c_REV/bjorklund.cpp @@ -1329,31 +1329,17 @@ const uint32_t bjorklund_patterns[] = { }; bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint32_t clock) { - if (num_beats > (num_steps + 1)) { - num_beats = num_steps + 1; - } - uint32_t pattern = bjorklund_patterns[((num_steps - 1) * 33) + num_beats]; - if (rotation) { - // Serial.print(pattern); - // Serial.print("\n"); - rotation = rotation % (num_steps + 1); - pattern = rotl32(pattern, num_steps, rotation) ; - // Serial.print(pattern); - // Serial.print("\n------\n"); - } - uint8_t position = clock % (num_steps + 1) ; - return static_cast(pattern & (0x01 << position)) ; + uint32_t pattern = EuclideanPattern(num_steps, num_beats, rotation); + clock %= num_steps; + return static_cast(pattern & (0x01 << clock)) ; } uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation) { - if (num_beats > (num_steps + 1)) { - num_beats = num_steps + 1; - } - uint32_t pattern = bjorklund_patterns[((num_steps - 1) * 33) + num_beats]; - if ((rotation) && ((num_steps + 1) != num_beats)) { - rotation = rotation % (num_steps + 1); + num_beats %= (num_steps + 1); + uint32_t pattern = bjorklund_patterns[((num_steps - 2) * 33) + num_beats]; + if (rotation) { + rotation %= num_steps; pattern = rotl32(pattern, num_steps, rotation) ; } - return pattern; } diff --git a/software/o_c_REV/bjorklund.h b/software/o_c_REV/bjorklund.h index 966ceb065..cabb3fece 100644 --- a/software/o_c_REV/bjorklund.h +++ b/software/o_c_REV/bjorklund.h @@ -45,7 +45,7 @@ inline uint32_t rotl32(uint32_t input, unsigned int length, unsigned int count) __attribute__((always_inline)); inline uint32_t rotl32(uint32_t input, unsigned int length, unsigned int count) { input &= ~(0xffffffff << length); - return (input << count) | (input >> (length - count + 1)); // off-by-ones or parenthesis mismatch likely + return (input << count) | (input >> (length - count)); // off-by-ones or parenthesis mismatch likely } bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint32_t clock); diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 8930f2cee..4bb1e251a 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -17,7 +17,7 @@ #define HEMISPHERE_APPLETS { \ DECLARE_APPLET( 8, 0x01, ADSREG), \ DECLARE_APPLET( 34, 0x01, ADEG), \ - DECLARE_APPLET( 15, 0x02, Euclid), \ + DECLARE_APPLET( 15, 0x02, AnnularFusion), \ DECLARE_APPLET( 47, 0x09, ASR), \ DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ DECLARE_APPLET( 51, 0x80, BugCrack), \ From cd46a2901005d8207441fa451ea273f6cc4b2958 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 9 Oct 2022 15:55:40 -0400 Subject: [PATCH 026/417] AnnularFusion: implement assignable CV input mod based on adegani's Euclid applet design CV is continuously processed so the display updates in realtime, rather than only on clock. --- software/o_c_REV/HEM_AnnularFusion.ino | 148 ++++++++++++++++--------- 1 file changed, 97 insertions(+), 51 deletions(-) diff --git a/software/o_c_REV/HEM_AnnularFusion.ino b/software/o_c_REV/HEM_AnnularFusion.ino index 81b1c70c2..486b7d870 100644 --- a/software/o_c_REV/HEM_AnnularFusion.ino +++ b/software/o_c_REV/HEM_AnnularFusion.ino @@ -22,10 +22,8 @@ #include "bjorklund.h" -const int NUM_PARAMS = 3; +const int NUM_PARAMS = 4; const int PARAM_SIZE = 5; -const int OUTER_RADIUS = 24; -const int INNER_RADIUS = 16; class AnnularFusion : public HemisphereApplet { public: @@ -37,38 +35,60 @@ public: void Start() { ForEachChannel(ch) { - length[ch] = 16; - beats[ch] = 4 + (ch * 4); - pattern[ch] = EuclideanPattern(length[ch], beats[ch], 0);; + actual_length[ch] = length[ch] = 16; + actual_beats[ch] = beats[ch] = 4 + (ch * 4); + actual_offset[ch] = offset[ch] = 0; + pattern[ch] = EuclideanPattern(length[ch], beats[ch], 0); } step = 0; - last_clock = OC::CORE::ticks; } void Controller() { if (Clock(1)) step = 0; // Reset + int cv_data[2]; + cv_data[0] = DetentedIn(0); + cv_data[1] = DetentedIn(1); + + // continuously recalculate pattern with CV offsets ForEachChannel(ch) { - int rotation = Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, length[ch]); + actual_length[ch] = length[ch]; + actual_beats[ch] = beats[ch]; + actual_offset[ch] = offset[ch]; + + // process CV inputs + ForEachChannel(cv_ch) { + switch (cv_dest[cv_ch] - ch * (NUM_PARAMS-1)) { + case 0: // length + actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 1, 32); + break; + case 1: // beats + actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); + break; + case 2: // offset + actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 0, actual_length[ch]-1); + break; + default: break; + } + } + // Store the pattern for display - pattern[ch] = EuclideanPattern(length[ch], beats[ch], rotation + offset[ch]); + pattern[ch] = EuclideanPattern(actual_length[ch], actual_beats[ch], actual_offset[ch]); } - - // Advance both rings + // Process triggers and step forward on clock if (Clock(0)) { - last_clock = OC::CORE::ticks; - ForEachChannel(ch) - { - int sb = step % length[ch]; + ForEachChannel(ch) { + // actually output the triggers + int sb = step % actual_length[ch]; if ((pattern[ch] >> sb) & 0x01) { ClockOut(ch); } } // Plan for the thing to run forever and ever - if (++step >= length[0] * length[1]) step = 0; + if (++step >= actual_length[0] * actual_length[1]) step = 0; } } @@ -79,23 +99,27 @@ public: } void OnButtonPress() { - if (++cursor > 5) cursor = 0; + if (++cursor > 7) cursor = 0; ResetCursor(); } void OnEncoderMove(int direction) { int ch = cursor < NUM_PARAMS ? 0 : 1; int f = cursor - (ch * NUM_PARAMS); // Cursor function - if (f == 0) { - length[ch] = constrain(length[ch] + direction, 3, 32); + switch (f) { + case 0: + actual_length[ch] = length[ch] = constrain(length[ch] + direction, 3, 32); if (beats[ch] > length[ch]) beats[ch] = length[ch]; - if (offset[ch] > length[ch]) offset[ch] = length[ch]; - } - if (f == 1) { - beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); - } - if (f == 2) { - offset[ch] = constrain(offset[ch] + direction, 0, length[ch] - 1); + if (offset[ch] >= length[ch]) offset[ch] = length[ch]-1; + break; + case 1: + actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); + break; + case 2: + actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] - 1); + break; + case 3: // CV destination + cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, 5); } } @@ -107,25 +131,29 @@ public: Pack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}, beats[1] - 1); Pack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}, offset[0]); Pack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}, offset[1]); + Pack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}, cv_dest[0]); + Pack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}, cv_dest[1]); return data; } void OnDataReceive(uint64_t data) { - length[0] = Unpack(data, PackLocation {0 * PARAM_SIZE, PARAM_SIZE}) + 1; - beats[0] = Unpack(data, PackLocation {1 * PARAM_SIZE, PARAM_SIZE}) + 1; - length[1] = Unpack(data, PackLocation {2 * PARAM_SIZE, PARAM_SIZE}) + 1; - beats[1] = Unpack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}) + 1; - offset[0] = Unpack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}); - offset[1] = Unpack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}); + actual_length[0] = length[0] = Unpack(data, PackLocation {0 * PARAM_SIZE, PARAM_SIZE}) + 1; + actual_beats[0] = beats[0] = Unpack(data, PackLocation {1 * PARAM_SIZE, PARAM_SIZE}) + 1; + actual_length[1] = length[1] = Unpack(data, PackLocation {2 * PARAM_SIZE, PARAM_SIZE}) + 1; + actual_beats[1] = beats[1] = Unpack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}) + 1; + actual_offset[0] = offset[0] = Unpack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}); + actual_offset[1] = offset[1] = Unpack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}); + cv_dest[0] = Unpack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}); + cv_dest[1] = Unpack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}); } protected: void SetHelp() { // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; - help[HEMISPHERE_HELP_CVS] = "Rotate 1=Ch1 2=Ch2"; + help[HEMISPHERE_HELP_CVS] = "Assignable"; help[HEMISPHERE_HELP_OUTS] = "Clock A=Ch1 B=Ch2"; - help[HEMISPHERE_HELP_ENCODER] = "Len/Hits/Rot Ch1,2"; + help[HEMISPHERE_HELP_ENCODER] = "Len/Hits/Rot/CV"; // "------------------" <-- Size Guide } @@ -133,12 +161,16 @@ private: int step; int cursor = 0; // Ch1: 0=Length, 1=Hits; Ch2: 2=Length 3=Hits uint32_t pattern[2]; - int last_clock; // Settings - int length[2]; - int beats[2]; - int offset[2]; + uint8_t length[2]; + uint8_t beats[2]; + uint8_t offset[2]; + uint8_t actual_length[2]; + uint8_t actual_beats[2]; + uint8_t actual_offset[2]; + + uint8_t cv_dest[2]; void DrawSteps() { //int spacing = 1; @@ -148,14 +180,14 @@ private: gfxLine(0, 54, 63, 54); ForEachChannel(ch) { for (int i = 0; i < 16; i++) { - if ((pattern[ch] >> ((i + step) % length[ch])) & 0x1) { + if ((pattern[ch] >> ((i + step) % actual_length[ch])) & 0x1) { gfxRect(4 * i + 1, 48 + 9 * ch, 3, 3); //gfxLine(4 * i + 2, 47 + 9 * ch, 4 * i + 2, 47 + 9 * ch + 4); } else { gfxPixel(4 * i + 2, 47 + 9 * ch + 2); } - if ((i + step) % length[ch] == 0) { + if ((i + step) % actual_length[ch] == 0) { //gfxLine(4 * i, 46 + 9 * ch, 4 * i, 52 + 9 * ch); gfxLine(4 * i, 46 + 9 * ch, 4 * i, 46 + 9 * ch + 1); gfxLine(4 * i, 52 + 9 * ch - 1, 4 * i, 52 + 9 * ch); @@ -165,22 +197,36 @@ private: } void DrawEditor() { - int spacing = 25; + int spacing = 18; - gfxBitmap(1 + 0 * spacing, 15, 8, LOOP_ICON); - gfxBitmap(1 + 1 * spacing, 15, 8, X_NOTE_ICON); - gfxBitmap(1 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); + gfxBitmap(4 + 0 * spacing, 15, 8, LOOP_ICON); + gfxBitmap(4 + 1 * spacing, 15, 8, X_NOTE_ICON); + gfxBitmap(4 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); ForEachChannel (ch) { int y = 15 + 10 * (ch + 1); - gfxPrint(1 + 0 * spacing, y, length[ch]); - if (cursor == 0 + ch * NUM_PARAMS) gfxCursor(1 + 0 * spacing, y + 7, 12); - - gfxPrint(1 + 1 * spacing, y, beats[ch]); - if (cursor == 1 + ch * NUM_PARAMS) gfxCursor(1 + 1 * spacing, y + 7, 12); + gfxPrint(4 + 0 * spacing, y, actual_length[ch]); + gfxPrint(4 + 1 * spacing, y, actual_beats[ch]); + gfxPrint(4 + 2 * spacing, y, actual_offset[ch]); + + int f = cursor - ch * NUM_PARAMS; + switch (f) { + case 0: + case 1: + case 2: + gfxCursor(4 + f * spacing, y + 7, 12); + break; + case 3: // CV dest selection + gfxBitmap(1 + 3 * spacing, y, 8, CV_ICON); + break; + } - gfxPrint(1 + 2 * spacing, y, offset[ch]); - if (cursor == 2 + ch * NUM_PARAMS) gfxCursor(1 + 2 * spacing, y + 7, 12); + // CV assignment indicators + ForEachChannel(ch_dest) { + int ff = cv_dest[ch_dest] - (NUM_PARAMS-1)*ch; + if (ff >= 0 && ff < (NUM_PARAMS-1)) + gfxBitmap(ff * spacing, y, 3, ch_dest?SUB_TWO:SUP_ONE); + } } } From c14971155ed86dec9ea2d103ea2f143fd6beafa0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 14 Oct 2022 22:27:37 -0400 Subject: [PATCH 027/417] Sequence5: rewrite/simplify/cleanup --- software/o_c_REV/HEM_Sequence5.ino | 42 ++++++++++-------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/software/o_c_REV/HEM_Sequence5.ino b/software/o_c_REV/HEM_Sequence5.ino index 75d335a1a..5b197824a 100644 --- a/software/o_c_REV/HEM_Sequence5.ino +++ b/software/o_c_REV/HEM_Sequence5.ino @@ -29,37 +29,27 @@ public: void Start() { for (int s = 0; s < 5; s++) note[s] = random(0, 30); - play = 1; - reset = true; } void Controller() { - // Reset sequencer - if (Clock(1)) { + if (Clock(1)) { // reset step = 0; reset = true; - ClockOut(1); } - - int transpose = 0; - if (DetentedIn(0)) { - transpose = In(0) / 128; // 128 ADC steps per semitone + if (Clock(0)) { // clock + if (!reset) Advance(step); + reset = false; + // send trigger on first step + if (step == 0) ClockOut(1); } + + // continuously compute the note + int transpose = DetentedIn(0) / 128; // 128 ADC steps per semitone int play_note = note[step] + 60 + transpose; play_note = constrain(play_note, 0, 127); + // set CV output + Out(0, MIDIQuantizer::CV(play_note)); - if (Clock(0)) StartADCLag(); - - if (EndOfADCLag()) { - Advance(step); - if (step == 0) ClockOut(1); - play = 1; - } - - if (play) { - int cv = MIDIQuantizer::CV(play_note); - Out(0, cv); - } } void View() { @@ -79,7 +69,6 @@ public: note[cursor] = constrain(note[cursor] += direction, 0, 30); muted &= ~(0x01 << cursor); } - play = 1; // Replay the changed step in the controller, so it can be heard } uint64_t OnDataRequest() { @@ -115,13 +104,10 @@ private: char muted = 0; // Bitfield for muted steps; ((muted >> step) & 1) means muted int note[5]; // Sequence value (0 - 30) int step = 0; // Current sequencer step - bool play; // Play the note - bool reset; + bool reset = true; void Advance(int starting_point) { - if (!reset) step++; - reset = false; - if (step == 5) step = 0; + if (++step == 5) step = 0; // If all the steps have been muted, stay where we were if (step_is_muted(step) && step != starting_point) Advance(starting_point); } @@ -195,4 +181,4 @@ uint64_t Sequence5_OnDataRequest(bool hemisphere) { void Sequence5_OnDataReceive(bool hemisphere, uint64_t data) { Sequence5_instance[hemisphere].OnDataReceive(data); -} +} From d4deb1709565bb6946f64b31f330036cfd5cebd1 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 8 Nov 2022 21:38:17 -0500 Subject: [PATCH 028/417] Prevent overflow condition in EndOfADCLag() function possible fix for strange behavior when left idle for a long time --- software/o_c_REV/HemisphereApplet.h | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index a8162adda..b377f0cfa 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -447,6 +447,7 @@ class HemisphereApplet { } bool EndOfADCLag(int ch = 0) { + if (adc_lag_countdown[ch] < 0) return false; return (--adc_lag_countdown[ch] == 0); } From 03d9554549d739e8564369fc91de9725bcbe2288 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 15 Nov 2022 04:01:05 -0500 Subject: [PATCH 029/417] Upgraded ShiftReg to DualTM * Featuring two concurrent 32-bit registers with shared length, probability, scale, and range * Outputs assignable as pitch, mod, trig or gate from either register * Only 16 bits of register 1 are currently saved --- .../{HEM_TM.ino => HEM_TM.ino.disabled} | 0 software/o_c_REV/HEM_TM2.ino | 350 ++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 2 +- 3 files changed, 351 insertions(+), 1 deletion(-) rename software/o_c_REV/{HEM_TM.ino => HEM_TM.ino.disabled} (100%) create mode 100644 software/o_c_REV/HEM_TM2.ino diff --git a/software/o_c_REV/HEM_TM.ino b/software/o_c_REV/HEM_TM.ino.disabled similarity index 100% rename from software/o_c_REV/HEM_TM.ino rename to software/o_c_REV/HEM_TM.ino.disabled diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino new file mode 100644 index 000000000..c30841c42 --- /dev/null +++ b/software/o_c_REV/HEM_TM2.ino @@ -0,0 +1,350 @@ +// Copyright (c) 2018, Jason Justian +// +// Based on Braids Quantizer, Copyright 2015 Émilie Gillet. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +/* + * Turing Machine based on https://thonk.co.uk/documents/random%20sequencer%20documentation%20v2-1%20THONK%20KIT_LargeImages.pdf + * + * Thanks to Tom Whitwell for creating the concept, and for clarifying some things + * Thanks to Jon Wheeler for the CV length and probability updates + * + * adapted as DualTM by djphazer (Nicholas J. Michalek) + */ + +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_scales.h" + +#define TM2_MAX_SCALE OC::Scales::NUM_SCALES + +#define TM2_MIN_LENGTH 2 +#define TM2_MAX_LENGTH 32 + +class DualTM : public HemisphereApplet { +public: + + const char* applet_name() { + return "DualTM"; + } + + void Start() { + reg = random(0, 65535); + reg2 = ~reg; + p = 0; + length = 16; + quant_range = 24; //APD: Quantizer range + cursor = 0; + quantizer.Init(); + scale = OC::Scales::SCALE_SEMI; + quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone + } + + void Controller() { + + // CV 1 control over length + int lengthCv = DetentedIn(0); + if (lengthCv < 0) length = TM2_MIN_LENGTH; + if (lengthCv > 0) { + length = constrain(ProportionCV(lengthCv, TM2_MAX_LENGTH + 1), TM2_MIN_LENGTH, TM2_MAX_LENGTH); + } + + // CV 2 bi-polar modulation of probability + int pCv = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100); + bool clk = Clock(0); + + if (clk) { + // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same + int prob = (cursor == 1 || Gate(1)) ? p + pCv : 0; + prob = constrain(prob, 0, 100); + + // Grab the bit that's about to be shifted away + int last = (reg >> (length - 1)) & 0x01; + int last2 = (reg2 >> (length - 1)) & 0x01; + + // Does it change? + if (random(0, 99) < prob) last = 1 - last; + if (random(0, 99) < prob) last2 = 1 - last2; + + // Shift left, then potentially add the bit from the other side + reg = (reg << 1) + last; + reg2 = (reg2 << 1) + last2; + } + + // Send 5-bit scaled and quantized CV + // scaled = note * quant_range / 0x1f + int32_t note = Proportion(reg & 0x1f, 0x1f, quant_range); + int32_t note2 = Proportion(reg2 & 0x1f, 0x1f, quant_range); + + /* + note *= quant_range; + simfloat x = int2simfloat(note) / (int32_t)0x1f; + note = simfloat2int(x); + + Out(0, quantizer.Lookup(note + 64)); + */ + + ForEachChannel(ch) { + switch (outmode[ch]) { + case 0: // pitch 1 + Out(ch, quantizer.Lookup(note + 64)); + break; + case 1: // pitch 2 + Out(ch, quantizer.Lookup(note2 + 64)); + break; + case 2: // mod A - 8-bit proportioned CV + Out(ch, Proportion(reg & 0x00ff, 255, HEMISPHERE_MAX_CV) ); + break; + case 3: // mod B - 8-bit proportioned CV + Out(ch, Proportion(reg2 & 0x00ff, 255, HEMISPHERE_MAX_CV) ); + break; + case 4: // trig A + if (clk && (reg & 0x01) == 1) // trigger if 1st bit is high + ClockOut(ch); + break; + case 5: // trig B + if (clk && (reg2 & 0x01) == 1) // trigger if 1st bit is high + ClockOut(ch); + break; + case 6: // gate A + Out(ch, (reg & 0x01)*HEMISPHERE_MAX_CV); + break; + case 7: // gate B + Out(ch, (reg2 & 0x01)*HEMISPHERE_MAX_CV); + break; + } + } + } + + void View() { + gfxHeader(applet_name()); + DrawSelector(); + DrawIndicator(); + } + + void OnButtonPress() { + isEditing = !isEditing; + } + + void OnEncoderMove(int direction) { + if (!isEditing) { + cursor += direction; + if (cursor < 0) cursor = 5; + if (cursor > 5) cursor = 0; + + ResetCursor(); // Reset blink so it's immediately visible when moved + } else { + switch (cursor) { + case 0: + length = constrain(length + direction, TM2_MIN_LENGTH, TM2_MAX_LENGTH); + break; + case 1: + p = constrain(p + direction, 0, 100); + break; + case 2: + scale += direction; + if (scale >= TM2_MAX_SCALE) scale = 0; + if (scale < 0) scale = TM2_MAX_SCALE - 1; + quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + break; + case 3: + quant_range = constrain(quant_range + direction, 1, 32); + break; + case 4: + outmode[0] = constrain(outmode[0] + direction, 0, 7); + break; + case 5: + outmode[1] = constrain(outmode[1] + direction, 0, 7); + break; + } + } + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation {0,16}, reg); + Pack(data, PackLocation {16,7}, p); + Pack(data, PackLocation {23,4}, length - 1); + Pack(data, PackLocation {27,5}, quant_range - 1); + Pack(data, PackLocation {32,3}, outmode[0]); + Pack(data, PackLocation {35,3}, outmode[1]); + + Pack(data, PackLocation {38,6}, constrain(scale, 0, 63)); + + return data; + } + + void OnDataReceive(uint64_t data) { + reg = Unpack(data, PackLocation {0,16}); + reg2 = ~reg; + p = Unpack(data, PackLocation {16,7}); + length = Unpack(data, PackLocation {23,4}) + 1; + quant_range = Unpack(data, PackLocation{27,5}) + 1; + outmode[0] = Unpack(data, PackLocation {32,3}); + outmode[1] = Unpack(data, PackLocation {35,3}); + scale = Unpack(data, PackLocation {38,6}); + quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=p Gate"; + help[HEMISPHERE_HELP_CVS] = "1=Length 2=p Mod"; + help[HEMISPHERE_HELP_OUTS] = "A=Quant5-bit B=CV2"; + help[HEMISPHERE_HELP_ENCODER] = "Len/Prob/Scl/Range"; + // "------------------" <-- Size Guide + } + +private: + int length; // Sequence length + int cursor; // 0 = length, 1 = p, 2 = scale + bool isEditing = false; + braids::Quantizer quantizer; + + // Settings + uint32_t reg; // 32-bit sequence register + uint32_t reg2; // DJP + int p; // Probability of bit flipping on each cycle + int scale; // Scale used for quantized output + int quant_range; + + // output modes: + // 0=pitch A; 2=mod A; 4=trig A; 6=gate A + // 1=pitch B; 3=mod B; 5=trig B; 7=gate B + int outmode[2] = {0, 5}; + + void DrawSelector() { + gfxBitmap(1, 14, 8, LOOP_ICON); + gfxPrint(12 + pad(10, length), 15, length); + gfxPrint(32, 15, "p="); + if (cursor == 1 || Gate(1)) { // p unlocked + int pCv = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100); + int prob = constrain(p + pCv, 0, 100); + gfxPrint(pad(100, prob), prob); + } else { // p is disabled + gfxBitmap(49, 14, 8, LOCK_ICON); + } + gfxBitmap(1, 24, 8, SCALE_ICON); + gfxPrint(12, 25, OC::scale_names_short[scale]); + gfxBitmap(41, 24, 8, NOTE4_ICON); + gfxPrint(49, 25, quant_range); // APD + gfxPrint(1, 35, "A:"); + gfxPrint(32, 35, "B:"); + + ForEachChannel(ch) { + switch (outmode[ch]) { + case 0: // pitch output + case 1: + gfxBitmap(13 + ch*32, 35, 8, NOTE_ICON); + break; + case 2: // mod output + case 3: + gfxBitmap(13 + ch*32, 35, 8, WAVEFORM_ICON); + break; + case 4: // trig output + case 5: + gfxBitmap(13 + ch*32, 35, 8, CLOCK_ICON); + break; + case 6: // gate output + case 7: + gfxBitmap(13 + ch*32, 35, 8, METER_ICON); + break; + } + // indicator for reg1 or reg2 + gfxBitmap(22+ch*32, 35, 3, (outmode[ch] % 2) ? SUB_TWO : SUP_ONE); + } + + switch (cursor) { + case 0: gfxCursor(13, 23, 12); break; // Length Cursor + case 1: gfxCursor(45, 23, 18); break; // Probability Cursor + case 2: gfxCursor(12, 33, 25); break; // Scale Cursor + case 3: gfxCursor(49, 33, 14); break; // Quant Range Cursor // APD + case 4: gfxCursor(12, 43, 10); break; // Out A + case 5: gfxCursor(44, 43, 10); break; // Out B + } + if (isEditing) { + switch (cursor) { + case 0: gfxInvert(13, 14, 12, 9); break; + case 1: gfxInvert(45, 14, 18, 9); break; + case 2: gfxInvert(12, 24, 25, 9); break; + case 3: gfxInvert(49, 24, 14, 9); break; + case 4: gfxInvert(12, 34, 14, 9); break; + case 5: gfxInvert(44, 34, 14, 9); break; + } + } + } + + void DrawIndicator() { + gfxLine(0, 45, 63, 45); + gfxLine(0, 62, 63, 62); + for (int b = 0; b < 16; b++) + { + int v = (reg >> b) & 0x01; + int v2 = (reg2 >> b) & 0x01; + if (v) gfxRect(60 - (4 * b), 47, 3, 7); + if (v2) gfxRect(60 - (4 * b), 54, 3, 7); + } + } + +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to DualTM, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +DualTM DualTM_instance[2]; + +void DualTM_Start(bool hemisphere) { + DualTM_instance[hemisphere].BaseStart(hemisphere); +} + +void DualTM_Controller(bool hemisphere, bool forwarding) { + DualTM_instance[hemisphere].BaseController(forwarding); +} + +void DualTM_View(bool hemisphere) { + DualTM_instance[hemisphere].BaseView(); +} + +void DualTM_OnButtonPress(bool hemisphere) { + DualTM_instance[hemisphere].OnButtonPress(); +} + +void DualTM_OnEncoderMove(bool hemisphere, int direction) { + DualTM_instance[hemisphere].OnEncoderMove(direction); +} + +void DualTM_ToggleHelpScreen(bool hemisphere) { + DualTM_instance[hemisphere].HelpScreen(); +} + +uint64_t DualTM_OnDataRequest(bool hemisphere) { + return DualTM_instance[hemisphere].OnDataRequest(); +} + +void DualTM_OnDataReceive(bool hemisphere, uint64_t data) { + DualTM_instance[hemisphere].OnDataReceive(data); +} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 763c090a6..8a6ddb910 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -52,7 +52,7 @@ DECLARE_APPLET( 23, 0x80, Scope), \ DECLARE_APPLET( 14, 0x02, Sequence5), \ DECLARE_APPLET( 48, 0x45, ShiftGate), \ - DECLARE_APPLET( 18, 0x02, TM), \ + DECLARE_APPLET( 18, 0x02, DualTM), \ DECLARE_APPLET( 58, 0x01, Shredder), \ DECLARE_APPLET( 36, 0x04, Shuffle), \ DECLARE_APPLET( 7, 0x01, SkewedLFO), \ From aa2e67e5df20d0364849d2b9d26c3727c8aa493f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 15 Nov 2022 04:43:02 -0500 Subject: [PATCH 030/417] DualTM: save data tweaks One whole 32-bit register is now saved --- software/o_c_REV/HEM_TM2.ino | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index c30841c42..52023b852 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -179,28 +179,29 @@ public: uint64_t OnDataRequest() { uint64_t data = 0; - Pack(data, PackLocation {0,16}, reg); - Pack(data, PackLocation {16,7}, p); - Pack(data, PackLocation {23,4}, length - 1); - Pack(data, PackLocation {27,5}, quant_range - 1); - Pack(data, PackLocation {32,3}, outmode[0]); - Pack(data, PackLocation {35,3}, outmode[1]); + Pack(data, PackLocation {0,7}, p); + Pack(data, PackLocation {7,5}, length - 1); + Pack(data, PackLocation {12,5}, quant_range - 1); + Pack(data, PackLocation {17,3}, outmode[0]); + Pack(data, PackLocation {20,3}, outmode[1]); + Pack(data, PackLocation {23,8}, constrain(scale, 0, 255)); - Pack(data, PackLocation {38,6}, constrain(scale, 0, 63)); + Pack(data, PackLocation {32,32}, reg); return data; } void OnDataReceive(uint64_t data) { - reg = Unpack(data, PackLocation {0,16}); - reg2 = ~reg; - p = Unpack(data, PackLocation {16,7}); - length = Unpack(data, PackLocation {23,4}) + 1; - quant_range = Unpack(data, PackLocation{27,5}) + 1; - outmode[0] = Unpack(data, PackLocation {32,3}); - outmode[1] = Unpack(data, PackLocation {35,3}); - scale = Unpack(data, PackLocation {38,6}); + p = Unpack(data, PackLocation {0,7}); + length = Unpack(data, PackLocation {7,5}) + 1; + quant_range = Unpack(data, PackLocation{12,5}) + 1; + outmode[0] = Unpack(data, PackLocation {17,3}); + outmode[1] = Unpack(data, PackLocation {20,3}); + scale = Unpack(data, PackLocation {23,8}); quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + + reg = Unpack(data, PackLocation {32,32}); + reg2 = ~reg; } protected: From 8250ed1b75c5138c18d2ad13eb85b45430cf2e4c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 15 Nov 2022 21:32:41 -0500 Subject: [PATCH 031/417] DualTM: rewrite CV input handling, make mod out bipolar Length modulation via CV is now a bipolar offset Mod output is also bipolar --- software/o_c_REV/HEM_TM2.ino | 42 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 52023b852..030a1ddc5 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -50,7 +50,7 @@ public: reg2 = ~reg; p = 0; length = 16; - quant_range = 24; //APD: Quantizer range + quant_range = 24; cursor = 0; quantizer.Init(); scale = OC::Scales::SCALE_SEMI; @@ -58,26 +58,22 @@ public: } void Controller() { + bool clk = Clock(0); - // CV 1 control over length - int lengthCv = DetentedIn(0); - if (lengthCv < 0) length = TM2_MIN_LENGTH; - if (lengthCv > 0) { - length = constrain(ProportionCV(lengthCv, TM2_MAX_LENGTH + 1), TM2_MIN_LENGTH, TM2_MAX_LENGTH); - } + // CV 1 bi-polar modulation of length + len_mod = constrain(length + Proportion(DetentedIn(0), HEMISPHERE_MAX_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); // CV 2 bi-polar modulation of probability - int pCv = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100); - bool clk = Clock(0); + p_mod = constrain(p + Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100), 0, 100); + // Advance the register on clock, flipping bits as necessary if (clk) { // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same - int prob = (cursor == 1 || Gate(1)) ? p + pCv : 0; - prob = constrain(prob, 0, 100); + int prob = (cursor == 1 || Gate(1)) ? p_mod : 0; // Grab the bit that's about to be shifted away - int last = (reg >> (length - 1)) & 0x01; - int last2 = (reg2 >> (length - 1)) & 0x01; + int last = (reg >> (len_mod - 1)) & 0x01; + int last2 = (reg2 >> (len_mod - 1)) & 0x01; // Does it change? if (random(0, 99) < prob) last = 1 - last; @@ -97,8 +93,6 @@ public: note *= quant_range; simfloat x = int2simfloat(note) / (int32_t)0x1f; note = simfloat2int(x); - - Out(0, quantizer.Lookup(note + 64)); */ ForEachChannel(ch) { @@ -109,11 +103,11 @@ public: case 1: // pitch 2 Out(ch, quantizer.Lookup(note2 + 64)); break; - case 2: // mod A - 8-bit proportioned CV - Out(ch, Proportion(reg & 0x00ff, 255, HEMISPHERE_MAX_CV) ); + case 2: // mod A - 8-bit bi-polar proportioned CV + Out(ch, Proportion( int8_t(reg & 0xff), 0x80, HEMISPHERE_MAX_CV) ); break; - case 3: // mod B - 8-bit proportioned CV - Out(ch, Proportion(reg2 & 0x00ff, 255, HEMISPHERE_MAX_CV) ); + case 3: // mod B + Out(ch, Proportion( int8_t(reg2 & 0xff), 0x80, HEMISPHERE_MAX_CV) ); break; case 4: // trig A if (clk && (reg & 0x01) == 1) // trigger if 1st bit is high @@ -216,7 +210,8 @@ protected: private: int length; // Sequence length - int cursor; // 0 = length, 1 = p, 2 = scale + int len_mod; // actual length after CV mod + int cursor; // 0 = length, 1 = p, 2 = scale, 3=range, 4=OutA, 5=OutB bool isEditing = false; braids::Quantizer quantizer; @@ -224,6 +219,7 @@ private: uint32_t reg; // 32-bit sequence register uint32_t reg2; // DJP int p; // Probability of bit flipping on each cycle + int p_mod; int scale; // Scale used for quantized output int quant_range; @@ -234,12 +230,10 @@ private: void DrawSelector() { gfxBitmap(1, 14, 8, LOOP_ICON); - gfxPrint(12 + pad(10, length), 15, length); + gfxPrint(12 + pad(10, len_mod), 15, len_mod); gfxPrint(32, 15, "p="); if (cursor == 1 || Gate(1)) { // p unlocked - int pCv = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100); - int prob = constrain(p + pCv, 0, 100); - gfxPrint(pad(100, prob), prob); + gfxPrint(pad(100, p_mod), p_mod); } else { // p is disabled gfxBitmap(49, 14, 8, LOCK_ICON); } From 705750b7e4573aed5355527214b5e88e84dce27f Mon Sep 17 00:00:00 2001 From: Logarhythm1 Date: Wed, 3 Jun 2020 01:53:18 -0400 Subject: [PATCH 032/417] Sequence5: ported some bits from Logarhythm to extend past 5 steps "Left in some commented test code to extend the number of steps, but note that this is a bit unwieldy to use in practice" --- software/o_c_REV/HEM_Sequence5.ino | 43 +++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_Sequence5.ino b/software/o_c_REV/HEM_Sequence5.ino index 5b197824a..1f5d46ee9 100644 --- a/software/o_c_REV/HEM_Sequence5.ino +++ b/software/o_c_REV/HEM_Sequence5.ino @@ -20,15 +20,18 @@ #include "HSMIDI.h" +#define SEQ5_STEPS 5 +// NOTE: Not claiming >5 when saving, or restoring when loading! + class Sequence5 : public HemisphereApplet { public: const char* applet_name() { // Maximum 10 characters - return "Sequence5"; + return "SequenceX"; } void Start() { - for (int s = 0; s < 5; s++) note[s] = random(0, 30); + for (int s = 0; s < SEQ5_STEPS; s++) note[s] = random(0, 30); } void Controller() { @@ -58,7 +61,7 @@ public: } void OnButtonPress() { - if (++cursor == 5) cursor = 0; + if (++cursor == SEQ5_STEPS) cursor = 0; } void OnEncoderMove(int direction) { @@ -102,21 +105,23 @@ protected: private: int cursor = 0; char muted = 0; // Bitfield for muted steps; ((muted >> step) & 1) means muted - int note[5]; // Sequence value (0 - 30) + int note[SEQ5_STEPS]; // Sequence value (0 - 30) int step = 0; // Current sequencer step bool reset = true; void Advance(int starting_point) { - if (++step == 5) step = 0; + if (++step == SEQ5_STEPS) step = 0; // If all the steps have been muted, stay where we were if (step_is_muted(step) && step != starting_point) Advance(starting_point); } void DrawPanel() { // Sliders - for (int s = 0; s < 5; s++) + for (int s = 0; s < SEQ5_STEPS; s++) { int x = 6 + (12 * s); + //int x = 6 + (7 * s); // APD: narrower to fit more + if (!step_is_muted(s)) { gfxLine(x, 25, x, 63); @@ -124,10 +129,28 @@ private: if (s == cursor) { gfxLine(x + 1, 25, x + 1, 63); gfxRect(x - 4, BottomAlign(note[s]), 9, 3); - } else gfxFrame(x - 4, BottomAlign(note[s]), 9, 3); - + //gfxRect(x - 2, BottomAlign(note[s]), 5, 3); // APD + } else + { + gfxFrame(x - 4, BottomAlign(note[s]), 9, 3); + //gfxFrame(x - 2, BottomAlign(note[s]), 5, 3); // APD + } + // When on this step, there's an indicator circle - if (s == step) {gfxCircle(x, 20, 3);} + if (s == step) + { + + + gfxCircle(x, 20, 3); //Original + + // APD + //int play_note = note[step];// + 60 + transpose; + + //gfxPrint(10, 15, "Scale "); + //gfxPrint(cursor < 12 ? 1 : 2); + //gfxPrint(x, 20, play_note); + + } } else if (s == cursor) { gfxLine(x, 25, x, 63); gfxLine(x + 1, 25, x + 1, 63); @@ -181,4 +204,4 @@ uint64_t Sequence5_OnDataRequest(bool hemisphere) { void Sequence5_OnDataReceive(bool hemisphere, uint64_t data) { Sequence5_instance[hemisphere].OnDataReceive(data); -} +} From 2cb4703f05c77abcbe8c9b92e7cb97983d1830bc Mon Sep 17 00:00:00 2001 From: Logarhythm1 Date: Mon, 6 Jul 2020 01:21:57 -0400 Subject: [PATCH 033/417] Shuffle applet: Added Triplets output to the unused OutB. Seemed a good match since shuffle works on odd/even counts that are naturally 16th or 8th notes, so the triplet output counts 4 clocks and fits 3 triplet pulses. --- software/o_c_REV/HEM_Shuffle.ino | 40 ++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_Shuffle.ino b/software/o_c_REV/HEM_Shuffle.ino index 9a63dbee6..c4c5215a0 100644 --- a/software/o_c_REV/HEM_Shuffle.ino +++ b/software/o_c_REV/HEM_Shuffle.ino @@ -18,6 +18,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// Logarhythm: Added triplets output (3 triggers per 4 input clocks) to the unused 2nd output + class Shuffle : public HemisphereApplet { public: @@ -31,6 +33,10 @@ public: which = 0; cursor = 1; last_tick = 0; + + triplet_which = 0; // Triplets + next_trip_trigger = 0; + triplet_time = 0; } void Controller() { @@ -38,9 +44,24 @@ public: if (Clock(1)) { which = 0; // Reset (next trigger will be even clock) last_tick = tick; + triplet_which = 0; // Triplets reset to down beat } - if (Clock(0) && !Gate(1)) { + if (Clock(0)) { + + // Triplets: Track what triplet timing should be to span 4 normal clocks + triplet_time = (ClockCycleTicks(0) * 4) / 3; + if(triplet_which == 0) + { + next_trip_trigger = tick; // Trigger right now (downbeat) + } + + if(++triplet_which > 3) + { + triplet_which = 0; + } + + // Swing which = 1 - which; if (last_tick) { tempo = tick - last_tick; @@ -52,7 +73,17 @@ public: last_tick = tick; } + // Shuffle output if (tick == next_trigger) ClockOut(0); + + // Logarhythm: Triplets output + if(tick == next_trip_trigger) + { + next_trip_trigger = tick + triplet_time; // Schedule the next triplet output + triplet_time = 0; // Ensure that triplets will stop being scheduled if clocks aren't received + ClockOut(1); + } + } void View() { @@ -87,7 +118,7 @@ protected: // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; help[HEMISPHERE_HELP_CVS] = "1=Odd Mod 2=Even"; - help[HEMISPHERE_HELP_OUTS] = "A=Clock"; + help[HEMISPHERE_HELP_OUTS] = "A=Clock B=Triplets"; help[HEMISPHERE_HELP_ENCODER] = "Odd/Even Delay"; // "------------------" <-- Size Guide } @@ -99,6 +130,11 @@ private: uint32_t next_trigger; // The tick of the next scheduled trigger uint32_t tempo; // Calculated time between ticks + // Logarhythm: Triplets (output on out B) + uint32_t triplet_which; // The current 4 count of clocks used to determine triplet reset + uint32_t next_trip_trigger; // The tick of the next scheduled triplet trigger + uint32_t triplet_time; // Number of ticks between triplet output pulses + // Settings int16_t delay[2]; // Percentage delay for even (0) and odd (1) clock From c667fb4633eb432200598eff2247ae6ec7cd3a8f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 16 Nov 2022 03:24:22 -0500 Subject: [PATCH 034/417] TrigSeq and TrigSeq16 fixes I've merged from several places, so things were broken. Simplified reset handling, fixed CV offset control/display. --- software/o_c_REV/HEM_TrigSeq.ino | 18 +++++++----------- software/o_c_REV/HEM_TrigSeq16.ino | 9 +++------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/software/o_c_REV/HEM_TrigSeq.ino b/software/o_c_REV/HEM_TrigSeq.ino index 070341461..56a9655d8 100644 --- a/software/o_c_REV/HEM_TrigSeq.ino +++ b/software/o_c_REV/HEM_TrigSeq.ino @@ -37,23 +37,19 @@ public: } void Controller() { - if (Clock(1)) { - reset = true; - ForEachChannel(ch) step[ch] = 0; + if (Clock(1)) { // reset + ForEachChannel(ch) step[ch] = -1; } - if (Clock(0)) { + if (Clock(0)) { // clock advance bool swap = In(0) >= HEMISPHERE_3V_CV; - if (!reset) { - ForEachChannel(ch) step[ch]++; - } - reset = false; ForEachChannel(ch) { - if (step[ch] > end_step[ch]) step[ch] = 0; - if ((pattern[ch] >> step[ch]) & 0x01) ClockOut(swap ? (1 - ch) : ch); + if (step[ch] >= end_step[ch]) step[ch] = -1; + step[ch]++; + active_step[ch] = Step(ch); + if ((pattern[ch] >> active_step[ch]) & 0x01) ClockOut(swap ? (1 - ch) : ch); } - } } diff --git a/software/o_c_REV/HEM_TrigSeq16.ino b/software/o_c_REV/HEM_TrigSeq16.ino index 61c5dca13..4173552e8 100644 --- a/software/o_c_REV/HEM_TrigSeq16.ino +++ b/software/o_c_REV/HEM_TrigSeq16.ino @@ -33,18 +33,16 @@ public: step = 0; end_step = 15; cursor = 0; - reset = true; } void Controller() { - reset = Clock(1); + if (Clock(1)) step = -1; // reset regardless of clock if (Clock(0)) { bool swap = In(0) >= HEMISPHERE_3V_CV; - if (reset || step >= end_step) step = -1; + if (step >= end_step) step = -1; step++; - reset = false; - active_step = Step(); + active_step = Step(); // actual step after Offset modulation if (active_step < 8) { if ((pattern[0] >> active_step) & 0x01) ClockOut(swap ? 1 : 0); else ClockOut(swap ? 0 : 1); @@ -118,7 +116,6 @@ private: uint8_t pattern[2]; int end_step; int cursor; // 0=ch1 low, 1=ch1 hi, 2=ch2 low, 3=ch3 hi, 4=end_step - bool reset; int Offset() { int offset = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, end_step); From 51b00c4b307e240043addd69587651b2965e6c41 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 01:14:45 -0500 Subject: [PATCH 035/417] Internal Clock Forwarding tweaks Clock Forwarding and internal clock Start/Stop are now independent settings. On ClockSetup screen, turn encoder left to toggle Start/Stop, turn right to toggle Forwarding, indicated by a LINK_ICON. Long-press left encoder will still Pause/Unpause. Internal clock will only trigger Digital 3 if forwarding is ON. --- software/o_c_REV/HEM_ClockSetup.ino | 21 ++++++++++++--------- software/o_c_REV/HSClockManager.h | 2 +- software/o_c_REV/HemisphereApplet.h | 9 ++++++--- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 44fc2ef5a..20c255c00 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -41,10 +41,12 @@ public: void OnEncoderMove(int direction) { if (cursor == 0) { // Source - if (clock_m->IsRunning() || clock_m->IsPaused()) clock_m->Stop(); + if (direction > 0) // right turn toggles Forwarding + clock_m->ToggleForwarding(); + else if (clock_m->IsRunning()) // left turn toggles clock + clock_m->Stop(); else { clock_m->Start(); - clock_m->Pause(); } } @@ -105,16 +107,17 @@ private: graphics.drawLine(0, 12, 127, 12); // Clock Source + gfxIcon(1, 15, CLOCK_ICON); if (clock_m->IsRunning()) { - gfxIcon(1, 15, PLAY_ICON); - gfxPrint(16, 15, "Internal"); + gfxIcon(10, 15, PLAY_ICON); } else if (clock_m->IsPaused()) { - gfxIcon(1, 15, PAUSE_ICON); - gfxPrint(16, 15, "Internal"); + gfxIcon(10, 15, PAUSE_ICON); } else { - gfxIcon(1, 15, CLOCK_ICON); - gfxPrint(16, 15, "Forward"); + gfxIcon(10, 15, STOP_ICON); } + gfxPrint(20, 15, ""); + if (clock_m->IsForwarded()) + gfxIcon(76, 15, LINK_ICON); // Tempo gfxIcon(1, 25, NOTE4_ICON); @@ -126,7 +129,7 @@ private: gfxPrint(1, 35, "x"); gfxPrint(clock_m->GetMultiply()); - if (cursor == 0) gfxCursor(16, 23, 46); + if (cursor == 0) gfxCursor(20, 23, 54); if (cursor == 1) gfxCursor(23, 33, 18); if (cursor == 2) gfxCursor(8, 43, 12); } diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 8b7734740..b2bab6483 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -89,7 +89,7 @@ class ClockManager { } void Start() { - forwarded = 0; + // forwarded = 0; // NJM- logical clock can be forwarded, too running = 1; Unpause(); } diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index b377f0cfa..1e04f0ac2 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -333,10 +333,13 @@ class HemisphereApplet { if (ch == 1) clocked = OC::DigitalInputs::clocked(); } - if (ch == 0 && !physical) { + if (!physical) { ClockManager *clock_m = clock_m->get(); - if (clock_m->IsRunning()) clocked = clock_m->Tock(); - else if (master_clock_bus) clocked = OC::DigitalInputs::clocked(); + // Logical Clock only on trig 1 or 3 if forwarding is on + if ( ch == 0 && (hemisphere == 0 || clock_m->IsForwarded()) ) { + if (clock_m->IsRunning()) clocked = clock_m->Tock(); + else if (master_clock_bus) clocked = OC::DigitalInputs::clocked(); + } } if (clocked) { From 15c3e28f7d127a2edcae64e14e62dc95e5a7e23a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 01:37:04 -0500 Subject: [PATCH 036/417] DualTM: proper method for Gate output --- software/o_c_REV/HEM_TM2.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 030a1ddc5..399f46827 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -118,10 +118,10 @@ public: ClockOut(ch); break; case 6: // gate A - Out(ch, (reg & 0x01)*HEMISPHERE_MAX_CV); + GateOut(ch, (reg & 0x01)); break; case 7: // gate B - Out(ch, (reg2 & 0x01)*HEMISPHERE_MAX_CV); + GateOut(ch, (reg2 & 0x01)); break; } } From 3d8203c0016e146cf5dc317314a752fac71cc0c9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 01:14:45 -0500 Subject: [PATCH 037/417] Internal Clock Forwarding tweaks Clock Forwarding and internal clock Start/Stop are now independent settings. On ClockSetup screen, turn encoder left to toggle Start/Stop, turn right to toggle Forwarding, indicated by a LINK_ICON. Long-press left encoder will still Pause/Unpause. Internal clock will only trigger Digital 3 if forwarding is ON. --- software/o_c_REV/HEM_ClockSetup.ino | 21 ++++++++++++--------- software/o_c_REV/HSClockManager.h | 2 +- software/o_c_REV/HemisphereApplet.h | 7 +++++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 44fc2ef5a..20c255c00 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -41,10 +41,12 @@ public: void OnEncoderMove(int direction) { if (cursor == 0) { // Source - if (clock_m->IsRunning() || clock_m->IsPaused()) clock_m->Stop(); + if (direction > 0) // right turn toggles Forwarding + clock_m->ToggleForwarding(); + else if (clock_m->IsRunning()) // left turn toggles clock + clock_m->Stop(); else { clock_m->Start(); - clock_m->Pause(); } } @@ -105,16 +107,17 @@ private: graphics.drawLine(0, 12, 127, 12); // Clock Source + gfxIcon(1, 15, CLOCK_ICON); if (clock_m->IsRunning()) { - gfxIcon(1, 15, PLAY_ICON); - gfxPrint(16, 15, "Internal"); + gfxIcon(10, 15, PLAY_ICON); } else if (clock_m->IsPaused()) { - gfxIcon(1, 15, PAUSE_ICON); - gfxPrint(16, 15, "Internal"); + gfxIcon(10, 15, PAUSE_ICON); } else { - gfxIcon(1, 15, CLOCK_ICON); - gfxPrint(16, 15, "Forward"); + gfxIcon(10, 15, STOP_ICON); } + gfxPrint(20, 15, ""); + if (clock_m->IsForwarded()) + gfxIcon(76, 15, LINK_ICON); // Tempo gfxIcon(1, 25, NOTE4_ICON); @@ -126,7 +129,7 @@ private: gfxPrint(1, 35, "x"); gfxPrint(clock_m->GetMultiply()); - if (cursor == 0) gfxCursor(16, 23, 46); + if (cursor == 0) gfxCursor(20, 23, 54); if (cursor == 1) gfxCursor(23, 33, 18); if (cursor == 2) gfxCursor(8, 43, 12); } diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 8b7734740..b2bab6483 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -89,7 +89,7 @@ class ClockManager { } void Start() { - forwarded = 0; + // forwarded = 0; // NJM- logical clock can be forwarded, too running = 1; Unpause(); } diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index a8162adda..621e8c453 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -335,8 +335,11 @@ class HemisphereApplet { if (ch == 0 && !physical) { ClockManager *clock_m = clock_m->get(); - if (clock_m->IsRunning()) clocked = clock_m->Tock(); - else if (master_clock_bus) clocked = OC::DigitalInputs::clocked(); + // Logical Clock only on RIGHT hemisphere if forwarding is on + if ( hemisphere == 0 || clock_m->IsForwarded() ) { + if (clock_m->IsRunning()) clocked = clock_m->Tock(); + else if (master_clock_bus) clocked = OC::DigitalInputs::clocked(); + } } if (clocked) { From df91893aa8fadf9dfcad868a59cd569c924536d8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 14:04:47 -0500 Subject: [PATCH 038/417] ClockSetup: Reset clock after Start --- software/o_c_REV/HEM_ClockSetup.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 20c255c00..55456fe48 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -47,6 +47,7 @@ public: clock_m->Stop(); else { clock_m->Start(); + clock_m->Reset(); } } From 21de359170a3d0877a0bd851a977b3790e585d0d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 15:10:48 -0500 Subject: [PATCH 039/417] Sequence5 now has 8 steps; re-enabled a couple applets --- software/o_c_REV/HEM_Sequence5.ino | 22 ++++++++++------------ software/o_c_REV/hemisphere_config.h | 8 ++++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/software/o_c_REV/HEM_Sequence5.ino b/software/o_c_REV/HEM_Sequence5.ino index 1f5d46ee9..58dfd9db2 100644 --- a/software/o_c_REV/HEM_Sequence5.ino +++ b/software/o_c_REV/HEM_Sequence5.ino @@ -20,8 +20,8 @@ #include "HSMIDI.h" -#define SEQ5_STEPS 5 -// NOTE: Not claiming >5 when saving, or restoring when loading! +// DON'T GO PAST 8! +#define SEQ5_STEPS 8 class Sequence5 : public HemisphereApplet { public: @@ -76,7 +76,7 @@ public: uint64_t OnDataRequest() { uint64_t data = 0; - for (int s = 0; s < 5; s++) + for (int s = 0; s < SEQ5_STEPS; s++) { Pack(data, PackLocation {s * 5,5}, note[s]); } @@ -85,7 +85,7 @@ public: } void OnDataReceive(uint64_t data) { - for (int s = 0; s < 5; s++) + for (int s = 0; s < SEQ5_STEPS; s++) { note[s] = Unpack(data, PackLocation {s * 5,5}); } @@ -119,8 +119,8 @@ private: // Sliders for (int s = 0; s < SEQ5_STEPS; s++) { - int x = 6 + (12 * s); - //int x = 6 + (7 * s); // APD: narrower to fit more + //int x = 6 + (12 * s); + int x = 6 + (7 * s); // APD: narrower to fit more if (!step_is_muted(s)) { gfxLine(x, 25, x, 63); @@ -128,19 +128,17 @@ private: // When cursor, there's a heavier bar and a solid slider if (s == cursor) { gfxLine(x + 1, 25, x + 1, 63); - gfxRect(x - 4, BottomAlign(note[s]), 9, 3); - //gfxRect(x - 2, BottomAlign(note[s]), 5, 3); // APD + //gfxRect(x - 4, BottomAlign(note[s]), 9, 3); + gfxRect(x - 2, BottomAlign(note[s]), 5, 3); // APD } else { - gfxFrame(x - 4, BottomAlign(note[s]), 9, 3); - //gfxFrame(x - 2, BottomAlign(note[s]), 5, 3); // APD + //gfxFrame(x - 4, BottomAlign(note[s]), 9, 3); + gfxFrame(x - 2, BottomAlign(note[s]), 5, 3); // APD } // When on this step, there's an indicator circle if (s == step) { - - gfxCircle(x, 20, 3); //Original // APD diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 8a6ddb910..0d05c8a8f 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 54 +#define HEMISPHERE_AVAILABLE_APPLETS 56 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ @@ -35,6 +35,7 @@ DECLARE_APPLET( 55, 0x80, DrCrusher), \ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ + DECLARE_APPLET( 18, 0x02, DualTM), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ DECLARE_APPLET( 29, 0x04, GateDelay), \ @@ -52,15 +53,16 @@ DECLARE_APPLET( 23, 0x80, Scope), \ DECLARE_APPLET( 14, 0x02, Sequence5), \ DECLARE_APPLET( 48, 0x45, ShiftGate), \ - DECLARE_APPLET( 18, 0x02, DualTM), \ DECLARE_APPLET( 58, 0x01, Shredder), \ DECLARE_APPLET( 36, 0x04, Shuffle), \ DECLARE_APPLET( 7, 0x01, SkewedLFO), \ DECLARE_APPLET( 19, 0x01, Slew), \ + DECLARE_APPLET( 46, 0x08, Squanch), \ DECLARE_APPLET( 61, 0x01, Stairs), \ DECLARE_APPLET( 3, 0x10, Switch), \ 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), \ @@ -76,6 +78,4 @@ DECLARE_APPLET( 27, 0x20, hMIDIOut), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 17, 0x50, GatedVCA), \ - DECLARE_APPLET( 46, 0x08, Squanch), \ - DECLARE_APPLET( 37, 0x40, Trending), \ */ From 3a24535fe0665202dbcf8e1f3776d9c143b1b419 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 17:43:26 -0500 Subject: [PATCH 040/417] Rewrote the Clock logic in HemisphereApplet ...hopefully making it more clear. Code size is also smaller somehow. --- software/o_c_REV/HemisphereApplet.h | 35 ++++++++++++++++------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index b993b84c1..eb8890f95 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -325,26 +325,29 @@ class HemisphereApplet { */ bool Clock(int ch, bool physical = 0) { bool clocked = 0; - if (hemisphere == 0) { - if (ch == 0) clocked = OC::DigitalInputs::clocked(); - if (ch == 1) clocked = OC::DigitalInputs::clocked(); - } else if (hemisphere == 1) { - if (ch == 0) clocked = OC::DigitalInputs::clocked(); - if (ch == 1) clocked = OC::DigitalInputs::clocked(); - } - - if (ch == 0 && !physical) { - ClockManager *clock_m = clock_m->get(); - // Logical Clock only on RIGHT hemisphere if forwarding is on - if ( hemisphere == 0 || clock_m->IsForwarded() ) { - if (clock_m->IsRunning()) clocked = clock_m->Tock(); - else if (master_clock_bus) clocked = OC::DigitalInputs::clocked(); + ClockManager *clock_m = clock_m->get(); + + if (ch == 0) { // clock triggers + if (hemisphere == LEFT_HEMISPHERE) { + if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(); + else clocked = OC::DigitalInputs::clocked(); + } else { // right side is special + if (master_clock_bus) { // forwarding from left + if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(); + else clocked = OC::DigitalInputs::clocked(); + } + else clocked = OC::DigitalInputs::clocked(); } + } else if (ch == 1) { // simple physical trig check + if (hemisphere == LEFT_HEMISPHERE) + clocked = OC::DigitalInputs::clocked(); + else + clocked = OC::DigitalInputs::clocked(); } if (clocked) { - cycle_ticks[ch] = OC::CORE::ticks - last_clock[ch]; - last_clock[ch] = OC::CORE::ticks; + cycle_ticks[ch] = OC::CORE::ticks - last_clock[ch]; + last_clock[ch] = OC::CORE::ticks; } return clocked; } From d2ca26ee274bbd3e631d70116de99d7d50c7b8ac Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 22:31:26 -0500 Subject: [PATCH 041/417] Add Random Walk applet, ported from adegani/main --- software/o_c_REV/HEM_RndWalk.ino | 327 +++++++++++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 1 + 2 files changed, 328 insertions(+) create mode 100644 software/o_c_REV/HEM_RndWalk.ino diff --git a/software/o_c_REV/HEM_RndWalk.ino b/software/o_c_REV/HEM_RndWalk.ino new file mode 100644 index 000000000..2d07b57cd --- /dev/null +++ b/software/o_c_REV/HEM_RndWalk.ino @@ -0,0 +1,327 @@ +// Copyright (c) 2022, Alessio Degani +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// #include +#include "OC_core.h" +//#include "HemisphereApplet.h" + +#define PROB_UP 500 +#define PROB_DN 500 + +#define MAX_RANGE 255 +#define MAX_STEP 255 +#define MAX_SMOOTH 255 + +#define HEM_1V 1536 //ADC value for 1 Volt +#define HEM_1ST 128 //ADC value for 1 semitone +#define HEM_HST 64 //ADC value for half semitone + +class RndWalk : public HemisphereApplet { +public: + + const char* applet_name() { // Maximum 10 characters + return "RndWalk"; + } + + void Start() { + ForEachChannel(ch) + { + // rndSeed[ch] = random(1, 255); + currentVal[ch] = 0; + currentOut[ch] = 0; + UpdateAlpha(); + } + cursor = 0; + } + + void Controller() { + // Main LOOP + // for triggers read from Clock(0|1) + // for CV in read from In(0|1) + // for CV out write to Out(0|1, value) + // INPUT + int maxVal = HEMISPHERE_MAX_CV; + switch (cvRange) { + case 0: + maxVal = HEM_HST; + break; + case 1: + maxVal = HEM_1ST; + break; + case 2: + maxVal = HEM_1V; + break; + default: + break; + } + int rangeCv = Proportion(In(0), HEMISPHERE_MAX_CV, MAX_RANGE); + int stepCv = Proportion(In(1), HEMISPHERE_MAX_CV, MAX_STEP); + + ForEachChannel(ch) { + // OUTPUT + if ( ((ch == 0) && Clock(0)) || + ((ch == 1) && Clock(yClkSrc)) ) + { + if ((ch == 1) && ((clkMod++ % yClkDiv) > 0) ){ + continue; + } + int randInt = random(0, 1000); + int randStep = (float)(random(1, constrain(step+stepCv, 0, MAX_STEP)))/MAX_STEP*maxVal/2; + int rangeScaled = (int)( ((float)constrain(range + rangeCv, 0, MAX_RANGE))/MAX_RANGE * maxVal); + currentVal[ch] += randStep * (((randInt > PROB_UP) && (currentVal[ch] < rangeScaled)) - + ((randInt < PROB_DN) && (currentVal[ch] > -rangeScaled))); + } + currentOut[ch] = alpha*currentOut[ch] + (1-alpha)*(float)currentVal[ch]; + + Out(ch, constrain((int)currentOut[ch], -HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV)); + } + } + + void View() { + gfxHeader(applet_name()); + DrawDisplay(); + } + + void OnButtonPress() { + cursor++; + if (cursor > 5) cursor = 0; + ResetCursor(); + } + + void OnEncoderMove(int direction) { + // Parameter Change handler + // var cursor is the param pointer + // var direction is the the movement of the encoder + // use valConstrained = constrain(val, min, max) to apply value limit + if (cursor == 0) { + range = constrain(range + direction, 0, MAX_RANGE); + } else if (cursor == 1) { + step = constrain(step + direction, 1, MAX_STEP); + } else if (cursor == 2) { + smoothness = constrain(smoothness + direction, 0, MAX_SMOOTH); + UpdateAlpha(); + } else if (cursor == 3) { + yClkSrc = constrain(yClkSrc + direction, 0, 1); + } else if (cursor == 4) { + yClkDiv = constrain(yClkDiv + direction, 1, 32); + } else if (cursor == 5) { + cvRange = constrain(cvRange + direction, 0, 3); + int maxVal = HEMISPHERE_MAX_CV; + switch (cvRange) { + case 0: + maxVal = HEM_HST; + break; + case 1: + maxVal = HEM_1ST; + break; + case 2: + maxVal = HEM_1V; + break; + default: + break; + } + ForEachChannel(ch) { + currentVal[ch] = currentVal[ch]%maxVal; + } + } + + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation {0,1}, yClkSrc); + Pack(data, PackLocation {1,4}, yClkDiv); + Pack(data, PackLocation {5,8}, range); + Pack(data, PackLocation {13,8}, step); + Pack(data, PackLocation {21,8}, smoothness); + Pack(data, PackLocation {29,2}, cvRange); + return data; + } + + void OnDataReceive(uint64_t data) { + yClkSrc = Unpack(data, PackLocation {0,1}); + yClkDiv = Unpack(data, PackLocation {1,4}); + range = Unpack(data, PackLocation {5,8}); + step = Unpack(data, PackLocation {13,8}); + smoothness = Unpack(data, PackLocation {21,8}); + cvRange = Unpack(data, PackLocation {29,2}); + UpdateAlpha(); + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=X Clock 2=Y Clk"; + help[HEMISPHERE_HELP_CVS] = "1=Range 2=step"; + help[HEMISPHERE_HELP_OUTS] = "RndWalk A=X B=Y"; + help[HEMISPHERE_HELP_ENCODER] = "T=Set P=Select"; + // "------------------" <-- Size Guide + } + +private: + // Parameters (saved in EEPROM) + bool yClkSrc = 0; // 0=TR1, 1=TR2 + uint8_t yClkDiv = 1; // 4 bits [1 .. 32] + int range = 20; // 8 bits + int step = 20; // 8 bits + uint8_t smoothness = 20; // 8 bits + uint8_t cvRange = 3; // 2 bit + uint8_t clkMod = 0; //not stored, used for clock division + float alpha; // not stored, used for smoothing + + // Runtime parameters + // unsigned int rndSeed[2]; + int currentVal[2]; + float currentOut[2]; + int cursor; // 0=Y clk src, 1=Y clk div, 2=Range, 3=step, 4=Smoothnes + + void DrawDisplay() { + + if (cursor < 3) { + gfxPrint(1, 15, "Range"); + gfxPrint(43, 15, range); + if (cursor == 0) gfxCursor(43, 22, 18); + + gfxPrint(1, 25, "Step"); + gfxPrint(43, 25, step); + if (cursor == 1) gfxCursor(43, 32, 18); + gfxPrint(1, 35, "Smooth"); + gfxPrint(43, 35, smoothness); + if (cursor == 2) gfxCursor(43, 42, 18); + } else { + + gfxPrint(1, 15, "Y TRIG"); + if (yClkSrc == 0) { + gfxPrint(43, 15, "TR1"); + } else { + gfxPrint(43, 15, "TR2"); + } + if (cursor == 3) gfxCursor(43, 22, 18); + + gfxPrint(1, 25, "Y CLK /"); + gfxPrint(43, 25, yClkDiv); + if (cursor == 4) gfxCursor(43, 32, 18); + + gfxPrint(1, 35, "CV Rng"); + if (cvRange == 0) { + gfxPrint(43, 35, ".5st"); + } else if (cvRange == 1) { + gfxPrint(43, 35, "1 st"); + } else if (cvRange == 2) { + gfxPrint(43, 35, "1oct"); + } else if (cvRange == 3) { + gfxPrint(43, 35, "FULL"); + } + if (cursor == 5) gfxCursor(43, 42, 24); + } + + // gfxPrint(1, 38, "x"); + // gfxPrint(1, 50, "y"); + + // gfxPrint(7, 38, currentVal[0]); + // gfxPrint(7, 50, currentVal[1]); + + int maxVal = HEMISPHERE_MAX_CV; + switch (cvRange) { + case 0: + maxVal = HEM_HST; + break; + case 1: + maxVal = HEM_1ST; + break; + case 2: + maxVal = HEM_1V; + break; + default: + break; + } + // gfxPrint(1, 47, currentVal[0]); + // gfxPrint(1, 55, currentVal[1]); + gfxPrint(1, 47, "x"); + gfxPrint(55, 55, "y"); + ForEachChannel(ch) { + int w = 0; + if (range > 0) { + w = (currentOut[ch]/((float)range/MAX_RANGE*maxVal))*31; + if (w > 31) { + w = 31; + } + if (w < -31) { + w = -31; + } + } + if (w >= 0) { + gfxInvert(31, 48 + (8 * ch), w, 7); + } else { + gfxInvert(31+w, 48 + (8 * ch), -w, 7); + } + } + + } + + void UpdateAlpha() { + // Use log mapping for better feeling + alpha = log(1+smoothness)/log(1+MAX_SMOOTH); + // alpha = (float)smoothness/(float)MAX_SMOOTH; + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to RndWalk, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +RndWalk RndWalk_instance[2]; + +void RndWalk_Start(bool hemisphere) { + RndWalk_instance[hemisphere].BaseStart(hemisphere); +} + +void RndWalk_Controller(bool hemisphere, bool forwarding) { + RndWalk_instance[hemisphere].BaseController(forwarding); +} + +void RndWalk_View(bool hemisphere) { + RndWalk_instance[hemisphere].BaseView(); +} + +void RndWalk_OnButtonPress(bool hemisphere) { + RndWalk_instance[hemisphere].OnButtonPress(); +} + +void RndWalk_OnEncoderMove(bool hemisphere, int direction) { + RndWalk_instance[hemisphere].OnEncoderMove(direction); +} + +void RndWalk_ToggleHelpScreen(bool hemisphere) { + RndWalk_instance[hemisphere].HelpScreen(); +} + +uint64_t RndWalk_OnDataRequest(bool hemisphere) { + return RndWalk_instance[hemisphere].OnDataRequest(); +} + +void RndWalk_OnDataReceive(bool hemisphere, uint64_t data) { + RndWalk_instance[hemisphere].OnDataReceive(data); +} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 0d05c8a8f..ece395d41 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -47,6 +47,7 @@ DECLARE_APPLET( 20, 0x02, Palimpsest), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ + DECLARE_APPLET( 45, 0x01, RndWalk), \ DECLARE_APPLET( 44, 0x01, RunglBook), \ DECLARE_APPLET( 26, 0x08, ScaleDuet), \ DECLARE_APPLET( 40, 0x40, Schmitt), \ From 4acc563ea523238358ad7da77a8c9c4527500e52 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 17 Nov 2022 22:45:44 -0500 Subject: [PATCH 042/417] RndWalk: cosmetic tweaks --- software/o_c_REV/HEM_RndWalk.ino | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/HEM_RndWalk.ino b/software/o_c_REV/HEM_RndWalk.ino index 2d07b57cd..30803abaf 100644 --- a/software/o_c_REV/HEM_RndWalk.ino +++ b/software/o_c_REV/HEM_RndWalk.ino @@ -18,9 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// #include #include "OC_core.h" -//#include "HemisphereApplet.h" #define PROB_UP 500 #define PROB_DN 500 @@ -197,14 +195,11 @@ private: if (cursor < 3) { gfxPrint(1, 15, "Range"); gfxPrint(43, 15, range); - if (cursor == 0) gfxCursor(43, 22, 18); gfxPrint(1, 25, "Step"); gfxPrint(43, 25, step); - if (cursor == 1) gfxCursor(43, 32, 18); gfxPrint(1, 35, "Smooth"); gfxPrint(43, 35, smoothness); - if (cursor == 2) gfxCursor(43, 42, 18); } else { gfxPrint(1, 15, "Y TRIG"); @@ -213,23 +208,28 @@ private: } else { gfxPrint(43, 15, "TR2"); } - if (cursor == 3) gfxCursor(43, 22, 18); gfxPrint(1, 25, "Y CLK /"); gfxPrint(43, 25, yClkDiv); - if (cursor == 4) gfxCursor(43, 32, 18); - gfxPrint(1, 35, "CV Rng"); + gfxPrint(1, 35, "CVRng"); if (cvRange == 0) { - gfxPrint(43, 35, ".5st"); + gfxPrint(40, 35, ".5st"); } else if (cvRange == 1) { - gfxPrint(43, 35, "1 st"); + gfxPrint(40, 35, "1 st"); } else if (cvRange == 2) { - gfxPrint(43, 35, "1oct"); + gfxPrint(40, 35, "1oct"); } else if (cvRange == 3) { - gfxPrint(43, 35, "FULL"); + gfxPrint(40, 35, "FULL"); } - if (cursor == 5) gfxCursor(43, 42, 24); + } + switch (cursor) { + case 0: gfxCursor(43, 22, 18); break; + case 1: gfxCursor(43, 32, 18); break; + case 2: gfxCursor(43, 42, 18); break; + case 3: gfxCursor(43, 22, 18); break; + case 4: gfxCursor(43, 32, 18); break; + case 5: gfxCursor(40, 42, 24); break; } // gfxPrint(1, 38, "x"); From 593e7c143a63217fe773e21275c75b636f8e6d21 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 20 Nov 2022 05:49:37 -0500 Subject: [PATCH 043/417] LoFi Tape -> LoFi Echo from armandvedel fixed a couple things for it to work right --- software/o_c_REV/HEM_LoFiPCM.ino | 86 ++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index c6a4673d1..4a3cd17fb 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -18,20 +18,22 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define HEM_LOFI_PCM_BUFFER_SIZE 2048 -#define HEM_LOFI_PCM_SPEED 8 -#define LOFI_PCM2CV(S) ((uint32_t)S << 8) - 32767; +#define HEM_LOFI_PCM_BUFFER_SIZE 4096 +#define HEM_LOFI_PCM_SPEED 4 +#define LOFI_PCM2CV(S) ((uint32_t)S << 8) - 32512 //32767 is 128 << 8 32512 is 127 << 8 // 0-126 is neg, 127 is 0, 128-254 is pos +#define CLIPLIMIT 32512 class LoFiPCM : public HemisphereApplet { public: const char* applet_name() { // Maximum 10 characters - return "LoFi Tape"; + return "LoFi Echo"; } void Start() { countdown = HEM_LOFI_PCM_SPEED; - for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 127; + for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 127; //char is unsigned in teensy (0-255)? + selected = 1; //for gui } void Controller() { @@ -40,41 +42,46 @@ public: countdown--; if (countdown == 0) { - if (play || record || gated_record) head++; + head++; if (head >= length) { - head = 0; - record = 0; + head = 0; ClockOut(1); } - if (record || gated_record) { - uint32_t s = (In(0) + 32767) >> 8; - pcm[head] = (char)s; - } - - uint32_t s = LOFI_PCM2CV(pcm[head]); - int SOS = In(1); // Sound-on-sound - int live = Proportion(SOS, HEMISPHERE_MAX_CV, In(0)); - int loop = play ? Proportion(HEMISPHERE_MAX_CV - SOS, HEMISPHERE_MAX_CV, s) : 0; - Out(0, live + loop); + int dt = delaytime_pct * length / 100; //convert delaytime to length in samples + int writehead = (head+length + dt) % length; //have to add the extra length to keep modulo positive in case delaytime is neg + int tapeout = LOFI_PCM2CV(pcm[head]); // get the char out from the array and convert back to cv (de-offset) + uint32_t feedbackmix = (constrain((tapeout * feedback / 100 + In(0)), -CLIPLIMIT, CLIPLIMIT) + CLIPLIMIT) >> 8; //add to the feedback, offset and bitshift down + pcm[writehead] = (char)feedbackmix; + + uint32_t s = play ? LOFI_PCM2CV(pcm[head]) : 0; + //int SOS = In(1); // Sound-on-sound + //int live = Proportion(SOS, HEMISPHERE_MAX_CV, In(0)); //max_cv is 7680 scales vol. of live + //int loop = play ? Proportion(HEMISPHERE_MAX_CV - SOS, HEMISPHERE_MAX_CV, s) : 0; + Out(0, s); countdown = HEM_LOFI_PCM_SPEED; } } void View() { gfxHeader(applet_name()); - DrawTransportBar(); + DrawSelector(); DrawWaveform(); } void OnButtonPress() { - record = 1 - record; - play = 0; - head = 0; + selected = 1 - selected; + ResetCursor(); } void OnEncoderMove(int direction) { - length = constrain(length += (direction * 32), 32, HEM_LOFI_PCM_BUFFER_SIZE); + if (selected == 0) delaytime_pct = constrain(delaytime_pct += direction, 0, 99); + if (selected == 1) feedback = constrain(feedback += direction, 0, 99); + + //amp_offset_cv = Proportion(amp_offset_pct, 100, HEMISPHERE_MAX_CV); + //p[cursor] = constrain(p[cursor] += direction, 0, 100); + + } uint64_t OnDataRequest() { @@ -97,18 +104,16 @@ protected: private: char pcm[HEM_LOFI_PCM_BUFFER_SIZE]; - bool record = 0; // Record activated via button + bool record = 0; // Record always on bool gated_record = 0; // Record gated via digital in - bool play = 0; + bool play = 0; //play always on int head = 0; // Locatioon of play/record head + int delaytime_pct = 50; //delaytime as percentage of delayline buffer + int feedback = 50; int countdown = HEM_LOFI_PCM_SPEED; int length = HEM_LOFI_PCM_BUFFER_SIZE; - - void DrawTransportBar() { - DrawStop(3, 15); - DrawPlay(26, 15); - DrawRecord(50, 15); - } + int selected; //for gui + void DrawWaveform() { int inc = HEM_LOFI_PCM_BUFFER_SIZE / 256; @@ -134,7 +139,22 @@ private: } } - void DrawStop(int x, int y) { + + + void DrawSelector() + { + for (int param = 0; param < 2; param++) + { + gfxPrint(31 * param, 15, param ? "Fb: " : "Ln: "); + gfxPrint(16, 15, delaytime_pct); + gfxPrint(48, 15, feedback); + if (param == selected) gfxCursor(0 + (31 * param), 23, 30); + } + } + + + + /* void DrawStop(int x, int y) { if (record || play || gated_record) gfxFrame(x, y, 11, 11); else gfxRect(x, y, 11, 11); } @@ -152,7 +172,6 @@ private: gfxLine(x, y + 10, x + 10, y + 5); } } - void DrawRecord(int x, int y) { gfxCircle(x + 5, y + 5, 5); if (record || gated_record) { @@ -162,6 +181,7 @@ private: } } } +*/ }; From 591c78882334bfe45152df19e4edc50f567df9c6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 20 Nov 2022 10:14:50 -0500 Subject: [PATCH 044/417] LoFi Echo - HiFi improvements Buffer speed parameter added. PCM buffer is int16_t instead of char, allowing full bit depth. Could be packed to 12 bits to squeeze a longer buffer in, idk. --- software/o_c_REV/HEM_LoFiPCM.ino | 130 +++++++++++++------------------ 1 file changed, 54 insertions(+), 76 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 4a3cd17fb..ed0ebaf46 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -18,10 +18,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define HEM_LOFI_PCM_BUFFER_SIZE 4096 +#define HEM_LOFI_PCM_BUFFER_SIZE 3000 #define HEM_LOFI_PCM_SPEED 4 #define LOFI_PCM2CV(S) ((uint32_t)S << 8) - 32512 //32767 is 128 << 8 32512 is 127 << 8 // 0-126 is neg, 127 is 0, 128-254 is pos -#define CLIPLIMIT 32512 +// #define CLIPLIMIT 32512 +#define CLIPLIMIT HEMISPHERE_3V_CV class LoFiPCM : public HemisphereApplet { public: @@ -32,51 +33,56 @@ public: void Start() { countdown = HEM_LOFI_PCM_SPEED; - for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 127; //char is unsigned in teensy (0-255)? - selected = 1; //for gui + for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 0; //char is unsigned in teensy (0-255)? + cursor = 1; //for gui } void Controller() { play = !Gate(0); // Continuously play unless gated - gated_record = Gate(1); - - countdown--; - if (countdown == 0) { - head++; - if (head >= length) { - head = 0; - ClockOut(1); - } + int fdbk = Gate(1) ? 100 : feedback; // Feedback = 100 when gated - int dt = delaytime_pct * length / 100; //convert delaytime to length in samples - int writehead = (head+length + dt) % length; //have to add the extra length to keep modulo positive in case delaytime is neg - int tapeout = LOFI_PCM2CV(pcm[head]); // get the char out from the array and convert back to cv (de-offset) - uint32_t feedbackmix = (constrain((tapeout * feedback / 100 + In(0)), -CLIPLIMIT, CLIPLIMIT) + CLIPLIMIT) >> 8; //add to the feedback, offset and bitshift down - pcm[writehead] = (char)feedbackmix; - - uint32_t s = play ? LOFI_PCM2CV(pcm[head]) : 0; - //int SOS = In(1); // Sound-on-sound - //int live = Proportion(SOS, HEMISPHERE_MAX_CV, In(0)); //max_cv is 7680 scales vol. of live - //int loop = play ? Proportion(HEMISPHERE_MAX_CV - SOS, HEMISPHERE_MAX_CV, s) : 0; - Out(0, s); - countdown = HEM_LOFI_PCM_SPEED; + if (play) { + countdown--; + if (countdown == 0) { + if (++head >= length) { + head = 0; + ClockOut(1); + } + + int dt = delaytime_pct * length / 100; //convert delaytime to length in samples + int writehead = (head+length + dt) % length; //have to add the extra length to keep modulo positive in case delaytime is neg + int16_t feedbackmix = constrain((pcm[head] * fdbk / 100 + In(0)), -CLIPLIMIT, CLIPLIMIT); + pcm[writehead] = feedbackmix; + + Out(0, pcm[head]); + countdown = rate_factor; + } } } void View() { gfxHeader(applet_name()); DrawSelector(); - DrawWaveform(); + if (play) DrawWaveform(); } void OnButtonPress() { - selected = 1 - selected; + if (++cursor > 2) cursor = 0; ResetCursor(); } void OnEncoderMove(int direction) { - if (selected == 0) delaytime_pct = constrain(delaytime_pct += direction, 0, 99); - if (selected == 1) feedback = constrain(feedback += direction, 0, 99); + switch (cursor) { + case 0: + delaytime_pct = constrain(delaytime_pct += direction, 0, 99); + break; + case 1: + feedback = constrain(feedback += direction, 0, 125); + break; + case 2: + rate_factor = constrain(rate_factor += direction, 1, 20); + break; + } //amp_offset_cv = Proportion(amp_offset_pct, 100, HEMISPHERE_MAX_CV); //p[cursor] = constrain(p[cursor] += direction, 0, 100); @@ -95,35 +101,34 @@ public: protected: void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "Gate 1=Pause 2=Rec"; - help[HEMISPHERE_HELP_CVS] = "1=Audio 2=SOS"; + help[HEMISPHERE_HELP_DIGITALS] = "1=Mute 2=LoopMode"; + help[HEMISPHERE_HELP_CVS] = "1=Audio 2="; help[HEMISPHERE_HELP_OUTS] = "A=Audio B=EOC Trg"; - help[HEMISPHERE_HELP_ENCODER] = "T=End Pt P=Rec"; + help[HEMISPHERE_HELP_ENCODER] = "Time/Fdbk/Speed"; // "------------------" <-- Size Guide } private: - char pcm[HEM_LOFI_PCM_BUFFER_SIZE]; - bool record = 0; // Record always on - bool gated_record = 0; // Record gated via digital in - bool play = 0; //play always on - int head = 0; // Locatioon of play/record head + int16_t pcm[HEM_LOFI_PCM_BUFFER_SIZE]; + bool play = 0; //play always on unless gated on Digital 1 + int head = 0; // Location of play/record head int delaytime_pct = 50; //delaytime as percentage of delayline buffer int feedback = 50; int countdown = HEM_LOFI_PCM_SPEED; + int rate_factor = HEM_LOFI_PCM_SPEED; int length = HEM_LOFI_PCM_BUFFER_SIZE; - int selected; //for gui + int cursor; //for gui void DrawWaveform() { int inc = HEM_LOFI_PCM_BUFFER_SIZE / 256; int disp[32]; - int high = 1; + int high = 128; int pos = head - (inc * 15) - random(1,3); // Try to center the head if (head < 0) head += length; for (int i = 0; i < 32; i++) { - int v = (int)pcm[pos] - 127; + int v = pcm[pos]; if (v < 0) v = 0; if (v > high) high = v; pos += inc; @@ -139,49 +144,22 @@ private: } } - - void DrawSelector() { - for (int param = 0; param < 2; param++) - { - gfxPrint(31 * param, 15, param ? "Fb: " : "Ln: "); - gfxPrint(16, 15, delaytime_pct); - gfxPrint(48, 15, feedback); - if (param == selected) gfxCursor(0 + (31 * param), 23, 30); - } - } - - - - /* void DrawStop(int x, int y) { - if (record || play || gated_record) gfxFrame(x, y, 11, 11); - else gfxRect(x, y, 11, 11); - } - - void DrawPlay(int x, int y) { - if (play) { - for (int i = 0; i < 11; i += 2) + if (cursor < 2) { + for (int param = 0; param < 2; param++) { - gfxLine(x + i, y + i/2, x + i, y + 10 - i/2); - gfxLine(x + i + 1, y + i/2, x + i + 1, y + 10 - i/2); + gfxIcon(31 * param, 15, param ? GAUGE_ICON : CLOCK_ICON ); + gfxPrint(4 + pad(100, delaytime_pct), 15, delaytime_pct); + gfxPrint(36 + pad(1000, feedback), 15, feedback); + if (param == cursor) gfxCursor(0 + (31 * param), 23, 30); } } else { - gfxLine(x, y, x, y + 10); - gfxLine(x, y, x + 10, y + 5); - gfxLine(x, y + 10, x + 10, y + 5); - } - } - void DrawRecord(int x, int y) { - gfxCircle(x + 5, y + 5, 5); - if (record || gated_record) { - for (int r = 1; r < 5; r++) - { - gfxCircle(x + 5, y + 5, r); - } + gfxPrint(0, 15, "Rate:"); + gfxPrint(30, 15, rate_factor); + gfxCursor(30, 23, 16); } } -*/ }; From 9628468ebe718c8f802f952f0ba884cfe2801f45 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 20 Nov 2022 10:57:33 -0500 Subject: [PATCH 045/417] LoFi Echo - improve waveform display --- software/o_c_REV/HEM_LoFiPCM.ino | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index ed0ebaf46..14c6a1ec6 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -121,16 +121,17 @@ private: void DrawWaveform() { - int inc = HEM_LOFI_PCM_BUFFER_SIZE / 256; + //int inc = HEM_LOFI_PCM_BUFFER_SIZE / 1024; + int inc = rate_factor; int disp[32]; - int high = 128; + int high = HEMISPHERE_3V_CV; int pos = head - (inc * 15) - random(1,3); // Try to center the head - if (head < 0) head += length; + if (pos < 0) pos += length; for (int i = 0; i < 32; i++) { int v = pcm[pos]; - if (v < 0) v = 0; - if (v > high) high = v; + //if (v < 0) v = 0; + //if (v > high) high = v; pos += inc; if (pos >= HEM_LOFI_PCM_BUFFER_SIZE) pos -= length; disp[i] = v; From caaf9affc90a50f9a0f0497032b8bb7db0c22c67 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Sun, 20 Nov 2022 22:43:38 -0500 Subject: [PATCH 046/417] Update README.md --- README.md | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 0a0772aa4..0be6d3e07 100755 --- a/README.md +++ b/README.md @@ -1,43 +1,33 @@ -Welcome to Benisphere Suite (djphazer mod) +Welcome to Benisphere Suite, djphazer mod (Phazerville Suite) === ## An active fork expanding upon Hemisphere Suite. -To download and install the latest release, head to [releases](https://github.com/benirose/O_C-BenisphereSuite/releases). +Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this "phazerville" branch takes the Hemisphere Suite in new directions, with many new applets and enhancements to existing ones, while also removing most full-width apps and MIDI-related stuff, abandoning the original minimalist approach, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible. -To learn more about what makes this fork different than the original Hemisphere Suite, head to [the wiki](https://github.com/benirose/O_C-BenisphereSuite/wiki). +I've merged bleeding-edge features from various other branches, and confirmed that it compiles and runs on my uO_C. -Benisphere takes the Hemisphere Suite in a new direction, adding new applets and some enhancements to existing ones, while also removing o_C style apps to make space for these changes. +### Notable Features in this branch: -This "phazerville" branch has merged bleeding-edge features from various other branches, and I've confirmed it compiles and runs on my uO_C. - -### An alternate firmware to an alternate firmware?? - -Yes! This fork was an opportunity for me add stuff I wanted to see on my o_C without having to worry about running up against any legacy issues for other Hemipshere users. When I shared some of the progress I was making in my fork, people seemed excited and interested in trying it out, so I have made it an official fork rather than just my own personal project. I try to give back any improvements I make to the [main Hemispheres repo](https://github.com/Chysn/O_C-HemisphereSuite) when possible, but 1) it is not clear if that repo is still active and 2) there is barely any room remaining on the Teensy for improvements. By deciding that this version of the suite will only contain Hemisphere applets I was able to make room for new applets and other improvements. I do accept feature request issues and pull request, but the things that will make it in to this fork are still largely up to my discression. - -### Ok, so what's changed? - -As of v1.0, I have added two new applets inspired by very popular modules ([Mutable Instruments Grids](https://mutable-instruments.net/modules/grids/) and [Noise Engineering Mimetic Digitalis](https://noiseengineering.us/products/mimetic-digitalis)) as well as some minor improvements to existing apps. I've also removed all of the full-width o_C style apps to make space for these changes. To see all of the changes in detail, visit the [wiki](https://github.com/benirose/O_C-BenisphereSuite/wiki). +* LoFi Tape has been transformed into LoFi Echo (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) +* ShiftReg has been upgraded to DualTM - two concurrent 32-bit registers governed by the same length/prob/scale/range settings, both outputs configurable to Pitch, Mod, Trig, Gate from either register +* AnnularFusion got a makeover, now includes configurable CV input modulation (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) +* Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) +* EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking +* Improved internal clock and left-to-right clock forwarding controls +* Modal-editing style navigation on some applets (TB-3PO, DualTM) ### How do I try it? -Head over to the [releases](https://github.com/benirose/O_C-BenisphereSuite/releases) section and download the latest release. Follow the ["Method A" instructions](https://ornament-and-cri.me/firmware/#method_a) from the Ornament and Crime website, except use the hex file you downloaded from the releases section. - -Alternatively, you can follow instructions in [this video](https://www.youtube.com/watch?v=dg_acylaMZU). - -Note: you can reload any other previous firmware you had installed (stock o_C or Hemisphere Suite) just as easily. +I might release a .hex file if there is demand... I just need to make sure I understand my licensing obligations and give proper credit. Meanwhile, I'm contributing my work upstream to Benispheres. ### How do I build it? You can download this repo and build the code following the ["Method B" instruction](https://ornament-and-cri.me/firmware/#method_b) from the Ornament and Crime website. Very specific legacy versions of the Arduino IDE and Teensyduino add-on are required to build, and are not installable on 64-bit only systems, like Mac OS. You must use an older version (Mojave or before) or a VM to install these versions. -### What's with the name? - -Beta tester [@jroo](https://github.com/jroo) jokingly called it that, and it kind of stuck! It's supposed to be a bit tongue in cheek, so I hope it's not taken too seriously! Also it has a nicer ringer than "Hemisphere Suite BR Fork". - ### Credits -This is a fork of [Hemisphere Suite](https://github.com/Chysn/O_C-HemisphereSuite) by Jason Justian (aka chysn). I could not have built Hemisphere Suite, so a million thanks to Jason for doing the true hard work and keeping it open source. +This is a fork of [Benisphere Suite](https://github.com/benirose/O_C-BenisphereSuite) which is a fork of [Hemisphere Suite](https://github.com/Chysn/O_C-HemisphereSuite) by Jason Justian (aka chysn). ornament**s** & crime**s** is a collaborative project by Patrick Dowling (aka pld), mxmxmx and Tim Churches (aka bennelong.bicyclist) (though mostly by pld and bennelong.bicyclist). it **(considerably) extends** the original firmware for the o_C / ASR eurorack module, designed by mxmxmx. From 41f1c5359e5c54d217f820d56ce52e8fd656bf8c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 20 Nov 2022 22:55:39 -0500 Subject: [PATCH 047/417] Version bump to 1.4.2-phz whatever, version numbers are so silly lol --- software/o_c_REV/OC_version.h | 2 +- software/o_c_REV/hemisphere_config.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 7fac18670..49de08f91 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.1-phz" +#define OC_VERSION "v1.4.2-phz" #define OC_VERSION_URL "github.com/djphazer" #endif diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index ece395d41..bf0c12148 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 56 +#define HEMISPHERE_AVAILABLE_APPLETS 57 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ From 945f0fc1b4575a4e2a72a9049a9b1ba568e9b89d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 22 Nov 2022 14:50:27 -0500 Subject: [PATCH 048/417] LoFi Echo: updates and refinements Add bitcrush, cv2 modulates rate. Output B plays buffer in reverse. Parameters are saved now. --- software/o_c_REV/HEM_LoFiPCM.ino | 89 +++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 14c6a1ec6..245e60d24 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -18,12 +18,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define HEM_LOFI_PCM_BUFFER_SIZE 3000 +#define HEM_LOFI_PCM_BUFFER_SIZE 2048 #define HEM_LOFI_PCM_SPEED 4 -#define LOFI_PCM2CV(S) ((uint32_t)S << 8) - 32512 //32767 is 128 << 8 32512 is 127 << 8 // 0-126 is neg, 127 is 0, 128-254 is pos -// #define CLIPLIMIT 32512 +// #define CLIPLIMIT 6144 // 4V #define CLIPLIMIT HEMISPHERE_3V_CV +const uint16_t CrushMask[14] = { 0x0000, 0x0001, 0x0003, 0x0007, + 0x000f, 0x001f, 0x003f, 0x007f, + 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff }; + class LoFiPCM : public HemisphereApplet { public: @@ -33,29 +36,38 @@ public: void Start() { countdown = HEM_LOFI_PCM_SPEED; - for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 0; //char is unsigned in teensy (0-255)? + for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 0; cursor = 1; //for gui } void Controller() { play = !Gate(0); // Continuously play unless gated - int fdbk = Gate(1) ? 100 : feedback; // Feedback = 100 when gated + fdbk_g = Gate(1) ? 100 : feedback; // Feedback = 100 when gated if (play) { - countdown--; - if (countdown == 0) { + if (--countdown == 0) { if (++head >= length) { - head = 0; - ClockOut(1); + head = 0; + //ClockOut(1); } - int dt = delaytime_pct * length / 100; //convert delaytime to length in samples + int cv = In(0); + int cv2 = DetentedIn(1); + + // bitcrush the input + cv = cv & (~CrushMask[depth]); + + // mix input into the buffer ahead, respecting feedback + int dt = dt_pct * length / 100; //convert delaytime to length in samples int writehead = (head+length + dt) % length; //have to add the extra length to keep modulo positive in case delaytime is neg - int16_t feedbackmix = constrain((pcm[head] * fdbk / 100 + In(0)), -CLIPLIMIT, CLIPLIMIT); + int16_t feedbackmix = constrain((pcm[head] * fdbk_g / 100 + cv), -CLIPLIMIT, CLIPLIMIT); pcm[writehead] = feedbackmix; Out(0, pcm[head]); - countdown = rate_factor; + Out(1, pcm[length-1 - head]); // reverse buffer! + + rate_mod = constrain( rate + Proportion(cv2, HEMISPHERE_MAX_CV, 32), 1, 64); + countdown = rate_mod; } } } @@ -67,62 +79,74 @@ public: } void OnButtonPress() { - if (++cursor > 2) cursor = 0; + if (++cursor > 3) cursor = 0; ResetCursor(); } void OnEncoderMove(int direction) { switch (cursor) { case 0: - delaytime_pct = constrain(delaytime_pct += direction, 0, 99); + dt_pct = constrain(dt_pct += direction, 0, 99); break; case 1: feedback = constrain(feedback += direction, 0, 125); break; case 2: - rate_factor = constrain(rate_factor += direction, 1, 20); + rate = constrain(rate += direction, 1, 32); + break; + case 3: + depth = constrain(depth += direction, 0, 13); break; } //amp_offset_cv = Proportion(amp_offset_pct, 100, HEMISPHERE_MAX_CV); //p[cursor] = constrain(p[cursor] += direction, 0, 100); - - } uint64_t OnDataRequest() { uint64_t data = 0; + Pack(data, PackLocation {0,7}, dt_pct); + Pack(data, PackLocation {7,7}, feedback); + Pack(data, PackLocation {14,5}, rate); + Pack(data, PackLocation {19,4}, depth); return data; } void OnDataReceive(uint64_t data) { + dt_pct = Unpack(data, PackLocation {0,7}); + feedback = Unpack(data, PackLocation {7,7}); + rate = Unpack(data, PackLocation {14,5}); + depth = Unpack(data, PackLocation {19,4}); } protected: void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Mute 2=LoopMode"; - help[HEMISPHERE_HELP_CVS] = "1=Audio 2="; - help[HEMISPHERE_HELP_OUTS] = "A=Audio B=EOC Trg"; + help[HEMISPHERE_HELP_DIGITALS] = "1=Mute 2=Fdbk=100%"; + help[HEMISPHERE_HELP_CVS] = "1=Audio 2=RateMod"; + help[HEMISPHERE_HELP_OUTS] = "A=Audio B=Reverse"; help[HEMISPHERE_HELP_ENCODER] = "Time/Fdbk/Speed"; // "------------------" <-- Size Guide } private: + const int length = HEM_LOFI_PCM_BUFFER_SIZE; + int16_t pcm[HEM_LOFI_PCM_BUFFER_SIZE]; bool play = 0; //play always on unless gated on Digital 1 int head = 0; // Location of play/record head - int delaytime_pct = 50; //delaytime as percentage of delayline buffer + int dt_pct = 50; //delaytime as percentage of delayline buffer int feedback = 50; + int fdbk_g = feedback; int countdown = HEM_LOFI_PCM_SPEED; - int rate_factor = HEM_LOFI_PCM_SPEED; - int length = HEM_LOFI_PCM_BUFFER_SIZE; + int rate = HEM_LOFI_PCM_SPEED; + int rate_mod = rate; + int depth = 4; // bit depth reduction aka bitcrush int cursor; //for gui - void DrawWaveform() { //int inc = HEM_LOFI_PCM_BUFFER_SIZE / 1024; - int inc = rate_factor; + int inc = rate_mod; int disp[32]; int high = HEMISPHERE_3V_CV; int pos = head - (inc * 15) - random(1,3); // Try to center the head @@ -151,14 +175,19 @@ private: for (int param = 0; param < 2; param++) { gfxIcon(31 * param, 15, param ? GAUGE_ICON : CLOCK_ICON ); - gfxPrint(4 + pad(100, delaytime_pct), 15, delaytime_pct); - gfxPrint(36 + pad(1000, feedback), 15, feedback); + gfxPrint(4 + pad(100, dt_pct), 15, dt_pct); + gfxPrint(36 + pad(1000, fdbk_g), 15, fdbk_g); if (param == cursor) gfxCursor(0 + (31 * param), 23, 30); } } else { - gfxPrint(0, 15, "Rate:"); - gfxPrint(30, 15, rate_factor); - gfxCursor(30, 23, 16); + //gfxPrint(0, 15, "XxX"); + gfxIcon(0, 15, WAVEFORM_ICON); + gfxIcon(8, 15, BURST_ICON); + gfxIcon(22, 15, LEFT_RIGHT_ICON); + gfxPrint(30, 15, rate_mod); + gfxIcon(42, 15, UP_DOWN_ICON); + gfxPrint(50, 15, depth); + gfxCursor(30 + (cursor-2)*20, 23, 14); } } From 501667d708a7664f4fad7a9ea6c5e8cc05344af0 Mon Sep 17 00:00:00 2001 From: Ben Rosenbach Date: Tue, 22 Nov 2022 11:20:49 -0500 Subject: [PATCH 049/417] Reseed the loop if CV value changes in ProbMeloD --- software/o_c_REV/HEM_ProbabilityMelody.ino | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index 2cde3a3c5..49a7e7d00 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -55,17 +55,24 @@ public: int downCv = DetentedIn(0); + int oldDown = down; if (downCv < 0) down = 1; if (downCv > 0) { down = constrain(ProportionCV(downCv, HEM_PROB_MEL_MAX_RANGE + 1), 1, up); } int upCv = DetentedIn(1); + int oldUp = up; if (upCv < 0) up = 1; if (upCv > 0) { up = constrain(ProportionCV(upCv, HEM_PROB_MEL_MAX_RANGE + 1), down, HEM_PROB_MEL_MAX_RANGE); } + // reseed loop if range has changed due to CV + if (isLooping && (oldDown != down || oldUp != up)) { + GenerateLoop(); + } + if (Clock(0) || loop_linker->Ready()) { if (isLooping) { pitch = loop[loop_linker->GetLoopStep()] + 60; From 32002656dcfa947b5f0781d822470cda14b25471 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 22 Nov 2022 15:13:50 -0500 Subject: [PATCH 050/417] Remove redundant copy of Euclid, and disable DrCrusher LoFi Echo can do everything DrCrusher does when time and feedback are both set to 0. Also disabled SkewedLFO, because I'm pretty sure EbbAndLfo fully replaces it. --- ...Crusher.ino => HEM_DrCrusher.ino.disabled} | 0 software/o_c_REV/HEM_Euclid.ino | 280 ------------------ software/o_c_REV/hemisphere_config.h | 6 +- 3 files changed, 3 insertions(+), 283 deletions(-) rename software/o_c_REV/{HEM_DrCrusher.ino => HEM_DrCrusher.ino.disabled} (100%) delete mode 100644 software/o_c_REV/HEM_Euclid.ino diff --git a/software/o_c_REV/HEM_DrCrusher.ino b/software/o_c_REV/HEM_DrCrusher.ino.disabled similarity index 100% rename from software/o_c_REV/HEM_DrCrusher.ino rename to software/o_c_REV/HEM_DrCrusher.ino.disabled diff --git a/software/o_c_REV/HEM_Euclid.ino b/software/o_c_REV/HEM_Euclid.ino deleted file mode 100644 index 788b0ff92..000000000 --- a/software/o_c_REV/HEM_Euclid.ino +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) 2022, Alessio Degani -// Copyright (c) 2018, Jason Justian -// -// Bjorklund pattern filter, Copyright (c) 2016 Tim Churches -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#include -#include "OC_core.h" -#include "bjorklund.h" - -class Euclid : public HemisphereApplet { -public: - - const char* applet_name() { - return "Euclid"; - } - - void Start() { - - ForEachChannel(ch) - { - length[ch] = 4; - beats[ch] = 1; - pattern[ch] = EuclideanPattern(length[ch] - 1, beats[ch], 0);; - } - step = 0; - } - - void Controller() { - if (Clock(1)) step = 0; // Reset - - // Advance both rings - if (Clock(0) && !Gate(1)) { - - int cv_data[2]; - - cv_data[0] = DetentedIn(0); - cv_data[1] = DetentedIn(1); - ForEachChannel(ch) - { - actual_length[ch] = length[ch]; - actual_rotation[ch] = rotation[ch]; - actual_beats[ch] = beats[ch]; - for (uint8_t ch_src = 0; ch_src <= 1; ch_src++) { - if (cv_dest[ch_src] == 0+3*ch) { - actual_length[ch] = (uint8_t)constrain(actual_length[ch] + Proportion(cv_data[ch_src], HEMISPHERE_MAX_CV, 29), 3, 32); - // if (actual_beats[ch] > actual_length[ch]) actual_beats[ch] = actual_length[ch]; - // if (actual_rotation[ch] > actual_length[ch]-1) actual_rotation[ch] = actual_length[ch]-1; - } - if (cv_dest[ch_src] == 1+3*ch) { - actual_beats[ch] = (uint8_t)constrain(actual_beats[ch] + Proportion(cv_data[ch_src], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); - } - if (cv_dest[ch_src] == 2+3*ch) { - actual_rotation[ch] = (uint8_t)constrain(actual_rotation[ch] + Proportion(cv_data[ch_src], HEMISPHERE_MAX_CV, actual_length[ch]), 0, actual_length[ch]-1); - } - } - - // Store the pattern for display - pattern[ch] = EuclideanPattern(actual_length[ch]-1, actual_beats[ch], actual_rotation[ch]); - int sb = step % actual_length[ch]; - if ((pattern[ch] >> sb) & 0x01) { - ClockOut(ch); - } - } - - // Plan for the thing to run forever and ever - if (++step >= actual_length[0] * actual_length[1]) step = 0; - } - } - - void View() { - gfxHeader(applet_name()); - DrawEditor(); - } - - void OnButtonPress() { - if (++cursor > 7) cursor = 0; - ResetCursor(); - } - - void OnEncoderMove(int direction) { - int ch = cursor < 4 ? 0 : 1; - int f = cursor - (ch * 4); // Cursor function - switch(f) { - case 0: - length[ch] = constrain(length[ch] + direction, 3, 32); - if (beats[ch] > length[ch]) beats[ch] = length[ch]; - if (rotation[ch] > length[ch]-1) rotation[ch] = length[ch]-1; - // SetDisplayPositions(ch, 24 - (8 * ch)); - break; - case 1: - beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); - break; - case 2: - rotation[ch] = constrain(rotation[ch] + direction, 0, length[ch]-1); - break; - case 3: - cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, 5); - break; - default: - break; - } - actual_length[ch] = length[ch]; - actual_rotation[ch] = rotation[ch]; - actual_beats[ch] = beats[ch]; - } - - uint64_t OnDataRequest() { - uint64_t data = 0; - Pack(data, PackLocation {0,4}, length[0] - 1); - Pack(data, PackLocation {4,4}, beats[0] - 1); - Pack(data, PackLocation {8,4}, rotation[0]); - Pack(data, PackLocation {12,4}, length[1] - 1); - Pack(data, PackLocation {16,4}, beats[1] - 1); - Pack(data, PackLocation {20,4}, rotation[1]); - Pack(data, PackLocation {24,4}, cv_dest[0]); - Pack(data, PackLocation {28,4}, cv_dest[1]); - return data; - } - - void OnDataReceive(uint64_t data) { - ForEachChannel(ch) { - length[ch] = Unpack(data, PackLocation {0+12*ch,4}) + 1; - beats[ch] = Unpack(data, PackLocation {4+12*ch,4}) + 1; - rotation[ch] = Unpack(data, PackLocation {8+12*ch,4}); - cv_dest[ch] = Unpack(data, PackLocation {24+4*ch,4}); - actual_length[ch] = length[ch]; - actual_beats[ch] = beats[ch]; - actual_rotation[ch] = rotation[ch]; - } - // length[0] = Unpack(data, PackLocation {0,4}) + 1; - // beats[0] = Unpack(data, PackLocation {4,4}) + 1; - // rotation[0] = Unpack(data, PackLocation {8,4}); - // length[1] = Unpack(data, PackLocation {12,4}) + 1; - // beats[1] = Unpack(data, PackLocation {16,4}) + 1; - // rotation[1] = Unpack(data, PackLocation {20,4}); - // cv_dest[0] = Unpack(data, PackLocation {24,4}); - // cv_dest[1] = Unpack(data, PackLocation {28,4}); - // actual_length[0] = length[0]; - // actual_beats[0] = beats[0]; - // actual_rotation[0] = rotation[0]; - // actual_length[1] = length[1]; - // actual_beats[1] = beats[1]; - // actual_rotation[1] = rotation[1]; - } - -protected: - void SetHelp() { - // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; - help[HEMISPHERE_HELP_CVS] = "Rotate 1=Ch1 2=Ch2"; - help[HEMISPHERE_HELP_OUTS] = "Clock A=Ch1 B=Ch2"; - help[HEMISPHERE_HELP_ENCODER] = "Length/Hits Ch1,2"; - // "------------------" <-- Size Guide - } - -private: - int step; - uint8_t cursor = 0; // Ch1: 0=Length, 1=Hits, 2=Rotation; Ch2: 3=Length, 4=Hits, 5=Rotation - // AFStepCoord disp_coord[2][32]; - uint32_t pattern[2]; - - // Settings - uint8_t length[2]; - uint8_t beats[2]; - uint8_t rotation[2]; - uint8_t actual_length[2]; - uint8_t actual_beats[2]; - uint8_t actual_rotation[2]; - uint8_t cv_dest[2]; - int cv_data[2]; - - void DrawEditor() { - int f = 0; - - ForEachChannel(ch) - { - f = cursor - (ch * 4); // Cursor function - - // Length cursor - gfxPrint(12 + 24*ch + pad(10, actual_length[ch]), 15, actual_length[ch]); - if (f == 0) gfxCursor(13 + 24*ch, 23, 12); - for (int ch_dest = 0; ch_dest < 2; ch_dest++){ - if (cv_dest[ch_dest] == 0+3*ch) gfxBitmap(26 + 24*ch, 14+ch, 3, ch_dest?SUB_TWO:SUP_ONE); - } - - // Beats cursor - gfxPrint(12 + 24*ch + pad(10, actual_beats[ch]), 25, actual_beats[ch]); - if (f == 1) gfxCursor(13 + 24*ch, 33, 12); - for (int ch_dest = 0; ch_dest < 2; ch_dest++){ - if (cv_dest[ch_dest] == 1+3*ch) gfxBitmap(26 + 24*ch, 24+ch, 3, ch_dest?SUB_TWO:SUP_ONE); - } - - // Rotation cursor - gfxPrint(12 + 24*ch + pad(10, actual_rotation[ch]), 35, actual_rotation[ch]); - if (f == 2) gfxCursor(13 + 24*ch, 43, 12); - for (int ch_dest = 0; ch_dest < 2; ch_dest++){ - if (cv_dest[ch_dest] == 2+3*ch) gfxBitmap(26 + 24*ch, 34+ch, 3, ch_dest?SUB_TWO:SUP_ONE); - } - - // CV destination - gfxPrint(12 + 24*ch + pad(10, cv_dest[ch]), 45, cv_dest[ch]); - if (f == 3) gfxCursor(13 + 24*ch, 53, 12); - - // int curr_cv = 0; - // for (int ch_src = 0; ch_src < 2; ch_src++) { - // if (cv_dest[ch] == 0+3*ch_src) curr_cv = length[ch_src] + cv_data[ch]; - // if (cv_dest[ch] == 1+3*ch_src) curr_cv = beats[ch_src] + cv_data[ch]; - // if (cv_dest[ch] == 2+3*ch_src) curr_cv = rotation[ch_src] + cv_data[ch]; - // } - // gfxPrint(12 + 24*ch + pad(10, pattern[ch]), 55, pattern[ch]); - } - - gfxBitmap(1, 15, 8, LEFT_RIGHT_ICON); - gfxBitmap(1, 25, 8, X_NOTE_ICON); - gfxBitmap(1, 35, 8, LOOP_ICON); - gfxBitmap(1, 45, 8, CV_ICON); - gfxLine(34, 15, 34, 55); - } -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// Hemisphere Applet Functions -/// -/// Once you run the find-and-replace to make these refer to Euclid, -/// it's usually not necessary to do anything with these functions. You -/// should prefer to handle things in the HemisphereApplet child class -/// above. -//////////////////////////////////////////////////////////////////////////////// -Euclid Euclid_instance[2]; - -void Euclid_Start(bool hemisphere) { - Euclid_instance[hemisphere].BaseStart(hemisphere); -} - -void Euclid_Controller(bool hemisphere, bool forwarding) { - Euclid_instance[hemisphere].BaseController(forwarding); -} - -void Euclid_View(bool hemisphere) { - Euclid_instance[hemisphere].BaseView(); -} - -void Euclid_OnButtonPress(bool hemisphere) { - Euclid_instance[hemisphere].OnButtonPress(); -} - -void Euclid_OnEncoderMove(bool hemisphere, int direction) { - Euclid_instance[hemisphere].OnEncoderMove(direction); -} - -void Euclid_ToggleHelpScreen(bool hemisphere) { - Euclid_instance[hemisphere].HelpScreen(); -} - -uint64_t Euclid_OnDataRequest(bool hemisphere) { - return Euclid_instance[hemisphere].OnDataRequest(); -} - -void Euclid_OnDataReceive(bool hemisphere, uint64_t data) { - Euclid_instance[hemisphere].OnDataReceive(data); -} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index bf0c12148..ce0c2f010 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 57 +#define HEMISPHERE_AVAILABLE_APPLETS 55 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ @@ -32,7 +32,6 @@ DECLARE_APPLET( 28, 0x04, ClockSkip), \ DECLARE_APPLET( 30, 0x10, Compare), \ DECLARE_APPLET( 24, 0x02, CVRecV2), \ - DECLARE_APPLET( 55, 0x80, DrCrusher), \ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ @@ -56,7 +55,6 @@ DECLARE_APPLET( 48, 0x45, ShiftGate), \ DECLARE_APPLET( 58, 0x01, Shredder), \ DECLARE_APPLET( 36, 0x04, Shuffle), \ - DECLARE_APPLET( 7, 0x01, SkewedLFO), \ DECLARE_APPLET( 19, 0x01, Slew), \ DECLARE_APPLET( 46, 0x08, Squanch), \ DECLARE_APPLET( 61, 0x01, Stairs), \ @@ -79,4 +77,6 @@ DECLARE_APPLET( 27, 0x20, hMIDIOut), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 17, 0x50, GatedVCA), \ + DECLARE_APPLET( 55, 0x80, DrCrusher), \ + DECLARE_APPLET( 7, 0x01, SkewedLFO), \ */ From 885119d9d5db08c9a8698064dccabf1541382e5e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 22 Nov 2022 16:44:12 -0500 Subject: [PATCH 051/417] LoFi Echo: improve waveform, simplify & cleanup --- software/o_c_REV/HEM_LoFiPCM.ino | 35 +++++++++----------------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 245e60d24..3d0da6d8d 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -23,10 +23,6 @@ // #define CLIPLIMIT 6144 // 4V #define CLIPLIMIT HEMISPHERE_3V_CV -const uint16_t CrushMask[14] = { 0x0000, 0x0001, 0x0003, 0x0007, - 0x000f, 0x001f, 0x003f, 0x007f, - 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff }; - class LoFiPCM : public HemisphereApplet { public: @@ -51,11 +47,12 @@ public: //ClockOut(1); } - int cv = In(0); + int16_t cv = In(0); int cv2 = DetentedIn(1); // bitcrush the input - cv = cv & (~CrushMask[depth]); + cv = cv >> depth; + cv = cv << depth; // mix input into the buffer ahead, respecting feedback int dt = dt_pct * length / 100; //convert delaytime to length in samples @@ -145,27 +142,16 @@ private: int cursor; //for gui void DrawWaveform() { - //int inc = HEM_LOFI_PCM_BUFFER_SIZE / 1024; - int inc = rate_mod; - int disp[32]; - int high = HEMISPHERE_3V_CV; - int pos = head - (inc * 15) - random(1,3); // Try to center the head + int inc = rate_mod/2 + 1; + int pos = head - (inc * 31) - random(1,3); // Try to center the head if (pos < 0) pos += length; - for (int i = 0; i < 32; i++) + for (int i = 0; i < 64; i++) { - int v = pcm[pos]; - //if (v < 0) v = 0; - //if (v > high) high = v; + int height = Proportion(pcm[pos], CLIPLIMIT, 16); + gfxLine(i, 46, i, 46+height); + pos += inc; - if (pos >= HEM_LOFI_PCM_BUFFER_SIZE) pos -= length; - disp[i] = v; - } - - for (int x = 0; x < 32; x++) - { - int height = Proportion(disp[x], high, 30); - int margin = (32 - height) / 2; - gfxLine(x * 2, 30 + margin, x * 2, height + 30 + margin); + if (pos >= length) pos -= length; } } @@ -180,7 +166,6 @@ private: if (param == cursor) gfxCursor(0 + (31 * param), 23, 30); } } else { - //gfxPrint(0, 15, "XxX"); gfxIcon(0, 15, WAVEFORM_ICON); gfxIcon(8, 15, BURST_ICON); gfxIcon(22, 15, LEFT_RIGHT_ICON); From 934c1dccedbc23141c39dfa6d7ca7d3717c13a80 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 25 Nov 2022 02:58:06 -0500 Subject: [PATCH 052/417] LoFi Echo: Help text, code cleanup, smaller data types for params --- software/o_c_REV/HEM_LoFiPCM.ino | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 3d0da6d8d..897d39dea 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -54,11 +54,11 @@ public: cv = cv >> depth; cv = cv << depth; + // int dt = dt_pct * length / 100; //convert delaytime to length in samples + head_w = (head + length + dt_pct*length/100) % length; //have to add the extra length to keep modulo positive in case delaytime is neg + // mix input into the buffer ahead, respecting feedback - int dt = dt_pct * length / 100; //convert delaytime to length in samples - int writehead = (head+length + dt) % length; //have to add the extra length to keep modulo positive in case delaytime is neg - int16_t feedbackmix = constrain((pcm[head] * fdbk_g / 100 + cv), -CLIPLIMIT, CLIPLIMIT); - pcm[writehead] = feedbackmix; + pcm[head_w] = constrain((pcm[head] * fdbk_g / 100 + cv), -CLIPLIMIT, CLIPLIMIT); Out(0, pcm[head]); Out(1, pcm[length-1 - head]); // reverse buffer! @@ -119,10 +119,10 @@ public: protected: void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Mute 2=Fdbk=100%"; + help[HEMISPHERE_HELP_DIGITALS] = "1=Pause 2= Fb=100"; help[HEMISPHERE_HELP_CVS] = "1=Audio 2=RateMod"; help[HEMISPHERE_HELP_OUTS] = "A=Audio B=Reverse"; - help[HEMISPHERE_HELP_ENCODER] = "Time/Fdbk/Speed"; + help[HEMISPHERE_HELP_ENCODER] = "Time/Fbk/Rate/Bits"; // "------------------" <-- Size Guide } @@ -131,15 +131,16 @@ private: int16_t pcm[HEM_LOFI_PCM_BUFFER_SIZE]; bool play = 0; //play always on unless gated on Digital 1 - int head = 0; // Location of play/record head - int dt_pct = 50; //delaytime as percentage of delayline buffer - int feedback = 50; - int fdbk_g = feedback; - int countdown = HEM_LOFI_PCM_SPEED; - int rate = HEM_LOFI_PCM_SPEED; - int rate_mod = rate; - int depth = 4; // bit depth reduction aka bitcrush - int cursor; //for gui + uint16_t head = 0; // Location of read/play head + uint16_t head_w = 0; // Location of write/record head + int8_t dt_pct = 50; //delaytime as percentage of delayline buffer + int8_t feedback = 50; + int8_t fdbk_g = feedback; + int8_t countdown = HEM_LOFI_PCM_SPEED; + uint8_t rate = HEM_LOFI_PCM_SPEED; + uint8_t rate_mod = rate; + int depth = 1; // bit reduction depth aka bitcrush + uint8_t cursor; //for gui void DrawWaveform() { int inc = rate_mod/2 + 1; @@ -158,13 +159,12 @@ private: void DrawSelector() { if (cursor < 2) { - for (int param = 0; param < 2; param++) - { + for (int param = 0; param < 2; param++) { gfxIcon(31 * param, 15, param ? GAUGE_ICON : CLOCK_ICON ); - gfxPrint(4 + pad(100, dt_pct), 15, dt_pct); - gfxPrint(36 + pad(1000, fdbk_g), 15, fdbk_g); - if (param == cursor) gfxCursor(0 + (31 * param), 23, 30); } + gfxPrint(4 + pad(100, dt_pct), 15, dt_pct); + gfxPrint(36 + pad(1000, fdbk_g), 15, fdbk_g); + gfxCursor(0 + (31 * cursor), 23, 30); } else { gfxIcon(0, 15, WAVEFORM_ICON); gfxIcon(8, 15, BURST_ICON); From 5d6c64b32afdefef72964e45dd35b182cc3d6520 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 25 Nov 2022 03:10:41 -0500 Subject: [PATCH 053/417] Save/recall Clock Forwarding state, don't save Metronome data Internal clock settings are stored in ClockSetup applet data, so Metronome settings are unnecessary. --- software/o_c_REV/HEM_ClockSetup.ino | 7 ++++--- software/o_c_REV/HEM_Metronome.ino | 12 ++---------- software/o_c_REV/HSClockManager.h | 10 +++++----- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 55456fe48..f2cb21171 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -69,18 +69,19 @@ public: Pack(data, PackLocation { 0, 1 }, clock_m->IsRunning() || clock_m->IsPaused()); Pack(data, PackLocation { 1, 9 }, clock_m->GetTempo()); Pack(data, PackLocation { 10, 5 }, clock_m->GetMultiply()); + Pack(data, PackLocation { 15, 1 }, clock_m->IsForwarded()); return data; } void OnDataReceive(uint64_t data) { if (Unpack(data, PackLocation { 0, 1 })) { - clock_m->Start(); - clock_m->Pause(); + clock_m->Start(1); // start paused } else { clock_m->Stop(); } clock_m->SetTempoBPM(Unpack(data, PackLocation { 1, 9 })); clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 })); + clock_m->SetForwarding(Unpack(data, PackLocation { 15, 1 })); } protected: @@ -94,7 +95,7 @@ protected: } private: - int cursor; // 0=Source, 1=Tempo, 2=Multiply + char cursor; // 0=Source, 1=Tempo, 2=Multiply ClockManager *clock_m = clock_m->get(); void DrawInterface() { diff --git a/software/o_c_REV/HEM_Metronome.ino b/software/o_c_REV/HEM_Metronome.ino index a551698a0..c767e3eb2 100644 --- a/software/o_c_REV/HEM_Metronome.ino +++ b/software/o_c_REV/HEM_Metronome.ino @@ -49,21 +49,14 @@ public: void OnButtonPress() { } void OnEncoderMove(int direction) { - uint16_t bpm = clock_m->GetTempo(); - bpm += direction; - clock_m->SetTempoBPM(bpm); + clock_m->SetTempoBPM(clock_m->GetTempo() + direction); } uint64_t OnDataRequest() { - uint64_t data = 0; - Pack(data, PackLocation {0,16}, clock_m->GetTempo()); - Pack(data, PackLocation {16,5}, clock_m->GetMultiply() - 1); - return data; + return 0; } void OnDataReceive(uint64_t data) { - clock_m->SetTempoBPM(Unpack(data, PackLocation {0,16})); - clock_m->SetMultiply(Unpack(data, PackLocation {16,5}) + 1); } protected: @@ -77,7 +70,6 @@ protected: } private: - int cursor; // 0=Tempo, 1=Multiply, 2=Start/Stop ClockManager *clock_m = clock_m->get(); void DrawInterface() { diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index b2bab6483..a935cafaf 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -88,23 +88,23 @@ class ClockManager { cycle = 1 - cycle; } - void Start() { + void Start(bool p = 0) { // forwarded = 0; // NJM- logical clock can be forwarded, too running = 1; - Unpause(); + paused = p; } void Stop() { running = 0; - Unpause(); + paused = 0; } void Pause() {paused = 1;} - void Unpause() {paused = 0;} - void ToggleForwarding() {forwarded = 1 - forwarded;} + void SetForwarding(bool f) {forwarded = f;} + bool IsRunning() {return (running && !paused);} bool IsPaused() {return paused;} From 201477265a0e15626659843ce16784a414d34b13 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 25 Nov 2022 23:50:13 -0500 Subject: [PATCH 054/417] Code size optimization --- software/o_c_REV/HEM_ClockSetup.ino | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index f2cb21171..f3fe2b2ea 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -40,7 +40,8 @@ public: } void OnEncoderMove(int direction) { - if (cursor == 0) { // Source + switch (cursor) { + case 0: // Source if (direction > 0) // right turn toggles Forwarding clock_m->ToggleForwarding(); else if (clock_m->IsRunning()) // left turn toggles clock @@ -49,18 +50,15 @@ public: clock_m->Start(); clock_m->Reset(); } - } + break; - if (cursor == 1) { // Set tempo - uint16_t bpm = clock_m->GetTempo(); - bpm += direction; - clock_m->SetTempoBPM(bpm); - } + case 1: // Set tempo + clock_m->SetTempoBPM(clock_m->GetTempo() + direction); + break; - if (cursor == 2) { // Set multiplier - int8_t mult = clock_m->GetMultiply(); - mult += direction; - clock_m->SetMultiply(mult); + case 2: // Set multiplier + clock_m->SetMultiply(clock_m->GetMultiply() + direction); + break; } } From 12558d8223f73888cfef84972a494581b5414ea4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 25 Nov 2022 05:24:57 -0500 Subject: [PATCH 055/417] ClockDivider: fire on the first step when dividing, rather than the last As discussed on modwiggler, this is a more musical way to do clock division, rather that mathematical. Plus some code size optimization. --- software/o_c_REV/HEM_ClockDivider.ino | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/software/o_c_REV/HEM_ClockDivider.ino b/software/o_c_REV/HEM_ClockDivider.ino index 060fef88e..ac226a986 100644 --- a/software/o_c_REV/HEM_ClockDivider.ino +++ b/software/o_c_REV/HEM_ClockDivider.ino @@ -27,16 +27,7 @@ public: return "Clock Div"; } - void Start() { - ForEachChannel(ch) - { - div[ch] = ch + 1; - count[ch] = 0; - next_clock[ch] = 0; - } - cycle_time = 0; - cursor = 0; - } + void Start() { } void Controller() { int this_tick = OC::CORE::ticks; @@ -64,10 +55,8 @@ public: { count[ch]++; if (div[ch] > 0) { // Positive value indicates clock division - if (count[ch] >= div[ch]) { - count[ch] = 0; // Reset - ClockOut(ch); - } + if (count[ch] == 1) ClockOut(ch); // fire on first step + if (count[ch] >= div[ch]) count[ch] = 0; // Reset on last step } else { // Calculate next clock for multiplication on each clock int clock_every = (cycle_time / -div[ch]); @@ -130,11 +119,11 @@ protected: } private: - int div[2]; // Division data for outputs. Positive numbers are divisions, negative numbers are multipliers - int count[2]; // Number of clocks since last output (for clock divide) - int next_clock[2]; // Tick number for the next output (for clock multiply) - int cursor; // Which output is currently being edited - int cycle_time; // Cycle time between the last two clock inputs + int div[2] = {1, 2}; // Division data for outputs. Positive numbers are divisions, negative numbers are multipliers + int count[2] = {0,0}; // Number of clocks since last output (for clock divide) + int next_clock[2] = {0,0}; // Tick number for the next output (for clock multiply) + bool cursor = 0; // Which output is currently being edited + int cycle_time = 0; // Cycle time between the last two clock inputs void DrawSelector() { ForEachChannel(ch) From 67c34fe391a68c82e57513e176dd4ae5f5e6bd7f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 25 Nov 2022 06:41:36 -0500 Subject: [PATCH 056/417] Separate clock multipliers for each hemisphere --- software/o_c_REV/HEM_ClockSetup.ino | 26 +++++++----- software/o_c_REV/HEM_Metronome.ino | 6 +-- software/o_c_REV/HSClockManager.h | 63 +++++++++++++---------------- software/o_c_REV/HemisphereApplet.h | 12 +++--- 4 files changed, 54 insertions(+), 53 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index f3fe2b2ea..bbca695b6 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -36,7 +36,7 @@ public: } void OnButtonPress() { - if (++cursor > 2) cursor = 0; + if (++cursor > 3) cursor = 0; } void OnEncoderMove(int direction) { @@ -57,7 +57,8 @@ public: break; case 2: // Set multiplier - clock_m->SetMultiply(clock_m->GetMultiply() + direction); + case 3: + clock_m->SetMultiply(clock_m->GetMultiply(cursor-2) + direction, cursor-2); break; } } @@ -66,8 +67,9 @@ public: uint64_t data = 0; Pack(data, PackLocation { 0, 1 }, clock_m->IsRunning() || clock_m->IsPaused()); Pack(data, PackLocation { 1, 9 }, clock_m->GetTempo()); - Pack(data, PackLocation { 10, 5 }, clock_m->GetMultiply()); - Pack(data, PackLocation { 15, 1 }, clock_m->IsForwarded()); + Pack(data, PackLocation { 10, 5 }, clock_m->GetMultiply(0)); + Pack(data, PackLocation { 15, 5 }, clock_m->GetMultiply(1)); + Pack(data, PackLocation { 20, 1 }, clock_m->IsForwarded()); return data; } @@ -78,8 +80,9 @@ public: clock_m->Stop(); } clock_m->SetTempoBPM(Unpack(data, PackLocation { 1, 9 })); - clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 })); - clock_m->SetForwarding(Unpack(data, PackLocation { 15, 1 })); + clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 }), 0); + clock_m->SetMultiply(Unpack(data, PackLocation { 15, 5 }), 1); + clock_m->SetForwarding(Unpack(data, PackLocation { 20, 1 })); } protected: @@ -101,8 +104,8 @@ private: // needs to extend across the screen graphics.setPrintPos(1, 2); graphics.print("Clock Setup"); - gfxLine(0, 10, 62, 10); - gfxLine(0, 12, 62, 12); + //gfxLine(0, 10, 62, 10); + //gfxLine(0, 12, 62, 12); graphics.drawLine(0, 10, 127, 10); graphics.drawLine(0, 12, 127, 12); @@ -127,11 +130,16 @@ private: // Multiply gfxPrint(1, 35, "x"); - gfxPrint(clock_m->GetMultiply()); + gfxPrint(clock_m->GetMultiply(0)); + + // secondary multiplier when forwarding internal clock + gfxPrint(33, 35, "x"); + gfxPrint(clock_m->GetMultiply(1)); if (cursor == 0) gfxCursor(20, 23, 54); if (cursor == 1) gfxCursor(23, 33, 18); if (cursor == 2) gfxCursor(8, 43, 12); + if (cursor == 3) gfxCursor(40, 43, 12); } }; diff --git a/software/o_c_REV/HEM_Metronome.ino b/software/o_c_REV/HEM_Metronome.ino index c767e3eb2..5ca557005 100644 --- a/software/o_c_REV/HEM_Metronome.ino +++ b/software/o_c_REV/HEM_Metronome.ino @@ -34,9 +34,9 @@ public: // Outputs if (clock_m->IsRunning()) { - if (clock_m->Tock()) { + if (clock_m->Tock(hemisphere)) { ClockOut(0); - if (clock_m->EndOfBeat()) ClockOut(1); + if (clock_m->EndOfBeat(hemisphere)) ClockOut(1); } } } @@ -98,7 +98,7 @@ private: gfxCircle(40,51,1); // Winder // Pendulum arm - if (clock_m->Cycle()) gfxLine(29,50,21,31); + if (clock_m->Cycle(hemisphere)) gfxLine(29,50,21,31); else gfxLine(29,50,37,32); } diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index a935cafaf..69aeb03da 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -29,29 +29,22 @@ const uint16_t CLOCK_TEMPO_MAX = 300; class ClockManager { static ClockManager *instance; - uint32_t ticks_per_tock; // Based on the selected tempo in BPM - uint32_t last_tock_tick; // The tick of the most recent tock - uint32_t last_tock_check; // To avoid checking the tock more than once per tick - bool tock; // The most recent tock value + uint16_t tempo; // The set tempo, for display somewhere else - bool running; // Specifies whether the clock is running for interprocess communication - bool paused; // Specifies whethr the clock is paused - int8_t tocks_per_beat; // Multiplier - bool cycle; // Alternates for each tock, for display purposes - byte count; // Multiple counter - bool forwarded; // Master clock forwarding is enabled when true + uint32_t ticks_per_tock; // Based on the selected tempo in BPM + bool running = 0; // Specifies whether the clock is running for interprocess communication + bool paused = 0; // Specifies whethr the clock is paused + bool forwarded = 0; // Master clock forwarding is enabled when true + + uint32_t last_tock_tick[2] = {0,0}; // The tick of the most recent tock + uint32_t last_tock_check[2] = {0,0}; // To avoid checking the tock more than once per tick + bool tock = 0; // The most recent tock value + int8_t tocks_per_beat[2] = {1, 1}; // Multiplier + bool cycle[2] = {0,0}; // Alternates for each tock, for display purposes + byte count[2] = {0,0}; // Multiple counter ClockManager() { SetTempoBPM(120); - SetMultiply(1); - running = 0; - paused = 0; - cycle = 0; - last_tock_tick = 0; - last_tock_check = 0; - count = 0; - tock = 0; - forwarded = 0; } public: @@ -60,9 +53,9 @@ class ClockManager { return instance; } - void SetMultiply(int8_t multiply) { + void SetMultiply(int8_t multiply, bool ch = 0) { multiply = constrain(multiply, 1, 24); - tocks_per_beat = multiply; + tocks_per_beat[ch] = multiply; } /* Set ticks per tock, based on one million ticks per minute divided by beats per minute. @@ -75,17 +68,17 @@ class ClockManager { tempo = bpm; } - int8_t GetMultiply() {return tocks_per_beat;} + int8_t GetMultiply(bool ch = 0) {return tocks_per_beat[ch];} /* Gets the current tempo. This can be used between client processes, like two different * hemispheres. */ uint16_t GetTempo() {return tempo;} - void Reset() { - last_tock_tick = OC::CORE::ticks; - count = 0; - cycle = 1 - cycle; + void Reset(bool ch = 0) { + last_tock_tick[ch] = OC::CORE::ticks; + count[ch] = 0; + cycle[ch] = 1 - cycle[ch]; } void Start(bool p = 0) { @@ -111,23 +104,23 @@ class ClockManager { bool IsForwarded() {return forwarded;} - /* Returns true if the clock should fire on this tick, based on the current tempo */ - bool Tock() { + /* Returns true if the clock should fire on this tick, based on the current tempo and multiplier */ + bool Tock(bool ch = 0) { uint32_t now = OC::CORE::ticks; - if (now != last_tock_check) { - last_tock_check = now; - if (now >= (last_tock_tick + (ticks_per_tock / static_cast(tocks_per_beat)))) { + if (now != last_tock_check[ch]) { + last_tock_check[ch] = now; + if (now >= (last_tock_tick[ch] + (ticks_per_tock / static_cast(tocks_per_beat[ch])))) { tock = 1; - last_tock_tick = now; - if (++count >= tocks_per_beat) Reset(); + last_tock_tick[ch] = now; + if (++count[ch] >= tocks_per_beat[ch]) Reset(ch); } else tock = 0; } return tock; } - bool EndOfBeat() {return count == 0;} + bool EndOfBeat(bool ch = 0) {return count[ch] == 0;} - bool Cycle() {return cycle;} + bool Cycle(bool ch = 0) {return cycle[ch];} }; ClockManager *ClockManager::instance = 0; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index eb8890f95..0c55fd707 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -68,8 +68,8 @@ typedef int32_t simfloat; // Specifies where data goes in flash storage for each selcted applet, and how big it is typedef struct PackLocation { - int location; - int size; + uint8_t location; + uint8_t size; } PackLocation; class HemisphereApplet { @@ -329,11 +329,11 @@ class HemisphereApplet { if (ch == 0) { // clock triggers if (hemisphere == LEFT_HEMISPHERE) { - if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(); + if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); else clocked = OC::DigitalInputs::clocked(); } else { // right side is special if (master_clock_bus) { // forwarding from left - if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(); + if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); else clocked = OC::DigitalInputs::clocked(); } else clocked = OC::DigitalInputs::clocked(); @@ -448,11 +448,11 @@ class HemisphereApplet { * // etc... * } */ - void StartADCLag(int ch = 0) { + void StartADCLag(bool ch = 0) { adc_lag_countdown[ch] = HEMISPHERE_ADC_LAG; } - bool EndOfADCLag(int ch = 0) { + bool EndOfADCLag(bool ch = 0) { if (adc_lag_countdown[ch] < 0) return false; return (--adc_lag_countdown[ch] == 0); } From 3aac302e30f897874882fc94f1afadbc353bad93 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 28 Nov 2022 03:43:09 -0500 Subject: [PATCH 057/417] Fix compiler warnings from data type optimization --- software/o_c_REV/HEM_Sequence5.ino | 4 ++-- software/o_c_REV/HEM_VectorLFO.ino | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/HEM_Sequence5.ino b/software/o_c_REV/HEM_Sequence5.ino index 58dfd9db2..2cd34b675 100644 --- a/software/o_c_REV/HEM_Sequence5.ino +++ b/software/o_c_REV/HEM_Sequence5.ino @@ -78,7 +78,7 @@ public: uint64_t data = 0; for (int s = 0; s < SEQ5_STEPS; s++) { - Pack(data, PackLocation {s * 5,5}, note[s]); + Pack(data, PackLocation {uint8_t(s * 5),5}, note[s]); } Pack(data, PackLocation{25,5}, muted); return data; @@ -87,7 +87,7 @@ public: void OnDataReceive(uint64_t data) { for (int s = 0; s < SEQ5_STEPS; s++) { - note[s] = Unpack(data, PackLocation {s * 5,5}); + note[s] = Unpack(data, PackLocation {uint8_t(s * 5),5}); } muted = Unpack(data, PackLocation {25,5}); } diff --git a/software/o_c_REV/HEM_VectorLFO.ino b/software/o_c_REV/HEM_VectorLFO.ino index d5ef56eb0..6a41827a8 100644 --- a/software/o_c_REV/HEM_VectorLFO.ino +++ b/software/o_c_REV/HEM_VectorLFO.ino @@ -132,10 +132,10 @@ public: if (freq[i] > 250) exponent++; if (freq[i] > 1000) exponent++; if (freq[i] > 10000) exponent++; - Pack(data, PackLocation {12 + i * 10, 2}, exponent); + Pack(data, PackLocation {uint8_t(12 + i * 10), 2}, exponent); int mantissa = freq[i] / pow10_lut[exponent]; - Pack(data, PackLocation {12 + i * 10 + 2, 8}, mantissa); + Pack(data, PackLocation {uint8_t(12 + i * 10 + 2), 8}, mantissa); } return data; @@ -143,8 +143,8 @@ public: void OnDataReceive(uint64_t data) { for (int i = 0; i < 2; ++i) { - int exponent = Unpack(data, PackLocation {12 + i * 10, 2}); - int mantissa = Unpack(data, PackLocation {12 + i * 10 + 2, 8}); + int exponent = Unpack(data, PackLocation {uint8_t(12 + i * 10), 2}); + int mantissa = Unpack(data, PackLocation {uint8_t(12 + i * 10 + 2), 8}); freq[i] = mantissa * pow10_lut[exponent]; } From 1e0384a98d1bb0b9de75b912c4789cf3e3148be1 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Sun, 27 Nov 2022 15:31:05 -0800 Subject: [PATCH 058/417] Fix transpose in quantizer --- software/o_c_REV/braids_quantizer.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/software/o_c_REV/braids_quantizer.cpp b/software/o_c_REV/braids_quantizer.cpp index 61b35019c..73231d3e3 100644 --- a/software/o_c_REV/braids_quantizer.cpp +++ b/software/o_c_REV/braids_quantizer.cpp @@ -85,6 +85,14 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { q = num_notes_ - 1; } + q += transpose; + octave += q / num_notes_; + q %= num_notes_; + if (q < 0) { + q += num_notes_; + octave--; + } + note_number_ = octave * num_notes_ + q; codeword_ = notes_[q] + octave * span_; previous_boundary_ = q == 0 @@ -97,6 +105,7 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { : notes_[q + 1] + octave * span_; next_boundary_ = (9 * next_boundary_ + 7 * codeword_) >> 4; + transpose_ = transpose; pitch = codeword_; } pitch += root; From bbd1e1d74074d2cdd350340bc1264c1b0ce7f157 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 28 Nov 2022 22:09:19 -0500 Subject: [PATCH 059/417] Version bump to 1.4.3 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 49de08f91..db3c1813e 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.2-phz" +#define OC_VERSION "v1.4.3" #define OC_VERSION_URL "github.com/djphazer" #endif From 4a334abdba7af67c71d1d00f95416a95dd440403 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 29 Nov 2022 02:53:30 -0500 Subject: [PATCH 060/417] LoFi Echo: use 8-bit buffer instead of 16 --- software/o_c_REV/HEM_LoFiPCM.ino | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 897d39dea..4e5a9edd0 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -20,9 +20,13 @@ #define HEM_LOFI_PCM_BUFFER_SIZE 2048 #define HEM_LOFI_PCM_SPEED 4 -// #define CLIPLIMIT 6144 // 4V + +// #define CLIPLIMIT 32512 #define CLIPLIMIT HEMISPHERE_3V_CV +#define PCM_TO_CV(S) Proportion((int)S - 127, 128, CLIPLIMIT) +#define CV_TO_PCM(S) Proportion(constrain(S, -CLIPLIMIT, CLIPLIMIT), CLIPLIMIT, 128) + 127 + class LoFiPCM : public HemisphereApplet { public: @@ -32,7 +36,7 @@ public: void Start() { countdown = HEM_LOFI_PCM_SPEED; - for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 0; + for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 127; cursor = 1; //for gui } @@ -47,7 +51,7 @@ public: //ClockOut(1); } - int16_t cv = In(0); + int cv = In(0); int cv2 = DetentedIn(1); // bitcrush the input @@ -58,10 +62,11 @@ public: head_w = (head + length + dt_pct*length/100) % length; //have to add the extra length to keep modulo positive in case delaytime is neg // mix input into the buffer ahead, respecting feedback - pcm[head_w] = constrain((pcm[head] * fdbk_g / 100 + cv), -CLIPLIMIT, CLIPLIMIT); + int fbmix = PCM_TO_CV(pcm[head]) * fdbk_g / 100 + cv; + pcm[head_w] = CV_TO_PCM(fbmix); - Out(0, pcm[head]); - Out(1, pcm[length-1 - head]); // reverse buffer! + Out(0, PCM_TO_CV(pcm[head])); + Out(1, PCM_TO_CV(pcm[length-1 - head])); // reverse buffer! rate_mod = constrain( rate + Proportion(cv2, HEMISPHERE_MAX_CV, 32), 1, 64); countdown = rate_mod; @@ -129,7 +134,7 @@ protected: private: const int length = HEM_LOFI_PCM_BUFFER_SIZE; - int16_t pcm[HEM_LOFI_PCM_BUFFER_SIZE]; + uint8_t pcm[HEM_LOFI_PCM_BUFFER_SIZE]; bool play = 0; //play always on unless gated on Digital 1 uint16_t head = 0; // Location of read/play head uint16_t head_w = 0; // Location of write/record head @@ -139,7 +144,7 @@ private: int8_t countdown = HEM_LOFI_PCM_SPEED; uint8_t rate = HEM_LOFI_PCM_SPEED; uint8_t rate_mod = rate; - int depth = 1; // bit reduction depth aka bitcrush + int depth = 0; // bit reduction depth aka bitcrush uint8_t cursor; //for gui void DrawWaveform() { @@ -148,7 +153,7 @@ private: if (pos < 0) pos += length; for (int i = 0; i < 64; i++) { - int height = Proportion(pcm[pos], CLIPLIMIT, 16); + int height = Proportion((int)pcm[pos]-127, 128, 16); gfxLine(i, 46, i, 46+height); pos += inc; From 474f7d4de3fb56e45b1d68b55e0ad4f14f7c4864 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 29 Nov 2022 02:56:33 -0500 Subject: [PATCH 061/417] Oof, didn't realize ClockSetup data is only stored in 16 bits I guess we only really need to store one multiplier. Also some small data type optimizations. --- software/o_c_REV/HEM_ClockSetup.ino | 6 +----- software/o_c_REV/HemisphereApplet.h | 10 +++++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index bbca695b6..578b7df1c 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -68,8 +68,6 @@ public: Pack(data, PackLocation { 0, 1 }, clock_m->IsRunning() || clock_m->IsPaused()); Pack(data, PackLocation { 1, 9 }, clock_m->GetTempo()); Pack(data, PackLocation { 10, 5 }, clock_m->GetMultiply(0)); - Pack(data, PackLocation { 15, 5 }, clock_m->GetMultiply(1)); - Pack(data, PackLocation { 20, 1 }, clock_m->IsForwarded()); return data; } @@ -80,9 +78,7 @@ public: clock_m->Stop(); } clock_m->SetTempoBPM(Unpack(data, PackLocation { 1, 9 })); - clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 }), 0); - clock_m->SetMultiply(Unpack(data, PackLocation { 15, 5 }), 1); - clock_m->SetForwarding(Unpack(data, PackLocation { 20, 1 })); + clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 })); } protected: diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 0c55fd707..5dea693d4 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -64,12 +64,12 @@ typedef int32_t simfloat; // Hemisphere-specific macros #define BottomAlign(h) (62 - h) -#define ForEachChannel(ch) for(int ch = 0; ch < 2; ch++) +#define ForEachChannel(ch) for(int_fast8_t ch = 0; ch < 2; ch++) // Specifies where data goes in flash storage for each selcted applet, and how big it is typedef struct PackLocation { - uint8_t location; - uint8_t size; + size_t location; + size_t size; } PackLocation; class HemisphereApplet { @@ -433,7 +433,7 @@ class HemisphereApplet { /* Retrieve value from a 64-bit storage unit at the specified location and of the specified bit size */ int Unpack(uint64_t data, PackLocation p) { uint64_t mask = 1; - for (int i = 1; i < p.size; i++) mask |= (0x01 << i); + for (size_t i = 1; i < p.size; i++) mask |= (0x01 << i); return (data >> p.location) & mask; } @@ -473,7 +473,7 @@ class HemisphereApplet { bool master_clock_bus; // Clock forwarding was on during the last ISR cycle bool applet_started; // Allow the app to maintain state during switching int last_view_tick; // Tick number of the most recent view - int help_active; + bool help_active; bool changed_cv[2]; // Has the input changed by more than 1/8 semitone since the last read? int last_cv[2]; // For change detection }; From a9c69730a38f03f37dfbe7b30ae65e2421975764 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 29 Nov 2022 18:14:10 -0500 Subject: [PATCH 062/417] Eliminate unnecessary assignment inside constrain() Apparently constrain() is implemented as a macro, which means all these instances of "x += y" would be evaluated up to 3 times... Hopefully I understand it correctly. This commit reduces compiled binary size by about 500 bytes. --- software/o_c_REV/HEM_ADEG.ino | 4 ++-- software/o_c_REV/HEM_ADSREG.ino | 2 +- software/o_c_REV/HEM_Brancher.ino | 2 +- software/o_c_REV/HEM_Burst.ino | 8 ++++---- software/o_c_REV/HEM_Calculate.ino | 2 +- software/o_c_REV/HEM_Carpeggio.ino | 6 +++--- software/o_c_REV/HEM_ClockSkip.ino | 2 +- software/o_c_REV/HEM_Compare.ino | 2 +- software/o_c_REV/HEM_DrumMap.ino | 10 +++++----- software/o_c_REV/HEM_GateDelay.ino | 2 +- software/o_c_REV/HEM_GatedVCA.ino | 2 +- software/o_c_REV/HEM_LoFiPCM.ino | 8 ++++---- software/o_c_REV/HEM_LowerRenz.ino | 4 ++-- software/o_c_REV/HEM_Palimpsest.ino | 6 +++--- software/o_c_REV/HEM_ProbabilityDivider.ino | 10 +++++----- software/o_c_REV/HEM_ProbabilityMelody.ino | 6 +++--- software/o_c_REV/HEM_ScaleDuet.ino | 2 +- software/o_c_REV/HEM_Sequence5.ino | 2 +- software/o_c_REV/HEM_Shredder.ino | 2 +- software/o_c_REV/HEM_SkewedLFO.ino | 4 ++-- software/o_c_REV/HEM_Slew.ino | 4 ++-- software/o_c_REV/HEM_Stairs.ino | 4 ++-- software/o_c_REV/HEM_TrigSeq.ino | 2 +- software/o_c_REV/HEM_TrigSeq16.ino | 2 +- software/o_c_REV/HEM_hMIDIIn.ino | 4 ++-- software/o_c_REV/HEM_hMIDIOut.ino | 6 +++--- 26 files changed, 54 insertions(+), 54 deletions(-) diff --git a/software/o_c_REV/HEM_ADEG.ino b/software/o_c_REV/HEM_ADEG.ino index fe9564ee4..72b7efd2c 100644 --- a/software/o_c_REV/HEM_ADEG.ino +++ b/software/o_c_REV/HEM_ADEG.ino @@ -98,11 +98,11 @@ public: void OnEncoderMove(int direction) { if (cursor == 0) { - attack = constrain(attack += direction, 0, HEM_ADEG_MAX_VALUE); + attack = constrain(attack + direction, 0, HEM_ADEG_MAX_VALUE); last_ms_value = Proportion(attack, HEM_ADEG_MAX_VALUE, HEM_ADEG_MAX_TICKS) / 17; } else { - decay = constrain(decay += direction, 0, HEM_ADEG_MAX_VALUE); + decay = constrain(decay + direction, 0, HEM_ADEG_MAX_VALUE); last_ms_value = Proportion(decay, HEM_ADEG_MAX_VALUE, HEM_ADEG_MAX_TICKS) / 17; } last_change_ticks = OC::CORE::ticks; diff --git a/software/o_c_REV/HEM_ADSREG.ino b/software/o_c_REV/HEM_ADSREG.ino index d99c39864..05735c431 100644 --- a/software/o_c_REV/HEM_ADSREG.ino +++ b/software/o_c_REV/HEM_ADSREG.ino @@ -104,7 +104,7 @@ public: void OnEncoderMove(int direction) { int adsr[4] = {attack, decay, sustain, release}; - adsr[edit_stage] = constrain(adsr[edit_stage] += direction, 1, HEM_EG_MAX_VALUE); + adsr[edit_stage] = constrain(adsr[edit_stage] + direction, 1, HEM_EG_MAX_VALUE); attack = adsr[HEM_EG_ATTACK]; decay = adsr[HEM_EG_DECAY]; sustain = adsr[HEM_EG_SUSTAIN]; diff --git a/software/o_c_REV/HEM_Brancher.ino b/software/o_c_REV/HEM_Brancher.ino index a6dedfbad..4b8f847df 100644 --- a/software/o_c_REV/HEM_Brancher.ino +++ b/software/o_c_REV/HEM_Brancher.ino @@ -58,7 +58,7 @@ public: /* Change the pability */ void OnEncoderMove(int direction) { - p = constrain(p += direction, 0, 100); + p = constrain(p + direction, 0, 100); } uint64_t OnDataRequest() { diff --git a/software/o_c_REV/HEM_Burst.ino b/software/o_c_REV/HEM_Burst.ino index 3746d0148..c1e99d583 100644 --- a/software/o_c_REV/HEM_Burst.ino +++ b/software/o_c_REV/HEM_Burst.ino @@ -128,17 +128,17 @@ public: } void OnEncoderMove(int direction) { - if (cursor == 0) number = constrain(number += direction, 1, HEM_BURST_NUMBER_MAX); + if (cursor == 0) number = constrain(number + direction, 1, HEM_BURST_NUMBER_MAX); if (cursor == 1) { - spacing = constrain(spacing += direction, HEM_BURST_SPACING_MIN, HEM_BURST_SPACING_MAX); + spacing = constrain(spacing + direction, HEM_BURST_SPACING_MIN, HEM_BURST_SPACING_MAX); clocked = 0; } if (cursor == 2) { - accel = constrain(accel += direction, -HEM_BURST_ACCEL_MAX, HEM_BURST_ACCEL_MAX); + accel = constrain(accel + direction, -HEM_BURST_ACCEL_MAX, HEM_BURST_ACCEL_MAX); } if (cursor == 3) { - jitter = constrain(jitter += direction, 0, HEM_BURST_JITTER_MAX); + jitter = constrain(jitter + direction, 0, HEM_BURST_JITTER_MAX); } if (cursor == 4) { diff --git a/software/o_c_REV/HEM_Calculate.ino b/software/o_c_REV/HEM_Calculate.ino index 46bb1824a..e508e296c 100644 --- a/software/o_c_REV/HEM_Calculate.ino +++ b/software/o_c_REV/HEM_Calculate.ino @@ -88,7 +88,7 @@ public: } void OnEncoderMove(int direction) { - operation[selected] = constrain(operation[selected] += direction, 0, HEMISPHERE_NUMBER_OF_CALC - 1); + operation[selected] = constrain(operation[selected] + direction, 0, HEMISPHERE_NUMBER_OF_CALC - 1); rand_clocked[selected] = 0; } diff --git a/software/o_c_REV/HEM_Carpeggio.ino b/software/o_c_REV/HEM_Carpeggio.ino index f5cba191a..0894f3a85 100644 --- a/software/o_c_REV/HEM_Carpeggio.ino +++ b/software/o_c_REV/HEM_Carpeggio.ino @@ -100,9 +100,9 @@ public: } void OnEncoderMove(int direction) { - if (cursor == 0) sequence[step] = constrain(sequence[step] += direction, -24, 60); - if (cursor == 1) chord = constrain(chord += direction, 0, Nr_of_arp_chords - 1); - if (cursor == 2) transpose = constrain(transpose += direction, -24, 24); + if (cursor == 0) sequence[step] = constrain(sequence[step] + direction, -24, 60); + if (cursor == 1) chord = constrain(chord + direction, 0, Nr_of_arp_chords - 1); + if (cursor == 2) transpose = constrain(transpose + direction, -24, 24); if (cursor == 3) { if (shuffle && direction < 0) { ImprintChord(sel_chord); diff --git a/software/o_c_REV/HEM_ClockSkip.ino b/software/o_c_REV/HEM_ClockSkip.ino index fe84e92cb..0b8f46522 100644 --- a/software/o_c_REV/HEM_ClockSkip.ino +++ b/software/o_c_REV/HEM_ClockSkip.ino @@ -59,7 +59,7 @@ public: } void OnEncoderMove(int direction) { - p[cursor] = constrain(p[cursor] += direction, 0, 100); + p[cursor] = constrain(p[cursor] + direction, 0, 100); } uint64_t OnDataRequest() { diff --git a/software/o_c_REV/HEM_Compare.ino b/software/o_c_REV/HEM_Compare.ino index 6b0cabbdb..ae79a1fd0 100644 --- a/software/o_c_REV/HEM_Compare.ino +++ b/software/o_c_REV/HEM_Compare.ino @@ -57,7 +57,7 @@ public: } void OnEncoderMove(int direction) { - level = constrain(level += direction, 0, HEM_COMPARE_MAX_VALUE); + level = constrain(level + direction, 0, HEM_COMPARE_MAX_VALUE); } uint64_t OnDataRequest() { diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index e95619fca..48d8738df 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -146,13 +146,13 @@ public: if (mode[1] < 0) mode[1] = 3; } // fill - if (cursor == 2) fill[0] = constrain(fill[0] += (direction * accel), 0, 255); - if (cursor == 3) fill[1] = constrain(fill[1] += (direction * accel), 0, 255); + if (cursor == 2) fill[0] = constrain(fill[0] + (direction * accel), 0, 255); + if (cursor == 3) fill[1] = constrain(fill[1] + (direction * accel), 0, 255); // x/y - if (cursor == 4) x = constrain(x += (direction * accel), 0, 255); - if (cursor == 5) y = constrain(y += (direction * accel), 0, 255); + if (cursor == 4) x = constrain(x + (direction * accel), 0, 255); + if (cursor == 5) y = constrain(y + (direction * accel), 0, 255); // chaos - if (cursor == 6) chaos = constrain(chaos += (direction * accel), 0, 255); + if (cursor == 6) chaos = constrain(chaos + (direction * accel), 0, 255); // cv assign if (cursor == 7) { cv_mode += direction; diff --git a/software/o_c_REV/HEM_GateDelay.ino b/software/o_c_REV/HEM_GateDelay.ino index c7c3401f1..50791aeb1 100644 --- a/software/o_c_REV/HEM_GateDelay.ino +++ b/software/o_c_REV/HEM_GateDelay.ino @@ -68,7 +68,7 @@ public: if (time[cursor] > 100) direction *= 2; if (time[cursor] > 500) direction *= 2; if (time[cursor] > 1000) direction *= 2; - time[cursor] = constrain(time[cursor] += direction, 0, 2000); + time[cursor] = constrain(time[cursor] + direction, 0, 2000); } uint64_t OnDataRequest() { diff --git a/software/o_c_REV/HEM_GatedVCA.ino b/software/o_c_REV/HEM_GatedVCA.ino index d3476d51f..98319cc2d 100644 --- a/software/o_c_REV/HEM_GatedVCA.ino +++ b/software/o_c_REV/HEM_GatedVCA.ino @@ -53,7 +53,7 @@ public: } void OnEncoderMove(int direction) { - amp_offset_pct = constrain(amp_offset_pct += direction, 0, 100); + amp_offset_pct = constrain(amp_offset_pct + direction, 0, 100); amp_offset_cv = Proportion(amp_offset_pct, 100, HEMISPHERE_MAX_CV); } diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 4e5a9edd0..b1243e7a2 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -88,16 +88,16 @@ public: void OnEncoderMove(int direction) { switch (cursor) { case 0: - dt_pct = constrain(dt_pct += direction, 0, 99); + dt_pct = constrain(dt_pct + direction, 0, 99); break; case 1: - feedback = constrain(feedback += direction, 0, 125); + feedback = constrain(feedback + direction, 0, 125); break; case 2: - rate = constrain(rate += direction, 1, 32); + rate = constrain(rate + direction, 1, 32); break; case 3: - depth = constrain(depth += direction, 0, 13); + depth = constrain(depth + direction, 0, 13); break; } diff --git a/software/o_c_REV/HEM_LowerRenz.ino b/software/o_c_REV/HEM_LowerRenz.ino index 18556fc4c..5fdfaea73 100644 --- a/software/o_c_REV/HEM_LowerRenz.ino +++ b/software/o_c_REV/HEM_LowerRenz.ino @@ -72,8 +72,8 @@ public: } void OnEncoderMove(int direction) { - if (cursor == 0) freq = constrain(freq += direction, 0, 255); - if (cursor == 1) rho = constrain(rho += direction, 4, 127); + if (cursor == 0) freq = constrain(freq + direction, 0, 255); + if (cursor == 1) rho = constrain(rho + direction, 4, 127); } uint64_t OnDataRequest() { diff --git a/software/o_c_REV/HEM_Palimpsest.ino b/software/o_c_REV/HEM_Palimpsest.ino index 2ef3616b8..a3c758210 100644 --- a/software/o_c_REV/HEM_Palimpsest.ino +++ b/software/o_c_REV/HEM_Palimpsest.ino @@ -92,9 +92,9 @@ public: } void OnEncoderMove(int direction) { - if (cursor == 0) compose = constrain(compose += direction, 0, HEM_PALIMPSEST_MAX_VALUE); - if (cursor == 1) decompose = constrain(decompose -= direction, 0, HEM_PALIMPSEST_MAX_VALUE); - if (cursor == 2) length = constrain(length += direction, 2, 16); + if (cursor == 0) compose = constrain(compose + direction, 0, HEM_PALIMPSEST_MAX_VALUE); + if (cursor == 1) decompose = constrain(decompose - direction, 0, HEM_PALIMPSEST_MAX_VALUE); + if (cursor == 2) length = constrain(length + direction, 2, 16); ResetCursor(); } diff --git a/software/o_c_REV/HEM_ProbabilityDivider.ino b/software/o_c_REV/HEM_ProbabilityDivider.ino index 9e4f66f00..cb1d22655 100644 --- a/software/o_c_REV/HEM_ProbabilityDivider.ino +++ b/software/o_c_REV/HEM_ProbabilityDivider.ino @@ -135,13 +135,13 @@ public: } void OnEncoderMove(int direction) { - if (cursor == 0) weight_1 = constrain(weight_1 += direction, 0, HEM_PROB_DIV_MAX_WEIGHT); - if (cursor == 1) weight_2 = constrain(weight_2 += direction, 0, HEM_PROB_DIV_MAX_WEIGHT); - if (cursor == 2) weight_4 = constrain(weight_4 += direction, 0, HEM_PROB_DIV_MAX_WEIGHT); - if (cursor == 3) weight_8 = constrain(weight_8 += direction, 0, HEM_PROB_DIV_MAX_WEIGHT); + if (cursor == 0) weight_1 = constrain(weight_1 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); + if (cursor == 1) weight_2 = constrain(weight_2 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); + if (cursor == 2) weight_4 = constrain(weight_4 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); + if (cursor == 3) weight_8 = constrain(weight_8 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); if (cursor == 4) { int old = loop_length; - loop_length = constrain(loop_length += direction, 0, HEM_PROB_DIV_MAX_LOOP_LENGTH); + loop_length = constrain(loop_length + direction, 0, HEM_PROB_DIV_MAX_LOOP_LENGTH); if (old == 0 && loop_length > 0) { // seed loop GenerateLoop(true); diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index 49a7e7d00..88fd199b3 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -117,12 +117,12 @@ public: } else { if (cursor < 12) { // editing note probability - weights[cursor] = constrain(weights[cursor] += direction, 0, HEM_PROB_MEL_MAX_WEIGHT); + weights[cursor] = constrain(weights[cursor] + direction, 0, HEM_PROB_MEL_MAX_WEIGHT); value_animation = HEMISPHERE_CURSOR_TICKS; } else { // editing scaling - if (cursor == 12) down = constrain(down += direction, 1, up); - if (cursor == 13) up = constrain(up += direction, down, 60); + if (cursor == 12) down = constrain(down + direction, 1, up); + if (cursor == 13) up = constrain(up + direction, down, 60); } if (isLooping) { GenerateLoop(); // regenerate loop on any param changes diff --git a/software/o_c_REV/HEM_ScaleDuet.ino b/software/o_c_REV/HEM_ScaleDuet.ino index 347f9e2f8..3cab0da9a 100644 --- a/software/o_c_REV/HEM_ScaleDuet.ino +++ b/software/o_c_REV/HEM_ScaleDuet.ino @@ -76,7 +76,7 @@ public: void OnEncoderMove(int direction) { if (cursor == 0 && direction == -1) cursor = 1; - cursor = constrain(cursor += direction, 0, 23); + cursor = constrain(cursor + direction, 0, 23); ResetCursor(); } diff --git a/software/o_c_REV/HEM_Sequence5.ino b/software/o_c_REV/HEM_Sequence5.ino index 2cd34b675..643f0706b 100644 --- a/software/o_c_REV/HEM_Sequence5.ino +++ b/software/o_c_REV/HEM_Sequence5.ino @@ -69,7 +69,7 @@ public: // If turning past zero, set the mute bit for this step muted |= (0x01 << cursor); } else { - note[cursor] = constrain(note[cursor] += direction, 0, 30); + note[cursor] = constrain(note[cursor] + direction, 0, 30); muted &= ~(0x01 << cursor); } } diff --git a/software/o_c_REV/HEM_Shredder.ino b/software/o_c_REV/HEM_Shredder.ino index b57e50d89..fa4f80603 100644 --- a/software/o_c_REV/HEM_Shredder.ino +++ b/software/o_c_REV/HEM_Shredder.ino @@ -140,7 +140,7 @@ public: } } } - if (cursor == 2) quant_channels = constrain(quant_channels += direction, 0, 2); + if (cursor == 2) quant_channels = constrain(quant_channels + direction, 0, 2); if (cursor == 3) { scale += direction; if (scale >= OC::Scales::NUM_SCALES) scale = 0; diff --git a/software/o_c_REV/HEM_SkewedLFO.ino b/software/o_c_REV/HEM_SkewedLFO.ino index 3f78d5a65..aa0dc15e6 100644 --- a/software/o_c_REV/HEM_SkewedLFO.ino +++ b/software/o_c_REV/HEM_SkewedLFO.ino @@ -68,9 +68,9 @@ public: void OnEncoderMove(int direction) { if (cursor == 0) { - rate = constrain(rate += direction, 0, HEM_LFO_MAX_VALUE); + rate = constrain(rate + direction, 0, HEM_LFO_MAX_VALUE); } else { - skew = constrain(skew += direction, 0, HEM_LFO_MAX_VALUE); + skew = constrain(skew + direction, 0, HEM_LFO_MAX_VALUE); } } diff --git a/software/o_c_REV/HEM_Slew.ino b/software/o_c_REV/HEM_Slew.ino index 48405dfa1..cd07c6138 100644 --- a/software/o_c_REV/HEM_Slew.ino +++ b/software/o_c_REV/HEM_Slew.ino @@ -75,11 +75,11 @@ public: void OnEncoderMove(int direction) { if (cursor == 0) { - rise = constrain(rise += direction, 0, HEM_SLEW_MAX_VALUE); + rise = constrain(rise + direction, 0, HEM_SLEW_MAX_VALUE); last_ms_value = Proportion(rise, HEM_SLEW_MAX_VALUE, HEM_SLEW_MAX_TICKS) / 17; } else { - fall = constrain(fall += direction, 0, HEM_SLEW_MAX_VALUE); + fall = constrain(fall + direction, 0, HEM_SLEW_MAX_VALUE); last_ms_value = Proportion(fall, HEM_SLEW_MAX_VALUE, HEM_SLEW_MAX_TICKS) / 17; } last_change_ticks = OC::CORE::ticks; diff --git a/software/o_c_REV/HEM_Stairs.ino b/software/o_c_REV/HEM_Stairs.ino index 390b62239..a3dd33457 100644 --- a/software/o_c_REV/HEM_Stairs.ino +++ b/software/o_c_REV/HEM_Stairs.ino @@ -208,11 +208,11 @@ public: void OnEncoderMove(int direction) { if (cursor == 0) { - steps = constrain( steps += direction, 0, HEM_STAIRS_MAX_STEPS-1); // constrain includes max + steps = constrain( steps + direction, 0, HEM_STAIRS_MAX_STEPS-1); // constrain includes max } else if (cursor == 1) { - dir = constrain( dir += direction, 0, 2); + dir = constrain( dir + direction, 0, 2); // Don't change current direction if up/down mode if(dir != 1) diff --git a/software/o_c_REV/HEM_TrigSeq.ino b/software/o_c_REV/HEM_TrigSeq.ino index 56a9655d8..a7ea2d4f8 100644 --- a/software/o_c_REV/HEM_TrigSeq.ino +++ b/software/o_c_REV/HEM_TrigSeq.ino @@ -70,7 +70,7 @@ public: // Update end_step if (this_cursor == 2) { - end_step[ch] = constrain(end_step[ch] += direction, 0, 7); + end_step[ch] = constrain(end_step[ch] + direction, 0, 7); } else { // Get the current pattern int curr_patt = pattern[ch]; diff --git a/software/o_c_REV/HEM_TrigSeq16.ino b/software/o_c_REV/HEM_TrigSeq16.ino index 4173552e8..fa057a695 100644 --- a/software/o_c_REV/HEM_TrigSeq16.ino +++ b/software/o_c_REV/HEM_TrigSeq16.ino @@ -67,7 +67,7 @@ public: void OnEncoderMove(int direction) { // Update end_step if (cursor == 4) { - end_step = constrain(end_step += direction, 0, 15); + end_step = constrain(end_step + direction, 0, 15); } else { int ch = cursor > 1 ? 1 : 0; int this_cursor = cursor - (ch * 2); diff --git a/software/o_c_REV/HEM_hMIDIIn.ino b/software/o_c_REV/HEM_hMIDIIn.ino index 75e295a9d..bcd7c3db3 100644 --- a/software/o_c_REV/HEM_hMIDIIn.ino +++ b/software/o_c_REV/HEM_hMIDIIn.ino @@ -185,10 +185,10 @@ public: void OnEncoderMove(int direction) { if (cursor == 3) return; - if (cursor == 0) channel = constrain(channel += direction, 0, 15); + if (cursor == 0) channel = constrain(channel + direction, 0, 15); else { int ch = cursor - 1; - function[ch] = constrain(function[ch] += direction, 0, 7); + function[ch] = constrain(function[ch] + direction, 0, 7); clock_count = 0; } ResetCursor(); diff --git a/software/o_c_REV/HEM_hMIDIOut.ino b/software/o_c_REV/HEM_hMIDIOut.ino index 01be880b3..6ba53c109 100644 --- a/software/o_c_REV/HEM_hMIDIOut.ino +++ b/software/o_c_REV/HEM_hMIDIOut.ino @@ -142,9 +142,9 @@ public: } void OnEncoderMove(int direction) { - if (cursor == 0) channel = constrain(channel += direction, 0, 15); - if (cursor == 1) transpose = constrain(transpose += direction, -24, 24); - if (cursor == 2) function = constrain(function += direction, 0, 3); + if (cursor == 0) channel = constrain(channel + direction, 0, 15); + if (cursor == 1) transpose = constrain(transpose + direction, -24, 24); + if (cursor == 2) function = constrain(function + direction, 0, 3); if (cursor == 3) legato = direction > 0 ? 1 : 0; ResetCursor(); } From 1e3cd15438e1fc73afeb16ddfeb0198264c29ce8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 29 Nov 2022 19:05:24 -0500 Subject: [PATCH 063/417] ClockSetup: save clock Forwarding state There is 1 bit to spare, so this should work --- software/o_c_REV/HEM_ClockSetup.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 578b7df1c..7e9e37be4 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -67,7 +67,8 @@ public: uint64_t data = 0; Pack(data, PackLocation { 0, 1 }, clock_m->IsRunning() || clock_m->IsPaused()); Pack(data, PackLocation { 1, 9 }, clock_m->GetTempo()); - Pack(data, PackLocation { 10, 5 }, clock_m->GetMultiply(0)); + Pack(data, PackLocation { 10, 5 }, clock_m->GetMultiply()); + Pack(data, PackLocation { 15, 1 }, clock_m->IsForwarded()); return data; } @@ -79,6 +80,7 @@ public: } clock_m->SetTempoBPM(Unpack(data, PackLocation { 1, 9 })); clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 })); + clock_m->SetForwarding(Unpack(data, PackLocation { 15, 1 })); } protected: From 834d170337a2cb551ec43527f0b77f478558897a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 7 Dec 2022 22:08:31 -0500 Subject: [PATCH 064/417] Remove standalone tideslite.cpp Unused and causes linking errors with PlatformIO --- software/o_c_REV/resources/tideslite.cpp | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 software/o_c_REV/resources/tideslite.cpp diff --git a/software/o_c_REV/resources/tideslite.cpp b/software/o_c_REV/resources/tideslite.cpp deleted file mode 100644 index 953c7f2c1..000000000 --- a/software/o_c_REV/resources/tideslite.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "tideslite.h" -#include -#include - -int main(int argc, char **argv) { - TidesLiteSample sample; - uint64_t samples = atol(argv[1]); - int16_t pitch = atoi(argv[2]); - uint32_t phase_inc = ComputePhaseIncrement(pitch); - uint16_t slope = atoi(argv[3]); - uint16_t shape = atoi(argv[4]); - int16_t fold = atoi(argv[5]); - - uint32_t phase = 0; - printf("unipolar,bipolar,high,low\n"); - for (uint64_t i = 0; i < samples; i++) { - ProcessSample(slope, shape, shape, fold, phase, sample); - printf("%d,%d,%d,%d\n", sample.unipolar, sample.bipolar, - (sample.flags & FLAG_EOA) ? 65535 : 0, - (sample.flags & FLAG_EOR) ? 65535 : 0); - phase += phase_inc; - } - return 0; -} \ No newline at end of file From 7c617d582f0e83ec6a6548227d38dd25bf2a2a76 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 7 Dec 2022 22:50:23 -0500 Subject: [PATCH 065/417] PlatformIO build config --- .gitignore | 3 +- platformio.ini | 28 +++++++++++++++++++ .../o_c_REV/extern/stmlib_utils_random.cpp | 2 +- software/o_c_REV/extern/stmlib_utils_random.h | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 platformio.ini diff --git a/.gitignore b/.gitignore index 14e457c24..8ff86f5db 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ software/o_c_REV/.DS_Store .DS_Store shelved/ Hemisphere\ Suite.cpp -builds \ No newline at end of file +builds +.pio diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 000000000..6f5ad90dc --- /dev/null +++ b/platformio.ini @@ -0,0 +1,28 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter, extra scripting +; Upload options: custom port, speed and extra flags +; Library options: dependencies, extra library storages +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[platformio] +src_dir = ./software/o_c_REV +default_envs = oc_prod + +[env] +platform = teensy@1.6.0 +framework = arduino +board = teensy31 +board_build.f_cpu = 120000000 +lib_deps = EEPROM +build_flags = + -DTEENSY_OPT_FASTER + -DUSB_MIDI -UUSB_SERIAL + +[env:oc_prod] +build_flags = ${env.build_flags} + +[env:oc_dev] +build_flags = ${env.build_flags} -DOC_DEV +; -DPRINT_DEBUG diff --git a/software/o_c_REV/extern/stmlib_utils_random.cpp b/software/o_c_REV/extern/stmlib_utils_random.cpp index c4a7e2625..376eeeecf 100644 --- a/software/o_c_REV/extern/stmlib_utils_random.cpp +++ b/software/o_c_REV/extern/stmlib_utils_random.cpp @@ -27,7 +27,7 @@ // Random number generator. // #include "stmlib/utils/random.h" -#include "utils/stmlib_utils_random.h" +#include "stmlib_utils_random.h" namespace stmlib { diff --git a/software/o_c_REV/extern/stmlib_utils_random.h b/software/o_c_REV/extern/stmlib_utils_random.h index 5ba74f43b..3a8b4749e 100644 --- a/software/o_c_REV/extern/stmlib_utils_random.h +++ b/software/o_c_REV/extern/stmlib_utils_random.h @@ -31,6 +31,7 @@ // #include "stmlib/stmlib.h" #include +#include "util/util_macros.h" namespace stmlib { From e82a43c218f8b9f256dbe841bd2ea810ed05a245 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 8 Dec 2022 04:38:33 -0500 Subject: [PATCH 066/417] Re-enable a bunch of stuff, version bump to 1.4.4-pio Platform IO build for testing. Fills only 77% of flash. --- software/o_c_REV/OC_options.h | 8 ++++---- software/o_c_REV/OC_version.h | 2 +- software/o_c_REV/hemisphere_config.h | 15 +++++++-------- platformio.ini => software/o_c_REV/platformio.ini | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) rename platformio.ini => software/o_c_REV/platformio.ini (95%) diff --git a/software/o_c_REV/OC_options.h b/software/o_c_REV/OC_options.h index 98c8cdfcc..d4596302c 100644 --- a/software/o_c_REV/OC_options.h +++ b/software/o_c_REV/OC_options.h @@ -26,11 +26,11 @@ /* Flags for the full-width apps, these enable/disable them in OC_apps.ino but also zero out the app */ /* files to prevent them from taking up space. Only Enigma is enabled by default. */ -//#define ENABLE_APP_ENIGMA -//#define ENABLE_APP_MIDI +#define ENABLE_APP_ENIGMA +#define ENABLE_APP_MIDI //#define ENABLE_APP_NEURAL_NETWORK -//#define ENABLE_APP_PONG -//#define ENABLE_APP_DARKEST_TIMELINE +#define ENABLE_APP_PONG +#define ENABLE_APP_DARKEST_TIMELINE #endif diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index db3c1813e..4a4edbe96 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.3" +#define OC_VERSION "v1.4.4-pio" #define OC_VERSION_URL "github.com/djphazer" #endif diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index ce0c2f010..bac4297d9 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 55 +#define HEMISPHERE_AVAILABLE_APPLETS 59 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ @@ -35,18 +35,22 @@ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ - DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ + DECLARE_APPLET( 45, 0x02, EnigmaJr), \ + DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 29, 0x04, GateDelay), \ + DECLARE_APPLET( 17, 0x50, GatedVCA), \ DECLARE_APPLET( 16, 0x80, LoFiPCM), \ DECLARE_APPLET( 10, 0x44, Logic), \ DECLARE_APPLET( 21, 0x01, LowerRenz), \ + DECLARE_APPLET(150, 0x20, hMIDIIn), \ + DECLARE_APPLET( 27, 0x20, hMIDIOut), \ DECLARE_APPLET( 50, 0x04, Metronome), \ DECLARE_APPLET( 33, 0x10, MixerBal), \ DECLARE_APPLET( 20, 0x02, Palimpsest), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ - DECLARE_APPLET( 45, 0x01, RndWalk), \ + DECLARE_APPLET( 69, 0x01, RndWalk), \ DECLARE_APPLET( 44, 0x01, RunglBook), \ DECLARE_APPLET( 26, 0x08, ScaleDuet), \ DECLARE_APPLET( 40, 0x40, Schmitt), \ @@ -73,10 +77,5 @@ } /* DECLARE_APPLET(127, 0x80, DIAGNOSTIC), \ - DECLARE_APPLET(150, 0x20, hMIDIIn), \ - DECLARE_APPLET( 27, 0x20, hMIDIOut), \ - DECLARE_APPLET( 45, 0x02, EnigmaJr), \ - DECLARE_APPLET( 17, 0x50, GatedVCA), \ - DECLARE_APPLET( 55, 0x80, DrCrusher), \ DECLARE_APPLET( 7, 0x01, SkewedLFO), \ */ diff --git a/platformio.ini b/software/o_c_REV/platformio.ini similarity index 95% rename from platformio.ini rename to software/o_c_REV/platformio.ini index 6f5ad90dc..1ce517d28 100644 --- a/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -7,7 +7,7 @@ ; http://docs.platformio.org/page/projectconf.html [platformio] -src_dir = ./software/o_c_REV +src_dir = . default_envs = oc_prod [env] From 1c9ab2ac74f0dcc8664e743b224bd9afe71f719a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 15 Dec 2022 23:32:08 -0500 Subject: [PATCH 067/417] CVRecorder: adapt for modal editing Toggling and (de)activating record mode should be quicker, as the cursor stays in place. --- software/o_c_REV/HEM_CVRecV2.ino | 61 +++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index 20f16a9b5..9a08583d8 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -84,28 +84,38 @@ public: } void OnButtonPress() { - if (cursor == 3) { - // Check recording status - if (mode > 0) punch_out = end - start; - else punch_out = 0; + if (cursor == 2) { // special case to toggle smoothing + smooth = 1 - smooth; + ResetCursor(); + return; + } + + isEditing = 1 - isEditing; // toggle editing + if (cursor == 3 && !isEditing) { // activate recording if selected + punch_out = (mode > 0) ? end - start : 0; } - if (++cursor > 3) cursor = 0; - ResetCursor(); } void OnEncoderMove(int direction) { - if (cursor == 0) { - int16_t fs = start; // Former start value - start = constrain(start + direction, 0, end - 1); - if (fs != start && punch_out) punch_out -= direction; - } - if (cursor == 1) { - int16_t fe = end; // Former end value - end = constrain(end + direction, start + 1, CVREC_MAX_STEP - 1); - if (fe != end && punch_out) punch_out += direction; + if (isEditing) { + if (cursor == 0) { + int16_t fs = start; // Former start value + start = constrain(start + direction, 0, end - 1); + if (fs != start && punch_out) punch_out -= direction; + } + if (cursor == 1) { + int16_t fe = end; // Former end value + end = constrain(end + direction, start + 1, CVREC_MAX_STEP - 1); + if (fe != end && punch_out) punch_out += direction; + } + //if (cursor == 2) smooth = direction > 0 ? 1 : 0; + if (cursor == 3) mode = constrain(mode + direction, 0, 3); + + return; } - if (cursor == 2) smooth = direction > 0 ? 1 : 0; - if (cursor == 3) mode = constrain(mode + direction, 0, 3); + + //not editing, move cursor + cursor = constrain(cursor + direction, 0, 3); ResetCursor(); } @@ -141,6 +151,7 @@ private: simfloat rise[2]; simfloat signal[2]; bool smooth; + bool isEditing = 0; // Transport int mode = 0; // 0=Playback, 1=Rec Track 1, 2=Rec Track 2, 3= Rec Tracks 1 & 2 @@ -164,9 +175,11 @@ private: gfxPrint(1, 35, CVRecV2_MODES[mode]); // Cursor - if (cursor == 0) gfxCursor(19, 23, 18); - if (cursor == 1) gfxCursor(43, 23, 18); - if (cursor == 3) gfxCursor(1, 43, 63); + switch(cursor){ + case 0: gfxCursor(19, 23, 18); break; + case 1: gfxCursor(43, 23, 18); break; + case 3: gfxCursor(1, 43, 63); break; + } // Status icon if (mode > 0 && punch_out > 0) { @@ -177,6 +190,14 @@ private: // Record time indicator if (punch_out > 0) gfxInvert(0, 34, punch_out / 6, 9); + if (isEditing) { + switch(cursor){ + case 0: gfxInvert(19, 14, 18, 9); break; + case 1: gfxInvert(43, 14, 18, 9); break; + case 3: gfxInvert(1, 34, 63, 9); break; + } + } + // Step indicator segment.PrintWhole(hemisphere * 64, 50, step + 1, 100); From 8b1baedf95b3f7d2bb6891a78ffe6448c4253fdf Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 16 Dec 2022 02:08:17 -0500 Subject: [PATCH 068/417] Button: expand to 2-channel operation Each output can be set to either Trig or Toggle. Turn CW to select channel, CCW to switch mode. Push encoder to fire on the selected channel. Digital inputs 1/2 correspond to each channel. --- software/o_c_REV/HEM_Button.ino | 74 ++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/software/o_c_REV/HEM_Button.ino b/software/o_c_REV/HEM_Button.ino index e36a0ca15..fc93bc322 100644 --- a/software/o_c_REV/HEM_Button.ino +++ b/software/o_c_REV/HEM_Button.ino @@ -22,25 +22,30 @@ class Button : public HemisphereApplet { public: const char* applet_name() { // Maximum 10 characters - return "Button"; + return "Button2"; } /* Run when the Applet is selected */ void Start() { - toggle_st = 0; // Set toggle state to off - trigger_out = 0; // Set trigger out queue to off trigger_countdown = 0; } /* Run during the interrupt service routine, 16667 times per second */ void Controller() { - if (Clock(0, 1)) OnButtonPress(); // Clock at Dig 0 emulates press (ignore forwarding) - if (trigger_out) { - ClockOut(0); - trigger_out = 0; // Clear trigger queue - trigger_countdown = 1667; // Trigger display countdown + ForEachChannel(ch) { + // Check physical trigger input to emulate button press (ignore forwarding) + if (Clock(ch, 1)) PressButton(ch); + + // Handle output if triggered + if (trigger_out[ch]) { + if (gate_mode[ch]) + GateOut(ch, toggle_st[ch]); + else + ClockOut(ch); + + trigger_out[ch] = 0; // Clear trigger queue + } } - GateOut(1, toggle_st); // Send toggle state if (trigger_countdown) trigger_countdown--; } @@ -52,8 +57,8 @@ public: /* Called when the encoder button for this hemisphere is pressed */ void OnButtonPress() { - trigger_out = 1; // Set trigger queue - toggle_st = 1 - toggle_st; // Alternate toggle state when pressed + PressButton(channel); + trigger_countdown = 1667; // Trigger display countdown } /* Called when the encoder for this hemisphere is rotated @@ -61,7 +66,15 @@ public: * direction -1 is counterclockwise */ void OnEncoderMove(int direction) { - /* Nothing for this applet */ + if (direction > 0) // CW toggles which channel + channel = 1 - channel; + else { // CCW toggles between Trig/Gate output + // retrigger if switching from a Gate On to Trig mode + trigger_out[channel] = gate_mode[channel] && toggle_st[channel]; + + gate_mode[channel] = 1 - gate_mode[channel]; + toggle_st[channel] = 0; // reset Gate to off + } } /* No state data for this applet @@ -81,23 +94,44 @@ protected: /* Set help text. Each help section can have up to 18 characters. Be concise! */ void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Press Button"; + help[HEMISPHERE_HELP_DIGITALS] = "1,2=Press Button"; help[HEMISPHERE_HELP_CVS] = ""; - help[HEMISPHERE_HELP_OUTS] = "A=Trg B=Toggle"; - help[HEMISPHERE_HELP_ENCODER] = "P=Trg/Toggle"; + help[HEMISPHERE_HELP_OUTS] = "A,B=Trig/Gate Out"; + help[HEMISPHERE_HELP_ENCODER] = "T=Config P=Button"; // "------------------" <-- Size Guide } private: - bool trigger_out; // Trigger output queue (output A/C) - bool toggle_st; // Toggle state (output B/D) + bool trigger_out[2] = {0,0}; // Trigger output queue (output A/C) + bool toggle_st[2] = {0,0}; // Toggle state (output B/D) int trigger_countdown; // For momentary display of trigger output + bool channel = 0; // selected channel, 0=(output A/C) 1=(output B/D) + bool gate_mode[2] = {0,1}; // mode flag for each channel, 0=trig, 1=gate toggle void DrawIndicator() { - if (trigger_countdown) gfxFrame(9, 42, 13, 13); - gfxBitmap(12, 45, 8, CLOCK_ICON); - gfxBitmap(44, 45, 8, toggle_st ? CLOSED_ICON : OPEN_ICON); + // Guide text + gfxIcon(1, 15, ROTATE_R_ICON); + gfxPrint(10, 15, "Channel"); + gfxIcon(1, 24, ROTATE_L_ICON); + gfxPrint(10, 24, "Mode"); + + if (trigger_countdown) gfxFrame(9 + 32*channel, 42, 13, 13); // momentary manual trigger indicator + + gfxIcon(12 + channel*32, 35, DOWN_BTN_ICON); // channel selector + + ForEachChannel(ch) { + if (!gate_mode[ch]) + gfxIcon(12 + ch*32, 45, CLOCK_ICON); + else + gfxIcon(12 + ch*32, 45, toggle_st[ch] ? CLOSED_ICON : OPEN_ICON); + } + } + + void PressButton(bool ch = 0) + { + toggle_st[ch] = 1 - toggle_st[ch]; // Alternate toggle state when pressed + trigger_out[ch] = 1; // Set trigger queue } }; From 22ca9a4a2a9ad5441a898ce36db84eff0ea0b5fc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 16 Dec 2022 03:21:31 -0500 Subject: [PATCH 069/417] Display encoder direction config on Calibration screen --- software/o_c_REV/OC_calibration.ino | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index 25db4af01..fd45a2aaf 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -521,6 +521,14 @@ void calibration_draw(const CalibrationState &state) { graphics.setPrintPos(menu::kIndentDx, y + 2); if (step->help) graphics.print(step->help); + + // NJM: display encoder direction config on first and last screens + if (step->step == HELLO || step->step == CALIBRATION_EXIT) { + y += menu::kMenuLineH; + graphics.setPrintPos(menu::kIndentDx, y + 2); + graphics.print("Encoders: "); + graphics.print(OC::Strings::encoder_config_strings[ OC::calibration_data.encoder_config() ]); + } weegfx::coord_t x = menu::kDisplayWidth - 22; y = 2; From 94d73f92b0cad7debe749a75a025d12f21ef6adc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 16 Dec 2022 04:47:52 -0500 Subject: [PATCH 070/417] Version bump to 1.4.5-pio --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 4a4edbe96..27343a1d5 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.4-pio" +#define OC_VERSION "v1.4.5-pio" #define OC_VERSION_URL "github.com/djphazer" #endif From 5ff8f257dd72c736cbb37351ac5aa45637f792ea Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 19 Dec 2022 09:24:10 -0500 Subject: [PATCH 071/417] Internal Clock sends realtime MIDI Clock/Start/Stop --- software/o_c_REV/APP_HEMISPHERE.ino | 4 ++- software/o_c_REV/HEM_ClockSetup.ino | 27 ++++++++++++--- software/o_c_REV/HSClockManager.h | 53 ++++++++++++++++++----------- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 39537ffc1..77581134e 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -136,7 +136,9 @@ public: } } - if (clock_setup) ClockSetup.Controller(LEFT_HEMISPHERE, clock_m->IsForwarded()); + // if (clock_setup) + // NJM: always execute ClockSetup controller - it handles MIDI clock out + ClockSetup.Controller(LEFT_HEMISPHERE, clock_m->IsForwarded()); for (int h = 0; h < 2; h++) { diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 7e9e37be4..37f0e1c71 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -18,6 +18,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#define MIDI_CLOCK 0xF8 +#define MIDI_START 0xFA +#define MIDI_STOP 0xFC + class ClockSetup : public HemisphereApplet { public: @@ -27,9 +31,18 @@ public: void Start() { } - // When the ClockSetup is active, the selected applets should continue to function, so - // there's no need to have a controller for ClockSetup. - void Controller() { } + // The ClockSetup controller handles MIDI Clock and Transport Start/Stop + void Controller() { + if (start_q){ + start_q = 0; + usbMIDI.sendRealTime(MIDI_START); + } + if (stop_q){ + stop_q = 0; + usbMIDI.sendRealTime(MIDI_STOP); + } + if (clock_m->IsRunning() && clock_m->MIDITock()) usbMIDI.sendRealTime(MIDI_CLOCK); + } void View() { DrawInterface(); @@ -45,10 +58,14 @@ public: if (direction > 0) // right turn toggles Forwarding clock_m->ToggleForwarding(); else if (clock_m->IsRunning()) // left turn toggles clock + { + stop_q = 1; clock_m->Stop(); + } else { - clock_m->Start(); + start_q = 1; clock_m->Reset(); + clock_m->Start(); } break; @@ -95,6 +112,8 @@ protected: private: char cursor; // 0=Source, 1=Tempo, 2=Multiply + bool start_q; + bool stop_q; ClockManager *clock_m = clock_m->get(); void DrawInterface() { diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 69aeb03da..3aad2839f 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -24,6 +24,8 @@ #ifndef CLOCK_MANAGER_H #define CLOCK_MANAGER_H +#define MIDI_CLOCK_LATENCY 30 + const uint16_t CLOCK_TEMPO_MIN = 10; const uint16_t CLOCK_TEMPO_MAX = 300; @@ -36,12 +38,12 @@ class ClockManager { bool paused = 0; // Specifies whethr the clock is paused bool forwarded = 0; // Master clock forwarding is enabled when true - uint32_t last_tock_tick[2] = {0,0}; // The tick of the most recent tock - uint32_t last_tock_check[2] = {0,0}; // To avoid checking the tock more than once per tick + uint32_t beat_tick[3] = {0,0,0}; // The tick to count from + uint32_t last_tock_check[3] = {0,0,0}; // To avoid checking the tock more than once per tick bool tock = 0; // The most recent tock value - int8_t tocks_per_beat[2] = {1, 1}; // Multiplier - bool cycle[2] = {0,0}; // Alternates for each tock, for display purposes - byte count[2] = {0,0}; // Multiple counter + int8_t tocks_per_beat[3] = {1, 1, 24}; // Multiplier + bool cycle = 0; // Alternates for each tock, for display purposes + int8_t count[3] = {0,0,0}; // Multiple counter ClockManager() { SetTempoBPM(120); @@ -64,7 +66,7 @@ class ClockManager { */ void SetTempoBPM(uint16_t bpm) { bpm = constrain(bpm, CLOCK_TEMPO_MIN, CLOCK_TEMPO_MAX); - ticks_per_tock = 1000000 / bpm; + ticks_per_tock = 1000000 / bpm; // NJM: scale by 24 to reduce rounding errors tempo = bpm; } @@ -75,10 +77,13 @@ class ClockManager { */ uint16_t GetTempo() {return tempo;} - void Reset(bool ch = 0) { - last_tock_tick[ch] = OC::CORE::ticks; - count[ch] = 0; - cycle[ch] = 1 - cycle[ch]; + void Reset() { + for (int ch = 0; ch < 3; ch++) { + beat_tick[ch] = OC::CORE::ticks; + count[ch] = 0; + } + beat_tick[2] -= MIDI_CLOCK_LATENCY; + cycle = 1 - cycle; } void Start(bool p = 0) { @@ -105,22 +110,32 @@ class ClockManager { bool IsForwarded() {return forwarded;} /* Returns true if the clock should fire on this tick, based on the current tempo and multiplier */ - bool Tock(bool ch = 0) { + bool Tock(int ch = 0) { uint32_t now = OC::CORE::ticks; - if (now != last_tock_check[ch]) { - last_tock_check[ch] = now; - if (now >= (last_tock_tick[ch] + (ticks_per_tock / static_cast(tocks_per_beat[ch])))) { - tock = 1; - last_tock_tick[ch] = now; - if (++count[ch] >= tocks_per_beat[ch]) Reset(ch); - } else tock = 0; + if (now == last_tock_check[ch]) return false; // cancel redundant check + last_tock_check[ch] = now; + + tock = (now - beat_tick[ch]) >= (count[ch]+1)*ticks_per_tock / static_cast(tocks_per_beat[ch]); + if (tock) { + if (++count[ch] >= tocks_per_beat[ch]) + { + beat_tick[ch] += ticks_per_tock; + count[ch] = 0; + if (ch == 0) cycle = 1 - cycle; + } } + return tock; } + // Returns true if MIDI Clock should be sent on this tick + bool MIDITock() { + return Tock(2); + } + bool EndOfBeat(bool ch = 0) {return count[ch] == 0;} - bool Cycle(bool ch = 0) {return cycle[ch];} + bool Cycle(bool ch = 0) {return cycle;} }; ClockManager *ClockManager::instance = 0; From 34fe884cebf9a9656bbad8b5624a3f27c1a14ef5 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Tue, 20 Dec 2022 02:45:04 -0500 Subject: [PATCH 072/417] Update README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0be6d3e07..cba230f09 100755 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Welcome to Benisphere Suite, djphazer mod (Phazerville Suite) ## An active fork expanding upon Hemisphere Suite. -Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this "phazerville" branch takes the Hemisphere Suite in new directions, with many new applets and enhancements to existing ones, while also removing most full-width apps and MIDI-related stuff, abandoning the original minimalist approach, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible. +Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this "phazerville" branch takes the Hemisphere Suite in new directions, with many new applets and enhancements to existing ones, while also removing some full-width apps, abandoning the original minimalist approach, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible. I've merged bleeding-edge features from various other branches, and confirmed that it compiles and runs on my uO_C. @@ -14,8 +14,8 @@ I've merged bleeding-edge features from various other branches, and confirmed th * AnnularFusion got a makeover, now includes configurable CV input modulation (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) * Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) * EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking -* Improved internal clock and left-to-right clock forwarding controls -* Modal-editing style navigation on some applets (TB-3PO, DualTM) +* Improved internal clock controls, independent multipliers for each Hemisphere, MIDI Clock out via USB +* Modal-editing style navigation on some applets (TB-3PO, DualTM, CVRec) ### How do I try it? @@ -23,7 +23,9 @@ I might release a .hex file if there is demand... I just need to make sure I und ### How do I build it? -You can download this repo and build the code following the ["Method B" instruction](https://ornament-and-cri.me/firmware/#method_b) from the Ornament and Crime website. Very specific legacy versions of the Arduino IDE and Teensyduino add-on are required to build, and are not installable on 64-bit only systems, like Mac OS. You must use an older version (Mojave or before) or a VM to install these versions. +Building the code is fairly simple using Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://platformio.org/install/cli) or a [plugin within VSCode](https://platformio.org/install/ide?install=vscode). The project lives within the `software/o_c_REV` directory. + +You might still be able to build this repo following the ["Method B" instruction](https://ornament-and-cri.me/firmware/#method_b) from the Ornament and Crime website. ### Credits From d7808d57ea378422620ed80c8254ef6e1bbb7a41 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 02:19:25 -0500 Subject: [PATCH 073/417] Internal Clock fixes I was struggling to keep the separate multipliers in sync. I think it's solid now. Start() always calls Reset(). MIDI Clock latency offset is disabled until I do some real measurements, or decide to make it a dynamic setting. --- software/o_c_REV/HEM_ClockSetup.ino | 1 - software/o_c_REV/HSClockManager.h | 26 +++++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 37f0e1c71..528732937 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -64,7 +64,6 @@ public: } else { start_q = 1; - clock_m->Reset(); clock_m->Start(); } break; diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 3aad2839f..c9dce20b1 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -24,7 +24,7 @@ #ifndef CLOCK_MANAGER_H #define CLOCK_MANAGER_H -#define MIDI_CLOCK_LATENCY 30 +#define MIDI_CLOCK_LATENCY 20 const uint16_t CLOCK_TEMPO_MIN = 10; const uint16_t CLOCK_TEMPO_MAX = 300; @@ -66,7 +66,7 @@ class ClockManager { */ void SetTempoBPM(uint16_t bpm) { bpm = constrain(bpm, CLOCK_TEMPO_MIN, CLOCK_TEMPO_MAX); - ticks_per_tock = 1000000 / bpm; // NJM: scale by 24 to reduce rounding errors + ticks_per_tock = 1000000 / bpm; tempo = bpm; } @@ -82,12 +82,13 @@ class ClockManager { beat_tick[ch] = OC::CORE::ticks; count[ch] = 0; } - beat_tick[2] -= MIDI_CLOCK_LATENCY; - cycle = 1 - cycle; + //beat_tick[2] -= MIDI_CLOCK_LATENCY; + cycle = 0; } void Start(bool p = 0) { // forwarded = 0; // NJM- logical clock can be forwarded, too + Reset(); running = 1; paused = p; } @@ -99,7 +100,14 @@ class ClockManager { void Pause() {paused = 1;} - void ToggleForwarding() {forwarded = 1 - forwarded;} + void ToggleForwarding() { + forwarded = 1 - forwarded; + if (forwarded) { + // sync start point on next beat for multiplier + count[1] = 0; + beat_tick[1] = beat_tick[0] + ticks_per_tock; + } + } void SetForwarding(bool f) {forwarded = f;} @@ -112,15 +120,15 @@ class ClockManager { /* Returns true if the clock should fire on this tick, based on the current tempo and multiplier */ bool Tock(int ch = 0) { uint32_t now = OC::CORE::ticks; - if (now == last_tock_check[ch]) return false; // cancel redundant check + if (now == last_tock_check[ch] || beat_tick[ch] > now) return false; // cancel redundant check last_tock_check[ch] = now; tock = (now - beat_tick[ch]) >= (count[ch]+1)*ticks_per_tock / static_cast(tocks_per_beat[ch]); if (tock) { - if (++count[ch] >= tocks_per_beat[ch]) + if (++count[ch] >= tocks_per_beat[ch]) // multiplier has been met or exceeded { - beat_tick[ch] += ticks_per_tock; - count[ch] = 0; + beat_tick[ch] += ticks_per_tock; // jump one beat ahead + count[ch] = 0; // reset counter if (ch == 0) cycle = 1 - cycle; } } From d213cce92c92db763ef9d58603a8aee2e27296c2 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 02:35:02 -0500 Subject: [PATCH 074/417] Move to platform-teensy v4.17.0 and optimize for smallest code It works tho. YOLO --- software/o_c_REV/platformio.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 1ce517d28..5e4356bfa 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -11,14 +11,15 @@ src_dir = . default_envs = oc_prod [env] -platform = teensy@1.6.0 +platform = teensy@4.17.0 framework = arduino board = teensy31 board_build.f_cpu = 120000000 lib_deps = EEPROM build_flags = - -DTEENSY_OPT_FASTER -DUSB_MIDI -UUSB_SERIAL + -DTEENSY_OPT_SMALLEST_CODE +; -DTEENSY_OPT_FASTEST [env:oc_prod] build_flags = ${env.build_flags} From 691b57f05f41a76a3b65cbea6c83d7c9ca1bc083 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 03:21:18 -0500 Subject: [PATCH 075/417] Use teensy-gui to upload, so you don't have to push the button --- software/o_c_REV/platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 5e4356bfa..35e0c9042 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -21,6 +21,8 @@ build_flags = -DTEENSY_OPT_SMALLEST_CODE ; -DTEENSY_OPT_FASTEST +upload_protocol = teensy-gui + [env:oc_prod] build_flags = ${env.build_flags} From f7c35c3094a7632175c8fd2808d49b0dd34bcdc6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 06:01:18 -0500 Subject: [PATCH 076/417] Rename AnnularFusion to EuclidX, adapt for modal editing --- software/o_c_REV/HEM_AnnularFusion.ino | 49 +++++++++++++++----------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/software/o_c_REV/HEM_AnnularFusion.ino b/software/o_c_REV/HEM_AnnularFusion.ino index 486b7d870..b51025f59 100644 --- a/software/o_c_REV/HEM_AnnularFusion.ino +++ b/software/o_c_REV/HEM_AnnularFusion.ino @@ -29,7 +29,7 @@ class AnnularFusion : public HemisphereApplet { public: const char* applet_name() { - return "AnnularFu"; + return "EuclidX"; } void Start() { @@ -99,27 +99,31 @@ public: } void OnButtonPress() { - if (++cursor > 7) cursor = 0; - ResetCursor(); + isEditing = !isEditing; } void OnEncoderMove(int direction) { - int ch = cursor < NUM_PARAMS ? 0 : 1; - int f = cursor - (ch * NUM_PARAMS); // Cursor function - switch (f) { - case 0: - actual_length[ch] = length[ch] = constrain(length[ch] + direction, 3, 32); - if (beats[ch] > length[ch]) beats[ch] = length[ch]; - if (offset[ch] >= length[ch]) offset[ch] = length[ch]-1; - break; - case 1: - actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); - break; - case 2: - actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] - 1); - break; - case 3: // CV destination - cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, 5); + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 7); + ResetCursor(); + } else { + int ch = cursor < NUM_PARAMS ? 0 : 1; + int f = cursor - (ch * NUM_PARAMS); // Cursor function + switch (f) { + case 0: + actual_length[ch] = length[ch] = constrain(length[ch] + direction, 3, 32); + if (beats[ch] > length[ch]) beats[ch] = length[ch]; + if (offset[ch] >= length[ch]) offset[ch] = length[ch]-1; + break; + case 1: + actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); + break; + case 2: + actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] - 1); + break; + case 3: // CV destination + cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, 5); + } } } @@ -160,6 +164,7 @@ protected: private: int step; int cursor = 0; // Ch1: 0=Length, 1=Hits; Ch2: 2=Length 3=Hits + bool isEditing = false; uint32_t pattern[2]; // Settings @@ -214,10 +219,12 @@ private: case 0: case 1: case 2: - gfxCursor(4 + f * spacing, y + 7, 12); + gfxCursor(4 + f * spacing, y + 7, 13); + if (isEditing) gfxInvert(4+f*spacing, y-1, 13, 9); break; case 3: // CV dest selection - gfxBitmap(1 + 3 * spacing, y, 8, CV_ICON); + gfxBitmap(1 + 3 * spacing, y+1, 8, CV_ICON); + if (isEditing) gfxInvert(3*spacing, y, 9, 7); break; } From c1b58e4e89050c21a7c53b14ac50d4eb48e61663 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 06:37:04 -0500 Subject: [PATCH 077/417] DualTM: CV2 input is transpose instead of p_mod potential TODO: make CV inputs assignable, which will require a 2nd settings page --- software/o_c_REV/HEM_TM2.ino | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 399f46827..39f76a426 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -64,7 +64,11 @@ public: len_mod = constrain(length + Proportion(DetentedIn(0), HEMISPHERE_MAX_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); // CV 2 bi-polar modulation of probability - p_mod = constrain(p + Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100), 0, 100); + //p_mod = constrain(p + Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100), 0, 100); + p_mod = p; + + // CV 2 bi-polar transpose before quantize + int note_trans = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, quant_range); // Advance the register on clock, flipping bits as necessary if (clk) { @@ -98,10 +102,10 @@ public: ForEachChannel(ch) { switch (outmode[ch]) { case 0: // pitch 1 - Out(ch, quantizer.Lookup(note + 64)); + Out(ch, quantizer.Lookup(note + note_trans + 64)); break; case 1: // pitch 2 - Out(ch, quantizer.Lookup(note2 + 64)); + Out(ch, quantizer.Lookup(note2 + note_trans + 64)); break; case 2: // mod A - 8-bit bi-polar proportioned CV Out(ch, Proportion( int8_t(reg & 0xff), 0x80, HEMISPHERE_MAX_CV) ); From 3cf279bcce5b97b0063945a1ba4029121aa798e4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 06:40:42 -0500 Subject: [PATCH 078/417] Version bump to 1.4.6-pio --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 27343a1d5..bd6d45475 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.5-pio" +#define OC_VERSION "v1.4.6-pio" #define OC_VERSION_URL "github.com/djphazer" #endif From 384fba96491b7e9dcde67967581118fdfb16e7da Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 07:37:30 -0500 Subject: [PATCH 079/417] ClockManager fix off-by-one sorry, I'm dumb --- software/o_c_REV/HSClockManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index c9dce20b1..6d40aac3a 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -123,7 +123,7 @@ class ClockManager { if (now == last_tock_check[ch] || beat_tick[ch] > now) return false; // cancel redundant check last_tock_check[ch] = now; - tock = (now - beat_tick[ch]) >= (count[ch]+1)*ticks_per_tock / static_cast(tocks_per_beat[ch]); + tock = (now - beat_tick[ch]) >= count[ch]*ticks_per_tock / static_cast(tocks_per_beat[ch]); if (tock) { if (++count[ch] >= tocks_per_beat[ch]) // multiplier has been met or exceeded { From ef7629094020f9d17ed40a88655db92be6f35f2a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 21 Dec 2022 12:55:30 -0500 Subject: [PATCH 080/417] Internal Clock syncs to external clock on Digital 1 --- software/o_c_REV/HSClockManager.h | 24 ++++++++++++++++++++---- software/o_c_REV/HemisphereApplet.h | 4 ++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 6d40aac3a..a0cddec40 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -29,6 +29,9 @@ const uint16_t CLOCK_TEMPO_MIN = 10; const uint16_t CLOCK_TEMPO_MAX = 300; +const uint32_t CLOCK_TICKS_MIN = 1000000 / CLOCK_TEMPO_MAX; +const uint32_t CLOCK_TICKS_MAX = 1000000 / CLOCK_TEMPO_MIN; + class ClockManager { static ClockManager *instance; @@ -38,6 +41,7 @@ class ClockManager { bool paused = 0; // Specifies whethr the clock is paused bool forwarded = 0; // Master clock forwarding is enabled when true + uint32_t clock_tick = 0; // tick when a physical clock was received on DIGITAL 1 uint32_t beat_tick[3] = {0,0,0}; // The tick to count from uint32_t last_tock_check[3] = {0,0,0}; // To avoid checking the tock more than once per tick bool tock = 0; // The most recent tock value @@ -77,13 +81,13 @@ class ClockManager { */ uint16_t GetTempo() {return tempo;} - void Reset() { + void Reset(int count_skip = 0) { for (int ch = 0; ch < 3; ch++) { beat_tick[ch] = OC::CORE::ticks; - count[ch] = 0; + count[ch] = count_skip; } //beat_tick[2] -= MIDI_CLOCK_LATENCY; - cycle = 0; + cycle = 1 - cycle; } void Start(bool p = 0) { @@ -118,8 +122,20 @@ class ClockManager { bool IsForwarded() {return forwarded;} /* Returns true if the clock should fire on this tick, based on the current tempo and multiplier */ - bool Tock(int ch = 0) { + bool Tock(int ch = 0, bool clocked = 0) { uint32_t now = OC::CORE::ticks; + // if physical clocks arrive, adapt to external tempo + if (clocked) { + if (clock_tick && clock_tick != now) { + uint32_t clock_diff = now - clock_tick; + bool skip_one = clock_diff > ticks_per_tock; + ticks_per_tock = constrain(clock_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); // time since last clock is new tempo + tempo = 1000000 / ticks_per_tock; // imprecise, for display purposes + Reset( skip_one ? 1 : 0 ); // if clock is late, avoid double trigger + } + clock_tick = now; + } + if (now == last_tock_check[ch] || beat_tick[ch] > now) return false; // cancel redundant check last_tock_check[ch] = now; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 5dea693d4..5db102ea7 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -329,8 +329,8 @@ class HemisphereApplet { if (ch == 0) { // clock triggers if (hemisphere == LEFT_HEMISPHERE) { - if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); - else clocked = OC::DigitalInputs::clocked(); + clocked = OC::DigitalInputs::clocked(); + if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere, clocked); } else { // right side is special if (master_clock_bus) { // forwarding from left if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); From 64581b9a04d259597f0bf89879fd70047db5770e Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Fri, 23 Dec 2022 23:22:05 -0800 Subject: [PATCH 081/417] Quantizer: Fix requantize reset, improve stability Fix quantization to lowest note in non-root-containing scales --- software/o_c_REV/braids_quantizer.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/braids_quantizer.cpp b/software/o_c_REV/braids_quantizer.cpp index 73231d3e3..b417ca40b 100644 --- a/software/o_c_REV/braids_quantizer.cpp +++ b/software/o_c_REV/braids_quantizer.cpp @@ -36,6 +36,9 @@ namespace braids { +const int32_t NEIGHBOR_WEIGHT = 10; // out of 16 +constexpr int32_t CUR_WEIGHT = 16 - NEIGHBOR_WEIGHT; + void SortScale(Scale &scale) { std::sort(scale.notes, scale.notes + scale.num_notes); } @@ -64,6 +67,7 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { // We're still in the voronoi cell for the active codeword. pitch = codeword_; } else { + requantize_ = false; int16_t octave = pitch / span_ - (pitch < 0 ? 1 : 0); int16_t rel_pitch = pitch - span_ * octave; @@ -77,10 +81,10 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { } } - if (abs(pitch - (octave + 1) * span_ + notes_[0]) < best_distance) { + if (abs(pitch - (octave + 1) * span_ - notes_[0]) < best_distance) { octave++; q = 0; - } else if (abs(pitch - (octave - 1) * span_ + notes_[num_notes_ - 1]) < best_distance) { + } else if (abs(pitch - (octave - 1) * span_ - notes_[num_notes_ - 1]) <= best_distance) { octave--; q = num_notes_ - 1; } @@ -99,11 +103,13 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { ? notes_[num_notes_ - 1] + (octave - 1) * span_ : notes_[q - 1] + octave * span_; - previous_boundary_ = (9 * previous_boundary_ + 7 * codeword_) >> 4; + previous_boundary_ = + (NEIGHBOR_WEIGHT * previous_boundary_ + CUR_WEIGHT * codeword_) >> 4; next_boundary_ = q == num_notes_ - 1 ? notes_[0] + (octave + 1) * span_ : notes_[q + 1] + octave * span_; - next_boundary_ = (9 * next_boundary_ + 7 * codeword_) >> 4; + next_boundary_ = + (NEIGHBOR_WEIGHT * next_boundary_ + CUR_WEIGHT * codeword_) >> 4; transpose_ = transpose; pitch = codeword_; From 4f558de360fd91e288d0c43bb6d3caf524a78946 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 24 Dec 2022 03:10:03 -0500 Subject: [PATCH 082/417] Chordinate: cursor tweaks, fix off-by-one for toggling root note --- software/o_c_REV/HEM_Chordinator.ino | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index 8d624cc08..5dcfb1c14 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -66,11 +66,15 @@ public: gfxHeader(applet_name()); gfxPrint(0, 15, OC::scale_names_short[scale]); - if (cursor == 0) + if (cursor == 0) { gfxCursor(0, 23, 30); + if (selected) gfxInvert(0, 14, 30, 9); + } gfxPrint(36, 15, OC::Strings::note_names_unpadded[root]); - if (cursor == 1) + if (cursor == 1) { gfxCursor(36, 23, 12); + if (selected) gfxInvert(36, 14, 12, 9); + } uint16_t mask = chord_mask; for (size_t i = 0; i < active_scale.num_notes; i++) { @@ -92,7 +96,7 @@ public: } void OnButtonPress() { - if (cursor <= 2) { + if (cursor < 2) { selected = !selected; } else { chord_mask ^= 1 << (cursor - 2); @@ -115,10 +119,8 @@ public: } update_chord_quantizer(); } else { - cursor++; - if (cursor >= 2 + active_scale.num_notes) { - cursor = 0; - } + cursor = constrain(cursor + direction, 0, 1+active_scale.num_notes); + ResetCursor(); } } @@ -154,7 +156,7 @@ private: bool continuous[2]; braids::Scale active_scale; - size_t cursor = 0; + int cursor = 0; bool selected = false; // Leftmost is root, second to left is 2, etc. Defaulting here to basic triad. From 6760ed3c36a1ed5de3361b788bb10cf12f4bfc8d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 24 Dec 2022 03:10:03 -0500 Subject: [PATCH 083/417] Chordinate: fix type warnings --- software/o_c_REV/HEM_Chordinator.ino | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index 5dcfb1c14..70a261a20 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -77,7 +77,7 @@ public: } uint16_t mask = chord_mask; - for (size_t i = 0; i < active_scale.num_notes; i++) { + for (int i = 0; i < int(active_scale.num_notes); i++) { if (mask & 1) { gfxRect(5 * i, 25, 4, 4); } else { @@ -119,7 +119,8 @@ public: } update_chord_quantizer(); } else { - cursor = constrain(cursor + direction, 0, 1+active_scale.num_notes); + cursor = + constrain(cursor + direction, 0, 1 + int(active_scale.num_notes)); ResetCursor(); } } @@ -180,7 +181,7 @@ private: int rel_pitch = pitch % active_scale.span; int d = active_scale.span; size_t p = 0; - for (size_t i = 0; i < active_scale.num_notes; i++) { + for (int i = 0; i < (int) active_scale.num_notes; i++) { int e = abs(rel_pitch - active_scale.notes[i]); if (e < d) { p = i; From d190fe65e0a72609dd273ce6b46464e9418a8fe1 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 25 Dec 2022 03:54:47 -0500 Subject: [PATCH 084/417] ClockManager: better sync mechanism, expect 4 PPQN external clock --- software/o_c_REV/APP_HEMISPHERE.ino | 5 +- software/o_c_REV/HSClockManager.h | 114 +++++++++++++++++----------- software/o_c_REV/HemisphereApplet.h | 4 +- 3 files changed, 77 insertions(+), 46 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 77581134e..c05d5f980 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -136,7 +136,10 @@ public: } } - // if (clock_setup) + // Advance internal clock, sync to external pulse + if (clock_m->IsRunning()) + clock_m->SyncTrig( OC::DigitalInputs::clocked() ); + // NJM: always execute ClockSetup controller - it handles MIDI clock out ClockSetup.Controller(LEFT_HEMISPHERE, clock_m->IsForwarded()); diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index a0cddec40..427fd1011 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -24,19 +24,19 @@ #ifndef CLOCK_MANAGER_H #define CLOCK_MANAGER_H -#define MIDI_CLOCK_LATENCY 20 +#define CLOCK_PPQN 4 -const uint16_t CLOCK_TEMPO_MIN = 10; -const uint16_t CLOCK_TEMPO_MAX = 300; +static constexpr uint16_t CLOCK_TEMPO_MIN = 10; +static constexpr uint16_t CLOCK_TEMPO_MAX = 300; +static constexpr uint32_t CLOCK_TICKS_MIN = 1000000 / CLOCK_TEMPO_MAX; +static constexpr uint32_t CLOCK_TICKS_MAX = 1000000 / CLOCK_TEMPO_MIN; -const uint32_t CLOCK_TICKS_MIN = 1000000 / CLOCK_TEMPO_MAX; -const uint32_t CLOCK_TICKS_MAX = 1000000 / CLOCK_TEMPO_MIN; class ClockManager { static ClockManager *instance; uint16_t tempo; // The set tempo, for display somewhere else - uint32_t ticks_per_tock; // Based on the selected tempo in BPM + uint32_t ticks_per_beat; // Based on the selected tempo in BPM bool running = 0; // Specifies whether the clock is running for interprocess communication bool paused = 0; // Specifies whethr the clock is paused bool forwarded = 0; // Master clock forwarding is enabled when true @@ -44,10 +44,10 @@ class ClockManager { uint32_t clock_tick = 0; // tick when a physical clock was received on DIGITAL 1 uint32_t beat_tick[3] = {0,0,0}; // The tick to count from uint32_t last_tock_check[3] = {0,0,0}; // To avoid checking the tock more than once per tick - bool tock = 0; // The most recent tock value - int8_t tocks_per_beat[3] = {1, 1, 24}; // Multiplier + bool tock[3] = {0,0,0}; // The current tock value + int tocks_per_beat[3] = {1, 1, 24}; // Multiplier bool cycle = 0; // Alternates for each tock, for display purposes - int8_t count[3] = {0,0,0}; // Multiple counter + int count[3] = {0,0,0}; // Multiple counter, 0 is a special case when first starting the clock ClockManager() { SetTempoBPM(120); @@ -59,7 +59,7 @@ class ClockManager { return instance; } - void SetMultiply(int8_t multiply, bool ch = 0) { + void SetMultiply(int multiply, bool ch = 0) { multiply = constrain(multiply, 1, 24); tocks_per_beat[ch] = multiply; } @@ -70,26 +70,80 @@ class ClockManager { */ void SetTempoBPM(uint16_t bpm) { bpm = constrain(bpm, CLOCK_TEMPO_MIN, CLOCK_TEMPO_MAX); - ticks_per_tock = 1000000 / bpm; + ticks_per_beat = 1000000 / bpm; tempo = bpm; } - int8_t GetMultiply(bool ch = 0) {return tocks_per_beat[ch];} + int GetMultiply(bool ch = 0) {return tocks_per_beat[ch];} /* Gets the current tempo. This can be used between client processes, like two different * hemispheres. */ uint16_t GetTempo() {return tempo;} - void Reset(int count_skip = 0) { + void Reset(bool count_skip = 0) { for (int ch = 0; ch < 3; ch++) { beat_tick[ch] = OC::CORE::ticks; count[ch] = count_skip; } - //beat_tick[2] -= MIDI_CLOCK_LATENCY; cycle = 1 - cycle; } + // used to align the internal clock with incoming clock pulses + void Nudge(int diff) { + for (int ch = 0; ch < 3; ch++) { + beat_tick[ch] += diff; + } + } + + // called on every tick when clock is running, before all Controllers + void SyncTrig(bool clocked) { + uint32_t now = OC::CORE::ticks; + + // Reset only when all multipliers have been met + bool reset = 1; + + // count and calculate Tocks + for (int ch = 0; ch < 3; ch++) { + uint32_t next_tock_tick = beat_tick[ch] + count[ch]*ticks_per_beat / static_cast(tocks_per_beat[ch]); + tock[ch] = now >= next_tock_tick; + if (tock[ch]) ++count[ch]; // increment multiplier counter + + reset = reset && (count[ch] > tocks_per_beat[ch]); // multiplier has been exceeded + } + if (reset) Reset(1); // skip one + + // handle syncing to physical clocks + if (clocked && clock_tick) { + + uint32_t clock_diff = now - clock_tick; // 4 PPQN clock input + if (CLOCK_PPQN * clock_diff > CLOCK_TICKS_MAX) clock_tick = 0; // too slow, reset clock tracking + + // if there is a previous clock tick, update tempo and sync + if (clock_tick && clock_diff) { + // update the tempo + ticks_per_beat = constrain(CLOCK_PPQN * clock_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); // time since last clock is new tempo + tempo = 1000000 / ticks_per_beat; // imprecise, for display purposes + + int ticks_per_clock = ticks_per_beat / CLOCK_PPQN; // rounded down + //int clock_err = ticks_per_beat % CLOCK_PPQN; // rounding error + + // time since last beat + int tick_offset = now - beat_tick[2]; + + // too long ago? time til next beat + if (tick_offset > ticks_per_clock / 2) tick_offset -= ticks_per_beat; + + // within half a clock pulse of the nearest beat + if (abs(tick_offset) <= ticks_per_clock / 2) + Nudge(tick_offset); // nudge the beat towards us + + } + } + // clock has been physically ticked + if (clocked) clock_tick = now; + } + void Start(bool p = 0) { // forwarded = 0; // NJM- logical clock can be forwarded, too Reset(); @@ -109,7 +163,7 @@ class ClockManager { if (forwarded) { // sync start point on next beat for multiplier count[1] = 0; - beat_tick[1] = beat_tick[0] + ticks_per_tock; + beat_tick[1] = beat_tick[0] + ticks_per_beat; } } @@ -122,34 +176,8 @@ class ClockManager { bool IsForwarded() {return forwarded;} /* Returns true if the clock should fire on this tick, based on the current tempo and multiplier */ - bool Tock(int ch = 0, bool clocked = 0) { - uint32_t now = OC::CORE::ticks; - // if physical clocks arrive, adapt to external tempo - if (clocked) { - if (clock_tick && clock_tick != now) { - uint32_t clock_diff = now - clock_tick; - bool skip_one = clock_diff > ticks_per_tock; - ticks_per_tock = constrain(clock_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); // time since last clock is new tempo - tempo = 1000000 / ticks_per_tock; // imprecise, for display purposes - Reset( skip_one ? 1 : 0 ); // if clock is late, avoid double trigger - } - clock_tick = now; - } - - if (now == last_tock_check[ch] || beat_tick[ch] > now) return false; // cancel redundant check - last_tock_check[ch] = now; - - tock = (now - beat_tick[ch]) >= count[ch]*ticks_per_tock / static_cast(tocks_per_beat[ch]); - if (tock) { - if (++count[ch] >= tocks_per_beat[ch]) // multiplier has been met or exceeded - { - beat_tick[ch] += ticks_per_tock; // jump one beat ahead - count[ch] = 0; // reset counter - if (ch == 0) cycle = 1 - cycle; - } - } - - return tock; + bool Tock(int ch = 0) { + return tock[ch]; } // Returns true if MIDI Clock should be sent on this tick diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 5db102ea7..5dea693d4 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -329,8 +329,8 @@ class HemisphereApplet { if (ch == 0) { // clock triggers if (hemisphere == LEFT_HEMISPHERE) { - clocked = OC::DigitalInputs::clocked(); - if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere, clocked); + if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); + else clocked = OC::DigitalInputs::clocked(); } else { // right side is special if (master_clock_bus) { // forwarding from left if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); From 2f508a19dea9698f21fb617a7ffecfb106c174d8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 03:15:08 -0500 Subject: [PATCH 085/417] Version bump 1.4.7-pio --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index bd6d45475..02bea7ee8 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.6-pio" +#define OC_VERSION "v1.4.7-pio" #define OC_VERSION_URL "github.com/djphazer" #endif From c374d9a11efcd52b6f2a2ae51fa4ec9008e9363b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 03:32:15 -0500 Subject: [PATCH 086/417] Extra quantizer scales from Logarhythm --- software/o_c_REV/OC_scales.cpp | 33 +++++++++++++++++++-- software/o_c_REV/braids_quantizer_scales.h | 34 +++++++++++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/OC_scales.cpp b/software/o_c_REV/OC_scales.cpp index 344b37128..e007b8151 100644 --- a/software/o_c_REV/OC_scales.cpp +++ b/software/o_c_REV/OC_scales.cpp @@ -159,7 +159,22 @@ const char* const scale_names_short[] = { "14S3", "12S3", "10S3", - "8S3" + "8S3", + + + #ifdef HEM_LOGARHYTHM_MOD_SCALES + "5+7", // Root +5th + 7th (5+7), + "5+6", // Root + 5th + 6th (5+6), + "3b7-",// Minor Triad + 7 (3b+5+7), + "3b7+",// Major Triad + 7 (Triad+7), + "3b6-",// Minor Triad + 6th (3b+5+6), + "3b6+",// Major Triad + 6th (Triad+6), + "5th", // Fifth, + "3b+", // major triad (Triad), + "3b-", // minor triad (3b+5), + "HAR-",// Harmonic Minor (Harm Minor), + #endif + }; const char* const scale_names[] = { @@ -298,7 +313,21 @@ const char* const scale_names[] = { "21-7-SD3[14]", "18-6-SD3[12]", "15-5-SD3[10]", - "12-4-SD3[8]" + "12-4-SD3[8]", + + #ifdef HEM_LOGARHYTHM_MOD_SCALES + "5th+7th", // Root +5th + 7th (5+7), + "5th+6th", // Root + 5th + 6th (5+6), + "Triad min+7",// Minor Triad + 7 (3b+5+7), + "Triad maj+7",// Major Triad + 7 (Triad+7), + "Triad min+6",// Minor Triad + 6th (3b+5+6), + "Triad maj+6",// Major Triad + 6th (Triad+6), + "Fifth",// Fifth, + "TriadMaj", // major triad (Triad), + "TriadMin",// minor triad (3b+5), + "HarmonicMin",// Harmonic Minor (Harm Minor), + #endif + }; const char* const voltage_scalings[] = { diff --git a/software/o_c_REV/braids_quantizer_scales.h b/software/o_c_REV/braids_quantizer_scales.h index 9f24aba8e..f96514249 100644 --- a/software/o_c_REV/braids_quantizer_scales.h +++ b/software/o_c_REV/braids_quantizer_scales.h @@ -301,7 +301,39 @@ const Scale scales[] = { // // 15-5-HD3 (10 step subharmonic series scale on the tritave) - see Xen-Arts VSTi microtuning library at http://www.xen-arts.net/Xen-Arts%20VSTi%20Microtuning%20Library.zip { 12 << 7, 10, { 0, 96, 200, 312, 434, 567, 714, 879, 1066, 1281} }, // // 12-4-HD3 (8 step subharmonic series scale on the tritave) - see Xen-Arts VSTi microtuning library at http://www.xen-arts.net/Xen-Arts%20VSTi%20Microtuning%20Library.zip -{ 12 << 7, 8, { 0, 122, 255, 402, 567, 754, 969, 1224} } +{ 12 << 7, 8, { 0, 122, 255, 402, 567, 754, 969, 1224} }, + + // Logarhythm Mod: Adding some musically useful equal temperment scales from the Disting mk.4 set that are otherwise not present here + // Root +5th + 7th (5+7), + { 12 << 7, 3, { 0, 896, 1408} }, + + // Root + 5th + 6th (5+6), + { 12 << 7, 3, { 0, 896, 1152} }, + + // Minor Triad + 7 (3b+5+7), + { 12 << 7, 4, { 0, 384, 896, 1280} }, + + // Major Triad + 7 (Triad+7), + { 12 << 7, 4, { 0, 512, 896, 1408} }, + + // Minor Triad + 6th (3b+5+6), + { 12 << 7, 4, { 0, 384, 896, 1152} }, + + // Major Triad + 6th (Triad+6), + { 12 << 7, 4, { 0, 512, 896, 1152} }, + + // Fifth, + { 12 << 7, 2, { 0, 896} }, + + // major triad (Triad), + { 12 << 7, 3, { 0, 512, 896} }, + + // minor triad (3b+5), + { 12 << 7, 3, { 0, 384, 896} }, + + // Harmonic Minor (Harm Minor), + { 12 << 7, 7, { 0, 256, 384, 640, 896, 1024, 1408} }, + } ; }// namespace braids From 0b1aac0dce1d633f0b95049edc38c46527378faa Mon Sep 17 00:00:00 2001 From: Tom Waters Date: Mon, 20 Dec 2021 11:34:41 +0000 Subject: [PATCH 087/417] Draw X/Y mode & options to select which input to draw and print --- software/o_c_REV/HEM_Scope.ino | 100 ++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/software/o_c_REV/HEM_Scope.ino b/software/o_c_REV/HEM_Scope.ino index 895568ab4..28faf2566 100644 --- a/software/o_c_REV/HEM_Scope.ino +++ b/software/o_c_REV/HEM_Scope.ino @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -const uint8_t HEM_PPQN_VALUES[] = {1, 2, 4, 8, 16, 24}; +#define SCOPE_CURRENT_SETTING_TIMEOUT 50001 class Scope : public HemisphereApplet { public: @@ -33,6 +33,8 @@ public: sample_ticks = 320; freeze = 0; last_scope_tick = 0; + current_setting = 0; + current_display = 0; } void Controller() { @@ -54,14 +56,17 @@ public: } if (!freeze) { - last_cv = In(1); + last_cv = In((current_display & 1) == 1); if (--sample_countdown < 1) { sample_countdown = sample_ticks; - if (++sample_num > 63) sample_num = 0; - int sample = Proportion(In(0), HEMISPHERE_MAX_CV, 128); - sample = constrain(sample, -128, 127) + 127; - snapshot[sample_num] = (uint8_t)sample; + sample_num = LoopInt(++sample_num, 63); + + for (int n = 0; n < 2; n++) { + int sample = Proportion(In(n), HEMISPHERE_MAX_CV, 128); + sample = constrain(sample, -128, 127) + 127; + snapshot[n][sample_num] = (uint8_t)sample; + } } ForEachChannel(ch) Out(ch, In(ch)); @@ -71,21 +76,37 @@ public: void View() { gfxHeader(applet_name()); DrawBPM(); - DrawInput1(); - DrawInput2(); + + if(current_display == 4) { + DrawInput(-1); + } else { + DrawInput((current_display & 2) == 2); + PrintInput(); + } + + DrawCurrentSetting(); if (freeze) { gfxInvert(0, 24, 64, 40); } } void OnButtonPress() { - freeze = 1 - freeze; + if (OC::CORE::ticks - last_encoder_move < SCOPE_CURRENT_SETTING_TIMEOUT) { + current_setting = LoopInt(++current_setting, 2); + } + last_encoder_move = OC::CORE::ticks; } void OnEncoderMove(int direction) { - if (sample_ticks < 32) sample_ticks += direction; - else sample_ticks += direction * 10; - sample_ticks = constrain(sample_ticks, 2, 64000); + if(current_setting == 0) { + if (sample_ticks < 32) sample_ticks += direction; + else sample_ticks += direction * 10; + sample_ticks = constrain(sample_ticks, 2, 64000); + } else if(current_setting == 1) { + current_display = LoopInt(++current_display, 4); + } else if(current_setting == 2) { + freeze = direction > 0; + } last_encoder_move = OC::CORE::ticks; } @@ -107,7 +128,7 @@ protected: help[HEMISPHERE_HELP_DIGITALS] = "Clk 1=BPM 2=Cycle1"; help[HEMISPHERE_HELP_CVS] = "1=CV1 2=CV2"; help[HEMISPHERE_HELP_OUTS] = "A=CV1 B=CV2"; - help[HEMISPHERE_HELP_ENCODER] = "T=SmplRate P=Freez"; + help[HEMISPHERE_HELP_ENCODER] = "T=Value P=Setting"; // "------------------" <-- Size Guide } @@ -121,15 +142,19 @@ private: bool freeze; // Scope - uint8_t snapshot[64]; + int current_display; + int current_setting; + uint8_t snapshot[2][64]; int sample_ticks; // Ticks between samples int sample_countdown; // Last time a sample was taken int sample_num; // Current sample number at the start int last_encoder_move; // The last the the sample_ticks value was changed int last_scope_tick; // Used to auto-calculate sample countdown - // Icons - + int LoopInt(int n, int max) { + return n > max ? 0 : n; + } + void DrawBPM() { gfxPrint(9, 15, "BPM "); gfxPrint(bpm / 4); @@ -138,26 +163,45 @@ private: if (OC::CORE::ticks - last_bpm_tick < 1666) gfxBitmap(1, 15, 8, CLOCK_ICON); } - void DrawInput1() { - for (int s = 0; s < 64; s++) - { - int x = s + sample_num; - if (x > 63) x -= 64; - int l = Proportion(snapshot[x], 255, 28); - gfxPixel(x, (28 - l) + 24); - } - - if (OC::CORE::ticks - last_encoder_move < 16667) { - gfxPrint(1, 26, sample_ticks); + void DrawCurrentSetting() { + if (OC::CORE::ticks - last_encoder_move < SCOPE_CURRENT_SETTING_TIMEOUT) { + if(current_setting == 0) { + gfxPrint(1, 26, "Rate"); + gfxPrint(32, 26, sample_ticks); + } else if(current_setting == 1) { + gfxPrint(1, 26, "Mode "); + if(current_display == 4) { + gfxPrint("1,2"); + } else { + gfxPrint((current_display & 2) == 2 ? 2 : 1); + gfxPrint("+"); + gfxPrint((current_display & 1) == 1 ? 2 : 1); + } + } else if(current_setting == 2) { + gfxPrint(1, 26, "Freeze "); + gfxPrint(freeze ? "ON" : "OFF"); + } } } - void DrawInput2() { + void PrintInput() { gfxLine(0, 53, 63, 53); gfxBitmap(1, 55, 8, CV_ICON); gfxPos(12, 55); gfxPrintVoltage(last_cv); } + + void DrawInput(int input) { + int max = input < 0 ? 40 : 28; + for (int s = 0; s < 64; s++) + { + int n = s + sample_num; + if (n > 63) n -= 64; + int px = input < 0 ? Proportion(snapshot[0][n], 255, max) + 12 : n; + int py = Proportion(snapshot[input < 0 ? 1 : input][n], 255, max); + gfxPixel(px, (max - py) + 24); + } + } }; From baf7ebfd02c21c30b54bc0423a845f549c389ea4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 10:12:13 -0500 Subject: [PATCH 088/417] Fix for Metronome's Beat output --- software/o_c_REV/HSClockManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 427fd1011..d95a42456 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -185,7 +185,7 @@ class ClockManager { return Tock(2); } - bool EndOfBeat(bool ch = 0) {return count[ch] == 0;} + bool EndOfBeat(bool ch = 0) {return count[ch] == 1;} bool Cycle(bool ch = 0) {return cycle;} }; From 76673fa545d3bf571de72fa672eedecc99d89988 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 10:13:04 -0500 Subject: [PATCH 089/417] CVRecorder: fix reset behavior don't step forward right after a reset --- software/o_c_REV/HEM_CVRecV2.ino | 17 ++++++++++------- software/o_c_REV/OC_version.h | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index 9a08583d8..0701d2a4b 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -37,15 +37,17 @@ public: } void Controller() { - bool reset = Clock(1); + if (Clock(1)) reset = true; - if (Clock(0) || reset) { - step++; + if (reset) { + step = start; + if (punch_out) punch_out = end - start; + } + + if (Clock(0) ) { // sequence advance + if (!reset) step++; + reset = false; if (step > end || step < start) step = start; - if (reset) { - step = start; - if (punch_out) punch_out = end - start; - } bool rec = 0; ForEachChannel(ch) { @@ -152,6 +154,7 @@ private: simfloat signal[2]; bool smooth; bool isEditing = 0; + bool reset = true; // Transport int mode = 0; // 0=Playback, 1=Rec Track 1, 2=Rec Track 2, 3= Rec Tracks 1 & 2 diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 02bea7ee8..34a36d524 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.7-pio" +#define OC_VERSION "v1.4.7.1" #define OC_VERSION_URL "github.com/djphazer" #endif From 36cd57c672ea3aa5092a8a6208bc609dff7f1960 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 10:18:37 -0500 Subject: [PATCH 090/417] oops I'm not really trying to disable a few extra scale options. gotta catch em all --- software/o_c_REV/OC_scales.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/software/o_c_REV/OC_scales.cpp b/software/o_c_REV/OC_scales.cpp index e007b8151..221a85111 100644 --- a/software/o_c_REV/OC_scales.cpp +++ b/software/o_c_REV/OC_scales.cpp @@ -161,8 +161,6 @@ const char* const scale_names_short[] = { "10S3", "8S3", - - #ifdef HEM_LOGARHYTHM_MOD_SCALES "5+7", // Root +5th + 7th (5+7), "5+6", // Root + 5th + 6th (5+6), "3b7-",// Minor Triad + 7 (3b+5+7), @@ -173,7 +171,6 @@ const char* const scale_names_short[] = { "3b+", // major triad (Triad), "3b-", // minor triad (3b+5), "HAR-",// Harmonic Minor (Harm Minor), - #endif }; @@ -315,7 +312,6 @@ const char* const scale_names[] = { "15-5-SD3[10]", "12-4-SD3[8]", - #ifdef HEM_LOGARHYTHM_MOD_SCALES "5th+7th", // Root +5th + 7th (5+7), "5th+6th", // Root + 5th + 6th (5+6), "Triad min+7",// Minor Triad + 7 (3b+5+7), @@ -326,7 +322,6 @@ const char* const scale_names[] = { "TriadMaj", // major triad (Triad), "TriadMin",// minor triad (3b+5), "HarmonicMin",// Harmonic Minor (Harm Minor), - #endif }; From 9d7b82411928aa1575be7540f1e74743261a26e7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 13:18:56 -0500 Subject: [PATCH 091/417] Scale names: Rename USER1, etc. to USR1 --- software/o_c_REV/OC_scales.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/OC_scales.cpp b/software/o_c_REV/OC_scales.cpp index 221a85111..2792e2a1d 100644 --- a/software/o_c_REV/OC_scales.cpp +++ b/software/o_c_REV/OC_scales.cpp @@ -24,10 +24,10 @@ const Scale &Scales::GetScale(int index) { } const char* const scale_names_short[] = { - "USER1", - "USER2", - "USER3", - "USER4", + "USR1", + "USR2", + "USR3", + "USR4", "OFF ", "SEMI", "IONI", From 5866a53c5ed93c99168489d8808e60af3df472d4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 13:48:57 -0500 Subject: [PATCH 092/417] ClockSetup: adapt for modal editing --- software/o_c_REV/HEM_ClockSetup.ino | 74 ++++++++++++++++++----------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 528732937..af3968e54 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -49,33 +49,38 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + isEditing = !isEditing; } void OnEncoderMove(int direction) { - switch (cursor) { - case 0: // Source - if (direction > 0) // right turn toggles Forwarding - clock_m->ToggleForwarding(); - else if (clock_m->IsRunning()) // left turn toggles clock - { - stop_q = 1; - clock_m->Stop(); - } - else { - start_q = 1; - clock_m->Start(); + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 3); + ResetCursor(); + } else { + switch (cursor) { + case 0: // Source + if (direction > 0) // right turn toggles Forwarding + clock_m->ToggleForwarding(); + else if (clock_m->IsRunning()) // left turn toggles clock + { + stop_q = 1; + clock_m->Stop(); + } + else { + start_q = 1; + clock_m->Start(); + } + break; + + case 1: // Set tempo + clock_m->SetTempoBPM(clock_m->GetTempo() + direction); + break; + + case 2: // Set multiplier + case 3: + clock_m->SetMultiply(clock_m->GetMultiply(cursor-2) + direction, cursor-2); + break; } - break; - - case 1: // Set tempo - clock_m->SetTempoBPM(clock_m->GetTempo() + direction); - break; - - case 2: // Set multiplier - case 3: - clock_m->SetMultiply(clock_m->GetMultiply(cursor-2) + direction, cursor-2); - break; } } @@ -111,6 +116,7 @@ protected: private: char cursor; // 0=Source, 1=Tempo, 2=Multiply + bool isEditing = false; bool start_q; bool stop_q; ClockManager *clock_m = clock_m->get(); @@ -152,10 +158,24 @@ private: gfxPrint(33, 35, "x"); gfxPrint(clock_m->GetMultiply(1)); - if (cursor == 0) gfxCursor(20, 23, 54); - if (cursor == 1) gfxCursor(23, 33, 18); - if (cursor == 2) gfxCursor(8, 43, 12); - if (cursor == 3) gfxCursor(40, 43, 12); + switch (cursor) { + case 0: + gfxCursor(20, 23, 54); + if (isEditing) gfxInvert(20, 14, 54, 9); + break; + case 1: + gfxCursor(23, 33, 18); + if (isEditing) gfxInvert(23, 24, 18, 9); + break; + case 2: + gfxCursor(8, 43, 12); + if (isEditing) gfxInvert(8, 34, 12, 9); + break; + case 3: + gfxCursor(40, 43, 12); + if (isEditing) gfxInvert(40, 34, 12, 9); + break; + } } }; From adead76188288a595d4295409ec0fc9282a2b7ec Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 14:07:41 -0500 Subject: [PATCH 093/417] Chordinate: validate scale selection, fix mask display --- software/o_c_REV/HEM_Chordinator.ino | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index 70a261a20..8aaa37e09 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -78,21 +78,23 @@ public: uint16_t mask = chord_mask; for (int i = 0; i < int(active_scale.num_notes); i++) { + int y = 7*(i / 12); // longer scales spill over + int x = 5*(i % 12); if (mask & 1) { - gfxRect(5 * i, 25, 4, 4); + gfxRect(x, 25 + y, 4, 4); } else { - gfxFrame(5 * i, 25, 4, 4); + gfxFrame(x, 25 + y, 4, 4); } if (cursor - 2 == i) { - gfxCursor(5 * i, 31, 4); + gfxCursor(x, 30 + y, 4); } mask >>= 1; } size_t root_ix = note_ix(chord_root_pitch); - gfxBitmap(5 * root_ix, 35, 8, NOTE4_ICON); - gfxBitmap(5 * note_ix(harm_pitch), 45, 8, NOTE4_ICON); + gfxBitmap(5 * root_ix, 40, 8, NOTE4_ICON); + gfxBitmap(5 * note_ix(harm_pitch), 50, 8, NOTE4_ICON); } void OnButtonPress() { @@ -152,7 +154,7 @@ private: braids::Quantizer root_quantizer; braids::Quantizer chord_quantizer; - size_t scale; // SEMI + int scale; // SEMI int16_t root; bool continuous[2]; braids::Scale active_scale; @@ -191,8 +193,10 @@ private: return p; } - void set_scale(size_t value) { - scale = value; + void set_scale(int value) { + if (value < 0) scale = OC::Scales::NUM_SCALES - 1; + else if (value >= OC::Scales::NUM_SCALES) scale = 0; + else scale = value; active_scale = OC::Scales::GetScale(scale); root_quantizer.Configure(active_scale); } From 4909eb923a30830b122d3c61c8ef1a83ee63c450 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 15:04:12 -0500 Subject: [PATCH 094/417] CVRec: fix reset for recording --- software/o_c_REV/HEM_CVRecV2.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index 0701d2a4b..49e2269da 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -46,7 +46,6 @@ public: if (Clock(0) ) { // sequence advance if (!reset) step++; - reset = false; if (step > end || step < start) step = start; bool rec = 0; ForEachChannel(ch) @@ -64,9 +63,10 @@ public: } } } - if (rec) { + if (rec && !reset) { if (--punch_out == 0) mode = 0; } + reset = false; } ForEachChannel(ch) From a8f97c299a980550328d6a25c73918e9f4287767 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 19:00:08 -0500 Subject: [PATCH 095/417] v1.4.8 just some things --- software/o_c_REV/HEM_TM2.ino | 2 +- software/o_c_REV/OC_version.h | 2 +- software/o_c_REV/hemisphere_config.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 39f76a426..a2d9f156a 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -199,7 +199,7 @@ public: quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); reg = Unpack(data, PackLocation {32,32}); - reg2 = ~reg; + reg2 = Unpack(data, PackLocation {0, 32}); // lol it could be fun } protected: diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 34a36d524..91b42825c 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.7.1" +#define OC_VERSION "v1.4.8" #define OC_VERSION_URL "github.com/djphazer" #endif diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index bac4297d9..3b163cac5 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -17,7 +17,6 @@ #define HEMISPHERE_APPLETS { \ DECLARE_APPLET( 8, 0x01, ADSREG), \ DECLARE_APPLET( 34, 0x01, ADEG), \ - DECLARE_APPLET( 15, 0x02, AnnularFusion), \ DECLARE_APPLET( 47, 0x09, ASR), \ DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ DECLARE_APPLET( 41, 0x41, Binary), \ @@ -35,6 +34,7 @@ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ + DECLARE_APPLET( 15, 0x02, AnnularFusion), \ DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ From 0362a64e3b4771d204c82b97f63421ea7e5bbc92 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 21:37:02 -0500 Subject: [PATCH 096/417] pressing both Up+Down buttons quickly opens Clock Setup it's kinda awkward, but much quicker than a long-press. --- software/o_c_REV/APP_HEMISPHERE.ino | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index c05d5f980..e238f20de 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -192,10 +192,13 @@ public: } void DelegateSelectButtonPush(int hemisphere) { - if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME && hemisphere == first_click) { + if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME) { // This is a double-click, so activate corresponding help screen, leave // Select Mode, and reset the double-click timer - SetHelpScreen(hemisphere); + if (hemisphere == first_click) + SetHelpScreen(hemisphere); + else // up + down simultaneous + clock_setup = 1; select_mode = -1; click_tick = 0; } else { @@ -208,12 +211,13 @@ public: // If we're in the clock setup screen, we want to exit the setup without turning on Select Mode if (hemisphere == select_mode) select_mode = -1; // Leave Select Mode is same button is pressed else select_mode = hemisphere; // Otherwise, set Select Mode - click_tick = OC::CORE::ticks; } + click_tick = OC::CORE::ticks; first_click = hemisphere; } - clock_setup = 0; // Turn off clock setup with any button press + if (click_tick) + clock_setup = 0; // Turn off clock setup with any single button press } void DelegateEncoderMovement(const UI::Event &event) { From e7bfd6452c0600d2419ca05e5607fbe7cabe99a8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 27 Dec 2022 21:49:55 -0500 Subject: [PATCH 097/417] DualTM: CV input mapping, extra output modes New output modes: Pitch 1+2, Gate 1+2 CV input destinations: Len, Prob, Range, Trans 1, Trans 2, Trans 1+2 --- software/o_c_REV/HEM_TM2.ino | 213 +++++++++++++++++++++++++---------- 1 file changed, 152 insertions(+), 61 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index a2d9f156a..17e5eada8 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -1,4 +1,5 @@ // Copyright (c) 2018, Jason Justian +// Copyright (c) 2022, Nicholas J. Michalek // // Based on Braids Quantizer, Copyright 2015 Émilie Gillet. // @@ -26,7 +27,7 @@ * Thanks to Tom Whitwell for creating the concept, and for clarifying some things * Thanks to Jon Wheeler for the CV length and probability updates * - * adapted as DualTM by djphazer (Nicholas J. Michalek) + * Heavily adapted as DualTM from ShiftReg/TM by djphazer (Nicholas J. Michalek) */ #include "braids_quantizer.h" @@ -60,16 +61,47 @@ public: void Controller() { bool clk = Clock(0); - // CV 1 bi-polar modulation of length - len_mod = constrain(length + Proportion(DetentedIn(0), HEMISPHERE_MAX_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); - - // CV 2 bi-polar modulation of probability - //p_mod = constrain(p + Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 100), 0, 100); + int cv_data[2]; + cv_data[0] = DetentedIn(0); + cv_data[1] = DetentedIn(1); + + // default to no mod p_mod = p; + len_mod = length; + range_mod = quant_range; + int note_trans1 = 0; + int note_trans2 = 0; + int note_trans3 = 0; + + // process CV inputs + ForEachChannel(ch) { + // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 + switch (cvmode[ch]) { + case 0: // bi-polar modulation of length + len_mod = constrain(len_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); + break; + + case 1: // bi-polar modulation of probability + p_mod = constrain(p_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 100), 0, 100); + break; + + case 2: // bi-polar modulation of note range + range_mod = constrain(range_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 32), 1, 32); + break; + + case 3: // bi-polar transpose before quantize + note_trans1 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); + break; + case 4: + note_trans2 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); + break; + case 5: + note_trans3 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); + break; + default: break; + } + } - // CV 2 bi-polar transpose before quantize - int note_trans = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, quant_range); - // Advance the register on clock, flipping bits as necessary if (clk) { // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same @@ -88,10 +120,10 @@ public: reg2 = (reg2 << 1) + last2; } - // Send 5-bit scaled and quantized CV + // Send 8-bit scaled and quantized CV // scaled = note * quant_range / 0x1f - int32_t note = Proportion(reg & 0x1f, 0x1f, quant_range); - int32_t note2 = Proportion(reg2 & 0x1f, 0x1f, quant_range); + int32_t note = Proportion(reg & 0xff, 0xff, range_mod); + int32_t note2 = Proportion(reg2 & 0xff, 0xff, range_mod); /* note *= quant_range; @@ -101,17 +133,20 @@ public: ForEachChannel(ch) { switch (outmode[ch]) { + case -1: // pitch 1+2 + Out(ch, quantizer.Lookup(note + note2 + note_trans3 + 64)); + break; case 0: // pitch 1 - Out(ch, quantizer.Lookup(note + note_trans + 64)); + Out(ch, quantizer.Lookup(note + note_trans1 + note_trans3 + 64)); break; case 1: // pitch 2 - Out(ch, quantizer.Lookup(note2 + note_trans + 64)); + Out(ch, quantizer.Lookup(note2 + note_trans2 + note_trans3 + 64)); break; case 2: // mod A - 8-bit bi-polar proportioned CV - Out(ch, Proportion( int8_t(reg & 0xff), 0x80, HEMISPHERE_MAX_CV) ); + Out(ch, Proportion( int(reg & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case 3: // mod B - Out(ch, Proportion( int8_t(reg2 & 0xff), 0x80, HEMISPHERE_MAX_CV) ); + Out(ch, Proportion( int(reg2 & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case 4: // trig A if (clk && (reg & 0x01) == 1) // trigger if 1st bit is high @@ -127,6 +162,9 @@ public: case 7: // gate B GateOut(ch, (reg2 & 0x01)); break; + case 8: // gate A+B + Out(ch, ((reg & 0x01)+(reg2 & 0x01))*HEMISPHERE_3V_CV); + break; } } } @@ -143,10 +181,7 @@ public: void OnEncoderMove(int direction) { if (!isEditing) { - cursor += direction; - if (cursor < 0) cursor = 5; - if (cursor > 5) cursor = 0; - + cursor = constrain(cursor + direction, 0, 7); ResetCursor(); // Reset blink so it's immediately visible when moved } else { switch (cursor) { @@ -166,10 +201,16 @@ public: quant_range = constrain(quant_range + direction, 1, 32); break; case 4: - outmode[0] = constrain(outmode[0] + direction, 0, 7); + outmode[0] = constrain(outmode[0] + direction, -1, 8); break; case 5: - outmode[1] = constrain(outmode[1] + direction, 0, 7); + outmode[1] = constrain(outmode[1] + direction, -1, 8); + break; + case 6: + cvmode[0] = constrain(cvmode[0] + direction, 0, 5); + break; + case 7: + cvmode[1] = constrain(cvmode[1] + direction, 0, 5); break; } } @@ -180,11 +221,14 @@ public: Pack(data, PackLocation {0,7}, p); Pack(data, PackLocation {7,5}, length - 1); Pack(data, PackLocation {12,5}, quant_range - 1); - Pack(data, PackLocation {17,3}, outmode[0]); - Pack(data, PackLocation {20,3}, outmode[1]); - Pack(data, PackLocation {23,8}, constrain(scale, 0, 255)); + Pack(data, PackLocation {17,4}, outmode[0]+1); + Pack(data, PackLocation {21,4}, outmode[1]+1); + Pack(data, PackLocation {25,8}, constrain(scale, 0, 255)); + Pack(data, PackLocation {33,4}, cvmode[0]); + Pack(data, PackLocation {37,4}, cvmode[1]); - Pack(data, PackLocation {32,32}, reg); + // maybe don't bother saving the damn register + //Pack(data, PackLocation {32,32}, reg); return data; } @@ -193,10 +237,12 @@ public: p = Unpack(data, PackLocation {0,7}); length = Unpack(data, PackLocation {7,5}) + 1; quant_range = Unpack(data, PackLocation{12,5}) + 1; - outmode[0] = Unpack(data, PackLocation {17,3}); - outmode[1] = Unpack(data, PackLocation {20,3}); - scale = Unpack(data, PackLocation {23,8}); + outmode[0] = Unpack(data, PackLocation {17,4}) - 1; + outmode[1] = Unpack(data, PackLocation {21,4}) - 1; + scale = Unpack(data, PackLocation {25,8}); quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + cvmode[0] = Unpack(data, PackLocation {33,4}); + cvmode[1] = Unpack(data, PackLocation {37,4}); reg = Unpack(data, PackLocation {32,32}); reg2 = Unpack(data, PackLocation {0, 32}); // lol it could be fun @@ -206,7 +252,7 @@ protected: void SetHelp() { // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=p Gate"; - help[HEMISPHERE_HELP_CVS] = "1=Length 2=p Mod"; + help[HEMISPHERE_HELP_CVS] = "Assignable"; help[HEMISPHERE_HELP_OUTS] = "A=Quant5-bit B=CV2"; help[HEMISPHERE_HELP_ENCODER] = "Len/Prob/Scl/Range"; // "------------------" <-- Size Guide @@ -215,7 +261,7 @@ protected: private: int length; // Sequence length int len_mod; // actual length after CV mod - int cursor; // 0 = length, 1 = p, 2 = scale, 3=range, 4=OutA, 5=OutB + int cursor; // 0 = length, 1 = p, 2 = scale, 3=range, 4=OutA, 5=OutB, 6=CV1, 7=CV2 bool isEditing = false; braids::Quantizer quantizer; @@ -226,12 +272,18 @@ private: int p_mod; int scale; // Scale used for quantized output int quant_range; + int range_mod; // output modes: // 0=pitch A; 2=mod A; 4=trig A; 6=gate A // 1=pitch B; 3=mod B; 5=trig B; 7=gate B + // -1=pitch A+B; 8=gate A+B int outmode[2] = {0, 5}; + // CV input mappings: + // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 + int cvmode[2] = {0, 5}; + void DrawSelector() { gfxBitmap(1, 14, 8, LOOP_ICON); gfxPrint(12 + pad(10, len_mod), 15, len_mod); @@ -241,34 +293,69 @@ private: } else { // p is disabled gfxBitmap(49, 14, 8, LOCK_ICON); } - gfxBitmap(1, 24, 8, SCALE_ICON); + gfxBitmap(1, 25, 8, SCALE_ICON); gfxPrint(12, 25, OC::scale_names_short[scale]); - gfxBitmap(41, 24, 8, NOTE4_ICON); - gfxPrint(49, 25, quant_range); // APD - gfxPrint(1, 35, "A:"); - gfxPrint(32, 35, "B:"); - - ForEachChannel(ch) { - switch (outmode[ch]) { - case 0: // pitch output - case 1: - gfxBitmap(13 + ch*32, 35, 8, NOTE_ICON); - break; - case 2: // mod output - case 3: - gfxBitmap(13 + ch*32, 35, 8, WAVEFORM_ICON); - break; - case 4: // trig output - case 5: - gfxBitmap(13 + ch*32, 35, 8, CLOCK_ICON); - break; - case 6: // gate output - case 7: - gfxBitmap(13 + ch*32, 35, 8, METER_ICON); - break; + gfxBitmap(41, 25, 8, UP_DOWN_ICON); + gfxPrint(49, 25, range_mod); // APD + + if (cursor < 6) { + gfxPrint(1, 36, "A:"); + gfxPrint(32, 36, "B:"); + + ForEachChannel(ch) { + switch (outmode[ch]) { + case -1: gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + case 0: // pitch output + case 1: + gfxBitmap(15 + ch*32, 35, 8, NOTE_ICON); + break; + case 2: // mod output + case 3: + gfxBitmap(15 + ch*32, 35, 8, WAVEFORM_ICON); + break; + case 4: // trig output + case 5: + gfxBitmap(15 + ch*32, 35, 8, CLOCK_ICON); + break; + case 8: gfxBitmap(24+ch*32, 35, 3, SUB_TWO); + case 6: // gate output + case 7: + gfxBitmap(15 + ch*32, 35, 8, METER_ICON); + break; + } + // indicator for reg1 or reg2 + gfxBitmap(24+ch*32, 35, 3, (outmode[ch] % 2) ? SUB_TWO : SUP_ONE); + } + } else { // CV inputs + gfxIcon(1, 35, CV_ICON); + gfxBitmap(9, 35, 3, SUP_ONE); + gfxIcon(32, 35, CV_ICON); + gfxBitmap(40, 35, 3, SUB_TWO); + + ForEachChannel(ch) { + // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 + switch (cvmode[ch]) { + case 0: + gfxIcon(15 + ch*32, 35, LOOP_ICON); + break; + case 1: + gfxPrint(15 + ch*32, 35, "p"); + break; + case 2: + gfxIcon(15 + ch*32, 35, UP_DOWN_ICON); + break; + case 3: + gfxIcon(15 + ch*32, 35, BEND_ICON); + gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + break; + case 5: + gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + case 4: + gfxIcon(15 + ch*32, 35, BEND_ICON); + gfxBitmap(24+ch*32, 35, 3, SUB_TWO); + break; + } } - // indicator for reg1 or reg2 - gfxBitmap(22+ch*32, 35, 3, (outmode[ch] % 2) ? SUB_TWO : SUP_ONE); } switch (cursor) { @@ -276,8 +363,10 @@ private: case 1: gfxCursor(45, 23, 18); break; // Probability Cursor case 2: gfxCursor(12, 33, 25); break; // Scale Cursor case 3: gfxCursor(49, 33, 14); break; // Quant Range Cursor // APD - case 4: gfxCursor(12, 43, 10); break; // Out A - case 5: gfxCursor(44, 43, 10); break; // Out B + case 6: + case 4: gfxCursor(14, 43, 10); break; // Out A / CV 1 + case 7: + case 5: gfxCursor(46, 43, 10); break; // Out B / CV 2 } if (isEditing) { switch (cursor) { @@ -285,8 +374,10 @@ private: case 1: gfxInvert(45, 14, 18, 9); break; case 2: gfxInvert(12, 24, 25, 9); break; case 3: gfxInvert(49, 24, 14, 9); break; - case 4: gfxInvert(12, 34, 14, 9); break; - case 5: gfxInvert(44, 34, 14, 9); break; + case 6: + case 4: gfxInvert(14, 34, 14, 9); break; + case 7: + case 5: gfxInvert(46, 34, 14, 9); break; } } } From 86902881b8130bd0eeae4b1947d4a2a2ec0b97de Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 28 Dec 2022 04:19:52 -0500 Subject: [PATCH 098/417] DualTM: add slew to all non-pitch outputs, with CV target Trig acts like a decay envelope. Still some rounding errors tho - very large smoothing values tend toward 0; --- software/o_c_REV/HEM_TM2.ino | 71 +++++++++++++++++++++-------- software/o_c_REV/HemisphereApplet.h | 2 +- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 17e5eada8..8796b4228 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -38,6 +38,7 @@ #define TM2_MIN_LENGTH 2 #define TM2_MAX_LENGTH 32 +#define TM2_SMOOTHING 128 class DualTM : public HemisphereApplet { public: @@ -49,6 +50,8 @@ public: void Start() { reg = random(0, 65535); reg2 = ~reg; + Output[0] = 0; + Output[1] = 0; p = 0; length = 16; quant_range = 24; @@ -69,6 +72,7 @@ public: p_mod = p; len_mod = length; range_mod = quant_range; + smooth_mod = smoothing; int note_trans1 = 0; int note_trans2 = 0; int note_trans3 = 0; @@ -77,6 +81,9 @@ public: ForEachChannel(ch) { // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 switch (cvmode[ch]) { + case -1: // bi-polar mod of slew factor + smooth_mod = constrain(smooth_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 128), 1, 128); + break; case 0: // bi-polar modulation of length len_mod = constrain(len_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); break; @@ -134,38 +141,44 @@ public: ForEachChannel(ch) { switch (outmode[ch]) { case -1: // pitch 1+2 - Out(ch, quantizer.Lookup(note + note2 + note_trans3 + 64)); + Output[ch] = quantizer.Lookup(note + note2 + note_trans3 + 64); break; case 0: // pitch 1 - Out(ch, quantizer.Lookup(note + note_trans1 + note_trans3 + 64)); + Output[ch] = quantizer.Lookup(note + note_trans1 + note_trans3 + 64); break; case 1: // pitch 2 - Out(ch, quantizer.Lookup(note2 + note_trans2 + note_trans3 + 64)); + Output[ch] = quantizer.Lookup(note2 + note_trans2 + note_trans3 + 64); break; case 2: // mod A - 8-bit bi-polar proportioned CV - Out(ch, Proportion( int(reg & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); + Output[ch] = slew(Output[ch], Proportion( int(reg & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case 3: // mod B - Out(ch, Proportion( int(reg2 & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); + Output[ch] = slew(Output[ch], Proportion( int(reg2 & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case 4: // trig A if (clk && (reg & 0x01) == 1) // trigger if 1st bit is high - ClockOut(ch); + Output[ch] = HEMISPHERE_MAX_CV; //ClockOut(ch); + else // decay + Output[ch] = slew(Output[ch]); break; case 5: // trig B if (clk && (reg2 & 0x01) == 1) // trigger if 1st bit is high - ClockOut(ch); + Output[ch] = HEMISPHERE_MAX_CV; //ClockOut(ch); + else + Output[ch] = slew(Output[ch]); break; case 6: // gate A - GateOut(ch, (reg & 0x01)); + Output[ch] = slew(Output[ch], (reg & 0x01)*HEMISPHERE_MAX_CV ); break; case 7: // gate B - GateOut(ch, (reg2 & 0x01)); + Output[ch] = slew(Output[ch], (reg2 & 0x01)*HEMISPHERE_MAX_CV ); break; case 8: // gate A+B - Out(ch, ((reg & 0x01)+(reg2 & 0x01))*HEMISPHERE_3V_CV); + Output[ch] = slew(Output[ch], ((reg & 0x01)+(reg2 & 0x01))*HEMISPHERE_3V_CV ); break; } + + Out(ch, Output[ch]); } } @@ -181,7 +194,7 @@ public: void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, 7); + cursor = constrain(cursor + direction, 0, 8); ResetCursor(); // Reset blink so it's immediately visible when moved } else { switch (cursor) { @@ -207,10 +220,13 @@ public: outmode[1] = constrain(outmode[1] + direction, -1, 8); break; case 6: - cvmode[0] = constrain(cvmode[0] + direction, 0, 5); + cvmode[0] = constrain(cvmode[0] + direction, -1, 5); break; case 7: - cvmode[1] = constrain(cvmode[1] + direction, 0, 5); + cvmode[1] = constrain(cvmode[1] + direction, -1, 5); + break; + case 8: + smoothing = constrain(smoothing + direction, 1, 128); break; } } @@ -224,8 +240,8 @@ public: Pack(data, PackLocation {17,4}, outmode[0]+1); Pack(data, PackLocation {21,4}, outmode[1]+1); Pack(data, PackLocation {25,8}, constrain(scale, 0, 255)); - Pack(data, PackLocation {33,4}, cvmode[0]); - Pack(data, PackLocation {37,4}, cvmode[1]); + Pack(data, PackLocation {33,4}, cvmode[0]+1); + Pack(data, PackLocation {37,4}, cvmode[1]+1); // maybe don't bother saving the damn register //Pack(data, PackLocation {32,32}, reg); @@ -241,8 +257,8 @@ public: outmode[1] = Unpack(data, PackLocation {21,4}) - 1; scale = Unpack(data, PackLocation {25,8}); quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); - cvmode[0] = Unpack(data, PackLocation {33,4}); - cvmode[1] = Unpack(data, PackLocation {37,4}); + cvmode[0] = Unpack(data, PackLocation {33,4}) - 1; + cvmode[1] = Unpack(data, PackLocation {37,4}) - 1; reg = Unpack(data, PackLocation {32,32}); reg2 = Unpack(data, PackLocation {0, 32}); // lol it could be fun @@ -268,11 +284,16 @@ private: // Settings uint32_t reg; // 32-bit sequence register uint32_t reg2; // DJP + + int Output[2]; + int p; // Probability of bit flipping on each cycle int p_mod; int scale; // Scale used for quantized output int quant_range; int range_mod; + int smoothing = 4; + int smooth_mod; // output modes: // 0=pitch A; 2=mod A; 4=trig A; 6=gate A @@ -284,6 +305,11 @@ private: // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 int cvmode[2] = {0, 5}; + int slew(int value_, int value = 0) { + value_ = (value_ * (smooth_mod*TM2_SMOOTHING - 1) + value) / (smooth_mod*TM2_SMOOTHING); + return value_; + } + void DrawSelector() { gfxBitmap(1, 14, 8, LOOP_ICON); gfxPrint(12 + pad(10, len_mod), 15, len_mod); @@ -326,7 +352,7 @@ private: // indicator for reg1 or reg2 gfxBitmap(24+ch*32, 35, 3, (outmode[ch] % 2) ? SUB_TWO : SUP_ONE); } - } else { // CV inputs + } else if (cursor < 8) { // CV inputs gfxIcon(1, 35, CV_ICON); gfxBitmap(9, 35, 3, SUP_ONE); gfxIcon(32, 35, CV_ICON); @@ -335,6 +361,9 @@ private: ForEachChannel(ch) { // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 switch (cvmode[ch]) { + case -1: // smoothing + gfxIcon(15 + ch*32, 35, MOD_ICON); + break; case 0: gfxIcon(15 + ch*32, 35, LOOP_ICON); break; @@ -356,6 +385,12 @@ private: break; } } + } else { // smoothing + gfxPrint(1, 35, "Slew:"); + gfxPrint(smooth_mod); + + gfxCursor(31, 43, 18); + if (isEditing) gfxInvert(31, 34, 18, 9); } switch (cursor) { diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 5dea693d4..4109107ad 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -297,7 +297,7 @@ class HemisphereApplet { void gfxHeader(const char *str) { gfxPrint(1, 2, str); gfxLine(0, 10, 62, 10); - gfxLine(0, 12, 62, 12); + gfxLine(0, 11, 62, 11); } //////////////// Offset I/O methods From f87f3f1d76067b92105b6fc3e99bf0e521b4b485 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 28 Dec 2022 22:57:03 -0500 Subject: [PATCH 099/417] EbbAndLFO: latest version from qiemem (Bryan Head) --- software/o_c_REV/HEM_EbbAndLfo.ino | 179 +++++++++++++---------- software/o_c_REV/platformio.ini | 6 +- software/o_c_REV/resources/tideslite.cpp | 24 +++ software/o_c_REV/resources/tideslite.h | 129 +++++++++------- 4 files changed, 207 insertions(+), 131 deletions(-) create mode 100644 software/o_c_REV/resources/tideslite.cpp diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index f0e0348d0..00de28856 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -9,18 +9,38 @@ public: void Controller() { uint32_t phase_increment = ComputePhaseIncrement(pitch + In(0)); phase += phase_increment; + if (Clock(1)) phase = 0; + if (Clock(0)) { + //uint32_t next_tick = predictor.Predict(ClockCycleTicks(0)); + int new_freq = 0xffffffff / ClockCycleTicks(0); + pitch = ComputePitch(new_freq); + phase = 0; + } int slope_cv = In(1) * 32768 / HEMISPHERE_3V_CV; int s = constrain(slope * 65535 / 127 + slope_cv, 0, 65535); - ProcessSample(s, att_shape * 65535 / 127, dec_shape * 65535 / 127, - fold * 32767 / 127, phase, sample); + ProcessSample(s, shape * 65535 / 127, fold * 32767 / 127, phase, sample); if (phase < phase_increment) { eoa_reached = false; } else { eoa_reached = eoa_reached || (sample.flags & FLAG_EOA); } - output(0, (Output) out_a); - output(1, (Output) out_b); + ForEachChannel(ch) { + switch (output(ch)) { + case UNIPOLAR: + Out(ch, Proportion(sample.unipolar, 65535, HEMISPHERE_MAX_CV)); + break; + case BIPOLAR: + Out(ch, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); + break; + case EOA: + GateOut(ch, eoa_reached); + break; + case EOR: + GateOut(ch, !eoa_reached); + break; + } + } if (knob_accel > (1 << 8)) knob_accel--; @@ -29,15 +49,32 @@ public: void View() { gfxHeader(applet_name()); - int last = 50; - for (int i = 1; i < 64; i++) { - ProcessSample(slope * 65535 / 127, att_shape * 65535 / 127, - dec_shape * 65535 / 127, fold * 32767 / 127, - 0xffffffff / 64 * i, disp_sample); - int next = 50 - disp_sample.unipolar * 35 / 65535; - gfxLine(i - 1, last, i, next); - last = next; - // gfxPixel(i, 50 - disp_sample.unipolar * 35 / 65536); + ForEachChannel(ch) { + int h = 17; + int bottom = 32 + (h + 1) * ch; + int last = bottom; + for (int i = 0; i < 64; i++) { + ProcessSample(slope * 65535 / 127, shape * 65535 / 127, + fold * 32767 / 127, 0xffffffff / 64 * i, disp_sample); + int next = 0; + switch (output(ch)) { + case UNIPOLAR: + next = bottom - disp_sample.unipolar * h / 65535; + break; + case BIPOLAR: + next = bottom - (disp_sample.bipolar + 32767) * h / 65535; + break; + case EOA: + next = bottom - ((disp_sample.flags & FLAG_EOA) ? h : 0); + break; + case EOR: + next = bottom - ((disp_sample.flags & FLAG_EOR) ? h : 0); + break; + } + if (i > 0) gfxLine(i - 1, last, i, next); + last = next; + // gfxPixel(i, 50 - disp_sample.unipolar * 35 / 65536); + } } uint32_t p = phase / (0xffffffff / 64); gfxLine(p, 15, p, 50); @@ -53,31 +90,25 @@ public: gfxPrint(slope); break; case 2: - gfxPrint(0, 55, "Att: "); - gfxPrint(att_shape); + gfxPrint(0, 55, "Shape: "); + gfxPrint(shape); break; case 3: - gfxPrint(0, 55, "Dec: "); - gfxPrint(dec_shape); - break; - case 4: gfxPrint(0, 55, "Fold: "); gfxPrint(fold); break; - case 5: - gfxPrint(0, 55, "OutA: "); - gfxPrint(out_labels[out_a]); - break; - case 6: - gfxPrint(0, 55, "OutB: "); - gfxPrint(out_labels[out_b]); + case 4: + gfxPrint(0, 55, hemisphere == 0 ? "A: " : "C: "); + gfxPrint(out_labels[output(0)]); + gfxPrint(hemisphere == 0 ? " B: " : " D: "); + gfxPrint(out_labels[output(1)]); break; } } - void OnButtonPress() { + void OnButtonPress() { // TODO: modal editing cursor++; - cursor %= 7; + cursor %= 5; } void OnEncoderMove(int direction) { @@ -96,38 +127,46 @@ public: break; } case 2: { - att_shape = constrain(att_shape + direction, 0, 127); + shape += direction; + while (shape < 0) shape += 128; + while (shape > 127) shape -= 128; break; } case 3: { - dec_shape = constrain(dec_shape + direction, 0, 127); - break; - } - case 4: { fold = constrain(fold + direction, 0, 127); break; } - case 5: { - out_a += direction; - out_a %= 4; - break; - } - case 6: { - out_b += direction; - out_b %= 4; - break; + case 4: { + out += direction; + out %= 0b10000; } } if (knob_accel < (1 << 13)) knob_accel <<= 1; } - uint64_t OnDataRequest() { return 0; } + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation { 0, 16 }, pitch + (1 << 15)); + Pack(data, PackLocation { 16, 7 }, slope); + Pack(data, PackLocation { 23, 7 }, shape); + Pack(data, PackLocation { 30, 7 }, fold); + Pack(data, PackLocation { 37, 4 }, out); + Pack(data, PackLocation { 41, 4 }, cv); + return data; + } - void OnDataReceive(uint64_t data) {} + void OnDataReceive(uint64_t data) { + pitch = Unpack(data, PackLocation { 0, 16 }) - (1 << 15); + slope = Unpack(data, PackLocation { 16, 7 }); + shape = Unpack(data, PackLocation { 23, 7 }); + fold = Unpack(data, PackLocation { 30, 7 }); + out = Unpack(data, PackLocation { 37, 4 }); + cv = Unpack(data, PackLocation { 41, 4 }); + } protected: - void SetHelp() { + void SetHelp() { // TODO: update help[HEMISPHERE_HELP_DIGITALS] = ""; help[HEMISPHERE_HELP_CVS] = "1=V/Oct 2=Slope"; help[HEMISPHERE_HELP_OUTS] = "A=OutA B=OutB"; @@ -142,16 +181,24 @@ private: EOA, EOR, }; + const char* out_labels[4] = {"Un", "Bi", "Hi", "Lo"}; - int cursor; - int16_t pitch; + enum CV { + FREQ, + SLOPE, + SHAPE, + FOLD, + }; + const char* cv_labels[4] = {"Hz", "Sl", "Sh", "Fo"}; + + int cursor = 0; + int16_t pitch = -3 * 12 * 128; int slope = 64; - int att_shape = 64; - int dec_shape = 64; + int shape = 48; // triangle int fold = 0; - const char* out_labels[4] = {"Uni", "Bi", "High", "Low"}; - uint8_t out_a = UNIPOLAR; - uint8_t out_b = BIPOLAR; + + uint8_t out = 0b0001; // Unipolar on A, bipolar on B + uint8_t cv = 0b0001; // Freq on 1, shape on 2 TidesLiteSample disp_sample; TidesLiteSample sample; bool eoa_reached = false; @@ -160,23 +207,13 @@ private: uint32_t phase; - void output(int ch, Output out) { - switch (out) { - case UNIPOLAR: - Out(ch, Proportion(sample.unipolar, 65535, HEMISPHERE_MAX_CV)); - break; - case BIPOLAR: - Out(ch, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); - break; - case EOA: - GateOut(ch, eoa_reached); - break; - case EOR: - GateOut(ch, !eoa_reached); - break; - } + Output output(int ch) { + return (Output) ((out >> ((1 - ch) * 2)) & 0b11); } + CV cv_type(int ch) { + return (CV) ((out >> ((1 - ch) * 2)) & 0b11); + } void gfxPrintFreq(int16_t pitch) { uint32_t num = ComputePhaseIncrement(pitch); @@ -213,14 +250,6 @@ private: } else { gfxPrint("Hz"); } - // int oom = 1; - // int i = -3; - // while (oom * phase_inc < 1000 * (uint64_t) 0xffffffff) { - // oom *= 10; - // i++; - // } - // gfxCursor(x, y); - // gfxPrint(x, y, phase_inc * oom / 0xffffffff); } }; //////////////////////////////////////////////////////////////////////////////// diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 35e0c9042..f8b3afbeb 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -17,9 +17,11 @@ board = teensy31 board_build.f_cpu = 120000000 lib_deps = EEPROM build_flags = - -DUSB_MIDI -UUSB_SERIAL -DTEENSY_OPT_SMALLEST_CODE -; -DTEENSY_OPT_FASTEST + -DUSB_MIDI +build_src_filter = + +<*> + - upload_protocol = teensy-gui diff --git a/software/o_c_REV/resources/tideslite.cpp b/software/o_c_REV/resources/tideslite.cpp new file mode 100644 index 000000000..cf4a398e6 --- /dev/null +++ b/software/o_c_REV/resources/tideslite.cpp @@ -0,0 +1,24 @@ +#include "tideslite.h" +#include +#include + +int main(int argc, char **argv) { + TidesLiteSample sample; + uint64_t samples = atol(argv[1]); + int16_t pitch = atoi(argv[2]); + uint32_t phase_inc = ComputePhaseIncrement(pitch); + uint16_t slope = atoi(argv[3]); + uint16_t shape = atoi(argv[4]); + int16_t fold = atoi(argv[5]); + + uint32_t phase = 0; + printf("unipolar,bipolar,high,low\n"); + for (uint64_t i = 0; i < samples; i++) { + ProcessSample(slope, shape, fold, phase, sample); + printf("%d,%d,%d,%d\n", sample.unipolar, sample.bipolar, + (sample.flags & FLAG_EOA) ? 65535 : 0, + (sample.flags & FLAG_EOR) ? 65535 : 0); + phase += phase_inc; + } + return 0; +} \ No newline at end of file diff --git a/software/o_c_REV/resources/tideslite.h b/software/o_c_REV/resources/tideslite.h index af5c888db..99f8bcf30 100644 --- a/software/o_c_REV/resources/tideslite.h +++ b/software/o_c_REV/resources/tideslite.h @@ -122,45 +122,6 @@ const int16_t wav_unipolar_fold[] = { 32451, 32476, 32501, 32526, 32551, 32576, 32601, 32625, 32649, 32673, 32697, 32720, 32744, 32767, 32767}; -/* -size_t lower_bound(uint32_t* first, uint32_t* last, const uint32_t target)) { - size_t len = last - first; - - if (first == last) { - return &first; - } - uint32_t* mid = first + (last - first) / 2; - if (mid == &target) { - return mid; - } - if (target < mid) -} - -int16_t ComputePitch(uint32_t phase_increment) { - uint32_t first = lut_increments[0]; - uint32_t last = lut_increments[LUT_INCREMENTS_SIZE - 2]; - int16_t pitch = 0; - - if (phase_increment == 0) { - phase_increment = 1; - } - - while (phase_increment > last) { - phase_increment >>= 1; - pitch += kOctave; - } - while (phase_increment < first) { - phase_increment <<= 1; - pitch -= kOctave; - } - pitch += (std::lower_bound( - lut_increments, - lut_increments + LUT_INCREMENTS_SIZE, - phase_increment) - lut_increments) << 4; - return pitch; -} -*/ - inline int16_t Interpolate1022(const int16_t *table, uint32_t phase) { int32_t a = table[phase >> 22]; int32_t b = table[(phase >> 22) + 1]; @@ -192,30 +153,90 @@ uint32_t ComputePhaseIncrement(int16_t pitch) { : phase_increment >> -num_shifts; } -const uint64_t max_phase = 0xffffffff; -const uint64_t max_16 = 0xffff; -uint32_t WarpPhase(uint32_t phase, uint16_t curve) { - int32_t c = curve - 32767; +int16_t ComputePitch(uint32_t phase_increment) { + uint32_t first = lut_increments[0]; + uint32_t last = lut_increments[LUT_INCREMENTS_SIZE - 2]; + int16_t pitch = 0; + + if (phase_increment == 0) { + phase_increment = 1; + } + + while (phase_increment > last) { + phase_increment >>= 1; + pitch += kOctave; + } + + while (phase_increment < first) { + phase_increment <<= 1; + pitch -= kOctave; + } + + int i = 0; + int j = LUT_INCREMENTS_SIZE - 1; + while (j - i > 1) { + int k = i + (j - i) / 2; + uint32_t mid = lut_increments[k]; + if (phase_increment < mid) { + j = k; + } else { + i = k; + } + } + pitch += (i << 4); + return pitch; +} + + +const uint32_t max_16 = 0xffff; +const uint32_t max_8 = 0xff; + +uint32_t WarpPhase(uint16_t phase, uint16_t curve) { + int32_t c = (curve - 32767) >> 8; bool flip = c < 0; if (flip) - phase = max_phase - phase; - uint64_t a = (uint64_t)128 * c * c; - phase = (max_16 + a / max_16) * phase / - ((max_phase + a / max_16 * phase / max_16) / max_16); + phase = max_16 - phase; + uint32_t a = 128 * c * c; + phase = (max_8 + a / max_8) * phase / + ((max_16 + a / max_8 * phase / max_8) / max_8); if (flip) - phase = max_phase - phase; + phase = max_16 - phase; return phase; } uint16_t ShapePhase(uint16_t phase, uint16_t attack_curve, uint16_t decay_curve) { return phase < (1UL << 15) - ? WarpPhase(phase << 17, attack_curve) >> 16 - : WarpPhase((0xffff - phase) << 17, decay_curve) >> 16; + ? WarpPhase(phase << 1, attack_curve) + : WarpPhase((0xffff - phase) << 1, decay_curve); +} + +uint16_t ShapePhase(uint16_t phase, uint16_t shape) { + uint32_t att = 0; + uint32_t dec = 0; + if (shape < 1 * 65536 / 4) { + shape *= 4; + att = 0; + dec = 65535 - shape; + } else if (shape < 2 * 65536 / 4) { + // shape between -24576 and 81 + shape = (shape - 65536 / 4) * 4; + att = shape; + dec = shape; + } else if (shape < 3 * 65536 / 4) { + shape = (shape - 2 * 65536 / 4) * 4; + att = 65535; + dec = 65535 - shape; + } else { + shape = (shape - 3 * 65536 / 4) * 4; + att = 65535 - shape; + dec = shape; + } + return ShapePhase(phase, att, dec); } -void ProcessSample(uint16_t slope, uint16_t att_shape, uint16_t dec_shape, - int16_t fold, uint32_t phase, TidesLiteSample &sample) { +void ProcessSample(uint16_t slope, uint16_t shape, int16_t fold, uint32_t phase, + TidesLiteSample &sample) { uint32_t eoa = slope << 16; // uint32_t skewed_phase = phase; slope = slope ? slope : 1; @@ -230,8 +251,8 @@ void ProcessSample(uint16_t slope, uint16_t att_shape, uint16_t dec_shape, skewed_phase += 1L << 31; } - sample.unipolar = ShapePhase(skewed_phase >> 16, att_shape, dec_shape); - sample.bipolar = ShapePhase(skewed_phase >> 15, att_shape, dec_shape) >> 1; + sample.unipolar = ShapePhase(skewed_phase >> 16, shape); + sample.bipolar = ShapePhase(skewed_phase >> 15, shape) >> 1; if (skewed_phase >= (1UL << 31)) { sample.bipolar = -sample.bipolar; } From b465457ea2cad1402c966a48190f1959991e51e2 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 29 Dec 2022 00:04:36 -0500 Subject: [PATCH 100/417] EbbAndLFO: modal editing, UI tweaks, help text --- software/o_c_REV/HEM_EbbAndLfo.ino | 102 ++++++++++++++++------------- 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 00de28856..df49b97d7 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -16,8 +16,9 @@ public: pitch = ComputePitch(new_freq); phase = 0; } - int slope_cv = In(1) * 32768 / HEMISPHERE_3V_CV; - int s = constrain(slope * 65535 / 127 + slope_cv, 0, 65535); + slope_mod = slope + Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 127); + slope_mod = constrain(slope_mod, 0, 127); + int s = constrain(slope_mod * 65535 / 127, 0, 65535); ProcessSample(s, shape * 65535 / 127, fold * 32767 / 127, phase, sample); if (phase < phase_increment) { eoa_reached = false; @@ -54,7 +55,7 @@ public: int bottom = 32 + (h + 1) * ch; int last = bottom; for (int i = 0; i < 64; i++) { - ProcessSample(slope * 65535 / 127, shape * 65535 / 127, + ProcessSample(slope_mod * 65535 / 127, shape * 65535 / 127, fold * 32767 / 127, 0xffffffff / 64 * i, disp_sample); int next = 0; switch (output(ch)) { @@ -82,67 +83,70 @@ public: switch (cursor) { case 0: // gfxPrint(0, 55, "Frq:"); - gfxPos(0, 55); + gfxPos(0, 56); gfxPrintFreq(pitch); break; case 1: - gfxPrint(0, 55, "Slope: "); + gfxPrint(0, 56, "Slope: "); gfxPrint(slope); break; case 2: - gfxPrint(0, 55, "Shape: "); + gfxPrint(0, 56, "Shape: "); gfxPrint(shape); break; case 3: - gfxPrint(0, 55, "Fold: "); + gfxPrint(0, 56, "Fold: "); gfxPrint(fold); break; case 4: - gfxPrint(0, 55, hemisphere == 0 ? "A: " : "C: "); + gfxPrint(0, 56, hemisphere == 0 ? "A:" : "C:"); gfxPrint(out_labels[output(0)]); - gfxPrint(hemisphere == 0 ? " B: " : " D: "); + gfxPrint(hemisphere == 0 ? " B:" : " D:"); gfxPrint(out_labels[output(1)]); break; } + if (isEditing) gfxInvert(0, 55, 64, 9); } - void OnButtonPress() { // TODO: modal editing - cursor++; - cursor %= 5; + void OnButtonPress() { + isEditing = !isEditing; } void OnEncoderMove(int direction) { - switch (cursor) { - case 0: { - uint32_t old_pi = ComputePhaseIncrement(pitch); - pitch += (knob_accel >> 8) * direction; - while (ComputePhaseIncrement(pitch) == old_pi) { - pitch += direction; - } - break; - } - case 1: { - // slope += (knob_accel >> 4) * direction; - slope = constrain(slope + direction, 0, 127); - break; - } - case 2: { - shape += direction; - while (shape < 0) shape += 128; - while (shape > 127) shape -= 128; - break; - } - case 3: { - fold = constrain(fold + direction, 0, 127); - break; - } - case 4: { - out += direction; - out %= 0b10000; - } + if (!isEditing) cursor = constrain(cursor + direction, 0, 4); + else { + switch (cursor) { + case 0: { + uint32_t old_pi = ComputePhaseIncrement(pitch); + pitch += (knob_accel >> 8) * direction; + while (ComputePhaseIncrement(pitch) == old_pi) { + pitch += direction; + } + break; + } + case 1: { + // slope += (knob_accel >> 4) * direction; + slope = constrain(slope + direction, 0, 127); + break; + } + case 2: { + shape += direction; + while (shape < 0) shape += 128; + while (shape > 127) shape -= 128; + break; + } + case 3: { + fold = constrain(fold + direction, 0, 127); + break; + } + case 4: { + out += direction; + out %= 0b10000; + } + } + if (knob_accel < (1 << 13)) + knob_accel <<= 1; } - if (knob_accel < (1 << 13)) - knob_accel <<= 1; } uint64_t OnDataRequest() { @@ -166,11 +170,13 @@ public: } protected: - void SetHelp() { // TODO: update - help[HEMISPHERE_HELP_DIGITALS] = ""; - help[HEMISPHERE_HELP_CVS] = "1=V/Oct 2=Slope"; - help[HEMISPHERE_HELP_OUTS] = "A=OutA B=OutB"; - help[HEMISPHERE_HELP_ENCODER] = "P=param T=adjust"; + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; + help[HEMISPHERE_HELP_CVS] = "1=V/Oct 2=Slope"; + help[HEMISPHERE_HELP_OUTS] = "A=OutA B=OutB"; + help[HEMISPHERE_HELP_ENCODER] = "Select/Edit params"; + // "------------------" <-- Size Guide } private: @@ -192,8 +198,10 @@ private: const char* cv_labels[4] = {"Hz", "Sl", "Sh", "Fo"}; int cursor = 0; + bool isEditing = false; int16_t pitch = -3 * 12 * 128; int slope = 64; + int slope_mod = 64; // actual value after CV mod int shape = 48; // triangle int fold = 0; From 0bf3d62db03444d7a445b819b80ea04ba8594eec Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 29 Dec 2022 01:21:34 -0500 Subject: [PATCH 101/417] DualTM: update Help text --- software/o_c_REV/HEM_TM2.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 8796b4228..aeaeea3b0 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -269,8 +269,8 @@ protected: // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=p Gate"; help[HEMISPHERE_HELP_CVS] = "Assignable"; - help[HEMISPHERE_HELP_OUTS] = "A=Quant5-bit B=CV2"; - help[HEMISPHERE_HELP_ENCODER] = "Len/Prob/Scl/Range"; + help[HEMISPHERE_HELP_OUTS] = "Assignable"; + help[HEMISPHERE_HELP_ENCODER] = "Select/Push 2 Edit"; // "------------------" <-- Size Guide } From d604316ea6e1ed9ed2f886f028397a72b36d4d69 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 29 Dec 2022 03:16:12 -0500 Subject: [PATCH 102/417] Scope Improvements - modal editing of params - make X-Y mode fill the space - offset inputs, maximizes resolution for non-VOR units --- software/o_c_REV/HEM_Scope.ino | 52 +++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/software/o_c_REV/HEM_Scope.ino b/software/o_c_REV/HEM_Scope.ino index 28faf2566..ace8272b6 100644 --- a/software/o_c_REV/HEM_Scope.ino +++ b/software/o_c_REV/HEM_Scope.ino @@ -63,8 +63,8 @@ public: sample_num = LoopInt(++sample_num, 63); for (int n = 0; n < 2; n++) { - int sample = Proportion(In(n), HEMISPHERE_MAX_CV, 128); - sample = constrain(sample, -128, 127) + 127; + int sample = Proportion(In(n) + HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV + HEMISPHERE_3V_CV, 255); + sample = constrain(sample, 0, 255); snapshot[n][sample_num] = (uint8_t)sample; } } @@ -75,11 +75,11 @@ public: void View() { gfxHeader(applet_name()); - DrawBPM(); if(current_display == 4) { DrawInput(-1); } else { + DrawBPM(); DrawInput((current_display & 2) == 2); PrintInput(); } @@ -91,21 +91,25 @@ public: } void OnButtonPress() { - if (OC::CORE::ticks - last_encoder_move < SCOPE_CURRENT_SETTING_TIMEOUT) { - current_setting = LoopInt(++current_setting, 2); - } - last_encoder_move = OC::CORE::ticks; + if (current_setting == 2) // FREEZE button + freeze = !freeze; + else if (OC::CORE::ticks - last_encoder_move < SCOPE_CURRENT_SETTING_TIMEOUT) // params visible? toggle edit + isEditing = !isEditing; + else // show params + last_encoder_move = OC::CORE::ticks; } void OnEncoderMove(int direction) { - if(current_setting == 0) { - if (sample_ticks < 32) sample_ticks += direction; - else sample_ticks += direction * 10; - sample_ticks = constrain(sample_ticks, 2, 64000); - } else if(current_setting == 1) { - current_display = LoopInt(++current_display, 4); - } else if(current_setting == 2) { - freeze = direction > 0; + if (!isEditing) { // switch setting + current_setting = constrain(current_setting + direction, 0, 2); + } else { // edit + if(current_setting == 0) { + if (sample_ticks < 32) sample_ticks += direction; + else sample_ticks += direction * 10; + sample_ticks = constrain(sample_ticks, 2, 64000); + } else if(current_setting == 1) { + current_display = constrain(current_display + direction, 0, 4); + } } last_encoder_move = OC::CORE::ticks; } @@ -144,6 +148,7 @@ private: // Scope int current_display; int current_setting; + bool isEditing = false; uint8_t snapshot[2][64]; int sample_ticks; // Ticks between samples int sample_countdown; // Last time a sample was taken @@ -181,6 +186,8 @@ private: gfxPrint(1, 26, "Freeze "); gfxPrint(freeze ? "ON" : "OFF"); } + + if (isEditing) gfxInvert(1, 25, 31, 9); } } @@ -192,14 +199,21 @@ private: } void DrawInput(int input) { - int max = input < 0 ? 40 : 28; + int max = input < 0 ? 54 : 28; for (int s = 0; s < 64; s++) { int n = s + sample_num; + int px, py; if (n > 63) n -= 64; - int px = input < 0 ? Proportion(snapshot[0][n], 255, max) + 12 : n; - int py = Proportion(snapshot[input < 0 ? 1 : input][n], 255, max); - gfxPixel(px, (max - py) + 24); + if (input < 0) { // X-Y mode + px = Proportion(snapshot[0][n], 255, 63); + py = Proportion(snapshot[1][n], 255, max); + gfxPixel(px, constrain((max - py) + 10, 0, 63)); + } else { + px = n; + py = Proportion(snapshot[input][n], 255, max); + gfxPixel(px, (max - py) + 24); + } } } }; From 9af6fb391f895efef98e7dde059c021c727b543f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 30 Dec 2022 03:07:07 -0500 Subject: [PATCH 103/417] DrumMap: modal editing, UI tweaks --- software/o_c_REV/HEM_DrumMap.ino | 124 ++++++++++++++++++------------- 1 file changed, 72 insertions(+), 52 deletions(-) diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index 48d8738df..719f766f2 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -128,46 +128,64 @@ public: } void OnButtonPress() { - if (++cursor > 7) cursor = 0; - if (mode[1] > 2 && cursor == 3) cursor = 4; + isEditing = !isEditing; } void OnEncoderMove(int direction) { - int accel = knob_accel >> 8; - // modes - if (cursor == 0) { - mode[0] += direction; - if (mode[0] > 2) mode[0] = 0; - if (mode[0] < 0) mode[0] = 2; - } - if (cursor == 1) { - mode[1] += direction; - if (mode[1] > 3) mode[1] = 0; - if (mode[1] < 0) mode[1] = 3; - } - // fill - if (cursor == 2) fill[0] = constrain(fill[0] + (direction * accel), 0, 255); - if (cursor == 3) fill[1] = constrain(fill[1] + (direction * accel), 0, 255); - // x/y - if (cursor == 4) x = constrain(x + (direction * accel), 0, 255); - if (cursor == 5) y = constrain(y + (direction * accel), 0, 255); - // chaos - if (cursor == 6) chaos = constrain(chaos + (direction * accel), 0, 255); - // cv assign - if (cursor == 7) { - cv_mode += direction; - if (cv_mode > 2) cv_mode = 0; - if (cv_mode < 0) cv_mode = 2; - } + if (!isEditing) { + cursor += direction; + if (mode[1] > 2 && cursor == 3) cursor += direction; + cursor = constrain(cursor, 0, 7); + ResetCursor(); + } else { + int accel = knob_accel >> 8; + // modes + switch (cursor) { + case 0: + mode[0] += direction; + if (mode[0] > 2) mode[0] = 0; + if (mode[0] < 0) mode[0] = 2; + break; + case 1: + mode[1] += direction; + if (mode[1] > 3) mode[1] = 0; + if (mode[1] < 0) mode[1] = 3; + break; + // fill + case 2: + fill[0] = constrain(fill[0] + (direction * accel), 0, 255); + break; + case 3: + fill[1] = constrain(fill[1] + (direction * accel), 0, 255); + break; + // x/y + case 4: + x = constrain(x + (direction * accel), 0, 255); + break; + case 5: + y = constrain(y + (direction * accel), 0, 255); + break; + // chaos + case 6: + chaos = constrain(chaos + (direction * accel), 0, 255); + break; + // cv assign + case 7: + cv_mode += direction; + if (cv_mode > 2) cv_mode = 0; + if (cv_mode < 0) cv_mode = 2; + break; + } - // knob acceleration and value display for slider params - if (cursor >= 2 && cursor <= 6 && knob_accel < 2049) { - if (knob_accel < 300) { - knob_accel = knob_accel << 1; - } - knob_accel = knob_accel << 2; - value_animation = HEM_DRUMMAP_VALUE_ANIMATION_TICKS; - } + // knob acceleration and value display for slider params + if (cursor >= 2 && cursor <= 6 && knob_accel < 2049) { + if (knob_accel < 300) { + knob_accel = knob_accel << 1; + } + knob_accel = knob_accel << 2; + value_animation = HEM_DRUMMAP_VALUE_ANIMATION_TICKS; + } + } // isEditing } uint64_t OnDataRequest() { @@ -208,7 +226,8 @@ private: const uint8_t *MODE_ICONS[3] = {BD_ICON,SN_ICON,HH_ICON}; const char *CV_MODE_NAMES[3] = {"FILL A/B", "X/Y", "FA/CHAOS"}; const int *VALUE_MAP[5] = {&fill[0], &fill[1], &x, &y, &chaos}; - uint8_t cursor = 0; + int cursor = 0; + bool isEditing = false; uint8_t step; uint8_t randomness[3] = {0, 0, 0}; int pulse_animation[2] = {0, 0}; @@ -250,15 +269,15 @@ private: void DrawInterface() { // output selection gfxPrint(1,15,"A:"); - gfxIcon(14,14,MODE_ICONS[mode[0]]); + gfxIcon(15,15,MODE_ICONS[mode[0]]); gfxPrint(32,15,"B:"); if (mode[1] == 3) { // accent - gfxIcon(45,14,MODE_ICONS[mode[0]]); + gfxIcon(46,15,MODE_ICONS[mode[0]]); gfxPrint(53,15,">"); } else { // standard - gfxIcon(45,14,MODE_ICONS[mode[1]]); + gfxIcon(46,15,MODE_ICONS[mode[1]]); } // pulse animation per channel ForEachChannel(ch){ @@ -298,22 +317,15 @@ private: gfxPrint(1,45,"CHAOS"); DrawKnobAt(32,45,28,_chaos,cursor == 6); - // cv input assignment - gfxIcon(1,57,CV_ICON); - gfxPrint(10,55,CV_MODE_NAMES[cv_mode]); - // step count in header gfxPrint((step < 9 ? 49 : 43),2,step+1); // cursor for non-knobs - if (cursor == 0) gfxCursor(14,23,16); // Part A - if (cursor == 1) gfxCursor(45,23,16); // Part B - if (cursor == 7) gfxCursor(10,63,50); // CV Assign + if (cursor <= 1) isEditing ? gfxInvert(14+cursor*31,14,16,9) + : gfxCursor(14+cursor*31,23,16); // Part A / B // display value for knobs if (value_animation > 0 && cursor >= 2 && cursor <= 6) { - gfxRect(1, 54, 60, 10); - gfxInvert(1, 54, 60, 10); int val = *VALUE_MAP[cursor-2]; int xPos = 27; if (val > 99) { @@ -322,15 +334,23 @@ private: xPos = 24; } gfxPrint(xPos, 55, val); - gfxInvert(1, 54, 60, 10); + gfxInvert(1, 54, 63, 10); + } else { + // cv input assignment + gfxIcon(1,57,CV_ICON); + gfxPrint(10,55,CV_MODE_NAMES[cv_mode]); + if (cursor == 7) isEditing ? gfxInvert(10,54,50,9) + : gfxCursor(10,63,50); // CV Assign } + } void DrawKnobAt(byte x, byte y, byte len, byte value, bool is_cursor) { - byte w = Proportion(value, 255, len); + byte w = Proportion(value, 255, len-1); // minus 1 because width is 2 byte p = is_cursor ? 1 : 3; gfxDottedLine(x, y + 4, x + len, y + 4, p); - gfxRect(x + w, y, 2, 7); + gfxRect(x + w, y, 2, 8); + if (is_cursor && isEditing) gfxInvert(x, y, len+1, 8); } void Reset() { From 83cf7a9fd7520287e8ede7ac6b51544d2bb6f313 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 30 Dec 2022 03:07:54 -0500 Subject: [PATCH 104/417] BugCrack: modal editing, UI tweaks --- software/o_c_REV/HEM_BugCrack.ino | 114 ++++++++++++++++-------------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/software/o_c_REV/HEM_BugCrack.ino b/software/o_c_REV/HEM_BugCrack.ino index 7e92c6949..dadcae0f2 100644 --- a/software/o_c_REV/HEM_BugCrack.ino +++ b/software/o_c_REV/HEM_BugCrack.ino @@ -224,45 +224,49 @@ public: } void OnButtonPress() { - if (++cursor > 9) cursor = 0; + isEditing = !isEditing; } void OnEncoderMove(int direction) { - // Kick drum - if (cursor == 0) { - tone_kick = constrain(tone_kick + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 1) { - decay_kick = constrain(decay_kick + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 2) { - punch = constrain(punch + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 3) { - decay_punch = constrain(decay_punch + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 4) { - cv_mode_kick = constrain(cv_mode_kick + direction, 0, 4); - } - - // Snare drum - if (cursor == 5) { - tone_snare = constrain(tone_snare + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 6) { - decay_snare = constrain(decay_snare + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 7) { - snap = constrain(snap + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 8) { - blend_snare = constrain(blend_snare + direction, 0, BNC_MAX_PARAM); - } - if (cursor == 9) { - cv_mode_snare = constrain(cv_mode_snare + direction, 0, 4); - } + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 9); + } else { + switch (cursor) { + // Kick drum + case 0: + tone_kick = constrain(tone_kick + direction, 0, BNC_MAX_PARAM); + break; + case 1: + decay_kick = constrain(decay_kick + direction, 0, BNC_MAX_PARAM); + break; + case 2: + punch = constrain(punch + direction, 0, BNC_MAX_PARAM); + break; + case 3: + decay_punch = constrain(decay_punch + direction, 0, BNC_MAX_PARAM); + break; + case 4: + cv_mode_kick = constrain(cv_mode_kick + direction, 0, 4); + break; - ResetCursor(); + // Snare drum + case 5: + tone_snare = constrain(tone_snare + direction, 0, BNC_MAX_PARAM); + break; + case 6: + decay_snare = constrain(decay_snare + direction, 0, BNC_MAX_PARAM); + break; + case 7: + snap = constrain(snap + direction, 0, BNC_MAX_PARAM); + break; + case 8: + blend_snare = constrain(blend_snare + direction, 0, BNC_MAX_PARAM); + break; + case 9: + cv_mode_snare = constrain(cv_mode_snare + direction, 0, 4); + break; + } + } // isEditing } uint64_t OnDataRequest() { @@ -310,6 +314,7 @@ protected: private: int cursor = 0; + bool isEditing = false; VectorOscillator kick; VectorOscillator env_kick; VectorOscillator env_punch; @@ -358,41 +363,40 @@ private: DrawDrumBody(1, _tone_kick, _decay_kick, _punch, _decay_punch, 0); DrawDrumBody(32, _tone_snare, _decay_snare, _snap, _blend_snare, 1); - // CV modes - gfxIcon(1, 57, CV_ICON); - gfxPrint(10, 55, CV_MODE_NAMES_BD[cv_mode_kick]); - gfxIcon(32, 57, CV_ICON); - gfxPrint(41, 55, CV_MODE_NAMES_SN[cv_mode_snare]); - switch (cursor) { // Kick drum case 0: - gfxPrint(1, 45, Proportion(_tone_kick, BNC_MAX_PARAM, 30) + 30); - gfxIcon(22, 44, HERTZ_ICON); + gfxPrint(1, 55, Proportion(_tone_kick, BNC_MAX_PARAM, 30) + 30); + gfxIcon(22, 54, HERTZ_ICON); break; case 1: - gfxPrint(1, 45, "decay"); break; + gfxPrint(1, 55, "decay"); break; case 2: - gfxPrint(1, 45, "punch"); break; + gfxPrint(1, 55, "punch"); break; case 3: - gfxPrint(1, 45, "drop"); break; - case 4: - gfxInvert(1, 54, 30, 9); break; + gfxPrint(1, 55, "drop"); break; + case 4: // CV modes + gfxIcon(1, 57, CV_ICON); + gfxPrint(10, 55, CV_MODE_NAMES_BD[cv_mode_kick]); + break; // Snare drum case 5: - gfxPrint(35, 45, Proportion(_tone_snare, BNC_MAX_PARAM, 600) + 100); - gfxIcon(54, 44, HERTZ_ICON); + gfxPrint(35, 55, Proportion(_tone_snare, BNC_MAX_PARAM, 600) + 100); + gfxIcon(54, 54, HERTZ_ICON); break; case 6: - gfxPrint(32, 45, "decay"); break; + gfxPrint(32, 55, "decay"); break; case 7: - gfxPrint(32, 45, "snap"); break; + gfxPrint(32, 55, "snap"); break; case 8: - gfxPrint(32, 45, "blend"); break; - case 9: - gfxInvert(32, 54, 30, 9); break; + gfxPrint(32, 55, "blend"); break; + case 9: // CV modes + gfxIcon(32, 57, CV_ICON); + gfxPrint(41, 55, CV_MODE_NAMES_SN[cv_mode_snare]); + break; } + if (isEditing) gfxInvert(1 + (cursor<5?0:31), 54, 31, 9); // Level indicators ForEachChannel(ch) From facdbfd3740596c7c8d907e3ebd8da362f7f56e7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 30 Dec 2022 03:43:56 -0500 Subject: [PATCH 105/417] ClockSetup: UI Tweaks Play/Stop and Forwarding are one-click toggles, Tempo and Multipliers are editable --- software/o_c_REV/HEM_ClockSetup.ino | 82 +++++++++++++---------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index af3968e54..7ba6182ac 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -49,36 +49,24 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + if (cursor == 0) PlayStop(); + else if (cursor == 1) clock_m->ToggleForwarding(); + else isEditing = !isEditing; } void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, 3); + cursor = constrain(cursor + direction, 0, 4); ResetCursor(); } else { switch (cursor) { - case 0: // Source - if (direction > 0) // right turn toggles Forwarding - clock_m->ToggleForwarding(); - else if (clock_m->IsRunning()) // left turn toggles clock - { - stop_q = 1; - clock_m->Stop(); - } - else { - start_q = 1; - clock_m->Start(); - } - break; - - case 1: // Set tempo + case 2: // Set tempo clock_m->SetTempoBPM(clock_m->GetTempo() + direction); break; - case 2: // Set multiplier - case 3: - clock_m->SetMultiply(clock_m->GetMultiply(cursor-2) + direction, cursor-2); + case 3: // Set multiplier + case 4: + clock_m->SetMultiply(clock_m->GetMultiply(cursor-3) + direction, cursor-3); break; } } @@ -115,12 +103,22 @@ protected: } private: - char cursor; // 0=Source, 1=Tempo, 2=Multiply + char cursor; // 0=Play/Stop, 1=Forwarding, 2=Tempo, 3,4=Multiply bool isEditing = false; bool start_q; bool stop_q; ClockManager *clock_m = clock_m->get(); + void PlayStop() { + if (clock_m->IsRunning()) { + stop_q = 1; + clock_m->Stop(); + } else { + start_q = 1; + clock_m->Start(); + } + } + void DrawInterface() { // Header: This is sort of a faux applet, so its header // needs to extend across the screen @@ -134,46 +132,40 @@ private: // Clock Source gfxIcon(1, 15, CLOCK_ICON); if (clock_m->IsRunning()) { - gfxIcon(10, 15, PLAY_ICON); + gfxIcon(12, 15, PLAY_ICON); } else if (clock_m->IsPaused()) { - gfxIcon(10, 15, PAUSE_ICON); + gfxIcon(12, 15, PAUSE_ICON); } else { - gfxIcon(10, 15, STOP_ICON); + gfxIcon(12, 15, STOP_ICON); } - gfxPrint(20, 15, ""); - if (clock_m->IsForwarded()) - gfxIcon(76, 15, LINK_ICON); + gfxPrint(26, 15, "Fwd "); + gfxIcon(50, 15, clock_m->IsForwarded() ? CHECK_ON_ICON : CHECK_OFF_ICON); // Tempo - gfxIcon(1, 25, NOTE4_ICON); - gfxPrint(9, 25, "= "); + gfxIcon(1, 26, NOTE4_ICON); + gfxPrint(9, 26, "= "); gfxPrint(pad(100, clock_m->GetTempo()), clock_m->GetTempo()); gfxPrint(" BPM"); // Multiply - gfxPrint(1, 35, "x"); + gfxPrint(1, 37, "x"); gfxPrint(clock_m->GetMultiply(0)); // secondary multiplier when forwarding internal clock - gfxPrint(33, 35, "x"); + gfxPrint(33, 37, "x"); gfxPrint(clock_m->GetMultiply(1)); switch (cursor) { - case 0: - gfxCursor(20, 23, 54); - if (isEditing) gfxInvert(20, 14, 54, 9); - break; - case 1: - gfxCursor(23, 33, 18); - if (isEditing) gfxInvert(23, 24, 18, 9); - break; - case 2: - gfxCursor(8, 43, 12); - if (isEditing) gfxInvert(8, 34, 12, 9); + case 0: gfxFrame(11, 14, 10, 10); break; // Play/Stop + case 1: gfxFrame(49, 14, 10, 10); break; // Forwarding + + case 2: // BPM + isEditing ? gfxInvert(22, 25, 18, 9) : gfxCursor(22, 34, 18); break; - case 3: - gfxCursor(40, 43, 12); - if (isEditing) gfxInvert(40, 34, 12, 9); + + case 3: // Multipliers + case 4: + isEditing ? gfxInvert(8 + 32*(cursor-3), 36, 12, 9) : gfxCursor(8 + 32*(cursor-3), 45, 12); break; } } From 27ddb0b92b2fe4606b24ccc74b754aacb3217b83 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 30 Dec 2022 04:19:12 -0500 Subject: [PATCH 106/417] Version bump v1.4.8.2 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 91b42825c..feea4301d 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.8" +#define OC_VERSION "v1.4.8.2" #define OC_VERSION_URL "github.com/djphazer" #endif From 1fd388b5a9087278c31da0bbde7cf2b9cf2c436f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 31 Dec 2022 18:38:46 -0500 Subject: [PATCH 107/417] CVRec: use ADC lag to defer recording --- software/o_c_REV/HEM_CVRecV2.ino | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index 49e2269da..1a0580c03 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -38,16 +38,27 @@ public: void Controller() { if (Clock(1)) reset = true; - if (reset) { step = start; if (punch_out) punch_out = end - start; } + // check for deferred recording + if (EndOfADCLag() && punch_out && mode) { + ForEachChannel(ch) + { + if (mode & (0x01 << ch)) { // Record this channel + cv[ch][step] = In(ch); + } + } + if (!reset) { + if (--punch_out == 0) mode = 0; + } + } + if (Clock(0) ) { // sequence advance if (!reset) step++; if (step > end || step < start) step = start; - bool rec = 0; ForEachChannel(ch) { signal[ch] = int2simfloat(cv[ch][step]); @@ -55,17 +66,10 @@ public: if (next_step > end) next_step = start; if (smooth) rise[ch] = (int2simfloat(cv[ch][next_step]) - int2simfloat(cv[ch][step])) / ClockCycleTicks(0); else rise[ch] = 0; - - if (mode & (0x01 << ch)) { // Record this channel - if (punch_out > 0) { - rec = 1; - cv[ch][step] = In(ch); - } - } - } - if (rec && !reset) { - if (--punch_out == 0) mode = 0; } + + // defer recording + StartADCLag(); reset = false; } From 7b3b16f3debf9dfe00f784f7043aff58484e88cd Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 31 Dec 2022 20:38:14 -0500 Subject: [PATCH 108/417] DualTM: better slew/smoothing algo --- software/o_c_REV/HEM_TM2.ino | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index aeaeea3b0..1532458e0 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -38,7 +38,7 @@ #define TM2_MIN_LENGTH 2 #define TM2_MAX_LENGTH 32 -#define TM2_SMOOTHING 128 +#define TM2_SMOOTHING 4 class DualTM : public HemisphereApplet { public: @@ -305,9 +305,13 @@ private: // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 int cvmode[2] = {0, 5}; - int slew(int value_, int value = 0) { - value_ = (value_ * (smooth_mod*TM2_SMOOTHING - 1) + value) / (smooth_mod*TM2_SMOOTHING); - return value_; + int slew(int old_val, const int new_val = 0) { + // more smoothing causes more ticks to be skipped + if (OC::CORE::ticks % smooth_mod) return old_val; + + int s = smooth_mod * TM2_SMOOTHING; + old_val = (old_val * (s - 1) + new_val) / s; + return old_val; } void DrawSelector() { From c209bf5ceed11079bf1b375d4a332a0d574b5c0e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 31 Dec 2022 20:39:23 -0500 Subject: [PATCH 109/417] LoFiPCM: don't initialize buffer on Start() prevents crash when scrolling thru applets quickly --- software/o_c_REV/HEM_LoFiPCM.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index b1243e7a2..58edf3621 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -36,7 +36,8 @@ public: void Start() { countdown = HEM_LOFI_PCM_SPEED; - for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 127; + // this might take too long, which causes crashes. It's not crucial. + //for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 127; cursor = 1; //for gui } @@ -134,6 +135,7 @@ protected: private: const int length = HEM_LOFI_PCM_BUFFER_SIZE; + // TODO: consider making a singleton class to manage/share buffers uint8_t pcm[HEM_LOFI_PCM_BUFFER_SIZE]; bool play = 0; //play always on unless gated on Digital 1 uint16_t head = 0; // Location of read/play head From ad26d6dd71be4ef0e4c93f2cc89859aae47567d5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 00:31:10 -0500 Subject: [PATCH 110/417] DualTM: Slew pitches, don't scale smoothing factor --- software/o_c_REV/HEM_TM2.ino | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 1532458e0..14bb9d338 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -38,7 +38,6 @@ #define TM2_MIN_LENGTH 2 #define TM2_MAX_LENGTH 32 -#define TM2_SMOOTHING 4 class DualTM : public HemisphereApplet { public: @@ -141,13 +140,13 @@ public: ForEachChannel(ch) { switch (outmode[ch]) { case -1: // pitch 1+2 - Output[ch] = quantizer.Lookup(note + note2 + note_trans3 + 64); + Output[ch] = slew(Output[ch], quantizer.Lookup(note + note2 + note_trans3 + 64)); break; case 0: // pitch 1 - Output[ch] = quantizer.Lookup(note + note_trans1 + note_trans3 + 64); + Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans1 + note_trans3 + 64)); break; case 1: // pitch 2 - Output[ch] = quantizer.Lookup(note2 + note_trans2 + note_trans3 + 64); + Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans2 + note_trans3 + 64)); break; case 2: // mod A - 8-bit bi-polar proportioned CV Output[ch] = slew(Output[ch], Proportion( int(reg & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); @@ -309,8 +308,7 @@ private: // more smoothing causes more ticks to be skipped if (OC::CORE::ticks % smooth_mod) return old_val; - int s = smooth_mod * TM2_SMOOTHING; - old_val = (old_val * (s - 1) + new_val) / s; + old_val = (old_val * (smooth_mod - 1) + new_val) / smooth_mod; return old_val; } From 6ce04400c0d7a07c776f5eb84a0b5a59b869c9b0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 00:31:51 -0500 Subject: [PATCH 111/417] ClockSetup: manual triggers --- software/o_c_REV/HEM_ClockSetup.ino | 18 +++++++++++++++--- software/o_c_REV/HSClockManager.h | 15 +++++++++++++++ software/o_c_REV/HemisphereApplet.h | 2 ++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 7ba6182ac..806df52a0 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -51,12 +51,13 @@ public: void OnButtonPress() { if (cursor == 0) PlayStop(); else if (cursor == 1) clock_m->ToggleForwarding(); + else if (cursor > 4) clock_m->Boop(cursor-5); else isEditing = !isEditing; } void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, 4); + cursor = constrain(cursor + direction, 0, 8); ResetCursor(); } else { switch (cursor) { @@ -152,9 +153,15 @@ private: gfxPrint(clock_m->GetMultiply(0)); // secondary multiplier when forwarding internal clock - gfxPrint(33, 37, "x"); + gfxPrint(65, 37, "x"); gfxPrint(clock_m->GetMultiply(1)); + // Manual triggers + gfxIcon(4, 49, BURST_ICON); + gfxIcon(36, 49, BURST_ICON); + gfxIcon(68, 49, BURST_ICON); + gfxIcon(100, 49, BURST_ICON); + switch (cursor) { case 0: gfxFrame(11, 14, 10, 10); break; // Play/Stop case 1: gfxFrame(49, 14, 10, 10); break; // Forwarding @@ -165,8 +172,13 @@ private: case 3: // Multipliers case 4: - isEditing ? gfxInvert(8 + 32*(cursor-3), 36, 12, 9) : gfxCursor(8 + 32*(cursor-3), 45, 12); + isEditing ? gfxInvert(8 + 64*(cursor-3), 36, 12, 9) : gfxCursor(8 + 64*(cursor-3), 45, 12); break; + + case 5: gfxFrame(3, 48, 10, 10); break; // Manual Trig 1 + case 6: gfxFrame(35, 48, 10, 10); break; // Manual Trig 2 + case 7: gfxFrame(67, 48, 10, 10); break; // Manual Trig 3 + case 8: gfxFrame(99, 48, 10, 10); break; // Manual Trig 4 } } }; diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index d95a42456..f9bac50ff 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -49,6 +49,8 @@ class ClockManager { bool cycle = 0; // Alternates for each tock, for display purposes int count[3] = {0,0,0}; // Multiple counter, 0 is a special case when first starting the clock + bool boop[4]; // Manual triggers + ClockManager() { SetTempoBPM(120); } @@ -142,6 +144,7 @@ class ClockManager { } // clock has been physically ticked if (clocked) clock_tick = now; + } void Start(bool p = 0) { @@ -175,6 +178,18 @@ class ClockManager { bool IsForwarded() {return forwarded;} + // beep boop + void Boop(int ch = 0) { + boop[ch] = true; + } + bool Beep(int ch = 0) { + if (boop[ch]) { + boop[ch] = false; + return true; + } + return false; + } + /* Returns true if the clock should fire on this tick, based on the current tempo and multiplier */ bool Tock(int ch = 0) { return tock[ch]; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 4109107ad..d0aef1b7b 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -345,6 +345,8 @@ class HemisphereApplet { clocked = OC::DigitalInputs::clocked(); } + clocked = clocked || clock_m->Beep(hemisphere*2 + ch); + if (clocked) { cycle_ticks[ch] = OC::CORE::ticks - last_clock[ch]; last_clock[ch] = OC::CORE::ticks; From d4376e7e9950b6dec397faa3cebbdb0d3a32c9b4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 12:11:53 -0500 Subject: [PATCH 112/417] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cba230f09..1f0641d9f 100755 --- a/README.md +++ b/README.md @@ -1,25 +1,25 @@ -Welcome to Benisphere Suite, djphazer mod (Phazerville Suite) +"What's the worst that could happen?" === -## An active fork expanding upon Hemisphere Suite. +## Phazerville Suite - an active fork expanding upon Hemisphere Suite. -Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this "phazerville" branch takes the Hemisphere Suite in new directions, with many new applets and enhancements to existing ones, while also removing some full-width apps, abandoning the original minimalist approach, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible. +Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible. -I've merged bleeding-edge features from various other branches, and confirmed that it compiles and runs on my uO_C. +I've merged bleeding-edge features from various other branches, and confirmed that it compiles and runs on my uo_C. ### Notable Features in this branch: +* Improved internal clock controls, external clock sync, independent multipliers for each Hemisphere, MIDI Clock out via USB * LoFi Tape has been transformed into LoFi Echo (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) * ShiftReg has been upgraded to DualTM - two concurrent 32-bit registers governed by the same length/prob/scale/range settings, both outputs configurable to Pitch, Mod, Trig, Gate from either register * AnnularFusion got a makeover, now includes configurable CV input modulation (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) * Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) * EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking -* Improved internal clock controls, independent multipliers for each Hemisphere, MIDI Clock out via USB -* Modal-editing style navigation on some applets (TB-3PO, DualTM, CVRec) +* Modal-editing style navigation (push to toggle editing) ### How do I try it? -I might release a .hex file if there is demand... I just need to make sure I understand my licensing obligations and give proper credit. Meanwhile, I'm contributing my work upstream to Benispheres. +I might release a .hex file if there is demand... but I think the beauty of this module is the fact that it's relatively easy to modify and build the source code to reprogram it. You are free to customize the firmware, similar to how you've no doubt already selected a custom set of physical modules. ### How do I build it? From 3d2b65a4d726f1a8f400ea164aa825ec6acda458 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Thu, 5 Jan 2023 15:15:40 -0500 Subject: [PATCH 113/417] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1f0641d9f..59304c7fa 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ "What's the worst that could happen?" === -## Phazerville Suite - an active fork expanding upon Hemisphere Suite. +## Phazerville Suite - an active o_C firmware fork Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible. @@ -12,10 +12,11 @@ I've merged bleeding-edge features from various other branches, and confirmed th * Improved internal clock controls, external clock sync, independent multipliers for each Hemisphere, MIDI Clock out via USB * LoFi Tape has been transformed into LoFi Echo (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) * ShiftReg has been upgraded to DualTM - two concurrent 32-bit registers governed by the same length/prob/scale/range settings, both outputs configurable to Pitch, Mod, Trig, Gate from either register -* AnnularFusion got a makeover, now includes configurable CV input modulation (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) +* EuclidX - AnnularFusion got a makeover, now includes configurable CV input modulation (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) * Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) * EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking * Modal-editing style navigation (push to toggle editing) +* other small tweaks + experimental applets ### How do I try it? @@ -23,9 +24,7 @@ I might release a .hex file if there is demand... but I think the beauty of this ### How do I build it? -Building the code is fairly simple using Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://platformio.org/install/cli) or a [plugin within VSCode](https://platformio.org/install/ide?install=vscode). The project lives within the `software/o_c_REV` directory. - -You might still be able to build this repo following the ["Method B" instruction](https://ornament-and-cri.me/firmware/#method_b) from the Ornament and Crime website. +Building the code is fairly simple using Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html) or a [plugin within VSCode](https://platformio.org/install/ide?install=vscode). The project lives within the `software/o_c_REV` directory. From there, you can Build and Upload via USB to your module. ### Credits From a2dfc0fa2d00b7a3bf2f451706fc6d8af96a0324 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 16:26:40 -0500 Subject: [PATCH 114/417] ClockSetup: external PPQN is configurable --- software/o_c_REV/HEM_ClockSetup.ino | 45 +++++++++++++++++++---------- software/o_c_REV/HSClockManager.h | 14 +++++++-- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 806df52a0..c6cbd72b7 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -51,23 +51,26 @@ public: void OnButtonPress() { if (cursor == 0) PlayStop(); else if (cursor == 1) clock_m->ToggleForwarding(); - else if (cursor > 4) clock_m->Boop(cursor-5); + else if (cursor > 5) clock_m->Boop(cursor-6); else isEditing = !isEditing; } void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, 8); + cursor = constrain(cursor + direction, 0, 9); ResetCursor(); } else { switch (cursor) { - case 2: // Set tempo + case 2: // input PPQN + clock_m->SetClockPPQN(clock_m->GetClockPPQN() + direction); + break; + case 3: // Set tempo clock_m->SetTempoBPM(clock_m->GetTempo() + direction); break; - case 3: // Set multiplier - case 4: - clock_m->SetMultiply(clock_m->GetMultiply(cursor-3) + direction, cursor-3); + case 4: // Set multiplier + case 5: + clock_m->SetMultiply(clock_m->GetMultiply(cursor-4) + direction, cursor-4); break; } } @@ -89,7 +92,8 @@ public: clock_m->Stop(); } clock_m->SetTempoBPM(Unpack(data, PackLocation { 1, 9 })); - clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 })); + clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 }),0); + clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 }),1); clock_m->SetForwarding(Unpack(data, PackLocation { 15, 1 })); } @@ -104,7 +108,7 @@ protected: } private: - char cursor; // 0=Play/Stop, 1=Forwarding, 2=Tempo, 3,4=Multiply + char cursor; // 0=Play/Stop, 1=Forwarding, 2=External PPQN, 3=Tempo, 4,5=Multiply bool isEditing = false; bool start_q; bool stop_q; @@ -142,6 +146,10 @@ private: gfxPrint(26, 15, "Fwd "); gfxIcon(50, 15, clock_m->IsForwarded() ? CHECK_ON_ICON : CHECK_OFF_ICON); + // Input PPQN + gfxPrint(64, 15, "PPQN x"); + gfxPrint(clock_m->GetClockPPQN()); + // Tempo gfxIcon(1, 26, NOTE4_ICON); gfxPrint(9, 26, "= "); @@ -166,19 +174,24 @@ private: case 0: gfxFrame(11, 14, 10, 10); break; // Play/Stop case 1: gfxFrame(49, 14, 10, 10); break; // Forwarding - case 2: // BPM + case 2: // Clock PPQN + isEditing ? gfxInvert(100,14, 12,9) : gfxCursor(100,23, 12); + break; + + case 3: // BPM isEditing ? gfxInvert(22, 25, 18, 9) : gfxCursor(22, 34, 18); break; - case 3: // Multipliers - case 4: - isEditing ? gfxInvert(8 + 64*(cursor-3), 36, 12, 9) : gfxCursor(8 + 64*(cursor-3), 45, 12); + case 4: // Multipliers + case 5: + isEditing ? gfxInvert(8 + 64*(cursor-4), 36, 12, 9) : gfxCursor(8 + 64*(cursor-4), 45, 12); break; - case 5: gfxFrame(3, 48, 10, 10); break; // Manual Trig 1 - case 6: gfxFrame(35, 48, 10, 10); break; // Manual Trig 2 - case 7: gfxFrame(67, 48, 10, 10); break; // Manual Trig 3 - case 8: gfxFrame(99, 48, 10, 10); break; // Manual Trig 4 + + case 6: gfxFrame(3, 48, 10, 10); break; // Manual Trig 1 + case 7: gfxFrame(35, 48, 10, 10); break; // Manual Trig 2 + case 8: gfxFrame(67, 48, 10, 10); break; // Manual Trig 3 + case 9: gfxFrame(99, 48, 10, 10); break; // Manual Trig 4 } } }; diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index f9bac50ff..3753fd175 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -46,6 +46,7 @@ class ClockManager { uint32_t last_tock_check[3] = {0,0,0}; // To avoid checking the tock more than once per tick bool tock[3] = {0,0,0}; // The current tock value int tocks_per_beat[3] = {1, 1, 24}; // Multiplier + int clock_ppqn = 4; // external clock multiple bool cycle = 0; // Alternates for each tock, for display purposes int count[3] = {0,0,0}; // Multiple counter, 0 is a special case when first starting the clock @@ -66,6 +67,11 @@ class ClockManager { tocks_per_beat[ch] = multiply; } + // adjusts the expected clock multiple for external clock pulses + void SetClockPPQN(int clkppqn) { + clock_ppqn = constrain(clkppqn, 1, 24); + } + /* Set ticks per tock, based on one million ticks per minute divided by beats per minute. * This is approximate, because the arithmetical value is likely to be fractional, and we * need to live with a certain amount of imprecision here. So I'm not even rounding up. @@ -77,12 +83,14 @@ class ClockManager { } int GetMultiply(bool ch = 0) {return tocks_per_beat[ch];} + int GetClockPPQN() { return clock_ppqn; } /* Gets the current tempo. This can be used between client processes, like two different * hemispheres. */ uint16_t GetTempo() {return tempo;} + // Resync multipliers, optionally skipping the first tock void Reset(bool count_skip = 0) { for (int ch = 0; ch < 3; ch++) { beat_tick[ch] = OC::CORE::ticks; @@ -119,15 +127,15 @@ class ClockManager { if (clocked && clock_tick) { uint32_t clock_diff = now - clock_tick; // 4 PPQN clock input - if (CLOCK_PPQN * clock_diff > CLOCK_TICKS_MAX) clock_tick = 0; // too slow, reset clock tracking + if (clock_ppqn * clock_diff > CLOCK_TICKS_MAX) clock_tick = 0; // too slow, reset clock tracking // if there is a previous clock tick, update tempo and sync if (clock_tick && clock_diff) { // update the tempo - ticks_per_beat = constrain(CLOCK_PPQN * clock_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); // time since last clock is new tempo + ticks_per_beat = constrain(clock_ppqn * clock_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); // time since last clock is new tempo tempo = 1000000 / ticks_per_beat; // imprecise, for display purposes - int ticks_per_clock = ticks_per_beat / CLOCK_PPQN; // rounded down + int ticks_per_clock = ticks_per_beat / clock_ppqn; // rounded down //int clock_err = ticks_per_beat % CLOCK_PPQN; // rounding error // time since last beat From f108f6fb3d51a620686e03f2fdfd38a4381e9f12 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 16:52:01 -0500 Subject: [PATCH 115/417] AttenOff: modal editing --- software/o_c_REV/HEM_AttenuateOffset.ino | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index 0922d3e4d..3ede6d05a 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -56,15 +56,17 @@ public: } void OnButtonPress() { - if (++cursor > 4) cursor = 0; - ResetCursor(); + if (cursor == 4) + mix = !mix; + else + isEditing = !isEditing; } void OnEncoderMove(int direction) { - if (cursor == 4) { - mix = (direction > 0); - } else - { + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 4); + ResetCursor(); + } else { uint8_t ch = cursor / 2; if (cursor == 0 || cursor == 2) { // Change offset voltage @@ -108,6 +110,7 @@ protected: private: int cursor; + bool isEditing = false; int level[2]; int offset[2]; bool mix = false; @@ -132,10 +135,9 @@ private: if (CursorBlink()) { gfxFrame(0, 24, 9, 10); } - } else{ - int ch = cursor / 2; - if (cursor == 0 or cursor == 2) gfxCursor(13, 23 + (ch * 20), 36); - else gfxCursor(13, 33 + (ch * 20), 36); + } else { + isEditing ? gfxInvert(12, 14 + cursor * 10, 37, 9) + : gfxCursor(12, 23 + cursor * 10, 37); } } From f0144c056371238158498fb1c3c067f43f256535 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 17:02:30 -0500 Subject: [PATCH 116/417] Voltage: modal editing --- software/o_c_REV/HEM_Voltage.ino | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/HEM_Voltage.ino b/software/o_c_REV/HEM_Voltage.ino index 30f0b4a31..822135fc4 100644 --- a/software/o_c_REV/HEM_Voltage.ino +++ b/software/o_c_REV/HEM_Voltage.ino @@ -56,19 +56,23 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; - ResetCursor(); + isEditing = !isEditing; } void OnEncoderMove(int direction) { - uint8_t ch = cursor / 2; - if (cursor == 0 || cursor == 2) { - // Change voltage - int min = -HEMISPHERE_3V_CV / VOLTAGE_INCREMENTS; - int max = HEMISPHERE_MAX_CV / VOLTAGE_INCREMENTS; - voltage[ch] = constrain(voltage[ch] + direction, min, max); + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 3); + ResetCursor(); } else { - gate[ch] = 1 - gate[ch]; + uint8_t ch = cursor / 2; + if (cursor == 0 || cursor == 2) { + // Change voltage + int min = -HEMISPHERE_3V_CV / VOLTAGE_INCREMENTS; + int max = HEMISPHERE_MAX_CV / VOLTAGE_INCREMENTS; + voltage[ch] = constrain(voltage[ch] + direction, min, max); + } else { + gate[ch] = 1 - gate[ch]; + } } } @@ -100,6 +104,7 @@ protected: private: int cursor; + bool isEditing = false; bool view[2]; // Settings @@ -117,9 +122,8 @@ private: if (view[ch]) gfxInvert(0, 14 + (ch * 20), 7, 9); } - int ch = cursor / 2; - if (cursor == 0 or cursor == 2) gfxCursor(13, 23 + (ch * 20), 36); - else gfxCursor(13, 33 + (ch * 20), 36); + isEditing ? gfxInvert(12, 14 + cursor * 10, 37, 9) + : gfxCursor(12, 23 + cursor * 10, 37); } }; From d75eab85acc0b666a4fbcdc26e23dd04905566b6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 17:57:52 -0500 Subject: [PATCH 117/417] LoFiPCM: modal editing --- software/o_c_REV/HEM_LoFiPCM.ino | 46 +++++++++++++++++--------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 58edf3621..fb926edfd 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -82,28 +82,29 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; - ResetCursor(); + isEditing = !isEditing; } void OnEncoderMove(int direction) { - switch (cursor) { - case 0: - dt_pct = constrain(dt_pct + direction, 0, 99); - break; - case 1: - feedback = constrain(feedback + direction, 0, 125); - break; - case 2: - rate = constrain(rate + direction, 1, 32); - break; - case 3: - depth = constrain(depth + direction, 0, 13); - break; + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 3); + ResetCursor(); + } else { + switch (cursor) { + case 0: + dt_pct = constrain(dt_pct + direction, 0, 99); + break; + case 1: + feedback = constrain(feedback + direction, 0, 125); + break; + case 2: + rate = constrain(rate + direction, 1, 32); + break; + case 3: + depth = constrain(depth + direction, 0, 13); + break; + } } - - //amp_offset_cv = Proportion(amp_offset_pct, 100, HEMISPHERE_MAX_CV); - //p[cursor] = constrain(p[cursor] += direction, 0, 100); } uint64_t OnDataRequest() { @@ -147,7 +148,8 @@ private: uint8_t rate = HEM_LOFI_PCM_SPEED; uint8_t rate_mod = rate; int depth = 0; // bit reduction depth aka bitcrush - uint8_t cursor; //for gui + int cursor; //for gui + bool isEditing = false; void DrawWaveform() { int inc = rate_mod/2 + 1; @@ -171,7 +173,8 @@ private: } gfxPrint(4 + pad(100, dt_pct), 15, dt_pct); gfxPrint(36 + pad(1000, fdbk_g), 15, fdbk_g); - gfxCursor(0 + (31 * cursor), 23, 30); + isEditing ? gfxInvert(10 + 31 * cursor, 14, 20, 9) + : gfxCursor(10 + 31 * cursor, 23, 20); } else { gfxIcon(0, 15, WAVEFORM_ICON); gfxIcon(8, 15, BURST_ICON); @@ -179,7 +182,8 @@ private: gfxPrint(30, 15, rate_mod); gfxIcon(42, 15, UP_DOWN_ICON); gfxPrint(50, 15, depth); - gfxCursor(30 + (cursor-2)*20, 23, 14); + isEditing ? gfxInvert(30 + (cursor-2)*20, 14, 14, 9) + : gfxCursor(30 + (cursor-2)*20, 23, 14); } } From 0ed5bd3c92863e5c024ca6a09b021221592cd853 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 5 Jan 2023 17:58:07 -0500 Subject: [PATCH 118/417] DualQuant: modal editing --- software/o_c_REV/HEM_DualQuant.ino | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/software/o_c_REV/HEM_DualQuant.ino b/software/o_c_REV/HEM_DualQuant.ino index 88c4be341..5238e2a43 100644 --- a/software/o_c_REV/HEM_DualQuant.ino +++ b/software/o_c_REV/HEM_DualQuant.ino @@ -66,22 +66,26 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; - ResetCursor(); + isEditing = !isEditing; } void OnEncoderMove(int direction) { - uint8_t ch = cursor / 2; - if (cursor == 0 || cursor == 2) { - // Scale selection - scale[ch] += direction; - if (scale[ch] >= OC::Scales::NUM_SCALES) scale[ch] = 0; - if (scale[ch] < 0) scale[ch] = OC::Scales::NUM_SCALES - 1; - quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); - continuous[ch] = 1; // Re-enable continuous mode when scale is changed + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 3); + ResetCursor(); } else { - // Root selection - root[ch] = constrain(root[ch] + direction, 0, 11); + uint8_t ch = cursor / 2; + if (cursor == 0 || cursor == 2) { + // Scale selection + scale[ch] += direction; + if (scale[ch] >= OC::Scales::NUM_SCALES) scale[ch] = 0; + if (scale[ch] < 0) scale[ch] = OC::Scales::NUM_SCALES - 1; + quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + continuous[ch] = 1; // Re-enable continuous mode when scale is changed + } else { + // Root selection + root[ch] = constrain(root[ch] + direction, 0, 11); + } } } @@ -121,6 +125,7 @@ private: int last_note[2]; // Last quantized note bool continuous[2]; // Each channel starts as continuous and becomes clocked when a clock is received int cursor; + bool isEditing = false; // Settings int scale[2]; // Scale per channel @@ -139,10 +144,10 @@ private: gfxPrint(10 + (31 * ch), 25, OC::Strings::note_names_unpadded[root[ch]]); // Draw cursor - int cursor_ch = cursor / 2; - if (ch == cursor_ch) { - if (cursor == 0 || cursor == 2) gfxCursor(0 + (31 * ch), 23, 30); - else gfxCursor(10 + (31 * ch), 33, 12); + int y = cursor % 2; // 0=top line, 1=bottom + if (ch == (cursor / 2)) { + isEditing ? gfxInvert(y*10 + ch*31, 14 + y*10, 12+(1-y)*18, 9) + : gfxCursor(y*10 + ch*31, 23 + y*10, 12+(1-y)*18); } // Little note display From 4def667ed8739d7bff500a1e4ca421b86c412285 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Fri, 6 Jan 2023 10:20:04 -0800 Subject: [PATCH 119/417] Fix off by 1 error in EuclideanPattern --- software/o_c_REV/bjorklund.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/bjorklund.cpp b/software/o_c_REV/bjorklund.cpp index f4f66e1af..246f30307 100644 --- a/software/o_c_REV/bjorklund.cpp +++ b/software/o_c_REV/bjorklund.cpp @@ -1335,10 +1335,15 @@ bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uin } uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation) { - num_beats %= (num_steps + 1); - uint32_t pattern = bjorklund_patterns[((num_steps - 2) * 33) + num_beats]; + if (num_steps < 2) { + num_steps = 2; // + } + if (num_beats > (num_steps)) { + num_beats = num_steps; + } + uint32_t pattern = bjorklund_patterns[((num_steps - 2) * 33) + num_beats]; if (rotation) { - rotation %= num_steps; + rotation = rotation % num_steps; pattern = rotl32(pattern, num_steps, rotation) ; } return pattern; From e1712780ff26cc72b9e810a97081f4452d13dbbf Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 10 Jan 2023 21:14:22 -0500 Subject: [PATCH 120/417] Finish renaming AnnularFusion to EuclidX, add credits --- ...{HEM_AnnularFusion.ino => HEM_EuclidX.ino} | 46 +++++++++++-------- software/o_c_REV/hemisphere_config.h | 2 +- 2 files changed, 28 insertions(+), 20 deletions(-) rename software/o_c_REV/{HEM_AnnularFusion.ino => HEM_EuclidX.ino} (87%) diff --git a/software/o_c_REV/HEM_AnnularFusion.ino b/software/o_c_REV/HEM_EuclidX.ino similarity index 87% rename from software/o_c_REV/HEM_AnnularFusion.ino rename to software/o_c_REV/HEM_EuclidX.ino index b51025f59..d54228cf8 100644 --- a/software/o_c_REV/HEM_AnnularFusion.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -1,3 +1,6 @@ +// Copyright (c) 2022, Bryan Head +// Copyright (c) 2022, Nicholas J. Michalek +// Copyright (c) 2022, Alessio Degani // Copyright (c) 2018, Jason Justian // // Bjorklund pattern filter, Copyright (c) 2016 Tim Churches @@ -20,12 +23,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +/* This applet is a replacement for the original Annular Fusion Euclidean Drummer, + * redesigned by qiemem and modified by djphazer to add CV modulation. + * The CV input logic as well as the name were copied from a separate rewrite by adegani. + */ + #include "bjorklund.h" const int NUM_PARAMS = 4; const int PARAM_SIZE = 5; -class AnnularFusion : public HemisphereApplet { +class EuclidX : public HemisphereApplet { public: const char* applet_name() { @@ -243,41 +251,41 @@ private: //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Applet Functions /// -/// Once you run the find-and-replace to make these refer to AnnularFusion, +/// Once you run the find-and-replace to make these refer to EuclidX, /// it's usually not necessary to do anything with these functions. You /// should prefer to handle things in the HemisphereApplet child class /// above. //////////////////////////////////////////////////////////////////////////////// -AnnularFusion AnnularFusion_instance[2]; +EuclidX EuclidX_instance[2]; -void AnnularFusion_Start(bool hemisphere) { - AnnularFusion_instance[hemisphere].BaseStart(hemisphere); +void EuclidX_Start(bool hemisphere) { + EuclidX_instance[hemisphere].BaseStart(hemisphere); } -void AnnularFusion_Controller(bool hemisphere, bool forwarding) { - AnnularFusion_instance[hemisphere].BaseController(forwarding); +void EuclidX_Controller(bool hemisphere, bool forwarding) { + EuclidX_instance[hemisphere].BaseController(forwarding); } -void AnnularFusion_View(bool hemisphere) { - AnnularFusion_instance[hemisphere].BaseView(); +void EuclidX_View(bool hemisphere) { + EuclidX_instance[hemisphere].BaseView(); } -void AnnularFusion_OnButtonPress(bool hemisphere) { - AnnularFusion_instance[hemisphere].OnButtonPress(); +void EuclidX_OnButtonPress(bool hemisphere) { + EuclidX_instance[hemisphere].OnButtonPress(); } -void AnnularFusion_OnEncoderMove(bool hemisphere, int direction) { - AnnularFusion_instance[hemisphere].OnEncoderMove(direction); +void EuclidX_OnEncoderMove(bool hemisphere, int direction) { + EuclidX_instance[hemisphere].OnEncoderMove(direction); } -void AnnularFusion_ToggleHelpScreen(bool hemisphere) { - AnnularFusion_instance[hemisphere].HelpScreen(); +void EuclidX_ToggleHelpScreen(bool hemisphere) { + EuclidX_instance[hemisphere].HelpScreen(); } -uint64_t AnnularFusion_OnDataRequest(bool hemisphere) { - return AnnularFusion_instance[hemisphere].OnDataRequest(); +uint64_t EuclidX_OnDataRequest(bool hemisphere) { + return EuclidX_instance[hemisphere].OnDataRequest(); } -void AnnularFusion_OnDataReceive(bool hemisphere, uint64_t data) { - AnnularFusion_instance[hemisphere].OnDataReceive(data); +void EuclidX_OnDataReceive(bool hemisphere, uint64_t data) { + EuclidX_instance[hemisphere].OnDataReceive(data); } diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 3b163cac5..0c6c1bbf2 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -34,7 +34,7 @@ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ - DECLARE_APPLET( 15, 0x02, AnnularFusion), \ + DECLARE_APPLET( 15, 0x02, EuclidX), \ DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ From eaa87e3e157a30267ca23c85f16e541e9980093d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 10 Jan 2023 21:34:16 -0500 Subject: [PATCH 121/417] Rename Sequence5 to SequenceX It comes with 8 steps now, unless you think that's too visually cramped, in which case, you can easily change the macro. --- .../{HEM_Sequence5.ino => HEM_SequenceX.ino} | 57 ++++++++++--------- software/o_c_REV/hemisphere_config.h | 2 +- 2 files changed, 31 insertions(+), 28 deletions(-) rename software/o_c_REV/{HEM_Sequence5.ino => HEM_SequenceX.ino} (79%) diff --git a/software/o_c_REV/HEM_Sequence5.ino b/software/o_c_REV/HEM_SequenceX.ino similarity index 79% rename from software/o_c_REV/HEM_Sequence5.ino rename to software/o_c_REV/HEM_SequenceX.ino index 643f0706b..8283c652d 100644 --- a/software/o_c_REV/HEM_Sequence5.ino +++ b/software/o_c_REV/HEM_SequenceX.ino @@ -18,12 +18,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +/* original 8-step mod by Logarhythm, adapted by djphazer + */ + #include "HSMIDI.h" // DON'T GO PAST 8! -#define SEQ5_STEPS 8 +#define SEQX_STEPS 8 -class Sequence5 : public HemisphereApplet { +class SequenceX : public HemisphereApplet { public: const char* applet_name() { // Maximum 10 characters @@ -31,7 +34,7 @@ public: } void Start() { - for (int s = 0; s < SEQ5_STEPS; s++) note[s] = random(0, 30); + for (int s = 0; s < SEQX_STEPS; s++) note[s] = random(0, 30); } void Controller() { @@ -61,7 +64,7 @@ public: } void OnButtonPress() { - if (++cursor == SEQ5_STEPS) cursor = 0; + if (++cursor == SEQX_STEPS) cursor = 0; } void OnEncoderMove(int direction) { @@ -76,7 +79,7 @@ public: uint64_t OnDataRequest() { uint64_t data = 0; - for (int s = 0; s < SEQ5_STEPS; s++) + for (int s = 0; s < SEQX_STEPS; s++) { Pack(data, PackLocation {uint8_t(s * 5),5}, note[s]); } @@ -85,7 +88,7 @@ public: } void OnDataReceive(uint64_t data) { - for (int s = 0; s < SEQ5_STEPS; s++) + for (int s = 0; s < SEQX_STEPS; s++) { note[s] = Unpack(data, PackLocation {uint8_t(s * 5),5}); } @@ -105,19 +108,19 @@ protected: private: int cursor = 0; char muted = 0; // Bitfield for muted steps; ((muted >> step) & 1) means muted - int note[SEQ5_STEPS]; // Sequence value (0 - 30) + int note[SEQX_STEPS]; // Sequence value (0 - 30) int step = 0; // Current sequencer step bool reset = true; void Advance(int starting_point) { - if (++step == SEQ5_STEPS) step = 0; + if (++step == SEQX_STEPS) step = 0; // If all the steps have been muted, stay where we were if (step_is_muted(step) && step != starting_point) Advance(starting_point); } void DrawPanel() { // Sliders - for (int s = 0; s < SEQ5_STEPS; s++) + for (int s = 0; s < SEQX_STEPS; s++) { //int x = 6 + (12 * s); int x = 6 + (7 * s); // APD: narrower to fit more @@ -165,41 +168,41 @@ private: //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Applet Functions /// -/// Once you run the find-and-replace to make these refer to Sequence5, +/// Once you run the find-and-replace to make these refer to SequenceX, /// it's usually not necessary to do anything with these functions. You /// should prefer to handle things in the HemisphereApplet child class /// above. //////////////////////////////////////////////////////////////////////////////// -Sequence5 Sequence5_instance[2]; +SequenceX SequenceX_instance[2]; -void Sequence5_Start(bool hemisphere) { - Sequence5_instance[hemisphere].BaseStart(hemisphere); +void SequenceX_Start(bool hemisphere) { + SequenceX_instance[hemisphere].BaseStart(hemisphere); } -void Sequence5_Controller(bool hemisphere, bool forwarding) { - Sequence5_instance[hemisphere].BaseController(forwarding); +void SequenceX_Controller(bool hemisphere, bool forwarding) { + SequenceX_instance[hemisphere].BaseController(forwarding); } -void Sequence5_View(bool hemisphere) { - Sequence5_instance[hemisphere].BaseView(); +void SequenceX_View(bool hemisphere) { + SequenceX_instance[hemisphere].BaseView(); } -void Sequence5_OnButtonPress(bool hemisphere) { - Sequence5_instance[hemisphere].OnButtonPress(); +void SequenceX_OnButtonPress(bool hemisphere) { + SequenceX_instance[hemisphere].OnButtonPress(); } -void Sequence5_OnEncoderMove(bool hemisphere, int direction) { - Sequence5_instance[hemisphere].OnEncoderMove(direction); +void SequenceX_OnEncoderMove(bool hemisphere, int direction) { + SequenceX_instance[hemisphere].OnEncoderMove(direction); } -void Sequence5_ToggleHelpScreen(bool hemisphere) { - Sequence5_instance[hemisphere].HelpScreen(); +void SequenceX_ToggleHelpScreen(bool hemisphere) { + SequenceX_instance[hemisphere].HelpScreen(); } -uint64_t Sequence5_OnDataRequest(bool hemisphere) { - return Sequence5_instance[hemisphere].OnDataRequest(); +uint64_t SequenceX_OnDataRequest(bool hemisphere) { + return SequenceX_instance[hemisphere].OnDataRequest(); } -void Sequence5_OnDataReceive(bool hemisphere, uint64_t data) { - Sequence5_instance[hemisphere].OnDataReceive(data); +void SequenceX_OnDataReceive(bool hemisphere, uint64_t data) { + SequenceX_instance[hemisphere].OnDataReceive(data); } diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 0c6c1bbf2..8321ec9b2 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -55,7 +55,7 @@ DECLARE_APPLET( 26, 0x08, ScaleDuet), \ DECLARE_APPLET( 40, 0x40, Schmitt), \ DECLARE_APPLET( 23, 0x80, Scope), \ - DECLARE_APPLET( 14, 0x02, Sequence5), \ + DECLARE_APPLET( 14, 0x02, SequenceX), \ DECLARE_APPLET( 48, 0x45, ShiftGate), \ DECLARE_APPLET( 58, 0x01, Shredder), \ DECLARE_APPLET( 36, 0x04, Shuffle), \ From c6122ada3a9296a0fa55c2cc5a0076a2f824b339 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 10 Jan 2023 22:25:35 -0500 Subject: [PATCH 122/417] BootsNCat: re-enabled for those who like its character --- .../{HEM_BootsNCat.ino.disabled => HEM_BootsNCat.ino} | 10 +++++----- software/o_c_REV/hemisphere_config.h | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) rename software/o_c_REV/{HEM_BootsNCat.ino.disabled => HEM_BootsNCat.ino} (97%) diff --git a/software/o_c_REV/HEM_BootsNCat.ino.disabled b/software/o_c_REV/HEM_BootsNCat.ino similarity index 97% rename from software/o_c_REV/HEM_BootsNCat.ino.disabled rename to software/o_c_REV/HEM_BootsNCat.ino index 44462c815..196b4825a 100644 --- a/software/o_c_REV/HEM_BootsNCat.ino.disabled +++ b/software/o_c_REV/HEM_BootsNCat.ino @@ -124,8 +124,8 @@ public: ResetCursor(); } - uint32_t OnDataRequest() { - uint32_t data = 0; + uint64_t OnDataRequest() { + uint64_t data = 0; Pack(data, PackLocation {0,6}, tone[0]); Pack(data, PackLocation {6,6}, decay[0]); Pack(data, PackLocation {12,6}, tone[1]); @@ -134,7 +134,7 @@ public: return data; } - void OnDataReceive(uint32_t data) { + void OnDataReceive(uint64_t data) { tone[0] = Unpack(data, PackLocation {0,6}); decay[0] = Unpack(data, PackLocation {6,6}); tone[1] = Unpack(data, PackLocation {12,6}); @@ -219,5 +219,5 @@ void BootsNCat_View(bool hemisphere) {BootsNCat_instance[hemisphere].BaseView(); void BootsNCat_OnButtonPress(bool hemisphere) {BootsNCat_instance[hemisphere].OnButtonPress();} void BootsNCat_OnEncoderMove(bool hemisphere, int direction) {BootsNCat_instance[hemisphere].OnEncoderMove(direction);} void BootsNCat_ToggleHelpScreen(bool hemisphere) {BootsNCat_instance[hemisphere].HelpScreen();} -uint32_t BootsNCat_OnDataRequest(bool hemisphere) {return BootsNCat_instance[hemisphere].OnDataRequest();} -void BootsNCat_OnDataReceive(bool hemisphere, uint32_t data) {BootsNCat_instance[hemisphere].OnDataReceive(data);} +uint64_t BootsNCat_OnDataRequest(bool hemisphere) {return BootsNCat_instance[hemisphere].OnDataRequest();} +void BootsNCat_OnDataReceive(bool hemisphere, uint64_t data) {BootsNCat_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 8321ec9b2..036bdc818 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,7 +11,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. -#define HEMISPHERE_AVAILABLE_APPLETS 59 +#define HEMISPHERE_AVAILABLE_APPLETS 60 ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ @@ -21,6 +21,7 @@ DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ DECLARE_APPLET( 41, 0x41, Binary), \ DECLARE_APPLET( 51, 0x80, BugCrack), \ + DECLARE_APPLET( 55, 0x80, BootsNCat), \ DECLARE_APPLET( 4, 0x14, Brancher), \ DECLARE_APPLET( 31, 0x04, Burst), \ DECLARE_APPLET( 65, 0x10, Button), \ From cdaf4984f6842f72f445c64c4be23a87352c5824 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 10 Jan 2023 22:26:30 -0500 Subject: [PATCH 123/417] Chordinator: fix for rotl32 function This originally worked because "AnnularFusion" included the same file... but it's renamed to EuclidX now. --- software/o_c_REV/HEM_Chordinator.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index 8aaa37e09..b48541d96 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -21,6 +21,7 @@ // SOFTWARE. #include "braids_quantizer.h" +#include "bjorklund.h" class Chordinator : public HemisphereApplet { public: From cfee7364a7cec1645f2f9243f657abb7ec28df4e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 11 Jan 2023 01:21:00 -0500 Subject: [PATCH 124/417] Version bump v1.4.9 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index feea4301d..a3d24587e 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,6 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.8.2" +#define OC_VERSION "v1.4.9" #define OC_VERSION_URL "github.com/djphazer" #endif From 61dcd6bfdaaeff08d078f07a59e332e48fc47619 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 11 Jan 2023 01:14:23 -0500 Subject: [PATCH 125/417] EuclidX: basic support for pattern "padding" Mimics behavior from Pam's PRO Workout, padding adds extra 0 bits to the end of a pattern, and allows rotation with the extra space. TODO: fixup UI --- software/o_c_REV/HEM_EuclidX.ino | 70 +++++++++++++++++++------------- software/o_c_REV/bjorklund.cpp | 15 +++---- software/o_c_REV/bjorklund.h | 2 +- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index d54228cf8..e09530c05 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -30,7 +30,7 @@ #include "bjorklund.h" -const int NUM_PARAMS = 4; +const int NUM_PARAMS = 5; const int PARAM_SIZE = 5; class EuclidX : public HemisphereApplet { @@ -46,7 +46,8 @@ public: actual_length[ch] = length[ch] = 16; actual_beats[ch] = beats[ch] = 4 + (ch * 4); actual_offset[ch] = offset[ch] = 0; - pattern[ch] = EuclideanPattern(length[ch], beats[ch], 0); + actual_padding[ch] = padding[ch] = 0; + pattern[ch] = EuclideanPattern(length[ch], beats[ch], offset[ch], padding[ch]); } step = 0; } @@ -63,25 +64,29 @@ public: actual_length[ch] = length[ch]; actual_beats[ch] = beats[ch]; actual_offset[ch] = offset[ch]; + actual_padding[ch] = padding[ch]; // process CV inputs ForEachChannel(cv_ch) { switch (cv_dest[cv_ch] - ch * (NUM_PARAMS-1)) { case 0: // length - actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 1, 32); + actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 2, 32); break; case 1: // beats actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); break; case 2: // offset - actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 0, actual_length[ch]-1); + actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); break; + case 3: // padding + actual_padding[ch] = constrain(actual_padding[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 32 - actual_length[ch]), 0, 32 - actual_length[ch]); + break; default: break; } } // Store the pattern for display - pattern[ch] = EuclideanPattern(actual_length[ch], actual_beats[ch], actual_offset[ch]); + pattern[ch] = EuclideanPattern(actual_length[ch], actual_beats[ch], actual_offset[ch], actual_padding[ch]); } // Process triggers and step forward on clock @@ -89,14 +94,14 @@ public: ForEachChannel(ch) { // actually output the triggers - int sb = step % actual_length[ch]; + int sb = step % (actual_length[ch] + actual_padding[ch]); if ((pattern[ch] >> sb) & 0x01) { ClockOut(ch); } } // Plan for the thing to run forever and ever - if (++step >= actual_length[0] * actual_length[1]) step = 0; + if (++step >= (actual_length[0]+actual_padding[0]) * (actual_length[1]+actual_padding[1])) step = 0; } } @@ -112,25 +117,30 @@ public: void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, 7); + cursor = constrain(cursor + direction, 0, NUM_PARAMS*2 - 1); ResetCursor(); } else { int ch = cursor < NUM_PARAMS ? 0 : 1; int f = cursor - (ch * NUM_PARAMS); // Cursor function switch (f) { case 0: - actual_length[ch] = length[ch] = constrain(length[ch] + direction, 3, 32); + actual_length[ch] = length[ch] = constrain(length[ch] + direction, 2, 32); if (beats[ch] > length[ch]) beats[ch] = length[ch]; - if (offset[ch] >= length[ch]) offset[ch] = length[ch]-1; + if (padding[ch] > 32 - length[ch]) padding[ch] = 32 - length[ch]; + if (offset[ch] >= length[ch] + padding[ch]) offset[ch] = length[ch] + padding[ch] - 1; break; case 1: actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); break; case 2: - actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] - 1); + actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] + padding[ch] - 1); break; - case 3: // CV destination - cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, 5); + case 3: + padding[ch] = constrain(padding[ch] + direction, 0, 32 - length[ch]); + break; + case 4: // CV destination + cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, (NUM_PARAMS-1)*2 - 1); + break; } } } @@ -179,28 +189,29 @@ private: uint8_t length[2]; uint8_t beats[2]; uint8_t offset[2]; + uint8_t padding[2]; uint8_t actual_length[2]; uint8_t actual_beats[2]; uint8_t actual_offset[2]; + uint8_t actual_padding[2]; uint8_t cv_dest[2]; void DrawSteps() { - //int spacing = 1; gfxLine(0, 45, 63, 45); gfxLine(0, 62, 63, 62); gfxLine(0, 53, 63, 53); gfxLine(0, 54, 63, 54); ForEachChannel(ch) { for (int i = 0; i < 16; i++) { - if ((pattern[ch] >> ((i + step) % actual_length[ch])) & 0x1) { + if ((pattern[ch] >> ((i + step) % (actual_length[ch]+actual_padding[ch]) )) & 0x1) { gfxRect(4 * i + 1, 48 + 9 * ch, 3, 3); //gfxLine(4 * i + 2, 47 + 9 * ch, 4 * i + 2, 47 + 9 * ch + 4); } else { gfxPixel(4 * i + 2, 47 + 9 * ch + 2); } - if ((i + step) % actual_length[ch] == 0) { + if ((i + step) % (actual_length[ch]+actual_padding[ch]) == 0) { //gfxLine(4 * i, 46 + 9 * ch, 4 * i, 52 + 9 * ch); gfxLine(4 * i, 46 + 9 * ch, 4 * i, 46 + 9 * ch + 1); gfxLine(4 * i, 52 + 9 * ch - 1, 4 * i, 52 + 9 * ch); @@ -210,29 +221,32 @@ private: } void DrawEditor() { - int spacing = 18; + const int spacing = 16; - gfxBitmap(4 + 0 * spacing, 15, 8, LOOP_ICON); - gfxBitmap(4 + 1 * spacing, 15, 8, X_NOTE_ICON); - gfxBitmap(4 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); + gfxBitmap(8 + 0 * spacing, 15, 8, LOOP_ICON); + gfxBitmap(8 + 1 * spacing, 15, 8, X_NOTE_ICON); + gfxBitmap(8 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); + gfxPrint(8 + 3 * spacing, 15, "+"); ForEachChannel (ch) { int y = 15 + 10 * (ch + 1); - gfxPrint(4 + 0 * spacing, y, actual_length[ch]); - gfxPrint(4 + 1 * spacing, y, actual_beats[ch]); - gfxPrint(4 + 2 * spacing, y, actual_offset[ch]); + gfxPrint(3 + 0 * spacing + pad(10, actual_length[ch]), y, actual_length[ch]); + gfxPrint(3 + 1 * spacing + pad(10, actual_beats[ch]), y, actual_beats[ch]); + gfxPrint(3 + 2 * spacing + pad(10, actual_offset[ch]), y, actual_offset[ch]); + gfxPrint(3 + 3 * spacing + pad(10, actual_padding[ch]), y, actual_padding[ch]); int f = cursor - ch * NUM_PARAMS; switch (f) { case 0: case 1: case 2: - gfxCursor(4 + f * spacing, y + 7, 13); - if (isEditing) gfxInvert(4+f*spacing, y-1, 13, 9); + case 3: + gfxCursor(3 + f * spacing, y + 7, 13); + if (isEditing) gfxInvert(3+f*spacing, y-1, 13, 9); break; - case 3: // CV dest selection - gfxBitmap(1 + 3 * spacing, y+1, 8, CV_ICON); - if (isEditing) gfxInvert(3*spacing, y, 9, 7); + case 4: // CV dest selection + gfxBitmap(0, 13+ch*3, 8, CV_ICON); + if (isEditing) gfxInvert(0, 13+ch*3, 8, 6); break; } diff --git a/software/o_c_REV/bjorklund.cpp b/software/o_c_REV/bjorklund.cpp index 246f30307..c96557699 100644 --- a/software/o_c_REV/bjorklund.cpp +++ b/software/o_c_REV/bjorklund.cpp @@ -1334,17 +1334,14 @@ bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uin return static_cast(pattern & (0x01 << clock)) ; } -uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation) { - if (num_steps < 2) { - num_steps = 2; // - } - if (num_beats > (num_steps)) { - num_beats = num_steps; - } +uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint8_t padding) { + if (num_steps < 2) num_steps = 2; + if (num_beats > num_steps) num_beats = num_steps; + uint32_t pattern = bjorklund_patterns[((num_steps - 2) * 33) + num_beats]; if (rotation) { - rotation = rotation % num_steps; - pattern = rotl32(pattern, num_steps, rotation) ; + rotation = rotation % (num_steps + padding); + pattern = rotl32(pattern, num_steps + padding, rotation) ; } return pattern; } diff --git a/software/o_c_REV/bjorklund.h b/software/o_c_REV/bjorklund.h index cabb3fece..91d69a7fb 100644 --- a/software/o_c_REV/bjorklund.h +++ b/software/o_c_REV/bjorklund.h @@ -49,6 +49,6 @@ inline uint32_t rotl32(uint32_t input, unsigned int length, unsigned int count) } bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint32_t clock); -uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation); +uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint8_t padding = 0); #endif // BJORKLUND_H_ From 570001750410e00329a531559cc6ec38b0d650b5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 11 Jan 2023 21:22:01 -0500 Subject: [PATCH 126/417] CVRec: fix off-by-one for recording --- software/o_c_REV/HEM_CVRecV2.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index 1a0580c03..d70e92602 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -40,7 +40,7 @@ public: if (Clock(1)) reset = true; if (reset) { step = start; - if (punch_out) punch_out = end - start; + if (punch_out) punch_out = end - start + 1; // inclusive } // check for deferred recording @@ -98,7 +98,7 @@ public: isEditing = 1 - isEditing; // toggle editing if (cursor == 3 && !isEditing) { // activate recording if selected - punch_out = (mode > 0) ? end - start : 0; + punch_out = (mode > 0) ? end - start + 1 : 0; } } From 98402224c359920ecbe772e26494638b920d3475 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Mon, 16 Jan 2023 04:50:20 -0500 Subject: [PATCH 127/417] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59304c7fa..7c43d9894 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ I've merged bleeding-edge features from various other branches, and confirmed th ### How do I try it? -I might release a .hex file if there is demand... but I think the beauty of this module is the fact that it's relatively easy to modify and build the source code to reprogram it. You are free to customize the firmware, similar to how you've no doubt already selected a custom set of physical modules. +Check the [Releases](https://github.com/djphazer/O_C-BenisphereSuite/releases) section for a .hex file, or clone the repository and build it yourself! I think the beauty of this module is the fact that it's relatively easy to modify and build the source code to reprogram it. You are free to customize the firmware, similar to how you've no doubt already selected a custom set of physical modules. ### How do I build it? From d5b21b576bc72ab578e5b25fd10eb7e9e94d2e87 Mon Sep 17 00:00:00 2001 From: bowlneudel <73544995+bowlneudel@users.noreply.github.com> Date: Fri, 20 Jan 2023 10:58:27 -0800 Subject: [PATCH 128/417] Update hemisphere_config.h --- software/o_c_REV/hemisphere_config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 036bdc818..c1e9e83f3 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -20,9 +20,9 @@ DECLARE_APPLET( 47, 0x09, ASR), \ DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ DECLARE_APPLET( 41, 0x41, Binary), \ - DECLARE_APPLET( 51, 0x80, BugCrack), \ 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),\ @@ -35,18 +35,18 @@ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ - DECLARE_APPLET( 15, 0x02, EuclidX), \ DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ + DECLARE_APPLET( 15, 0x02, EuclidX), \ DECLARE_APPLET( 29, 0x04, GateDelay), \ DECLARE_APPLET( 17, 0x50, GatedVCA), \ DECLARE_APPLET( 16, 0x80, LoFiPCM), \ 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( 50, 0x04, Metronome), \ DECLARE_APPLET( 33, 0x10, MixerBal), \ DECLARE_APPLET( 20, 0x02, Palimpsest), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ From fc3fa7ca1c30690b41d6704f0fa33ddbe78d3afe Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 16 Jan 2023 18:04:05 -0500 Subject: [PATCH 129/417] Scope: full range scaling for X-Y view (0V is centered now) --- software/o_c_REV/HEM_Scope.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_Scope.ino b/software/o_c_REV/HEM_Scope.ino index ace8272b6..48c40d959 100644 --- a/software/o_c_REV/HEM_Scope.ino +++ b/software/o_c_REV/HEM_Scope.ino @@ -63,7 +63,7 @@ public: sample_num = LoopInt(++sample_num, 63); for (int n = 0; n < 2; n++) { - int sample = Proportion(In(n) + HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV + HEMISPHERE_3V_CV, 255); + int sample = Proportion(In(n) + HEMISPHERE_MAX_CV, 2*HEMISPHERE_MAX_CV, 255); sample = constrain(sample, 0, 255); snapshot[n][sample_num] = (uint8_t)sample; } From b899661e7fed9c004419bc310c5128914baba395 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 21 Jan 2023 01:31:46 -0500 Subject: [PATCH 130/417] EuclidX: save/recall padding --- software/o_c_REV/HEM_EuclidX.ino | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index e09530c05..511ae7a6d 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -155,6 +155,8 @@ public: Pack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}, offset[1]); Pack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}, cv_dest[0]); Pack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}, cv_dest[1]); + Pack(data, PackLocation {8 * PARAM_SIZE, PARAM_SIZE}, padding[0]); + Pack(data, PackLocation {9 * PARAM_SIZE, PARAM_SIZE}, padding[1]); return data; } @@ -167,6 +169,8 @@ public: actual_offset[1] = offset[1] = Unpack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}); cv_dest[0] = Unpack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}); cv_dest[1] = Unpack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}); + actual_padding[0] = padding[0] = Unpack(data, PackLocation {8 * PARAM_SIZE, PARAM_SIZE}); + actual_padding[1] = padding[1] = Unpack(data, PackLocation {9 * PARAM_SIZE, PARAM_SIZE}); } protected: From f741b4d58f8735c20a158cedecaa89ee67214f9c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 21 Jan 2023 01:24:37 -0500 Subject: [PATCH 131/417] Internal Clock Division, multipliers can be 0 to disable Converted cursor to enum in ClockSetup. The constants usbMIDI.Start, .Stop, etc didn't work in the older Arduino IDE, but they do now. --- software/o_c_REV/HEM_ClockSetup.ino | 87 ++++++++++++++++------------- software/o_c_REV/HSClockManager.h | 41 +++++++++++--- 2 files changed, 81 insertions(+), 47 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index c6cbd72b7..f2238cf5a 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -18,13 +18,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define MIDI_CLOCK 0xF8 -#define MIDI_START 0xFA -#define MIDI_STOP 0xFC - class ClockSetup : public HemisphereApplet { public: + enum ClockSetupCursor { + PLAY_STOP, + FORWARDING, + EXT_PPQN, + TEMPO, + MULT1, + MULT2, + TRIG1, + TRIG2, + TRIG3, + TRIG4, + LAST_SETTING + }; + const char* applet_name() { return "ClockSet"; } @@ -35,13 +45,13 @@ public: void Controller() { if (start_q){ start_q = 0; - usbMIDI.sendRealTime(MIDI_START); + usbMIDI.sendRealTime(usbMIDI.Start); } if (stop_q){ stop_q = 0; - usbMIDI.sendRealTime(MIDI_STOP); + usbMIDI.sendRealTime(usbMIDI.Stop); } - if (clock_m->IsRunning() && clock_m->MIDITock()) usbMIDI.sendRealTime(MIDI_CLOCK); + if (clock_m->IsRunning() && clock_m->MIDITock()) usbMIDI.sendRealTime(usbMIDI.Clock); } void View() { @@ -49,29 +59,31 @@ public: } void OnButtonPress() { - if (cursor == 0) PlayStop(); - else if (cursor == 1) clock_m->ToggleForwarding(); - else if (cursor > 5) clock_m->Boop(cursor-6); + if (cursor == PLAY_STOP) PlayStop(); + else if (cursor == FORWARDING) clock_m->ToggleForwarding(); + else if (cursor >= TRIG1) clock_m->Boop(cursor-TRIG1); else isEditing = !isEditing; } void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, 9); + cursor = (ClockSetupCursor) constrain(cursor + direction, 0, LAST_SETTING-1); ResetCursor(); } else { switch (cursor) { - case 2: // input PPQN + case EXT_PPQN: clock_m->SetClockPPQN(clock_m->GetClockPPQN() + direction); break; - case 3: // Set tempo + case TEMPO: clock_m->SetTempoBPM(clock_m->GetTempo() + direction); break; - case 4: // Set multiplier - case 5: - clock_m->SetMultiply(clock_m->GetMultiply(cursor-4) + direction, cursor-4); + case MULT1: + case MULT2: + clock_m->SetMultiply(clock_m->GetMultiply(cursor - MULT1) + direction, cursor - MULT1); break; + + default: break; } } } @@ -108,7 +120,7 @@ protected: } private: - char cursor; // 0=Play/Stop, 1=Forwarding, 2=External PPQN, 3=Tempo, 4,5=Multiply + ClockSetupCursor cursor; // 0=Play/Stop, 1=Forwarding, 2=External PPQN, 3=Tempo, 4,5=Multiply bool isEditing = false; bool start_q; bool stop_q; @@ -157,41 +169,40 @@ private: gfxPrint(" BPM"); // Multiply - gfxPrint(1, 37, "x"); - gfxPrint(clock_m->GetMultiply(0)); - - // secondary multiplier when forwarding internal clock - gfxPrint(65, 37, "x"); - gfxPrint(clock_m->GetMultiply(1)); + ForEachChannel(ch) { + int mult = clock_m->GetMultiply(ch); + gfxPrint(1 + ch*64, 37, (mult >= 0) ? "x" : "/"); + gfxPrint( (mult >= 0) ? mult : 1 - mult ); + } // Manual triggers - gfxIcon(4, 49, BURST_ICON); - gfxIcon(36, 49, BURST_ICON); - gfxIcon(68, 49, BURST_ICON); - gfxIcon(100, 49, BURST_ICON); + for (int i=0; i<4; i++) { gfxIcon(4 + i*32, 49, BURST_ICON); } switch (cursor) { - case 0: gfxFrame(11, 14, 10, 10); break; // Play/Stop - case 1: gfxFrame(49, 14, 10, 10); break; // Forwarding + case PLAY_STOP: gfxFrame(11, 14, 10, 10); break; + case FORWARDING: gfxFrame(49, 14, 10, 10); break; - case 2: // Clock PPQN + case EXT_PPQN: isEditing ? gfxInvert(100,14, 12,9) : gfxCursor(100,23, 12); break; - case 3: // BPM + case TEMPO: isEditing ? gfxInvert(22, 25, 18, 9) : gfxCursor(22, 34, 18); break; - case 4: // Multipliers - case 5: - isEditing ? gfxInvert(8 + 64*(cursor-4), 36, 12, 9) : gfxCursor(8 + 64*(cursor-4), 45, 12); + case MULT1: + case MULT2: + isEditing ? gfxInvert(8 + 64*(cursor-MULT1), 36, 12, 9) : gfxCursor(8 + 64*(cursor-MULT1), 45, 12); break; + case TRIG1: + case TRIG2: + case TRIG3: + case TRIG4: + gfxFrame(3 + 32*(cursor-TRIG1), 48, 10, 10); + break; - case 6: gfxFrame(3, 48, 10, 10); break; // Manual Trig 1 - case 7: gfxFrame(35, 48, 10, 10); break; // Manual Trig 2 - case 8: gfxFrame(67, 48, 10, 10); break; // Manual Trig 3 - case 9: gfxFrame(99, 48, 10, 10); break; // Manual Trig 4 + default: break; } } }; diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 3753fd175..e44047e3f 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -31,6 +31,9 @@ static constexpr uint16_t CLOCK_TEMPO_MAX = 300; static constexpr uint32_t CLOCK_TICKS_MIN = 1000000 / CLOCK_TEMPO_MAX; static constexpr uint32_t CLOCK_TICKS_MAX = 1000000 / CLOCK_TEMPO_MIN; +constexpr int MIDI_OUT_PPQN = 24; +constexpr int CLOCK_MAX_MULTIPLE = 24; +constexpr int CLOCK_MIN_MULTIPLE = -31; // becomes /32 class ClockManager { static ClockManager *instance; @@ -45,7 +48,7 @@ class ClockManager { uint32_t beat_tick[3] = {0,0,0}; // The tick to count from uint32_t last_tock_check[3] = {0,0,0}; // To avoid checking the tock more than once per tick bool tock[3] = {0,0,0}; // The current tock value - int tocks_per_beat[3] = {1, 1, 24}; // Multiplier + int tocks_per_beat[3] = {1, 1, MIDI_OUT_PPQN}; // Multiplier int clock_ppqn = 4; // external clock multiple bool cycle = 0; // Alternates for each tock, for display purposes int count[3] = {0,0,0}; // Multiple counter, 0 is a special case when first starting the clock @@ -63,7 +66,7 @@ class ClockManager { } void SetMultiply(int multiply, bool ch = 0) { - multiply = constrain(multiply, 1, 24); + multiply = constrain(multiply, CLOCK_MIN_MULTIPLE, CLOCK_MAX_MULTIPLE); tocks_per_beat[ch] = multiply; } @@ -112,21 +115,42 @@ class ClockManager { // Reset only when all multipliers have been met bool reset = 1; + int div_count = 1; + + for (int ch = 0; ch < 2; ch++) { + if (tocks_per_beat[ch] < 0) + div_count = div_count * ( 1 - tocks_per_beat[ch] ); + } // count and calculate Tocks for (int ch = 0; ch < 3; ch++) { - uint32_t next_tock_tick = beat_tick[ch] + count[ch]*ticks_per_beat / static_cast(tocks_per_beat[ch]); - tock[ch] = now >= next_tock_tick; - if (tock[ch]) ++count[ch]; // increment multiplier counter + if (tocks_per_beat[ch] == 0) { // disabled + tock[ch] = 0; continue; + } + + if (tocks_per_beat[ch] > 0) { // multiply + uint32_t next_tock_tick = beat_tick[ch] + count[ch]*ticks_per_beat / static_cast(tocks_per_beat[ch]); + tock[ch] = now >= next_tock_tick; + if (tock[ch]) ++count[ch]; // increment multiplier counter + + reset = reset && (count[ch] > tocks_per_beat[ch]); // multiplier has been exceeded + } else { // division: -1 becomes /2, -2 becomes /3, etc. + int div = 1 - tocks_per_beat[ch]; + bool beat_exceeded = (now >= (beat_tick[ch] + count[ch] * ticks_per_beat)); + if (beat_exceeded) ++count[ch]; + + tock[ch] = beat_exceeded && ((count[ch] % div) == 1); + + reset = reset && (count[ch] > div_count); + } - reset = reset && (count[ch] > tocks_per_beat[ch]); // multiplier has been exceeded } - if (reset) Reset(1); // skip one + if (reset) Reset(1); // skip the one we're already on // handle syncing to physical clocks if (clocked && clock_tick) { - uint32_t clock_diff = now - clock_tick; // 4 PPQN clock input + uint32_t clock_diff = now - clock_tick; if (clock_ppqn * clock_diff > CLOCK_TICKS_MAX) clock_tick = 0; // too slow, reset clock tracking // if there is a previous clock tick, update tempo and sync @@ -136,7 +160,6 @@ class ClockManager { tempo = 1000000 / ticks_per_beat; // imprecise, for display purposes int ticks_per_clock = ticks_per_beat / clock_ppqn; // rounded down - //int clock_err = ticks_per_beat % CLOCK_PPQN; // rounding error // time since last beat int tick_offset = now - beat_tick[2]; From 7a8c102e4ae1b9972b31b67ee25b956064493cf3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 22 Jan 2023 23:22:56 -0500 Subject: [PATCH 132/417] SequenceX: modal editing, fix save/load --- software/o_c_REV/HEM_SequenceX.ino | 54 +++++++++++++----------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/software/o_c_REV/HEM_SequenceX.ino b/software/o_c_REV/HEM_SequenceX.ino index 8283c652d..cf3ad4068 100644 --- a/software/o_c_REV/HEM_SequenceX.ino +++ b/software/o_c_REV/HEM_SequenceX.ino @@ -25,6 +25,7 @@ // DON'T GO PAST 8! #define SEQX_STEPS 8 +#define SEQX_MAX_VALUE 31 class SequenceX : public HemisphereApplet { public: @@ -34,7 +35,7 @@ public: } void Start() { - for (int s = 0; s < SEQX_STEPS; s++) note[s] = random(0, 30); + for (int s = 0; s < SEQX_STEPS; s++) note[s] = random(0, SEQX_MAX_VALUE); } void Controller() { @@ -64,16 +65,20 @@ public: } void OnButtonPress() { - if (++cursor == SEQX_STEPS) cursor = 0; + isEditing = !isEditing; } void OnEncoderMove(int direction) { - if (note[cursor] + direction < 0 && cursor > 0) { - // If turning past zero, set the mute bit for this step - muted |= (0x01 << cursor); + if (!isEditing) { + cursor = constrain(cursor + direction, 0, SEQX_STEPS-1); } else { - note[cursor] = constrain(note[cursor] + direction, 0, 30); - muted &= ~(0x01 << cursor); + if (note[cursor] + direction < 0 && cursor > 0) { + // If turning past zero, set the mute bit for this step + muted |= (0x01 << cursor); + } else { + note[cursor] = constrain(note[cursor] + direction, 0, SEQX_MAX_VALUE); + muted &= ~(0x01 << cursor); + } } } @@ -83,7 +88,7 @@ public: { Pack(data, PackLocation {uint8_t(s * 5),5}, note[s]); } - Pack(data, PackLocation{25,5}, muted); + Pack(data, PackLocation{SEQX_STEPS * 5, SEQX_STEPS}, muted); return data; } @@ -92,7 +97,7 @@ public: { note[s] = Unpack(data, PackLocation {uint8_t(s * 5),5}); } - muted = Unpack(data, PackLocation {25,5}); + muted = Unpack(data, PackLocation {SEQX_STEPS * 5, SEQX_STEPS}); } protected: @@ -111,6 +116,7 @@ private: int note[SEQX_STEPS]; // Sequence value (0 - 30) int step = 0; // Current sequencer step bool reset = true; + bool isEditing = false; void Advance(int starting_point) { if (++step == SEQX_STEPS) step = 0; @@ -126,36 +132,24 @@ private: int x = 6 + (7 * s); // APD: narrower to fit more if (!step_is_muted(s)) { - gfxLine(x, 25, x, 63); + gfxLine(x, 25, x, 63, (s != cursor) ); // dotted line for unselected steps - // When cursor, there's a heavier bar and a solid slider + // When cursor, there's a solid slider if (s == cursor) { - gfxLine(x + 1, 25, x + 1, 63); - //gfxRect(x - 4, BottomAlign(note[s]), 9, 3); gfxRect(x - 2, BottomAlign(note[s]), 5, 3); // APD - } else - { - //gfxFrame(x - 4, BottomAlign(note[s]), 9, 3); - gfxFrame(x - 2, BottomAlign(note[s]), 5, 3); // APD + } else { + gfxFrame(x - 2, BottomAlign(note[s]), 5, 3); // APD } // When on this step, there's an indicator circle - if (s == step) - { - gfxCircle(x, 20, 3); //Original - - // APD - //int play_note = note[step];// + 60 + transpose; - - //gfxPrint(10, 15, "Scale "); - //gfxPrint(cursor < 12 ? 1 : 2); - //gfxPrint(x, 20, play_note); - + if (s == step) { + gfxCircle(x, 20, 3); } } else if (s == cursor) { gfxLine(x, 25, x, 63); - gfxLine(x + 1, 25, x + 1, 63); - } + } + + if (s == cursor && isEditing) gfxInvert(x - 2, 25, 5, 39); } } From da48a712b1423a2e80753e80e2856b9d73e65af1 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 22 Jan 2023 23:23:55 -0500 Subject: [PATCH 133/417] Calculate: move init stuff out of Start() to prevent crash --- software/o_c_REV/HEM_Calculate.ino | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/HEM_Calculate.ino b/software/o_c_REV/HEM_Calculate.ino index e508e296c..7e668c132 100644 --- a/software/o_c_REV/HEM_Calculate.ino +++ b/software/o_c_REV/HEM_Calculate.ino @@ -41,16 +41,6 @@ public: operation[ch] = ch; rand_clocked[ch] = 0; } - const char * op_name_list[] = {"Min", "Max", "Sum", "Diff", "Mean", "S&H", "Rnd"}; - // hem_MIN goes in the Rand and S&H slots, because those are handled in Controller() and - // don't need functions. But providing 0 isn't safe because the encoder change can - // happen any time and cause the system to try to run one of those null functions. - CalcFunction calc_fn_list[] = {hem_MIN, hem_MAX, hem_SUM, hem_DIFF, hem_MEAN, hem_MIN, hem_MIN}; - for(int i = 0; i < HEMISPHERE_NUMBER_OF_CALC; i++) - { - op_name[i] = op_name_list[i]; - calc_fn[i] = calc_fn_list[i]; - } } void Controller() { @@ -114,8 +104,11 @@ protected: } private: - const char* op_name[HEMISPHERE_NUMBER_OF_CALC]; - CalcFunction calc_fn[HEMISPHERE_NUMBER_OF_CALC]; + const char* op_name[HEMISPHERE_NUMBER_OF_CALC] = {"Min", "Max", "Sum", "Diff", "Mean", "S&H", "Rnd"}; + // hem_MIN goes in the Rand and S&H slots, because those are handled in Controller() and + // don't need functions. But providing 0 isn't safe because the encoder change can + // happen any time and cause the system to try to run one of those null functions. + CalcFunction calc_fn[HEMISPHERE_NUMBER_OF_CALC] = {hem_MIN, hem_MAX, hem_SUM, hem_DIFF, hem_MEAN, hem_MIN, hem_MIN}; int hold[2]; int operation[2]; int selected; From 3612baec4ca3ef0a4e98cebfdc9e59b8385894f1 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 23 Jan 2023 00:12:17 -0500 Subject: [PATCH 134/417] Modal edit refactoring Use gfxCursor for highlighting. ProbDiv and ProbMeloD got the modal treatment here as well. --- software/o_c_REV/HEM_AttenuateOffset.ino | 25 ++- software/o_c_REV/HEM_BugCrack.ino | 75 ++++---- software/o_c_REV/HEM_CVRecV2.ino | 58 +++--- software/o_c_REV/HEM_Chordinator.ino | 6 +- software/o_c_REV/HEM_ClockSetup.ino | 40 ++--- software/o_c_REV/HEM_DrumMap.ino | 105 ++++++----- software/o_c_REV/HEM_DualQuant.ino | 32 ++-- software/o_c_REV/HEM_EbbAndLfo.ino | 69 +++---- software/o_c_REV/HEM_EuclidX.ino | 55 +++--- software/o_c_REV/HEM_LoFiPCM.ino | 38 ++-- software/o_c_REV/HEM_ProbabilityDivider.ino | 40 +++-- software/o_c_REV/HEM_ProbabilityMelody.ino | 66 +++---- software/o_c_REV/HEM_RndWalk.ino | 24 ++- software/o_c_REV/HEM_Scope.ino | 1 - software/o_c_REV/HEM_SequenceX.ino | 1 - software/o_c_REV/HEM_Squanch.ino | 3 +- software/o_c_REV/HEM_TB3PO.ino | 189 ++++++++++---------- software/o_c_REV/HEM_TM2.ino | 81 ++++----- software/o_c_REV/HEM_Voltage.ino | 23 ++- software/o_c_REV/HSicons.h | 1 + software/o_c_REV/HemisphereApplet.h | 6 +- 21 files changed, 466 insertions(+), 472 deletions(-) diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index 3ede6d05a..3795b0645 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -66,17 +66,18 @@ public: if (!isEditing) { cursor = constrain(cursor + direction, 0, 4); ResetCursor(); + return; + } + + uint8_t ch = cursor / 2; + if (cursor == 0 || cursor == 2) { + // Change offset voltage + int min = -HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; + int max = HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; + offset[ch] = constrain(offset[ch] + direction, min, max); } else { - uint8_t ch = cursor / 2; - if (cursor == 0 || cursor == 2) { - // Change offset voltage - int min = -HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; - int max = HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; - offset[ch] = constrain(offset[ch] + direction, min, max); - } else { - // Change level percentage - level[ch] = constrain(level[ch] + direction, -63, 63); - } + // Change level percentage + level[ch] = constrain(level[ch] + direction, -63, 63); } } @@ -110,7 +111,6 @@ protected: private: int cursor; - bool isEditing = false; int level[2]; int offset[2]; bool mix = false; @@ -136,8 +136,7 @@ private: gfxFrame(0, 24, 9, 10); } } else { - isEditing ? gfxInvert(12, 14 + cursor * 10, 37, 9) - : gfxCursor(12, 23 + cursor * 10, 37); + gfxCursor(12, 23 + cursor * 10, 37); } } diff --git a/software/o_c_REV/HEM_BugCrack.ino b/software/o_c_REV/HEM_BugCrack.ino index dadcae0f2..2f68775f9 100644 --- a/software/o_c_REV/HEM_BugCrack.ino +++ b/software/o_c_REV/HEM_BugCrack.ino @@ -230,43 +230,45 @@ public: void OnEncoderMove(int direction) { if (!isEditing) { cursor = constrain(cursor + direction, 0, 9); - } else { - switch (cursor) { - // Kick drum - case 0: - tone_kick = constrain(tone_kick + direction, 0, BNC_MAX_PARAM); - break; - case 1: - decay_kick = constrain(decay_kick + direction, 0, BNC_MAX_PARAM); - break; - case 2: - punch = constrain(punch + direction, 0, BNC_MAX_PARAM); - break; - case 3: - decay_punch = constrain(decay_punch + direction, 0, BNC_MAX_PARAM); - break; - case 4: - cv_mode_kick = constrain(cv_mode_kick + direction, 0, 4); - break; + return; + } + + switch (cursor) { + // Kick drum + case 0: + tone_kick = constrain(tone_kick + direction, 0, BNC_MAX_PARAM); + break; + case 1: + decay_kick = constrain(decay_kick + direction, 0, BNC_MAX_PARAM); + break; + case 2: + punch = constrain(punch + direction, 0, BNC_MAX_PARAM); + break; + case 3: + decay_punch = constrain(decay_punch + direction, 0, BNC_MAX_PARAM); + break; + case 4: + cv_mode_kick = constrain(cv_mode_kick + direction, 0, 4); + break; + + // Snare drum + case 5: + tone_snare = constrain(tone_snare + direction, 0, BNC_MAX_PARAM); + break; + case 6: + decay_snare = constrain(decay_snare + direction, 0, BNC_MAX_PARAM); + break; + case 7: + snap = constrain(snap + direction, 0, BNC_MAX_PARAM); + break; + case 8: + blend_snare = constrain(blend_snare + direction, 0, BNC_MAX_PARAM); + break; + case 9: + cv_mode_snare = constrain(cv_mode_snare + direction, 0, 4); + break; + } - // Snare drum - case 5: - tone_snare = constrain(tone_snare + direction, 0, BNC_MAX_PARAM); - break; - case 6: - decay_snare = constrain(decay_snare + direction, 0, BNC_MAX_PARAM); - break; - case 7: - snap = constrain(snap + direction, 0, BNC_MAX_PARAM); - break; - case 8: - blend_snare = constrain(blend_snare + direction, 0, BNC_MAX_PARAM); - break; - case 9: - cv_mode_snare = constrain(cv_mode_snare + direction, 0, 4); - break; - } - } // isEditing } uint64_t OnDataRequest() { @@ -314,7 +316,6 @@ protected: private: int cursor = 0; - bool isEditing = false; VectorOscillator kick; VectorOscillator env_kick; VectorOscillator env_punch; diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index d70e92602..e42c553e0 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -96,33 +96,36 @@ public: return; } - isEditing = 1 - isEditing; // toggle editing + isEditing = !isEditing; // toggle editing if (cursor == 3 && !isEditing) { // activate recording if selected punch_out = (mode > 0) ? end - start + 1 : 0; } } void OnEncoderMove(int direction) { - if (isEditing) { - if (cursor == 0) { - int16_t fs = start; // Former start value - start = constrain(start + direction, 0, end - 1); - if (fs != start && punch_out) punch_out -= direction; - } - if (cursor == 1) { - int16_t fe = end; // Former end value - end = constrain(end + direction, start + 1, CVREC_MAX_STEP - 1); - if (fe != end && punch_out) punch_out += direction; - } - //if (cursor == 2) smooth = direction > 0 ? 1 : 0; - if (cursor == 3) mode = constrain(mode + direction, 0, 3); - + if (!isEditing) { //not editing, move cursor + cursor = constrain(cursor + direction, 0, 3); + ResetCursor(); return; } - //not editing, move cursor - cursor = constrain(cursor + direction, 0, 3); - ResetCursor(); + switch (cursor) { + case 0: { + int16_t fs = start; // Former start value + start = constrain(start + direction, 0, end - 1); + if (fs != start && punch_out) punch_out -= direction; + break; + } + case 1: { + int16_t fe = end; // Former end value + end = constrain(end + direction, start + 1, CVREC_MAX_STEP - 1); + if (fe != end && punch_out) punch_out += direction; + break; + } + case 3: + mode = constrain(mode + direction, 0, 3); + break; + } } uint64_t OnDataRequest() { @@ -157,7 +160,6 @@ private: simfloat rise[2]; simfloat signal[2]; bool smooth; - bool isEditing = 0; bool reset = true; // Transport @@ -181,13 +183,6 @@ private: // Record Mode gfxPrint(1, 35, CVRecV2_MODES[mode]); - // Cursor - switch(cursor){ - case 0: gfxCursor(19, 23, 18); break; - case 1: gfxCursor(43, 23, 18); break; - case 3: gfxCursor(1, 43, 63); break; - } - // Status icon if (mode > 0 && punch_out > 0) { if (!CursorBlink()) gfxIcon(54, 35, RECORD_ICON); @@ -197,12 +192,11 @@ private: // Record time indicator if (punch_out > 0) gfxInvert(0, 34, punch_out / 6, 9); - if (isEditing) { - switch(cursor){ - case 0: gfxInvert(19, 14, 18, 9); break; - case 1: gfxInvert(43, 14, 18, 9); break; - case 3: gfxInvert(1, 34, 63, 9); break; - } + // Cursor + switch(cursor){ + case 0: gfxCursor(19, 23, 18); break; + case 1: gfxCursor(43, 23, 18); break; + case 3: gfxCursor(1, 43, 63); break; } // Step indicator diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index b48541d96..a61a039be 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -68,13 +68,11 @@ public: gfxPrint(0, 15, OC::scale_names_short[scale]); if (cursor == 0) { - gfxCursor(0, 23, 30); - if (selected) gfxInvert(0, 14, 30, 9); + gfxCursor(0, 23, 30, selected); } gfxPrint(36, 15, OC::Strings::note_names_unpadded[root]); if (cursor == 1) { - gfxCursor(36, 23, 12); - if (selected) gfxInvert(36, 14, 12, 9); + gfxCursor(36, 23, 12, selected); } uint16_t mask = chord_mask; diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index f2238cf5a..ce5c5497e 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -69,22 +69,23 @@ public: if (!isEditing) { cursor = (ClockSetupCursor) constrain(cursor + direction, 0, LAST_SETTING-1); ResetCursor(); - } else { - switch (cursor) { - case EXT_PPQN: - clock_m->SetClockPPQN(clock_m->GetClockPPQN() + direction); - break; - case TEMPO: - clock_m->SetTempoBPM(clock_m->GetTempo() + direction); - break; - - case MULT1: - case MULT2: - clock_m->SetMultiply(clock_m->GetMultiply(cursor - MULT1) + direction, cursor - MULT1); - break; - - default: break; - } + return; + } + + switch (cursor) { + case EXT_PPQN: + clock_m->SetClockPPQN(clock_m->GetClockPPQN() + direction); + break; + case TEMPO: + clock_m->SetTempoBPM(clock_m->GetTempo() + direction); + break; + + case MULT1: + case MULT2: + clock_m->SetMultiply(clock_m->GetMultiply(cursor - MULT1) + direction, cursor - MULT1); + break; + + default: break; } } @@ -121,7 +122,6 @@ protected: private: ClockSetupCursor cursor; // 0=Play/Stop, 1=Forwarding, 2=External PPQN, 3=Tempo, 4,5=Multiply - bool isEditing = false; bool start_q; bool stop_q; ClockManager *clock_m = clock_m->get(); @@ -183,16 +183,16 @@ private: case FORWARDING: gfxFrame(49, 14, 10, 10); break; case EXT_PPQN: - isEditing ? gfxInvert(100,14, 12,9) : gfxCursor(100,23, 12); + gfxCursor(100,23, 12); break; case TEMPO: - isEditing ? gfxInvert(22, 25, 18, 9) : gfxCursor(22, 34, 18); + gfxCursor(22, 34, 18); break; case MULT1: case MULT2: - isEditing ? gfxInvert(8 + 64*(cursor-MULT1), 36, 12, 9) : gfxCursor(8 + 64*(cursor-MULT1), 45, 12); + gfxCursor(8 + 64*(cursor-MULT1), 45, 12); break; case TRIG1: diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index 719f766f2..c515c0f09 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -137,55 +137,56 @@ public: if (mode[1] > 2 && cursor == 3) cursor += direction; cursor = constrain(cursor, 0, 7); ResetCursor(); - } else { - int accel = knob_accel >> 8; - // modes - switch (cursor) { - case 0: - mode[0] += direction; - if (mode[0] > 2) mode[0] = 0; - if (mode[0] < 0) mode[0] = 2; - break; - case 1: - mode[1] += direction; - if (mode[1] > 3) mode[1] = 0; - if (mode[1] < 0) mode[1] = 3; - break; - // fill - case 2: - fill[0] = constrain(fill[0] + (direction * accel), 0, 255); - break; - case 3: - fill[1] = constrain(fill[1] + (direction * accel), 0, 255); - break; - // x/y - case 4: - x = constrain(x + (direction * accel), 0, 255); - break; - case 5: - y = constrain(y + (direction * accel), 0, 255); - break; - // chaos - case 6: - chaos = constrain(chaos + (direction * accel), 0, 255); - break; - // cv assign - case 7: - cv_mode += direction; - if (cv_mode > 2) cv_mode = 0; - if (cv_mode < 0) cv_mode = 2; - break; - } + return; + } - // knob acceleration and value display for slider params - if (cursor >= 2 && cursor <= 6 && knob_accel < 2049) { - if (knob_accel < 300) { - knob_accel = knob_accel << 1; - } - knob_accel = knob_accel << 2; - value_animation = HEM_DRUMMAP_VALUE_ANIMATION_TICKS; - } - } // isEditing + int accel = knob_accel >> 8; + // modes + switch (cursor) { + case 0: + mode[0] += direction; + if (mode[0] > 2) mode[0] = 0; + if (mode[0] < 0) mode[0] = 2; + break; + case 1: + mode[1] += direction; + if (mode[1] > 3) mode[1] = 0; + if (mode[1] < 0) mode[1] = 3; + break; + // fill + case 2: + fill[0] = constrain(fill[0] + (direction * accel), 0, 255); + break; + case 3: + fill[1] = constrain(fill[1] + (direction * accel), 0, 255); + break; + // x/y + case 4: + x = constrain(x + (direction * accel), 0, 255); + break; + case 5: + y = constrain(y + (direction * accel), 0, 255); + break; + // chaos + case 6: + chaos = constrain(chaos + (direction * accel), 0, 255); + break; + // cv assign + case 7: + cv_mode += direction; + if (cv_mode > 2) cv_mode = 0; + if (cv_mode < 0) cv_mode = 2; + break; + } + + // knob acceleration and value display for slider params + if (cursor >= 2 && cursor <= 6 && knob_accel < 2049) { + if (knob_accel < 300) { + knob_accel = knob_accel << 1; + } + knob_accel = knob_accel << 2; + value_animation = HEM_DRUMMAP_VALUE_ANIMATION_TICKS; + } } uint64_t OnDataRequest() { @@ -227,7 +228,6 @@ private: const char *CV_MODE_NAMES[3] = {"FILL A/B", "X/Y", "FA/CHAOS"}; const int *VALUE_MAP[5] = {&fill[0], &fill[1], &x, &y, &chaos}; int cursor = 0; - bool isEditing = false; uint8_t step; uint8_t randomness[3] = {0, 0, 0}; int pulse_animation[2] = {0, 0}; @@ -321,8 +321,8 @@ private: gfxPrint((step < 9 ? 49 : 43),2,step+1); // cursor for non-knobs - if (cursor <= 1) isEditing ? gfxInvert(14+cursor*31,14,16,9) - : gfxCursor(14+cursor*31,23,16); // Part A / B + if (cursor <= 1) + gfxCursor(14+cursor*31,23,16); // Part A / B // display value for knobs if (value_animation > 0 && cursor >= 2 && cursor <= 6) { @@ -339,8 +339,7 @@ private: // cv input assignment gfxIcon(1,57,CV_ICON); gfxPrint(10,55,CV_MODE_NAMES[cv_mode]); - if (cursor == 7) isEditing ? gfxInvert(10,54,50,9) - : gfxCursor(10,63,50); // CV Assign + if (cursor == 7) gfxCursor(10,63,50); // CV Assign } } diff --git a/software/o_c_REV/HEM_DualQuant.ino b/software/o_c_REV/HEM_DualQuant.ino index 5238e2a43..3f53c3e75 100644 --- a/software/o_c_REV/HEM_DualQuant.ino +++ b/software/o_c_REV/HEM_DualQuant.ino @@ -73,19 +73,20 @@ public: if (!isEditing) { cursor = constrain(cursor + direction, 0, 3); ResetCursor(); + return; + } + + uint8_t ch = cursor / 2; + if (cursor == 0 || cursor == 2) { + // Scale selection + scale[ch] += direction; + if (scale[ch] >= OC::Scales::NUM_SCALES) scale[ch] = 0; + if (scale[ch] < 0) scale[ch] = OC::Scales::NUM_SCALES - 1; + quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + continuous[ch] = 1; // Re-enable continuous mode when scale is changed } else { - uint8_t ch = cursor / 2; - if (cursor == 0 || cursor == 2) { - // Scale selection - scale[ch] += direction; - if (scale[ch] >= OC::Scales::NUM_SCALES) scale[ch] = 0; - if (scale[ch] < 0) scale[ch] = OC::Scales::NUM_SCALES - 1; - quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); - continuous[ch] = 1; // Re-enable continuous mode when scale is changed - } else { - // Root selection - root[ch] = constrain(root[ch] + direction, 0, 11); - } + // Root selection + root[ch] = constrain(root[ch] + direction, 0, 11); } } @@ -125,7 +126,6 @@ private: int last_note[2]; // Last quantized note bool continuous[2]; // Each channel starts as continuous and becomes clocked when a clock is received int cursor; - bool isEditing = false; // Settings int scale[2]; // Scale per channel @@ -133,8 +133,7 @@ private: void DrawSelector() { - const uint8_t notes[2][8] = {{0xc0, 0xe0, 0xe0, 0xe0, 0x7f, 0x02, 0x14, 0x08}, - {0xc0, 0xa0, 0xa0, 0xa0, 0x7f, 0x00, 0x00, 0x00}}; + const uint8_t * notes[2] = {NOTE_ICON, NOTE2_ICON}; ForEachChannel(ch) { @@ -146,8 +145,7 @@ private: // Draw cursor int y = cursor % 2; // 0=top line, 1=bottom if (ch == (cursor / 2)) { - isEditing ? gfxInvert(y*10 + ch*31, 14 + y*10, 12+(1-y)*18, 9) - : gfxCursor(y*10 + ch*31, 23 + y*10, 12+(1-y)*18); + gfxCursor(y*10 + ch*31, 23 + y*10, 12+(1-y)*18); } // Little note display diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index df49b97d7..46d24f217 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -113,40 +113,42 @@ public: } void OnEncoderMove(int direction) { - if (!isEditing) cursor = constrain(cursor + direction, 0, 4); - else { - switch (cursor) { - case 0: { - uint32_t old_pi = ComputePhaseIncrement(pitch); - pitch += (knob_accel >> 8) * direction; - while (ComputePhaseIncrement(pitch) == old_pi) { - pitch += direction; - } - break; - } - case 1: { - // slope += (knob_accel >> 4) * direction; - slope = constrain(slope + direction, 0, 127); - break; - } - case 2: { - shape += direction; - while (shape < 0) shape += 128; - while (shape > 127) shape -= 128; - break; - } - case 3: { - fold = constrain(fold + direction, 0, 127); - break; - } - case 4: { - out += direction; - out %= 0b10000; - } - } - if (knob_accel < (1 << 13)) - knob_accel <<= 1; + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 4); + return; + } + + switch (cursor) { + case 0: { + uint32_t old_pi = ComputePhaseIncrement(pitch); + pitch += (knob_accel >> 8) * direction; + while (ComputePhaseIncrement(pitch) == old_pi) { + pitch += direction; + } + break; + } + case 1: { + // slope += (knob_accel >> 4) * direction; + slope = constrain(slope + direction, 0, 127); + break; + } + case 2: { + shape += direction; + while (shape < 0) shape += 128; + while (shape > 127) shape -= 128; + break; + } + case 3: { + fold = constrain(fold + direction, 0, 127); + break; + } + case 4: { + out += direction; + out %= 0b10000; + } } + if (knob_accel < (1 << 13)) + knob_accel <<= 1; } uint64_t OnDataRequest() { @@ -198,7 +200,6 @@ private: const char* cv_labels[4] = {"Hz", "Sl", "Sh", "Fo"}; int cursor = 0; - bool isEditing = false; int16_t pitch = -3 * 12 * 128; int slope = 64; int slope_mod = 64; // actual value after CV mod diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 511ae7a6d..6b56e69c9 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -119,29 +119,30 @@ public: if (!isEditing) { cursor = constrain(cursor + direction, 0, NUM_PARAMS*2 - 1); ResetCursor(); - } else { - int ch = cursor < NUM_PARAMS ? 0 : 1; - int f = cursor - (ch * NUM_PARAMS); // Cursor function - switch (f) { - case 0: - actual_length[ch] = length[ch] = constrain(length[ch] + direction, 2, 32); - if (beats[ch] > length[ch]) beats[ch] = length[ch]; - if (padding[ch] > 32 - length[ch]) padding[ch] = 32 - length[ch]; - if (offset[ch] >= length[ch] + padding[ch]) offset[ch] = length[ch] + padding[ch] - 1; - break; - case 1: - actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); - break; - case 2: - actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] + padding[ch] - 1); - break; - case 3: - padding[ch] = constrain(padding[ch] + direction, 0, 32 - length[ch]); - break; - case 4: // CV destination - cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, (NUM_PARAMS-1)*2 - 1); - break; - } + return; + } + + int ch = cursor < NUM_PARAMS ? 0 : 1; + int f = cursor - (ch * NUM_PARAMS); // Cursor function + switch (f) { + case 0: + actual_length[ch] = length[ch] = constrain(length[ch] + direction, 2, 32); + if (beats[ch] > length[ch]) beats[ch] = length[ch]; + if (padding[ch] > 32 - length[ch]) padding[ch] = 32 - length[ch]; + if (offset[ch] >= length[ch] + padding[ch]) offset[ch] = length[ch] + padding[ch] - 1; + break; + case 1: + actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); + break; + case 2: + actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] + padding[ch] - 1); + break; + case 3: + padding[ch] = constrain(padding[ch] + direction, 0, 32 - length[ch]); + break; + case 4: // CV destination + cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, (NUM_PARAMS-1)*2 - 1); + break; } } @@ -186,7 +187,6 @@ protected: private: int step; int cursor = 0; // Ch1: 0=Length, 1=Hits; Ch2: 2=Length 3=Hits - bool isEditing = false; uint32_t pattern[2]; // Settings @@ -245,12 +245,11 @@ private: case 1: case 2: case 3: - gfxCursor(3 + f * spacing, y + 7, 13); - if (isEditing) gfxInvert(3+f*spacing, y-1, 13, 9); + gfxCursor(3 + f * spacing, y + 8, 13); break; case 4: // CV dest selection - gfxBitmap(0, 13+ch*3, 8, CV_ICON); - if (isEditing) gfxInvert(0, 13+ch*3, 8, 6); + gfxBitmap(0, 13+ch*5, 8, CV_ICON); + if (isEditing) gfxInvert(0, 13+ch*5, 8, 6); break; } diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index fb926edfd..a302f6336 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -89,21 +89,22 @@ public: if (!isEditing) { cursor = constrain(cursor + direction, 0, 3); ResetCursor(); - } else { - switch (cursor) { - case 0: - dt_pct = constrain(dt_pct + direction, 0, 99); - break; - case 1: - feedback = constrain(feedback + direction, 0, 125); - break; - case 2: - rate = constrain(rate + direction, 1, 32); - break; - case 3: - depth = constrain(depth + direction, 0, 13); - break; - } + return; + } + + switch (cursor) { + case 0: + dt_pct = constrain(dt_pct + direction, 0, 99); + break; + case 1: + feedback = constrain(feedback + direction, 0, 125); + break; + case 2: + rate = constrain(rate + direction, 1, 32); + break; + case 3: + depth = constrain(depth + direction, 0, 13); + break; } } @@ -149,7 +150,6 @@ private: uint8_t rate_mod = rate; int depth = 0; // bit reduction depth aka bitcrush int cursor; //for gui - bool isEditing = false; void DrawWaveform() { int inc = rate_mod/2 + 1; @@ -173,8 +173,7 @@ private: } gfxPrint(4 + pad(100, dt_pct), 15, dt_pct); gfxPrint(36 + pad(1000, fdbk_g), 15, fdbk_g); - isEditing ? gfxInvert(10 + 31 * cursor, 14, 20, 9) - : gfxCursor(10 + 31 * cursor, 23, 20); + gfxCursor(10 + 31 * cursor, 23, 20); } else { gfxIcon(0, 15, WAVEFORM_ICON); gfxIcon(8, 15, BURST_ICON); @@ -182,8 +181,7 @@ private: gfxPrint(30, 15, rate_mod); gfxIcon(42, 15, UP_DOWN_ICON); gfxPrint(50, 15, depth); - isEditing ? gfxInvert(30 + (cursor-2)*20, 14, 14, 9) - : gfxCursor(30 + (cursor-2)*20, 23, 14); + gfxCursor(30 + (cursor-2)*20, 23, 14); } } diff --git a/software/o_c_REV/HEM_ProbabilityDivider.ino b/software/o_c_REV/HEM_ProbabilityDivider.ino index cb1d22655..f12b3e3e2 100644 --- a/software/o_c_REV/HEM_ProbabilityDivider.ino +++ b/software/o_c_REV/HEM_ProbabilityDivider.ino @@ -26,6 +26,12 @@ class ProbabilityDivider : public HemisphereApplet { public: + enum ProbDivCursor { + WEIGHT1, WEIGHT2, WEIGHT4, WEIGHT8, + LOOP_LENGTH, + LAST_SETTING + }; + const char* applet_name() { return "ProbDiv"; } @@ -131,23 +137,34 @@ public: } void OnButtonPress() { - if (++cursor > 4) cursor = 0; + isEditing = !isEditing; } void OnEncoderMove(int direction) { - if (cursor == 0) weight_1 = constrain(weight_1 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); - if (cursor == 1) weight_2 = constrain(weight_2 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); - if (cursor == 2) weight_4 = constrain(weight_4 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); - if (cursor == 3) weight_8 = constrain(weight_8 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); - if (cursor == 4) { + if (!isEditing) { + cursor = (ProbDivCursor) constrain(cursor + direction, 0, LAST_SETTING-1); + ResetCursor(); + return; + } + + switch (cursor) { + case WEIGHT1: weight_1 = constrain(weight_1 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); break; + case WEIGHT2: weight_2 = constrain(weight_2 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); break; + case WEIGHT4: weight_4 = constrain(weight_4 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); break; + case WEIGHT8: weight_8 = constrain(weight_8 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); break; + case LOOP_LENGTH: { int old = loop_length; loop_length = constrain(loop_length + direction, 0, HEM_PROB_DIV_MAX_LOOP_LENGTH); if (old == 0 && loop_length > 0) { // seed loop GenerateLoop(true); } + break; } - if (cursor < 4 && loop_length > 0) { + default: break; + } + + if (cursor < LOOP_LENGTH && loop_length > 0) { GenerateLoop(false); } } @@ -187,7 +204,7 @@ protected: } private: - int cursor; + ProbDivCursor cursor; int weight_1; int weight_2; int weight_4; @@ -234,7 +251,7 @@ private: } else { gfxPrint(19, 55, loop_length); } - if (cursor == 4) gfxCursor(19, 63, 18); + if (cursor == LOOP_LENGTH) gfxCursor(19, 63, 18); if (reset_animation > 0) { gfxPrint(52, 55, "R"); @@ -243,9 +260,10 @@ private: void DrawKnobAt(byte x, byte y, byte len, byte value, bool is_cursor) { byte p = is_cursor ? 1 : 3; - byte w = Proportion(value, HEM_PROB_DIV_MAX_WEIGHT, len); - gfxDottedLine(x, y + 4, x + len, y + 4, p); + byte w = Proportion(value, HEM_PROB_DIV_MAX_WEIGHT, len-1); + gfxDottedLine(x, y + 3, x + len, y + 3, p); gfxRect(x + w, y, 2, 7); + if (isEditing && is_cursor) gfxInvert(x-1, y, len+3, 7); } int GetNextWeightedDiv() { diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index 88fd199b3..953629e9c 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -27,6 +27,12 @@ class ProbabilityMelody : public HemisphereApplet { public: + enum ProbMeloCursor { + LOWER, UPPER, + FIRST_NOTE = 2, + LAST_NOTE = 13 + }; + const char* applet_name() { return "ProbMeloD"; } @@ -110,23 +116,23 @@ public: void OnEncoderMove(int direction) { if (!isEditing) { - cursor += direction; - if (cursor < 0) cursor = 13; - if (cursor > 13) cursor = 0; + cursor = (ProbMeloCursor) constrain(cursor + direction, 0, LAST_NOTE); ResetCursor(); + return; + } + + if (cursor >= FIRST_NOTE) { + // editing note probability + int i = cursor - FIRST_NOTE; + weights[i] = constrain(weights[i] + direction, 0, HEM_PROB_MEL_MAX_WEIGHT); + value_animation = HEMISPHERE_CURSOR_TICKS; } else { - if (cursor < 12) { - // editing note probability - weights[cursor] = constrain(weights[cursor] + direction, 0, HEM_PROB_MEL_MAX_WEIGHT); - value_animation = HEMISPHERE_CURSOR_TICKS; - } else { - // editing scaling - if (cursor == 12) down = constrain(down + direction, 1, up); - if (cursor == 13) up = constrain(up + direction, down, 60); - } - if (isLooping) { - GenerateLoop(); // regenerate loop on any param changes - } + // editing scaling + if (cursor == LOWER) down = constrain(down + direction, 1, up); + if (cursor == UPPER) up = constrain(up + direction, down, 60); + } + if (isLooping) { + GenerateLoop(); // regenerate loop on any param changes } } @@ -177,8 +183,7 @@ protected: } private: - int cursor; - bool isEditing = false; + ProbMeloCursor cursor; int weights[12] = {10,0,0,2,0,0,0,2,0,0,4,0}; int up; int down; @@ -255,7 +260,7 @@ private: if (pulse_animation > 0 && note == i) { gfxRect(xOffset - 1, yOffset, 3, 10); } else { - if (isEditing && i == cursor) { + if (isEditing && i == (cursor - FIRST_NOTE)) { // blink line when editing if (CursorBlink()) { gfxLine(xOffset, yOffset, xOffset, yOffset + 10); @@ -271,12 +276,11 @@ private: // cursor for keys if (!isEditing) { - if (cursor < 12) { - gfxCursor(x[cursor] - 1, p[cursor] ? 24 : 60, p[cursor] ? 5 : 6); - gfxCursor(x[cursor] - 1, p[cursor] ? 25 : 61, p[cursor] ? 5 : 6); + if (cursor >= FIRST_NOTE) { + int i = cursor - FIRST_NOTE; + gfxCursor(x[i] - 1, p[i] ? 24 : 60, p[i] ? 5 : 6); + gfxCursor(x[i] - 1, p[i] ? 25 : 61, p[i] ? 5 : 6); } - if (cursor == 12) gfxCursor(7, 23, 22); - if (cursor == 13) gfxCursor(37, 23, 22); } // scaling params @@ -291,10 +295,8 @@ private: gfxPrint(43, 15, "."); gfxPrint(47, 15, ((up - 1) % 12) + 1); - if (isEditing) { - if (cursor == 12) gfxInvert(9, 14, 21, 9); - if (cursor == 13) gfxInvert(38, 14, 21, 9); - } + if (cursor == LOWER) gfxCursor(9, 23, 21); + if (cursor == UPPER) gfxCursor(39, 23, 21); if (pulse_animation > 0) { // int note = pitch % 12; @@ -304,15 +306,17 @@ private: gfxRect(58, 54 - (octave * 6), 3, 3); } - if (value_animation > 0 && cursor < 12) { + if (value_animation > 0 && cursor >= FIRST_NOTE) { + int i = cursor - FIRST_NOTE; + gfxRect(1, 15, 60, 10); gfxInvert(1, 15, 60, 10); - gfxPrint(18, 16, n[cursor]); - if (p[cursor]) { + gfxPrint(18, 16, n[i]); + if (p[i]) { gfxPrint(24, 16, "#"); } - gfxPrint(34, 16, weights[cursor]); + gfxPrint(34, 16, weights[i]); gfxInvert(1, 15, 60, 10); } } diff --git a/software/o_c_REV/HEM_RndWalk.ino b/software/o_c_REV/HEM_RndWalk.ino index 30803abaf..25f5fd0a8 100644 --- a/software/o_c_REV/HEM_RndWalk.ino +++ b/software/o_c_REV/HEM_RndWalk.ino @@ -98,12 +98,16 @@ public: } void OnButtonPress() { - cursor++; - if (cursor > 5) cursor = 0; - ResetCursor(); + isEditing = !isEditing; } void OnEncoderMove(int direction) { + if (!isEditing) { + cursor = constrain(cursor + direction, 0, 5); + ResetCursor(); + return; + } + // Parameter Change handler // var cursor is the param pointer // var direction is the the movement of the encoder @@ -224,12 +228,14 @@ private: } } switch (cursor) { - case 0: gfxCursor(43, 22, 18); break; - case 1: gfxCursor(43, 32, 18); break; - case 2: gfxCursor(43, 42, 18); break; - case 3: gfxCursor(43, 22, 18); break; - case 4: gfxCursor(43, 32, 18); break; - case 5: gfxCursor(40, 42, 24); break; + case 0: + case 3: gfxCursor(43, 23, 18); break; + + case 1: + case 4: gfxCursor(43, 33, 18); break; + + case 2: gfxCursor(43, 43, 18); break; + case 5: gfxCursor(40, 43, 24); break; } // gfxPrint(1, 38, "x"); diff --git a/software/o_c_REV/HEM_Scope.ino b/software/o_c_REV/HEM_Scope.ino index 48c40d959..37ced1b92 100644 --- a/software/o_c_REV/HEM_Scope.ino +++ b/software/o_c_REV/HEM_Scope.ino @@ -148,7 +148,6 @@ private: // Scope int current_display; int current_setting; - bool isEditing = false; uint8_t snapshot[2][64]; int sample_ticks; // Ticks between samples int sample_countdown; // Last time a sample was taken diff --git a/software/o_c_REV/HEM_SequenceX.ino b/software/o_c_REV/HEM_SequenceX.ino index cf3ad4068..34d81bb73 100644 --- a/software/o_c_REV/HEM_SequenceX.ino +++ b/software/o_c_REV/HEM_SequenceX.ino @@ -116,7 +116,6 @@ private: int note[SEQX_STEPS]; // Sequence value (0 - 30) int step = 0; // Current sequencer step bool reset = true; - bool isEditing = false; void Advance(int starting_point) { if (++step == SEQX_STEPS) step = 0; diff --git a/software/o_c_REV/HEM_Squanch.ino b/software/o_c_REV/HEM_Squanch.ino index 4620b20f8..2f30f76d5 100644 --- a/software/o_c_REV/HEM_Squanch.ino +++ b/software/o_c_REV/HEM_Squanch.ino @@ -117,8 +117,7 @@ private: int16_t shift[2]; void DrawInterface() { - const uint8_t notes[2][8] = {{0xc0, 0xe0, 0xe0, 0xe0, 0x7f, 0x02, 0x14, 0x08}, - {0xc0, 0xa0, 0xa0, 0xa0, 0x7f, 0x00, 0x00, 0x00}}; + const uint8_t * notes[2] = {NOTE_ICON, NOTE2_ICON}; // Display icon if clocked if (!continuous) gfxIcon(56, 25, CLOCK_ICON); diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 2cc02874b..55caf1b01 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -269,105 +269,105 @@ class TB_3PO : public HemisphereApplet void OnEncoderMove(int direction) { if (!isEditing) { // move cursor - cursor += direction; - if (cursor < 0) cursor = 8; - if (cursor > 8) cursor = 0; + cursor = constrain(cursor + direction, 0, 8); if (!lock_seed && cursor == 1) cursor = 5; // skip from 1 to 5 if not locked if (!lock_seed && cursor == 4) cursor = 0; // skip from 4 to 0 if not locked ResetCursor(); // Reset blink so it's immediately visible when moved - } else { // edit param - switch(cursor) { - case 0: - // Toggle the seed between auto (randomized every reset input pulse) - // or Manual (seed becomes locked, cursor can be moved to edit each digit) - lock_seed += direction; - - // See if the turn would move beyond the random die to the left or the lock to the right - // If so, take this as a manual input just like receiving a reset pulse (handled in Controller()) - // regenerate_all() will honor the random or locked icon shown (seed will be randomized or not) - manual_reset_flag = (lock_seed > 1 || lock_seed < 0) ? 1 : 0; - - // constrain to legal values before regeneration - lock_seed = constrain(lock_seed, 0, 1); - break; - case 1: - case 2: - case 3: - case 4: { // Editing one of the 4 hex digits of the seed - // cursor==1 is at the most significant byte, - // cursor==4 is at least significant byte - int byte_offs = 4-cursor; - int shift_amt = byte_offs*4; - - uint32_t nib = (seed >> shift_amt)& 0xf; // Abduct the nibble - uint8_t c = nib; - c = constrain(c+direction, 0, 0xF); // Edit the nibble - nib = c; - uint32_t mask = 0xf; - seed &= ~(mask << shift_amt); // Clear bits where this nibble lives - seed |= (nib << shift_amt); // Move the nibble to its home - break; - } - case 5: // density - density_encoder = constrain(density_encoder + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice - density_encoder_display = 400; // How long to show the encoder version of density in the number display for - - //density = constrain(density + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice - - // Disabled: Let this occur when detected on the next step - //regenerate_density_if_changed(); - break; - case 6: { // Scale selection - scale += direction; - if (scale >= OC::Scales::NUM_SCALES) scale = 0; - if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - // Apply to the quantizer - set_quantizer_scale(scale); - - // New: Constrain root to scale size (leave oct offset where it is) - int max_root = scale_size > 12 ? 12 : scale_size; - if(max_root > 0) - { - root = constrain(root, 0, max_root-1); - } - break; - } - case 7: { // Root note selection + return; + } + + // edit param + switch(cursor) { + case 0: + // Toggle the seed between auto (randomized every reset input pulse) + // or Manual (seed becomes locked, cursor can be moved to edit each digit) + lock_seed += direction; + + // See if the turn would move beyond the random die to the left or the lock to the right + // If so, take this as a manual input just like receiving a reset pulse (handled in Controller()) + // regenerate_all() will honor the random or locked icon shown (seed will be randomized or not) + manual_reset_flag = (lock_seed > 1 || lock_seed < 0) ? 1 : 0; + + // constrain to legal values before regeneration + lock_seed = constrain(lock_seed, 0, 1); + break; + case 1: + case 2: + case 3: + case 4: { // Editing one of the 4 hex digits of the seed + // cursor==1 is at the most significant byte, + // cursor==4 is at least significant byte + int byte_offs = 4-cursor; + int shift_amt = byte_offs*4; + + uint32_t nib = (seed >> shift_amt)& 0xf; // Abduct the nibble + uint8_t c = nib; + c = constrain(c+direction, 0, 0xF); // Edit the nibble + nib = c; + uint32_t mask = 0xf; + seed &= ~(mask << shift_amt); // Clear bits where this nibble lives + seed |= (nib << shift_amt); // Move the nibble to its home + break; + } + case 5: // density + density_encoder = constrain(density_encoder + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice + density_encoder_display = 400; // How long to show the encoder version of density in the number display for + + //density = constrain(density + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice - // No oct version - //root = constrain(root + direction, 0, 11); + // Disabled: Let this occur when detected on the next step + //regenerate_density_if_changed(); + break; + case 6: { // Scale selection + scale += direction; + if (scale >= OC::Scales::NUM_SCALES) scale = 0; + if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; + // Apply to the quantizer + set_quantizer_scale(scale); + + // New: Constrain root to scale size (leave oct offset where it is) + int max_root = scale_size > 12 ? 12 : scale_size; + if(max_root > 0) + { + root = constrain(root, 0, max_root-1); + } + break; + } + case 7: { // Root note selection + + // No oct version + //root = constrain(root + direction, 0, 11); - // Add in handling for octave settings without affecting root's range - int r = root + direction; + // Add in handling for octave settings without affecting root's range + int r = root + direction; - int max_root = scale_size > 12 ? 12 : scale_size; - - //if(direction > 0 && r > 11 && octave_offset < 3) - if(direction > 0 && r >= max_root && octave_offset < 3) - { - ++octave_offset; // Go up to next octave - r = 0; // Roll around root note - } - else if(direction < 0 && r < 0 && octave_offset > -3) - { - --octave_offset; + int max_root = scale_size > 12 ? 12 : scale_size; + + //if(direction > 0 && r > 11 && octave_offset < 3) + if(direction > 0 && r >= max_root && octave_offset < 3) + { + ++octave_offset; // Go up to next octave + r = 0; // Roll around root note + } + else if(direction < 0 && r < 0 && octave_offset > -3) + { + --octave_offset; - r = max_root-1; - //r = 11; // Roll around root note - } + r = max_root-1; + //r = 11; // Roll around root note + } - // Limit root value - //root = constrain(r, 0, 11); - root = constrain(r, 0, max_root-1); + // Limit root value + //root = constrain(r, 0, 11); + root = constrain(r, 0, max_root-1); - break; - } - case 8: // pattern length - num_steps = constrain(num_steps + direction, 1, 32); - break; - } //switch + break; } + case 8: // pattern length + num_steps = constrain(num_steps + direction, 1, 32); + break; + } //switch } //OnEncoderMove uint64_t OnDataRequest() { @@ -417,7 +417,6 @@ class TB_3PO : public HemisphereApplet private: int cursor = 0; - bool isEditing = false; braids::Quantizer quantizer; // Helper for note index --> pitch cv braids::Quantizer display_semi_quantizer; // Quantizer to interpret the current note for display on a keyboard @@ -874,8 +873,8 @@ class TB_3PO : public HemisphereApplet */ // Scale and root note select - xd = (scale < 4) ? 32 : 39; // Slide/crowd to the left a bit if showing the "USER1"-"USER4" scales, which are uniquely five instead of four characters - gfxPrint(xd, 27, OC::scale_names_short[scale]); + // xd = (scale < 4) ? 32 : 39; // Slide/crowd to the left a bit if showing the "USER1"-"USER4" scales, which are uniquely five instead of four characters + gfxPrint(39, 26, OC::scale_names_short[scale]); gfxPrint((octave_offset == 0 ? 45 : 39), 36, OC::Strings::note_names_unpadded[root]); if(octave_offset != 0) @@ -960,30 +959,24 @@ class TB_3PO : public HemisphereApplet case 0: // Set length to indicate length gfxCursor(14, 23, lock_seed ? 11 : 36); // Seed = auto-randomize / locked-manual - if (isEditing) gfxInvert(14, 14, lock_seed ? 11 : 36, 9); break; case 1: case 2: case 3: case 4: // seed, 4 positions (1-4) gfxCursor(25 + 6*(cursor-1), 23, 7); - if (isEditing) gfxInvert(25 + 6*(cursor-1), 14, 7, 9); break; case 5: gfxCursor(9, 45, 14); // density - if (isEditing) gfxInvert(9, 36, 14, 9); break; case 6: gfxCursor(39, 34, 25); // scale - if (isEditing) gfxInvert(39, 26, 25, 8); break; case 7: gfxCursor(39, 44, 24); // root note - if (isEditing) gfxInvert(39, 35, 24, 9); break; case 8: - gfxCursor(20, 54, 12); // step - if (isEditing) gfxInvert(20, 46, 12, 8); + gfxCursor(20, 54, 12, 8); // step break; } } diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 14bb9d338..af17b9b81 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -195,39 +195,40 @@ public: if (!isEditing) { cursor = constrain(cursor + direction, 0, 8); ResetCursor(); // Reset blink so it's immediately visible when moved - } else { - switch (cursor) { - case 0: - length = constrain(length + direction, TM2_MIN_LENGTH, TM2_MAX_LENGTH); - break; - case 1: - p = constrain(p + direction, 0, 100); - break; - case 2: - scale += direction; - if (scale >= TM2_MAX_SCALE) scale = 0; - if (scale < 0) scale = TM2_MAX_SCALE - 1; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); - break; - case 3: - quant_range = constrain(quant_range + direction, 1, 32); - break; - case 4: - outmode[0] = constrain(outmode[0] + direction, -1, 8); - break; - case 5: - outmode[1] = constrain(outmode[1] + direction, -1, 8); - break; - case 6: - cvmode[0] = constrain(cvmode[0] + direction, -1, 5); - break; - case 7: - cvmode[1] = constrain(cvmode[1] + direction, -1, 5); - break; - case 8: - smoothing = constrain(smoothing + direction, 1, 128); - break; - } + return; + } + + switch (cursor) { + case 0: + length = constrain(length + direction, TM2_MIN_LENGTH, TM2_MAX_LENGTH); + break; + case 1: + p = constrain(p + direction, 0, 100); + break; + case 2: + scale += direction; + if (scale >= TM2_MAX_SCALE) scale = 0; + if (scale < 0) scale = TM2_MAX_SCALE - 1; + quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + break; + case 3: + quant_range = constrain(quant_range + direction, 1, 32); + break; + case 4: + outmode[0] = constrain(outmode[0] + direction, -1, 8); + break; + case 5: + outmode[1] = constrain(outmode[1] + direction, -1, 8); + break; + case 6: + cvmode[0] = constrain(cvmode[0] + direction, -1, 5); + break; + case 7: + cvmode[1] = constrain(cvmode[1] + direction, -1, 5); + break; + case 8: + smoothing = constrain(smoothing + direction, 1, 128); + break; } } @@ -277,7 +278,6 @@ private: int length; // Sequence length int len_mod; // actual length after CV mod int cursor; // 0 = length, 1 = p, 2 = scale, 3=range, 4=OutA, 5=OutB, 6=CV1, 7=CV2 - bool isEditing = false; braids::Quantizer quantizer; // Settings @@ -392,7 +392,6 @@ private: gfxPrint(smooth_mod); gfxCursor(31, 43, 18); - if (isEditing) gfxInvert(31, 34, 18, 9); } switch (cursor) { @@ -405,18 +404,6 @@ private: case 7: case 5: gfxCursor(46, 43, 10); break; // Out B / CV 2 } - if (isEditing) { - switch (cursor) { - case 0: gfxInvert(13, 14, 12, 9); break; - case 1: gfxInvert(45, 14, 18, 9); break; - case 2: gfxInvert(12, 24, 25, 9); break; - case 3: gfxInvert(49, 24, 14, 9); break; - case 6: - case 4: gfxInvert(14, 34, 14, 9); break; - case 7: - case 5: gfxInvert(46, 34, 14, 9); break; - } - } } void DrawIndicator() { diff --git a/software/o_c_REV/HEM_Voltage.ino b/software/o_c_REV/HEM_Voltage.ino index 822135fc4..84c7c66a6 100644 --- a/software/o_c_REV/HEM_Voltage.ino +++ b/software/o_c_REV/HEM_Voltage.ino @@ -63,16 +63,17 @@ public: if (!isEditing) { cursor = constrain(cursor + direction, 0, 3); ResetCursor(); + return; + } + + uint8_t ch = cursor / 2; + if (cursor == 0 || cursor == 2) { + // Change voltage + int min = -HEMISPHERE_3V_CV / VOLTAGE_INCREMENTS; + int max = HEMISPHERE_MAX_CV / VOLTAGE_INCREMENTS; + voltage[ch] = constrain(voltage[ch] + direction, min, max); } else { - uint8_t ch = cursor / 2; - if (cursor == 0 || cursor == 2) { - // Change voltage - int min = -HEMISPHERE_3V_CV / VOLTAGE_INCREMENTS; - int max = HEMISPHERE_MAX_CV / VOLTAGE_INCREMENTS; - voltage[ch] = constrain(voltage[ch] + direction, min, max); - } else { - gate[ch] = 1 - gate[ch]; - } + gate[ch] = 1 - gate[ch]; } } @@ -104,7 +105,6 @@ protected: private: int cursor; - bool isEditing = false; bool view[2]; // Settings @@ -122,8 +122,7 @@ private: if (view[ch]) gfxInvert(0, 14 + (ch * 20), 7, 9); } - isEditing ? gfxInvert(12, 14 + cursor * 10, 37, 9) - : gfxCursor(12, 23 + cursor * 10, 37); + gfxCursor(12, 23 + cursor * 10, 37); } }; diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index 826171cb6..fef15e0dc 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -51,6 +51,7 @@ const uint8_t METRO_R_ICON[8] = {0x00,0xc0,0xb0,0x88,0x94,0x88,0xb4,0xc2}; // Notes const uint8_t X_NOTE_ICON[8] = {0x00,0xa0,0x40,0xa0,0x1f,0x02,0x0c,0x00}; const uint8_t NOTE_ICON[8] = {0xc0,0xe0,0xe0,0xe0,0x7f,0x02,0x14,0x08}; +const uint8_t NOTE2_ICON[8] = {0xc0,0xa0,0xa0,0xa0,0x7f,0x00,0x00,0x00}; const uint8_t NOTE4_ICON[8] = {0x00,0x00,0x60,0x70,0x70,0x3f,0x00,0x00}; // Waveform diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index d0aef1b7b..7bf245b35 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -183,8 +183,9 @@ class HemisphereApplet { //////////////// Offset graphics methods //////////////////////////////////////////////////////////////////////////////// - void gfxCursor(int x, int y, int w) { - if (CursorBlink()) gfxLine(x, y, x + w - 1, y); + void gfxCursor(int x, int y, int w, int h = 9) { // assumes standard text height for highlighting + if (isEditing) gfxInvert(x, y - h, w, h); + else if (CursorBlink()) gfxLine(x, y, x + w - 1, y); } void gfxPos(int x, int y) { @@ -384,6 +385,7 @@ class HemisphereApplet { protected: bool hemisphere; // Which hemisphere (0, 1) this applet uses + bool isEditing = false; // modal editing toggle const char* help[4]; virtual void SetHelp(); From f0a7d6989f1d6d137e45298171475ca645f5f94c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 23 Jan 2023 07:35:24 -0500 Subject: [PATCH 135/417] DualTM: convert cursor and I/O modes to enums --- software/o_c_REV/HEM_TM2.ino | 332 ++++++++++++++++++++--------------- 1 file changed, 190 insertions(+), 142 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index af17b9b81..0b625cab9 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -41,6 +41,44 @@ class DualTM : public HemisphereApplet { public: + + enum TM2Cursor { + LENGTH, + PROB, + SCALE, + RANGE, + OUT_A, + OUT_B, + CVMODE1, + CVMODE2, + SLEW, + LAST_SETTING + }; + + enum OutputMode { + PITCH_SUM, + PITCH1, + PITCH2, + MOD1, + MOD2, + TRIG1, + TRIG2, + GATE1, + GATE2, + GATE_SUM, + OUTMODE_LAST + }; + + enum InputMode { + SLEW_MOD, + LENGTH_MOD, + P_MOD, + RANGE_MOD, + TRANSPOSE1, + TRANSPOSE2, + TRANSPOSE_BOTH, + INMODE_LAST + }; const char* applet_name() { return "DualTM"; @@ -49,14 +87,8 @@ public: void Start() { reg = random(0, 65535); reg2 = ~reg; - Output[0] = 0; - Output[1] = 0; - p = 0; - length = 16; - quant_range = 24; - cursor = 0; + quantizer.Init(); - scale = OC::Scales::SCALE_SEMI; quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone } @@ -70,7 +102,7 @@ public: // default to no mod p_mod = p; len_mod = length; - range_mod = quant_range; + range_mod = range; smooth_mod = smoothing; int note_trans1 = 0; int note_trans2 = 0; @@ -78,32 +110,33 @@ public: // process CV inputs ForEachChannel(ch) { - // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 switch (cvmode[ch]) { - case -1: // bi-polar mod of slew factor + case SLEW_MOD: smooth_mod = constrain(smooth_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 128), 1, 128); break; - case 0: // bi-polar modulation of length + case LENGTH_MOD: len_mod = constrain(len_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); break; - case 1: // bi-polar modulation of probability + case P_MOD: p_mod = constrain(p_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 100), 0, 100); break; - case 2: // bi-polar modulation of note range + case RANGE_MOD: range_mod = constrain(range_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 32), 1, 32); break; - case 3: // bi-polar transpose before quantize + // bi-polar transpose before quantize + case TRANSPOSE1: note_trans1 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); break; - case 4: + case TRANSPOSE2: note_trans2 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); break; - case 5: + case TRANSPOSE_BOTH: note_trans3 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); break; + default: break; } } @@ -111,7 +144,7 @@ public: // Advance the register on clock, flipping bits as necessary if (clk) { // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same - int prob = (cursor == 1 || Gate(1)) ? p_mod : 0; + int prob = (cursor == PROB || Gate(1)) ? p_mod : 0; // Grab the bit that's about to be shifted away int last = (reg >> (len_mod - 1)) & 0x01; @@ -127,54 +160,56 @@ public: } // Send 8-bit scaled and quantized CV - // scaled = note * quant_range / 0x1f + // scaled = note * range / 0x1f int32_t note = Proportion(reg & 0xff, 0xff, range_mod); int32_t note2 = Proportion(reg2 & 0xff, 0xff, range_mod); /* - note *= quant_range; + note *= range; simfloat x = int2simfloat(note) / (int32_t)0x1f; note = simfloat2int(x); */ ForEachChannel(ch) { switch (outmode[ch]) { - case -1: // pitch 1+2 + case PITCH_SUM: Output[ch] = slew(Output[ch], quantizer.Lookup(note + note2 + note_trans3 + 64)); break; - case 0: // pitch 1 + case PITCH1: Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans1 + note_trans3 + 64)); break; - case 1: // pitch 2 + case PITCH2: Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans2 + note_trans3 + 64)); break; - case 2: // mod A - 8-bit bi-polar proportioned CV + case MOD1: // 8-bit bi-polar proportioned CV Output[ch] = slew(Output[ch], Proportion( int(reg & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; - case 3: // mod B + case MOD2: Output[ch] = slew(Output[ch], Proportion( int(reg2 & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; - case 4: // trig A + case TRIG1: if (clk && (reg & 0x01) == 1) // trigger if 1st bit is high Output[ch] = HEMISPHERE_MAX_CV; //ClockOut(ch); else // decay Output[ch] = slew(Output[ch]); break; - case 5: // trig B - if (clk && (reg2 & 0x01) == 1) // trigger if 1st bit is high - Output[ch] = HEMISPHERE_MAX_CV; //ClockOut(ch); + case TRIG2: + if (clk && (reg2 & 0x01) == 1) + Output[ch] = HEMISPHERE_MAX_CV; else Output[ch] = slew(Output[ch]); break; - case 6: // gate A + case GATE1: Output[ch] = slew(Output[ch], (reg & 0x01)*HEMISPHERE_MAX_CV ); break; - case 7: // gate B + case GATE2: Output[ch] = slew(Output[ch], (reg2 & 0x01)*HEMISPHERE_MAX_CV ); break; - case 8: // gate A+B + case GATE_SUM: Output[ch] = slew(Output[ch], ((reg & 0x01)+(reg2 & 0x01))*HEMISPHERE_3V_CV ); break; + + default: break; } Out(ch, Output[ch]); @@ -193,42 +228,44 @@ public: void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, 8); + cursor = (TM2Cursor) constrain(cursor + direction, 0, LAST_SETTING-1); ResetCursor(); // Reset blink so it's immediately visible when moved return; } switch (cursor) { - case 0: + case LENGTH: length = constrain(length + direction, TM2_MIN_LENGTH, TM2_MAX_LENGTH); break; - case 1: + case PROB: p = constrain(p + direction, 0, 100); break; - case 2: + case SCALE: scale += direction; if (scale >= TM2_MAX_SCALE) scale = 0; if (scale < 0) scale = TM2_MAX_SCALE - 1; quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); break; - case 3: - quant_range = constrain(quant_range + direction, 1, 32); + case RANGE: + range = constrain(range + direction, 1, 32); break; - case 4: - outmode[0] = constrain(outmode[0] + direction, -1, 8); + case OUT_A: + outmode[0] = (OutputMode) constrain(outmode[0] + direction, 0, OUTMODE_LAST-1); break; - case 5: - outmode[1] = constrain(outmode[1] + direction, -1, 8); + case OUT_B: + outmode[1] = (OutputMode) constrain(outmode[1] + direction, 0, OUTMODE_LAST-1); break; - case 6: - cvmode[0] = constrain(cvmode[0] + direction, -1, 5); + case CVMODE1: + cvmode[0] = (InputMode) constrain(cvmode[0] + direction, 0, INMODE_LAST-1); break; - case 7: - cvmode[1] = constrain(cvmode[1] + direction, -1, 5); + case CVMODE2: + cvmode[1] = (InputMode) constrain(cvmode[1] + direction, 0, INMODE_LAST-1); break; - case 8: + case SLEW: smoothing = constrain(smoothing + direction, 1, 128); break; + + default: break; } } @@ -236,12 +273,12 @@ public: uint64_t data = 0; Pack(data, PackLocation {0,7}, p); Pack(data, PackLocation {7,5}, length - 1); - Pack(data, PackLocation {12,5}, quant_range - 1); - Pack(data, PackLocation {17,4}, outmode[0]+1); - Pack(data, PackLocation {21,4}, outmode[1]+1); + Pack(data, PackLocation {12,5}, range - 1); + Pack(data, PackLocation {17,4}, outmode[0]); + Pack(data, PackLocation {21,4}, outmode[1]); Pack(data, PackLocation {25,8}, constrain(scale, 0, 255)); - Pack(data, PackLocation {33,4}, cvmode[0]+1); - Pack(data, PackLocation {37,4}, cvmode[1]+1); + Pack(data, PackLocation {33,4}, cvmode[0]); + Pack(data, PackLocation {37,4}, cvmode[1]); // maybe don't bother saving the damn register //Pack(data, PackLocation {32,32}, reg); @@ -252,13 +289,13 @@ public: void OnDataReceive(uint64_t data) { p = Unpack(data, PackLocation {0,7}); length = Unpack(data, PackLocation {7,5}) + 1; - quant_range = Unpack(data, PackLocation{12,5}) + 1; - outmode[0] = Unpack(data, PackLocation {17,4}) - 1; - outmode[1] = Unpack(data, PackLocation {21,4}) - 1; + range = Unpack(data, PackLocation{12,5}) + 1; + outmode[0] = (OutputMode) Unpack(data, PackLocation {17,4}); + outmode[1] = (OutputMode) Unpack(data, PackLocation {21,4}); scale = Unpack(data, PackLocation {25,8}); quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); - cvmode[0] = Unpack(data, PackLocation {33,4}) - 1; - cvmode[1] = Unpack(data, PackLocation {37,4}) - 1; + cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); + cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); reg = Unpack(data, PackLocation {32,32}); reg2 = Unpack(data, PackLocation {0, 32}); // lol it could be fun @@ -275,34 +312,28 @@ protected: } private: - int length; // Sequence length + int length = 16; // Sequence length int len_mod; // actual length after CV mod - int cursor; // 0 = length, 1 = p, 2 = scale, 3=range, 4=OutA, 5=OutB, 6=CV1, 7=CV2 + TM2Cursor cursor; braids::Quantizer quantizer; // Settings uint32_t reg; // 32-bit sequence register uint32_t reg2; // DJP - int Output[2]; + // most recent output values + int Output[2] = {0, 0}; - int p; // Probability of bit flipping on each cycle + int p = 0; // Probability of bit flipping on each cycle int p_mod; - int scale; // Scale used for quantized output - int quant_range; + int scale = OC::Scales::SCALE_SEMI; // Scale used for quantized output + int range = 24; int range_mod; int smoothing = 4; int smooth_mod; - // output modes: - // 0=pitch A; 2=mod A; 4=trig A; 6=gate A - // 1=pitch B; 3=mod B; 5=trig B; 7=gate B - // -1=pitch A+B; 8=gate A+B - int outmode[2] = {0, 5}; - - // CV input mappings: - // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 - int cvmode[2] = {0, 5}; + OutputMode outmode[2] = {PITCH1, TRIG2}; + InputMode cvmode[2] = {LENGTH_MOD, RANGE_MOD}; int slew(int old_val, const int new_val = 0) { // more smoothing causes more ticks to be skipped @@ -312,11 +343,74 @@ private: return old_val; } + void DrawOutputMode(int ch) { + gfxPrint(1 + 31*ch, 36, ch ? (hemisphere ? "D" : "B") : (hemisphere ? "C" : "A") ); + gfxPrint(":"); + + switch (outmode[ch]) { + case PITCH_SUM: gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + case PITCH1: + case PITCH2: + gfxBitmap(15 + ch*32, 35, 8, NOTE_ICON); + break; + case MOD1: + case MOD2: + gfxBitmap(15 + ch*32, 35, 8, WAVEFORM_ICON); + break; + case TRIG1: + case TRIG2: + gfxBitmap(15 + ch*32, 35, 8, CLOCK_ICON); + break; + case GATE_SUM: gfxBitmap(24+ch*32, 35, 3, SUB_TWO); + case GATE1: + case GATE2: + gfxBitmap(15 + ch*32, 35, 8, METER_ICON); + break; + + default: break; + } + + // indicator for reg1 or reg2 + gfxBitmap(24+ch*32, 35, 3, (outmode[ch] % 2) ? SUP_ONE : SUB_TWO ); + } + + void DrawCVMode(int ch) { + gfxIcon(1 + 31*ch, 35, CV_ICON); + gfxBitmap(9 + 31*ch, 35, 3, ch ? SUB_TWO : SUP_ONE); + + switch (cvmode[ch]) { + case SLEW_MOD: + gfxIcon(15 + ch*32, 35, MOD_ICON); + break; + case LENGTH_MOD: + gfxIcon(15 + ch*32, 35, LOOP_ICON); + break; + case P_MOD: + gfxPrint(15 + ch*32, 35, "p"); + break; + case RANGE_MOD: + gfxIcon(15 + ch*32, 35, UP_DOWN_ICON); + break; + case TRANSPOSE1: + gfxIcon(15 + ch*32, 35, BEND_ICON); + gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + break; + case TRANSPOSE_BOTH: + gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + case TRANSPOSE2: + gfxIcon(15 + ch*32, 35, BEND_ICON); + gfxBitmap(24+ch*32, 35, 3, SUB_TWO); + break; + + default: break; + } + } + void DrawSelector() { gfxBitmap(1, 14, 8, LOOP_ICON); gfxPrint(12 + pad(10, len_mod), 15, len_mod); gfxPrint(32, 15, "p="); - if (cursor == 1 || Gate(1)) { // p unlocked + if (cursor == PROB || Gate(1)) { // p unlocked gfxPrint(pad(100, p_mod), p_mod); } else { // p is disabled gfxBitmap(49, 14, 8, LOCK_ICON); @@ -326,83 +420,37 @@ private: gfxBitmap(41, 25, 8, UP_DOWN_ICON); gfxPrint(49, 25, range_mod); // APD - if (cursor < 6) { - gfxPrint(1, 36, "A:"); - gfxPrint(32, 36, "B:"); - - ForEachChannel(ch) { - switch (outmode[ch]) { - case -1: gfxBitmap(24+ch*32, 35, 3, SUP_ONE); - case 0: // pitch output - case 1: - gfxBitmap(15 + ch*32, 35, 8, NOTE_ICON); - break; - case 2: // mod output - case 3: - gfxBitmap(15 + ch*32, 35, 8, WAVEFORM_ICON); - break; - case 4: // trig output - case 5: - gfxBitmap(15 + ch*32, 35, 8, CLOCK_ICON); - break; - case 8: gfxBitmap(24+ch*32, 35, 3, SUB_TWO); - case 6: // gate output - case 7: - gfxBitmap(15 + ch*32, 35, 8, METER_ICON); - break; - } - // indicator for reg1 or reg2 - gfxBitmap(24+ch*32, 35, 3, (outmode[ch] % 2) ? SUB_TWO : SUP_ONE); - } - } else if (cursor < 8) { // CV inputs - gfxIcon(1, 35, CV_ICON); - gfxBitmap(9, 35, 3, SUP_ONE); - gfxIcon(32, 35, CV_ICON); - gfxBitmap(40, 35, 3, SUB_TWO); - - ForEachChannel(ch) { - // 0=length; 1=p_mod; 2=range; 3=trans1; 4=trans2; 5=trans1+2 - switch (cvmode[ch]) { - case -1: // smoothing - gfxIcon(15 + ch*32, 35, MOD_ICON); - break; - case 0: - gfxIcon(15 + ch*32, 35, LOOP_ICON); - break; - case 1: - gfxPrint(15 + ch*32, 35, "p"); - break; - case 2: - gfxIcon(15 + ch*32, 35, UP_DOWN_ICON); - break; - case 3: - gfxIcon(15 + ch*32, 35, BEND_ICON); - gfxBitmap(24+ch*32, 35, 3, SUP_ONE); - break; - case 5: - gfxBitmap(24+ch*32, 35, 3, SUP_ONE); - case 4: - gfxIcon(15 + ch*32, 35, BEND_ICON); - gfxBitmap(24+ch*32, 35, 3, SUB_TWO); - break; - } - } - } else { // smoothing + switch (cursor) { + default: + ForEachChannel(ch) DrawOutputMode(ch); + + break; + case CVMODE1: + case CVMODE2: + ForEachChannel(ch) DrawCVMode(ch); + + break; + case SLEW: gfxPrint(1, 35, "Slew:"); gfxPrint(smooth_mod); gfxCursor(31, 43, 18); + break; } switch (cursor) { - case 0: gfxCursor(13, 23, 12); break; // Length Cursor - case 1: gfxCursor(45, 23, 18); break; // Probability Cursor - case 2: gfxCursor(12, 33, 25); break; // Scale Cursor - case 3: gfxCursor(49, 33, 14); break; // Quant Range Cursor // APD - case 6: - case 4: gfxCursor(14, 43, 10); break; // Out A / CV 1 - case 7: - case 5: gfxCursor(46, 43, 10); break; // Out B / CV 2 + case LENGTH: gfxCursor(13, 23, 12); break; + case PROB: gfxCursor(45, 23, 18); break; + case SCALE: gfxCursor(12, 33, 25); break; + case RANGE: gfxCursor(49, 33, 14); break; + + case OUT_A: + case CVMODE1: gfxCursor(14, 43, 10); break; + + case OUT_B: + case CVMODE2: gfxCursor(46, 43, 10); break; + + default: break; } } From 2c10a984a123f048387f9c73d8f39bb11a9ab159 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 24 Jan 2023 06:01:59 -0500 Subject: [PATCH 136/417] EuclidX: convert parameters to enum, UI tweaks --- software/o_c_REV/HEM_EuclidX.ino | 97 ++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 6b56e69c9..620e580da 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -36,6 +36,13 @@ const int PARAM_SIZE = 5; class EuclidX : public HemisphereApplet { public: + enum EuclidXParam { + LENGTH1, BEATS1, OFFSET1, PADDING1, + LENGTH2, BEATS2, OFFSET2, PADDING2, + CV_DEST1, + CV_DEST2, + }; + const char* applet_name() { return "EuclidX"; } @@ -68,17 +75,17 @@ public: // process CV inputs ForEachChannel(cv_ch) { - switch (cv_dest[cv_ch] - ch * (NUM_PARAMS-1)) { - case 0: // length + switch (cv_dest[cv_ch] - ch * LENGTH2) { // this is dumb, but efficient + case LENGTH1: actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 2, 32); break; - case 1: // beats + case BEATS1: actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); break; - case 2: // offset + case OFFSET1: actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); break; - case 3: // padding + case PADDING1: actual_padding[ch] = constrain(actual_padding[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 32 - actual_length[ch]), 0, 32 - actual_length[ch]); break; default: break; @@ -117,31 +124,35 @@ public: void OnEncoderMove(int direction) { if (!isEditing) { - cursor = constrain(cursor + direction, 0, NUM_PARAMS*2 - 1); + cursor = (EuclidXParam) constrain(cursor + direction, LENGTH1, CV_DEST2); ResetCursor(); return; } - int ch = cursor < NUM_PARAMS ? 0 : 1; - int f = cursor - (ch * NUM_PARAMS); // Cursor function - switch (f) { - case 0: + int ch = cursor < LENGTH2 ? 0 : 1; + switch (cursor) { + case LENGTH1: + case LENGTH2: actual_length[ch] = length[ch] = constrain(length[ch] + direction, 2, 32); if (beats[ch] > length[ch]) beats[ch] = length[ch]; if (padding[ch] > 32 - length[ch]) padding[ch] = 32 - length[ch]; if (offset[ch] >= length[ch] + padding[ch]) offset[ch] = length[ch] + padding[ch] - 1; break; - case 1: + case BEATS1: + case BEATS2: actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); break; - case 2: + case OFFSET1: + case OFFSET2: actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] + padding[ch] - 1); break; - case 3: + case PADDING1: + case PADDING2: padding[ch] = constrain(padding[ch] + direction, 0, 32 - length[ch]); break; - case 4: // CV destination - cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, (NUM_PARAMS-1)*2 - 1); + case CV_DEST1: + case CV_DEST2: + cv_dest[cursor - CV_DEST1] = (EuclidXParam) constrain(cv_dest[cursor - CV_DEST1] + direction, LENGTH1, PADDING2); break; } } @@ -168,8 +179,8 @@ public: actual_beats[1] = beats[1] = Unpack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}) + 1; actual_offset[0] = offset[0] = Unpack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}); actual_offset[1] = offset[1] = Unpack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}); - cv_dest[0] = Unpack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}); - cv_dest[1] = Unpack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}); + cv_dest[0] = (EuclidXParam) Unpack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}); + cv_dest[1] = (EuclidXParam) Unpack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}); actual_padding[0] = padding[0] = Unpack(data, PackLocation {8 * PARAM_SIZE, PARAM_SIZE}); actual_padding[1] = padding[1] = Unpack(data, PackLocation {9 * PARAM_SIZE, PARAM_SIZE}); } @@ -186,7 +197,7 @@ protected: private: int step; - int cursor = 0; // Ch1: 0=Length, 1=Hits; Ch2: 2=Length 3=Hits + EuclidXParam cursor = LENGTH1; uint32_t pattern[2]; // Settings @@ -199,7 +210,7 @@ private: uint8_t actual_offset[2]; uint8_t actual_padding[2]; - uint8_t cv_dest[2]; + EuclidXParam cv_dest[2] = {BEATS1, BEATS2}; // input modulation void DrawSteps() { gfxLine(0, 45, 63, 45); @@ -227,40 +238,52 @@ private: void DrawEditor() { const int spacing = 16; - gfxBitmap(8 + 0 * spacing, 15, 8, LOOP_ICON); + if (cursor < CV_DEST1) { + gfxBitmap(8 + 0 * spacing, 15, 8, LOOP_ICON); + } gfxBitmap(8 + 1 * spacing, 15, 8, X_NOTE_ICON); gfxBitmap(8 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); gfxPrint(8 + 3 * spacing, 15, "+"); + int y = 15; ForEachChannel (ch) { - int y = 15 + 10 * (ch + 1); + y += 10; gfxPrint(3 + 0 * spacing + pad(10, actual_length[ch]), y, actual_length[ch]); gfxPrint(3 + 1 * spacing + pad(10, actual_beats[ch]), y, actual_beats[ch]); gfxPrint(3 + 2 * spacing + pad(10, actual_offset[ch]), y, actual_offset[ch]); gfxPrint(3 + 3 * spacing + pad(10, actual_padding[ch]), y, actual_padding[ch]); - int f = cursor - ch * NUM_PARAMS; - switch (f) { - case 0: - case 1: - case 2: - case 3: - gfxCursor(3 + f * spacing, y + 8, 13); - break; - case 4: // CV dest selection - gfxBitmap(0, 13+ch*5, 8, CV_ICON); - if (isEditing) gfxInvert(0, 13+ch*5, 8, 6); - break; - } - // CV assignment indicators ForEachChannel(ch_dest) { - int ff = cv_dest[ch_dest] - (NUM_PARAMS-1)*ch; - if (ff >= 0 && ff < (NUM_PARAMS-1)) + int ff = cv_dest[ch_dest] - LENGTH2*ch; + if (ff >= 0 && ff < LENGTH2) gfxBitmap(ff * spacing, y, 3, ch_dest?SUB_TWO:SUP_ONE); } } + int ch = cursor < LENGTH2 ? 0 : 1; + int f = cursor - ch * LENGTH2; + y = 33; + switch (cursor) { + case LENGTH2: + case BEATS2: + case OFFSET2: + case PADDING2: + y += 10; + case LENGTH1: + case BEATS1: + case OFFSET1: + case PADDING1: + gfxCursor(3 + f * spacing, y, 13); + break; + + case CV_DEST1: + case CV_DEST2: + gfxBitmap(0, 13 + (cursor - CV_DEST1)*5, 8, CV_ICON); + gfxBitmap(8, 15, 3, (cursor - CV_DEST1)? SUB_TWO : SUP_ONE); + gfxCursor(0, 19 + (cursor - CV_DEST1)*5, 11, 7); + break; + } } }; From 20a08d917ccbec2cac62821e8c989a36247e76eb Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 28 Jan 2023 01:21:58 -0500 Subject: [PATCH 137/417] ClockSetup: expand save data to store all params --- software/o_c_REV/APP_HEMISPHERE.ino | 13 +++++++++---- software/o_c_REV/HEM_ClockSetup.ino | 17 ++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index e238f20de..95af21e43 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -66,7 +66,8 @@ enum HEMISPHERE_SETTINGS { HEMISPHERE_RIGHT_DATA_B3, HEMISPHERE_LEFT_DATA_B4, HEMISPHERE_RIGHT_DATA_B4, - HEMISPHERE_CLOCK_DATA, + HEMISPHERE_CLOCK_DATA1, + HEMISPHERE_CLOCK_DATA2, HEMISPHERE_SETTING_LAST }; @@ -103,7 +104,8 @@ public: (uint64_t(values_[2 + h])); available_applets[index].OnDataReceive(h, data); } - ClockSetup.OnDataReceive(0, uint64_t(values_[HEMISPHERE_CLOCK_DATA])); + ClockSetup.OnDataReceive(0, (uint64_t(values_[HEMISPHERE_CLOCK_DATA2]) << 16) | + uint64_t(values_[HEMISPHERE_CLOCK_DATA1])); } void SetApplet(int hemisphere, int index) { @@ -266,7 +268,9 @@ public: apply_value(6 + h, (data >> 32) & 0xffff); apply_value(8 + h, (data >> 48) & 0xffff); } - apply_value(HEMISPHERE_CLOCK_DATA, ClockSetup.OnDataRequest(0)); + uint64_t data = ClockSetup.OnDataRequest(0); + apply_value(HEMISPHERE_CLOCK_DATA1, data & 0xffff); + apply_value(HEMISPHERE_CLOCK_DATA2, (data >> 16) & 0xffff); } void OnSendSysEx() { @@ -371,7 +375,8 @@ SETTINGS_DECLARE(HemisphereManager, HEMISPHERE_SETTING_LAST) { {0, 0, 65535, "Data R block 3", NULL, settings::STORAGE_TYPE_U16}, {0, 0, 65535, "Data L block 4", NULL, settings::STORAGE_TYPE_U16}, {0, 0, 65535, "Data R block 4", NULL, settings::STORAGE_TYPE_U16}, - {0, 0, 65535, "Clock data", NULL, settings::STORAGE_TYPE_U16} + {0, 0, 65535, "Clock data 1", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Clock data 2", NULL, settings::STORAGE_TYPE_U16} }; HemisphereManager manager; diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index ce5c5497e..5628bced1 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -92,9 +92,11 @@ public: uint64_t OnDataRequest() { uint64_t data = 0; Pack(data, PackLocation { 0, 1 }, clock_m->IsRunning() || clock_m->IsPaused()); - Pack(data, PackLocation { 1, 9 }, clock_m->GetTempo()); - Pack(data, PackLocation { 10, 5 }, clock_m->GetMultiply()); - Pack(data, PackLocation { 15, 1 }, clock_m->IsForwarded()); + Pack(data, PackLocation { 1, 1 }, clock_m->IsForwarded()); + Pack(data, PackLocation { 2, 9 }, clock_m->GetTempo()); + Pack(data, PackLocation { 11, 6 }, clock_m->GetMultiply(0)+32); + Pack(data, PackLocation { 17, 6 }, clock_m->GetMultiply(1)+32); + Pack(data, PackLocation { 23, 5 }, clock_m->GetClockPPQN()); return data; } @@ -104,10 +106,11 @@ public: } else { clock_m->Stop(); } - clock_m->SetTempoBPM(Unpack(data, PackLocation { 1, 9 })); - clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 }),0); - clock_m->SetMultiply(Unpack(data, PackLocation { 10, 5 }),1); - clock_m->SetForwarding(Unpack(data, PackLocation { 15, 1 })); + clock_m->SetForwarding(Unpack(data, PackLocation { 1, 1 })); + clock_m->SetTempoBPM(Unpack(data, PackLocation { 2, 9 })); + clock_m->SetMultiply(Unpack(data, PackLocation { 11, 6 })-32,0); + clock_m->SetMultiply(Unpack(data, PackLocation { 17, 6 })-32,1); + clock_m->SetClockPPQN(Unpack(data, PackLocation { 23, 5 })); } protected: From b78b4901205f738d21d16e39e386bea1b917896f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 28 Jan 2023 04:27:00 -0500 Subject: [PATCH 138/417] EbbAndLFO: implement CV input config --- software/o_c_REV/HEM_EbbAndLfo.ino | 63 ++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 46d24f217..d3923862a 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -7,8 +7,6 @@ public: void Start() { phase = 0; } void Controller() { - uint32_t phase_increment = ComputePhaseIncrement(pitch + In(0)); - phase += phase_increment; if (Clock(1)) phase = 0; if (Clock(0)) { //uint32_t next_tick = predictor.Predict(ClockCycleTicks(0)); @@ -16,16 +14,47 @@ public: pitch = ComputePitch(new_freq); phase = 0; } - slope_mod = slope + Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 127); + + // handle CV inputs + pitch_mod = pitch; + slope_mod = slope; + shape_mod = shape; + fold_mod = fold; + + ForEachChannel(ch) { + switch (cv_type(ch)) { + case FREQ: + pitch_mod += In(ch); + break; + case SLOPE: + slope_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 127); + break; + case SHAPE: + shape_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 127); + break; + case FOLD: + fold_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 127); + break; + } + } slope_mod = constrain(slope_mod, 0, 127); + while (shape_mod < 0) shape_mod += 128; + while (shape_mod > 127) shape_mod -= 128; + fold_mod = constrain(fold_mod, 0, 127); + + uint32_t phase_increment = ComputePhaseIncrement(pitch_mod); + phase += phase_increment; + + // COMPUTE int s = constrain(slope_mod * 65535 / 127, 0, 65535); - ProcessSample(s, shape * 65535 / 127, fold * 32767 / 127, phase, sample); + ProcessSample(s, shape_mod * 65535 / 127, fold_mod * 32767 / 127, phase, sample); if (phase < phase_increment) { eoa_reached = false; } else { eoa_reached = eoa_reached || (sample.flags & FLAG_EOA); } + ForEachChannel(ch) { switch (output(ch)) { case UNIPOLAR: @@ -55,8 +84,8 @@ public: int bottom = 32 + (h + 1) * ch; int last = bottom; for (int i = 0; i < 64; i++) { - ProcessSample(slope_mod * 65535 / 127, shape * 65535 / 127, - fold * 32767 / 127, 0xffffffff / 64 * i, disp_sample); + ProcessSample(slope_mod * 65535 / 127, shape_mod * 65535 / 127, + fold_mod * 32767 / 127, 0xffffffff / 64 * i, disp_sample); int next = 0; switch (output(ch)) { case UNIPOLAR: @@ -104,6 +133,13 @@ public: gfxPrint(hemisphere == 0 ? " B:" : " D:"); gfxPrint(out_labels[output(1)]); break; + case 5: + ForEachChannel(ch) { + gfxIcon(0 + ch*32, 56, CV_ICON); + gfxBitmap(8 + ch*32, 56, 3, ch ? SUB_TWO : SUP_ONE); + gfxPrint(13 + ch*32, 56, cv_labels[cv_type(ch)]); + } + break; } if (isEditing) gfxInvert(0, 55, 64, 9); } @@ -145,8 +181,14 @@ public: case 4: { out += direction; out %= 0b10000; + break; } + case 5: + cv += direction; + cv %= 0b10000; + break; } + if (knob_accel < (1 << 13)) knob_accel <<= 1; } @@ -202,10 +244,15 @@ private: int cursor = 0; int16_t pitch = -3 * 12 * 128; int slope = 64; - int slope_mod = 64; // actual value after CV mod int shape = 48; // triangle int fold = 0; + // actual values after CV mod + int16_t pitch_mod; + int slope_mod; + int shape_mod; + int fold_mod; + uint8_t out = 0b0001; // Unipolar on A, bipolar on B uint8_t cv = 0b0001; // Freq on 1, shape on 2 TidesLiteSample disp_sample; @@ -221,7 +268,7 @@ private: } CV cv_type(int ch) { - return (CV) ((out >> ((1 - ch) * 2)) & 0b11); + return (CV) ((cv >> ((1 - ch) * 2)) & 0b11); } void gfxPrintFreq(int16_t pitch) { From 7c4a5ed734e2bc57a9ed3ad0880c2739edcdd334 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 28 Jan 2023 05:11:32 -0500 Subject: [PATCH 139/417] Brancher: fix internal clock operation on Left Hemisphere --- software/o_c_REV/HEM_Brancher.ino | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/software/o_c_REV/HEM_Brancher.ino b/software/o_c_REV/HEM_Brancher.ino index 4b8f847df..b2b86c379 100644 --- a/software/o_c_REV/HEM_Brancher.ino +++ b/software/o_c_REV/HEM_Brancher.ino @@ -26,25 +26,27 @@ public: } void Start() { - p = 50; - choice = 0; + p = 50; + choice = 0; } void Controller() { - bool master_clock = MasterClockForwarded(); - + // handles physical and logical clock if (Clock(0)) { int prob = p + Proportion(DetentedIn(0), HEMISPHERE_MAX_CV, 100); choice = (random(1, 100) <= prob) ? 0 : 1; - // If Master Clock Forwarding is enabled, respond to this clock by - // sending a clock - if (master_clock) ClockOut(choice); + // will be true only for logical clocks + clocked = !Gate(0); + + if (clocked) ClockOut(choice); } - // Pass along the gate state if Master Clock Forwarding is off. If it's on, - // the clock is handled above - if (!master_clock) GateOut(choice, Gate(0)); + // only pass thru physical gates + if (!clocked) { + GateOut(choice, Gate(0)); + GateOut(1 - choice, 0); // reset the other output + } } void View() { @@ -53,7 +55,7 @@ public: } void OnButtonPress() { - choice = 1 - choice; + choice = 1 - choice; } /* Change the pability */ @@ -82,6 +84,7 @@ protected: private: int p; int choice; + bool clocked; // indicates a logical clock without a physical gate void DrawInterface() { // Show the probability in the middle From 5e4443d92fa38340b837d07eef62fd3c8c289891 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 28 Jan 2023 04:06:18 -0500 Subject: [PATCH 140/417] Modal edit refactoring, generalization, cleanup Edit behavior can be cycled with long-press of DOWN button: 0= old behavior, button advances cursor 1= modal editing, no wrap (default) 2= modal with wraparound --- software/o_c_REV/APP_HEMISPHERE.ino | 2 +- software/o_c_REV/HEM_AttenuateOffset.ino | 13 ++++-- software/o_c_REV/HEM_BootsNCat.ino | 9 +++- software/o_c_REV/HEM_BugCrack.ino | 8 ++-- software/o_c_REV/HEM_Burst.ino | 45 +++++++++++------- software/o_c_REV/HEM_CVRecV2.ino | 16 ++++--- software/o_c_REV/HEM_ClockSetup.ino | 39 +++++++++++----- software/o_c_REV/HEM_DrumMap.ino | 13 +++--- software/o_c_REV/HEM_DualQuant.ino | 7 ++- software/o_c_REV/HEM_EbbAndLfo.ino | 8 ++-- software/o_c_REV/HEM_EnigmaJr.ino | 7 ++- software/o_c_REV/HEM_EuclidX.ino | 10 ++-- software/o_c_REV/HEM_LoFiPCM.ino | 7 ++- software/o_c_REV/HEM_Palimpsest.ino | 23 ++++++--- software/o_c_REV/HEM_ProbabilityDivider.ino | 15 +++--- software/o_c_REV/HEM_ProbabilityMelody.ino | 39 +++++++--------- software/o_c_REV/HEM_RndWalk.ino | 7 ++- software/o_c_REV/HEM_Scope.ino | 10 ++-- software/o_c_REV/HEM_SequenceX.ino | 23 ++++----- software/o_c_REV/HEM_ShiftGate.ino | 7 ++- software/o_c_REV/HEM_Shredder.ino | 13 +++++- software/o_c_REV/HEM_Squanch.ino | 11 +++-- software/o_c_REV/HEM_Stairs.ino | 52 +++++++++++---------- software/o_c_REV/HEM_TB3PO.ino | 8 ++-- software/o_c_REV/HEM_TM2.ino | 17 ++++--- software/o_c_REV/HEM_Trending.ino | 8 +++- software/o_c_REV/HEM_VectorEG.ino | 9 +++- software/o_c_REV/HEM_VectorLFO.ino | 8 +++- software/o_c_REV/HEM_VectorMod.ino | 8 +++- software/o_c_REV/HEM_VectorMorph.ino | 8 +++- software/o_c_REV/HEM_Voltage.ino | 7 ++- software/o_c_REV/HSicons.h | 2 + software/o_c_REV/HemisphereApplet.h | 29 ++++++++++++ 33 files changed, 302 insertions(+), 186 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 95af21e43..76a2d93bc 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -438,7 +438,7 @@ void HEMISPHERE_handleButtonEvent(const UI::Event &event) { } if (event.type == UI::EVENT_BUTTON_LONG_PRESS) { - if (event.control == OC::CONTROL_BUTTON_DOWN) manager.ToggleClockSetup(); + if (event.control == OC::CONTROL_BUTTON_DOWN) HemisphereApplet::CycleEditMode(); if (event.control == OC::CONTROL_BUTTON_L) manager.ToggleClockRun(); } } diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index 3795b0645..e11512e04 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -56,16 +56,19 @@ public: } void OnButtonPress() { - if (cursor == 4) + if (cursor == 4 && !EditMode()) // special case when modal editing mix = !mix; else - isEditing = !isEditing; + CursorAction(cursor, 4); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, 4); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, 4); + return; + } + if (cursor == 4) { // non-modal editing special case toggle + mix = !mix; return; } diff --git a/software/o_c_REV/HEM_BootsNCat.ino b/software/o_c_REV/HEM_BootsNCat.ino index 196b4825a..b91522130 100644 --- a/software/o_c_REV/HEM_BootsNCat.ino +++ b/software/o_c_REV/HEM_BootsNCat.ino @@ -100,10 +100,15 @@ public: } void OnButtonPress() { - if (++cursor > 4) cursor = 0; + CursorAction(cursor, 4); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 4); + return; + } + if (cursor == 4) { // Blend blend = constrain(blend + direction, 0, BNC_MAX_PARAM); } else { @@ -121,7 +126,6 @@ public: SetEGFreq(ch); } } - ResetCursor(); } uint64_t OnDataRequest() { @@ -191,6 +195,7 @@ private: byte p = is_cursor ? 1 : 3; gfxDottedLine(x, y + 4, 62, y + 4, p); gfxRect(x + w, y, 2, 7); + if (EditMode() && is_cursor) gfxInvert(x, y, 17, 8); } void SetBDFreq() { diff --git a/software/o_c_REV/HEM_BugCrack.ino b/software/o_c_REV/HEM_BugCrack.ino index 2f68775f9..ecb091580 100644 --- a/software/o_c_REV/HEM_BugCrack.ino +++ b/software/o_c_REV/HEM_BugCrack.ino @@ -224,12 +224,12 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 9); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, 9); + if (!EditMode()) { + MoveCursor(cursor, direction, 9); return; } @@ -397,7 +397,7 @@ private: gfxPrint(41, 55, CV_MODE_NAMES_SN[cv_mode_snare]); break; } - if (isEditing) gfxInvert(1 + (cursor<5?0:31), 54, 31, 9); + if (EditMode()) gfxInvert(1 + (cursor<5?0:31), 54, 31, 9); // Level indicators ForEachChannel(ch) diff --git a/software/o_c_REV/HEM_Burst.ino b/software/o_c_REV/HEM_Burst.ino index c1e99d583..5f35e30be 100644 --- a/software/o_c_REV/HEM_Burst.ino +++ b/software/o_c_REV/HEM_Burst.ino @@ -27,6 +27,9 @@ class Burst : public HemisphereApplet { public: + enum BurstCursor { + + }; const char* applet_name() { return "Burst"; @@ -122,31 +125,37 @@ public: } void OnButtonPress() { - cursor += 1; - if (cursor > 4) cursor = 0; - if (cursor > 3 && !clocked) cursor = 0; + CursorAction(cursor, clocked ? 4 : 3); } void OnEncoderMove(int direction) { - if (cursor == 0) number = constrain(number + direction, 1, HEM_BURST_NUMBER_MAX); - if (cursor == 1) { + if (!EditMode()) { + MoveCursor(cursor, direction, clocked ? 4 : 3); + return; + } + + switch (cursor) { + case 0: + number = constrain(number + direction, 1, HEM_BURST_NUMBER_MAX); + break; + case 1: spacing = constrain(spacing + direction, HEM_BURST_SPACING_MIN, HEM_BURST_SPACING_MAX); clocked = 0; - } - if (cursor == 2) { + break; + case 2: accel = constrain(accel + direction, -HEM_BURST_ACCEL_MAX, HEM_BURST_ACCEL_MAX); - } - - if (cursor == 3) { + break; + case 3: jitter = constrain(jitter + direction, 0, HEM_BURST_JITTER_MAX); - } + break; - if (cursor == 4) { + case 4: div += direction; if (div > HEM_BURST_CLOCKDIV_MAX) div = HEM_BURST_CLOCKDIV_MAX; if (div < -HEM_BURST_CLOCKDIV_MAX) div = -HEM_BURST_CLOCKDIV_MAX; if (div == 0) div = direction > 0 ? 1 : -2; // No such thing as 1/1 Multiple if (div == -1) div = 1; // Must be moving up to hit -1 (see previous line) + break; } } @@ -199,7 +208,7 @@ private: void DrawSelector() { // Number gfxPrint(1, 15, number); - gfxPrint(28, 15, "bursts"); + gfxPrint(27, 15, "bursts"); // Spacing gfxPrint(1, 25, clocked ? get_effective_spacing() : spacing); @@ -210,8 +219,8 @@ private: gfxPrint(10, 35, accel); // Jitter - gfxIcon(30, 34, RANDOM_ICON); - gfxPrint(38, 35, jitter); + gfxIcon(32, 34, RANDOM_ICON); + gfxPrint(40, 35, jitter); // Div if (clocked) { @@ -222,9 +231,9 @@ private: } // Cursor - if (cursor < 2) gfxCursor(1, 23 + (cursor * 10), 62); - if (cursor == 2) gfxCursor(10, 43, 12); - if (cursor == 3) gfxCursor(38, 43, 12); + if (cursor < 2) gfxCursor(1, 23 + (cursor * 10), 13 + 12*cursor); + if (cursor == 2) gfxCursor(10, 43, 19); + if (cursor == 3) gfxCursor(40, 43, 13); if (cursor == 4) gfxCursor(1, 53, 62); } diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index e42c553e0..65bd75e08 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -90,22 +90,22 @@ public: } void OnButtonPress() { - if (cursor == 2) { // special case to toggle smoothing + if (cursor == 2 && !EditMode()) { // special case to toggle smoothing smooth = 1 - smooth; ResetCursor(); return; } - isEditing = !isEditing; // toggle editing - if (cursor == 3 && !isEditing) { // activate recording if selected + if (cursor == 3 && EditMode()) { // activate recording if selected punch_out = (mode > 0) ? end - start + 1 : 0; } + + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { - if (!isEditing) { //not editing, move cursor - cursor = constrain(cursor + direction, 0, 3); - ResetCursor(); + if (!EditMode()) { //not editing, move cursor + MoveCursor(cursor, direction, 3); return; } @@ -122,6 +122,10 @@ public: if (fe != end && punch_out) punch_out += direction; break; } + case 2: + smooth = 1 - smooth; + ResetCursor(); + break; case 3: mode = constrain(mode + direction, 0, 3); break; diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 5628bced1..1ad8b3a9a 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -32,7 +32,7 @@ public: TRIG2, TRIG3, TRIG4, - LAST_SETTING + LAST_SETTING = TRIG4 }; const char* applet_name() { @@ -59,20 +59,37 @@ public: } void OnButtonPress() { - if (cursor == PLAY_STOP) PlayStop(); - else if (cursor == FORWARDING) clock_m->ToggleForwarding(); - else if (cursor >= TRIG1) clock_m->Boop(cursor-TRIG1); - else isEditing = !isEditing; + if (!EditMode()) { // special cases for toggle buttons + if (cursor == PLAY_STOP) PlayStop(); + else if (cursor == FORWARDING) clock_m->ToggleForwarding(); + else if (cursor >= TRIG1) clock_m->Boop(cursor-TRIG1); + else CursorAction(cursor, LAST_SETTING); + } + else CursorAction(cursor, LAST_SETTING); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = (ClockSetupCursor) constrain(cursor + direction, 0, LAST_SETTING-1); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, LAST_SETTING); return; } - switch (cursor) { + switch ((ClockSetupCursor)cursor) { + case PLAY_STOP: + PlayStop(); + break; + + case FORWARDING: + clock_m->ToggleForwarding(); + break; + + case TRIG1: + case TRIG2: + case TRIG3: + case TRIG4: + clock_m->Boop(cursor-TRIG1); + break; + case EXT_PPQN: clock_m->SetClockPPQN(clock_m->GetClockPPQN() + direction); break; @@ -124,7 +141,7 @@ protected: } private: - ClockSetupCursor cursor; // 0=Play/Stop, 1=Forwarding, 2=External PPQN, 3=Tempo, 4,5=Multiply + int cursor; // ClockSetupCursor bool start_q; bool stop_q; ClockManager *clock_m = clock_m->get(); @@ -181,7 +198,7 @@ private: // Manual triggers for (int i=0; i<4; i++) { gfxIcon(4 + i*32, 49, BURST_ICON); } - switch (cursor) { + switch ((ClockSetupCursor)cursor) { case PLAY_STOP: gfxFrame(11, 14, 10, 10); break; case FORWARDING: gfxFrame(49, 14, 10, 10); break; diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index c515c0f09..e0dad03ec 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -128,14 +128,15 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 7); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor += direction; - if (mode[1] > 2 && cursor == 3) cursor += direction; - cursor = constrain(cursor, 0, 7); + if (!EditMode()) { + do { + MoveCursor(cursor, direction, 7); + } while (mode[1] > 2 && cursor == 3); + ResetCursor(); return; } @@ -349,7 +350,7 @@ private: byte p = is_cursor ? 1 : 3; gfxDottedLine(x, y + 4, x + len, y + 4, p); gfxRect(x + w, y, 2, 8); - if (is_cursor && isEditing) gfxInvert(x, y, len+1, 8); + if (is_cursor && EditMode()) gfxInvert(x, y, len+1, 8); } void Reset() { diff --git a/software/o_c_REV/HEM_DualQuant.ino b/software/o_c_REV/HEM_DualQuant.ino index 3f53c3e75..dbc87d90f 100644 --- a/software/o_c_REV/HEM_DualQuant.ino +++ b/software/o_c_REV/HEM_DualQuant.ino @@ -66,13 +66,12 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, 3); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, 3); return; } diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index d3923862a..576640ae9 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -141,16 +141,16 @@ public: } break; } - if (isEditing) gfxInvert(0, 55, 64, 9); + if (EditMode()) gfxInvert(0, 55, 64, 9); } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 5); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, 4); + if (!EditMode()) { + MoveCursor(cursor, direction, 5); return; } diff --git a/software/o_c_REV/HEM_EnigmaJr.ino b/software/o_c_REV/HEM_EnigmaJr.ino index b188cdda9..208e1dbc5 100644 --- a/software/o_c_REV/HEM_EnigmaJr.ino +++ b/software/o_c_REV/HEM_EnigmaJr.ino @@ -74,10 +74,15 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } + if (cursor == 0) { // Switch TM byte prev_tm = tm_state.GetTMIndex(); byte new_tm = constrain(prev_tm + direction, 0, HS::TURING_MACHINE_COUNT - 1); diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 620e580da..3419caee8 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -41,6 +41,7 @@ public: LENGTH2, BEATS2, OFFSET2, PADDING2, CV_DEST1, CV_DEST2, + LAST_SETTING = CV_DEST2 }; const char* applet_name() { @@ -119,13 +120,12 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, LAST_SETTING); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = (EuclidXParam) constrain(cursor + direction, LENGTH1, CV_DEST2); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, LAST_SETTING); return; } @@ -197,7 +197,7 @@ protected: private: int step; - EuclidXParam cursor = LENGTH1; + int cursor = LENGTH1; // EuclidXParam uint32_t pattern[2]; // Settings diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index a302f6336..f1ae4f455 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -82,13 +82,12 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, 3); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, 3); return; } diff --git a/software/o_c_REV/HEM_Palimpsest.ino b/software/o_c_REV/HEM_Palimpsest.ino index a3c758210..a327dd13c 100644 --- a/software/o_c_REV/HEM_Palimpsest.ino +++ b/software/o_c_REV/HEM_Palimpsest.ino @@ -86,15 +86,26 @@ public: } void OnButtonPress() { - cursor++; - if (cursor > 2) cursor = 0; - ResetCursor(); + CursorAction(cursor, 2); } void OnEncoderMove(int direction) { - if (cursor == 0) compose = constrain(compose + direction, 0, HEM_PALIMPSEST_MAX_VALUE); - if (cursor == 1) decompose = constrain(decompose - direction, 0, HEM_PALIMPSEST_MAX_VALUE); - if (cursor == 2) length = constrain(length + direction, 2, 16); + if (!EditMode()) { + MoveCursor(cursor, direction, 2); + return; + } + + switch (cursor) { + case 0: + compose = constrain(compose + direction, 0, HEM_PALIMPSEST_MAX_VALUE); + break; + case 1: + decompose = constrain(decompose - direction, 0, HEM_PALIMPSEST_MAX_VALUE); + break; + case 2: + length = constrain(length + direction, 2, 16); + break; + } ResetCursor(); } diff --git a/software/o_c_REV/HEM_ProbabilityDivider.ino b/software/o_c_REV/HEM_ProbabilityDivider.ino index f12b3e3e2..5a5acc663 100644 --- a/software/o_c_REV/HEM_ProbabilityDivider.ino +++ b/software/o_c_REV/HEM_ProbabilityDivider.ino @@ -29,7 +29,7 @@ public: enum ProbDivCursor { WEIGHT1, WEIGHT2, WEIGHT4, WEIGHT8, LOOP_LENGTH, - LAST_SETTING + LAST_SETTING = LOOP_LENGTH }; const char* applet_name() { @@ -137,17 +137,16 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, LAST_SETTING); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = (ProbDivCursor) constrain(cursor + direction, 0, LAST_SETTING-1); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, LAST_SETTING); return; } - switch (cursor) { + switch ((ProbDivCursor)cursor) { case WEIGHT1: weight_1 = constrain(weight_1 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); break; case WEIGHT2: weight_2 = constrain(weight_2 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); break; case WEIGHT4: weight_4 = constrain(weight_4 + direction, 0, HEM_PROB_DIV_MAX_WEIGHT); break; @@ -204,7 +203,7 @@ protected: } private: - ProbDivCursor cursor; + int cursor; // ProbDivCursor int weight_1; int weight_2; int weight_4; @@ -263,7 +262,7 @@ private: byte w = Proportion(value, HEM_PROB_DIV_MAX_WEIGHT, len-1); gfxDottedLine(x, y + 3, x + len, y + 3, p); gfxRect(x + w, y, 2, 7); - if (isEditing && is_cursor) gfxInvert(x-1, y, len+3, 7); + if (EditMode() && is_cursor) gfxInvert(x-1, y, len+3, 7); } int GetNextWeightedDiv() { diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index 953629e9c..bef921221 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -46,20 +46,6 @@ public: void Controller() { loop_linker->RegisterMelo(hemisphere); - // looping enabled from ProbDiv - if (loop_linker->IsLooping() && !isLooping) { - isLooping = true; - GenerateLoop(); - } - - // reseed from ProbDiv - if (loop_linker->ShouldReseed()) { - GenerateLoop(); - } - - isLooping = loop_linker->IsLooping(); - - int downCv = DetentedIn(0); int oldDown = down; if (downCv < 0) down = 1; @@ -74,8 +60,17 @@ public: up = constrain(ProportionCV(upCv, HEM_PROB_MEL_MAX_RANGE + 1), down, HEM_PROB_MEL_MAX_RANGE); } + // regen when looping was enabled from ProbDiv + bool regen = loop_linker->IsLooping() && !isLooping; + isLooping = loop_linker->IsLooping(); + + // reseed from ProbDiv + regen = regen || loop_linker->ShouldReseed(); + // reseed loop if range has changed due to CV - if (isLooping && (oldDown != down || oldUp != up)) { + regen = regen || (isLooping && (oldDown != down || oldUp != up)); + + if (regen) { GenerateLoop(); } @@ -110,14 +105,12 @@ public: } void OnButtonPress() { - isEditing = !isEditing; - ResetCursor(); + CursorAction(cursor, LAST_NOTE); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = (ProbMeloCursor) constrain(cursor + direction, 0, LAST_NOTE); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, LAST_NOTE); return; } @@ -183,7 +176,7 @@ protected: } private: - ProbMeloCursor cursor; + int cursor; // ProbMeloCursor int weights[12] = {10,0,0,2,0,0,0,2,0,0,4,0}; int up; int down; @@ -260,7 +253,7 @@ private: if (pulse_animation > 0 && note == i) { gfxRect(xOffset - 1, yOffset, 3, 10); } else { - if (isEditing && i == (cursor - FIRST_NOTE)) { + if (EditMode() && i == (cursor - FIRST_NOTE)) { // blink line when editing if (CursorBlink()) { gfxLine(xOffset, yOffset, xOffset, yOffset + 10); @@ -275,7 +268,7 @@ private: } // cursor for keys - if (!isEditing) { + if (!EditMode()) { if (cursor >= FIRST_NOTE) { int i = cursor - FIRST_NOTE; gfxCursor(x[i] - 1, p[i] ? 24 : 60, p[i] ? 5 : 6); diff --git a/software/o_c_REV/HEM_RndWalk.ino b/software/o_c_REV/HEM_RndWalk.ino index 25f5fd0a8..a57a9d92d 100644 --- a/software/o_c_REV/HEM_RndWalk.ino +++ b/software/o_c_REV/HEM_RndWalk.ino @@ -98,13 +98,12 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 5); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, 5); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, 5); return; } diff --git a/software/o_c_REV/HEM_Scope.ino b/software/o_c_REV/HEM_Scope.ino index 37ced1b92..7fe24c4ce 100644 --- a/software/o_c_REV/HEM_Scope.ino +++ b/software/o_c_REV/HEM_Scope.ino @@ -91,17 +91,17 @@ public: } void OnButtonPress() { - if (current_setting == 2) // FREEZE button + if (current_setting == 2 && !EditMode()) // FREEZE button freeze = !freeze; else if (OC::CORE::ticks - last_encoder_move < SCOPE_CURRENT_SETTING_TIMEOUT) // params visible? toggle edit - isEditing = !isEditing; + CursorAction(current_setting, 2); else // show params last_encoder_move = OC::CORE::ticks; } void OnEncoderMove(int direction) { - if (!isEditing) { // switch setting - current_setting = constrain(current_setting + direction, 0, 2); + if (!EditMode()) { // switch setting + MoveCursor(current_setting, direction, 2); } else { // edit if(current_setting == 0) { if (sample_ticks < 32) sample_ticks += direction; @@ -186,7 +186,7 @@ private: gfxPrint(freeze ? "ON" : "OFF"); } - if (isEditing) gfxInvert(1, 25, 31, 9); + if (EditMode()) gfxInvert(1, 25, 31, 9); } } diff --git a/software/o_c_REV/HEM_SequenceX.ino b/software/o_c_REV/HEM_SequenceX.ino index 34d81bb73..ce0ab186a 100644 --- a/software/o_c_REV/HEM_SequenceX.ino +++ b/software/o_c_REV/HEM_SequenceX.ino @@ -65,20 +65,21 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, SEQX_STEPS-1); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, SEQX_STEPS-1); + if (!EditMode()) { + MoveCursor(cursor, direction, SEQX_STEPS-1); + return; + } + + if (note[cursor] + direction < 0 && cursor > 0) { + // If turning past zero, set the mute bit for this step + muted |= (0x01 << cursor); } else { - if (note[cursor] + direction < 0 && cursor > 0) { - // If turning past zero, set the mute bit for this step - muted |= (0x01 << cursor); - } else { - note[cursor] = constrain(note[cursor] + direction, 0, SEQX_MAX_VALUE); - muted &= ~(0x01 << cursor); - } + note[cursor] = constrain(note[cursor] + direction, 0, SEQX_MAX_VALUE); + muted &= ~(0x01 << cursor); } } @@ -148,7 +149,7 @@ private: gfxLine(x, 25, x, 63); } - if (s == cursor && isEditing) gfxInvert(x - 2, 25, 5, 39); + if (s == cursor && EditMode()) gfxInvert(x - 2, 25, 5, 39); } } diff --git a/software/o_c_REV/HEM_ShiftGate.ino b/software/o_c_REV/HEM_ShiftGate.ino index 574d62a58..2cfd9199a 100644 --- a/software/o_c_REV/HEM_ShiftGate.ino +++ b/software/o_c_REV/HEM_ShiftGate.ino @@ -47,10 +47,15 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } + byte ch = cursor > 1 ? 1 : 0; byte c = cursor > 1 ? cursor - 2 : cursor; if (c == 0) length[ch] = constrain(length[ch] + direction, 1, 16); diff --git a/software/o_c_REV/HEM_Shredder.ino b/software/o_c_REV/HEM_Shredder.ino index fa4f80603..211cc16b5 100644 --- a/software/o_c_REV/HEM_Shredder.ino +++ b/software/o_c_REV/HEM_Shredder.ino @@ -91,7 +91,7 @@ public: // decrement delay and if it's 0, move the cursor if (--double_click_delay < 1) { // if we hit zero before being reset (aka no double click), move the cursor - if (++cursor > 3) cursor = 0; // we should never be > 3, so this is just for safety + CursorButton(); } } } @@ -103,6 +103,10 @@ public: DrawGrid(); } + void CursorButton() { + CursorAction(cursor, 3); + } + void OnButtonPress() { if (cursor < 2) { // first two cursor params support double-click to shred voltages @@ -115,11 +119,16 @@ public: Shred(cursor); } } else { - if (++cursor > 3) cursor = 0; + CursorButton(); } } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } + if (cursor < 2) { range[cursor] += direction; if (bipolar[cursor]) { diff --git a/software/o_c_REV/HEM_Squanch.ino b/software/o_c_REV/HEM_Squanch.ino index 2f30f76d5..daa949ec0 100644 --- a/software/o_c_REV/HEM_Squanch.ino +++ b/software/o_c_REV/HEM_Squanch.ino @@ -66,10 +66,15 @@ public: } void OnButtonPress() { - if (++cursor > 2) cursor = 0; + CursorAction(cursor, 2); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 2); + return; + } + if (cursor == 2) { // Scale selection scale += direction; if (scale >= OC::Scales::NUM_SCALES) scale = 0; @@ -138,8 +143,8 @@ private: // Cursors if (cursor == 0) gfxCursor(10, 23, 18); - if (cursor == 1) gfxCursor(42, 23, 18); - if (cursor == 2) gfxCursor(13, 33, 30); // Scale Cursor + if (cursor == 1) gfxCursor(44, 23, 18); + if (cursor == 2) gfxCursor(12, 33, 30); // Scale Cursor // Little note display diff --git a/software/o_c_REV/HEM_Stairs.ino b/software/o_c_REV/HEM_Stairs.ino index a3dd33457..24f0d43d1 100644 --- a/software/o_c_REV/HEM_Stairs.ino +++ b/software/o_c_REV/HEM_Stairs.ino @@ -32,8 +32,11 @@ class Stairs : public HemisphereApplet { public: - // Icons made with http://beigemaze.com/bitmap8x8.html (Thanks for making this public!) - const uint8_t STAIRS_ICON[8] = {0x00,0x20,0x20,0x38,0x08,0x0e,0x02,0x02}; // Some stairs going up + enum StairsCursor { + STEPS, + DIRECTION, + RANDOM + }; const char* applet_name() { return "Stairs"; @@ -200,29 +203,30 @@ public: } void OnButtonPress() { - if(++cursor > 2) cursor = 0; - - ResetCursor(); // Reset blink so it's immediately visible when moved + CursorAction(cursor, 2); } void OnEncoderMove(int direction) { - if (cursor == 0) - { - steps = constrain( steps + direction, 0, HEM_STAIRS_MAX_STEPS-1); // constrain includes max + if (!EditMode()) { + MoveCursor(cursor, direction, 2); + return; } - else if (cursor == 1) - { + + switch ((StairsCursor)cursor) { + case STEPS: + steps = constrain( steps + direction, 0, HEM_STAIRS_MAX_STEPS-1); // constrain includes max + break; + case DIRECTION: dir = constrain( dir + direction, 0, 2); // Don't change current direction if up/down mode if(dir != 1) - { reverse = (dir == 2); // Change current trend to up or down if required - } - } - else - { + + break; + case RANDOM: rand = 1-rand; + break; } } @@ -271,8 +275,7 @@ private: //int8_t graph_pos; // Current position on the graph //int8_t graph_points[HEM_STAIRS_GRAPH_SIZE]; - - int cursor; // 0 = steps, 1 = direction, 2 = random + int cursor; // StairsCursor void DrawDisplay() { @@ -337,17 +340,16 @@ private: */ // Cursor - if(cursor == 0) - { + switch ((StairsCursor)cursor) { + case STEPS: gfxCursor(16, 23, 15); // flashing underline on the number - } - else if(cursor == 1) - { + break; + case DIRECTION: gfxCursor(34, 23, 9); // flashing underline on up/down icon - } - else - { + break; + case RANDOM: gfxCursor(16, 43, 20); // flashing underline on the random setting + break; } } }; diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 55caf1b01..6370235e1 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -263,17 +263,17 @@ class TB_3PO : public HemisphereApplet } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 8); } void OnEncoderMove(int direction) { - if (!isEditing) { // move cursor - cursor = constrain(cursor + direction, 0, 8); + if (!EditMode()) { // move cursor + MoveCursor(cursor, direction, 8); + if (!lock_seed && cursor == 1) cursor = 5; // skip from 1 to 5 if not locked if (!lock_seed && cursor == 4) cursor = 0; // skip from 4 to 0 if not locked - ResetCursor(); // Reset blink so it's immediately visible when moved return; } diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 0b625cab9..e8d2b1156 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -52,7 +52,7 @@ public: CVMODE1, CVMODE2, SLEW, - LAST_SETTING + LAST_SETTING = SLEW }; enum OutputMode { @@ -223,17 +223,16 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, LAST_SETTING); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = (TM2Cursor) constrain(cursor + direction, 0, LAST_SETTING-1); - ResetCursor(); // Reset blink so it's immediately visible when moved + if (!EditMode()) { + MoveCursor(cursor, direction, LAST_SETTING); return; } - switch (cursor) { + switch ((TM2Cursor)cursor) { case LENGTH: length = constrain(length + direction, TM2_MIN_LENGTH, TM2_MAX_LENGTH); break; @@ -314,7 +313,7 @@ protected: private: int length = 16; // Sequence length int len_mod; // actual length after CV mod - TM2Cursor cursor; + int cursor; // TM2Cursor braids::Quantizer quantizer; // Settings @@ -420,7 +419,7 @@ private: gfxBitmap(41, 25, 8, UP_DOWN_ICON); gfxPrint(49, 25, range_mod); // APD - switch (cursor) { + switch ((TM2Cursor)cursor) { default: ForEachChannel(ch) DrawOutputMode(ch); @@ -438,7 +437,7 @@ private: break; } - switch (cursor) { + switch ((TM2Cursor)cursor) { case LENGTH: gfxCursor(13, 23, 12); break; case PROB: gfxCursor(45, 23, 18); break; case SCALE: gfxCursor(12, 33, 25); break; diff --git a/software/o_c_REV/HEM_Trending.ino b/software/o_c_REV/HEM_Trending.ino index 2271aaf73..e26d6b780 100644 --- a/software/o_c_REV/HEM_Trending.ino +++ b/software/o_c_REV/HEM_Trending.ino @@ -92,11 +92,15 @@ public: } void OnButtonPress() { - if (++cursor > 2) cursor = 0; - ResetCursor(); + CursorAction(cursor, 2); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 2); + return; + } + if (cursor < 2) { assign[cursor] = constrain(assign[cursor] + direction, 0, 5); reset[cursor] = 1; diff --git a/software/o_c_REV/HEM_VectorEG.ino b/software/o_c_REV/HEM_VectorEG.ino index accbeacd5..21b97a7b3 100644 --- a/software/o_c_REV/HEM_VectorEG.ino +++ b/software/o_c_REV/HEM_VectorEG.ino @@ -62,10 +62,15 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } + byte c = cursor; byte ch = cursor < 2 ? 0 : 1; if (ch) c -= 2; @@ -135,7 +140,7 @@ private: DrawWaveform(ch); if (c == 0) gfxCursor(8, 23, 55); - if (c == 1 && CursorBlink()) gfxFrame(0, 24, 63, 40); + if (c == 1 && (EditMode() || CursorBlink()) ) gfxFrame(0, 24, 63, 40); } void DrawWaveform(byte ch) { diff --git a/software/o_c_REV/HEM_VectorLFO.ino b/software/o_c_REV/HEM_VectorLFO.ino index 6a41827a8..21ad190e5 100644 --- a/software/o_c_REV/HEM_VectorLFO.ino +++ b/software/o_c_REV/HEM_VectorLFO.ino @@ -92,10 +92,14 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } byte c = cursor; byte ch = cursor < 2 ? 0 : 1; if (ch) c -= 2; @@ -194,7 +198,7 @@ private: DrawWaveform(ch); if (c == 0) gfxCursor(8, 23, 55); - if (c == 1 && CursorBlink()) gfxFrame(0, 24, 63, 40); + if (c == 1 && (EditMode() || CursorBlink()) ) gfxFrame(0, 24, 63, 40); } void DrawWaveform(byte ch) { diff --git a/software/o_c_REV/HEM_VectorMod.ino b/software/o_c_REV/HEM_VectorMod.ino index 78933d6d6..6ed3d082e 100644 --- a/software/o_c_REV/HEM_VectorMod.ino +++ b/software/o_c_REV/HEM_VectorMod.ino @@ -55,10 +55,14 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } byte c = cursor; byte ch = cursor < 2 ? 0 : 1; if (ch) c -= 2; @@ -127,7 +131,7 @@ private: DrawWaveform(ch); if (c == 0) gfxCursor(8, 23, 55); - if (c == 1 && CursorBlink()) gfxFrame(0, 24, 63, 40); + if (c == 1 && (EditMode() || CursorBlink()) ) gfxFrame(0, 24, 63, 40); } void DrawWaveform(byte ch) { diff --git a/software/o_c_REV/HEM_VectorMorph.ino b/software/o_c_REV/HEM_VectorMorph.ino index a7685de8c..df748e593 100644 --- a/software/o_c_REV/HEM_VectorMorph.ino +++ b/software/o_c_REV/HEM_VectorMorph.ino @@ -59,10 +59,14 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } byte c = cursor; byte ch = cursor < 2 ? 0 : 1; if (ch) c -= 2; @@ -131,7 +135,7 @@ private: // Cursors if (c == 0) gfxCursor(8, 23, 55); - if (c == 1 && CursorBlink()) gfxFrame(0, 24, 63, 40); + if (c == 1 && (EditMode() || CursorBlink()) ) gfxFrame(0, 24, 63, 40); // Link icon if (linked) gfxIcon(54, 15, LINK_ICON); diff --git a/software/o_c_REV/HEM_Voltage.ino b/software/o_c_REV/HEM_Voltage.ino index 84c7c66a6..dcc91d0ef 100644 --- a/software/o_c_REV/HEM_Voltage.ino +++ b/software/o_c_REV/HEM_Voltage.ino @@ -56,13 +56,12 @@ public: } void OnButtonPress() { - isEditing = !isEditing; + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { - if (!isEditing) { - cursor = constrain(cursor + direction, 0, 3); - ResetCursor(); + if (!EditMode()) { + MoveCursor(cursor, direction, 3); return; } diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index fef15e0dc..b32c557af 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -60,6 +60,8 @@ const uint8_t LEFT_RIGHT_ICON[8] = {0x10,0x38,0x7c,0x10,0x10,0x7c,0x38,0x10}; const uint8_t SEGMENT_ICON[8] = {0xc0,0xc0,0x20,0x10,0x08,0x06,0x06,0x00}; const uint8_t WAVEFORM_ICON[8] = {0x10,0x08,0x04,0x08,0x10,0x20,0x10,0x08}; +const uint8_t STAIRS_ICON[8] = {0x00,0x20,0x20,0x38,0x08,0x0e,0x02,0x02}; // Some stairs going up + // Superscript and subscript 1 and 2 const uint8_t SUP_ONE[3] = {0x0a,0x0f,0x08}; const uint8_t SUB_TWO[3] = {0x90,0xd0,0xa0}; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 7bf245b35..be8c595be 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -74,6 +74,10 @@ typedef struct PackLocation { class HemisphereApplet { public: + static uint8_t modal_edit_mode; + static void CycleEditMode() { + ++modal_edit_mode %= 3; + } virtual const char* applet_name(); // Maximum of 9 characters virtual void Start(); @@ -181,6 +185,29 @@ class HemisphereApplet { } } + // handle modal edit mode toggle or cursor advance + void CursorAction(int &cursor, int max) { + if (modal_edit_mode) { + isEditing = !isEditing; + } else { + cursor++; + cursor %= max + 1; + } + } + void MoveCursor(int &cursor, int direction, int max) { + cursor += direction; + if (modal_edit_mode == 2) { // wrap cursor + if (cursor < 0) cursor = max; + else cursor %= max + 1; + } else { + cursor = constrain(cursor, 0, max); + } + ResetCursor(); + } + bool EditMode() { + return (isEditing || !modal_edit_mode); + } + //////////////// Offset graphics methods //////////////////////////////////////////////////////////////////////////////// void gfxCursor(int x, int y, int w, int h = 9) { // assumes standard text height for highlighting @@ -481,3 +508,5 @@ class HemisphereApplet { bool changed_cv[2]; // Has the input changed by more than 1/8 semitone since the last read? int last_cv[2]; // For change detection }; + +uint8_t HemisphereApplet::modal_edit_mode = 1; // 0=old behavior, 1=modal editing, 2=modal with wraparound From 11524b838b08e7c90a039aad1a23f4ca5ddbe5c0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 30 Jan 2023 00:48:12 -0500 Subject: [PATCH 141/417] Carpeggio: modal editing + some other UI tweaks --- software/o_c_REV/HEM_BootsNCat.ino | 2 +- software/o_c_REV/HEM_Carpeggio.ino | 54 +++++++++++++++++++++-------- software/o_c_REV/HemisphereApplet.h | 1 + 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/software/o_c_REV/HEM_BootsNCat.ino b/software/o_c_REV/HEM_BootsNCat.ino index b91522130..983224384 100644 --- a/software/o_c_REV/HEM_BootsNCat.ino +++ b/software/o_c_REV/HEM_BootsNCat.ino @@ -195,7 +195,7 @@ private: byte p = is_cursor ? 1 : 3; gfxDottedLine(x, y + 4, 62, y + 4, p); gfxRect(x + w, y, 2, 7); - if (EditMode() && is_cursor) gfxInvert(x, y, 17, 8); + if (EditMode() && is_cursor) gfxInvert(x, y, 18, 7); } void SetBDFreq() { diff --git a/software/o_c_REV/HEM_Carpeggio.ino b/software/o_c_REV/HEM_Carpeggio.ino index 0894f3a85..de57b3d19 100644 --- a/software/o_c_REV/HEM_Carpeggio.ino +++ b/software/o_c_REV/HEM_Carpeggio.ino @@ -27,6 +27,14 @@ class Carpeggio : public HemisphereApplet { public: + enum CarpeggioCursor { + CHORD, + TRANSPOSE, + SHUFFLE, + NOTE_EDIT, + LAST_SETTING = NOTE_EDIT + }; + const char* applet_name() { return "Carpeggio"; } @@ -90,28 +98,46 @@ public: } void OnButtonPress() { + if (cursor == SHUFFLE && !EditMode()) // special case toggle + shuffle ? ImprintChord(sel_chord) : ShuffleChord(); + else // Advance or toggle cursor + CursorAction(cursor, LAST_SETTING); + // Set a chord imprint if a new chord is picked - if (cursor == 1 && chord != sel_chord) { - cursor = 0; // Don't advance cursor when chord is changed + if (chord != sel_chord) { ImprintChord(chord); + cursor = CHORD; // keep cursor on chord selection } - if (++cursor > 3) cursor = 0; - ResetCursor(); } void OnEncoderMove(int direction) { - if (cursor == 0) sequence[step] = constrain(sequence[step] + direction, -24, 60); - if (cursor == 1) chord = constrain(chord + direction, 0, Nr_of_arp_chords - 1); - if (cursor == 2) transpose = constrain(transpose + direction, -24, 24); - if (cursor == 3) { + if (!EditMode()) { + MoveCursor(cursor, direction, LAST_SETTING); + return; + } + + switch ((CarpeggioCursor)cursor) { + case NOTE_EDIT: + sequence[step] = constrain(sequence[step] + direction, -24, 60); + break; + case CHORD: + chord = constrain(chord + direction, 0, Nr_of_arp_chords - 1); + break; + case TRANSPOSE: + transpose = constrain(transpose + direction, -24, 24); + break; + + case SHUFFLE: if (shuffle && direction < 0) { ImprintChord(sel_chord); } if (direction > 0) { ShuffleChord(); } + break; } - if (cursor != 1) replay = 1; + + if (cursor != CHORD) replay = 1; } uint64_t OnDataRequest() { @@ -137,7 +163,7 @@ protected: } private: - int cursor; // 0=notes, 1=chord + int cursor; // CarpeggioCursor // Sequencer state uint8_t step; // Current step number @@ -158,7 +184,7 @@ private: void DrawSelector() { // Chord selector gfxPrint(0, 15, Arp_Chords[chord].chord_name); - if (cursor == 1) { + if (cursor == CHORD) { gfxCursor(1, 23, 53); gfxBitmap(55, 14, 8, chord == sel_chord ? CHECK_ON_ICON : CHECK_OFF_ICON); } @@ -167,18 +193,18 @@ private: gfxPrint(32, 25, "Tr"); gfxPrint(transpose < 0 ? "" : "+"); gfxPrint(transpose); - if (cursor == 2) gfxCursor(32, 33, 30); + if (cursor == TRANSPOSE) gfxCursor(32, 33, 30); // Shuffle selector gfxBitmap(37, 36, 8, PLAY_ICON); gfxBitmap(49, 36, 8, LOOP_ICON); gfxInvert(36 + (shuffle ? 12 : 0), 35, 10, 10); - if (cursor == 3) gfxCursor(37, 46, 20); + if (cursor == SHUFFLE) gfxCursor(36, 46, 22, 11); // Note name editor uint8_t midi_note = constrain(sequence[step] + 36 + transpose, 0, 127); gfxPrint(38, 50, midi_note_numbers[midi_note]); - if (cursor == 0) gfxCursor(32, 58, 30); + if (cursor == NOTE_EDIT) gfxCursor(32, 58, 30); } void DrawGrid() { diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index be8c595be..5e6cafdb2 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -192,6 +192,7 @@ class HemisphereApplet { } else { cursor++; cursor %= max + 1; + ResetCursor(); } } void MoveCursor(int &cursor, int direction, int max) { From e320f1d1901b1f36c2aadabfa7e892a8099dd825 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 1 Feb 2023 06:00:32 -0500 Subject: [PATCH 142/417] Number of available applets is computed at compile time To achieve this, I moved the available_applets array to the global scope, much like the array of top-level apps. Maybe consider a new namespace "HEM" if name collisions become a concern. --- software/o_c_REV/APP_HEMISPHERE.ino | 9 +++++---- software/o_c_REV/hemisphere_config.h | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 76a2d93bc..7447f402f 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -54,7 +54,7 @@ typedef struct Applet { void (*OnDataReceive)(bool, uint64_t); // Send a data int to the applet } Applet; -// The settings specify the selected applets, and 32 bits of data for each applet +// The settings specify the selected applets, and 64 bits of data for each applet enum HEMISPHERE_SETTINGS { HEMISPHERE_SELECTED_LEFT_ID, HEMISPHERE_SELECTED_RIGHT_ID, @@ -71,6 +71,9 @@ enum HEMISPHERE_SETTINGS { HEMISPHERE_SETTING_LAST }; +Applet available_applets[] = HEMISPHERE_APPLETS; +const int HEMISPHERE_AVAILABLE_APPLETS = ARRAY_SIZE(available_applets); + //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Manager //////////////////////////////////////////////////////////////////////////////// @@ -81,8 +84,7 @@ public: void Init() { select_mode = -1; // Not selecting midi_in_hemisphere = -1; // No MIDI In - Applet applets[] = HEMISPHERE_APPLETS; - memcpy(&available_applets, &applets, sizeof(applets)); + ClockSetup = DECLARE_APPLET(9999, 0x01, ClockSetup); help_hemisphere = -1; @@ -323,7 +325,6 @@ public: } private: - Applet available_applets[HEMISPHERE_AVAILABLE_APPLETS]; Applet ClockSetup; int my_applet[2]; // Indexes to available_applets int select_mode; diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index c1e9e83f3..1105e912d 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -11,8 +11,6 @@ // * Category filtering is deprecated at 1.8, but I'm leaving the per-applet categorization // alone to avoid breaking forked codebases by other developers. -#define HEMISPHERE_AVAILABLE_APPLETS 60 - ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ DECLARE_APPLET( 8, 0x01, ADSREG), \ From bd537685086bf1c6949e3458289ed2375b688716 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 30 Jan 2023 08:07:35 -0500 Subject: [PATCH 143/417] ClockManager refinements Physical triggers are passed thru when multiplier is x0 Resync happens on every beat, even for division. External clocks will still knock it out of phase sometimes... might need more hysteresis in Nudge(), but it works well most of the time. --- software/o_c_REV/HSClockManager.h | 70 ++++++++++++++--------------- software/o_c_REV/HemisphereApplet.h | 17 ++++--- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index e44047e3f..58916a607 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -38,6 +38,13 @@ constexpr int CLOCK_MIN_MULTIPLE = -31; // becomes /32 class ClockManager { static ClockManager *instance; + enum ClockOutput { + LEFT_CLOCK, + RIGHT_CLOCK, + MIDI_CLOCK, + NR_OF_CLOCKS + }; + uint16_t tempo; // The set tempo, for display somewhere else uint32_t ticks_per_beat; // Based on the selected tempo in BPM bool running = 0; // Specifies whether the clock is running for interprocess communication @@ -45,13 +52,12 @@ class ClockManager { bool forwarded = 0; // Master clock forwarding is enabled when true uint32_t clock_tick = 0; // tick when a physical clock was received on DIGITAL 1 - uint32_t beat_tick[3] = {0,0,0}; // The tick to count from - uint32_t last_tock_check[3] = {0,0,0}; // To avoid checking the tock more than once per tick - bool tock[3] = {0,0,0}; // The current tock value - int tocks_per_beat[3] = {1, 1, MIDI_OUT_PPQN}; // Multiplier + uint32_t beat_tick = 0; // The tick to count from + bool tock[NR_OF_CLOCKS] = {0,0,0}; // The current tock value + int tocks_per_beat[NR_OF_CLOCKS] = {1, 1, MIDI_OUT_PPQN}; // Multiplier int clock_ppqn = 4; // external clock multiple bool cycle = 0; // Alternates for each tock, for display purposes - int count[3] = {0,0,0}; // Multiple counter, 0 is a special case when first starting the clock + int count[NR_OF_CLOCKS] = {0,0,0}; // Multiple counter, 0 is a special case when first starting the clock bool boop[4]; // Manual triggers @@ -95,18 +101,18 @@ class ClockManager { // Resync multipliers, optionally skipping the first tock void Reset(bool count_skip = 0) { - for (int ch = 0; ch < 3; ch++) { - beat_tick[ch] = OC::CORE::ticks; - count[ch] = count_skip; + beat_tick = OC::CORE::ticks; + for (int ch = 0; ch < NR_OF_CLOCKS; ch++) { + if (tocks_per_beat[ch] > 0 || 0 == count_skip) count[ch] = count_skip; } cycle = 1 - cycle; } // used to align the internal clock with incoming clock pulses void Nudge(int diff) { - for (int ch = 0; ch < 3; ch++) { - beat_tick[ch] += diff; - } + if (diff > 0) diff--; + if (diff < 0) diff++; + beat_tick += diff; } // called on every tick when clock is running, before all Controllers @@ -115,33 +121,33 @@ class ClockManager { // Reset only when all multipliers have been met bool reset = 1; - int div_count = 1; - - for (int ch = 0; ch < 2; ch++) { - if (tocks_per_beat[ch] < 0) - div_count = div_count * ( 1 - tocks_per_beat[ch] ); - } // count and calculate Tocks - for (int ch = 0; ch < 3; ch++) { + for (int ch = 0; ch < NR_OF_CLOCKS; ch++) { if (tocks_per_beat[ch] == 0) { // disabled tock[ch] = 0; continue; } if (tocks_per_beat[ch] > 0) { // multiply - uint32_t next_tock_tick = beat_tick[ch] + count[ch]*ticks_per_beat / static_cast(tocks_per_beat[ch]); + uint32_t next_tock_tick = beat_tick + count[ch]*ticks_per_beat / static_cast(tocks_per_beat[ch]); tock[ch] = now >= next_tock_tick; if (tock[ch]) ++count[ch]; // increment multiplier counter reset = reset && (count[ch] > tocks_per_beat[ch]); // multiplier has been exceeded } else { // division: -1 becomes /2, -2 becomes /3, etc. int div = 1 - tocks_per_beat[ch]; - bool beat_exceeded = (now >= (beat_tick[ch] + count[ch] * ticks_per_beat)); - if (beat_exceeded) ++count[ch]; - - tock[ch] = beat_exceeded && ((count[ch] % div) == 1); - - reset = reset && (count[ch] > div_count); + uint32_t next_beat = beat_tick + (count[ch] ? ticks_per_beat : 0); + bool beat_exceeded = (now > next_beat); + if (beat_exceeded) { + ++count[ch]; + tock[ch] = (count[ch] % div) == 1; + } + else + tock[ch] = 0; + + // resync on every beat + reset = reset && beat_exceeded; + if (tock[ch]) count[ch] = 1; } } @@ -162,13 +168,13 @@ class ClockManager { int ticks_per_clock = ticks_per_beat / clock_ppqn; // rounded down // time since last beat - int tick_offset = now - beat_tick[2]; + int tick_offset = now - beat_tick; // too long ago? time til next beat if (tick_offset > ticks_per_clock / 2) tick_offset -= ticks_per_beat; - // within half a clock pulse of the nearest beat - if (abs(tick_offset) <= ticks_per_clock / 2) + // within half a clock pulse of the nearest beat AND significantly large + if (abs(tick_offset) < ticks_per_clock / 2 && abs(tick_offset) > 4) Nudge(tick_offset); // nudge the beat towards us } @@ -179,7 +185,6 @@ class ClockManager { } void Start(bool p = 0) { - // forwarded = 0; // NJM- logical clock can be forwarded, too Reset(); running = 1; paused = p; @@ -194,11 +199,6 @@ class ClockManager { void ToggleForwarding() { forwarded = 1 - forwarded; - if (forwarded) { - // sync start point on next beat for multiplier - count[1] = 0; - beat_tick[1] = beat_tick[0] + ticks_per_beat; - } } void SetForwarding(bool f) {forwarded = f;} @@ -228,7 +228,7 @@ class ClockManager { // Returns true if MIDI Clock should be sent on this tick bool MIDITock() { - return Tock(2); + return Tock(MIDI_CLOCK); } bool EndOfBeat(bool ch = 0) {return count[ch] == 1;} diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 5e6cafdb2..769d55c91 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -358,14 +358,17 @@ class HemisphereApplet { if (ch == 0) { // clock triggers if (hemisphere == LEFT_HEMISPHERE) { - if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); - else clocked = OC::DigitalInputs::clocked(); + if (!physical && clock_m->IsRunning() && clock_m->GetMultiply(hemisphere) != 0) + clocked = clock_m->Tock(hemisphere); + else + clocked = OC::DigitalInputs::clocked(); } else { // right side is special - if (master_clock_bus) { // forwarding from left - if (!physical && clock_m->IsRunning()) clocked = clock_m->Tock(hemisphere); - else clocked = OC::DigitalInputs::clocked(); - } - else clocked = OC::DigitalInputs::clocked(); + if (!physical && clock_m->IsRunning() && clock_m->GetMultiply(hemisphere) != 0) + clocked = clock_m->Tock(hemisphere); + else if (master_clock_bus) // forwarding from left + clocked = OC::DigitalInputs::clocked(); + else + clocked = OC::DigitalInputs::clocked(); } } else if (ch == 1) { // simple physical trig check if (hemisphere == LEFT_HEMISPHERE) From 18d7747aa8dd5b6cbd31a5f44a167e17f05c58b5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 1 Feb 2023 07:09:43 -0500 Subject: [PATCH 144/417] Move string literal initializations out of Start() --- software/o_c_REV/HEM_Logic.ino | 11 ++--------- software/o_c_REV/HEM_hMIDIIn.ino | 5 +---- software/o_c_REV/HEM_hMIDIOut.ino | 4 +--- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/software/o_c_REV/HEM_Logic.ino b/software/o_c_REV/HEM_Logic.ino index d97542f9d..dae0dd860 100644 --- a/software/o_c_REV/HEM_Logic.ino +++ b/software/o_c_REV/HEM_Logic.ino @@ -49,13 +49,6 @@ public: selected = 0; operation[0] = 0; operation[1] = 2; - const char * op_name_list[] = {"AND", "OR", "XOR", "NAND", "NOR", "XNOR", "-CV-"}; - LogicGateFunction logic_gate_list[] = {hem_AND, hem_OR, hem_XOR, hem_NAND, hem_NOR, hem_XNOR, hem_null}; - for(int i = 0; i < HEMISPHERE_NUMBER_OF_LOGIC; i++) - { - op_name[i] = op_name_list[i]; - logic_gate[i] = logic_gate_list[i]; - } } void Controller() { @@ -116,8 +109,8 @@ protected: } private: - const char* op_name[HEMISPHERE_NUMBER_OF_LOGIC]; - LogicGateFunction logic_gate[HEMISPHERE_NUMBER_OF_LOGIC]; + const char* op_name[HEMISPHERE_NUMBER_OF_LOGIC] = {"AND", "OR", "XOR", "NAND", "NOR", "XNOR", "-CV-"}; + LogicGateFunction logic_gate[HEMISPHERE_NUMBER_OF_LOGIC] = {hem_AND, hem_OR, hem_XOR, hem_NAND, hem_NOR, hem_XNOR, hem_null}; int operation[2]; bool result[2]; int source[2]; diff --git a/software/o_c_REV/HEM_hMIDIIn.ino b/software/o_c_REV/HEM_hMIDIIn.ino index bcd7c3db3..04bb8e67a 100644 --- a/software/o_c_REV/HEM_hMIDIIn.ino +++ b/software/o_c_REV/HEM_hMIDIIn.ino @@ -57,9 +57,6 @@ public: first_note = -1; channel = 0; // Default channel 1 - const char * fn_name_list[] = {"Note#", "Trig", "Gate", "Veloc", "Mod", "Aft", "Bend", "Clock"}; - for (int i = 0; i < 8; i++) fn_name[i] = fn_name_list[i]; - ForEachChannel(ch) { function[ch] = ch * 2; @@ -227,7 +224,7 @@ private: int cursor; // 0=MIDI channel, 1=A/C function, 2=B/D function int last_tick; // Tick of last received message int first_note; // First note received, for awaiting Note Off - const char* fn_name[8]; + const char* fn_name[8] = {"Note#", "Trig", "Gate", "Veloc", "Mod", "Aft", "Bend", "Clock"}; uint8_t clock_count; // MIDI clock counter (24ppqn) // Logging diff --git a/software/o_c_REV/HEM_hMIDIOut.ino b/software/o_c_REV/HEM_hMIDIOut.ino index 6ba53c109..1d23d466a 100644 --- a/software/o_c_REV/HEM_hMIDIOut.ino +++ b/software/o_c_REV/HEM_hMIDIOut.ino @@ -42,8 +42,6 @@ public: legato = 1; log_index = 0; - const char * fn_name_list[] = {"Mod", "Aft", "Bend", "Veloc"}; - for (int i = 0; i < 4; i++) fn_name[i] = fn_name_list[i]; } void Controller() { @@ -189,7 +187,7 @@ private: bool legato_on; // The note handler may currently respond to legato note changes int last_tick; // Most recent MIDI message sent int adc_lag_countdown; - const char* fn_name[4]; + const char* fn_name[4] = {"Mod", "Aft", "Bend", "Veloc"}; // Logging MIDILogEntry log[7]; From c7b56ece8dc7befde89d9192e5a309cbb1548033 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 7 Feb 2023 10:44:15 -0500 Subject: [PATCH 145/417] ClockManager: external PPQN can be x0 to disable sync --- software/o_c_REV/HSClockManager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 58916a607..c28cb4ee9 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -78,7 +78,7 @@ class ClockManager { // adjusts the expected clock multiple for external clock pulses void SetClockPPQN(int clkppqn) { - clock_ppqn = constrain(clkppqn, 1, 24); + clock_ppqn = constrain(clkppqn, 0, 24); } /* Set ticks per tock, based on one million ticks per minute divided by beats per minute. @@ -154,7 +154,7 @@ class ClockManager { if (reset) Reset(1); // skip the one we're already on // handle syncing to physical clocks - if (clocked && clock_tick) { + if (clocked && clock_tick && clock_ppqn) { uint32_t clock_diff = now - clock_tick; if (clock_ppqn * clock_diff > CLOCK_TICKS_MAX) clock_tick = 0; // too slow, reset clock tracking From b0d32f50ee71dc4ca17ddb348ab9a33cad8f0c21 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 7 Feb 2023 10:40:46 -0500 Subject: [PATCH 146/417] EnvFollow improvements modal editing, use rectified signal for detection add speed control for quicker response --- software/o_c_REV/HEM_EnvFollow.ino | 70 ++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/HEM_EnvFollow.ino b/software/o_c_REV/HEM_EnvFollow.ino index 4d6d04772..1b262778a 100644 --- a/software/o_c_REV/HEM_EnvFollow.ino +++ b/software/o_c_REV/HEM_EnvFollow.ino @@ -19,10 +19,18 @@ // SOFTWARE. #define HEM_ENV_FOLLOWER_SAMPLES 166 +#define HEM_ENV_FOLLOWER_MAXSPEED 16 class EnvFollow : public HemisphereApplet { public: + enum EnvFollowCursor { + MODE_1, MODE_2, + GAIN_1, GAIN_2, + SPEED, + MAX_CURSOR = SPEED + }; + const char* applet_name() { return "EnvFollow"; } @@ -51,9 +59,10 @@ public: ForEachChannel(ch) { - if (In(ch) > max[ch]) max[ch] = In(ch); - if (target[ch] > signal[ch]) signal[ch]++; - else if (target[ch] < signal[ch]) signal[ch]--; + int v = abs(In(ch)); + if (v > max[ch]) max[ch] = v; + if (target[ch] > signal[ch]) signal[ch] += speed; + else if (target[ch] < signal[ch]) signal[ch] -= speed; Out(ch, signal[ch]); } } @@ -65,15 +74,29 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; - ResetCursor(); + CursorAction(cursor, MAX_CURSOR); } void OnEncoderMove(int direction) { - if (cursor < 2) { // Gain per channel - gain[cursor] = constrain(gain[cursor] + direction, 1, 31); - } else { - duck[cursor - 2] = 1 - duck[cursor - 2]; + if (!EditMode()) { + MoveCursor(cursor, direction, MAX_CURSOR); + return; + } + + switch (cursor) { + case GAIN_1: + case GAIN_2: + gain[cursor - GAIN_1] = constrain(gain[cursor - GAIN_1] + direction, 1, 31); + break; + + case MODE_1: + case MODE_2: + duck[cursor] = 1 - duck[cursor]; + break; + + case SPEED: + speed = constrain(speed + direction, 1, HEM_ENV_FOLLOWER_MAXSPEED); + break; } ResetCursor(); } @@ -84,6 +107,7 @@ public: Pack(data, PackLocation {5,5}, gain[1]); Pack(data, PackLocation {10,1}, duck[0]); Pack(data, PackLocation {11,1}, duck[1]); + Pack(data, PackLocation {12,4}, speed - 1); return data; } @@ -92,6 +116,8 @@ public: gain[1] = Unpack(data, PackLocation {5,5}); duck[0] = Unpack(data, PackLocation {10,1}); duck[1] = Unpack(data, PackLocation {11,1}); + speed = Unpack(data, PackLocation {12,4}) + 1; + speed = constrain(speed, 1, HEM_ENV_FOLLOWER_MAXSPEED); } protected: @@ -100,12 +126,12 @@ protected: help[HEMISPHERE_HELP_DIGITALS] = ""; help[HEMISPHERE_HELP_CVS] = "Inputs 1,2"; help[HEMISPHERE_HELP_OUTS] = "Follow/Duck"; - help[HEMISPHERE_HELP_ENCODER] = "Gain/Assign"; + help[HEMISPHERE_HELP_ENCODER] = "Gain/Assign/Speed"; // "------------------" <-- Size Guide } private: - uint8_t cursor; + int cursor; int max[2]; uint8_t countdown; int signal[2]; @@ -114,6 +140,7 @@ private: // Setting uint8_t gain[2]; bool duck[2]; // Choose between follow and duck per channel + int speed = 1; // attack/release rate void DrawInterface() { ForEachChannel(ch) @@ -124,10 +151,27 @@ private: // Gain gfxFrame(32 * ch, 25, gain[ch], 3); - if (cursor == ch && CursorBlink()) gfxRect(32 * ch, 25, gain[ch], 3); + } + + switch (cursor) { + case MODE_1: + case MODE_2: + gfxCursor(1 + (38 * cursor), 23, 24); + break; + + case GAIN_1: + case GAIN_2: + gfxCursor(32 * (cursor - GAIN_1), 29, gain[cursor - GAIN_1], 5); + break; + + case SPEED: + gfxIcon(20, 31, GAUGE_ICON); + gfxPrint(28, 31, speed); + gfxCursor(28, 39, 14); + break; + default: break; } - if (cursor > 1) gfxCursor(1 + (38 * (cursor - 2)), 23, 24); } }; From bf96e957c66e1d00db5c1917557f04ec561e1799 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 9 Feb 2023 14:24:28 -0500 Subject: [PATCH 147/417] Squashed merge from benirose/production re-enable Neural Network include OC_strings.h in o_c_REV.ino to avoid dependency problems --- software/o_c_REV/APP_ENIGMA.ino | 1 - software/o_c_REV/OC_options.h | 2 +- software/o_c_REV/OC_ui.cpp | 1 - software/o_c_REV/o_c_REV.ino | 1 + 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/APP_ENIGMA.ino b/software/o_c_REV/APP_ENIGMA.ino index 6a4cc6044..069cac1ef 100644 --- a/software/o_c_REV/APP_ENIGMA.ino +++ b/software/o_c_REV/APP_ENIGMA.ino @@ -20,7 +20,6 @@ #ifdef ENABLE_APP_ENIGMA -#include "OC_strings.h" #include "HSApplication.h" #include "HSMIDI.h" #include "enigma/TuringMachine.h" diff --git a/software/o_c_REV/OC_options.h b/software/o_c_REV/OC_options.h index d4596302c..61c46b725 100644 --- a/software/o_c_REV/OC_options.h +++ b/software/o_c_REV/OC_options.h @@ -28,7 +28,7 @@ /* files to prevent them from taking up space. Only Enigma is enabled by default. */ #define ENABLE_APP_ENIGMA #define ENABLE_APP_MIDI -//#define ENABLE_APP_NEURAL_NETWORK +#define ENABLE_APP_NEURAL_NETWORK #define ENABLE_APP_PONG #define ENABLE_APP_DARKEST_TIMELINE diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index e7f96d3da..5f8abe36e 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -8,7 +8,6 @@ #include "OC_core.h" #include "OC_gpio.h" #include "OC_menus.h" -#include "OC_strings.h" #include "OC_ui.h" #include "OC_version.h" #include "OC_options.h" diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index ab1b95ed6..08ee4edb0 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -35,6 +35,7 @@ #include "OC_calibration.h" #include "OC_digital_inputs.h" #include "OC_menus.h" +#include "OC_strings.h" #include "OC_ui.h" #include "OC_version.h" #include "OC_options.h" From 4b6432e1fddea017a8d9af8c252200e7dcab06db Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 12 Feb 2023 20:25:42 -0500 Subject: [PATCH 148/417] Increase center detent just a lil bit --- software/o_c_REV/HemisphereApplet.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 769d55c91..0f2fa3106 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -37,6 +37,7 @@ #define HEMISPHERE_MAX_CV 7680 #define HEMISPHERE_CENTER_CV 0 #endif +#define HEMISPHERE_CENTER_DETENT 80 #define HEMISPHERE_3V_CV 4608 #define HEMISPHERE_CLOCK_TICKS 100 #define HEMISPHERE_CURSOR_TICKS 12000 @@ -337,7 +338,8 @@ class HemisphereApplet { // Apply small center detent to input, so it reads zero before a threshold int DetentedIn(int ch) { - return (In(ch) > (HEMISPHERE_CENTER_CV + 64) || In(ch) < (HEMISPHERE_CENTER_CV - 64)) ? In(ch) : HEMISPHERE_CENTER_CV; + return (In(ch) > (HEMISPHERE_CENTER_CV + HEMISPHERE_CENTER_DETENT) || In(ch) < (HEMISPHERE_CENTER_CV - HEMISPHERE_CENTER_DETENT)) + ? In(ch) : HEMISPHERE_CENTER_CV; } void Out(int ch, int value, int octave = 0) { From 150d3b02e5a893609926ca62a27ea5a3b793c2a5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 12 Feb 2023 20:26:32 -0500 Subject: [PATCH 149/417] const -> static constexpr maybe this is more correct? matches the top-level app counter --- software/o_c_REV/APP_HEMISPHERE.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 7447f402f..3eb00e628 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -72,7 +72,7 @@ enum HEMISPHERE_SETTINGS { }; Applet available_applets[] = HEMISPHERE_APPLETS; -const int HEMISPHERE_AVAILABLE_APPLETS = ARRAY_SIZE(available_applets); +static constexpr int HEMISPHERE_AVAILABLE_APPLETS = ARRAY_SIZE(available_applets); //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Manager From d179f6cf04e47a1c91213779dd73467f5cc6dff4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 13 Feb 2023 20:31:15 -0500 Subject: [PATCH 150/417] Chordinator: cursor fix --- software/o_c_REV/HEM_Chordinator.ino | 45 +++++++++++++--------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index a61a039be..f0e90d557 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -67,13 +67,10 @@ public: gfxHeader(applet_name()); gfxPrint(0, 15, OC::scale_names_short[scale]); - if (cursor == 0) { - gfxCursor(0, 23, 30, selected); - } + if (cursor == 0) gfxCursor(0, 23, 30); + gfxPrint(36, 15, OC::Strings::note_names_unpadded[root]); - if (cursor == 1) { - gfxCursor(36, 23, 12, selected); - } + if (cursor == 1) gfxCursor(36, 23, 12); uint16_t mask = chord_mask; for (int i = 0; i < int(active_scale.num_notes); i++) { @@ -98,7 +95,7 @@ public: void OnButtonPress() { if (cursor < 2) { - selected = !selected; + isEditing = !isEditing; } else { chord_mask ^= 1 << (cursor - 2); update_chord_quantizer(); @@ -106,24 +103,23 @@ public: } void OnEncoderMove(int direction) { - if (selected) { - switch (cursor) { - case 0: - set_scale(scale + direction); - break; - case 1: - root = constrain(root + direction, 0, 11); - break; - default: - // shouldn't happen... - break; - } - update_chord_quantizer(); - } else { - cursor = - constrain(cursor + direction, 0, 1 + int(active_scale.num_notes)); - ResetCursor(); + if (!isEditing) { + MoveCursor(cursor, direction, 1 + int(active_scale.num_notes)); + return; + } + + switch (cursor) { + case 0: + set_scale(scale + direction); + break; + case 1: + root = constrain(root + direction, 0, 11); + break; + default: + // shouldn't happen... + break; } + update_chord_quantizer(); } uint64_t OnDataRequest() { @@ -159,7 +155,6 @@ private: braids::Scale active_scale; int cursor = 0; - bool selected = false; // Leftmost is root, second to left is 2, etc. Defaulting here to basic triad. uint16_t chord_mask = 0b10101; From 0bd6a6f82e3f0137df40c0796547019acae4d8fc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 14 Feb 2023 13:20:44 -0500 Subject: [PATCH 151/417] FLIP_180 calibration fix + PIO build target --- software/o_c_REV/OC_ADC.h | 12 ++++++++++++ software/o_c_REV/OC_DAC.h | 19 ++++++++++++++++++- software/o_c_REV/platformio.ini | 3 +++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_ADC.h b/software/o_c_REV/OC_ADC.h index 4d1a7de29..8b2f2fcbe 100644 --- a/software/o_c_REV/OC_ADC.h +++ b/software/o_c_REV/OC_ADC.h @@ -55,11 +55,19 @@ class ADC { template static int32_t value() { +#ifdef FLIP_180 + return calibration_data_->offset[ADC_CHANNEL_LAST-1 - channel] - (smoothed_[channel] >> kAdcValueShift); +#else return calibration_data_->offset[channel] - (smoothed_[channel] >> kAdcValueShift); +#endif } static int32_t value(ADC_CHANNEL channel) { +#ifdef FLIP_180 + return calibration_data_->offset[ADC_CHANNEL_LAST-1 - channel] - (smoothed_[channel] >> kAdcValueShift); +#else return calibration_data_->offset[channel] - (smoothed_[channel] >> kAdcValueShift); +#endif } static uint32_t raw_value(ADC_CHANNEL channel) { @@ -75,7 +83,11 @@ class ADC { } static int32_t raw_pitch_value(ADC_CHANNEL channel) { +#ifdef FLIP_180 + int32_t value = calibration_data_->offset[ADC_CHANNEL_LAST-1 - channel] - raw_value(channel); +#else int32_t value = calibration_data_->offset[channel] - raw_value(channel); +#endif return (value * calibration_data_->pitch_cv_scale) >> 12; } diff --git a/software/o_c_REV/OC_DAC.h b/software/o_c_REV/OC_DAC.h index 219b1a8e7..bb86471ee 100644 --- a/software/o_c_REV/OC_DAC.h +++ b/software/o_c_REV/OC_DAC.h @@ -102,6 +102,9 @@ class DAC { const int32_t octave = pitch / (12 << 7); const int32_t fractional = pitch - octave * (12 << 7); +#ifdef FLIP_180 + channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); +#endif int32_t sample = calibration_data_->calibrated_octaves[channel][octave]; if (fractional) { int32_t span = calibration_data_->calibrated_octaves[channel][octave + 1] - sample; @@ -159,6 +162,9 @@ class DAC { const int32_t octave = pitch / (12 << 7); const int32_t fractional = pitch - octave * (12 << 7); +#ifdef FLIP_180 + channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); +#endif int32_t sample = calibration_data_->calibrated_octaves[channel][octave]; if (fractional) { int32_t span = calibration_data_->calibrated_octaves[channel][octave + 1] - sample; @@ -187,7 +193,12 @@ class DAC { // Set integer voltage value, where 0 = 0V, 1 = 1V static void set_octave(DAC_CHANNEL channel, int v) { - set(channel, calibration_data_->calibrated_octaves[channel][kOctaveZero + v]); +#ifdef FLIP_180 + int cal_ch = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); +#else + int cal_ch = channel; +#endif + set(channel, calibration_data_->calibrated_octaves[cal_ch][kOctaveZero + v]); } // Set all channels to integer voltage value, where 0 = 0V, 1 = 1V @@ -199,10 +210,16 @@ class DAC { } static uint32_t get_zero_offset(DAC_CHANNEL channel) { +#ifdef FLIP_180 + channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); +#endif return calibration_data_->calibrated_octaves[channel][kOctaveZero]; } static uint32_t get_octave_offset(DAC_CHANNEL channel, int octave) { +#ifdef FLIP_180 + channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); +#endif return calibration_data_->calibrated_octaves[channel][kOctaveZero + octave]; } diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index f8b3afbeb..860bc2e3c 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -28,6 +28,9 @@ upload_protocol = teensy-gui [env:oc_prod] build_flags = ${env.build_flags} +[env:oc_prod_flipped] +build_flags = ${env.build_flags} -DFLIP_180 + [env:oc_dev] build_flags = ${env.build_flags} -DOC_DEV ; -DPRINT_DEBUG From c43c15f4fbc5d4d9bfd37acd5b7cc2786d8ffb23 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 14 Feb 2023 14:10:29 -0500 Subject: [PATCH 152/417] LoFiPCM: one shared buffer for both hemispheres This is incredibly interesting when running it in both sides! --- software/o_c_REV/HEM_LoFiPCM.ino | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index f1ae4f455..59bcb60e7 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -27,8 +27,12 @@ #define PCM_TO_CV(S) Proportion((int)S - 127, 128, CLIPLIMIT) #define CV_TO_PCM(S) Proportion(constrain(S, -CLIPLIMIT, CLIPLIMIT), CLIPLIMIT, 128) + 127 +uint8_t lofi_pcm_buffer[HEM_LOFI_PCM_BUFFER_SIZE]; + class LoFiPCM : public HemisphereApplet { public: + // TODO: consider making a singleton class to manage/share buffers + const int length = HEM_LOFI_PCM_BUFFER_SIZE; const char* applet_name() { // Maximum 10 characters return "LoFi Echo"; @@ -37,7 +41,7 @@ public: void Start() { countdown = HEM_LOFI_PCM_SPEED; // this might take too long, which causes crashes. It's not crucial. - //for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) pcm[i] = 127; + //for (int i = 0; i < HEM_LOFI_PCM_BUFFER_SIZE; i++) lofi_pcm_buffer[i] = 127; cursor = 1; //for gui } @@ -63,11 +67,11 @@ public: head_w = (head + length + dt_pct*length/100) % length; //have to add the extra length to keep modulo positive in case delaytime is neg // mix input into the buffer ahead, respecting feedback - int fbmix = PCM_TO_CV(pcm[head]) * fdbk_g / 100 + cv; - pcm[head_w] = CV_TO_PCM(fbmix); + int fbmix = PCM_TO_CV(lofi_pcm_buffer[head]) * fdbk_g / 100 + cv; + lofi_pcm_buffer[head_w] = CV_TO_PCM(fbmix); - Out(0, PCM_TO_CV(pcm[head])); - Out(1, PCM_TO_CV(pcm[length-1 - head])); // reverse buffer! + Out(0, PCM_TO_CV(lofi_pcm_buffer[head])); + Out(1, PCM_TO_CV(lofi_pcm_buffer[length-1 - head])); // reverse buffer! rate_mod = constrain( rate + Proportion(cv2, HEMISPHERE_MAX_CV, 32), 1, 64); countdown = rate_mod; @@ -134,10 +138,6 @@ protected: } private: - const int length = HEM_LOFI_PCM_BUFFER_SIZE; - - // TODO: consider making a singleton class to manage/share buffers - uint8_t pcm[HEM_LOFI_PCM_BUFFER_SIZE]; bool play = 0; //play always on unless gated on Digital 1 uint16_t head = 0; // Location of read/play head uint16_t head_w = 0; // Location of write/record head @@ -156,7 +156,7 @@ private: if (pos < 0) pos += length; for (int i = 0; i < 64; i++) { - int height = Proportion((int)pcm[pos]-127, 128, 16); + int height = Proportion((int)lofi_pcm_buffer[pos]-127, 128, 16); gfxLine(i, 46, i, 46+height); pos += inc; @@ -183,7 +183,7 @@ private: gfxCursor(30 + (cursor-2)*20, 23, 14); } } - + }; From 680a51cc61911189b17184026b384fca266d8d40 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 14 Feb 2023 14:44:11 -0500 Subject: [PATCH 153/417] VectorLFO: change CV2 input to bi-polar mix level as suggested by X-Modular on Github, 0V input yields two independent LFOs. A maximum input on CV2 will yield 50/50 blend for Out B; negative voltages invert the copy of LFO1. --- software/o_c_REV/HEM_VectorLFO.ino | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/software/o_c_REV/HEM_VectorLFO.ino b/software/o_c_REV/HEM_VectorLFO.ino index 21ad190e5..2e8541ca3 100644 --- a/software/o_c_REV/HEM_VectorLFO.ino +++ b/software/o_c_REV/HEM_VectorLFO.ino @@ -49,11 +49,9 @@ public: if (mod + freq[0] > 10) osc[0].SetFrequency(freq[0] + mod); } - // Input 2 determines signal 1's attenuation on the B/D output mix; at 0V, signal 1 - // accounts for 50% of the B/D output. At 5V, signal 1 accounts for none of the - // B/D output. - int atten1 = DetentedIn(1); - atten1 = constrain(atten1, 0, HEMISPHERE_MAX_CV); + // Input 2 determines signal 1's level on the B/D output mix + int mix_level = DetentedIn(1); + mix_level = constrain(mix_level, -HEMISPHERE_MAX_CV, HEMISPHERE_MAX_CV); int signal = 0; // Declared here because the first channel's output is used in the second channel; see below ForEachChannel(ch) @@ -71,16 +69,15 @@ public: // Out A is always just the first oscillator at full amplitude signal = osc[ch].Next(); } else { - // Out B can have channel 1 blended into it, depending on the value of atten1. At a value - // of 0, Out B is a 50/50 mix of channels 1 and 2. At a value of 5V, channel 1 is absent - // from Out B. - signal = Proportion(HEMISPHERE_MAX_CV - atten1, HEMISPHERE_MAX_CV, signal); // signal from channel 1's iteration + // Out B can have channel 1 blended into it, depending on the value of mix_level. + // At a value of 6V, Out B is a 50/50 mix of channels 1 and 2. + // At a value of 0V, channel 1 is absent from Out B. + signal = Proportion(mix_level, HEMISPHERE_MAX_CV, signal); // signal from channel 1's iteration signal += osc[ch].Next(); - // Proportionally blend the signal, depending on attenuation. If atten1 is 0, then this - // effectively divides the signal by 2. If atten1 is 5V, then the channel 2 signal will be - // output at full amplitude. - signal = Proportion(HEMISPHERE_MAX_CV, HEMISPHERE_MAX_CV + (HEMISPHERE_MAX_CV - atten1), signal); + // Proportionally blend the signal, depending on mix. + // If mix_level is at (+ or -) max, then this effectively divides the signal by 2. + signal = Proportion(HEMISPHERE_MAX_CV, HEMISPHERE_MAX_CV + abs(mix_level), signal); } Out(ch, signal); } @@ -160,7 +157,7 @@ protected: void SetHelp() { // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1,2=Sync"; - help[HEMISPHERE_HELP_CVS] = "1=Freq1 2=Atten1@B"; + help[HEMISPHERE_HELP_CVS] = "1=Freq1 2=Mix 1@B"; help[HEMISPHERE_HELP_OUTS] = "Out A=1, B=2+1"; help[HEMISPHERE_HELP_ENCODER] = "Freq./Waveform"; // "------------------" <-- Size Guide From a1edc5075a206c73b3685cb4a266605122852cf3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 17 Feb 2023 20:36:23 -0500 Subject: [PATCH 154/417] Modal editing for MIDI In/Out applets --- software/o_c_REV/HEM_hMIDIIn.ino | 8 ++++++-- software/o_c_REV/HEM_hMIDIOut.ino | 13 +++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/HEM_hMIDIIn.ino b/software/o_c_REV/HEM_hMIDIIn.ino index 04bb8e67a..bb17f85cb 100644 --- a/software/o_c_REV/HEM_hMIDIIn.ino +++ b/software/o_c_REV/HEM_hMIDIIn.ino @@ -176,11 +176,15 @@ public: } void OnButtonPress() { - if (++cursor > 3) cursor = 0; - ResetCursor(); + CursorAction(cursor, 3); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } + if (cursor == 3) return; if (cursor == 0) channel = constrain(channel + direction, 0, 15); else { diff --git a/software/o_c_REV/HEM_hMIDIOut.ino b/software/o_c_REV/HEM_hMIDIOut.ino index 1d23d466a..87ec58d48 100644 --- a/software/o_c_REV/HEM_hMIDIOut.ino +++ b/software/o_c_REV/HEM_hMIDIOut.ino @@ -135,11 +135,20 @@ public: } void OnButtonPress() { - if (++cursor > 4) cursor = 0; - ResetCursor(); + if (cursor == 3 && !EditMode()) { // special case to toggle legato + legato = 1 - legato; + ResetCursor(); + return; + } + + CursorAction(cursor, 4); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 4); + return; + } if (cursor == 0) channel = constrain(channel + direction, 0, 15); if (cursor == 1) transpose = constrain(transpose + direction, -24, 24); if (cursor == 2) function = constrain(function + direction, 0, 3); From a02d79a91d0b3c57c728c399e376586ffc101907 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Feb 2023 13:21:03 -0500 Subject: [PATCH 155/417] EnvFollow: prevent over-correction with high speed values --- software/o_c_REV/HEM_EnvFollow.ino | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_EnvFollow.ino b/software/o_c_REV/HEM_EnvFollow.ino index 1b262778a..aa5c5148f 100644 --- a/software/o_c_REV/HEM_EnvFollow.ino +++ b/software/o_c_REV/HEM_EnvFollow.ino @@ -61,8 +61,14 @@ public: { int v = abs(In(ch)); if (v > max[ch]) max[ch] = v; - if (target[ch] > signal[ch]) signal[ch] += speed; - else if (target[ch] < signal[ch]) signal[ch] -= speed; + if (target[ch] > signal[ch]) { + signal[ch] += speed; + if (signal[ch] > target[ch]) signal[ch] = target[ch]; + } + else if (target[ch] < signal[ch]) { + signal[ch] -= speed; + if (signal[ch] < target[ch]) signal[ch] = target[ch]; + } Out(ch, signal[ch]); } } From 74dde1b533f9135a1bce0c979c1f207d9ada1fbb Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 17 Feb 2023 19:23:50 -0500 Subject: [PATCH 156/417] EuclidX: extra checks when params change; code readability --- software/o_c_REV/HEM_EuclidX.ino | 33 +++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 3419caee8..6369a86ab 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -51,10 +51,10 @@ public: void Start() { ForEachChannel(ch) { - actual_length[ch] = length[ch] = 16; - actual_beats[ch] = beats[ch] = 4 + (ch * 4); - actual_offset[ch] = offset[ch] = 0; - actual_padding[ch] = padding[ch] = 0; + length[ch] = 16; + beats[ch] = 4 + ch*4; + offset[ch] = 0; + padding[ch] = ch*16; pattern[ch] = EuclideanPattern(length[ch], beats[ch], offset[ch], padding[ch]); } step = 0; @@ -79,6 +79,14 @@ public: switch (cv_dest[cv_ch] - ch * LENGTH2) { // this is dumb, but efficient case LENGTH1: actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 2, 32); + + if (actual_beats[ch] > actual_length[ch]) + actual_beats[ch] = actual_length[ch]; + if (actual_padding[ch] > 32 - actual_length[ch]) + actual_padding[ch] = 32 - actual_length[ch]; + if (actual_offset[ch] >= actual_length[ch] + actual_padding[ch]) + actual_offset[ch] = actual_length[ch] + actual_padding[ch] - 1; + break; case BEATS1: actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); @@ -86,9 +94,11 @@ public: case OFFSET1: actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); break; - case PADDING1: + case PADDING1: actual_padding[ch] = constrain(actual_padding[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 32 - actual_length[ch]), 0, 32 - actual_length[ch]); - break; + if (actual_offset[ch] >= actual_length[ch] + actual_padding[ch]) + actual_offset[ch] = actual_length[ch] + actual_padding[ch] - 1; + break; default: break; } } @@ -134,9 +144,12 @@ public: case LENGTH1: case LENGTH2: actual_length[ch] = length[ch] = constrain(length[ch] + direction, 2, 32); - if (beats[ch] > length[ch]) beats[ch] = length[ch]; - if (padding[ch] > 32 - length[ch]) padding[ch] = 32 - length[ch]; - if (offset[ch] >= length[ch] + padding[ch]) offset[ch] = length[ch] + padding[ch] - 1; + if (beats[ch] > length[ch]) + beats[ch] = length[ch]; + if (padding[ch] > 32 - length[ch]) + padding[ch] = 32 - length[ch]; + if (offset[ch] >= length[ch] + padding[ch]) + offset[ch] = length[ch] + padding[ch] - 1; break; case BEATS1: case BEATS2: @@ -149,6 +162,8 @@ public: case PADDING1: case PADDING2: padding[ch] = constrain(padding[ch] + direction, 0, 32 - length[ch]); + if (offset[ch] >= length[ch] + padding[ch]) + offset[ch] = length[ch] + padding[ch] - 1; break; case CV_DEST1: case CV_DEST2: From afc4d08460a14a8727c45c0121ebef94d493e1d6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Feb 2023 17:06:54 -0500 Subject: [PATCH 157/417] new SmoothedIn() and SmoothedOut() applet methods SmoothedIn() just uses the existing ADC smoothing SmoothedOut() takes smoothing factor as a param --- software/o_c_REV/HemisphereApplet.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 0f2fa3106..58ee6d15f 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -96,6 +96,7 @@ class HemisphereApplet { clock_countdown[ch] = 0; inputs[ch] = 0; outputs[ch] = 0; + outputs_smooth[ch] = 0; adc_lag_countdown[ch] = 0; } help_active = 0; @@ -342,12 +343,24 @@ class HemisphereApplet { ? In(ch) : HEMISPHERE_CENTER_CV; } + int SmoothedIn(int ch) { + ADC_CHANNEL channel = (ADC_CHANNEL)(ch + io_offset); + return OC::ADC::value(channel); + } + void Out(int ch, int value, int octave = 0) { DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); OC::DAC::set_pitch(channel, value, octave); outputs[ch] = value + (octave * (12 << 7)); } + void SmoothedOut(int ch, int value, int kSmoothing) { + DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); + value = (outputs_smooth[channel] * (kSmoothing - 1) + value) / kSmoothing; + OC::DAC::set_pitch(channel, value, 0); + outputs[ch] = outputs_smooth[channel] = value; + } + /* * Has the specified Digital input been clocked this cycle? * @@ -502,6 +515,7 @@ class HemisphereApplet { int io_offset; // Input/Output offset, based on the side int inputs[2]; int outputs[2]; + int outputs_smooth[2]; uint32_t last_clock[2]; // Tick number of the last clock observed by the child class uint32_t cycle_ticks[2]; // Number of ticks between last two clocks int clock_countdown[2]; From c6688b5b430289002ec14423e16d717c28a9ae75 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Feb 2023 17:08:31 -0500 Subject: [PATCH 158/417] LoFiPCM: Use smoothing to filter out harshness Uses buffer rate as smoothing factor. This sounds very similar to actual tape when modulating rate. Higher rate factors (slower speed tape) sound more low-passed --- software/o_c_REV/HEM_LoFiPCM.ino | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 59bcb60e7..3a4b3bd00 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -56,7 +56,7 @@ public: //ClockOut(1); } - int cv = In(0); + int cv = SmoothedIn(0); int cv2 = DetentedIn(1); // bitcrush the input @@ -70,12 +70,13 @@ public: int fbmix = PCM_TO_CV(lofi_pcm_buffer[head]) * fdbk_g / 100 + cv; lofi_pcm_buffer[head_w] = CV_TO_PCM(fbmix); - Out(0, PCM_TO_CV(lofi_pcm_buffer[head])); - Out(1, PCM_TO_CV(lofi_pcm_buffer[length-1 - head])); // reverse buffer! - rate_mod = constrain( rate + Proportion(cv2, HEMISPHERE_MAX_CV, 32), 1, 64); + countdown = rate_mod; } + + SmoothedOut(0, PCM_TO_CV(lofi_pcm_buffer[head]), rate_mod); + SmoothedOut(1, PCM_TO_CV(lofi_pcm_buffer[length-1 - head]), rate_mod); // reverse buffer! } } From 3ffa002488718aea3d9179eca465f27299cc1c7a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 19 Feb 2023 12:36:48 -0500 Subject: [PATCH 159/417] Modal editing for 2-param applets; remove SkewedLFO --- software/o_c_REV/HEM_ASR.ino | 7 +- software/o_c_REV/HEM_Calculate.ino | 7 +- software/o_c_REV/HEM_ClockDivider.ino | 12 +- software/o_c_REV/HEM_ClockSkip.ino | 8 +- software/o_c_REV/HEM_GateDelay.ino | 11 +- software/o_c_REV/HEM_Logic.ino | 8 +- software/o_c_REV/HEM_LowerRenz.ino | 7 +- software/o_c_REV/HEM_Shuffle.ino | 6 +- software/o_c_REV/HEM_SkewedLFO.ino | 199 -------------------------- software/o_c_REV/hemisphere_config.h | 3 +- 10 files changed, 51 insertions(+), 217 deletions(-) delete mode 100644 software/o_c_REV/HEM_SkewedLFO.ino diff --git a/software/o_c_REV/HEM_ASR.ino b/software/o_c_REV/HEM_ASR.ino index dc41bd059..1367dc7b7 100644 --- a/software/o_c_REV/HEM_ASR.ino +++ b/software/o_c_REV/HEM_ASR.ino @@ -63,10 +63,15 @@ public: } void OnButtonPress() { - if (++cursor > 1) cursor = 0; + CursorAction(cursor, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 1); + return; + } + if (cursor == 0) { // Index byte ix = buffer_m->GetIndex(); buffer_m->SetIndex(ix + direction); diff --git a/software/o_c_REV/HEM_Calculate.ino b/software/o_c_REV/HEM_Calculate.ino index 7e668c132..2bb5eeea4 100644 --- a/software/o_c_REV/HEM_Calculate.ino +++ b/software/o_c_REV/HEM_Calculate.ino @@ -73,11 +73,14 @@ public: } void OnButtonPress() { - selected = 1 - selected; - ResetCursor(); + CursorAction(selected, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(selected, direction, 1); + return; + } operation[selected] = constrain(operation[selected] + direction, 0, HEMISPHERE_NUMBER_OF_CALC - 1); rand_clocked[selected] = 0; } diff --git a/software/o_c_REV/HEM_ClockDivider.ino b/software/o_c_REV/HEM_ClockDivider.ino index ac226a986..85c744576 100644 --- a/software/o_c_REV/HEM_ClockDivider.ino +++ b/software/o_c_REV/HEM_ClockDivider.ino @@ -85,11 +85,15 @@ public: } void OnButtonPress() { - cursor = 1 - cursor; - ResetCursor(); + CursorAction(cursor, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 1); + return; + } + div[cursor] += direction; if (div[cursor] > HEM_CLOCKDIV_MAX) div[cursor] = HEM_CLOCKDIV_MAX; if (div[cursor] < -HEM_CLOCKDIV_MAX) div[cursor] = -HEM_CLOCKDIV_MAX; @@ -122,14 +126,13 @@ private: int div[2] = {1, 2}; // Division data for outputs. Positive numbers are divisions, negative numbers are multipliers int count[2] = {0,0}; // Number of clocks since last output (for clock divide) int next_clock[2] = {0,0}; // Tick number for the next output (for clock multiply) - bool cursor = 0; // Which output is currently being edited + int cursor = 0; // Which output is currently being edited int cycle_time = 0; // Cycle time between the last two clock inputs void DrawSelector() { ForEachChannel(ch) { int y = 15 + (ch * 25); - if (ch == cursor) gfxCursor(0, y + 8, 63); if (div[ch] > 0) { gfxPrint(1, y, "/"); @@ -142,6 +145,7 @@ private: gfxPrint(" Mult"); } } + gfxCursor(0, 23 + (cursor * 25), 63); } }; diff --git a/software/o_c_REV/HEM_ClockSkip.ino b/software/o_c_REV/HEM_ClockSkip.ino index 0b8f46522..3d95cf68c 100644 --- a/software/o_c_REV/HEM_ClockSkip.ino +++ b/software/o_c_REV/HEM_ClockSkip.ino @@ -55,10 +55,14 @@ public: } void OnButtonPress() { - cursor = 1 - cursor; + CursorAction(cursor, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 1); + return; + } p[cursor] = constrain(p[cursor] + direction, 0, 100); } @@ -85,7 +89,7 @@ protected: private: int16_t p[2]; int trigger_countdown[2]; - uint8_t cursor; + int cursor; void DrawSelector() { diff --git a/software/o_c_REV/HEM_GateDelay.ino b/software/o_c_REV/HEM_GateDelay.ino index 50791aeb1..930e46a61 100644 --- a/software/o_c_REV/HEM_GateDelay.ino +++ b/software/o_c_REV/HEM_GateDelay.ino @@ -61,10 +61,15 @@ public: } void OnButtonPress() { - cursor = 1 - cursor; + CursorAction(cursor, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 1); + return; + } + if (time[cursor] > 100) direction *= 2; if (time[cursor] > 500) direction *= 2; if (time[cursor] > 1000) direction *= 2; @@ -98,20 +103,20 @@ private: int time[2]; // Length of each channel (in ms) uint16_t location[2]; // Location of record head (playback head = location + time) uint32_t last_gate[2]; // Time of last gate, for display of icon - uint8_t cursor; + int cursor; int16_t ms_countdown; // Countdown for 1 ms void DrawInterface() { ForEachChannel(ch) { int y = 15 + (ch * 25); - if (ch == cursor) gfxCursor(0, y + 8, 63); gfxPrint(1, y, time[ch]); gfxPrint("ms"); if (OC::CORE::ticks - last_gate[ch] < 1667) gfxBitmap(54, y, 8, CLOCK_ICON); } + gfxCursor(0, 23 + (cursor * 25), 63); } /* Write the gate state into the tape at the tape head */ diff --git a/software/o_c_REV/HEM_Logic.ino b/software/o_c_REV/HEM_Logic.ino index dae0dd860..acdd8ec28 100644 --- a/software/o_c_REV/HEM_Logic.ino +++ b/software/o_c_REV/HEM_Logic.ino @@ -77,11 +77,15 @@ public: } void OnButtonPress() { - selected = 1 - selected; - ResetCursor(); + CursorAction(selected, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(selected, direction, 1); + return; + } + operation[selected] += direction; if (operation[selected] == HEMISPHERE_NUMBER_OF_LOGIC) operation[selected] = 0; if (operation[selected] < 0) operation[selected] = HEMISPHERE_NUMBER_OF_LOGIC - 1; diff --git a/software/o_c_REV/HEM_LowerRenz.ino b/software/o_c_REV/HEM_LowerRenz.ino index 5fdfaea73..af2adc633 100644 --- a/software/o_c_REV/HEM_LowerRenz.ino +++ b/software/o_c_REV/HEM_LowerRenz.ino @@ -68,10 +68,15 @@ public: } void OnButtonPress() { - cursor = 1 - cursor; + CursorAction(cursor, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 1); + return; + } + if (cursor == 0) freq = constrain(freq + direction, 0, 255); if (cursor == 1) rho = constrain(rho + direction, 4, 127); } diff --git a/software/o_c_REV/HEM_Shuffle.ino b/software/o_c_REV/HEM_Shuffle.ino index c4c5215a0..3114b5c9d 100644 --- a/software/o_c_REV/HEM_Shuffle.ino +++ b/software/o_c_REV/HEM_Shuffle.ino @@ -93,10 +93,14 @@ public: } void OnButtonPress() { - cursor = 1 - cursor; + CursorAction(cursor, 1); } void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 1); + return; + } delay[cursor] += direction; delay[cursor] = constrain(delay[cursor], 0, 99); } diff --git a/software/o_c_REV/HEM_SkewedLFO.ino b/software/o_c_REV/HEM_SkewedLFO.ino deleted file mode 100644 index aa0dc15e6..000000000 --- a/software/o_c_REV/HEM_SkewedLFO.ino +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2018, Jason Justian -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#define HEM_LFO_HIGH 40000 -#define HEM_LFO_LOW 800 -#define HEM_LFO_MAX_VALUE 120 - -class SkewedLFO : public HemisphereApplet { -public: - - const char* applet_name() { - return "SkewedLFO"; - } - - void Start() { - rate = 61; - skew = 61; - cursor = 0; - cycle_tick = 0; - } - - void Controller() { - rate_mod = get_modification_with_input(0); - skew_mod = get_modification_with_input(1); - - cycle_tick++; - - // Handle reset trigger - if (Clock(0)) cycle_tick = 0; - - // Handle LFO output - if (cycle_tick >= TicksAtRate()) { - cycle_tick = 0; - ClockOut(1); - } - int cv = AmplitudeAtPosition(cycle_tick, HEMISPHERE_MAX_CV) - (HEMISPHERE_MAX_CV / 2); // subtract for bipolar - Out(0, cv); - } - - void View() { - gfxHeader(applet_name()); - - DrawSkewedWaveform(); - DrawRateIndicator(); - DrawWaveformPosition(); - } - - void OnButtonPress() { - cursor = 1 - cursor; - } - - void OnEncoderMove(int direction) { - if (cursor == 0) { - rate = constrain(rate + direction, 0, HEM_LFO_MAX_VALUE); - } else { - skew = constrain(skew + direction, 0, HEM_LFO_MAX_VALUE); - } - } - - uint64_t OnDataRequest() { - uint64_t data = 0; - Pack(data, PackLocation {0, 8}, skew); - Pack(data, PackLocation {8, 8}, rate); - return data; - } - - void OnDataReceive(uint64_t data) { - skew = Unpack(data, PackLocation {0,8}); - rate = Unpack(data, PackLocation {8,8}); - } - -protected: - void SetHelp() { - help[HEMISPHERE_HELP_DIGITALS] = "1=Reset"; - help[HEMISPHERE_HELP_CVS] = "Mod 1=Rate 2=Skew"; - help[HEMISPHERE_HELP_OUTS] = "A=CV B=Clock"; - help[HEMISPHERE_HELP_ENCODER] = "Rate/Skew"; - } - -private: - int rate; // LFO rate between 0 and HEM_LFO_MAX_VALUE, where 0 is slowest - int skew; // LFO skew, where 0 is saw, HEM_LFO_MAX_VALUE is ramp, and 31 is triangle - int cursor; // Whether knob is editing rate (0) or skew (1) - int cycle_tick; // The current tick number within the cycle - int rate_mod; // Modification of rate from CV 1 - int skew_mod; // Modification of skew from CV 2 - - void DrawRateIndicator() { - gfxFrame(1, 15, 62, 6); - int x = Proportion(rate, HEM_LFO_MAX_VALUE, 62); - gfxLine(x, 15, x, 20); - if (cursor == 0) gfxRect(1, 16, x, 4); - } - - void DrawSkewedWaveform() { - int x = Proportion(skew, HEM_LFO_MAX_VALUE, 62); - gfxLine(0, 62, x, 33, cursor == 0); - gfxLine(x, 33, 62, 62, cursor == 0); - - // Draw zero-crossing line - gfxDottedLine(0, 48, 63, 48, 5); - } - - void DrawWaveformPosition() { - int height = AmplitudeAtPosition(cycle_tick, 30); - int x = Proportion(cycle_tick, TicksAtRate(), 62); - gfxLine(x, 63, x, 63 - height); - } - - int AmplitudeAtPosition(int position, int max_amplitude) { - int amplitude = 0; - int effective_skew = constrain(skew + skew_mod, 0, HEM_LFO_MAX_VALUE); - int ticks_at_rate = TicksAtRate(); - int fall_point = Proportion(effective_skew, HEM_LFO_MAX_VALUE, ticks_at_rate); - if (position < fall_point) { - // Rise portion - amplitude = Proportion(position, fall_point, max_amplitude); - } else { - // Fall portion - amplitude = Proportion(ticks_at_rate - position, ticks_at_rate - fall_point, max_amplitude); - } - - return amplitude; - } - - int TicksAtRate() { - int effective_rate = constrain(rate + rate_mod, 0, HEM_LFO_MAX_VALUE); - int inv_rate = HEM_LFO_MAX_VALUE - effective_rate; - int range = HEM_LFO_HIGH - HEM_LFO_LOW; - int ticks_at_rate = Proportion(inv_rate, HEM_LFO_MAX_VALUE, range) + HEM_LFO_LOW; - return ticks_at_rate; - } - - int get_modification_with_input(int in) { - int mod = 0; - mod = Proportion(DetentedIn(in), HEMISPHERE_MAX_CV, HEM_LFO_MAX_VALUE / 2); - return mod; - } -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// Hemisphere Applet Functions -/// -/// Once you run the find-and-replace to make these refer to SkewedLFO, -/// it's usually not necessary to do anything with these functions. You -/// should prefer to handle things in the HemisphereApplet child class -/// above. -//////////////////////////////////////////////////////////////////////////////// -SkewedLFO SkewedLFO_instance[2]; - -void SkewedLFO_Start(bool hemisphere) { - SkewedLFO_instance[hemisphere].BaseStart(hemisphere); -} - -void SkewedLFO_Controller(bool hemisphere, bool forwarding) { - SkewedLFO_instance[hemisphere].BaseController(forwarding); -} - -void SkewedLFO_View(bool hemisphere) { - SkewedLFO_instance[hemisphere].BaseView(); -} - -void SkewedLFO_OnButtonPress(bool hemisphere) { - SkewedLFO_instance[hemisphere].OnButtonPress(); -} - -void SkewedLFO_OnEncoderMove(bool hemisphere, int direction) { - SkewedLFO_instance[hemisphere].OnEncoderMove(direction); -} - -void SkewedLFO_ToggleHelpScreen(bool hemisphere) { - SkewedLFO_instance[hemisphere].HelpScreen(); -} - -uint64_t SkewedLFO_OnDataRequest(bool hemisphere) { - return SkewedLFO_instance[hemisphere].OnDataRequest(); -} - -void SkewedLFO_OnDataReceive(bool hemisphere, uint64_t data) { - SkewedLFO_instance[hemisphere].OnDataReceive(data); -} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 1105e912d..34f146d20 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -33,7 +33,7 @@ DECLARE_APPLET( 57, 0x02, DrumMap), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ - DECLARE_APPLET( 63, 0x06, EbbAndLfo), \ + DECLARE_APPLET( 7, 0x01, EbbAndLfo), \ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 15, 0x02, EuclidX), \ @@ -76,5 +76,4 @@ } /* DECLARE_APPLET(127, 0x80, DIAGNOSTIC), \ - DECLARE_APPLET( 7, 0x01, SkewedLFO), \ */ From ca5776fc85c7ec179245d772bcc99b082d950943 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 19 Feb 2023 15:07:50 -0500 Subject: [PATCH 160/417] Reduce RAM usage by refactoring applet variables Most private variables in the base HemisphereApplet class pertain to I/O and do not need to be unique to each applet. --- software/o_c_REV/HEM_ClockSetup.ino | 3 +- software/o_c_REV/HemisphereApplet.h | 99 +++++++++++++++-------------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 1ad8b3a9a..ab86362e3 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -239,7 +239,8 @@ private: ClockSetup ClockSetup_instance[1]; void ClockSetup_Start(bool hemisphere) {ClockSetup_instance[hemisphere].BaseStart(hemisphere);} -void ClockSetup_Controller(bool hemisphere, bool forwarding) {ClockSetup_instance[hemisphere].BaseController(forwarding);} +// NJM: don't call BaseController for ClockSetup +void ClockSetup_Controller(bool hemisphere, bool forwarding) {ClockSetup_instance[hemisphere].Controller();} void ClockSetup_View(bool hemisphere) {ClockSetup_instance[hemisphere].BaseView();} void ClockSetup_OnButtonPress(bool hemisphere) {ClockSetup_instance[hemisphere].OnButtonPress();} void ClockSetup_OnEncoderMove(bool hemisphere, int direction) {ClockSetup_instance[hemisphere].OnEncoderMove(direction);} diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 58ee6d15f..c7fdcc5df 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -66,6 +66,8 @@ typedef int32_t simfloat; // Hemisphere-specific macros #define BottomAlign(h) (62 - h) #define ForEachChannel(ch) for(int_fast8_t ch = 0; ch < 2; ch++) +#define gfx_offset (hemisphere * 64) // Graphics offset, based on the side +#define io_offset (hemisphere * 2) // Input/Output offset, based on the side // Specifies where data goes in flash storage for each selcted applet, and how big it is typedef struct PackLocation { @@ -75,6 +77,17 @@ typedef struct PackLocation { class HemisphereApplet { public: + static int inputs[4]; + static int outputs[4]; + static int outputs_smooth[4]; + static int clock_countdown[4]; + static int adc_lag_countdown[4]; // Time between a clock event and an ADC read event + static uint32_t last_clock[4]; // Tick number of the last clock observed by the child class + static uint32_t cycle_ticks[4]; // Number of ticks between last two clocks + static bool changed_cv[4]; // Has the input changed by more than 1/8 semitone since the last read? + static int last_cv[4]; // For change detection + static int cursor_countdown[2]; + static uint8_t modal_edit_mode; static void CycleEditMode() { ++modal_edit_mode %= 3; @@ -87,20 +100,18 @@ class HemisphereApplet { void BaseStart(bool hemisphere_) { hemisphere = hemisphere_; - gfx_offset = hemisphere * 64; - io_offset = hemisphere * 2; // Initialize some things for startup ForEachChannel(ch) { - clock_countdown[ch] = 0; - inputs[ch] = 0; - outputs[ch] = 0; - outputs_smooth[ch] = 0; - adc_lag_countdown[ch] = 0; + clock_countdown[io_offset + ch] = 0; + inputs[io_offset + ch] = 0; + outputs[io_offset + ch] = 0; + outputs_smooth[io_offset + ch] = 0; + adc_lag_countdown[io_offset + ch] = 0; } help_active = 0; - cursor_countdown = HEMISPHERE_CURSOR_TICKS; + cursor_countdown[hemisphere] = HEMISPHERE_CURSOR_TICKS; // Shutdown FTM capture on Digital 4, used by Tuner #ifdef FLIP_180 @@ -126,20 +137,20 @@ class HemisphereApplet { { // Set CV inputs ADC_CHANNEL channel = (ADC_CHANNEL)(ch + io_offset); - inputs[ch] = OC::ADC::raw_pitch_value(channel); - if (abs(inputs[ch] - last_cv[ch]) > HEMISPHERE_CHANGE_THRESHOLD) { - changed_cv[ch] = 1; - last_cv[ch] = inputs[ch]; - } else changed_cv[ch] = 0; + inputs[channel] = OC::ADC::raw_pitch_value(channel); + if (abs(inputs[channel] - last_cv[channel]) > HEMISPHERE_CHANGE_THRESHOLD) { + changed_cv[channel] = 1; + last_cv[channel] = inputs[channel]; + } else changed_cv[channel] = 0; // Handle clock timing - if (clock_countdown[ch] > 0) { - if (--clock_countdown[ch] == 0) Out(ch, 0); + if (clock_countdown[channel] > 0) { + if (--clock_countdown[channel] == 0) Out(ch, 0); } } // Cursor countdowns. See CursorBlink(), ResetCursor(), gfxCursor() - if (--cursor_countdown < -HEMISPHERE_CURSOR_TICKS) cursor_countdown = HEMISPHERE_CURSOR_TICKS; + if (--cursor_countdown[hemisphere] < -HEMISPHERE_CURSOR_TICKS) cursor_countdown[hemisphere] = HEMISPHERE_CURSOR_TICKS; Controller(); } @@ -148,7 +159,6 @@ class HemisphereApplet { // If help is active, draw the help screen instead of the application screen if (help_active) DrawHelpScreen(); else View(); - last_view_tick = OC::CORE::ticks; } // Screensavers are deprecated in favor of screen blanking, but the BaseScreensaverView() remains @@ -162,11 +172,11 @@ class HemisphereApplet { /* Check cursor blink cycle. */ bool CursorBlink() { - return (cursor_countdown > 0); + return (cursor_countdown[hemisphere] > 0); } void ResetCursor() { - cursor_countdown = HEMISPHERE_CURSOR_TICKS; + cursor_countdown[hemisphere] = HEMISPHERE_CURSOR_TICKS; } void DrawHelpScreen() { @@ -334,7 +344,7 @@ class HemisphereApplet { //////////////// Offset I/O methods //////////////////////////////////////////////////////////////////////////////// int In(int ch) { - return inputs[ch]; + return inputs[io_offset + ch]; } // Apply small center detent to input, so it reads zero before a threshold @@ -351,14 +361,14 @@ class HemisphereApplet { void Out(int ch, int value, int octave = 0) { DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); OC::DAC::set_pitch(channel, value, octave); - outputs[ch] = value + (octave * (12 << 7)); + outputs[channel] = value + (octave * (12 << 7)); } void SmoothedOut(int ch, int value, int kSmoothing) { DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); value = (outputs_smooth[channel] * (kSmoothing - 1) + value) / kSmoothing; OC::DAC::set_pitch(channel, value, 0); - outputs[ch] = outputs_smooth[channel] = value; + outputs[channel] = outputs_smooth[channel] = value; } /* @@ -392,17 +402,17 @@ class HemisphereApplet { clocked = OC::DigitalInputs::clocked(); } - clocked = clocked || clock_m->Beep(hemisphere*2 + ch); + clocked = clocked || clock_m->Beep(io_offset + ch); if (clocked) { - cycle_ticks[ch] = OC::CORE::ticks - last_clock[ch]; - last_clock[ch] = OC::CORE::ticks; + cycle_ticks[io_offset + ch] = OC::CORE::ticks - last_clock[io_offset + ch]; + last_clock[io_offset + ch] = OC::CORE::ticks; } return clocked; } void ClockOut(int ch, int ticks = HEMISPHERE_CLOCK_TICKS) { - clock_countdown[ch] = ticks; + clock_countdown[io_offset + ch] = ticks; Out(ch, 0, PULSE_VOLTAGE); } @@ -424,10 +434,10 @@ class HemisphereApplet { } // Buffered I/O functions - int ViewIn(int ch) {return inputs[ch];} - int ViewOut(int ch) {return outputs[ch];} - int ClockCycleTicks(int ch) {return cycle_ticks[ch];} - bool Changed(int ch) {return changed_cv[ch];} + int ViewIn(int ch) {return inputs[io_offset + ch];} + int ViewOut(int ch) {return outputs[io_offset + ch];} + int ClockCycleTicks(int ch) {return cycle_ticks[io_offset + ch];} + bool Changed(int ch) {return changed_cv[io_offset + ch];} protected: bool hemisphere; // Which hemisphere (0, 1) this applet uses @@ -499,34 +509,31 @@ class HemisphereApplet { * } */ void StartADCLag(bool ch = 0) { - adc_lag_countdown[ch] = HEMISPHERE_ADC_LAG; + adc_lag_countdown[io_offset + ch] = HEMISPHERE_ADC_LAG; } bool EndOfADCLag(bool ch = 0) { - if (adc_lag_countdown[ch] < 0) return false; - return (--adc_lag_countdown[ch] == 0); + if (adc_lag_countdown[io_offset + ch] < 0) return false; + return (--adc_lag_countdown[io_offset + ch] == 0); } /* Master Clock Forwarding is activated. This is updated with each ISR cycle by the Hemisphere Manager */ bool MasterClockForwarded() {return master_clock_bus;} private: - int gfx_offset; // Graphics offset, based on the side - int io_offset; // Input/Output offset, based on the side - int inputs[2]; - int outputs[2]; - int outputs_smooth[2]; - uint32_t last_clock[2]; // Tick number of the last clock observed by the child class - uint32_t cycle_ticks[2]; // Number of ticks between last two clocks - int clock_countdown[2]; - int cursor_countdown; - int adc_lag_countdown[2]; // Time between a clock event and an ADC read event bool master_clock_bus; // Clock forwarding was on during the last ISR cycle bool applet_started; // Allow the app to maintain state during switching - int last_view_tick; // Tick number of the most recent view bool help_active; - bool changed_cv[2]; // Has the input changed by more than 1/8 semitone since the last read? - int last_cv[2]; // For change detection }; uint8_t HemisphereApplet::modal_edit_mode = 1; // 0=old behavior, 1=modal editing, 2=modal with wraparound +int HemisphereApplet::inputs[4]; +int HemisphereApplet::outputs[4]; +int HemisphereApplet::outputs_smooth[4]; +int HemisphereApplet::clock_countdown[4]; +int HemisphereApplet::adc_lag_countdown[4]; +uint32_t HemisphereApplet::last_clock[4]; +uint32_t HemisphereApplet::cycle_ticks[4]; +bool HemisphereApplet::changed_cv[4]; +int HemisphereApplet::last_cv[4]; +int HemisphereApplet::cursor_countdown[2]; From c24c3b02c68c7d20ec9e970c7d74f45f14287503 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 19 Feb 2023 16:59:12 -0500 Subject: [PATCH 161/417] Merge in VOR support, enabled via PIO build env still untested --- software/o_c_REV/HEM_Voltage.ino | 2 +- software/o_c_REV/OC_DAC.cpp | 24 +++ software/o_c_REV/OC_DAC.h | 9 ++ software/o_c_REV/OC_autotuner.h | 10 +- software/o_c_REV/OC_calibration.h | 5 + software/o_c_REV/OC_calibration.ino | 219 +++++++++++++++++++++------- software/o_c_REV/OC_gpio.h | 52 ++++++- software/o_c_REV/OC_options.h | 14 ++ software/o_c_REV/OC_ui.cpp | 35 ++++- software/o_c_REV/OC_ui.h | 18 ++- software/o_c_REV/OC_version.h | 7 +- software/o_c_REV/VBiasManager.h | 122 ++++++++++++++++ software/o_c_REV/o_c_REV.ino | 13 ++ software/o_c_REV/platformio.ini | 10 +- 14 files changed, 480 insertions(+), 60 deletions(-) create mode 100644 software/o_c_REV/VBiasManager.h diff --git a/software/o_c_REV/HEM_Voltage.ino b/software/o_c_REV/HEM_Voltage.ino index dcc91d0ef..69e4389fe 100644 --- a/software/o_c_REV/HEM_Voltage.ino +++ b/software/o_c_REV/HEM_Voltage.ino @@ -68,7 +68,7 @@ public: uint8_t ch = cursor / 2; if (cursor == 0 || cursor == 2) { // Change voltage - int min = -HEMISPHERE_3V_CV / VOLTAGE_INCREMENTS; + int min = -HEMISPHERE_MAX_CV / VOLTAGE_INCREMENTS; int max = HEMISPHERE_MAX_CV / VOLTAGE_INCREMENTS; voltage[ch] = constrain(voltage[ch] + direction, min, max); } else { diff --git a/software/o_c_REV/OC_DAC.cpp b/software/o_c_REV/OC_DAC.cpp index 7b899130c..78bf621f4 100644 --- a/software/o_c_REV/OC_DAC.cpp +++ b/software/o_c_REV/OC_DAC.cpp @@ -53,6 +53,14 @@ void DAC::Init(CalibrationData *calibration_data) { restore_scaling(0x0); // set up DAC pins +#ifdef VOR + OC::pinMode(DAC_CS, OUTPUT); + //OC::pinMode(DAC_RST,OUTPUT); + + // set Vbias, using onboard DAC: + init_Vbias(); + delay(10); +#else pinMode(DAC_CS, OUTPUT); pinMode(DAC_RST,OUTPUT); @@ -61,6 +69,7 @@ void DAC::Init(CalibrationData *calibration_data) { #else // default to DAC8565 - pull RST high digitalWrite(DAC_RST, HIGH); #endif +#endif history_tail_ = 0; memset(history_, 0, sizeof(uint16_t) * kHistoryDepth * DAC_CHANNEL_LAST); @@ -193,6 +202,21 @@ uint32_t DAC::store_scaling() { _scaling |= (DAC_scaling[i] << (i * 8)); return _scaling; } + +#ifdef VOR +/*static*/ +void DAC::init_Vbias() { + /* using MK20 DAC0 for Vbias*/ + VREF_TRM = 0x60; VREF_SC = 0xE1; // enable 1v2 reference + SIM_SCGC2 |= SIM_SCGC2_DAC0; // DAC clock + DAC0_C0 = DAC_C0_DACEN; // enable module + use internal 1v2 reference +} +/*static*/ +void DAC::set_Vbias(uint32_t data) { + *(volatile int16_t *)&(DAC0_DAT0L) = data; +} +#endif + /*static*/ DAC::CalibrationData *DAC::calibration_data_ = nullptr; /*static*/ diff --git a/software/o_c_REV/OC_DAC.h b/software/o_c_REV/OC_DAC.h index bb86471ee..4eee7dce3 100644 --- a/software/o_c_REV/OC_DAC.h +++ b/software/o_c_REV/OC_DAC.h @@ -41,6 +41,11 @@ class DAC { #ifdef BUCHLA_4U static constexpr int kOctaveZero = 0; + #elif defined(VOR) + static constexpr int kOctaveZero = 5; + static constexpr int VBiasUnipolar = 3900; // onboard DAC @ Vref 1.2V (internal), 1.75x gain + static constexpr int VBiasBipolar = 2000; // onboard DAC @ Vref 1.2V (internal), 1.75x gain + static constexpr int VBiasAsymmetric = 2760; // onboard DAC @ Vref 1.2V (internal), 1.75x gain #else static constexpr int kOctaveZero = 3; #endif @@ -62,6 +67,10 @@ class DAC { static void restore_scaling(uint32_t scaling); static uint8_t get_voltage_scaling(uint8_t channel_id); static uint32_t store_scaling(); +#ifdef VOR + static void set_Vbias(uint32_t data); + static void init_Vbias(); +#endif static void set_all(uint32_t value) { for (int i = DAC_CHANNEL_A; i < DAC_CHANNEL_LAST; ++i) diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index 543817a4f..2b8c526b4 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -4,10 +4,18 @@ #include "OC_autotune.h" #include "OC_options.h" -#ifdef BUCHLA_4U +#if defined(BUCHLA_4U) && !defined(IO_10V) const char* const AT_steps[] = { "0.0V", "1.2V", "2.4V", "3.6V", "4.8V", "6.0V", "7.2V", "8.4V", "9.6V", "10.8V", " " }; +#elif defined(IO_10V) +const char* const AT_steps[] = { + "0.0V", "1.0V", "2.0V", "3.0V", "4.0V", "5.0V", "6.0V", "7.0V", "8.0V", "9.0V", " " +}; +#elif defined(VOR) +const char* const AT_steps[] = { + "0.0V", "1.0V", "2.0V", "3.0V", "4.0V", "5.0V", "6.0V", "7.0V", "8.0V", "9.0V", "10.0V", " " +}; #else const char* const AT_steps[] = { "-3V", "-2V", "-1V", " 0V", "+1V", "+2V", "+3V", "+4V", "+5V", "+6V", " " diff --git a/software/o_c_REV/OC_calibration.h b/software/o_c_REV/OC_calibration.h index fa61a22ba..275de48ac 100644 --- a/software/o_c_REV/OC_calibration.h +++ b/software/o_c_REV/OC_calibration.h @@ -44,7 +44,12 @@ struct CalibrationData { uint32_t flags; uint8_t screensaver_timeout; // 0: default, else seconds uint8_t reserved0[3]; +#ifdef VOR + /* less complicated this way than adding it to DAC::CalibrationData... */ + uint32_t v_bias; +#else uint32_t reserved1; +#endif EncoderConfig encoder_config() const { return static_cast(flags & CALIBRATION_FLAG_ENCODER_MASK); diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index fd45a2aaf..93d095658 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -10,7 +10,7 @@ using OC::DAC; -#ifdef BUCHLA_cOC +#if defined(BUCHLA_cOC) || defined(VOR) static constexpr uint16_t DAC_OFFSET = 0; // DAC offset, initial approx., ish (Easel card) #else static constexpr uint16_t DAC_OFFSET = 4890; // DAC offset, initial approx., ish --> -3.5V to 6V @@ -33,11 +33,16 @@ bool calibration_data_loaded = false; const OC::CalibrationData kCalibrationDefaults = { // DAC { { - #ifdef BUCHLA_cOC + #ifdef BUCHLA_cOC {197, 6634, 13083, 19517, 25966, 32417, 38850, 45301, 51733, 58180, 64400}, {197, 6634, 13083, 19517, 25966, 32417, 38850, 45301, 51733, 58180, 64400}, {197, 6634, 13083, 19517, 25966, 32417, 38850, 45301, 51733, 58180, 64400}, {197, 6634, 13083, 19517, 25966, 32417, 38850, 45301, 51733, 58180, 64400} + #elif defined(VOR) + {880, 7190, 13510, 19830, 26300, 32460, 38770, 45090, 51410, 57720, 64040}, + {880, 7190, 13510, 19830, 26300, 32460, 38770, 45090, 51410, 57720, 64040}, + {880, 7190, 13510, 19830, 26300, 32460, 38770, 45090, 51410, 57720, 64040}, + {880, 7190, 13510, 19830, 26300, 32460, 38770, 45090, 51410, 57720, 64040} #else {0, 6553, 13107, 19661, 26214, 32768, 39321, 45875, 52428, 58981, 65535}, {0, 6553, 13107, 19661, 26214, 32768, 39321, 45875, 52428, 58981, 65535}, @@ -54,8 +59,13 @@ const OC::CalibrationData kCalibrationDefaults = { // display_offset SH1106_128x64_Driver::kDefaultOffset, OC_CALIBRATION_DEFAULT_FLAGS, - SCREENSAVER_TIMEOUT_S, { 0, 0, 0 }, - 0 // reserved + SCREENSAVER_TIMEOUT_S, + { 0, 0, 0 }, // reserved0 + #ifdef VOR + DAC::VBiasBipolar | (DAC::VBiasAsymmetric << 16) // default v_bias values + #else + 0 // reserved1 + #endif }; void calibration_reset() { @@ -107,12 +117,20 @@ void calibration_save() { enum CALIBRATION_STEP { HELLO, CENTER_DISPLAY, - + + #ifdef VOR + DAC_A_VOLT_3m, DAC_A_VOLT_2m, DAC_A_VOLT_1m, DAC_A_VOLT_0, DAC_A_VOLT_1, DAC_A_VOLT_2, DAC_A_VOLT_3, DAC_A_VOLT_4, DAC_A_VOLT_5, DAC_A_VOLT_6, DAC_A_VOLT_7, + DAC_B_VOLT_3m, DAC_B_VOLT_2m, DAC_B_VOLT_1m, DAC_B_VOLT_0, DAC_B_VOLT_1, DAC_B_VOLT_2, DAC_B_VOLT_3, DAC_B_VOLT_4, DAC_B_VOLT_5, DAC_B_VOLT_6, DAC_B_VOLT_7, + DAC_C_VOLT_3m, DAC_C_VOLT_2m, DAC_C_VOLT_1m, DAC_C_VOLT_0, DAC_C_VOLT_1, DAC_C_VOLT_2, DAC_C_VOLT_3, DAC_C_VOLT_4, DAC_C_VOLT_5, DAC_C_VOLT_6, DAC_C_VOLT_7, + DAC_D_VOLT_3m, DAC_D_VOLT_2m, DAC_D_VOLT_1m, DAC_D_VOLT_0, DAC_D_VOLT_1, DAC_D_VOLT_2, DAC_D_VOLT_3, DAC_D_VOLT_4, DAC_D_VOLT_5, DAC_D_VOLT_6, DAC_D_VOLT_7, + V_BIAS_BIPOLAR, V_BIAS_ASYMMETRIC, + #else DAC_A_VOLT_3m, DAC_A_VOLT_2m, DAC_A_VOLT_1m, DAC_A_VOLT_0, DAC_A_VOLT_1, DAC_A_VOLT_2, DAC_A_VOLT_3, DAC_A_VOLT_4, DAC_A_VOLT_5, DAC_A_VOLT_6, DAC_B_VOLT_3m, DAC_B_VOLT_2m, DAC_B_VOLT_1m, DAC_B_VOLT_0, DAC_B_VOLT_1, DAC_B_VOLT_2, DAC_B_VOLT_3, DAC_B_VOLT_4, DAC_B_VOLT_5, DAC_B_VOLT_6, DAC_C_VOLT_3m, DAC_C_VOLT_2m, DAC_C_VOLT_1m, DAC_C_VOLT_0, DAC_C_VOLT_1, DAC_C_VOLT_2, DAC_C_VOLT_3, DAC_C_VOLT_4, DAC_C_VOLT_5, DAC_C_VOLT_6, DAC_D_VOLT_3m, DAC_D_VOLT_2m, DAC_D_VOLT_1m, DAC_D_VOLT_0, DAC_D_VOLT_1, DAC_D_VOLT_2, DAC_D_VOLT_3, DAC_D_VOLT_4, DAC_D_VOLT_5, DAC_D_VOLT_6, - + #endif + CV_OFFSET_0, CV_OFFSET_1, CV_OFFSET_2, CV_OFFSET_3, ADC_PITCH_C2, ADC_PITCH_C4, CALIBRATION_SCREENSAVER_TIMEOUT, @@ -124,6 +142,10 @@ enum CALIBRATION_STEP { enum CALIBRATION_TYPE { CALIBRATE_NONE, CALIBRATE_OCTAVE, + #ifdef VOR + CALIBRATE_VBIAS_BIPOLAR, + CALIBRATE_VBIAS_ASYMMETRIC, + #endif CALIBRATE_ADC_OFFSET, CALIBRATE_ADC_1V, CALIBRATE_ADC_3V, @@ -149,7 +171,8 @@ DAC_CHANNEL step_to_channel(int step) { if (step >= DAC_D_VOLT_3m) return DAC_CHANNEL_D; if (step >= DAC_C_VOLT_3m) return DAC_CHANNEL_C; if (step >= DAC_B_VOLT_3m) return DAC_CHANNEL_B; - /*if (step >= DAC_B_VOLT_3m)*/ return DAC_CHANNEL_A; + /*if (step >= DAC_B_VOLT_3m)*/ + return DAC_CHANNEL_A; } struct CalibrationState { @@ -178,7 +201,7 @@ const CalibrationStep calibration_steps[CALIBRATION_STEP_LAST] = { { HELLO, "Setup: Calibrate", "Use defaults? ", select_help, start_footer, CALIBRATE_NONE, 0, OC::Strings::no_yes, 0, 1 }, { CENTER_DISPLAY, "Center Display", "Pixel offset ", default_help_r, default_footer, CALIBRATE_DISPLAY, 0, nullptr, 0, 2 }, - #ifdef BUCHLA_4U + #if defined(BUCHLA_4U) && !defined(IO_10V) { DAC_A_VOLT_3m, "DAC A 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, { DAC_A_VOLT_2m, "DAC A 1.2 volts", "-> 1.200V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, { DAC_A_VOLT_1m, "DAC A 2.4 volts", "-> 2.400V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, @@ -222,6 +245,98 @@ const CalibrationStep calibration_steps[CALIBRATION_STEP_LAST] = { { DAC_D_VOLT_4, "DAC D 8.4 volts", "-> 8.400V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, { DAC_D_VOLT_5, "DAC D 9.6 volts", "-> 9.600V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, { DAC_D_VOLT_6, "DAC D 10.8 volts", "-> 10.800V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + #elif defined(IO_10V) && !defined(VOR) + { DAC_A_VOLT_3m, "DAC A 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_2m, "DAC A 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_1m, "DAC A 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_0, "DAC A 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_1, "DAC A 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_2, "DAC A 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_3, "DAC A 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_4, "DAC A 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_5, "DAC A 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_6, "DAC A 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + + { DAC_B_VOLT_3m, "DAC B 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_2m, "DAC B 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_1m, "DAC B 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_0, "DAC B 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_1, "DAC B 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_2, "DAC B 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_3, "DAC B 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_4, "DAC B 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_5, "DAC B 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_6, "DAC B 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + + { DAC_C_VOLT_3m, "DAC C 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_2m, "DAC C 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_1m, "DAC C 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_0, "DAC C 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_1, "DAC C 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_2, "DAC C 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_3, "DAC C 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_4, "DAC C 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_5, "DAC C 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_6, "DAC C 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + + { DAC_D_VOLT_3m, "DAC D 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_2m, "DAC D 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_1m, "DAC D 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_0, "DAC D 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_1, "DAC D 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_2, "DAC D 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_3, "DAC D 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_4, "DAC D 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_5, "DAC D 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_6, "DAC D 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + #elif defined(VOR) + { DAC_A_VOLT_3m, "DAC A 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_2m, "DAC A 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_1m, "DAC A 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_0, "DAC A 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_1, "DAC A 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_2, "DAC A 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_3, "DAC A 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_4, "DAC A 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_5, "DAC A 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_6, "DAC A 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + { DAC_A_VOLT_7, "DAC A 10.0 volts", "-> 10.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 10, nullptr, 0, DAC::MAX_VALUE }, + + { DAC_B_VOLT_3m, "DAC B 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_2m, "DAC B 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_1m, "DAC B 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_0, "DAC B 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_1, "DAC B 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_2, "DAC B 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_3, "DAC B 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_4, "DAC B 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_5, "DAC B 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_6, "DAC B 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + { DAC_B_VOLT_7, "DAC B 10.0 volts", "-> 10.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 10, nullptr, 0, DAC::MAX_VALUE }, + + { DAC_C_VOLT_3m, "DAC C 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_2m, "DAC C 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_1m, "DAC C 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_0, "DAC C 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_1, "DAC C 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_2, "DAC C 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_3, "DAC C 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_4, "DAC C 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_5, "DAC C 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_6, "DAC C 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + { DAC_C_VOLT_7, "DAC C 10.0 volts", "-> 10.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 10, nullptr, 0, DAC::MAX_VALUE }, + + { DAC_D_VOLT_3m, "DAC D 0.0 volts", "-> 0.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 0, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_2m, "DAC D 1.0 volts", "-> 1.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 1, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_1m, "DAC D 2.0 volts", "-> 2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 2, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_0, "DAC D 3.0 volts", "-> 3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 3, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_1, "DAC D 4.0 volts", "-> 4.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 4, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_2, "DAC D 5.0 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_3, "DAC D 6.0 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_4, "DAC D 7.0 volts", "-> 7.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 7, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_5, "DAC D 8.0 volts", "-> 8.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 8, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_6, "DAC D 9.0 volts", "-> 9.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 9, nullptr, 0, DAC::MAX_VALUE }, + { DAC_D_VOLT_7, "DAC D 10.0 volts", "-> 10.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 10, nullptr, 0, DAC::MAX_VALUE }, #else { DAC_A_VOLT_3m, "DAC A -3 volts", "-> -3.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, -3, nullptr, 0, DAC::MAX_VALUE }, { DAC_A_VOLT_2m, "DAC A -2 volts", "-> -2.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, -2, nullptr, 0, DAC::MAX_VALUE }, @@ -267,13 +382,18 @@ const CalibrationStep calibration_steps[CALIBRATION_STEP_LAST] = { { DAC_D_VOLT_5, "DAC D 5 volts", "-> 5.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 5, nullptr, 0, DAC::MAX_VALUE }, { DAC_D_VOLT_6, "DAC D 6 volts", "-> 6.000V ", default_help_r, default_footer, CALIBRATE_OCTAVE, 6, nullptr, 0, DAC::MAX_VALUE }, #endif + + #ifdef VOR + { V_BIAS_BIPOLAR, "0.000V: bipolar", "--> 0.000V", default_help_r, default_footer, CALIBRATE_VBIAS_BIPOLAR, 0, nullptr, 0, 4095 }, + { V_BIAS_ASYMMETRIC, "0.000V: asym.", "--> 0.000V", default_help_r, default_footer, CALIBRATE_VBIAS_ASYMMETRIC, 0, nullptr, 0, 4095 }, + #endif { CV_OFFSET_0, "ADC CV1", "ADC value at 0V", default_help_r, default_footer, CALIBRATE_ADC_OFFSET, ADC_CHANNEL_1, nullptr, 0, 4095 }, { CV_OFFSET_1, "ADC CV2", "ADC value at 0V", default_help_r, default_footer, CALIBRATE_ADC_OFFSET, ADC_CHANNEL_2, nullptr, 0, 4095 }, { CV_OFFSET_2, "ADC CV3", "ADC value at 0V", default_help_r, default_footer, CALIBRATE_ADC_OFFSET, ADC_CHANNEL_3, nullptr, 0, 4095 }, { CV_OFFSET_3, "ADC CV4", "ADC value at 0V", default_help_r, default_footer, CALIBRATE_ADC_OFFSET, ADC_CHANNEL_4, nullptr, 0, 4095 }, - #ifdef BUCHLA_4U + #if defined(BUCHLA_4U) && !defined(IO_10V) { ADC_PITCH_C2, "ADC cal. octave #1", "CV1: Input 1.2V", "[R] Long press to set", default_footer, CALIBRATE_ADC_1V, 0, nullptr, 0, 0 }, { ADC_PITCH_C4, "ADC cal. octave #3", "CV1: Input 3.6V", "[R] Long press to set", default_footer, CALIBRATE_ADC_3V, 0, nullptr, 0, 0 }, #else @@ -399,6 +519,16 @@ void OC::Ui::Calibrate() { calibration_state.encoder_value = OC::calibration_data.dac.calibrated_octaves[step_to_channel(next_step->step)][next_step->index + DAC::kOctaveZero]; break; + + #ifdef VOR + case CALIBRATE_VBIAS_BIPOLAR: + calibration_state.encoder_value = (0xFFFF & OC::calibration_data.v_bias); // bipolar = lower 2 bytes + break; + case CALIBRATE_VBIAS_ASYMMETRIC: + calibration_state.encoder_value = (OC::calibration_data.v_bias >> 16); // asymmetric = upper 2 bytes + break; + #endif + case CALIBRATE_ADC_OFFSET: calibration_state.encoder_value = OC::calibration_data.adc.offset[next_step->index]; break; @@ -449,9 +579,12 @@ void calibration_draw(const CalibrationState &state) { GRAPHICS_BEGIN_FRAME(true); const CalibrationStep *step = state.current_step; + /* graphics.drawLine(0, 10, 127, 10); graphics.drawLine(0, 12, 127, 12); graphics.setPrintPos(1, 2); + */ + menu::DefaultTitleBar::Draw(); graphics.print(step->title); weegfx::coord_t y = menu::CalcLineY(0); @@ -462,6 +595,10 @@ void calibration_draw(const CalibrationState &state) { switch (step->calibration_type) { case CALIBRATE_OCTAVE: case CALIBRATE_SCREENSAVER: + #ifdef VOR + case CALIBRATE_VBIAS_BIPOLAR: + case CALIBRATE_VBIAS_ASYMMETRIC: + #endif graphics.print(step->message); graphics.setPrintPos(kValueX, y + 2); graphics.print((int)state.encoder_value, 5); @@ -539,7 +676,7 @@ void calibration_draw(const CalibrationState &state) { x += 5; } - graphics.drawStr(0, menu::kDisplayHeight - menu::kFontHeight - 1, step->footer); + graphics.drawStr(1, menu::kDisplayHeight - menu::kFontHeight - 3, step->footer); static constexpr uint16_t step_width = (menu::kDisplayWidth << 8 ) / (CALIBRATION_STEP_LAST - 1); graphics.drawRect(0, menu::kDisplayHeight - 2, (state.step * step_width) >> 8, 2); @@ -562,10 +699,32 @@ void calibration_update(CalibrationState &state) { OC::calibration_data.dac.calibrated_octaves[step_to_channel(step->step)][step->index + DAC::kOctaveZero] = state.encoder_value; DAC::set_all_octave(step->index); + #ifdef VOR + /* set 0V @ unipolar range */ + DAC::set_Vbias(DAC::VBiasUnipolar); + #endif break; + #ifdef VOR + case CALIBRATE_VBIAS_BIPOLAR: + /* set 0V @ bipolar range */ + DAC::set_all_octave(5); + OC::calibration_data.v_bias = (OC::calibration_data.v_bias & 0xFFFF0000) | state.encoder_value; + DAC::set_Vbias(0xFFFF & OC::calibration_data.v_bias); + break; + case CALIBRATE_VBIAS_ASYMMETRIC: + /* set 0V @ asym. range */ + DAC::set_all_octave(3); + OC::calibration_data.v_bias = (OC::calibration_data.v_bias & 0xFFFF) | (state.encoder_value << 16); + DAC::set_Vbias(OC::calibration_data.v_bias >> 16); + break; + #endif case CALIBRATE_ADC_OFFSET: OC::calibration_data.adc.offset[step->index] = state.encoder_value; DAC::set_all_octave(0); + #ifdef VOR + /* set 0V @ unipolar range */ + DAC::set_Vbias(DAC::VBiasUnipolar); + #endif break; case CALIBRATE_ADC_1V: DAC::set_all_octave(1); @@ -594,42 +753,4 @@ uint32_t adc_average() { OC::ADC::smoothed_raw_value(ADC_CHANNEL_3) + OC::ADC::smoothed_raw_value(ADC_CHANNEL_4); } -#ifdef CALIBRATION_LOAD_LEGACY -/* read settings from original O&C */ -void calibration_read_old() { - - delay(1000); - uint8_t byte0, byte1, adr; - - adr = 0; - SERIAL_PRINTLN("Loading original O&C calibration from eeprom:"); - - for (int i = 0; i < OCTAVES; i++) { - - byte0 = EEPROM.read(adr); - adr++; - byte1 = EEPROM.read(adr); - adr++; - OC::calibration_data.dac.octaves[i] = (uint16_t)(byte0 << 8) + byte1; - SERIAL_PRINTLN(" OCTAVE %2d: %u", i, OC::calibration_data.dac.octaves[i]); - } - - uint16_t _offset[ADC_CHANNEL_LAST]; - - for (int i = 0; i < ADC_CHANNEL_LAST; i++) { - - byte0 = EEPROM.read(adr); - adr++; - byte1 = EEPROM.read(adr); - adr++; - _offset[i] = (uint16_t)(byte0 << 8) + byte1; - SERIAL_PRINTLN("ADC %d: %u", i, _offset[i]); - } - - OC::calibration_data.adc.offset[ADC_CHANNEL_1] = _offset[0]; - OC::calibration_data.adc.offset[ADC_CHANNEL_2] = _offset[1]; - OC::calibration_data.adc.offset[ADC_CHANNEL_3] = _offset[2]; - OC::calibration_data.adc.offset[ADC_CHANNEL_4] = _offset[3]; - SERIAL_PRINTLN("......"); -} -#endif +// end diff --git a/software/o_c_REV/OC_gpio.h b/software/o_c_REV/OC_gpio.h index 21cd5fc9c..50417b182 100644 --- a/software/o_c_REV/OC_gpio.h +++ b/software/o_c_REV/OC_gpio.h @@ -39,9 +39,14 @@ #define OLED_CS_ACTIVE LOW #define OLED_CS_INACTIVE HIGH -#define DAC_RST 9 #define DAC_CS 10 +#ifdef VOR + #define but_mid 9 +#else + #define DAC_RST 9 +#endif + // NOTE: encoder pins R1/R2 changed for rev >= 2c #ifdef FLIP_180 #define encL1 16 @@ -69,4 +74,49 @@ #define OC_GPIO_TRx_PINMODE INPUT_PULLUP #define OC_GPIO_ENC_PINMODE INPUT_PULLUP +#ifdef VOR +/* local copy of pinMode (cf. cores/pins_teensy.c), using faster slew rate */ + +namespace OC { + +void inline pinMode(uint8_t pin, uint8_t mode) { + + volatile uint32_t *config; + + if (pin >= CORE_NUM_DIGITAL) return; + config = portConfigRegister(pin); + + if (mode == OUTPUT || mode == OUTPUT_OPENDRAIN) { + #ifdef KINETISK + *portModeRegister(pin) = 1; + #else + *portModeRegister(pin) |= digitalPinToBitMask(pin); // TODO: atomic + #endif + /* use fast slew rate for output */ + *config = PORT_PCR_DSE | PORT_PCR_MUX(1); + if (mode == OUTPUT_OPENDRAIN) { + *config |= PORT_PCR_ODE; + } else { + *config &= ~PORT_PCR_ODE; + } + } else { + #ifdef KINETISK + *portModeRegister(pin) = 0; + #else + *portModeRegister(pin) &= ~digitalPinToBitMask(pin); + #endif + if (mode == INPUT) { + *config = PORT_PCR_MUX(1); + } else if (mode == INPUT_PULLUP) { + *config = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; + } else if (mode == INPUT_PULLDOWN) { + *config = PORT_PCR_MUX(1) | PORT_PCR_PE; + } else { // INPUT_DISABLE + *config = 0; + } + } + } +} +#endif // VOR + #endif // OC_GPIO_H_ diff --git a/software/o_c_REV/OC_options.h b/software/o_c_REV/OC_options.h index 61c46b725..ce7760874 100644 --- a/software/o_c_REV/OC_options.h +++ b/software/o_c_REV/OC_options.h @@ -24,6 +24,20 @@ /* ------------ use DAC8564 ------------------------------------------------------------------------- */ //#define DAC8564 + +// Phazerville Suite includes basic support for the 10V OC Plus and VOR +// +// The VOR flag is set in platformio.ini. The other one is here if you need it. -NJM +/* ------------ uncomment for use with Plum Audio VOR anabled versions (OCP, 1uO_c v2, 4Robots) --------------------------------------------------------- */ +//#define VOR +/* ------------ uncomment for use with Plum Audio 1uO_c 4Robots (To use Up button long press to change VOR instead activate screensaver) ---------------- */ +//#define VOR_NO_RANGE_BUTTON + +// idk what this means so I'm keeping it -NJM +#if defined(VOR) + #define IO_10V +#endif + /* Flags for the full-width apps, these enable/disable them in OC_apps.ino but also zero out the app */ /* files to prevent them from taking up space. Only Enigma is enabled by default. */ #define ENABLE_APP_ENIGMA diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index 5f8abe36e..58d91e558 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -13,6 +13,11 @@ #include "OC_options.h" #include "src/drivers/display.h" +#ifdef VOR +#include "VBiasManager.h" +VBiasManager *VBiasManager::instance = 0; +#endif + extern uint_fast8_t MENU_REDRAW; namespace OC { @@ -23,7 +28,12 @@ void Ui::Init() { ticks_ = 0; set_screensaver_timeout(SCREENSAVER_TIMEOUT_S); +#if defined(VOR) && !defined(VOR_NO_RANGE_BUTTON) + static const int button_pins[] = { but_top, but_bot, butL, butR, but_mid }; +#else static const int button_pins[] = { but_top, but_bot, butL, butR }; +#endif + for (size_t i = 0; i < CONTROL_BUTTON_LAST; ++i) { buttons_[i].Init(button_pins[i], OC_GPIO_BUTTON_PINMODE); } @@ -110,12 +120,31 @@ UiMode Ui::DispatchEvents(App *app) { switch (event.type) { case UI::EVENT_BUTTON_PRESS: + #ifdef VOR + #ifdef VOR_NO_RANGE_BUTTON + if (OC::CONTROL_BUTTON_UP == event.control) { + VBiasManager *vbias_m = vbias_m->get(); + if (vbias_m->IsEditing()) vbias_m->AdvanceBias(); + else app->HandleButtonEvent(event); + } else app->HandleButtonEvent(event); + #else + if (OC::CONTROL_BUTTON_M == event.control) { + VBiasManager *vbias_m = vbias_m->get(); + vbias_m->AdvanceBias(); + } else app->HandleButtonEvent(event); + #endif + #else app->HandleButtonEvent(event); + #endif break; case UI::EVENT_BUTTON_LONG_PRESS: if (OC::CONTROL_BUTTON_UP == event.control) { - if (!preempt_screensaver_) - screensaver_ = true; + #ifdef VOR_NO_RANGE_BUTTON + VBiasManager *vbias_m = vbias_m->get(); + vbias_m->AdvanceBias(); + #else + if (!preempt_screensaver_) screensaver_ = true; + #endif } else if (OC::CONTROL_BUTTON_R == event.control) return UI_MODE_APP_SETTINGS; @@ -156,7 +185,7 @@ UiMode Ui::Splashscreen(bool &reset_settings) { mode = UI_MODE_APP_SETTINGS; reset_settings = - #ifdef BUCHLA_4U + #if defined(BUCHLA_4U) && !defined(IO_10V) read_immediate(CONTROL_BUTTON_UP) && read_immediate(CONTROL_BUTTON_R); #else read_immediate(CONTROL_BUTTON_UP) && read_immediate(CONTROL_BUTTON_DOWN); diff --git a/software/o_c_REV/OC_ui.h b/software/o_c_REV/OC_ui.h index f9b92a80d..f0a288f47 100644 --- a/software/o_c_REV/OC_ui.h +++ b/software/o_c_REV/OC_ui.h @@ -2,6 +2,7 @@ #define OC_UI_H_ #include "OC_config.h" +#include "OC_options.h" #include "OC_debug.h" #include "UI/ui_button.h" #include "UI/ui_encoder.h" @@ -27,13 +28,24 @@ enum UiControl { CONTROL_BUTTON_L = 0x4, CONTROL_BUTTON_R = 0x8, #endif - CONTROL_BUTTON_MASK = 0xf, +#ifdef VOR + CONTROL_BUTTON_M = 0x10, + CONTROL_ENCODER_L = 0x20, + CONTROL_ENCODER_R = 0x40, +#else + CONTROL_BUTTON_MASK = 0xf, CONTROL_ENCODER_L = 0x10, CONTROL_ENCODER_R = 0x20, +#endif + #if defined(VOR) && !defined(VOR_NO_RANGE_BUTTON) + CONTROL_LAST = 6, + CONTROL_BUTTON_LAST = 5, + #else CONTROL_LAST = 5, CONTROL_BUTTON_LAST = 4, + #endif }; static inline uint16_t control_mask(unsigned i) { @@ -117,8 +129,8 @@ class Ui { uint32_t ticks_; uint32_t screensaver_timeout_; - UI::Button buttons_[4]; - uint32_t button_press_time_[4]; + UI::Button buttons_[CONTROL_BUTTON_LAST]; + uint32_t button_press_time_[CONTROL_BUTTON_LAST]; uint16_t button_state_; uint16_t button_ignore_mask_; bool screensaver_; diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index a3d24587e..c8680b639 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,6 +1,11 @@ #ifndef OC_VERSION_H_ #define OC_VERSION_H_ + +#ifndef OC_VERSION_EXTRA +#define OC_VERSION_EXTRA " main" +#endif + #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.9" +#define OC_VERSION "v1.4.9" OC_VERSION_EXTRA #define OC_VERSION_URL "github.com/djphazer" #endif diff --git a/software/o_c_REV/VBiasManager.h b/software/o_c_REV/VBiasManager.h new file mode 100644 index 000000000..04d4c04e8 --- /dev/null +++ b/software/o_c_REV/VBiasManager.h @@ -0,0 +1,122 @@ +// Copyright (c) 2019, Jason Justian +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// This singleton class has a few jobs related to keeping track of the bias value +// selected by the user and/or the app: +// +// (1) It allows advancing bias through three settings, one at a time +// (2) It allows setting the bias directly with a state +// (3) It shows a popup indicator for one second when the setting is advanced + +#ifndef VBIAS_MANAGER_H +#define VBIAS_MANAGER_H + +#include "OC_options.h" + +#ifdef VOR + +#define BIAS_EDITOR_TIMEOUT 20000 + +class VBiasManager { + static VBiasManager *instance; + int bias_state; + uint32_t last_advance_tick; + + VBiasManager() { + bias_state = 0; + last_advance_tick = 0; + } + +public: + static const int BI = 0; + static const int ASYM = 1; + static const int UNI = 2; + + static VBiasManager *get() { + if (!instance) instance = new VBiasManager; + return instance; + } + + /* + * Advance to the next state, when the button is pushed + */ + void AdvanceBias() { + // Only advance the bias if it's been less than a second since the last button press. + // This is so that the first button press shows the popup without changing anything. + if (OC::CORE::ticks - last_advance_tick < BIAS_EDITOR_TIMEOUT) { + if (++bias_state > 2) bias_state = 0; + instance->ChangeBiasToState(bias_state); + } + last_advance_tick = OC::CORE::ticks; + } + + int IsEditing() { + return (OC::CORE::ticks - last_advance_tick < BIAS_EDITOR_TIMEOUT); + } + + /* + * Change to a specific state. This should replace a direct call to OC::DAC::set_Vbias(), because it + * allows VBiasManager to keep track of the current state so that the button advances the state as + * expected. For example: + * + * #ifdef VOR + * VBiasManager *vbias_m = vbias_m->get(); + * vbias_m->ChangeBiasToState(VBiasManager::BI); + * #endif + * + */ + void ChangeBiasToState(int new_bias_state) { + int new_bias_value = OC::calibration_data.v_bias & 0xFFFF; // Bipolar = lower 2 bytes + if (new_bias_state == VBiasManager::UNI) new_bias_value = OC::DAC::VBiasUnipolar; + if (new_bias_state == VBiasManager::ASYM) new_bias_value = (OC::calibration_data.v_bias >> 16); // asym. = upper 2 bytes + OC::DAC::set_Vbias(new_bias_value); + bias_state = new_bias_state; + } + + /* + * If the last state advance (with the button) was less than a second ago, draw the popup indicator + */ + void DrawPopupPerhaps() { + if (OC::CORE::ticks - last_advance_tick < BIAS_EDITOR_TIMEOUT) { + graphics.clearRect(17, 7, 82, 43); + graphics.drawFrame(18, 8, 80, 42); + + graphics.setPrintPos(20, 10); + graphics.print("Range:"); + + // Bipolar state + graphics.setPrintPos(30, 20); + graphics.print("-5V -> 5V"); + // Asym State + graphics.setPrintPos(30, 30); + graphics.print("-3V -> 7V"); + // Unipolar state + graphics.setPrintPos(30, 40); + graphics.print(" 0V -> 10V"); + + graphics.setPrintPos(20, 20 + (bias_state * 10)); + graphics.print("> "); + } + } +}; + +#endif + +#endif // VBIAS_MANAGER_H diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 08ee4edb0..c6633eae5 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -42,6 +42,7 @@ #include "src/drivers/display.h" #include "src/drivers/ADC/OC_util_ADC.h" #include "util/util_debugpins.h" +#include "VBiasManager.h" unsigned long LAST_REDRAW_TIME = 0; uint_fast8_t MENU_REDRAW = true; @@ -149,6 +150,11 @@ void setup() { // initialize apps OC::apps::Init(reset_settings); + +#ifdef VOR + VBiasManager *vbias_m = vbias_m->get(); + vbias_m->ChangeBiasToState(VBiasManager::BI); +#endif } /* --------- main loop -------- */ @@ -173,6 +179,13 @@ void FASTRUN loop() { OC_DEBUG_PROFILE_SCOPE(OC::DEBUG::MENU_draw_cycles); OC::apps::current_app->DrawMenu(); ++menu_redraws; + + #ifdef VOR + // JEJ:On app screens, show the bias popup, if necessary + VBiasManager *vbias_m = vbias_m->get(); + vbias_m->DrawPopupPerhaps(); + #endif + } else { //Blank the screen instead of drawing the screensaver (chysn 9/2/2018) //OC::apps::current_app->DrawScreensaver(); diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 860bc2e3c..ea7e9b3ff 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -8,7 +8,9 @@ [platformio] src_dir = . -default_envs = oc_prod +default_envs = + oc_prod + oc_prod_vor [env] platform = teensy@4.17.0 @@ -31,6 +33,12 @@ build_flags = ${env.build_flags} [env:oc_prod_flipped] build_flags = ${env.build_flags} -DFLIP_180 +[env:oc_prod_vor] +build_flags = + ${env:oc_prod.build_flags} + -DVOR + -DOC_VERSION_EXTRA="\"+VOR\"" + [env:oc_dev] build_flags = ${env.build_flags} -DOC_DEV ; -DPRINT_DEBUG From 9e09f230a67b4df2d250c1192eec15d879ad29da Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 19 Feb 2023 10:05:24 -0500 Subject: [PATCH 162/417] Add stock O&C apps Main build includes: Quantermain, Meta-Q, Acid Curds, Harrington 1200 + Alternate build also includes: Sequins, Quadraturia* - but disables: Captain MIDI, Neural Net, and Enigma Screensavers re-enabled; timeout is still in minutes, 25 by default *Known BUG: Frequency display is broken in Quadraturia (halp!) --- software/o_c_REV/APP_CHORDS.ino | 1400 +++++++++++++ software/o_c_REV/APP_DQ.ino | 1576 ++++++++++++++ software/o_c_REV/APP_H1200.ino | 1217 +++++++++++ software/o_c_REV/APP_POLYLFO.ino | 491 +++++ software/o_c_REV/APP_QQ.ino | 1584 ++++++++++++++ software/o_c_REV/APP_SEQ.ino | 2635 +++++++++++++++++++++++ software/o_c_REV/OC_apps.h | 1 + software/o_c_REV/OC_apps.ino | 21 + software/o_c_REV/OC_chords.cpp | 29 + software/o_c_REV/OC_chords.h | 105 + software/o_c_REV/OC_chords_edit.h | 450 ++++ software/o_c_REV/OC_chords_presets.h | 84 + software/o_c_REV/OC_menus.h | 4 +- software/o_c_REV/OC_options.h | 21 +- software/o_c_REV/OC_scale_edit.h | 656 ++++++ software/o_c_REV/OC_sequence_edit.h | 422 ++++ software/o_c_REV/OC_version.h | 4 +- software/o_c_REV/frames_poly_lfo.cpp | 257 +++ software/o_c_REV/frames_poly_lfo.h | 310 +++ software/o_c_REV/frames_resources.cpp | 2770 +++++++++++++++++++++++++ software/o_c_REV/frames_resources.h | 96 + software/o_c_REV/o_c_REV.ino | 4 +- software/o_c_REV/platformio.ini | 40 +- software/o_c_REV/tonnetz/tonnetz.h | 2 + 24 files changed, 14161 insertions(+), 18 deletions(-) create mode 100644 software/o_c_REV/APP_CHORDS.ino create mode 100644 software/o_c_REV/APP_DQ.ino create mode 100644 software/o_c_REV/APP_H1200.ino create mode 100644 software/o_c_REV/APP_POLYLFO.ino create mode 100644 software/o_c_REV/APP_QQ.ino create mode 100644 software/o_c_REV/APP_SEQ.ino create mode 100644 software/o_c_REV/OC_chords.cpp create mode 100644 software/o_c_REV/OC_chords.h create mode 100644 software/o_c_REV/OC_chords_edit.h create mode 100644 software/o_c_REV/OC_chords_presets.h create mode 100644 software/o_c_REV/OC_scale_edit.h create mode 100644 software/o_c_REV/OC_sequence_edit.h create mode 100644 software/o_c_REV/frames_poly_lfo.cpp create mode 100644 software/o_c_REV/frames_poly_lfo.h create mode 100644 software/o_c_REV/frames_resources.cpp create mode 100644 software/o_c_REV/frames_resources.h diff --git a/software/o_c_REV/APP_CHORDS.ino b/software/o_c_REV/APP_CHORDS.ino new file mode 100644 index 000000000..e8b41986f --- /dev/null +++ b/software/o_c_REV/APP_CHORDS.ino @@ -0,0 +1,1400 @@ +// Copyright (c) 2015, 2016, 2017 Patrick Dowling, Tim Churches, Max Stadler +// +// Initial app implementation: Patrick Dowling (pld@gurkenkiste.com) +// Modifications by: Tim Churches (tim.churches@gmail.com) +// Yet more Modifications by: mxmxmx +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Quad quantizer app, based around the the quantizer/scales implementation from +// from Braids by Olivier Gillet (see braids_quantizer.h/cc et al.). It has since +// grown a little bit... + +#ifdef ENABLE_APP_CHORDS + +#include "OC_apps.h" +#include "util/util_settings.h" +#include "util/util_trigger_delay.h" +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_menus.h" +#include "OC_scales.h" +#include "OC_scale_edit.h" +#include "OC_strings.h" +#include "OC_chords.h" +#include "OC_chords_edit.h" +#include "OC_input_map.h" +#include "OC_input_maps.h" + +enum CHORDS_SETTINGS { + CHORDS_SETTING_SCALE, + CHORDS_SETTING_ROOT, + CHORDS_SETTING_PROGRESSION, + CHORDS_SETTING_MASK, + CHORDS_SETTING_CV_SOURCE, + CHORDS_SETTING_CHORDS_ADVANCE_TRIGGER_SOURCE, + CHORDS_SETTING_PLAYMODES, + CHORDS_SETTING_DIRECTION, + CHORDS_SETTING_BROWNIAN_PROBABILITY, + CHORDS_SETTING_TRIGGER_DELAY, + CHORDS_SETTING_TRANSPOSE, + CHORDS_SETTING_OCTAVE, + CHORDS_SETTING_CHORD_SLOT, + CHORDS_SETTING_NUM_CHORDS_0, + CHORDS_SETTING_NUM_CHORDS_1, + CHORDS_SETTING_NUM_CHORDS_2, + CHORDS_SETTING_NUM_CHORDS_3, + CHORDS_SETTING_CHORD_EDIT, + // CV sources + CHORDS_SETTING_ROOT_CV, + CHORDS_SETTING_MASK_CV, + CHORDS_SETTING_TRANSPOSE_CV, + CHORDS_SETTING_OCTAVE_CV, + CHORDS_SETTING_QUALITY_CV, + CHORDS_SETTING_VOICING_CV, + CHORDS_SETTING_INVERSION_CV, + CHORDS_SETTING_PROGRESSION_CV, + CHORDS_SETTING_DIRECTION_CV, + CHORDS_SETTING_BROWNIAN_CV, + CHORDS_SETTING_NUM_CHORDS_CV, + CHORDS_SETTING_DUMMY, + CHORDS_SETTING_MORE_DUMMY, + CHORDS_SETTING_LAST +}; + +enum CHORDS_CV_SOURCES { + CHORDS_CV_SOURCE_CV1, + CHORDS_CV_SOURCE_CV2, + CHORDS_CV_SOURCE_CV3, + CHORDS_CV_SOURCE_CV4, + CHORDS_CV_SOURCE_LAST +}; + +enum CHORDS_ADVANCE_TRIGGER_SOURCE { + CHORDS_ADVANCE_TRIGGER_SOURCE_TR1, + CHORDS_ADVANCE_TRIGGER_SOURCE_TR2, + CHORDS_ADVANCE_TRIGGER_SOURCE_LAST +}; + +enum CHORDS_CV_DESTINATIONS { + CHORDS_CV_DEST_NONE, + CHORDS_CV_DEST_ROOT, + CHORDS_CV_DEST_OCTAVE, + CHORDS_CV_DEST_TRANSPOSE, + CHORDS_CV_DEST_MASK, + CHORDS_CV_DEST_LAST +}; + +// enum MENU_PAGES { +// PARAMETERS, +// CV_MAPPING +// }; + +enum CHORDS_MENU_PAGES { + MENU_PARAMETERS, + MENU_CV_MAPPING, + MENU_PAGES_LAST +}; + +enum CHORDS_PLAYMODES { + _NONE, + _SEQ1, + _SEQ2, + _SEQ3, + _TR1, + _TR2, + _TR3, + _SH1, + _SH2, + _SH3, + _SH4, + _CV1, + _CV2, + _CV3, + _CV4, + CHORDS_PLAYMODES_LAST +}; + +enum CHORDS_DIRECTIONS { + CHORDS_FORWARD, + CHORDS_REVERSE, + CHORDS_PENDULUM1, + CHORDS_PENDULUM2, + CHORDS_RANDOM, + CHORDS_BROWNIAN, + CHORDS_DIRECTIONS_LAST +}; + +namespace menu = OC::menu; + +extern uint_fast8_t MENU_REDRAW; + +class Chords : public settings::SettingsBase { +public: + + int get_scale(uint8_t selected_scale_slot_) const { + return values_[CHORDS_SETTING_SCALE]; + } + + void set_scale(int scale) { + + if (scale != get_scale(DUMMY)) { + const OC::Scale &scale_def = OC::Scales::GetScale(scale); + uint16_t mask = get_mask(); + if (0 == (mask & ~(0xffff << scale_def.num_notes))) + mask |= 0x1; + apply_value(CHORDS_SETTING_MASK, mask); + apply_value(CHORDS_SETTING_SCALE, scale); + } + } + + // dummy + int get_scale_select() const { + return 0; + } + + // dummy + void set_scale_at_slot(int scale, uint16_t mask, int root, int transpose, uint8_t scale_slot) { + + } + + // dummy + int get_transpose(uint8_t DUMMY) const { + return 0; + } + + bool octave_toggle() { + _octave_toggle = (~_octave_toggle) & 1u; + return _octave_toggle; + } + + bool poke_octave_toggle() const { + return _octave_toggle; + } + + int get_progression() const { + return values_[CHORDS_SETTING_PROGRESSION]; + } + + int get_progression_cv() const { + return values_[CHORDS_SETTING_PROGRESSION_CV]; + } + + int get_active_progression() const { + return active_progression_; + } + + int get_chord_slot() const { + return values_[CHORDS_SETTING_CHORD_SLOT]; + } + + void set_chord_slot(int8_t slot) { + apply_value(CHORDS_SETTING_CHORD_SLOT, slot); + } + + int get_num_chords(uint8_t progression) const { + + int len = 0x0; + switch (progression) { + + case 0: + len = values_[CHORDS_SETTING_NUM_CHORDS_0]; + break; + case 1: + len = values_[CHORDS_SETTING_NUM_CHORDS_1]; + break; + case 2: + len = values_[CHORDS_SETTING_NUM_CHORDS_2]; + break; + case 3: + len = values_[CHORDS_SETTING_NUM_CHORDS_3]; + break; + default: + break; + } + return len; + } + + void set_num_chords(int8_t num_chords, uint8_t progression) { + + // set progression length: + switch (progression) { + case 0: + apply_value(CHORDS_SETTING_NUM_CHORDS_0, num_chords); + break; + case 1: + apply_value(CHORDS_SETTING_NUM_CHORDS_1, num_chords); + break; + case 2: + apply_value(CHORDS_SETTING_NUM_CHORDS_2, num_chords); + break; + case 3: + apply_value(CHORDS_SETTING_NUM_CHORDS_3, num_chords); + break; + default: + break; + } + } + + int get_num_chords_cv() const { + return values_[CHORDS_SETTING_NUM_CHORDS_CV]; + } + + int active_chord() const { + return active_chord_; + } + + int get_playmode() const { + return values_[CHORDS_SETTING_PLAYMODES]; + } + + int get_direction() const { + return values_[CHORDS_SETTING_DIRECTION]; + } + + uint8_t get_direction_cv() const { + return values_[CHORDS_SETTING_DIRECTION_CV]; + } + + uint8_t get_brownian_probability() const { + return values_[CHORDS_SETTING_BROWNIAN_PROBABILITY]; + } + + int8_t get_brownian_probability_cv() const { + return values_[CHORDS_SETTING_BROWNIAN_CV]; + } + + int get_root() const { + return values_[CHORDS_SETTING_ROOT]; + } + + int get_root(uint8_t DUMMY) const { + return 0x0; + } + + uint8_t get_root_cv() const { + return values_[CHORDS_SETTING_ROOT_CV]; + } + + int get_display_num_chords() const { + return display_num_chords_; + } + + uint16_t get_mask() const { + return values_[CHORDS_SETTING_MASK]; + } + + uint16_t get_rotated_mask() const { + return last_mask_; + } + + uint8_t get_mask_cv() const { + return values_[CHORDS_SETTING_MASK_CV]; + } + + int16_t get_cv_source() const { + return values_[CHORDS_SETTING_CV_SOURCE]; + } + + int8_t get_chords_trigger_source() const { + return values_[CHORDS_SETTING_CHORDS_ADVANCE_TRIGGER_SOURCE]; + } + + uint16_t get_trigger_delay() const { + return values_[CHORDS_SETTING_TRIGGER_DELAY]; + } + + int get_transpose() const { + return values_[CHORDS_SETTING_TRANSPOSE]; + } + + uint8_t get_transpose_cv() const { + return values_[CHORDS_SETTING_TRANSPOSE_CV]; + } + + int get_octave() const { + return values_[CHORDS_SETTING_OCTAVE]; + } + + uint8_t get_octave_cv() const { + return values_[CHORDS_SETTING_OCTAVE_CV]; + } + + uint8_t get_quality_cv() const { + return values_[CHORDS_SETTING_QUALITY_CV]; + } + + uint8_t get_inversion_cv() const { + return values_[CHORDS_SETTING_INVERSION_CV]; + } + + uint8_t get_voicing_cv() const { + return values_[CHORDS_SETTING_VOICING_CV]; + } + + uint8_t clockState() const { + return clock_display_.getState(); + } + + uint8_t get_menu_page() const { + return menu_page_; + } + + void set_menu_page(uint8_t _menu_page) { + menu_page_ = _menu_page; + } + + void update_inputmap(int num_slots, uint8_t range) { + input_map_.Configure(OC::InputMaps::GetInputMap(num_slots), range); + } + + void clear_CV_mapping() { + // clear all ... + apply_value(CHORDS_SETTING_ROOT_CV, 0); + apply_value(CHORDS_SETTING_MASK_CV, 0); + apply_value(CHORDS_SETTING_TRANSPOSE_CV, 0); + apply_value(CHORDS_SETTING_OCTAVE_CV, 0); + apply_value(CHORDS_SETTING_QUALITY_CV, 0); + apply_value(CHORDS_SETTING_VOICING_CV, 0); + apply_value(CHORDS_SETTING_INVERSION_CV, 0); + apply_value(CHORDS_SETTING_BROWNIAN_CV, 0); + apply_value(CHORDS_SETTING_DIRECTION_CV, 0); + apply_value(CHORDS_SETTING_PROGRESSION_CV, 0); + apply_value(CHORDS_SETTING_NUM_CHORDS_CV, 0); + } + + void Init() { + + InitDefaults(); + menu_page_ = MENU_PARAMETERS; + apply_value(CHORDS_SETTING_CV_SOURCE, 0x0); + set_scale(OC::Scales::SCALE_SEMI); + force_update_ = true; + _octave_toggle = false; + last_scale_= -1; + last_mask_ = 0; + last_sample_ = 0; + chord_advance_last_ = true; + progression_advance_last_ = true; + active_chord_ = 0; + chord_repeat_ = false; + progression_cnt_ = 0; + active_progression_ = 0; + playmode_last_ = 0; + progression_last_ = 0; + progression_EoP_ = 0; + num_chords_last_ = 0; + chords_direction_ = true; + display_num_chords_ = 0x1; + + trigger_delay_.Init(); + input_map_.Init(); + quantizer_.Init(); + chords_.Init(); + update_scale(true, false); + clock_display_.Init(); + update_enabled_settings(); + } + + void force_update() { + //force_update_ = true; + } + + int8_t _clock(uint8_t sequence_length, uint8_t sequence_count, uint8_t sequence_max, bool _reset) { + + int8_t EoP = 0x0, _clk_cnt, _direction; + bool reset = !digitalReadFast(TR4) | _reset; + + _clk_cnt = active_chord_; + _direction = get_direction(); + + if (get_direction_cv()) { + _direction += (OC::ADC::value(static_cast(get_direction_cv() - 1)) + 255) >> 9; + CONSTRAIN(_direction, 0, CHORDS_DIRECTIONS_LAST - 0x1); + } + + switch (_direction) { + + case CHORDS_FORWARD: + { + _clk_cnt++; + if (reset) + _clk_cnt = 0x0; + // end of sequence? + else if (_clk_cnt > sequence_length) + _clk_cnt = 0x0; + else if (_clk_cnt == sequence_length) + EoP = 0x1; + } + break; + case CHORDS_REVERSE: + { + _clk_cnt--; + if (reset) + _clk_cnt = sequence_length; + // end of sequence? + else if (_clk_cnt < 0) + _clk_cnt = sequence_length; + else if (!_clk_cnt) + EoP = 0x1; + } + break; + case CHORDS_PENDULUM1: + case CHORDS_BROWNIAN: + if (CHORDS_BROWNIAN == get_direction()) { + // Compare Brownian probability and reverse direction if needed + int16_t brown_prb = get_brownian_probability(); + + if (get_brownian_probability_cv()) { + brown_prb += (OC::ADC::value(static_cast(get_brownian_probability_cv() - 1)) + 8) >> 3; + CONSTRAIN(brown_prb, 0, 256); + } + if (random(0,256) < brown_prb) + chords_direction_ = !chords_direction_; + } + { + if (chords_direction_) { + _clk_cnt++; + if (reset) + _clk_cnt = 0x0; + else if (_clk_cnt >= sequence_length) { + + if (sequence_count >= sequence_max) { + chords_direction_ = false; + _clk_cnt = sequence_length; + } + else EoP = 0x1; + } + } + // reverse direction: + else { + _clk_cnt--; + if (reset) + _clk_cnt = sequence_length; + else if (_clk_cnt <= 0) { + // end of sequence ? + if (sequence_count == 0x0) { + chords_direction_ = true; + _clk_cnt = 0x0; + } + else EoP = -0x1; + } + } + } + break; + case CHORDS_PENDULUM2: + { + if (chords_direction_) { + + if (!chord_repeat_) + _clk_cnt++; + chord_repeat_ = false; + + if (reset) + _clk_cnt = 0x0; + else if (_clk_cnt >= sequence_length) { + // end of sequence ? + if (sequence_count >= sequence_max) { + chords_direction_ = false; + _clk_cnt = sequence_length; + chord_repeat_ = true; // repeat last step + } + else EoP = 0x1; + } + } + // reverse direction: + else { + + if (!chord_repeat_) + _clk_cnt--; + chord_repeat_ = false; + + if (reset) + _clk_cnt = sequence_length; + else if (_clk_cnt <= 0x0) { + // end of sequence ? + if (sequence_count == 0x0) { + chords_direction_ = true; + _clk_cnt = 0x0; + chord_repeat_ = true; // repeat first step + } + else EoP = -0x1; + } + } + } + break; + case CHORDS_RANDOM: + _clk_cnt = random(sequence_length + 0x1); + if (reset) + _clk_cnt = 0x0; + // jump to next sequence if we happen to hit the last note: + else if (_clk_cnt >= sequence_length) + EoP = random(0x2); + break; + default: + break; + } + active_chord_ = _clk_cnt; + return EoP; + } + + + inline void Update(uint32_t triggers) { + + bool triggered = triggers & DIGITAL_INPUT_MASK(0x0); + + trigger_delay_.Update(); + if (triggered) + trigger_delay_.Push(OC::trigger_delay_ticks[get_trigger_delay()]); + triggered = trigger_delay_.triggered(); + + int32_t sample_a = last_sample_; + int32_t temp_sample = 0; + + if (triggered) { + + int32_t pitch, cv_source, transpose, octave, root, mask_rotate; + int8_t num_progression, num_progression_cv, num_chords, num_chords_cv, progression_max, progression_cnt, playmode, reset; + + cv_source = get_cv_source(); + transpose = get_transpose(); + octave = get_octave(); + root = get_root(); + num_progression = get_progression(); + progression_max = 0; + progression_cnt = 0; + num_progression_cv = 0; + num_chords = 0; + num_chords_cv = 0; + reset = 0; + mask_rotate = 0; + playmode = get_playmode(); + + // update mask? + if (get_mask_cv()) { + mask_rotate = (OC::ADC::value(static_cast(get_mask_cv() - 0x1)) + 127) >> 8; + } + + update_scale(force_update_, mask_rotate); + + if (num_progression != progression_last_ || playmode != playmode_last_) { + // reset progression: + progression_cnt_ = 0x0; + active_progression_ = num_progression; + } + playmode_last_ = playmode; + progression_last_ = num_progression; + + if (get_progression_cv()) { + num_progression_cv = num_progression += (OC::ADC::value(static_cast(get_progression_cv() - 1)) + 255) >> 9; + CONSTRAIN(num_progression, 0, OC::Chords::NUM_CHORD_PROGRESSIONS - 0x1); + } + + if (get_num_chords_cv()) + num_chords_cv = (OC::ADC::value(static_cast(get_num_chords_cv() - 1)) + 255) >> 9; + + switch (playmode) { + + case _NONE: + active_progression_ = num_progression; + break; + case _SEQ1: + case _SEQ2: + case _SEQ3: + { + progression_max = playmode; + + if (progression_EoP_) { + + // increment progression # + progression_cnt_ += progression_EoP_; + // reset progression # + progression_cnt_ = progression_cnt_ > progression_max ? 0x0 : progression_cnt_; + // update progression + active_progression_ = num_progression + progression_cnt_; + // wrap around: + if (active_progression_ >= OC::Chords::NUM_CHORD_PROGRESSIONS) + active_progression_ -= OC::Chords::NUM_CHORD_PROGRESSIONS; + // reset + _clock(get_num_chords(active_progression_), 0x0, progression_max, true); + reset = true; + } + else if (num_progression_cv) { + active_progression_ += num_progression_cv; + CONSTRAIN(active_progression_, 0, OC::Chords::NUM_CHORD_PROGRESSIONS - 0x1); + } + progression_cnt = progression_cnt_; + } + break; + case _TR1: + case _TR2: + case _TR3: + { + // get trigger + uint8_t _progression_advance_trig = digitalReadFast(TR3); + progression_max = playmode - _SEQ3; + + if (_progression_advance_trig < progression_advance_last_) { + // increment progression # + progression_cnt_++; + // reset progression # + progression_cnt_ = progression_cnt_ > progression_max ? 0x0 : progression_cnt_; + // update progression + active_progression_ = num_progression + progression_cnt_; + // + reset + reset = true; + // wrap around: + if (active_progression_ >= OC::Chords::NUM_CHORD_PROGRESSIONS) + active_progression_ -= OC::Chords::NUM_CHORD_PROGRESSIONS; + } + else if (num_progression_cv) { + active_progression_ += num_progression_cv; + CONSTRAIN(active_progression_, 0, OC::Chords::NUM_CHORD_PROGRESSIONS - 0x1); + } + progression_advance_last_ = _progression_advance_trig; + progression_max = 0x0; + } + break; + case _SH1: + case _SH2: + case _SH3: + case _SH4: + { + // SH? + uint8_t _progression_advance_trig = digitalReadFast(TR3); + if (_progression_advance_trig < progression_advance_last_) { + + num_chords = get_num_chords(num_progression) + num_chords_cv; + if (num_chords_cv) + CONSTRAIN(num_chords, 0, OC::Chords::NUM_CHORDS - 0x1); + // length changed? + if (num_chords_last_ != num_chords) + update_inputmap(num_chords + 0x1, 0x0); + // store values: + num_chords_last_ = num_chords; + active_progression_ = num_progression; + // process input: + active_chord_ = input_map_.Process(OC::ADC::value(static_cast(playmode - _SH1))); + } + progression_advance_last_ = _progression_advance_trig; + } + break; + case _CV1: + case _CV2: + case _CV3: + case _CV4: + { + num_chords = get_num_chords(num_progression) + num_chords_cv; + if (num_chords_cv) + CONSTRAIN(num_chords, 0, OC::Chords::NUM_CHORDS - 0x1); + // length changed ? + if (num_chords_last_ != num_chords) + update_inputmap(num_chords + 0x1, 0x0); + // store values: + num_chords_last_ = num_chords; + active_progression_ = num_progression; + // process input: + active_chord_ = input_map_.Process(OC::ADC::value(static_cast(playmode - _CV1))); + } + break; + default: + break; + } + + num_progression = active_progression_; + + if (playmode < _SH1) { + // next chord via trigger? + uint8_t _advance_trig = get_chords_trigger_source(); + + if (_advance_trig == CHORDS_ADVANCE_TRIGGER_SOURCE_TR2) + _advance_trig = digitalReadFast(TR2); + else if (triggered) { + _advance_trig = 0x0; + chord_advance_last_ = 0x1; + } + + num_chords = get_num_chords(num_progression) + num_chords_cv; + if (num_chords_cv) + CONSTRAIN(num_chords, 0, OC::Chords::NUM_CHORDS - 0x1); + + CONSTRAIN(active_chord_, 0x0, num_chords); + + if (num_chords && (_advance_trig < chord_advance_last_)) + progression_EoP_ = _clock(num_chords, progression_cnt, progression_max, reset); + chord_advance_last_ = _advance_trig; + } + + display_num_chords_ = num_chords; + // active chord: + OC::Chord *active_chord = &OC::user_chords[active_chord_ + num_progression * OC::Chords::NUM_CHORDS]; + + int8_t _base_note = active_chord->base_note; + int8_t _octave = active_chord->octave; + int8_t _quality = active_chord->quality; + int8_t _voicing = active_chord->voicing; + int8_t _inversion = active_chord->inversion; + + octave += _octave; + CONSTRAIN(octave, -6, 6); + + if (_base_note) { + /* + * we don't use the incoming CV pitch value — limit to valid base notes. + */ + int8_t _limit = OC::Scales::GetScale(get_scale(DUMMY)).num_notes; + _base_note = _base_note > _limit ? _limit : _base_note; + pitch = 0x0; + transpose += (_base_note - 0x1); + } + else { + pitch = quantizer_.enabled() + ? OC::ADC::raw_pitch_value(static_cast(cv_source)) + : OC::ADC::pitch_value(static_cast(cv_source)); + } + + switch (cv_source) { + + case CHORDS_CV_SOURCE_CV1: + break; + // todo + default: + break; + } + + // S/H mode + if (get_root_cv()) { + root += (OC::ADC::value(static_cast(get_root_cv() - 1)) + 127) >> 8; + CONSTRAIN(root, 0, 15); + } + + if (get_octave_cv()) { + octave += (OC::ADC::value(static_cast(get_octave_cv() - 1)) + 255) >> 9; + CONSTRAIN(octave, -4, 4); + } + + if (get_transpose_cv()) { + transpose += (OC::ADC::value(static_cast(get_transpose_cv() - 1)) + 63) >> 7; + CONSTRAIN(transpose, -15, 15); + } + + if (get_quality_cv()) { + _quality += (OC::ADC::value(static_cast(get_quality_cv() - 1)) + 255) >> 9; + CONSTRAIN(_quality, 0, OC::Chords::CHORDS_QUALITY_LAST - 1); + } + + if (get_inversion_cv()) { + _inversion += (OC::ADC::value(static_cast(get_inversion_cv() - 1)) + 511) >> 10; + CONSTRAIN(_inversion, 0, OC::Chords::CHORDS_INVERSION_LAST - 1); + } + + if (get_voicing_cv()) { + _voicing += (OC::ADC::value(static_cast(get_voicing_cv() - 1)) + 255) >> 9; + CONSTRAIN(_voicing, 0, OC::Chords::CHORDS_VOICING_LAST - 1); + } + + int32_t quantized = quantizer_.Process(pitch, root << 7, transpose); + // main sample, S/H: + sample_a = temp_sample = OC::DAC::pitch_to_scaled_voltage_dac(DAC_CHANNEL_A, quantized, octave + OC::inversion[_inversion][0], OC::DAC::get_voltage_scaling(DAC_CHANNEL_A)); + + // now derive chords ... + transpose += OC::qualities[_quality][1]; + int32_t sample_b = quantizer_.Process(pitch, root << 7, transpose); + transpose += OC::qualities[_quality][2]; + int32_t sample_c = quantizer_.Process(pitch, root << 7, transpose); + transpose += OC::qualities[_quality][3]; + int32_t sample_d = quantizer_.Process(pitch, root << 7, transpose); + + //todo voicing for root note + sample_b = OC::DAC::pitch_to_scaled_voltage_dac(DAC_CHANNEL_B, sample_b, octave + OC::voicing[_voicing][1] + OC::inversion[_inversion][1], OC::DAC::get_voltage_scaling(DAC_CHANNEL_B)); + sample_c = OC::DAC::pitch_to_scaled_voltage_dac(DAC_CHANNEL_C, sample_c, octave + OC::voicing[_voicing][2] + OC::inversion[_inversion][2], OC::DAC::get_voltage_scaling(DAC_CHANNEL_C)); + sample_d = OC::DAC::pitch_to_scaled_voltage_dac(DAC_CHANNEL_D, sample_d, octave + OC::voicing[_voicing][3] + OC::inversion[_inversion][3], OC::DAC::get_voltage_scaling(DAC_CHANNEL_D)); + + OC::DAC::set(sample_a); + OC::DAC::set(sample_b); + OC::DAC::set(sample_c); + OC::DAC::set(sample_d); + } + + bool changed = (last_sample_ != sample_a); + + if (changed) { + MENU_REDRAW = 1; + last_sample_ = sample_a; + } + + if (triggered) { + clock_display_.Update(1, true); + } else { + clock_display_.Update(1, false); + } + } + + // Wrappers for ScaleEdit + void scale_changed() { + force_update_ = true; + } + + uint16_t get_scale_mask(uint8_t scale_select) const { + return get_mask(); + } + + void update_scale_mask(uint16_t mask, uint8_t scale_select) { + apply_value(CHORDS_SETTING_MASK, mask); + last_mask_ = mask; + force_update_ = true; + } + + // Maintain an internal list of currently available settings, since some are + // dependent on others. It's kind of brute force, but eh, works :) If other + // apps have a similar need, it can be moved to a common wrapper + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + CHORDS_SETTINGS enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + void update_enabled_settings() { + + CHORDS_SETTINGS *settings = enabled_settings_; + + switch(get_menu_page()) { + + case MENU_PARAMETERS: { + + *settings++ = CHORDS_SETTING_MASK; + // hide root ? + if (get_scale(DUMMY) != OC::Scales::SCALE_NONE) + *settings++ = CHORDS_SETTING_ROOT; + else + *settings++ = CHORDS_SETTING_MORE_DUMMY; + + *settings++ = CHORDS_SETTING_PROGRESSION; + *settings++ = CHORDS_SETTING_CHORD_EDIT; + *settings++ = CHORDS_SETTING_PLAYMODES; + if (get_playmode() < _SH1) + *settings++ = CHORDS_SETTING_DIRECTION; + if (get_direction() == CHORDS_BROWNIAN) + *settings++ = CHORDS_SETTING_BROWNIAN_PROBABILITY; + *settings++ = CHORDS_SETTING_TRANSPOSE; + *settings++ = CHORDS_SETTING_OCTAVE; + *settings++ = CHORDS_SETTING_CV_SOURCE; + *settings++ = CHORDS_SETTING_CHORDS_ADVANCE_TRIGGER_SOURCE; + *settings++ = CHORDS_SETTING_TRIGGER_DELAY; + } + break; + case MENU_CV_MAPPING: { + + *settings++ = CHORDS_SETTING_MASK_CV; + // destinations: + // hide root CV? + if (get_scale(DUMMY) != OC::Scales::SCALE_NONE) + *settings++ = CHORDS_SETTING_ROOT_CV; + else + *settings++ = CHORDS_SETTING_MORE_DUMMY; + + *settings++ = CHORDS_SETTING_PROGRESSION_CV; + *settings++ = CHORDS_SETTING_CHORD_EDIT; + *settings++ = CHORDS_SETTING_NUM_CHORDS_CV; + if (get_playmode() < _SH1) + *settings++ = CHORDS_SETTING_DIRECTION_CV; + if (get_direction() == CHORDS_BROWNIAN) + *settings++ = CHORDS_SETTING_BROWNIAN_CV; + *settings++ = CHORDS_SETTING_TRANSPOSE_CV; + *settings++ = CHORDS_SETTING_OCTAVE_CV; + *settings++ = CHORDS_SETTING_QUALITY_CV; + *settings++ = CHORDS_SETTING_INVERSION_CV; + *settings++ = CHORDS_SETTING_VOICING_CV; + } + break; + default: + break; + } + // end switch + num_enabled_settings_ = settings - enabled_settings_; + } + + void RenderScreensaver(weegfx::coord_t x) const; + +private: + bool force_update_; + bool _octave_toggle; + int last_scale_; + uint16_t last_mask_; + int32_t last_sample_; + uint8_t display_num_chords_; + bool chord_advance_last_; + bool progression_advance_last_; + int8_t active_chord_; + int8_t progression_cnt_; + int8_t progression_EoP_; + bool chord_repeat_; + int8_t active_progression_; + int8_t menu_page_; + bool chords_direction_; + int8_t playmode_last_; + int8_t progression_last_; + int8_t num_chords_last_; + + util::TriggerDelay trigger_delay_; + braids::Quantizer quantizer_; + OC::Input_Map input_map_; + OC::DigitalInputDisplay clock_display_; + OC::Chords chords_; + + int num_enabled_settings_; + CHORDS_SETTINGS enabled_settings_[CHORDS_SETTING_LAST]; + + bool update_scale(bool force, int32_t mask_rotate) { + + force_update_ = false; + const int scale = get_scale(DUMMY); + uint16_t mask = get_mask(); + + if (mask_rotate) + mask = OC::ScaleEditor::RotateMask(mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); + + if (force || (last_scale_ != scale || last_mask_ != mask)) { + last_scale_ = scale; + last_mask_ = mask; + quantizer_.Configure(OC::Scales::GetScale(scale), mask); + return true; + } else { + return false; + } + } +}; + +const char* const chords_advance_trigger_sources[] = { + "TR1", "TR2" +}; + +const char* const chords_slots[] = { + "#1", "#2", "#3", "#4", "#5", "#6", "#7", "#8" +}; + +const char* const chord_playmodes[] = { + "-", "SEQ+1", "SEQ+2", "SEQ+3", "TR3+1", "TR3+2", "TR3+3", "S+H#1", "S+H#2", "S+H#3", "S+H#4", "CV#1", "CV#2", "CV#3", "CV#4" +}; + +SETTINGS_DECLARE(Chords, CHORDS_SETTING_LAST) { + { OC::Scales::SCALE_SEMI, OC::Scales::SCALE_SEMI, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + { 0, 0, 11, "root", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, 0, OC::Chords::NUM_CHORD_PROGRESSIONS - 1, "progression", chords_slots, settings::STORAGE_TYPE_U8 }, + { 65535, 1, 65535, "scale -->", NULL, settings::STORAGE_TYPE_U16 }, // mask + { 0, 0, CHORDS_CV_SOURCE_LAST - 1, "CV source", OC::Strings::cv_input_names, settings::STORAGE_TYPE_U8 }, /// to do .. + { CHORDS_ADVANCE_TRIGGER_SOURCE_TR2, 0, CHORDS_ADVANCE_TRIGGER_SOURCE_LAST - 1, "chords trg src", chords_advance_trigger_sources, settings::STORAGE_TYPE_U8 }, + { 0, 0, CHORDS_PLAYMODES_LAST - 1, "playmode", chord_playmodes, settings::STORAGE_TYPE_U8 }, + { 0, 0, CHORDS_DIRECTIONS_LAST - 1, "direction", OC::Strings::seq_directions, settings::STORAGE_TYPE_U8 }, + { 64, 0, 255, "-->brown prob", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, OC::kNumDelayTimes - 1, "TR1 delay", OC::Strings::trigger_delay_times, settings::STORAGE_TYPE_U8 }, + { 0, -5, 7, "transpose", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -4, 4, "octave", NULL, settings::STORAGE_TYPE_I8 }, + { 0, 0, OC::Chords::CHORDS_USER_LAST - 1, "chord:", chords_slots, settings::STORAGE_TYPE_U8 }, + { 0, 0, OC::Chords::CHORDS_USER_LAST - 1, "num.chords", NULL, settings::STORAGE_TYPE_U8 }, // progression 1 + { 0, 0, OC::Chords::CHORDS_USER_LAST - 1, "num.chords", NULL, settings::STORAGE_TYPE_U8 }, // progression 2 + { 0, 0, OC::Chords::CHORDS_USER_LAST - 1, "num.chords", NULL, settings::STORAGE_TYPE_U8 }, // progression 3 + { 0, 0, OC::Chords::CHORDS_USER_LAST - 1, "num.chords", NULL, settings::STORAGE_TYPE_U8 }, // progression 4 + { 0, 0, 0, "chords -->", NULL, settings::STORAGE_TYPE_U4 }, // = chord editor + // CV + { 0, 0, 4, "root CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "mask CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "transpose CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "octave CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "quality CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "voicing CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "inversion CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "prg.slot# CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "direction CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "-->br.prb CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "num.chrds CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 0, "-", NULL, settings::STORAGE_TYPE_U4 }, // DUMMY + { 0, 0, 0, " ", NULL, settings::STORAGE_TYPE_U4 } // MORE DUMMY +}; + +class ChordQuantizer { +public: + void Init() { + cursor.Init(CHORDS_SETTING_SCALE, CHORDS_SETTING_LAST - 1); + scale_editor.Init(false); + chord_editor.Init(); + left_encoder_value = OC::Scales::SCALE_SEMI; + } + + inline bool editing() const { + return cursor.editing(); + } + + inline int cursor_pos() const { + return cursor.cursor_pos(); + } + + menu::ScreenCursor cursor; + // menu::ScreenCursor cursor; + OC::ScaleEditor scale_editor; + OC::ChordEditor chord_editor; + int left_encoder_value; +}; + +ChordQuantizer chords_state; +Chords chords; + +void CHORDS_init() { + + chords.InitDefaults(); + chords.Init(); + chords_state.Init(); + chords.update_enabled_settings(); + chords_state.cursor.AdjustEnd(chords.num_enabled_settings() - 1); +} + +size_t CHORDS_storageSize() { + return Chords::storageSize(); +} + +size_t CHORDS_save(void *storage) { + return chords.Save(storage); +} + +size_t CHORDS_restore(const void *storage) { + + size_t storage_size = chords.Restore(storage); + chords.update_enabled_settings(); + chords_state.left_encoder_value = chords.get_scale(DUMMY); + chords.set_scale(chords_state.left_encoder_value); + chords_state.cursor.AdjustEnd(chords.num_enabled_settings() - 1); + return storage_size; +} + +void CHORDS_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + chords_state.cursor.set_editing(false); + chords_state.scale_editor.Close(); + chords_state.chord_editor.Close(); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void CHORDS_isr() { + + uint32_t triggers = OC::DigitalInputs::clocked(); + chords.Update(triggers); +} + +void CHORDS_loop() { +} + +void CHORDS_menu() { + + menu::TitleBar<0, 4, 0>::Draw(); + + // print scale + int scale = chords_state.left_encoder_value; + graphics.movePrintPos(5, 0); + graphics.print(OC::scale_names[scale]); + if (chords.get_scale(DUMMY) == scale) + graphics.drawBitmap8(1, menu::QuadTitleBar::kTextY, 4, OC::bitmap_indicator_4x8); + + // active progression # + graphics.setPrintPos(106, 2); + if (chords.poke_octave_toggle()) + graphics.print("+"); + else + graphics.print("#"); + graphics.print(chords.get_active_progression() + 0x1); + + uint8_t clock_state = (chords.clockState() + 3) >> 2; + if (clock_state && !chords_state.chord_editor.active()) + graphics.drawBitmap8(121, 2, 4, OC::bitmap_gate_indicators_8 + (clock_state << 2)); + + menu::SettingsList settings_list(chords_state.cursor); + menu::SettingsListItem list_item; + + while (settings_list.available()) { + + const int setting = chords.enabled_setting_at(settings_list.Next(list_item)); + const int value = chords.get_value(setting); + const settings::value_attr &attr = Chords::value_attr(setting); + + switch(setting) { + + case CHORDS_SETTING_MASK: + menu::DrawMask(menu::kDisplayWidth, list_item.y, chords.get_rotated_mask(), OC::Scales::GetScale(chords.get_scale(DUMMY)).num_notes); + list_item.DrawNoValue(value, attr); + break; + case CHORDS_SETTING_DUMMY: + case CHORDS_SETTING_CHORD_EDIT: + // to do: draw something that makes sense, presumably some pre-made icons would work best. + menu::DrawMiniChord(menu::kDisplayWidth, list_item.y, chords.get_display_num_chords(), chords.active_chord()); + list_item.DrawNoValue(value, attr); + break; + case CHORDS_SETTING_MORE_DUMMY: + list_item.DrawNoValue(value, attr); + break; + case CHORDS_SETTING_CHORD_SLOT: + //special case: + list_item.DrawValueMax(value, attr, chords.get_num_chords(chords.get_progression())); + break; + default: + list_item.DrawDefault(value, attr); + break; + } + + if (chords_state.scale_editor.active()) + chords_state.scale_editor.Draw(); + else if (chords_state.chord_editor.active()) + chords_state.chord_editor.Draw(); + } +} + +void CHORDS_handleEncoderEvent(const UI::Event &event) { + + if (chords_state.scale_editor.active()) { + chords_state.scale_editor.HandleEncoderEvent(event); + return; + } + else if (chords_state.chord_editor.active()) { + chords_state.chord_editor.HandleEncoderEvent(event); + return; + } + + if (OC::CONTROL_ENCODER_L == event.control) { + + int value = chords_state.left_encoder_value + event.value; + CONSTRAIN(value, OC::Scales::SCALE_SEMI, OC::Scales::NUM_SCALES - 1); + chords_state.left_encoder_value = value; + + } else if (OC::CONTROL_ENCODER_R == event.control) { + + if (chords_state.editing()) { + + CHORDS_SETTINGS setting = chords.enabled_setting_at(chords_state.cursor_pos()); + + if (CHORDS_SETTING_MASK != setting) { + + if (chords.change_value(setting, event.value)) + chords.force_update(); + + switch (setting) { + case CHORDS_SETTING_CHORD_SLOT: + // special case, slot shouldn't be > num.chords + if (chords.get_chord_slot() > chords.get_num_chords(chords.get_progression())) + chords.set_chord_slot(chords.get_num_chords(chords.get_progression())); + break; + case CHORDS_SETTING_DIRECTION: + case CHORDS_SETTING_PLAYMODES: + // show options, or don't: + chords.update_enabled_settings(); + chords_state.cursor.AdjustEnd(chords.num_enabled_settings() - 1); + break; + default: + break; + } + } + } else { + chords_state.cursor.Scroll(event.value); + } + } +} + +void CHORDS_topButton() { + + if (chords.get_menu_page() == MENU_PARAMETERS) { + + if (chords.octave_toggle()) + chords.change_value(CHORDS_SETTING_OCTAVE, 1); + else + chords.change_value(CHORDS_SETTING_OCTAVE, -1); + } + else { + chords.set_menu_page(MENU_PARAMETERS); + chords.update_enabled_settings(); + chords_state.cursor.set_editing(false); + } +} + +void CHORDS_lowerButton() { + // go the CV mapping + + if (!chords_state.chord_editor.active() && !chords_state.scale_editor.active()) { + + uint8_t _menu_page = chords.get_menu_page(); + + switch (_menu_page) { + + case MENU_PARAMETERS: + _menu_page = MENU_CV_MAPPING; + break; + default: + _menu_page = MENU_PARAMETERS; + break; + } + + chords.set_menu_page(_menu_page); + chords.update_enabled_settings(); + chords_state.cursor.set_editing(false); + } +} + +void CHORDS_rightButton() { + + switch (chords.enabled_setting_at(chords_state.cursor_pos())) { + + case CHORDS_SETTING_MASK: { + int scale = chords.get_scale(DUMMY); + if (OC::Scales::SCALE_NONE != scale) + chords_state.scale_editor.Edit(&chords, scale); + } + break; + case CHORDS_SETTING_CHORD_EDIT: + chords_state.chord_editor.Edit(&chords, chords.get_chord_slot(), chords.get_num_chords(chords.get_progression()), chords.get_progression()); + break; + case CHORDS_SETTING_DUMMY: + case CHORDS_SETTING_MORE_DUMMY: + chords.set_menu_page(MENU_PARAMETERS); + chords.update_enabled_settings(); + break; + default: + chords_state.cursor.toggle_editing(); + break; + } +} + +void CHORDS_leftButton() { + + if (chords_state.left_encoder_value != chords.get_scale(DUMMY) || chords_state.left_encoder_value == OC::Scales::SCALE_SEMI) { + chords.set_scale(chords_state.left_encoder_value); + // hide/show root + chords.update_enabled_settings(); + } +} + +void CHORDS_leftButtonLong() { + // todo +} + +void CHORDS_downButtonLong() { + chords.clear_CV_mapping(); + chords_state.cursor.set_editing(false); +} + +void CHORDS_upButtonLong() { + // screensaver short cut +} + +void CHORDS_handleButtonEvent(const UI::Event &event) { + + if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + CHORDS_upButtonLong(); + break; + case OC::CONTROL_BUTTON_DOWN: + CHORDS_downButtonLong(); + break; + case OC::CONTROL_BUTTON_L: + if (!(chords_state.chord_editor.active())) + CHORDS_leftButtonLong(); + break; + default: + break; + } + } + + if (chords_state.scale_editor.active()) { + chords_state.scale_editor.HandleButtonEvent(event); + return; + } + else if (chords_state.chord_editor.active()) { + chords_state.chord_editor.HandleButtonEvent(event); + return; + } + + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + CHORDS_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + CHORDS_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + CHORDS_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + CHORDS_rightButton(); + break; + } + } +} + +static const weegfx::coord_t chords_kBottom = 60; + +inline int32_t chords_render_pitch(int32_t pitch, weegfx::coord_t x, weegfx::coord_t width) { + + CONSTRAIN(pitch, 0, 120 << 7); + int32_t octave = pitch / (12 << 7); + pitch -= (octave * 12 << 7); + graphics.drawHLine(x, chords_kBottom - ((pitch * 4) >> 7), width << 1); + return octave; +} + +void Chords::RenderScreensaver(weegfx::coord_t start_x) const { + + int _active_chord = active_chord(); + int _num_progression = get_active_progression(); + int _num_chords = get_display_num_chords(); + int x = start_x + 4; + int y = 42; + + // todo: CV + for (int j = 0; j <= _num_chords; j++) { + + if (j == _active_chord) + menu::DrawChord(x + (j << 4) + 1, y, 6, j, _num_progression); + else + menu::DrawChord(x + (j << 4) + 2, y, 4, j, _num_progression); + } +} + + +void CHORDS_screensaver() { +#ifdef CHORDS_DEBUG_SCREENSAVER + debug::CycleMeasurement render_cycles; +#endif + + chords.RenderScreensaver(0); + +#ifdef CHORDS_DEBUG_SCREENSAVER + graphics.drawHLine(0, menu::kMenuLineH, menu::kDisplayWidth); + uint32_t us = debug::cycles_to_us(render_cycles.read()); + graphics.setPrintPos(0, 32); + graphics.printf("%u", us); +#endif +} + +#endif // ENABLE_APP_CHORDS diff --git a/software/o_c_REV/APP_DQ.ino b/software/o_c_REV/APP_DQ.ino new file mode 100644 index 000000000..142139e95 --- /dev/null +++ b/software/o_c_REV/APP_DQ.ino @@ -0,0 +1,1576 @@ +// Copyright (c) 2015, 2016 Patrick Dowling, Tim Churches, Max Stadler +// +// Initial app implementation: Patrick Dowling (pld@gurkenkiste.com) +// Modifications by: Tim Churches (tim.churches@gmail.com) +// Yet more Modifications by: mxmxmx +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Quad quantizer app, based around the the quantizer/scales implementation from +// from Braids by Olivier Gillet (see braids_quantizer.h/cc et al.). It has since +// grown a little bit... + +#ifdef ENABLE_APP_METAQ + +#include "OC_apps.h" +#include "util/util_settings.h" +#include "util/util_trigger_delay.h" +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_menus.h" +#include "OC_visualfx.h" +#include "OC_scales.h" +#include "OC_scale_edit.h" +#include "OC_strings.h" +#include "OC_digital_inputs.h" +#include "OC_ADC.h" +#include "extern/dspinst.h" + +namespace menu = OC::menu; + +#ifdef BUCHLA_4U + #define DQ_OFFSET_X 22 +#else + #define DQ_OFFSET_X 47 +#endif + +const uint8_t NUMCHANNELS = 2; +const uint8_t NUM_SCALE_SLOTS = 4; +// const uint8_t PULSEW_MAX = 255; +// const uint32_t TICKS_TO_MS = 43691; + +enum DQ_ChannelSetting { + DQ_CHANNEL_SETTING_SCALE1, + DQ_CHANNEL_SETTING_SCALE2, + DQ_CHANNEL_SETTING_SCALE3, + DQ_CHANNEL_SETTING_SCALE4, + DQ_CHANNEL_SETTING_ROOT1, + DQ_CHANNEL_SETTING_ROOT2, + DQ_CHANNEL_SETTING_ROOT3, + DQ_CHANNEL_SETTING_ROOT4, + DQ_CHANNEL_SETTING_SCALE_SEQ, + DQ_CHANNEL_SETTING_MASK1, + DQ_CHANNEL_SETTING_MASK2, + DQ_CHANNEL_SETTING_MASK3, + DQ_CHANNEL_SETTING_MASK4, + DQ_CHANNEL_SETTING_SEQ_MODE, + DQ_CHANNEL_SETTING_SOURCE, + DQ_CHANNEL_SETTING_TRIGGER, + DQ_CHANNEL_SETTING_DELAY, + DQ_CHANNEL_SETTING_TRANSPOSE1, + DQ_CHANNEL_SETTING_TRANSPOSE2, + DQ_CHANNEL_SETTING_TRANSPOSE3, + DQ_CHANNEL_SETTING_TRANSPOSE4, + DQ_CHANNEL_SETTING_OCTAVE, + DQ_CHANNEL_SETTING_AUX_OUTPUT, + DQ_CHANNEL_SETTING_PULSEWIDTH, + DQ_CHANNEL_SETTING_AUX_OCTAVE, + DQ_CHANNEL_SETTING_AUX_CV_DEST, + DQ_CHANNEL_SETTING_TURING_LENGTH, + DQ_CHANNEL_SETTING_TURING_PROB, + DQ_CHANNEL_SETTING_TURING_CV_SOURCE, + DQ_CHANNEL_SETTING_TURING_RANGE, + DQ_CHANNEL_SETTING_TURING_TRIG_OUT, + DQ_CHANNEL_SETTING_LAST +}; + +enum DQ_ChannelTriggerSource { + DQ_CHANNEL_TRIGGER_TR1, + DQ_CHANNEL_TRIGGER_TR2, + DQ_CHANNEL_TRIGGER_TR3, + DQ_CHANNEL_TRIGGER_TR4, + DQ_CHANNEL_TRIGGER_CONTINUOUS_UP, + DQ_CHANNEL_TRIGGER_CONTINUOUS_DOWN, + DQ_CHANNEL_TRIGGER_LAST +}; + +enum DQ_ChannelSource { + DQ_CHANNEL_SOURCE_CV1, + DQ_CHANNEL_SOURCE_CV2, + DQ_CHANNEL_SOURCE_CV3, + DQ_CHANNEL_SOURCE_CV4, + DQ_CHANNEL_SOURCE_TURING, + DQ_CHANNEL_SOURCE_LOGISTIC_MAP, + DQ_CHANNEL_SOURCE_BYTEBEAT, + DQ_CHANNEL_SOURCE_INT_SEQ, + DQ_CHANNEL_SOURCE_LAST +}; + +enum DQ_AUX_MODE { + DQ_GATE, + DQ_COPY, + DQ_ASR, + DQ_AUX_MODE_LAST +}; + +enum TRIG_AUX_MODE { + DQ_ECHO, + DQ_LSB, + DQ_CHANGE, + DQ_TRIG_AUX_LAST +}; + +enum DQ_CV_DEST { + DQ_DEST_NONE, + DQ_DEST_SCALE_SLOT, + DQ_DEST_ROOT, + DQ_DEST_OCTAVE, + DQ_DEST_TRANSPOSE, + DQ_DEST_MASK, + DQ_DEST_LAST +}; + +enum DQ_SLOTS { + SLOT1, + SLOT2, + SLOT3, + SLOT4, + LAST_SLOT +}; + +void DQ_topButton(); +void DQ_lowerButton(); +void DQ_leftButton(); +void DQ_rightButton(); +void DQ_leftButtonLong(); +void DQ_rightButton(); +void DQ_leftButton(); +void DQ_lowerButton(); +void DQ_topButton(); +void DQ_downButtonLong(); + +class DQ_QuantizerChannel : public settings::SettingsBase { +public: + enum DQ_STATES { + OFF, + ON = 0xFFFF + }; + int get_scale(uint8_t selected_scale_slot_) const { + + switch(selected_scale_slot_) { + + case SLOT2: + return values_[DQ_CHANNEL_SETTING_SCALE2]; + break; + case SLOT3: + return values_[DQ_CHANNEL_SETTING_SCALE3]; + break; + case SLOT4: + return values_[DQ_CHANNEL_SETTING_SCALE4]; + break; + case SLOT1: + default: + return values_[DQ_CHANNEL_SETTING_SCALE1]; + break; + } + } + + int get_scale_select() const { + return values_[DQ_CHANNEL_SETTING_SCALE_SEQ]; + } + + int get_scale_seq_mode() const { + return values_[DQ_CHANNEL_SETTING_SEQ_MODE]; + } + + int get_display_scale() const { + return display_scale_slot_; + } + + int get_display_root() const { + return display_root_; + } + + void set_scale_at_slot(int scale, uint16_t mask, int root, int transpose, uint8_t scale_slot) { + + if (scale != get_scale(scale_slot) || mask != get_mask(scale_slot) || root != root_last_ || transpose != transpose_last_) { + + const OC::Scale &scale_def = OC::Scales::GetScale(scale); + root_last_ = root; + transpose_last_ = transpose; + + if (0 == (mask & ~(0xffff << scale_def.num_notes))) + mask |= 0x1; + switch (scale_slot) { + case SLOT2: + apply_value(DQ_CHANNEL_SETTING_MASK2, mask); + apply_value(DQ_CHANNEL_SETTING_SCALE2, scale); + apply_value(DQ_CHANNEL_SETTING_ROOT2, root); + apply_value(DQ_CHANNEL_SETTING_TRANSPOSE2, transpose); + break; + case SLOT3: + apply_value(DQ_CHANNEL_SETTING_MASK3, mask); + apply_value(DQ_CHANNEL_SETTING_SCALE3, scale); + apply_value(DQ_CHANNEL_SETTING_ROOT3, root); + apply_value(DQ_CHANNEL_SETTING_TRANSPOSE3, transpose); + break; + case SLOT4: + apply_value(DQ_CHANNEL_SETTING_MASK4, mask); + apply_value(DQ_CHANNEL_SETTING_SCALE4, scale); + apply_value(DQ_CHANNEL_SETTING_ROOT4, root); + apply_value(DQ_CHANNEL_SETTING_TRANSPOSE4, transpose); + break; + case SLOT1: + default: + apply_value(DQ_CHANNEL_SETTING_MASK1, mask); + apply_value(DQ_CHANNEL_SETTING_SCALE1, scale); + apply_value(DQ_CHANNEL_SETTING_ROOT1, root); + apply_value(DQ_CHANNEL_SETTING_TRANSPOSE1, transpose); + break; + } + } + } + + int get_root(uint8_t selected_scale_slot_) const { + + switch(selected_scale_slot_) { + + case SLOT2: + return values_[DQ_CHANNEL_SETTING_ROOT2]; + break; + case SLOT3: + return values_[DQ_CHANNEL_SETTING_ROOT3]; + break; + case SLOT4: + return values_[DQ_CHANNEL_SETTING_ROOT4]; + break; + case SLOT1: + default: + return values_[DQ_CHANNEL_SETTING_ROOT1]; + break; + } + } + + uint16_t get_mask(uint8_t selected_scale_slot_) const { + + switch(selected_scale_slot_) { + + case SLOT2: + return values_[DQ_CHANNEL_SETTING_MASK2]; + break; + case SLOT3: + return values_[DQ_CHANNEL_SETTING_MASK3]; + break; + case SLOT4: + return values_[DQ_CHANNEL_SETTING_MASK4]; + break; + case SLOT1: + default: + return values_[DQ_CHANNEL_SETTING_MASK1]; + break; + } + } + + uint16_t get_rotated_mask(uint8_t selected_scale_slot_) const { + return last_mask_[selected_scale_slot_]; + } + + DQ_ChannelSource get_source() const { + return static_cast(values_[DQ_CHANNEL_SETTING_SOURCE]); + } + + DQ_ChannelTriggerSource get_trigger_source() const { + return static_cast(values_[DQ_CHANNEL_SETTING_TRIGGER]); + } + + OC::DigitalInput get_digital_input() const { + return static_cast(values_[DQ_CHANNEL_SETTING_TRIGGER]); + } + + uint8_t get_aux_cv_dest() const { + return values_[DQ_CHANNEL_SETTING_AUX_CV_DEST]; + } + + uint16_t get_trigger_delay() const { + return values_[DQ_CHANNEL_SETTING_DELAY]; + } + + int get_transpose(uint8_t selected_scale_slot_) const { + + switch(selected_scale_slot_) { + + case SLOT2: + return values_[DQ_CHANNEL_SETTING_TRANSPOSE2]; + break; + case SLOT3: + return values_[DQ_CHANNEL_SETTING_TRANSPOSE3]; + break; + case SLOT4: + return values_[DQ_CHANNEL_SETTING_TRANSPOSE4]; + break; + case SLOT1: + default: + return values_[DQ_CHANNEL_SETTING_TRANSPOSE1]; + break; + } + } + + int get_octave() const { + return values_[DQ_CHANNEL_SETTING_OCTAVE]; + } + + int get_aux_mode() const { + return values_[DQ_CHANNEL_SETTING_AUX_OUTPUT]; + } + + int get_aux_octave() const { + return values_[DQ_CHANNEL_SETTING_AUX_OCTAVE]; + } + + int get_pulsewidth() const { + return values_[DQ_CHANNEL_SETTING_PULSEWIDTH]; + } + + uint8_t get_turing_length() const { + return values_[DQ_CHANNEL_SETTING_TURING_LENGTH]; + } + + uint8_t get_turing_display_length() const { + return turing_display_length_; + } + + uint8_t get_turing_range() const { + return values_[DQ_CHANNEL_SETTING_TURING_RANGE]; + } + + uint8_t get_turing_probability() const { + return values_[DQ_CHANNEL_SETTING_TURING_PROB]; + } + + uint8_t get_turing_CV() const { + return values_[DQ_CHANNEL_SETTING_TURING_CV_SOURCE]; + } + + uint8_t get_turing_trig_out() const { + return values_[DQ_CHANNEL_SETTING_TURING_TRIG_OUT]; + } + + uint32_t get_shift_register() const { + return turing_machine_.get_shift_register(); + } + + void clear_dest() { + // ... + schedule_mask_rotate_ = 0x0; + continuous_offset_ = 0x0; + prev_transpose_cv_ = 0x0; + prev_octave_cv_ = 0x0; + prev_root_cv_ = 0x0; + prev_scale_cv_ = 0x0; + } + + void reset_scale() { + scale_reset_ = true; + } + + void Init(DQ_ChannelSource source, DQ_ChannelTriggerSource trigger_source) { + + InitDefaults(); + apply_value(DQ_CHANNEL_SETTING_SOURCE, source); + apply_value(DQ_CHANNEL_SETTING_TRIGGER, trigger_source); + + force_update_ = true; + + for (int i = 0; i < NUM_SCALE_SLOTS; i++) { + last_scale_[i] = -1; + last_mask_[i] = 0xFFFF; + } + + aux_sample_ = 0; + last_sample_ = 0; + last_aux_sample_ = 0; + continuous_offset_ = 0; + scale_sequence_cnt_ = 0; + scale_reset_ = 0; + active_scale_slot_ = 0; + display_scale_slot_ = 0; + display_root_ = 0; + root_last_ = 0; + transpose_last_ = 0; + prev_scale_slot_ = 0; + scale_advance_ = 0; + scale_advance_state_ = 0; + schedule_scale_update_ = 0; + schedule_mask_rotate_ = 0; + prev_octave_cv_ = 0; + prev_transpose_cv_ = 0; + prev_root_cv_ = 0; + prev_scale_cv_ = 0; + prev_destination_ = 0; + prev_pulsewidth_ = 100; + ticks_ = 0; + channel_frequency_in_ticks_ = 1000; + pulse_width_in_ticks_ = 1000; + + trigger_delay_.Init(); + quantizer_.Init(); + update_scale(true, 0, false); + trigger_display_.Init(); + update_enabled_settings(); + + turing_machine_.Init(); + turing_display_length_ = get_turing_length(); + + scrolling_history_.Init(OC::DAC::kOctaveZero * 12 << 7); + } + + void force_update() { + force_update_ = true; + } + + void schedule_scale_update() { + schedule_scale_update_ = true; + } + + inline void Update(uint32_t triggers, DAC_CHANNEL dac_channel, DAC_CHANNEL aux_channel) { + + ticks_++; + + DQ_ChannelTriggerSource trigger_source = get_trigger_source(); + bool continuous = DQ_CHANNEL_TRIGGER_CONTINUOUS_UP == trigger_source || DQ_CHANNEL_TRIGGER_CONTINUOUS_DOWN == trigger_source; + bool triggered = !continuous && + (triggers & DIGITAL_INPUT_MASK(trigger_source - DQ_CHANNEL_TRIGGER_TR1)); + + trigger_delay_.Update(); + if (triggered) + trigger_delay_.Push(OC::trigger_delay_ticks[get_trigger_delay()]); + triggered = trigger_delay_.triggered(); + + if (triggered) { + channel_frequency_in_ticks_ = ticks_; + ticks_ = 0x0; + update_asr_ = true; + aux_sample_ = ON; + } + + if (scale_reset_) { + // manual change? + scale_reset_ = false; + scale_sequence_cnt_ = 0x0; + scale_advance_state_ = 0x1; + active_scale_slot_ = get_scale_select(); + prev_scale_slot_ = display_scale_slot_ = active_scale_slot_; + } + + if (get_scale_seq_mode()) { + // to do, don't hardcode .. + uint8_t _advance_trig = (dac_channel == DAC_CHANNEL_A) ? digitalReadFast(TR2) : digitalReadFast(TR4); + if (_advance_trig < scale_advance_state_) + scale_advance_ = true; + scale_advance_state_ = _advance_trig; + } + else if (prev_scale_slot_ != get_scale_select()) { + active_scale_slot_ = get_scale_select(); + prev_scale_slot_ = display_scale_slot_ = active_scale_slot_; + } + + if (scale_advance_) { + scale_sequence_cnt_++; + active_scale_slot_ = get_scale_select() + (scale_sequence_cnt_ % (get_scale_seq_mode() + 1)); + + if (active_scale_slot_ >= NUM_SCALE_SLOTS) + active_scale_slot_ -= NUM_SCALE_SLOTS; + scale_advance_ = false; + schedule_scale_update_ = true; + } + + bool update = continuous || triggered; + + int32_t sample = last_sample_; + int32_t temp_sample = 0; + int32_t history_sample = 0; + uint8_t aux_mode = get_aux_mode(); + + if (update) { + + int32_t transpose, pitch, quantized = 0x0; + int source, cv_source, channel_id, octave, root, _aux_cv_destination; + + source = cv_source = get_source(); + _aux_cv_destination = get_aux_cv_dest(); + channel_id = (dac_channel == DAC_CHANNEL_A) ? 1 : 3; // hardcoded to use CV2, CV4, for now + + if (_aux_cv_destination != prev_destination_) + clear_dest(); + prev_destination_ = _aux_cv_destination; + // active scale slot: + display_scale_slot_ = prev_scale_slot_ = active_scale_slot_ + prev_scale_cv_; + // get root value + root = get_root(display_scale_slot_) + prev_root_cv_; + // get transpose value + transpose = get_transpose(display_scale_slot_) + prev_transpose_cv_; + // get octave value + octave = get_octave() + prev_octave_cv_; + + // S/H: ADC values + if (!continuous) { + + switch(_aux_cv_destination) { + + case DQ_DEST_NONE: + break; + case DQ_DEST_SCALE_SLOT: + display_scale_slot_ += (OC::ADC::value(static_cast(channel_id)) + 255) >> 9; + // if scale changes, we have to update the root and transpose values, too; mask gets updated in update_scale + root = get_root(display_scale_slot_); + transpose = get_transpose(display_scale_slot_); + schedule_scale_update_ = true; + break; + case DQ_DEST_ROOT: + root += (OC::ADC::value(static_cast(channel_id)) + 127) >> 8; + break; + case DQ_DEST_MASK: + schedule_mask_rotate_ = (OC::ADC::value(static_cast(channel_id)) + 127) >> 8; + break; + case DQ_DEST_OCTAVE: + octave += (OC::ADC::value(static_cast(channel_id)) + 255) >> 9; + break; + case DQ_DEST_TRANSPOSE: + transpose += (OC::ADC::value(static_cast(channel_id)) + 64) >> 7; + break; + default: + break; + } // end switch + + if (schedule_scale_update_) { + force_update_ = true; + schedule_scale_update_ = false; + } + } // -> triggered update + + // constrain values: + CONSTRAIN(display_scale_slot_, 0, NUM_SCALE_SLOTS-1); + CONSTRAIN(octave, -4, 4); + CONSTRAIN(root, 0, 11); + CONSTRAIN(transpose, -12, 12); + + // update scale? + update_scale(force_update_, display_scale_slot_, schedule_mask_rotate_); + + // internal CV source? + if (source > DQ_CHANNEL_SOURCE_CV4) + cv_source = channel_id - 1; + + // now, acquire + process sample: + pitch = quantizer_.enabled() + ? OC::ADC::raw_pitch_value(static_cast(cv_source)) + : OC::ADC::pitch_value(static_cast(cv_source)); + + switch (source) { + + case DQ_CHANNEL_SOURCE_CV1: + case DQ_CHANNEL_SOURCE_CV2: + case DQ_CHANNEL_SOURCE_CV3: + case DQ_CHANNEL_SOURCE_CV4: + quantized = quantizer_.Process(pitch, root << 7, transpose); + break; + case DQ_CHANNEL_SOURCE_TURING: + { + if (continuous) + break; + + int16_t _length = get_turing_length(); + int16_t _probability = get_turing_probability(); + int16_t _range = get_turing_range(); + + // _pitch can do other things now -- + switch (get_turing_CV()) { + + case 1: // range + _range += ((pitch + 63) >> 6); + CONSTRAIN(_range, 1, 120); + case 2: // LEN, 1-32 + _length += ((pitch + 255) >> 8); + CONSTRAIN(_length, 1, 32); + break; + case 3: // P + _probability += ((pitch + 15) >> 4); + CONSTRAIN(_probability, 0, 255); + break; + default: + break; + } + + turing_machine_.set_length(_length); + turing_machine_.set_probability(_probability); + turing_display_length_ = _length; + + uint32_t _shift_register = turing_machine_.Clock(); + // Since our range is limited anyway, just grab the last byte for lengths > 8, otherwise scale to use bits. + uint32_t shift = turing_machine_.length(); + uint32_t _scaled = (_shift_register & 0xFF) * _range; + _scaled = _scaled >> (shift > 7 ? 8 : shift); + quantized = quantizer_.Lookup(64 + _range / 2 - _scaled + transpose) + (root<< 7); + } + break; + default: + break; + } + + // the output, thus far: + sample = temp_sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, quantized, octave + continuous_offset_, OC::DAC::get_voltage_scaling(dac_channel)); + + // special treatment, continuous update -- only update the modulation values if/when the quantized input changes: + bool _continuous_update = continuous && last_sample_ != sample; + + if (_continuous_update) { + + bool _re_quantize = false; + int _aux_cv = 0; + + switch(_aux_cv_destination) { + + case DQ_DEST_NONE: + break; + case DQ_DEST_SCALE_SLOT: + _aux_cv = (OC::ADC::value(static_cast(channel_id)) + 255) >> 9; + if (_aux_cv != prev_scale_cv_) { + display_scale_slot_ += _aux_cv; + CONSTRAIN(display_scale_slot_, 0, NUM_SCALE_SLOTS - 0x1); + prev_scale_cv_ = _aux_cv; + // update the root and transpose values + root = get_root(display_scale_slot_); + transpose = get_transpose(display_scale_slot_); + // and update quantizer below: + schedule_scale_update_ = true; + _re_quantize = true; + } + break; + case DQ_DEST_TRANSPOSE: + _aux_cv = (OC::ADC::value(static_cast(channel_id)) + 63) >> 7; + if (_aux_cv != prev_transpose_cv_) { + transpose = get_transpose(display_scale_slot_) + _aux_cv; + CONSTRAIN(transpose, -12, 12); + prev_transpose_cv_ = _aux_cv; + _re_quantize = true; + } + break; + case DQ_DEST_ROOT: + _aux_cv = (OC::ADC::value(static_cast(channel_id)) + 127) >> 8; + if (_aux_cv != prev_root_cv_) { + display_root_ = root = get_root(display_scale_slot_) + _aux_cv; + CONSTRAIN(root, 0, 11); + prev_root_cv_ = _aux_cv; + _re_quantize = true; + } + break; + case DQ_DEST_OCTAVE: + _aux_cv = (OC::ADC::value(static_cast(channel_id)) + 255) >> 9; + if (_aux_cv != prev_octave_cv_) { + octave = get_octave() + _aux_cv; + CONSTRAIN(octave, -4, 4); + prev_octave_cv_ = _aux_cv; + _re_quantize = true; + } + break; + case DQ_DEST_MASK: + schedule_mask_rotate_ = (OC::ADC::value(static_cast(channel_id)) + 127) >> 8; + schedule_scale_update_ = true; + break; + default: + break; + } + // end switch + + // update scale? + if (schedule_scale_update_ && _continuous_update) { + update_scale(true, display_scale_slot_, schedule_mask_rotate_); + schedule_scale_update_ = false; + } + + // offset when TR source = continuous ? + int8_t _trigger_offset = 0; + bool _trigger_update = false; + if (OC::DigitalInputs::read_immediate(static_cast(channel_id - 1))) { + _trigger_offset = (trigger_source == DQ_CHANNEL_TRIGGER_CONTINUOUS_UP) ? 1 : -1; + } + if (_trigger_offset != continuous_offset_) + _trigger_update = true; + continuous_offset_ = _trigger_offset; + + // run quantizer again -- presumably could be made more efficient... + if (_re_quantize) + quantized = quantizer_.Process(pitch, root << 7, transpose); + if (_re_quantize || _trigger_update) + sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, quantized, octave + continuous_offset_, OC::DAC::get_voltage_scaling(dac_channel)); + // update ASR? + update_asr_ = (aux_mode == DQ_ASR && last_sample_ != sample); + + } + // end special treatment + + display_root_ = root; + history_sample = quantized + ((OC::DAC::kOctaveZero + octave) * 12 << 7); + + // deal with aux output: + switch (aux_mode) { + + case DQ_COPY: + // offset the quantized value: + aux_sample_ = OC::DAC::pitch_to_scaled_voltage_dac(aux_channel, quantized, octave + continuous_offset_ + get_aux_octave(), OC::DAC::get_voltage_scaling(aux_channel)); + break; + case DQ_ASR: + { + if (update_asr_) { + update_asr_ = false; + aux_sample_ = OC::DAC::pitch_to_scaled_voltage_dac(aux_channel, last_aux_sample_, octave + continuous_offset_ + get_aux_octave(), OC::DAC::get_voltage_scaling(aux_channel)); + last_aux_sample_ = quantized; + } + } + break; + case DQ_GATE: { + + if (source == DQ_CHANNEL_SOURCE_TURING) { + + switch(get_turing_trig_out()) { + + case DQ_ECHO: + break; + case DQ_LSB: + if (!turing_machine_.get_LSB()) + aux_sample_ = OFF; + break; + case DQ_CHANGE: + if (last_sample_ == sample) + aux_sample_ = OFF; + break; + default: + break; + } + } + } + break; + default: + break; + } + } + + // in continuous mode, don't track transposed sample: + bool changed = continuous ? (last_sample_ != temp_sample) : (last_sample_ != sample); + + if (changed) { + + MENU_REDRAW = 1; + last_sample_ = continuous ? temp_sample : sample; + + // in continuous mode, make aux output go high: + if (continuous && aux_mode == DQ_GATE) { + aux_sample_ = ON; + ticks_ = 0x0; + } + } + + // aux outputs: + int32_t aux_sample = aux_sample_; + + switch (aux_mode) { + + case DQ_COPY: + case DQ_ASR: + break; + case DQ_GATE: + { + if (aux_sample) { + + // pulsewidth setting -- + int16_t _pulsewidth = get_pulsewidth(); + + if (_pulsewidth || continuous) { // don't echo + + bool _gates = false; + + if (_pulsewidth == 255) + _gates = true; + // we-can't-echo-hack + if (continuous && !_pulsewidth) + _pulsewidth = 0x1; + + // recalculate (in ticks), if new pulsewidth setting: + if (prev_pulsewidth_ != _pulsewidth || ! ticks_) { + + if (!_gates) { + int32_t _fraction = signed_multiply_32x16b(43691, static_cast(_pulsewidth)); // = * 0.6667f + _fraction = signed_saturate_rshift(_fraction, 16, 0); + pulse_width_in_ticks_ = (_pulsewidth << 4) + _fraction; + } + else { // put out gates/half duty cycle: + + pulse_width_in_ticks_ = channel_frequency_in_ticks_ >> 1; + + if (_pulsewidth != 255) { // CV? + pulse_width_in_ticks_ = signed_multiply_32x16b(static_cast(_pulsewidth) << 8, pulse_width_in_ticks_); // + pulse_width_in_ticks_ = signed_saturate_rshift(pulse_width_in_ticks_, 16, 0); + } + } + } + prev_pulsewidth_ = _pulsewidth; + + // limit pulsewidth, if approaching half duty cycle: + if (!_gates && pulse_width_in_ticks_ >= channel_frequency_in_ticks_>>1) + pulse_width_in_ticks_ = (channel_frequency_in_ticks_ >> 1) | 1u; + + // turn off output? + if (ticks_ >= pulse_width_in_ticks_) + aux_sample_ = OFF; + else // keep on + aux_sample_ = ON; + } + else { + // we simply echo the pulsewidth: + aux_sample_ = OC::DigitalInputs::read_immediate(get_digital_input()) ? ON : OFF; + } + } + } + // scale gate + #ifdef BUCHLA_4U + aux_sample = (aux_sample_ == ON) ? OC::DAC::get_octave_offset(aux_channel, OCTAVES - OC::DAC::kOctaveZero - 0x2) : OC::DAC::get_zero_offset(aux_channel); + #else + aux_sample = (aux_sample_ == ON) ? OC::DAC::get_octave_offset(aux_channel, OCTAVES - OC::DAC::kOctaveZero - 0x1) : OC::DAC::get_zero_offset(aux_channel); + #endif + break; + default: + break; + } + + OC::DAC::set(dac_channel, sample); + OC::DAC::set(aux_channel, aux_sample); + + if (triggered || (continuous && changed)) { + scrolling_history_.Push(history_sample); + trigger_display_.Update(1, true); + } else { + trigger_display_.Update(1, false); + } + scrolling_history_.Update(); + } + + // Wrappers for ScaleEdit + void scale_changed() { + force_update_ = true; + } + + uint16_t get_scale_mask(uint8_t scale_select) const { + return get_mask(scale_select); + } + + void update_scale_mask(uint16_t mask, uint8_t scale_select) { + + switch (scale_select) { + + case SLOT1: + apply_value(DQ_CHANNEL_SETTING_MASK1, mask); + last_mask_[0] = mask; + break; + case SLOT2: + apply_value(DQ_CHANNEL_SETTING_MASK2, mask); + last_mask_[1] = mask; + break; + case SLOT3: + apply_value(DQ_CHANNEL_SETTING_MASK3, mask); + last_mask_[2] = mask; + break; + case SLOT4: + apply_value(DQ_CHANNEL_SETTING_MASK4, mask); + last_mask_[3] = mask; + break; + default: + break; + } + force_update_ = true; + } + // + + uint8_t getTriggerState() const { + return trigger_display_.getState(); + } + + // Maintain an internal list of currently available settings, since some are + // dependent on others. It's kind of brute force, but eh, works :) If other + // apps have a similar need, it can be moved to a common wrapper + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + DQ_ChannelSetting enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + void update_enabled_settings() { + DQ_ChannelSetting *settings = enabled_settings_; + + switch(get_scale_select()) { + + case SLOT1: + *settings++ = DQ_CHANNEL_SETTING_SCALE1; + break; + case SLOT2: + *settings++ = DQ_CHANNEL_SETTING_SCALE2; + break; + case SLOT3: + *settings++ = DQ_CHANNEL_SETTING_SCALE3; + break; + case SLOT4: + *settings++ = DQ_CHANNEL_SETTING_SCALE4; + break; + default: + break; + } + + // to do -- might as well disable no scale + if (OC::Scales::SCALE_NONE != get_scale(get_scale_select())) { + + switch(get_scale_select()) { + + case SLOT1: + *settings++ = DQ_CHANNEL_SETTING_MASK1; + break; + case SLOT2: + *settings++ = DQ_CHANNEL_SETTING_MASK2; + break; + case SLOT3: + *settings++ = DQ_CHANNEL_SETTING_MASK3; + break; + case SLOT4: + *settings++ = DQ_CHANNEL_SETTING_MASK4; + break; + default: + break; + } + + *settings++ = DQ_CHANNEL_SETTING_SEQ_MODE; + *settings++ = DQ_CHANNEL_SETTING_SCALE_SEQ; + + switch(get_scale_select()) { + + case SLOT1: + *settings++ = DQ_CHANNEL_SETTING_ROOT1; + *settings++ = DQ_CHANNEL_SETTING_TRANSPOSE1; + break; + case SLOT2: + *settings++ = DQ_CHANNEL_SETTING_ROOT2; + *settings++ = DQ_CHANNEL_SETTING_TRANSPOSE2; + break; + case SLOT3: + *settings++ = DQ_CHANNEL_SETTING_ROOT3; + *settings++ = DQ_CHANNEL_SETTING_TRANSPOSE3; + break; + case SLOT4: + *settings++ = DQ_CHANNEL_SETTING_ROOT4; + *settings++ = DQ_CHANNEL_SETTING_TRANSPOSE4; + break; + default: + break; + } + } + + // todo -- item order? + *settings++ = DQ_CHANNEL_SETTING_OCTAVE; + *settings++ = DQ_CHANNEL_SETTING_SOURCE; + + // CV sources: + switch (get_source()) { + + case DQ_CHANNEL_SOURCE_TURING: + *settings++ = DQ_CHANNEL_SETTING_TURING_RANGE; + *settings++ = DQ_CHANNEL_SETTING_TURING_LENGTH; + *settings++ = DQ_CHANNEL_SETTING_TURING_PROB; + *settings++ = DQ_CHANNEL_SETTING_TURING_CV_SOURCE; + *settings++ = DQ_CHANNEL_SETTING_TURING_TRIG_OUT; + break; + default: + break; + } + + *settings++ = DQ_CHANNEL_SETTING_AUX_CV_DEST; + *settings++ = DQ_CHANNEL_SETTING_TRIGGER; + + if (get_trigger_source() < DQ_CHANNEL_TRIGGER_CONTINUOUS_UP) + *settings++ = DQ_CHANNEL_SETTING_DELAY; + + *settings++ = DQ_CHANNEL_SETTING_AUX_OUTPUT; + + switch(get_aux_mode()) { + + case DQ_GATE: + *settings++ = DQ_CHANNEL_SETTING_PULSEWIDTH; + break; + case DQ_COPY: + *settings++ = DQ_CHANNEL_SETTING_AUX_OCTAVE; + break; + case DQ_ASR: + *settings++ = DQ_CHANNEL_SETTING_AUX_OCTAVE; + break; + default: + break; + } + + num_enabled_settings_ = settings - enabled_settings_; + } + + // + + void RenderScreensaver(weegfx::coord_t x) const; + +private: + bool force_update_; + bool update_asr_; + int last_scale_[NUM_SCALE_SLOTS]; + uint16_t last_mask_[NUM_SCALE_SLOTS]; + int scale_sequence_cnt_; + int active_scale_slot_; + int display_scale_slot_; + int display_root_; + int root_last_; + int transpose_last_; + int prev_scale_slot_; + int8_t scale_advance_; + int8_t scale_advance_state_; + bool scale_reset_; + bool schedule_scale_update_; + int32_t schedule_mask_rotate_; + int32_t last_sample_; + int32_t aux_sample_; + int32_t last_aux_sample_; + int8_t continuous_offset_; + uint8_t prev_pulsewidth_; + int8_t prev_destination_; + int8_t prev_octave_cv_; + int8_t prev_transpose_cv_; + int8_t prev_root_cv_; + int8_t prev_scale_cv_; + + uint32_t ticks_; + uint32_t channel_frequency_in_ticks_; + uint32_t pulse_width_in_ticks_; + + util::TriggerDelay trigger_delay_; + braids::Quantizer quantizer_; + OC::DigitalInputDisplay trigger_display_; + + // internal CV sources; + util::TuringShiftRegister turing_machine_; + int8_t turing_display_length_; + + int num_enabled_settings_; + DQ_ChannelSetting enabled_settings_[DQ_CHANNEL_SETTING_LAST]; + + OC::vfx::ScrollingHistory scrolling_history_; + + bool update_scale(bool force, uint8_t scale_select, int32_t mask_rotate) { + + force_update_ = false; + const int scale = get_scale(scale_select); + uint16_t mask = get_mask(scale_select); + + if (mask_rotate) + mask = OC::ScaleEditor::RotateMask(mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); + + if (force || (last_scale_[scale_select] != scale || last_mask_[scale_select] != mask)) { + last_scale_[scale_select] = scale; + last_mask_[scale_select] = mask; + quantizer_.Configure(OC::Scales::GetScale(scale), mask); + return true; + } else { + return false; + } + } +}; + +const char* const dq_seq_scales[] = { + "s#1", "s#2", "s#3", "s#4" +}; + +const char* const dq_seq_modes[] = { + "-", "TR+1", "TR+2", "TR+3" +}; + +const char* const dq_aux_outputs[] = { + "gate", "copy", "asr" +}; + +const char* const dq_aux_cv_dest[] = { + "-", "scl#", "root", "oct", "trns", "mask" +}; + +const char* const dq_tm_trig_out[] = { + "echo", "lsb", "chng" +}; + +SETTINGS_DECLARE(DQ_QuantizerChannel, DQ_CHANNEL_SETTING_LAST) { + { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + { 0, 0, 11, "root #1", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, 0, 11, "root #2", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, 0, 11, "root #3", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, 0, 11, "root #4", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, 0, NUM_SCALE_SLOTS - 1, "scale #", dq_seq_scales, settings::STORAGE_TYPE_U8 }, + { 65535, 1, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, + { 65535, 1, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, + { 65535, 1, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, + { 65535, 1, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, + { 0, 0, 3, "seq_mode", dq_seq_modes, settings::STORAGE_TYPE_U4 }, + { DQ_CHANNEL_SOURCE_CV1, DQ_CHANNEL_SOURCE_CV1, 4, "CV source", OC::Strings::cv_input_names, settings::STORAGE_TYPE_U4 }, /// to do .. + { DQ_CHANNEL_TRIGGER_CONTINUOUS_DOWN, 0, DQ_CHANNEL_TRIGGER_LAST - 1, "trigger source", OC::Strings::channel_trigger_sources, settings::STORAGE_TYPE_U8 }, + { 0, 0, OC::kNumDelayTimes - 1, "--> latency", OC::Strings::trigger_delay_times, settings::STORAGE_TYPE_U8 }, + { 0, -5, 7, "transpose #1", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -5, 7, "transpose #2", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -5, 7, "transpose #3", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -5, 7, "transpose #4", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -4, 4, "octave", NULL, settings::STORAGE_TYPE_I8 }, + { 0, 0, DQ_AUX_MODE_LAST-1, "aux.output", dq_aux_outputs, settings::STORAGE_TYPE_U8 }, + { 25, 0, 255, "--> pw", NULL, settings::STORAGE_TYPE_U8 }, + { 0, -5, 5, "--> aux +/-", NULL, settings::STORAGE_TYPE_I8 }, // aux octave + { 0, 0, DQ_DEST_LAST-1, "CV aux.", dq_aux_cv_dest, settings::STORAGE_TYPE_U8 }, + { 16, 1, 32, " > LFSR length", NULL, settings::STORAGE_TYPE_U8 }, + { 128, 0, 255, " > LFSR p", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 3, " > LFSR CV", OC::Strings::TM_aux_cv_destinations, settings::STORAGE_TYPE_U8 }, // ?? + { 15, 1, 120, " > LFSR range", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, DQ_TRIG_AUX_LAST-1, " > LFSR TRIG", dq_tm_trig_out, settings::STORAGE_TYPE_U8 } +}; + +// WIP refactoring to better encapsulate and for possible app interface change +class DualQuantizer { +public: + void Init() { + selected_channel = 0; + cursor.Init(DQ_CHANNEL_SETTING_SCALE1, DQ_CHANNEL_SETTING_LAST - 1); + scale_editor.Init(true); + } + + inline bool editing() const { + return cursor.editing(); + } + + inline int cursor_pos() const { + return cursor.cursor_pos(); + } + + int selected_channel; + menu::ScreenCursor cursor; + OC::ScaleEditor scale_editor; +}; + +DualQuantizer dq_state; +DQ_QuantizerChannel dq_quantizer_channels[NUMCHANNELS]; + +void DQ_init() { + + dq_state.Init(); + for (size_t i = 0; i < NUMCHANNELS; ++i) { + dq_quantizer_channels[i].Init(static_cast(DQ_CHANNEL_SOURCE_CV1 + 2*i), static_cast(DQ_CHANNEL_TRIGGER_TR1 + 2*i)); + } + + dq_state.cursor.AdjustEnd(dq_quantizer_channels[0].num_enabled_settings() - 1); +} + +size_t DQ_storageSize() { + return NUMCHANNELS * DQ_QuantizerChannel::storageSize(); +} + +size_t DQ_save(void *storage) { + size_t used = 0; + for (size_t i = 0; i < NUMCHANNELS; ++i) { + used += dq_quantizer_channels[i].Save(static_cast(storage) + used); + } + return used; +} + +size_t DQ_restore(const void *storage) { + size_t used = 0; + for (size_t i = 0; i < NUMCHANNELS; ++i) { + used += dq_quantizer_channels[i].Restore(static_cast(storage) + used); + //int scale = dq_quantizer_channels[i].get_scale_select(); + for (size_t j = SLOT1; j < LAST_SLOT; j++) { + dq_quantizer_channels[i].update_scale_mask(dq_quantizer_channels[i].get_mask(j), j); + } + dq_quantizer_channels[i].update_enabled_settings(); + } + dq_state.cursor.AdjustEnd(dq_quantizer_channels[0].num_enabled_settings() - 1); + return used; +} + +void DQ_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + dq_state.cursor.set_editing(false); + dq_state.scale_editor.Close(); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void DQ_isr() { + + uint32_t triggers = OC::DigitalInputs::clocked(); + + dq_quantizer_channels[0].Update(triggers, DAC_CHANNEL_A, DAC_CHANNEL_C); + dq_quantizer_channels[1].Update(triggers, DAC_CHANNEL_B, DAC_CHANNEL_D); +} + +void DQ_loop() { +} + +void DQ_menu() { + + menu::DualTitleBar::Draw(); + + for (int i = 0, x = 0; i < NUMCHANNELS; ++i, x += 21) { + + const DQ_QuantizerChannel &channel = dq_quantizer_channels[i]; + menu::DualTitleBar::SetColumn(i); + menu::DualTitleBar::DrawGateIndicator(i, channel.getTriggerState()); + + graphics.movePrintPos(5, 0); + graphics.print((char)('A' + i)); + graphics.movePrintPos(2, 0); + graphics.print('#'); + graphics.print(channel.get_display_scale() + 1); + graphics.movePrintPos(12, 0); + if (channel.get_aux_cv_dest() == DQ_DEST_ROOT) + graphics.print(OC::Strings::note_names[channel.get_display_root()]); + else + graphics.print(OC::Strings::note_names[channel.get_root(channel.get_display_scale())]); + int octave = channel.get_octave(); + if (octave) + graphics.pretty_print(octave); + } + menu::DualTitleBar::Selected(dq_state.selected_channel); + + + const DQ_QuantizerChannel &channel = dq_quantizer_channels[dq_state.selected_channel]; + + menu::SettingsList settings_list(dq_state.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int setting = + channel.enabled_setting_at(settings_list.Next(list_item)); + const int value = channel.get_value(setting); + const settings::value_attr &attr = DQ_QuantizerChannel::value_attr(setting); + + switch (setting) { + case DQ_CHANNEL_SETTING_SCALE1: + case DQ_CHANNEL_SETTING_SCALE2: + case DQ_CHANNEL_SETTING_SCALE3: + case DQ_CHANNEL_SETTING_SCALE4: + list_item.SetPrintPos(); + if (list_item.editing) { + menu::DrawEditIcon(6, list_item.y, value, attr); + graphics.movePrintPos(6, 0); + } + graphics.print(OC::scale_names[value]); + list_item.DrawCustom(); + break; + case DQ_CHANNEL_SETTING_MASK1: + case DQ_CHANNEL_SETTING_MASK2: + case DQ_CHANNEL_SETTING_MASK3: + case DQ_CHANNEL_SETTING_MASK4: + menu::DrawMask(menu::kDisplayWidth, list_item.y, channel.get_rotated_mask(channel.get_display_scale()), OC::Scales::GetScale(channel.get_scale(channel.get_display_scale())).num_notes); + list_item.DrawNoValue(value, attr); + break; + case DQ_CHANNEL_SETTING_TRIGGER: + { + if (channel.get_source() > DQ_CHANNEL_SOURCE_CV4) + list_item.DrawValueMax(value, attr, DQ_CHANNEL_TRIGGER_TR4); + else + list_item.DrawDefault(value, attr); + } + break; + case DQ_CHANNEL_SETTING_SOURCE: + { + if (channel.get_source() == DQ_CHANNEL_SOURCE_TURING) { + + int turing_length = channel.get_turing_display_length(); + int w = turing_length >= 16 ? 16 * 3 : turing_length * 3; + + menu::DrawMask(menu::kDisplayWidth, list_item.y, channel.get_shift_register(), turing_length); + list_item.valuex = menu::kDisplayWidth - w - 1; + list_item.DrawNoValue(value, attr); + } + else if (channel.get_trigger_source() > DQ_CHANNEL_TRIGGER_TR4) + list_item.DrawValueMax(value, attr, DQ_CHANNEL_SOURCE_CV4); + else + list_item.DrawDefault(value, attr); + } + break; + case DQ_CHANNEL_SETTING_PULSEWIDTH: + list_item.Draw_PW_Value(value, attr); + break; + default: + list_item.DrawDefault(value, attr); + break; + } + } + + if (dq_state.scale_editor.active()) + dq_state.scale_editor.Draw(); +} + +void DQ_handleButtonEvent(const UI::Event &event) { + + if (UI::EVENT_BUTTON_LONG_PRESS == event.type && OC::CONTROL_BUTTON_DOWN == event.control) + DQ_downButtonLong(); + + if (dq_state.scale_editor.active()) { + dq_state.scale_editor.HandleButtonEvent(event); + return; + } + + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + DQ_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + DQ_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + DQ_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + DQ_rightButton(); + break; + } + } else { + if (OC::CONTROL_BUTTON_L == event.control) + DQ_leftButtonLong(); + } +} + +void DQ_handleEncoderEvent(const UI::Event &event) { + if (dq_state.scale_editor.active()) { + dq_state.scale_editor.HandleEncoderEvent(event); + return; + } + + if (OC::CONTROL_ENCODER_L == event.control) { + + int selected_channel = dq_state.selected_channel + event.value; + CONSTRAIN(selected_channel, 0, NUMCHANNELS - 0x1); + dq_state.selected_channel = selected_channel; + + DQ_QuantizerChannel &selected = dq_quantizer_channels[dq_state.selected_channel]; + selected.update_enabled_settings(); + dq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + //?? + dq_state.cursor.Scroll(0x0); + + } else if (OC::CONTROL_ENCODER_R == event.control) { + + DQ_QuantizerChannel &selected = dq_quantizer_channels[dq_state.selected_channel]; + + if (dq_state.editing()) { + + DQ_ChannelSetting setting = selected.enabled_setting_at(dq_state.cursor_pos()); + if (DQ_CHANNEL_SETTING_MASK1 != setting || DQ_CHANNEL_SETTING_MASK2 != setting || DQ_CHANNEL_SETTING_MASK3 != setting || DQ_CHANNEL_SETTING_MASK4 != setting) { + + int event_value = event.value; + + // hack disable internal sources when mode = continuous: + switch (setting) { + + case DQ_CHANNEL_SETTING_TRIGGER: + { + if (selected.get_trigger_source() == DQ_CHANNEL_TRIGGER_TR4 && selected.get_source() > DQ_CHANNEL_SOURCE_CV4 && event_value > 0) + event_value = 0x0; + } + break; + case DQ_CHANNEL_SETTING_SOURCE: + { + if (selected.get_source() == DQ_CHANNEL_SOURCE_CV4 && selected.get_trigger_source() > DQ_CHANNEL_TRIGGER_TR4 && event_value > 0) + event_value = 0x0; + } + break; + default: + break; + } + + if (selected.change_value(setting, event_value)) + selected.force_update(); + + switch (setting) { + case DQ_CHANNEL_SETTING_SCALE1: + case DQ_CHANNEL_SETTING_SCALE2: + case DQ_CHANNEL_SETTING_SCALE3: + case DQ_CHANNEL_SETTING_SCALE4: + case DQ_CHANNEL_SETTING_TRIGGER: + case DQ_CHANNEL_SETTING_SOURCE: + case DQ_CHANNEL_SETTING_AUX_OUTPUT: + selected.update_enabled_settings(); + dq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + break; + case DQ_CHANNEL_SETTING_SCALE_SEQ: + case DQ_CHANNEL_SETTING_SEQ_MODE: + selected.update_enabled_settings(); + dq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + selected.reset_scale(); + break; + default: + break; + } + } + } else { + dq_state.cursor.Scroll(event.value); + } + } +} + +void DQ_topButton() { + DQ_QuantizerChannel &selected = dq_quantizer_channels[dq_state.selected_channel]; + if (selected.change_value(DQ_CHANNEL_SETTING_OCTAVE, 1)) { + selected.force_update(); + } +} + +void DQ_lowerButton() { + DQ_QuantizerChannel &selected = dq_quantizer_channels[dq_state.selected_channel]; + if (selected.change_value(DQ_CHANNEL_SETTING_OCTAVE, -1)) { + selected.force_update(); + } +} + +void DQ_rightButton() { + DQ_QuantizerChannel &selected = dq_quantizer_channels[dq_state.selected_channel]; + switch (selected.enabled_setting_at(dq_state.cursor_pos())) { + case DQ_CHANNEL_SETTING_MASK1: + case DQ_CHANNEL_SETTING_MASK2: + case DQ_CHANNEL_SETTING_MASK3: + case DQ_CHANNEL_SETTING_MASK4: { + int scale = selected.get_scale(selected.get_scale_select()); + if (OC::Scales::SCALE_NONE != scale) { + dq_state.scale_editor.Edit(&selected, scale); + } + } + break; + default: + dq_state.cursor.toggle_editing(); + break; + } +} + +void DQ_leftButton() { + dq_state.selected_channel = (dq_state.selected_channel + 1) & 1u; + DQ_QuantizerChannel &selected = dq_quantizer_channels[dq_state.selected_channel]; + dq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); +} + +void DQ_leftButtonLong() { + + // copy scale settings to all slots: + DQ_QuantizerChannel &selected_channel = dq_quantizer_channels[dq_state.selected_channel]; + int _slot = selected_channel.get_scale_select(); + int scale = selected_channel.get_scale(_slot); + int mask = selected_channel.get_mask(_slot); + int root = selected_channel.get_root(_slot); + int transpose = selected_channel.get_transpose(_slot); + + for (int i = 0; i < NUM_SCALE_SLOTS; ++i) { + for (int j = 0; j < NUMCHANNELS; ++j) + dq_quantizer_channels[j].set_scale_at_slot(scale, mask, root, transpose, i); + } +} + +void DQ_downButtonLong() { + // reset mask + DQ_QuantizerChannel &selected_channel = dq_quantizer_channels[dq_state.selected_channel]; + int scale_slot = selected_channel.get_scale_select(); + selected_channel.set_scale_at_slot(selected_channel.get_scale(scale_slot), 0xFFFF, selected_channel.get_root(scale_slot), selected_channel.get_transpose(scale_slot), scale_slot); +} + +int32_t dq_history[5]; +static const weegfx::coord_t dq_kBottom = 60; + +inline int32_t dq_render_pitch(int32_t pitch, weegfx::coord_t x, weegfx::coord_t width) { + CONSTRAIN(pitch, 0, 120 << 7); + int32_t octave = pitch / (12 << 7); + pitch -= (octave * 12 << 7); + graphics.drawHLine(x, dq_kBottom - ((pitch * 4) >> 7), width << 1); + return octave; +} + +void DQ_QuantizerChannel::RenderScreensaver(weegfx::coord_t start_x) const { + + // History + scrolling_history_.Read(dq_history); + weegfx::coord_t scroll_pos = (scrolling_history_.get_scroll_pos() * 6) >> 8; + + // Top: Show gate & CV (or register bits) + menu::DrawGateIndicator(start_x + 1, 2, getTriggerState()); + const DQ_ChannelSource source = get_source(); + + switch (source) { + case DQ_CHANNEL_SOURCE_TURING: + menu::DrawMask(start_x + 58, 1, get_shift_register(), get_turing_display_length()); + break; + default: { + graphics.setPixel(start_x + DQ_OFFSET_X - 16, 4); + int32_t cv = OC::ADC::value(static_cast(source)); + cv = (cv * 20 + 2047) >> 11; + if (cv < 0) + graphics.drawRect(start_x + DQ_OFFSET_X - 16 + cv, 6, -cv, 2); + else if (cv > 0) + graphics.drawRect(start_x + DQ_OFFSET_X - 16, 6, cv, 2); + else + graphics.drawRect(start_x + DQ_OFFSET_X - 16, 6, 1, 2); + } + break; + } + +#ifdef DQ_DEBUG_SCREENSAVER + graphics.drawVLinePattern(start_x + 56, 0, 64, 0x55); +#endif + + // Draw semitone intervals, 4px apart + weegfx::coord_t x = start_x + 56; + weegfx::coord_t y = dq_kBottom; + for (int i = 0; i < 12; ++i, y -= 4) + graphics.setPixel(x, y); + + x = start_x + 1; + dq_render_pitch(dq_history[0], x, scroll_pos); x += scroll_pos; + dq_render_pitch(dq_history[1], x, 6); x += 6; + dq_render_pitch(dq_history[2], x, 6); x += 6; + dq_render_pitch(dq_history[3], x, 6); x += 6; + + int32_t octave = dq_render_pitch(dq_history[4], x, 6 - scroll_pos); + graphics.drawBitmap8(start_x + 58, dq_kBottom - octave * 4 - 1, OC::kBitmapLoopMarkerW, OC::bitmap_loop_markers_8 + OC::kBitmapLoopMarkerW); +} + +void DQ_screensaver() { +#ifdef DQ_DEBUG_SCREENSAVER + debug::CycleMeasurement render_cycles; +#endif + + dq_quantizer_channels[0].RenderScreensaver(0); + dq_quantizer_channels[1].RenderScreensaver(64); + +#ifdef DQ_DEBUG_SCREENSAVER + graphics.drawHLine(0, menu::kMenuLineH, menu::kDisplayWidth); + uint32_t us = debug::cycles_to_us(render_cycles.read()); + graphics.setPrintPos(0, 32); + graphics.printf("%u", us); +#endif +} + +#endif // ENABLE_APP_METAQ diff --git a/software/o_c_REV/APP_H1200.ino b/software/o_c_REV/APP_H1200.ino new file mode 100644 index 000000000..3a88a6c43 --- /dev/null +++ b/software/o_c_REV/APP_H1200.ino @@ -0,0 +1,1217 @@ +// Copyright (c) 2015, 2016 Patrick Dowling, Tim Churches +// +// Initial app implementation: Patrick Dowling (pld@gurkenkiste.com) +// Modifications by: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Trigger-driven Neo-Riemannian Tonnetz transformations to generate chords + +#ifdef ENABLE_APP_H1200 + +#include "OC_bitmaps.h" +#include "OC_strings.h" +#include "OC_trigger_delays.h" +#include "tonnetz/tonnetz_state.h" +#include "util/util_settings.h" +#include "util/util_ringbuffer.h" +#include "bjorklund.h" + +// NOTE: H1200 state is updated in the ISR, and we're accessing shared state +// (e.g. outputs) without any sync mechanism. So there is a chance of the +// display being slightly inconsistent but the risk seems acceptable. +// Similarly for changing settings, but this should also be safe(ish) since +// - Each setting should be atomic (int) +// - Changing more than one settings happens seldomly +// - Settings aren't modified in the ISR + +enum OutputMode { + OUTPUT_CHORD_VOICING, + OUTPUT_TUNE, + OUTPUT_MODE_LAST +}; + +enum TransformPriority { + TRANSFORM_PRIO_XPLR, + TRANSFORM_PRIO_XLRP, + TRANSFORM_PRIO_XRPL, + TRANSFORM_PRIO_XPRL, + TRANSFORM_PRIO_XRLP, + TRANSFORM_PRIO_XLPR, + TRANSFORM_PRIO_PLR_LAST +}; + +enum NshTransformPriority { + TRANSFORM_PRIO_XNSH, + TRANSFORM_PRIO_XSHN, + TRANSFORM_PRIO_XHNS, + TRANSFORM_PRIO_XNHS, + TRANSFORM_PRIO_XHSN, + TRANSFORM_PRIO_XSNH, + TRANSFORM_PRIO_NSH_LAST +}; + +enum H1200Setting { + H1200_SETTING_ROOT_OFFSET, + H1200_SETTING_ROOT_OFFSET_CV, + H1200_SETTING_OCTAVE, + H1200_SETTING_OCTAVE_CV, + H1200_SETTING_MODE, + H1200_SETTING_INVERSION, + H1200_SETTING_INVERSION_CV, + H1200_SETTING_PLR_TRANSFORM_PRIO, + H1200_SETTING_PLR_TRANSFORM_PRIO_CV, + H1200_SETTING_NSH_TRANSFORM_PRIO, + H1200_SETTING_NSH_TRANSFORM_PRIO_CV, + H1200_SETTING_CV_SAMPLING, + H1200_SETTING_OUTPUT_MODE, + H1200_SETTING_TRIGGER_DELAY, + H1200_SETTING_TRIGGER_TYPE, + H1200_SETTING_EUCLIDEAN_CV1_MAPPING, + H1200_SETTING_EUCLIDEAN_CV2_MAPPING, + H1200_SETTING_EUCLIDEAN_CV3_MAPPING, + H1200_SETTING_EUCLIDEAN_CV4_MAPPING, + H1200_SETTING_P_EUCLIDEAN_LENGTH, + H1200_SETTING_P_EUCLIDEAN_FILL, + H1200_SETTING_P_EUCLIDEAN_OFFSET, + H1200_SETTING_L_EUCLIDEAN_LENGTH, + H1200_SETTING_L_EUCLIDEAN_FILL, + H1200_SETTING_L_EUCLIDEAN_OFFSET, + H1200_SETTING_R_EUCLIDEAN_LENGTH, + H1200_SETTING_R_EUCLIDEAN_FILL, + H1200_SETTING_R_EUCLIDEAN_OFFSET, + H1200_SETTING_N_EUCLIDEAN_LENGTH, + H1200_SETTING_N_EUCLIDEAN_FILL, + H1200_SETTING_N_EUCLIDEAN_OFFSET, + H1200_SETTING_S_EUCLIDEAN_LENGTH, + H1200_SETTING_S_EUCLIDEAN_FILL, + H1200_SETTING_S_EUCLIDEAN_OFFSET, + H1200_SETTING_H_EUCLIDEAN_LENGTH, + H1200_SETTING_H_EUCLIDEAN_FILL, + H1200_SETTING_H_EUCLIDEAN_OFFSET, + H1200_SETTING_LAST +}; + +enum H1200CvSampling { + H1200_CV_SAMPLING_CONT, + H1200_CV_SAMPLING_TRIG, + H1200_CV_SAMPLING_LAST +}; + +enum H1200CvSource { + H1200_CV_SOURCE_NONE, + H1200_CV_SOURCE_CV1, + H1200_CV_SOURCE_CV2, + H1200_CV_SOURCE_CV3, + H1200_CV_SOURCE_CV4, + H1200_CV_SOURCE_LAST +}; + +enum H1200TriggerTypes { + H1200_TRIGGER_TYPE_PLR, + H1200_TRIGGER_TYPE_NSH, + H1200_TRIGGER_TYPE_EUCLIDEAN, + H1200_TRIGGER_TYPE_LAST +}; + +enum H1200EuclCvMappings { + H1200_EUCL_CV_MAPPING_NONE, + H1200_EUCL_CV_MAPPING_P_EUCLIDEAN_LENGTH, + H1200_EUCL_CV_MAPPING_P_EUCLIDEAN_FILL, + H1200_EUCL_CV_MAPPING_P_EUCLIDEAN_OFFSET, + H1200_EUCL_CV_MAPPING_L_EUCLIDEAN_LENGTH, + H1200_EUCL_CV_MAPPING_L_EUCLIDEAN_FILL, + H1200_EUCL_CV_MAPPING_L_EUCLIDEAN_OFFSET, + H1200_EUCL_CV_MAPPING_R_EUCLIDEAN_LENGTH, + H1200_EUCL_CV_MAPPING_R_EUCLIDEAN_FILL, + H1200_EUCL_CV_MAPPING_R_EUCLIDEAN_OFFSET, + H1200_EUCL_CV_MAPPING_N_EUCLIDEAN_LENGTH, + H1200_EUCL_CV_MAPPING_N_EUCLIDEAN_FILL, + H1200_EUCL_CV_MAPPING_N_EUCLIDEAN_OFFSET, + H1200_EUCL_CV_MAPPING_S_EUCLIDEAN_LENGTH, + H1200_EUCL_CV_MAPPING_S_EUCLIDEAN_FILL, + H1200_EUCL_CV_MAPPING_S_EUCLIDEAN_OFFSET, + H1200_EUCL_CV_MAPPING_H_EUCLIDEAN_LENGTH, + H1200_EUCL_CV_MAPPING_H_EUCLIDEAN_FILL, + H1200_EUCL_CV_MAPPING_H_EUCLIDEAN_OFFSET, + H1200_EUCL_CV_MAPPING_LAST +} ; + +class H1200Settings : public settings::SettingsBase { +public: + + H1200CvSampling get_cv_sampling() const { + return static_cast(values_[H1200_SETTING_CV_SAMPLING]); + } + + int root_offset() const { + return values_[H1200_SETTING_ROOT_OFFSET]; + } + + H1200CvSource get_root_offset_cv_src() const { + return static_cast(values_[H1200_SETTING_ROOT_OFFSET_CV]); + } + + int octave() const { + return values_[H1200_SETTING_OCTAVE]; + } + + H1200CvSource get_octave_cv_src() const { + return static_cast(values_[H1200_SETTING_OCTAVE_CV]); + } + + EMode mode() const { + return static_cast(values_[H1200_SETTING_MODE]); + } + + int inversion() const { + return values_[H1200_SETTING_INVERSION]; + } + + H1200CvSource get_inversion_cv_src() const { + return static_cast(values_[H1200_SETTING_INVERSION_CV]); + } + + uint8_t get_transform_priority() const { + return values_[H1200_SETTING_PLR_TRANSFORM_PRIO]; + } + + H1200CvSource get_transform_priority_cv_src() const { + return static_cast(values_[H1200_SETTING_PLR_TRANSFORM_PRIO_CV]); + } + + uint8_t get_nsh_transform_priority() const { + return values_[H1200_SETTING_NSH_TRANSFORM_PRIO]; + } + + H1200CvSource get_nsh_transform_priority_cv_src() const { + return static_cast(values_[H1200_SETTING_NSH_TRANSFORM_PRIO_CV]); + } + + OutputMode output_mode() const { + return static_cast(values_[H1200_SETTING_OUTPUT_MODE]); + } + + H1200TriggerTypes get_trigger_type() const { + return static_cast(values_[H1200_SETTING_TRIGGER_TYPE]); + } + + uint16_t get_trigger_delay() const { + return values_[H1200_SETTING_TRIGGER_DELAY]; + } + + uint8_t get_euclidean_cv1_mapping() const { + return values_[H1200_SETTING_EUCLIDEAN_CV1_MAPPING]; + } + + uint8_t get_euclidean_cv2_mapping() const { + return values_[H1200_SETTING_EUCLIDEAN_CV2_MAPPING]; + } + + uint8_t get_euclidean_cv3_mapping() const { + return values_[H1200_SETTING_EUCLIDEAN_CV3_MAPPING]; + } + + uint8_t get_euclidean_cv4_mapping() const { + return values_[H1200_SETTING_EUCLIDEAN_CV4_MAPPING]; + } + + uint8_t get_p_euclidean_length() const { + return values_[H1200_SETTING_P_EUCLIDEAN_LENGTH]; + } + + uint8_t get_p_euclidean_fill() const { + return values_[H1200_SETTING_P_EUCLIDEAN_FILL]; + } + + uint8_t get_p_euclidean_offset() const { + return values_[H1200_SETTING_P_EUCLIDEAN_OFFSET]; + } + + uint8_t get_l_euclidean_length() const { + return values_[H1200_SETTING_L_EUCLIDEAN_LENGTH]; + } + + uint8_t get_l_euclidean_fill() const { + return values_[H1200_SETTING_L_EUCLIDEAN_FILL]; + } + + uint8_t get_l_euclidean_offset() const { + return values_[H1200_SETTING_L_EUCLIDEAN_OFFSET]; + } + + uint8_t get_r_euclidean_length() const { + return values_[H1200_SETTING_R_EUCLIDEAN_LENGTH]; + } + + uint8_t get_r_euclidean_fill() const { + return values_[H1200_SETTING_R_EUCLIDEAN_FILL]; + } + + uint8_t get_r_euclidean_offset() const { + return values_[H1200_SETTING_R_EUCLIDEAN_OFFSET]; + } + + uint8_t get_n_euclidean_length() const { + return values_[H1200_SETTING_N_EUCLIDEAN_LENGTH]; + } + + uint8_t get_n_euclidean_fill() const { + return values_[H1200_SETTING_N_EUCLIDEAN_FILL]; + } + + uint8_t get_n_euclidean_offset() const { + return values_[H1200_SETTING_N_EUCLIDEAN_OFFSET]; + } + + uint8_t get_s_euclidean_length() const { + return values_[H1200_SETTING_S_EUCLIDEAN_LENGTH]; + } + + uint8_t get_s_euclidean_fill() const { + return values_[H1200_SETTING_S_EUCLIDEAN_FILL]; + } + + uint8_t get_s_euclidean_offset() const { + return values_[H1200_SETTING_S_EUCLIDEAN_OFFSET]; + } + + uint8_t get_h_euclidean_length() const { + return values_[H1200_SETTING_H_EUCLIDEAN_LENGTH]; + } + + uint8_t get_h_euclidean_fill() const { + return values_[H1200_SETTING_H_EUCLIDEAN_FILL]; + } + + uint8_t get_h_euclidean_offset() const { + return values_[H1200_SETTING_H_EUCLIDEAN_OFFSET]; + } + + + void Init() { + InitDefaults(); + update_enabled_settings(); + manual_mode_change_ = false; + } + + H1200Setting enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + void mode_change(bool yn) { + manual_mode_change_ = yn; + } + + bool mode_manual_change() const { + return manual_mode_change_; + } + + void update_enabled_settings() { + + H1200Setting *settings = enabled_settings_; + + *settings++ = H1200_SETTING_ROOT_OFFSET; + *settings++ = H1200_SETTING_ROOT_OFFSET_CV; + *settings++ = H1200_SETTING_OCTAVE; + *settings++ = H1200_SETTING_OCTAVE_CV; + *settings++ = H1200_SETTING_MODE; + *settings++ = H1200_SETTING_INVERSION; + *settings++ = H1200_SETTING_INVERSION_CV; + *settings++ = H1200_SETTING_PLR_TRANSFORM_PRIO; + *settings++ = H1200_SETTING_PLR_TRANSFORM_PRIO_CV; + *settings++ = H1200_SETTING_NSH_TRANSFORM_PRIO; + *settings++ = H1200_SETTING_NSH_TRANSFORM_PRIO_CV; + *settings++ = H1200_SETTING_CV_SAMPLING; + *settings++ = H1200_SETTING_OUTPUT_MODE; + *settings++ = H1200_SETTING_TRIGGER_DELAY; + *settings++ = H1200_SETTING_TRIGGER_TYPE; + + switch (get_trigger_type()) { + case H1200_TRIGGER_TYPE_EUCLIDEAN: + *settings++ = H1200_SETTING_EUCLIDEAN_CV1_MAPPING; + *settings++ = H1200_SETTING_EUCLIDEAN_CV2_MAPPING; + *settings++ = H1200_SETTING_EUCLIDEAN_CV3_MAPPING; + *settings++ = H1200_SETTING_EUCLIDEAN_CV4_MAPPING; + *settings++ = H1200_SETTING_P_EUCLIDEAN_LENGTH; + *settings++ = H1200_SETTING_P_EUCLIDEAN_FILL; + *settings++ = H1200_SETTING_P_EUCLIDEAN_OFFSET; + *settings++ = H1200_SETTING_L_EUCLIDEAN_LENGTH; + *settings++ = H1200_SETTING_L_EUCLIDEAN_FILL; + *settings++ = H1200_SETTING_L_EUCLIDEAN_OFFSET; + *settings++ = H1200_SETTING_R_EUCLIDEAN_LENGTH; + *settings++ = H1200_SETTING_R_EUCLIDEAN_FILL; + *settings++ = H1200_SETTING_R_EUCLIDEAN_OFFSET; + *settings++ = H1200_SETTING_N_EUCLIDEAN_LENGTH; + *settings++ = H1200_SETTING_N_EUCLIDEAN_FILL; + *settings++ = H1200_SETTING_N_EUCLIDEAN_OFFSET; + *settings++ = H1200_SETTING_S_EUCLIDEAN_LENGTH; + *settings++ = H1200_SETTING_S_EUCLIDEAN_FILL; + *settings++ = H1200_SETTING_S_EUCLIDEAN_OFFSET; + *settings++ = H1200_SETTING_H_EUCLIDEAN_LENGTH; + *settings++ = H1200_SETTING_H_EUCLIDEAN_FILL; + *settings++ = H1200_SETTING_H_EUCLIDEAN_OFFSET; + break; + default: + break; + } + + num_enabled_settings_ = settings - enabled_settings_; + } + +private: + int num_enabled_settings_; + bool manual_mode_change_; + H1200Setting enabled_settings_[H1200_SETTING_LAST]; +}; + +const char * const output_mode_names[] = { + "Chord", + "Tune" +}; + +const char * const plr_trigger_mode_names[] = { + "P>L>R", + "L>R>P", + "R>P>L", + "P>R>L", + "R>L>P", + "L>P>R", +}; + +const char * const nsh_trigger_mode_names[] = { + "N>S>H", + "S>H>N", + "H>N>S", + "N>H>S", + "H>S>N", + "S>N>H", +}; + +const char * const trigger_type_names[] = { + "PLR", + "NSH", + "Eucl", +}; + +const char * const mode_names[] = { + "Maj", "Min" +}; + +const char* const h1200_cv_sampling[2] = { + "Cont", "Trig" +}; + +const char* const h1200_eucl_cv_mappings[] = { + "None", + "Plen", "Pfil", "Poff", + "Llen", "Lfil", "Loff", + "Rlen", "Rfil", "Roff", + "Nlen", "Nfil", "Noff", + "Slen", "Sfil", "Soff", + "Hlen", "Hfil", "Hoff", +}; + +SETTINGS_DECLARE(H1200Settings, H1200_SETTING_LAST) { + {0, -11, 11, "Transpose", NULL, settings::STORAGE_TYPE_I8}, + {H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_LAST-1, "Transpose CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U8}, + #ifdef BUCHLA_4U + {0, 0, 7, "Octave", NULL, settings::STORAGE_TYPE_I8}, + #else + {0, -3, 3, "Octave", NULL, settings::STORAGE_TYPE_I8}, + #endif + {H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_LAST-1, "Octave CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U8}, + {MODE_MAJOR, 0, MODE_LAST-1, "Root mode", mode_names, settings::STORAGE_TYPE_U8}, + {0, -3, 3, "Inversion", NULL, settings::STORAGE_TYPE_I8}, + {H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_LAST-1, "Inversion CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U8}, + {TRANSFORM_PRIO_XPLR, 0, TRANSFORM_PRIO_PLR_LAST-1, "PLR Priority", plr_trigger_mode_names, settings::STORAGE_TYPE_U8}, + {H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_LAST-1, "PLR Prior CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U8}, + {TRANSFORM_PRIO_XNSH, 0, TRANSFORM_PRIO_NSH_LAST-1, "NSH Priority", nsh_trigger_mode_names, settings::STORAGE_TYPE_U8}, + {H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_LAST-1, "NSH Prior CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U8}, + {H1200_CV_SAMPLING_CONT, H1200_CV_SAMPLING_CONT, H1200_CV_SAMPLING_LAST-1, "CV sampling", h1200_cv_sampling, settings::STORAGE_TYPE_U8}, + {OUTPUT_CHORD_VOICING, 0, OUTPUT_MODE_LAST-1, "Output mode", output_mode_names, settings::STORAGE_TYPE_U8}, + { 0, 0, OC::kNumDelayTimes - 1, "Trigger delay", OC::Strings::trigger_delay_times, settings::STORAGE_TYPE_U8 }, + {H1200_TRIGGER_TYPE_PLR, 0, H1200_TRIGGER_TYPE_LAST-1, "Trigger type", trigger_type_names, settings::STORAGE_TYPE_U8}, + {H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_LAST-1, "Eucl CV1 map", h1200_eucl_cv_mappings, settings::STORAGE_TYPE_U8}, + {H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_LAST-1, "Eucl CV2 map", h1200_eucl_cv_mappings, settings::STORAGE_TYPE_U8}, + {H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_LAST-1, "Eucl CV3 map", h1200_eucl_cv_mappings, settings::STORAGE_TYPE_U8}, + {H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_NONE, H1200_EUCL_CV_MAPPING_LAST-1, "Eucl CV4 map", h1200_eucl_cv_mappings, settings::STORAGE_TYPE_U8}, + { 8, 2, 32, " P EuLeng", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 32, " P EuFill", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 31, " P EuOffs", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 2, 32, " L EuLeng", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 32, " L EuFill", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 31, " L EuOffs", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 2, 32, " R EuLeng", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 32, " R EuFill", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 31, " R EuOffs", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 2, 32, " N EuLeng", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 32, " N EuFill", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 31, " N EuOffs", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 2, 32, " S EuLeng", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 32, " S EuFill", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 31, " S EuOffs", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 2, 32, " H EuLeng", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 32, " H EuFill", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 31, " H EuOffs", NULL, settings::STORAGE_TYPE_U8 }, +}; + +static constexpr uint32_t TRIGGER_MASK_TR1 = OC::DIGITAL_INPUT_1_MASK; +static constexpr uint32_t TRIGGER_MASK_P = OC::DIGITAL_INPUT_2_MASK; +static constexpr uint32_t TRIGGER_MASK_L = OC::DIGITAL_INPUT_3_MASK; +static constexpr uint32_t TRIGGER_MASK_R = OC::DIGITAL_INPUT_4_MASK; +static constexpr uint32_t TRIGGER_MASK_N = OC::DIGITAL_INPUT_2_MASK; +static constexpr uint32_t TRIGGER_MASK_S = OC::DIGITAL_INPUT_3_MASK; +static constexpr uint32_t TRIGGER_MASK_H = OC::DIGITAL_INPUT_4_MASK; +static constexpr uint32_t TRIGGER_MASK_DIRTY = 0x10; +static constexpr uint32_t TRIGGER_MASK_RESET = TRIGGER_MASK_TR1 | TRIGGER_MASK_DIRTY; + +namespace H1200 { + enum UserActions { + ACTION_FORCE_UPDATE, + ACTION_MANUAL_RESET + }; + + typedef uint32_t UiAction; +}; + +H1200Settings h1200_settings; + +class H1200State { +public: + static constexpr int kMaxInversion = 3; + + void Init() { + cursor.Init(H1200_SETTING_ROOT_OFFSET, H1200_SETTING_LAST - 1); + display_notes = true; + + quantizer.Init(); + tonnetz_state.init(); + trigger_delays_.Init(); + + euclidean_counter_ = 0; + root_sample_ = false; + root_ = 0; + p_euclidean_length_ = 8; + p_euclidean_fill_ = 0; + p_euclidean_offset_ = 0; + l_euclidean_length_ = 8; + l_euclidean_fill_ = 0; + l_euclidean_offset_ = 0; + r_euclidean_length_ = 8; + r_euclidean_fill_ = 0; + r_euclidean_offset_ = 0; + n_euclidean_length_ = 8; + n_euclidean_fill_ = 0; + n_euclidean_offset_ = 0; + s_euclidean_length_ = 8; + s_euclidean_fill_ = 0; + s_euclidean_offset_ = 0; + h_euclidean_length_ = 8; + h_euclidean_fill_ = 0; + h_euclidean_offset_ = 0; + + } + + void map_euclidean_cv(uint8_t cv_mapping, int channel_cv) { + switch(cv_mapping) { + case H1200_EUCL_CV_MAPPING_P_EUCLIDEAN_LENGTH: + p_euclidean_length_ = p_euclidean_length_ + channel_cv; + CONSTRAIN(p_euclidean_length_, 2, 32); + break; + case H1200_EUCL_CV_MAPPING_P_EUCLIDEAN_FILL: + p_euclidean_fill_ = p_euclidean_fill_ + channel_cv; + CONSTRAIN(p_euclidean_fill_, 0, 32); + break; + case H1200_EUCL_CV_MAPPING_P_EUCLIDEAN_OFFSET: + p_euclidean_offset_ = p_euclidean_offset_ + channel_cv; + CONSTRAIN(p_euclidean_offset_, 0, 31); + break; + case H1200_EUCL_CV_MAPPING_L_EUCLIDEAN_LENGTH: + l_euclidean_length_ = l_euclidean_length_ + channel_cv; + CONSTRAIN(l_euclidean_length_, 2, 32); + break; + case H1200_EUCL_CV_MAPPING_L_EUCLIDEAN_FILL: + l_euclidean_fill_ = l_euclidean_fill_ + channel_cv; + CONSTRAIN(l_euclidean_fill_, 0, 32); + break; + case H1200_EUCL_CV_MAPPING_L_EUCLIDEAN_OFFSET: + l_euclidean_offset_ = l_euclidean_offset_ + channel_cv; + CONSTRAIN(l_euclidean_offset_, 0, 31); + break; + case H1200_EUCL_CV_MAPPING_R_EUCLIDEAN_LENGTH: + r_euclidean_length_ = r_euclidean_length_ + channel_cv; + CONSTRAIN(r_euclidean_length_, 2, 32); + break; + case H1200_EUCL_CV_MAPPING_R_EUCLIDEAN_FILL: + r_euclidean_fill_ = r_euclidean_fill_ + channel_cv; + CONSTRAIN(r_euclidean_fill_, 0, 32); + break; + case H1200_EUCL_CV_MAPPING_R_EUCLIDEAN_OFFSET: + r_euclidean_offset_ = r_euclidean_offset_ + channel_cv; + CONSTRAIN(r_euclidean_offset_, 0, 31); + break; + case H1200_EUCL_CV_MAPPING_N_EUCLIDEAN_LENGTH: + n_euclidean_length_ = n_euclidean_length_ + channel_cv; + CONSTRAIN(n_euclidean_length_, 2, 32); + break; + case H1200_EUCL_CV_MAPPING_N_EUCLIDEAN_FILL: + n_euclidean_fill_ = n_euclidean_fill_ + channel_cv; + CONSTRAIN(n_euclidean_fill_, 0, 32); + break; + case H1200_EUCL_CV_MAPPING_N_EUCLIDEAN_OFFSET: + n_euclidean_offset_ = n_euclidean_offset_ + channel_cv; + CONSTRAIN(n_euclidean_offset_, 0, 31); + break; + case H1200_EUCL_CV_MAPPING_S_EUCLIDEAN_LENGTH: + s_euclidean_length_ = s_euclidean_length_ + channel_cv; + CONSTRAIN(s_euclidean_length_, 2, 32); + break; + case H1200_EUCL_CV_MAPPING_S_EUCLIDEAN_FILL: + s_euclidean_fill_ = s_euclidean_fill_ + channel_cv; + CONSTRAIN(s_euclidean_fill_, 0, 32); + break; + case H1200_EUCL_CV_MAPPING_S_EUCLIDEAN_OFFSET: + s_euclidean_offset_ = s_euclidean_offset_ + channel_cv; + CONSTRAIN(s_euclidean_offset_, 0, 31); + break; + case H1200_EUCL_CV_MAPPING_H_EUCLIDEAN_LENGTH: + h_euclidean_length_ = h_euclidean_length_ + channel_cv; + CONSTRAIN(h_euclidean_length_, 2, 32); + break; + case H1200_EUCL_CV_MAPPING_H_EUCLIDEAN_FILL: + h_euclidean_fill_ = h_euclidean_fill_ + channel_cv; + CONSTRAIN(h_euclidean_fill_, 0, 32); + break; + case H1200_EUCL_CV_MAPPING_H_EUCLIDEAN_OFFSET: + h_euclidean_offset_ = h_euclidean_offset_ + channel_cv; + CONSTRAIN(h_euclidean_offset_, 0, 31); + break; + default: + break; + } + } + + void force_update() { + ui_actions.Write(H1200::ACTION_FORCE_UPDATE); + } + + void manual_reset() { + ui_actions.Write(H1200::ACTION_MANUAL_RESET); + } + + void Render(int32_t root, int inversion, int octave, OutputMode output_mode) { + tonnetz_state.render(root + octave * 12, inversion); + + switch (output_mode) { + case OUTPUT_CHORD_VOICING: { + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(0), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_A)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(1), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_B)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(2), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_C)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(3), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_D)); + } + break; + case OUTPUT_TUNE: { + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(0), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_A)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(0), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_B)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(0), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_C)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(0), 0, OC::DAC::get_voltage_scaling(DAC_CHANNEL_D)); + } + break; + default: break; + } + } + + menu::ScreenCursor cursor; + menu::ScreenCursor cursor_state; + bool display_notes; + + inline int cursor_pos() const { + return cursor.cursor_pos(); + } + + OC::SemitoneQuantizer quantizer; + TonnetzState tonnetz_state; + util::RingBuffer ui_actions; + OC::TriggerDelays trigger_delays_; + uint32_t euclidean_counter_; + bool root_sample_ ; + int32_t root_ ; + uint8_t p_euclidean_length_ ; + uint8_t p_euclidean_fill_ ; + uint8_t p_euclidean_offset_ ; + uint8_t l_euclidean_length_ ; + uint8_t l_euclidean_fill_ ; + uint8_t l_euclidean_offset_ ; + uint8_t r_euclidean_length_ ; + uint8_t r_euclidean_fill_ ; + uint8_t r_euclidean_offset_ ; + uint8_t n_euclidean_length_ ; + uint8_t n_euclidean_fill_ ; + uint8_t n_euclidean_offset_ ; + uint8_t s_euclidean_length_ ; + uint8_t s_euclidean_fill_ ; + uint8_t s_euclidean_offset_ ; + uint8_t h_euclidean_length_ ; + uint8_t h_euclidean_fill_ ; + uint8_t h_euclidean_offset_ ; + +}; + +H1200State h1200_state; + +void FASTRUN H1200_clock(uint32_t triggers) { + + triggers = h1200_state.trigger_delays_.Process(triggers, OC::trigger_delay_ticks[h1200_settings.get_trigger_delay()]); + + // Reset has priority + if (triggers & TRIGGER_MASK_TR1) { + h1200_state.tonnetz_state.reset(h1200_settings.mode()); + h1200_settings.mode_change(false); + } + + // Reset on next trigger = manual change min/maj + if (h1200_settings.mode_manual_change()) { + + if ((triggers & OC::DIGITAL_INPUT_2_MASK) || (triggers & OC::DIGITAL_INPUT_3_MASK) || (triggers & OC::DIGITAL_INPUT_4_MASK)) { + h1200_settings.mode_change(false); + h1200_state.tonnetz_state.reset(h1200_settings.mode()); + } + } + + int32_t root_ = h1200_settings.root_offset(); + int8_t octave_ = h1200_settings.octave(); + int inversion_ = h1200_settings.inversion(); + uint8_t plr_transform_priority_ = h1200_settings.get_transform_priority(); + uint8_t nsh_transform_priority_ = h1200_settings.get_nsh_transform_priority(); + + if (triggers || (h1200_settings.get_cv_sampling() == H1200_CV_SAMPLING_CONT)) { + switch (h1200_settings.get_root_offset_cv_src()) { + case H1200_CV_SOURCE_CV1: + root_ += h1200_state.quantizer.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_1)); + break ; + case H1200_CV_SOURCE_CV2: + root_ += h1200_state.quantizer.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_2)); + break ; + case H1200_CV_SOURCE_CV3: + root_ += h1200_state.quantizer.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_3)); + break ; + case H1200_CV_SOURCE_CV4: + root_ += h1200_state.quantizer.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_4)); + break ; + default: + break; + } + + switch (h1200_settings.get_octave_cv_src()) { + case H1200_CV_SOURCE_CV1: + octave_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV2: + octave_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV3: + octave_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV4: + octave_ += ((OC::ADC::value() + 255) >> 9); + break ; + default: + break; + } + #ifdef BUCHLA_4U + CONSTRAIN(octave_, 0, 7); + #else + CONSTRAIN(octave_, -3, 3); + #endif + + switch (h1200_settings.get_inversion_cv_src()) { + case H1200_CV_SOURCE_CV1: + inversion_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV2: + inversion_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV3: + inversion_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV4: + inversion_ += ((OC::ADC::value() + 255) >> 9); + break ; + default: + break; + } + CONSTRAIN(inversion_,-H1200State::kMaxInversion, H1200State::kMaxInversion); + + switch (h1200_settings.get_transform_priority_cv_src()) { + case H1200_CV_SOURCE_CV1: + plr_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV2: + plr_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV3: + plr_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV4: + plr_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + default: + break; + } + + CONSTRAIN(plr_transform_priority_, TRANSFORM_PRIO_XPLR, TRANSFORM_PRIO_PLR_LAST-1); + + switch (h1200_settings.get_nsh_transform_priority_cv_src()) { + case H1200_CV_SOURCE_CV1: + nsh_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV2: + nsh_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV3: + nsh_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + case H1200_CV_SOURCE_CV4: + nsh_transform_priority_ += ((OC::ADC::value() + 255) >> 9); + break ; + default: + break; + } + + CONSTRAIN(nsh_transform_priority_, TRANSFORM_PRIO_XNSH, TRANSFORM_PRIO_NSH_LAST-1); + } + + if (h1200_settings.get_trigger_type() == H1200_TRIGGER_TYPE_PLR) { + + // Since there can be simultaneous triggers, there is a definable priority. + // Reset always has top priority + + switch (plr_transform_priority_) { + case TRANSFORM_PRIO_XPLR: + if (triggers & TRIGGER_MASK_P) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (triggers & TRIGGER_MASK_L) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (triggers & TRIGGER_MASK_R) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + break; + + case TRANSFORM_PRIO_XLRP: + if (triggers & TRIGGER_MASK_L) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (triggers & TRIGGER_MASK_R) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (triggers & TRIGGER_MASK_P) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + break; + + case TRANSFORM_PRIO_XRPL: + if (triggers & TRIGGER_MASK_R) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (triggers & TRIGGER_MASK_P) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (triggers & TRIGGER_MASK_L) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + break; + + case TRANSFORM_PRIO_XPRL: + if (triggers & TRIGGER_MASK_P) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (triggers & TRIGGER_MASK_R) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (triggers & TRIGGER_MASK_L) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + break; + + case TRANSFORM_PRIO_XRLP: + if (triggers & TRIGGER_MASK_R) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (triggers & TRIGGER_MASK_L) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (triggers & TRIGGER_MASK_P) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + break; + + case TRANSFORM_PRIO_XLPR: + if (triggers & TRIGGER_MASK_L) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (triggers & TRIGGER_MASK_P) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (triggers & TRIGGER_MASK_R) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + break; + + default: break; + } + } else if (h1200_settings.get_trigger_type() == H1200_TRIGGER_TYPE_NSH) { + + // Since there can be simultaneous triggers, there is a definable priority. + // Reset always has top priority + + switch (nsh_transform_priority_) { + case TRANSFORM_PRIO_XNSH: + if (triggers & TRIGGER_MASK_N) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (triggers & TRIGGER_MASK_S) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (triggers & TRIGGER_MASK_H) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + break; + + case TRANSFORM_PRIO_XSHN: + if (triggers & TRIGGER_MASK_S) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (triggers & TRIGGER_MASK_H) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (triggers & TRIGGER_MASK_N) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + break; + + case TRANSFORM_PRIO_XHNS: + if (triggers & TRIGGER_MASK_H) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (triggers & TRIGGER_MASK_N) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (triggers & TRIGGER_MASK_S) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + break; + + case TRANSFORM_PRIO_XNHS: + if (triggers & TRIGGER_MASK_N) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (triggers & TRIGGER_MASK_H) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (triggers & TRIGGER_MASK_S) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + break; + + case TRANSFORM_PRIO_XHSN: + if (triggers & TRIGGER_MASK_H) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (triggers & TRIGGER_MASK_S) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (triggers & TRIGGER_MASK_N) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + break; + + case TRANSFORM_PRIO_XSNH: + if (triggers & TRIGGER_MASK_S) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (triggers & TRIGGER_MASK_N) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (triggers & TRIGGER_MASK_H) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + break; + + default: break; + } + } else { + + if (triggers) { + + h1200_state.p_euclidean_length_ = h1200_settings.get_p_euclidean_length() ; + h1200_state.p_euclidean_fill_ = h1200_settings.get_p_euclidean_fill() ; + h1200_state.p_euclidean_offset_ = h1200_settings.get_p_euclidean_offset() ; + h1200_state.l_euclidean_length_ = h1200_settings.get_l_euclidean_length() ; + h1200_state.l_euclidean_fill_ = h1200_settings.get_l_euclidean_fill() ; + h1200_state.l_euclidean_offset_ = h1200_settings.get_l_euclidean_offset() ; + h1200_state.r_euclidean_length_ = h1200_settings.get_r_euclidean_length() ; + h1200_state.r_euclidean_fill_ = h1200_settings.get_r_euclidean_fill() ; + h1200_state.r_euclidean_offset_ = h1200_settings.get_r_euclidean_offset() ; + h1200_state.n_euclidean_length_ = h1200_settings.get_n_euclidean_length() ; + h1200_state.n_euclidean_fill_ = h1200_settings.get_n_euclidean_fill() ; + h1200_state.n_euclidean_offset_ = h1200_settings.get_n_euclidean_offset() ; + h1200_state.s_euclidean_length_ = h1200_settings.get_s_euclidean_length() ; + h1200_state.s_euclidean_fill_ = h1200_settings.get_s_euclidean_fill() ; + h1200_state.s_euclidean_offset_ = h1200_settings.get_s_euclidean_offset() ; + h1200_state.h_euclidean_length_ = h1200_settings.get_h_euclidean_length() ; + h1200_state.h_euclidean_fill_ = h1200_settings.get_h_euclidean_fill() ; + h1200_state.h_euclidean_offset_ = h1200_settings.get_h_euclidean_offset() ; + + int channel_1_cv_ = ((OC::ADC::value() + 127) >> 8); + int channel_2_cv_ = ((OC::ADC::value() + 127) >> 8); + int channel_3_cv_ = ((OC::ADC::value() + 127) >> 8); + int channel_4_cv_ = ((OC::ADC::value() + 127) >> 8); + + + h1200_state.map_euclidean_cv(h1200_settings.get_euclidean_cv1_mapping(), channel_1_cv_) ; + h1200_state.map_euclidean_cv(h1200_settings.get_euclidean_cv2_mapping(), channel_2_cv_) ; + h1200_state.map_euclidean_cv(h1200_settings.get_euclidean_cv3_mapping(), channel_3_cv_) ; + h1200_state.map_euclidean_cv(h1200_settings.get_euclidean_cv4_mapping(), channel_4_cv_) ; + + ++h1200_state.euclidean_counter_; + + switch (plr_transform_priority_) { + case TRANSFORM_PRIO_XPLR: + if (EuclideanFilter(h1200_state.p_euclidean_length_, h1200_state.p_euclidean_fill_, h1200_state.p_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (EuclideanFilter(h1200_state.l_euclidean_length_, h1200_state.l_euclidean_fill_, h1200_state.l_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (EuclideanFilter(h1200_state.r_euclidean_length_, h1200_state.r_euclidean_fill_, h1200_state.r_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + break; + case TRANSFORM_PRIO_XLRP: + if (EuclideanFilter(h1200_state.l_euclidean_length_, h1200_state.l_euclidean_fill_, h1200_state.l_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (EuclideanFilter(h1200_state.r_euclidean_length_, h1200_state.r_euclidean_fill_, h1200_state.r_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (EuclideanFilter(h1200_state.p_euclidean_length_, h1200_state.p_euclidean_fill_, h1200_state.p_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + break; + case TRANSFORM_PRIO_XRPL: + if (EuclideanFilter(h1200_state.r_euclidean_length_, h1200_state.r_euclidean_fill_, h1200_state.r_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (EuclideanFilter(h1200_state.p_euclidean_length_, h1200_state.p_euclidean_fill_, h1200_state.p_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (EuclideanFilter(h1200_state.l_euclidean_length_, h1200_state.l_euclidean_fill_, h1200_state.l_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + break; + case TRANSFORM_PRIO_XPRL: + if (EuclideanFilter(h1200_state.p_euclidean_length_, h1200_state.p_euclidean_fill_, h1200_state.p_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (EuclideanFilter(h1200_state.r_euclidean_length_, h1200_state.r_euclidean_fill_, h1200_state.r_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (EuclideanFilter(h1200_state.l_euclidean_length_, h1200_state.l_euclidean_fill_, h1200_state.l_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + break; + case TRANSFORM_PRIO_XRLP: + if (EuclideanFilter(h1200_state.r_euclidean_length_, h1200_state.r_euclidean_fill_, h1200_state.r_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + if (EuclideanFilter(h1200_state.l_euclidean_length_, h1200_state.l_euclidean_fill_, h1200_state.l_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (EuclideanFilter(h1200_state.p_euclidean_length_, h1200_state.p_euclidean_fill_, h1200_state.p_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + break; + case TRANSFORM_PRIO_XLPR: + if (EuclideanFilter(h1200_state.l_euclidean_length_, h1200_state.l_euclidean_fill_, h1200_state.l_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_L); + if (EuclideanFilter(h1200_state.p_euclidean_length_, h1200_state.p_euclidean_fill_, h1200_state.p_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_P); + if (EuclideanFilter(h1200_state.r_euclidean_length_, h1200_state.r_euclidean_fill_, h1200_state.r_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_R); + break; + + default: break; + } + + switch (nsh_transform_priority_) { + case TRANSFORM_PRIO_XNSH: + if (EuclideanFilter(h1200_state.n_euclidean_length_, h1200_state.n_euclidean_fill_, h1200_state.n_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (EuclideanFilter(h1200_state.s_euclidean_length_, h1200_state.s_euclidean_fill_, h1200_state.s_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (EuclideanFilter(h1200_state.h_euclidean_length_, h1200_state.h_euclidean_fill_, h1200_state.h_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + break; + case TRANSFORM_PRIO_XSHN: + if (EuclideanFilter(h1200_state.s_euclidean_length_, h1200_state.s_euclidean_fill_, h1200_state.s_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (EuclideanFilter(h1200_state.h_euclidean_length_, h1200_state.h_euclidean_fill_, h1200_state.h_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (EuclideanFilter(h1200_state.n_euclidean_length_, h1200_state.n_euclidean_fill_, h1200_state.n_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + break; + case TRANSFORM_PRIO_XHNS: + if (EuclideanFilter(h1200_state.h_euclidean_length_, h1200_state.h_euclidean_fill_, h1200_state.h_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (EuclideanFilter(h1200_state.n_euclidean_length_, h1200_state.n_euclidean_fill_, h1200_state.n_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (EuclideanFilter(h1200_state.s_euclidean_length_, h1200_state.s_euclidean_fill_, h1200_state.s_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + break; + case TRANSFORM_PRIO_XNHS: + if (EuclideanFilter(h1200_state.n_euclidean_length_, h1200_state.n_euclidean_fill_, h1200_state.n_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (EuclideanFilter(h1200_state.h_euclidean_length_, h1200_state.h_euclidean_fill_, h1200_state.h_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (EuclideanFilter(h1200_state.s_euclidean_length_, h1200_state.s_euclidean_fill_, h1200_state.s_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + break; + case TRANSFORM_PRIO_XHSN: + if (EuclideanFilter(h1200_state.h_euclidean_length_, h1200_state.h_euclidean_fill_, h1200_state.h_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + if (EuclideanFilter(h1200_state.s_euclidean_length_, h1200_state.s_euclidean_fill_, h1200_state.s_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (EuclideanFilter(h1200_state.n_euclidean_length_, h1200_state.n_euclidean_fill_, h1200_state.n_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + break; + case TRANSFORM_PRIO_XSNH: + if (EuclideanFilter(h1200_state.s_euclidean_length_, h1200_state.s_euclidean_fill_, h1200_state.s_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_S); + if (EuclideanFilter(h1200_state.n_euclidean_length_, h1200_state.n_euclidean_fill_, h1200_state.n_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_N); + if (EuclideanFilter(h1200_state.h_euclidean_length_, h1200_state.h_euclidean_fill_, h1200_state.h_euclidean_offset_, h1200_state.euclidean_counter_)) h1200_state.tonnetz_state.apply_transformation(tonnetz::TRANSFORM_H); + break; + + default: break; + } + + } + } + + // Finally, we're ready to actually render the triad transformation! + if (triggers || (h1200_settings.get_cv_sampling() == H1200_CV_SAMPLING_CONT)) h1200_state.Render(root_, inversion_, octave_, h1200_settings.output_mode()); + + if (triggers) + MENU_REDRAW = 1; +} + +void H1200_init() { + h1200_settings.Init(); + h1200_state.Init(); + h1200_settings.update_enabled_settings(); + h1200_state.cursor.AdjustEnd(h1200_settings.num_enabled_settings() - 1); +} + +size_t H1200_storageSize() { + return H1200Settings::storageSize(); +} + +size_t H1200_save(void *storage) { + return h1200_settings.Save(storage); +} + +size_t H1200_restore(const void *storage) { + h1200_settings.update_enabled_settings(); + h1200_state.cursor.AdjustEnd(h1200_settings.num_enabled_settings() - 1); + return h1200_settings.Restore(storage); +} + +void H1200_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + h1200_state.cursor.set_editing(false); + h1200_state.tonnetz_state.reset(h1200_settings.mode()); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + h1200_settings.update_enabled_settings(); + h1200_state.cursor.AdjustEnd(h1200_settings.num_enabled_settings() - 1); + break; + } +} + +void H1200_isr() { + uint32_t triggers = OC::DigitalInputs::clocked(); + + while (h1200_state.ui_actions.readable()) { + switch (h1200_state.ui_actions.Read()) { + case H1200::ACTION_FORCE_UPDATE: + triggers |= TRIGGER_MASK_DIRTY; + break; + case H1200::ACTION_MANUAL_RESET: + triggers |= TRIGGER_MASK_RESET; + break; + default: + break; + } + } + + H1200_clock(triggers); +} + +void H1200_loop() { +} + +void H1200_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + if (h1200_settings.change_value(H1200_SETTING_OCTAVE, 1)) + h1200_state.force_update(); + break; + case OC::CONTROL_BUTTON_DOWN: + if (h1200_settings.change_value(H1200_SETTING_OCTAVE, -1)) + h1200_state.force_update(); + break; + case OC::CONTROL_BUTTON_L: + h1200_state.display_notes = !h1200_state.display_notes; + break; + case OC::CONTROL_BUTTON_R: + h1200_state.cursor.toggle_editing(); + break; + } + } else { + if (OC::CONTROL_BUTTON_L == event.control) { + h1200_settings.InitDefaults(); + h1200_state.manual_reset(); + } + } +} + +void H1200_handleEncoderEvent(const UI::Event &event) { + + if (OC::CONTROL_ENCODER_L == event.control) { + if (h1200_settings.change_value(H1200_SETTING_INVERSION, event.value)) + h1200_state.force_update(); + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (h1200_state.cursor.editing()) { + H1200Setting setting = h1200_settings.enabled_setting_at(h1200_state.cursor_pos()); + + if (h1200_settings.change_value(h1200_state.cursor.cursor_pos(), event.value)) { + if (setting == H1200_SETTING_TRIGGER_TYPE) { + h1200_settings.update_enabled_settings(); + h1200_state.cursor.AdjustEnd(h1200_settings.num_enabled_settings() - 1); + } + h1200_state.force_update(); + + switch(setting) { + + case H1200_SETTING_TRIGGER_TYPE: + h1200_settings.update_enabled_settings(); + h1200_state.cursor.AdjustEnd(h1200_settings.num_enabled_settings() - 1); + // hack/hide extra options when default trigger type is selected + if (h1200_settings.get_trigger_type() != H1200_TRIGGER_TYPE_EUCLIDEAN) + h1200_state.cursor.Scroll(h1200_state.cursor_pos()); + break; + case H1200_SETTING_MODE: + h1200_settings.mode_change(true); + break; + default: + break; + } + + } + } else { + h1200_state.cursor.Scroll(event.value); + } + } +} + +void H1200_menu() { + + /* show mode change instantly, because it's somewhat confusing (inconsistent?) otherwise */ + const EMode current_mode = h1200_settings.mode(); // const EMode current_mode = h1200_state.tonnetz_state.current_chord().mode(); + int outputs[4]; + h1200_state.tonnetz_state.get_outputs(outputs); + + menu::DefaultTitleBar::Draw(); + graphics.print(note_name(outputs[0])); + graphics.movePrintPos(weegfx::Graphics::kFixedFontW, 0); + graphics.print(mode_names[current_mode]); + graphics.movePrintPos(weegfx::Graphics::kFixedFontW, 0); + + if (h1200_state.display_notes) { + for (size_t i=1; i < 4; ++i) { + graphics.movePrintPos(weegfx::Graphics::kFixedFontW, 0); + graphics.print(note_name(outputs[i])); + } + } else { + for (size_t i=1; i < 4; ++i) { + graphics.movePrintPos(weegfx::Graphics::kFixedFontW, 0); + graphics.pretty_print(outputs[i]); + } + } + + menu::SettingsList settings_list(h1200_state.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + + const int setting = h1200_settings.enabled_setting_at(settings_list.Next(list_item)); + const int value = h1200_settings.get_value(setting); + const settings::value_attr &attr = H1200Settings::value_attr(setting); + + list_item.DrawDefault(value, attr); + } +} + +void H1200_screensaver() { + uint8_t y = 0; + static const uint8_t x_col_0 = 66; + static const uint8_t x_col_1 = 66 + 24; + static const uint8_t line_h = 16; + static const weegfx::coord_t note_circle_x = 32; + static const weegfx::coord_t note_circle_y = 32; + + uint32_t history = h1200_state.tonnetz_state.history(); + int outputs[4]; + h1200_state.tonnetz_state.get_outputs(outputs); + + uint8_t normalized[3]; + y = 8; + for (size_t i=0; i < 3; ++i, y += line_h) { + int note = outputs[i + 1]; + int octave = note / 12; + note = (note + 120) % 12; + normalized[i] = note; + + graphics.setPrintPos(x_col_1, y); + graphics.print(OC::Strings::note_names_unpadded[note]); + graphics.print(octave + 1); + } + y = 0; + + size_t len = 4; + while (len--) { + graphics.setPrintPos(x_col_0, y); + graphics.print(history & 0x80 ? '+' : '-'); + graphics.print(tonnetz::transform_names[static_cast(history & 0x7f)]); + y += line_h; + history >>= 8; + } + + OC::visualize_pitch_classes(normalized, note_circle_x, note_circle_y); +} + +#ifdef H1200_DEBUG +void H1200_debug() { + int cv = OC::ADC::value(); + int scaled = ((OC::ADC::value() + 127) >> 8); + + graphics.setPrintPos(2, 12); + graphics.printf("I: %4d %4d", cv, scaled); +} +#endif // H1200_DEBUG + +#endif // ENABLE_APP_H1200 diff --git a/software/o_c_REV/APP_POLYLFO.ino b/software/o_c_REV/APP_POLYLFO.ino new file mode 100644 index 000000000..75c1ab831 --- /dev/null +++ b/software/o_c_REV/APP_POLYLFO.ino @@ -0,0 +1,491 @@ +// Copyright (c) 2016 Patrick Dowling, Tim Churches +// +// Initial app implementation: Patrick Dowling (pld@gurkenkiste.com) +// Modifications by: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Quadrrature LFO app, based on the Mutable Instruments Frames Easter egg +// quadrature wavetable LFO by Olivier Gillet (see frames_poly_lfo.h/cpp) + +#ifdef ENABLE_APP_POLYLFO + +#include "OC_apps.h" +#include "OC_digital_inputs.h" +#include "OC_menus.h" + +#include "util/util_math.h" +#include "util/util_settings.h" +#include "frames_poly_lfo.h" + +enum POLYLFO_SETTINGS { + POLYLFO_SETTING_COARSE, + POLYLFO_SETTING_FINE, + POLYLFO_SETTING_TAP_TEMPO, + POLYLFO_SETTING_SHAPE, + POLYLFO_SETTING_SHAPE_SPREAD, + POLYLFO_SETTING_SPREAD, + POLYLFO_SETTING_COUPLING, + POLYLFO_SETTING_ATTENUATION, + POLYLFO_SETTING_OFFSET, + POLYLFO_SETTING_FREQ_RANGE, + POLYLFO_SETTING_FREQ_DIV_B, + POLYLFO_SETTING_FREQ_DIV_C, + POLYLFO_SETTING_FREQ_DIV_D, + POLYLFO_SETTING_B_XOR_A, + POLYLFO_SETTING_C_XOR_A, + POLYLFO_SETTING_D_XOR_A, + POLYLFO_SETTING_B_AM_BY_A, + POLYLFO_SETTING_C_AM_BY_B, + POLYLFO_SETTING_D_AM_BY_C, + POLYLFO_SETTING_CV4, + POLYLFO_SETTING_TR4_MULT, + POLYLFO_SETTING_LAST +}; + +class PolyLfo : public settings::SettingsBase { +public: + + uint16_t get_coarse() const { + return values_[POLYLFO_SETTING_COARSE]; + } + + int16_t get_fine() const { + return values_[POLYLFO_SETTING_FINE]; + } + + bool get_tap_tempo() const { + return static_cast(values_[POLYLFO_SETTING_TAP_TEMPO]); + } + + uint16_t get_freq_range() const { + return values_[POLYLFO_SETTING_FREQ_RANGE]; + } + + uint16_t get_shape() const { + return values_[POLYLFO_SETTING_SHAPE]; + } + + int16_t get_shape_spread() const { + return values_[POLYLFO_SETTING_SHAPE_SPREAD]; + } + + int16_t get_spread() const { + return values_[POLYLFO_SETTING_SPREAD]; + } + + int16_t get_coupling() const { + return values_[POLYLFO_SETTING_COUPLING]; + } + + uint16_t get_attenuation() const { + return values_[POLYLFO_SETTING_ATTENUATION]; + } + + int16_t get_offset() const { + return values_[POLYLFO_SETTING_OFFSET]; + } + + frames::PolyLfoFreqMultipliers get_freq_div_b() const { + return static_cast(values_[POLYLFO_SETTING_FREQ_DIV_B]); + } + + frames::PolyLfoFreqMultipliers get_freq_div_c() const { + return static_cast(values_[POLYLFO_SETTING_FREQ_DIV_C]); + } + + frames::PolyLfoFreqMultipliers get_freq_div_d() const { + return static_cast(values_[POLYLFO_SETTING_FREQ_DIV_D]); + } + + uint8_t get_b_xor_a() const { + return values_[POLYLFO_SETTING_B_XOR_A]; + } + + uint8_t get_c_xor_a() const { + return values_[POLYLFO_SETTING_C_XOR_A]; + } + + uint8_t get_d_xor_a() const { + return values_[POLYLFO_SETTING_D_XOR_A]; + } + + uint8_t get_b_am_by_a() const { + return values_[POLYLFO_SETTING_B_AM_BY_A]; + } + + uint8_t get_c_am_by_b() const { + return values_[POLYLFO_SETTING_C_AM_BY_B]; + } + + uint8_t get_d_am_by_c() const { + return values_[POLYLFO_SETTING_D_AM_BY_C]; + } + + uint8_t cv4_destination() const { + return values_[POLYLFO_SETTING_CV4]; + } + + uint8_t tr4_multiplier() const { + return values_[POLYLFO_SETTING_TR4_MULT]; + } + + void Init(); + + void freeze() { + frozen_ = true; + } + + void thaw() { + frozen_ = false; + } + + bool frozen() const { + return frozen_; + } + + uint8_t freq_mult() const { + return freq_mult_; + } + + void set_freq_mult(uint8_t freq_mult) { + freq_mult_ = freq_mult; + } + + frames::PolyLfo lfo; + bool frozen_; + uint8_t freq_mult_; + + // ISR update is at 16.666kHz, we don't need it that fast so smooth the values to ~1Khz + static constexpr int32_t kSmoothing = 16; + + SmoothedValue cv_freq; + SmoothedValue cv_shape; + SmoothedValue cv_spread; + SmoothedValue cv_mappable; +}; + +void PolyLfo::Init() { + InitDefaults(); + lfo.Init(); + frozen_= false; + freq_mult_ = 0x3; // == x2 / default +} + +const char* const freq_range_names[12] = { + "cosm", "geol", "glacl", "snail", "sloth", "vlazy", "lazy", "vslow", "slow", "med", "fast", "vfast", +}; + +const char* const freq_div_names[frames::POLYLFO_FREQ_MULT_LAST] = { + "16/1", "15/1", "14/1", "13/1", "12/1", "11/1", "10/1", "9/1", "8/1", "7/1", "6/1", "5/1", "4/1", "3/1", "5/2", "2/1", "5/3", "3/2", "5/4", + "unity", + "4/5", "2/3", "3/5", "1/2", "2/5", "1/3", "1/4", "1/5", "1/6", "1/7", "1/8", "1/9", "1/10", "1/11", "1/12", "1/13", "1/14", "1/15", "1/16" +}; + +const char* const xor_levels[9] = { + "off", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8" +}; + +const char* const cv4_destinations[7] = { + "cplg", "sprd", " rng", "offs", "a->b", "b->c", "c->d" +}; + +const char* const tr4_multiplier[6] = { + "/8", "/4", "/2", "x2", "x4", "x8" +}; + +SETTINGS_DECLARE(PolyLfo, POLYLFO_SETTING_LAST) { + { 64, 0, 255, "C", NULL, settings::STORAGE_TYPE_U8 }, + { 0, -128, 127, "F", NULL, settings::STORAGE_TYPE_I16 }, + { 0, 0, 1, "Tap tempo", OC::Strings::off_on, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Shape", NULL, settings::STORAGE_TYPE_U8 }, + { 0, -128, 127, "Shape spread", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -128, 127, "Phase/frq sprd", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -128, 127, "Coupling", NULL, settings::STORAGE_TYPE_I8 }, + { 230, 0, 230, "Output range", NULL, settings::STORAGE_TYPE_U8 }, + { 0, -128, 127, "Offset", NULL, settings::STORAGE_TYPE_I8 }, + { 9, 0, 11, "Freq range", freq_range_names, settings::STORAGE_TYPE_U8 }, + { frames::POLYLFO_FREQ_MULT_NONE, frames::POLYLFO_FREQ_MULT_BY16, frames::POLYLFO_FREQ_MULT_LAST - 1, "B freq ratio", freq_div_names, settings::STORAGE_TYPE_U8 }, + { frames::POLYLFO_FREQ_MULT_NONE, frames::POLYLFO_FREQ_MULT_BY16, frames::POLYLFO_FREQ_MULT_LAST - 1, "C freq ratio", freq_div_names, settings::STORAGE_TYPE_U8 }, + { frames::POLYLFO_FREQ_MULT_NONE, frames::POLYLFO_FREQ_MULT_BY16, frames::POLYLFO_FREQ_MULT_LAST - 1, "D freq ratio", freq_div_names, settings::STORAGE_TYPE_U8 }, + { 0, 0, 8, "B XOR A", xor_levels, settings::STORAGE_TYPE_U8 }, + { 0, 0, 8, "C XOR A", xor_levels, settings::STORAGE_TYPE_U8 }, + { 0, 0, 8, "D XOR A", xor_levels, settings::STORAGE_TYPE_U8 }, + { 0, 0, 127, "B AM by A", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 127, "C AM by B", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 127, "D AM by C", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 6, "CV4: DEST", cv4_destinations, settings::STORAGE_TYPE_U8 }, + { 3, 0, 5, "TR4: MULT", tr4_multiplier, settings::STORAGE_TYPE_U8 }, + }; + +PolyLfo poly_lfo; +struct { + + POLYLFO_SETTINGS left_edit_mode; + menu::ScreenCursor cursor; + +} poly_lfo_state; + +void FASTRUN POLYLFO_isr() { + + bool reset_phase = OC::DigitalInputs::clocked(); + bool freeze = OC::DigitalInputs::read_immediate(); + bool tempo_sync = OC::DigitalInputs::clocked(); + + poly_lfo.cv_freq.push(OC::ADC::value()); + poly_lfo.cv_shape.push(OC::ADC::value()); + poly_lfo.cv_spread.push(OC::ADC::value()); + poly_lfo.cv_mappable.push(OC::ADC::value()); + + // Range in settings is (0-256] so this gets scaled to (0,65535] + // CV value is 12 bit so also needs scaling + + int32_t freq = SCALE8_16(poly_lfo.get_coarse()) + (poly_lfo.cv_freq.value() * 16) + poly_lfo.get_fine() * 2; + freq = USAT16(freq); + + poly_lfo.lfo.set_freq_range(poly_lfo.get_freq_range()); + + poly_lfo.lfo.set_sync(poly_lfo.get_tap_tempo()); + + int32_t shape = SCALE8_16(poly_lfo.get_shape()) + (poly_lfo.cv_shape.value() * 16); + poly_lfo.lfo.set_shape(USAT16(shape)); + + int32_t spread = SCALE8_16(poly_lfo.get_spread() + 128) + (poly_lfo.cv_spread.value() * 16); + poly_lfo.lfo.set_spread(USAT16(spread)); + + int32_t coupling = 0; + int32_t shape_spread = 0; + int32_t attenuation = 0; + int32_t offset = 0; + int32_t b_am_by_a = 0; + int32_t c_am_by_b = 0; + int32_t d_am_by_c = 0; + + switch (poly_lfo.cv4_destination()) { + case 1: // shape spread: -128, 127 + shape_spread = poly_lfo.cv_mappable.value() << 4; + break; + case 2: // attenuation: 0, 230 + attenuation = poly_lfo.cv_mappable.value() << 4; + break; + case 3: // offset: -128, 127 + offset = poly_lfo.cv_mappable.value() << 4; + break; + case 4: // "a->b", 0-127 + b_am_by_a = (poly_lfo.cv_mappable.value() + 15) >> 5; + break; + case 5: // "b->c", 0-127 + c_am_by_b = (poly_lfo.cv_mappable.value() + 15) >> 5; + break; + case 6: // "c->d", 0-127 + d_am_by_c = (poly_lfo.cv_mappable.value() + 15) >> 5; + break; + case 0: // coupling, -128, 127 + default: + coupling = poly_lfo.cv_mappable.value() << 4; + break; + } + + coupling += SCALE8_16(poly_lfo.get_coupling() + 127); + poly_lfo.lfo.set_coupling(USAT16(coupling)); + + shape_spread += SCALE8_16(poly_lfo.get_shape_spread() + 127); + poly_lfo.lfo.set_shape_spread(USAT16(shape_spread)); + + attenuation += SCALE8_16(poly_lfo.get_attenuation()); + poly_lfo.lfo.set_attenuation(USAT16(attenuation)); + + offset += SCALE8_16(poly_lfo.get_offset()); + poly_lfo.lfo.set_offset(USAT16(offset)); + + poly_lfo.lfo.set_freq_div_b(poly_lfo.get_freq_div_b()); + poly_lfo.lfo.set_freq_div_c(poly_lfo.get_freq_div_c()); + poly_lfo.lfo.set_freq_div_d(poly_lfo.get_freq_div_d()); + + poly_lfo.lfo.set_b_xor_a(poly_lfo.get_b_xor_a()); + poly_lfo.lfo.set_c_xor_a(poly_lfo.get_c_xor_a()); + poly_lfo.lfo.set_d_xor_a(poly_lfo.get_d_xor_a()); + + b_am_by_a += poly_lfo.get_b_am_by_a(); + CONSTRAIN(b_am_by_a, 0, 127); + poly_lfo.lfo.set_b_am_by_a(b_am_by_a); + + c_am_by_b += poly_lfo.get_c_am_by_b(); + CONSTRAIN(c_am_by_b, 0, 127); + poly_lfo.lfo.set_c_am_by_b(c_am_by_b); + + d_am_by_c += poly_lfo.get_d_am_by_c(); + CONSTRAIN(d_am_by_c, 0, 127); + poly_lfo.lfo.set_d_am_by_c(d_am_by_c); + + // div/multiply frequency if TR4 / gate high + int8_t freq_mult = digitalReadFast(TR4) ? 0xFF : poly_lfo.tr4_multiplier(); + poly_lfo.set_freq_mult(freq_mult); + + if (!freeze && !poly_lfo.frozen()) + poly_lfo.lfo.Render(freq, reset_phase, tempo_sync, freq_mult); + + OC::DAC::set(poly_lfo.lfo.dac_code(0)); + OC::DAC::set(poly_lfo.lfo.dac_code(1)); + OC::DAC::set(poly_lfo.lfo.dac_code(2)); + OC::DAC::set(poly_lfo.lfo.dac_code(3)); +} + +void POLYLFO_init() { + + poly_lfo_state.left_edit_mode = POLYLFO_SETTING_COARSE; + poly_lfo_state.cursor.Init(POLYLFO_SETTING_TAP_TEMPO, POLYLFO_SETTING_LAST - 1); + poly_lfo.Init(); +} + +size_t POLYLFO_storageSize() { + return PolyLfo::storageSize(); +} + +size_t POLYLFO_save(void *storage) { + return poly_lfo.Save(storage); +} + +size_t POLYLFO_restore(const void *storage) { + return poly_lfo.Restore(storage); +} + +void POLYLFO_loop() { +} + +static const size_t kSmallPreviewBufferSize = 32; +uint16_t preview_buffer[kSmallPreviewBufferSize]; + +void POLYLFO_menu() { + + menu::DefaultTitleBar::Draw(); + if (poly_lfo.get_tap_tempo()) { + graphics.print("(T) Ch A: tap tempo") ; + } else { + float menu_freq_ = poly_lfo.lfo.get_freq_ch1(); + if (poly_lfo.freq_mult() < 0xFF) + graphics.drawBitmap8(122, menu::DefaultTitleBar::kTextY, 4, OC::bitmap_indicator_4x8); + if (menu_freq_ >= 0.1f) { + graphics.printf("(%s) Ch A: %6.2f Hz", PolyLfo::value_attr(poly_lfo_state.left_edit_mode).name, menu_freq_); + } else { + graphics.printf("(%s) Ch A: %6.3fs", PolyLfo::value_attr(poly_lfo_state.left_edit_mode).name, 1.0f / menu_freq_); + } + } + menu::SettingsList settings_list(poly_lfo_state.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int current = settings_list.Next(list_item); + const int value = poly_lfo.get_value(current); + if (POLYLFO_SETTING_SHAPE != current) { + list_item.DrawDefault(value, PolyLfo::value_attr(current)); + } else { + poly_lfo.lfo.RenderPreview(value << 8, preview_buffer, kSmallPreviewBufferSize); + const uint16_t *preview = preview_buffer; + uint16_t count = kSmallPreviewBufferSize; + weegfx::coord_t x = list_item.valuex; + while (count--) + graphics.setPixel(x++, list_item.y + 8 - (*preview++ >> 13)); + + list_item.endx = menu::kDefaultMenuEndX - 39; + list_item.DrawDefault(value, PolyLfo::value_attr(current)); + } + } +} + +void POLYLFO_screensaver() { + OC::scope_render(); +} + +void POLYLFO_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + poly_lfo_state.cursor.set_editing(false); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void POLYLFO_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + if (!poly_lfo.get_tap_tempo()) poly_lfo.change_value(POLYLFO_SETTING_COARSE, 32); + break; + case OC::CONTROL_BUTTON_DOWN: + if (!poly_lfo.get_tap_tempo()) poly_lfo.change_value(POLYLFO_SETTING_COARSE, -32); + break; + case OC::CONTROL_BUTTON_L: + if (!poly_lfo.get_tap_tempo()) { + if (POLYLFO_SETTING_COARSE == poly_lfo_state.left_edit_mode) + poly_lfo_state.left_edit_mode = POLYLFO_SETTING_FINE; + else + poly_lfo_state.left_edit_mode = POLYLFO_SETTING_COARSE; + } + break; + case OC::CONTROL_BUTTON_R: + poly_lfo_state.cursor.toggle_editing(); + break; + } + } + + if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_DOWN: + poly_lfo.lfo.set_phase_reset_flag(true); + break; + default: + break; + } + } + +} + + +void POLYLFO_handleEncoderEvent(const UI::Event &event) { + if (OC::CONTROL_ENCODER_L == event.control) { + if (!poly_lfo.get_tap_tempo()) poly_lfo.change_value(poly_lfo_state.left_edit_mode, event.value); + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (poly_lfo_state.cursor.editing()) { + poly_lfo.change_value(poly_lfo_state.cursor.cursor_pos(), event.value); + } else { + poly_lfo_state.cursor.Scroll(event.value); + } + } +} + +#ifdef POLYLFO_DEBUG +void POLYLFO_debug() { + graphics.setPrintPos(2, 12); + graphics.print(poly_lfo.cv_shape.value()); + graphics.print(" "); + int32_t value = SCALE8_16(poly_lfo.get_shape()); + graphics.print(value); + graphics.print(" "); + graphics.print(poly_lfo.cv_shape.value() * 16); + value += poly_lfo.cv_shape.value() * 16; + graphics.setPrintPos(2, 22); + graphics.print(value); graphics.print(" "); + value = USAT16(value); +} +#endif // POLYLFO_DEBUG + +#endif // ENABLE_APP_POLYLFO diff --git a/software/o_c_REV/APP_QQ.ino b/software/o_c_REV/APP_QQ.ino new file mode 100644 index 000000000..56375237b --- /dev/null +++ b/software/o_c_REV/APP_QQ.ino @@ -0,0 +1,1584 @@ +// Copyright (c) 2015, 2016 Patrick Dowling, Tim Churches +// +// Initial app implementation: Patrick Dowling (pld@gurkenkiste.com) +// Modifications by: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Quad quantizer app, based around the the quantizer/scales implementation from +// from Braids by Olivier Gillet (see braids_quantizer.h/cc et al.). It has since +// grown a little bit... + +#ifdef ENABLE_APP_QUANTERMAIN + +#include "OC_apps.h" +#include "util/util_logistic_map.h" +#include "util/util_settings.h" +#include "util/util_trigger_delay.h" +#include "util/util_turing.h" +#include "util/util_integer_sequences.h" +#include "peaks_bytebeat.h" +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_menus.h" +#include "OC_scales.h" +#include "OC_scale_edit.h" +#include "OC_strings.h" + +namespace menu = OC::menu; + +// unsigned long LAST_REDRAW_TIME = 0; +extern uint_fast8_t MENU_REDRAW; +// OC::UiMode ui_mode = OC::UI_MODE_MENU; + +#ifdef BUCHLA_4U + #define QQ_OFFSET_X 20 +#else + #define QQ_OFFSET_X 31 +#endif + +void QQ_downButtonLong(); +void QQ_topButton(); +void QQ_lowerButton(); +void QQ_leftButton(); +void QQ_rightButton(); +void QQ_leftButtonLong(); + +enum ChannelSetting { + CHANNEL_SETTING_SCALE, + CHANNEL_SETTING_ROOT, + CHANNEL_SETTING_MASK, + CHANNEL_SETTING_SOURCE, + CHANNEL_SETTING_AUX_SOURCE_DEST, + CHANNEL_SETTING_TRIGGER, + CHANNEL_SETTING_CLKDIV, + CHANNEL_SETTING_DELAY, + CHANNEL_SETTING_TRANSPOSE, + CHANNEL_SETTING_OCTAVE, + CHANNEL_SETTING_FINE, + CHANNEL_SETTING_TURING_LENGTH, + CHANNEL_SETTING_TURING_PROB, + CHANNEL_SETTING_TURING_MODULUS, + CHANNEL_SETTING_TURING_RANGE, + CHANNEL_SETTING_TURING_PROB_CV_SOURCE, + CHANNEL_SETTING_TURING_MODULUS_CV_SOURCE, + CHANNEL_SETTING_TURING_RANGE_CV_SOURCE, + CHANNEL_SETTING_LOGISTIC_MAP_R, + CHANNEL_SETTING_LOGISTIC_MAP_RANGE, + CHANNEL_SETTING_LOGISTIC_MAP_R_CV_SOURCE, + CHANNEL_SETTING_LOGISTIC_MAP_RANGE_CV_SOURCE, + CHANNEL_SETTING_BYTEBEAT_EQUATION, + CHANNEL_SETTING_BYTEBEAT_RANGE, + CHANNEL_SETTING_BYTEBEAT_P0, + CHANNEL_SETTING_BYTEBEAT_P1, + CHANNEL_SETTING_BYTEBEAT_P2, + CHANNEL_SETTING_BYTEBEAT_EQUATION_CV_SOURCE, + CHANNEL_SETTING_BYTEBEAT_RANGE_CV_SOURCE, + CHANNEL_SETTING_BYTEBEAT_P0_CV_SOURCE, + CHANNEL_SETTING_BYTEBEAT_P1_CV_SOURCE, + CHANNEL_SETTING_BYTEBEAT_P2_CV_SOURCE, + CHANNEL_SETTING_INT_SEQ_INDEX, + CHANNEL_SETTING_INT_SEQ_MODULUS, + CHANNEL_SETTING_INT_SEQ_RANGE, + CHANNEL_SETTING_INT_SEQ_DIRECTION, + CHANNEL_SETTING_INT_SEQ_BROWNIAN_PROB, + CHANNEL_SETTING_INT_SEQ_LOOP_START, + CHANNEL_SETTING_INT_SEQ_LOOP_LENGTH, + CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_PROB, + CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_RANGE, + CHANNEL_SETTING_INT_SEQ_STRIDE, + CHANNEL_SETTING_INT_SEQ_INDEX_CV_SOURCE, + CHANNEL_SETTING_INT_SEQ_MODULUS_CV_SOURCE, + CHANNEL_SETTING_INT_SEQ_RANGE_CV_SOURCE, + CHANNEL_SETTING_INT_SEQ_STRIDE_CV_SOURCE, + CHANNEL_SETTING_INT_SEQ_RESET_TRIGGER, + CHANNEL_SETTING_LAST +}; + +enum ChannelTriggerSource { + CHANNEL_TRIGGER_TR1, + CHANNEL_TRIGGER_TR2, + CHANNEL_TRIGGER_TR3, + CHANNEL_TRIGGER_TR4, + CHANNEL_TRIGGER_CONTINUOUS_UP, + CHANNEL_TRIGGER_CONTINUOUS_DOWN, + CHANNEL_TRIGGER_LAST +}; + +enum ChannelSource { + CHANNEL_SOURCE_CV1, + CHANNEL_SOURCE_CV2, + CHANNEL_SOURCE_CV3, + CHANNEL_SOURCE_CV4, + CHANNEL_SOURCE_TURING, + CHANNEL_SOURCE_LOGISTIC_MAP, + CHANNEL_SOURCE_BYTEBEAT, + CHANNEL_SOURCE_INT_SEQ, + CHANNEL_SOURCE_LAST +}; + +enum QQ_CV_DEST { + QQ_DEST_NONE, + QQ_DEST_ROOT, + QQ_DEST_OCTAVE, + QQ_DEST_TRANSPOSE, + QQ_DEST_MASK, + QQ_DEST_LAST +}; + +class QuantizerChannel : public settings::SettingsBase { +public: + + int get_scale(uint8_t dummy) const { + return values_[CHANNEL_SETTING_SCALE]; + } + + void set_scale(int scale) { + if (scale != get_scale(DUMMY)) { + const OC::Scale &scale_def = OC::Scales::GetScale(scale); + uint16_t mask = get_mask(); + if (0 == (mask & ~(0xffff << scale_def.num_notes))) + mask |= 0x1; + apply_value(CHANNEL_SETTING_MASK, mask); + apply_value(CHANNEL_SETTING_SCALE, scale); + } + } + + // dummy + int get_scale_select() const { + return 0; + } + + // dummy + void set_scale_at_slot(int scale, uint16_t mask, int root, int transpose, uint8_t scale_slot) { + + } + + // dummy + int get_transpose(uint8_t DUMMY) const { + return 0; + } + + int get_root() const { + return values_[CHANNEL_SETTING_ROOT]; + } + + int get_root(uint8_t DUMMY) const { + return 0x0; + } + + uint16_t get_mask() const { + return values_[CHANNEL_SETTING_MASK]; + } + + uint16_t get_rotated_scale_mask() const { + return last_mask_; + } + + ChannelSource get_source() const { + return static_cast(values_[CHANNEL_SETTING_SOURCE]); + } + + ChannelTriggerSource get_trigger_source() const { + return static_cast(values_[CHANNEL_SETTING_TRIGGER]); + } + + uint8_t get_channel_index() const { + return channel_index_; + } + + uint8_t get_clkdiv() const { + return values_[CHANNEL_SETTING_CLKDIV]; + } + + uint16_t get_trigger_delay() const { + return values_[CHANNEL_SETTING_DELAY]; + } + + int get_transpose() const { + return values_[CHANNEL_SETTING_TRANSPOSE]; + } + + int get_octave() const { + return values_[CHANNEL_SETTING_OCTAVE]; + } + + int get_fine() const { + return values_[CHANNEL_SETTING_FINE]; + } + + uint8_t get_aux_cv_dest() const { + return values_[CHANNEL_SETTING_AUX_SOURCE_DEST]; + } + + uint8_t get_turing_length() const { + return values_[CHANNEL_SETTING_TURING_LENGTH]; + } + + uint8_t get_turing_prob() const { + return values_[CHANNEL_SETTING_TURING_PROB]; + } + + uint8_t get_turing_modulus() const { + return values_[CHANNEL_SETTING_TURING_MODULUS]; + } + + uint8_t get_turing_range() const { + return values_[CHANNEL_SETTING_TURING_RANGE]; + } + + uint8_t get_turing_prob_cv_source() const { + return values_[CHANNEL_SETTING_TURING_PROB_CV_SOURCE]; + } + + uint8_t get_turing_modulus_cv_source() const { + return values_[CHANNEL_SETTING_TURING_MODULUS_CV_SOURCE]; + } + + uint8_t get_turing_range_cv_source() const { + return values_[CHANNEL_SETTING_TURING_RANGE_CV_SOURCE]; + } + + uint8_t get_logistic_map_r() const { + return values_[CHANNEL_SETTING_LOGISTIC_MAP_R]; + } + + uint8_t get_logistic_map_range() const { + return values_[CHANNEL_SETTING_LOGISTIC_MAP_RANGE]; + } + + uint8_t get_logistic_map_r_cv_source() const { + return values_[CHANNEL_SETTING_LOGISTIC_MAP_R_CV_SOURCE]; + } + + uint8_t get_logistic_map_range_cv_source() const { + return values_[CHANNEL_SETTING_LOGISTIC_MAP_RANGE_CV_SOURCE]; + } + + uint8_t get_bytebeat_equation() const { + return values_[CHANNEL_SETTING_BYTEBEAT_EQUATION]; + } + + uint8_t get_bytebeat_range() const { + return values_[CHANNEL_SETTING_BYTEBEAT_RANGE]; + } + + uint8_t get_bytebeat_p0() const { + return values_[CHANNEL_SETTING_BYTEBEAT_P0]; + } + + uint8_t get_bytebeat_p1() const { + return values_[CHANNEL_SETTING_BYTEBEAT_P1]; + } + + uint8_t get_bytebeat_p2() const { + return values_[CHANNEL_SETTING_BYTEBEAT_P2]; + } + + uint8_t get_bytebeat_equation_cv_source() const { + return values_[CHANNEL_SETTING_BYTEBEAT_EQUATION_CV_SOURCE]; + } + + uint8_t get_bytebeat_range_cv_source() const { + return values_[CHANNEL_SETTING_BYTEBEAT_RANGE_CV_SOURCE]; + } + + uint8_t get_bytebeat_p0_cv_source() const { + return values_[CHANNEL_SETTING_BYTEBEAT_P0_CV_SOURCE]; + } + + uint8_t get_bytebeat_p1_cv_source() const { + return values_[CHANNEL_SETTING_BYTEBEAT_P1_CV_SOURCE]; + } + + uint8_t get_bytebeat_p2_cv_source() const { + return values_[CHANNEL_SETTING_BYTEBEAT_P2_CV_SOURCE]; + } + + uint8_t get_int_seq_index() const { + return values_[CHANNEL_SETTING_INT_SEQ_INDEX]; + } + + uint8_t get_int_seq_modulus() const { + return values_[CHANNEL_SETTING_INT_SEQ_MODULUS]; + } + + uint8_t get_int_seq_range() const { + return values_[CHANNEL_SETTING_INT_SEQ_RANGE]; + } + + int16_t get_int_seq_start() const { + return static_cast(values_[CHANNEL_SETTING_INT_SEQ_LOOP_START]); + } + + void set_int_seq_start(uint8_t start_pos) { + values_[CHANNEL_SETTING_INT_SEQ_LOOP_START] = start_pos; + } + + int16_t get_int_seq_length() const { + return static_cast(values_[CHANNEL_SETTING_INT_SEQ_LOOP_LENGTH] - 1); + } + + bool get_int_seq_dir() const { + return static_cast(values_[CHANNEL_SETTING_INT_SEQ_DIRECTION]); + } + + int16_t get_int_seq_brownian_prob() const { + return static_cast(values_[CHANNEL_SETTING_INT_SEQ_BROWNIAN_PROB]); + } + + uint8_t get_int_seq_index_cv_source() const { + return values_[CHANNEL_SETTING_INT_SEQ_INDEX_CV_SOURCE]; + } + + uint8_t get_int_seq_modulus_cv_source() const { + return values_[CHANNEL_SETTING_INT_SEQ_MODULUS_CV_SOURCE]; + } + + uint8_t get_int_seq_range_cv_source() const { + return values_[CHANNEL_SETTING_INT_SEQ_RANGE_CV_SOURCE]; + } + + uint8_t get_int_seq_frame_shift_prob() const { + return values_[CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_PROB]; + } + + uint8_t get_int_seq_frame_shift_range() const { + return values_[CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_RANGE]; + } + + uint8_t get_int_seq_stride() const { + return values_[CHANNEL_SETTING_INT_SEQ_STRIDE]; + } + + uint8_t get_int_seq_stride_cv_source() const { + return values_[CHANNEL_SETTING_INT_SEQ_STRIDE_CV_SOURCE]; + } + + ChannelTriggerSource get_int_seq_reset_trigger_source() const { + return static_cast(values_[CHANNEL_SETTING_INT_SEQ_RESET_TRIGGER]); + } + + void clear_dest() { + // ... + schedule_mask_rotate_ = 0x0; + continuous_offset_ = 0x0; + prev_transpose_cv_ = 0x0; + prev_transpose_cv_ = 0x0; + prev_root_cv_ = 0x0; + } + + void Init(ChannelSource source, ChannelTriggerSource trigger_source) { + InitDefaults(); + apply_value(CHANNEL_SETTING_SOURCE, source); + apply_value(CHANNEL_SETTING_TRIGGER, trigger_source); + + channel_index_ = source; + force_update_ = true; + instant_update_ = false; + last_scale_ = -1; + last_mask_ = 0; + last_sample_ = 0; + clock_ = 0; + int_seq_reset_ = false; + continuous_offset_ = false; + schedule_mask_rotate_ = false; + prev_octave_cv_ = 0; + prev_transpose_cv_ = 0; + prev_root_cv_ = 0; + prev_destination_ = 0; + + trigger_delay_.Init(); + turing_machine_.Init(); + logistic_map_.Init(); + bytebeat_.Init(); + int_seq_.Init(get_int_seq_start(), get_int_seq_length()); + quantizer_.Init(); + update_scale(true, false); + trigger_display_.Init(); + update_enabled_settings(); + + scrolling_history_.Init(OC::DAC::kOctaveZero * 12 << 7); + } + + void force_update() { + force_update_ = true; + } + + void instant_update() { + instant_update_ = (~instant_update_) & 1u; + } + + inline void Update(uint32_t triggers, DAC_CHANNEL dac_channel) { + + uint8_t index = channel_index_; + + ChannelSource source = get_source(); + ChannelTriggerSource trigger_source = get_trigger_source(); + bool continuous = CHANNEL_TRIGGER_CONTINUOUS_UP == trigger_source || CHANNEL_TRIGGER_CONTINUOUS_DOWN == trigger_source; + bool triggered = !continuous && + (triggers & DIGITAL_INPUT_MASK(trigger_source - CHANNEL_TRIGGER_TR1)); + + if (source == CHANNEL_SOURCE_INT_SEQ) { + ChannelTriggerSource int_seq_reset_trigger_source = get_int_seq_reset_trigger_source() ; + int_seq_reset_ = (triggers & DIGITAL_INPUT_MASK(int_seq_reset_trigger_source - 1)); + } + + trigger_delay_.Update(); + if (triggered) + trigger_delay_.Push(OC::trigger_delay_ticks[get_trigger_delay()]); + triggered = trigger_delay_.triggered(); + + if (triggered) { + ++clock_; + if (clock_ >= get_clkdiv()) { + clock_ = 0; + } else { + triggered = false; + } + } + + bool update = continuous || triggered; + + if (update) + update_scale(force_update_, schedule_mask_rotate_); + + int32_t sample = last_sample_; + int32_t temp_sample = 0; + int32_t history_sample = 0; + + + switch (source) { + case CHANNEL_SOURCE_TURING: { + // this doesn't make sense when continuously quantizing; should be hidden via the menu ... + if (continuous) + break; + + turing_machine_.set_length(get_turing_length()); + int32_t probability = get_turing_prob(); + if (get_turing_prob_cv_source()) { + probability += (OC::ADC::value(static_cast(get_turing_prob_cv_source() - 1)) + 7) >> 4; + CONSTRAIN(probability, 0, 255); + } + turing_machine_.set_probability(probability); + if (triggered) { + uint32_t shift_register = turing_machine_.Clock(); + uint8_t range = get_turing_range(); + if (get_turing_range_cv_source()) { + range += (OC::ADC::value(static_cast(get_turing_range_cv_source() - 1)) + 15) >> 5; + CONSTRAIN(range, 1, 120); + } + + if (quantizer_.enabled()) { + + uint8_t modulus = get_turing_modulus(); + if (get_turing_modulus_cv_source()) { + modulus += (OC::ADC::value(static_cast(get_turing_modulus_cv_source() - 1)) + 15) >> 5; + CONSTRAIN(modulus, 2, 121); + } + + // Since our range is limited anyway, just grab the last byte for lengths > 8, + // otherwise scale to use bits. And apply the modulus + uint32_t shift = turing_machine_.length(); + uint32_t scaled = (shift_register & 0xff) * range; + scaled = (scaled >> (shift > 7 ? 8 : shift)) % modulus; + + // The quantizer uses a lookup codebook with 128 entries centered + // about 0, so we use the range/scaled output to lookup a note + // directly instead of changing to pitch first. + int32_t pitch = + quantizer_.Lookup(64 + range / 2 - scaled + get_transpose()) + (get_root() << 7); + sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, pitch, get_octave(), OC::DAC::get_voltage_scaling(dac_channel)); + history_sample = pitch + ((OC::DAC::kOctaveZero + get_octave()) * 12 << 7); + } else { + // Scale range by 128, so 12 steps = 1V + // We dont' need a calibrated value here, really. + uint32_t scaled = multiply_u32xu32_rshift(range << 7, shift_register, get_turing_length()); + scaled += get_transpose() << 7; + sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, scaled, get_octave(), OC::DAC::get_voltage_scaling(dac_channel)); + history_sample = scaled + ((OC::DAC::kOctaveZero + get_octave()) * 12 << 7); + } + } + } + break; + case CHANNEL_SOURCE_BYTEBEAT: { + // this doesn't make sense when continuously quantizing; should be hidden via the menu ... + if (continuous) + break; + + int32_t bytebeat_eqn = get_bytebeat_equation() << 12; + if (get_bytebeat_equation_cv_source()) { + bytebeat_eqn += (OC::ADC::value(static_cast(get_bytebeat_equation_cv_source() - 1)) << 4); + bytebeat_eqn = USAT16(bytebeat_eqn); + } + bytebeat_.set_equation(bytebeat_eqn); + + int32_t bytebeat_p0 = get_bytebeat_p0() << 8; + if (get_bytebeat_p0_cv_source()) { + bytebeat_p0 += (OC::ADC::value(static_cast(get_bytebeat_p0_cv_source() - 1)) << 4); + bytebeat_p0 = USAT16(bytebeat_p0); + } + bytebeat_.set_p0(bytebeat_p0); + + int32_t bytebeat_p1 = get_bytebeat_p1() << 8; + if (get_bytebeat_p1_cv_source()) { + bytebeat_p1 += (OC::ADC::value(static_cast(get_bytebeat_p1_cv_source() - 1)) << 4); + bytebeat_p1 = USAT16(bytebeat_p1); + } + bytebeat_.set_p1(bytebeat_p1); + + int32_t bytebeat_p2 = get_bytebeat_p2() << 8; + if (get_bytebeat_p2_cv_source()) { + bytebeat_p2 += (OC::ADC::value(static_cast(get_bytebeat_p2_cv_source() - 1)) << 4); + bytebeat_p2 = USAT16(bytebeat_p2); + } + bytebeat_.set_p2(bytebeat_p2); + + if (triggered) { + uint32_t bb = bytebeat_.Clock(); + uint8_t range = get_bytebeat_range(); + if (get_bytebeat_range_cv_source()) { + range += (OC::ADC::value(static_cast(get_bytebeat_range_cv_source() - 1)) + 15) >> 5; + CONSTRAIN(range, 1, 120); + } + + if (quantizer_.enabled()) { + + // Since our range is limited anyway, just grab the last byte + uint32_t scaled = ((bb >> 8) * range) >> 8; + + // The quantizer uses a lookup codebook with 128 entries centered + // about 0, so we use the range/scaled output to lookup a note + // directly instead of changing to pitch first. + int32_t pitch = + quantizer_.Lookup(64 + range / 2 - scaled + get_transpose()) + (get_root() << 7); + sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, pitch, get_octave(), OC::DAC::get_voltage_scaling(dac_channel)); + history_sample = pitch + ((OC::DAC::kOctaveZero + get_octave()) * 12 << 7); + } else { + // We dont' need a calibrated value here, really + int octave = get_octave(); + CONSTRAIN(octave, 0, 6); + sample = OC::DAC::get_octave_offset(dac_channel, octave) + (get_transpose() << 7); + // range is actually 120 (10 oct) but 65535 / 128 is close enough + sample += multiply_u32xu32_rshift32((static_cast(range) * 65535U) >> 7, bb << 16); + sample = USAT16(sample); + history_sample = sample; + } + } + } + break; + case CHANNEL_SOURCE_LOGISTIC_MAP: { + // this doesn't make sense when continuously quantizing; should be hidden via the menu ... + if (continuous) + break; + + logistic_map_.set_seed(123); + int32_t logistic_map_r = get_logistic_map_r(); + if (get_logistic_map_r_cv_source()) { + logistic_map_r += (OC::ADC::value(static_cast(get_logistic_map_r_cv_source() - 1)) + 7) >> 4; + CONSTRAIN(logistic_map_r, 0, 255); + } + logistic_map_.set_r(logistic_map_r); + if (triggered) { + int64_t logistic_map_x = logistic_map_.Clock(); + uint8_t range = get_logistic_map_range(); + if (get_logistic_map_range_cv_source()) { + range += (OC::ADC::value(static_cast(get_logistic_map_range_cv_source() - 1)) + 15) >> 5; + CONSTRAIN(range, 1, 120); + } + + if (quantizer_.enabled()) { + uint32_t logistic_scaled = (logistic_map_x * range) >> 24; + + // See above, may need tweaking + int32_t pitch = + quantizer_.Lookup(64 + range / 2 - logistic_scaled + get_transpose()) + (get_root() << 7); + sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, pitch, get_octave(), OC::DAC::get_voltage_scaling(dac_channel)); + history_sample = pitch + ((OC::DAC::kOctaveZero + get_octave()) * 12 << 7); + } else { + int octave = get_octave(); + CONSTRAIN(octave, 0, 6); + sample = OC::DAC::get_octave_offset(dac_channel, octave) + (get_transpose() << 7); + sample += multiply_u32xu32_rshift24((static_cast(range) * 65535U) >> 7, logistic_map_x); + sample = USAT16(sample); + history_sample = sample; + } + } + } + break; + case CHANNEL_SOURCE_INT_SEQ: { + // this doesn't make sense when continuously quantizing; should be hidden via the menu ... + if (continuous) + break; + + int_seq_.set_loop_direction(get_int_seq_dir()); + int_seq_.set_brownian_prob(get_int_seq_brownian_prob()); + int16_t int_seq_index = get_int_seq_index(); + int16_t int_seq_stride = get_int_seq_stride(); + + if (get_int_seq_index_cv_source()) { + int_seq_index += (OC::ADC::value(static_cast(get_int_seq_index_cv_source() - 1)) + 127) >> 8; + } + if (int_seq_index < 0) int_seq_index = 0; + if (int_seq_index > 11) int_seq_index = 11; + int_seq_.set_int_seq(int_seq_index); + int16_t int_seq_modulus_ = get_int_seq_modulus(); + if (get_int_seq_modulus_cv_source()) { + int_seq_modulus_ += (OC::ADC::value(static_cast(get_int_seq_modulus_cv_source() - 1)) + 31) >> 6; + CONSTRAIN(int_seq_modulus_, 2, 121); + } + int_seq_.set_int_seq_modulus(int_seq_modulus_); + + if (get_int_seq_stride_cv_source()) { + int_seq_stride += (OC::ADC::value(static_cast(get_int_seq_stride_cv_source() - 1)) + 31) >> 6; + } + if (int_seq_stride < 1) int_seq_stride = 1; + if (int_seq_stride > kIntSeqLen - 1) int_seq_stride = kIntSeqLen - 1; + int_seq_.set_fractal_stride(int_seq_stride); + + int_seq_.set_loop_start(get_int_seq_start()); + + int_seq_.set_loop_length(get_int_seq_length()); + + if (int_seq_reset_) { + int_seq_.reset_loop(); + int_seq_reset_ = false; + } + + if (triggered) { + // uint32_t is = int_seq_.Clock(); + // check whether frame should be shifted and if so, by how much. + if (get_int_seq_pass_go()) { + // OK, we're at the start of a loop or at one end of a pendulum swing + uint8_t fs_prob = get_int_seq_frame_shift_prob(); + uint8_t fs_range = get_int_seq_frame_shift_range(); + // Serial.print("fs_prob="); + // Serial.println(fs_prob); + // Serial.print("fs_range="); + // Serial.println(fs_range); + uint8_t fs_rand = static_cast(random(0,256)) ; + // Serial.print("fs_rand="); + // Serial.println(fs_rand); + // Serial.println("---"); + if (fs_rand < fs_prob) { + // OK, move the frame! + int16_t frame_shift = random(-fs_range, fs_range + 1) ; + // Serial.print("frame_shift="); + // Serial.println(frame_shift); + // Serial.print("current start pos="); + // Serial.println(get_int_seq_start()); + int16_t new_start_pos = get_int_seq_start() + frame_shift ; + // Serial.print("new_start_pos="); + // Serial.println(new_start_pos); + // Serial.println("==="); + if (new_start_pos < 0) new_start_pos = 0; + if (new_start_pos > kIntSeqLen - 2) new_start_pos = kIntSeqLen - 2; + set_int_seq_start(static_cast(new_start_pos)) ; + int_seq_.set_loop_start(get_int_seq_start()); + } + } + uint32_t is = int_seq_.Clock(); + int16_t range_ = get_int_seq_range(); + if (get_int_seq_range_cv_source()) { + range_ += (OC::ADC::value(static_cast(get_int_seq_range_cv_source() - 1)) + 31) >> 6; + CONSTRAIN(range_, 1, 120); + } + if (quantizer_.enabled()) { + + // Since our range is limited anyway, just grab the last byte + uint32_t scaled = ((is >> 4) * range_) >> 8; + + // The quantizer uses a lookup codebook with 128 entries centered + // about 0, so we use the range/scaled output to lookup a note + // directly instead of changing to pitch first. + int32_t pitch = + quantizer_.Lookup(64 + range_ / 2 - scaled + get_transpose()) + (get_root() << 7); + sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, pitch, get_octave(), OC::DAC::get_voltage_scaling(dac_channel)); + history_sample = pitch + ((OC::DAC::kOctaveZero + get_octave()) * 12 << 7); + } else { + // We dont' need a calibrated value here, really + int octave = get_octave(); + CONSTRAIN(octave, 0, 6); + sample = OC::DAC::get_octave_offset(dac_channel, octave) + (get_transpose() << 7); + // range is actually 120 (10 oct) but 65535 / 128 is close enough + sample += multiply_u32xu32_rshift32((static_cast(range_) * 65535U) >> 7, is << 20); + sample = USAT16(sample); + history_sample = sample; + } + } + } + break; + + default: { + if (update) { + + int32_t transpose = get_transpose() + prev_transpose_cv_; + int octave = get_octave() + prev_octave_cv_; + int root = get_root() + prev_root_cv_; + + int32_t pitch = quantizer_.enabled() + ? OC::ADC::raw_pitch_value(static_cast(source)) + : OC::ADC::pitch_value(static_cast(source)); + + // repurpose channel CV input? -- + uint8_t _aux_cv_destination = get_aux_cv_dest(); + + if (_aux_cv_destination != prev_destination_) + clear_dest(); + prev_destination_ = _aux_cv_destination; + + if (!continuous && index != source) { + // this doesn't really work all that well for continuous quantizing... + // see below + + switch(_aux_cv_destination) { + + case QQ_DEST_NONE: + break; + case QQ_DEST_TRANSPOSE: + transpose += (OC::ADC::value(static_cast(index)) + 63) >> 7; + break; + case QQ_DEST_ROOT: + root += (OC::ADC::value(static_cast(index)) + 127) >> 8; + break; + case QQ_DEST_OCTAVE: + octave += (OC::ADC::value(static_cast(index)) + 255) >> 9; + break; + case QQ_DEST_MASK: + update_scale(false, (OC::ADC::value(static_cast(index)) + 127) >> 8); + break; + default: + break; + } + } + + // limit: + CONSTRAIN(octave, -4, 4); + CONSTRAIN(root, 0, 11); + CONSTRAIN(transpose, -12, 12); + + int32_t quantized = quantizer_.Process(pitch, root << 7, transpose); + sample = temp_sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, quantized, octave + continuous_offset_, OC::DAC::get_voltage_scaling(dac_channel)); + + // continuous mode needs special treatment to give useful results. + // basically, update on note change only + + if (continuous && last_sample_ != sample) { + + bool _re_quantize = false; + int _aux_cv = 0; + + if (index != source) { + + switch(_aux_cv_destination) { + + case QQ_DEST_NONE: + break; + case QQ_DEST_TRANSPOSE: + _aux_cv = (OC::ADC::value(static_cast(index)) + 63) >> 7; + if (_aux_cv != prev_transpose_cv_) { + transpose = get_transpose() + _aux_cv; + CONSTRAIN(transpose, -12, 12); + prev_transpose_cv_ = _aux_cv; + _re_quantize = true; + } + break; + case QQ_DEST_ROOT: + _aux_cv = (OC::ADC::value(static_cast(index)) + 127) >> 8; + if (_aux_cv != prev_root_cv_) { + root = get_root() + _aux_cv; + CONSTRAIN(root, 0, 11); + prev_root_cv_ = _aux_cv; + _re_quantize = true; + } + break; + case QQ_DEST_OCTAVE: + _aux_cv = (OC::ADC::value(static_cast(index)) + 255) >> 9; + if (_aux_cv != prev_octave_cv_) { + octave = get_octave() + _aux_cv; + CONSTRAIN(octave, -4, 4); + prev_octave_cv_ = _aux_cv; + _re_quantize = true; + } + break; + case QQ_DEST_MASK: + schedule_mask_rotate_ = (OC::ADC::value(static_cast(index)) + 127) >> 8; + update_scale(force_update_, schedule_mask_rotate_); + break; + default: + break; + } + // end switch + } + + // offset when TR source = continuous ? + int8_t _trigger_offset = 0; + bool _trigger_update = false; + if (OC::DigitalInputs::read_immediate(static_cast(index))) { + _trigger_offset = (trigger_source == CHANNEL_TRIGGER_CONTINUOUS_UP) ? 1 : -1; + } + if (_trigger_offset != continuous_offset_) + _trigger_update = true; + continuous_offset_ = _trigger_offset; + + // run quantizer again -- presumably could be made more efficient... + if (_re_quantize) + quantized = quantizer_.Process(pitch, root << 7, transpose); + if (_re_quantize || _trigger_update) + sample = OC::DAC::pitch_to_scaled_voltage_dac(dac_channel, quantized, octave + continuous_offset_, OC::DAC::get_voltage_scaling(dac_channel)); + } + // end special treatment + + history_sample = quantized + ((OC::DAC::kOctaveZero + octave + continuous_offset_) * 12 << 7); + } + } + } // end switch + + bool changed = continuous ? (last_sample_ != temp_sample) : (last_sample_ != sample); + + if (changed) { + MENU_REDRAW = 1; + last_sample_ = continuous ? temp_sample : sample; + } + + OC::DAC::set(dac_channel, sample + get_fine()); + + if (triggered || (continuous && changed)) { + scrolling_history_.Push(history_sample); + trigger_display_.Update(1, true); + } else { + trigger_display_.Update(1, false); + } + scrolling_history_.Update(); + } + + // Wrappers for ScaleEdit + void scale_changed() { + force_update_ = true; + } + + uint16_t get_scale_mask(uint8_t scale_select) const { + return get_mask(); + } + + void update_scale_mask(uint16_t mask, uint16_t dummy) { + apply_value(CHANNEL_SETTING_MASK, mask); // Should automatically be updated + last_mask_ = mask; + force_update_ = true; + } + // + + uint8_t getTriggerState() const { + return trigger_display_.getState(); + } + + uint32_t get_shift_register() const { + return turing_machine_.get_shift_register(); + } + + uint32_t get_logistic_map_register() const { + return logistic_map_.get_register(); + } + + uint32_t get_bytebeat_register() const { + return bytebeat_.get_last_sample(); + } + + uint32_t get_int_seq_register() const { + return int_seq_.get_register(); + } + + int16_t get_int_seq_k() const { + return int_seq_.get_k(); + } + + int16_t get_int_seq_l() const { + return int_seq_.get_l(); + } + + int16_t get_int_seq_i() const { + return int_seq_.get_i(); + } + + int16_t get_int_seq_j() const { + return int_seq_.get_j(); + } + + int16_t get_int_seq_n() const { + return int_seq_.get_n(); + } + + int16_t get_int_seq_x() const { + return int_seq_.get_x(); + } + + bool get_int_seq_pass_go() const { + return int_seq_.get_pass_go(); + } + + // Maintain an internal list of currently available settings, since some are + // dependent on others. It's kind of brute force, but eh, works :) If other + // apps have a similar need, it can be moved to a common wrapper + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + ChannelSetting enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + void update_enabled_settings() { + ChannelSetting *settings = enabled_settings_; + *settings++ = CHANNEL_SETTING_SCALE; + if (OC::Scales::SCALE_NONE != get_scale(DUMMY)) { + *settings++ = CHANNEL_SETTING_ROOT; + *settings++ = CHANNEL_SETTING_MASK; + } + *settings++ = CHANNEL_SETTING_SOURCE; + switch (get_source()) { + case CHANNEL_SOURCE_CV1: + case CHANNEL_SOURCE_CV2: + case CHANNEL_SOURCE_CV3: + case CHANNEL_SOURCE_CV4: + if (get_source() != get_channel_index()) + *settings++ = CHANNEL_SETTING_AUX_SOURCE_DEST; + break; + case CHANNEL_SOURCE_TURING: + *settings++ = CHANNEL_SETTING_TURING_LENGTH; + if (OC::Scales::SCALE_NONE != get_scale(DUMMY)) + *settings++ = CHANNEL_SETTING_TURING_MODULUS; + *settings++ = CHANNEL_SETTING_TURING_RANGE; + *settings++ = CHANNEL_SETTING_TURING_PROB; + if (OC::Scales::SCALE_NONE != get_scale(DUMMY)) + *settings++ = CHANNEL_SETTING_TURING_MODULUS_CV_SOURCE; + *settings++ = CHANNEL_SETTING_TURING_RANGE_CV_SOURCE; + *settings++ = CHANNEL_SETTING_TURING_PROB_CV_SOURCE; + break; + case CHANNEL_SOURCE_LOGISTIC_MAP: + *settings++ = CHANNEL_SETTING_LOGISTIC_MAP_R; + *settings++ = CHANNEL_SETTING_LOGISTIC_MAP_RANGE; + *settings++ = CHANNEL_SETTING_LOGISTIC_MAP_R_CV_SOURCE; + *settings++ = CHANNEL_SETTING_LOGISTIC_MAP_RANGE_CV_SOURCE; + break; + case CHANNEL_SOURCE_BYTEBEAT: + *settings++ = CHANNEL_SETTING_BYTEBEAT_EQUATION; + *settings++ = CHANNEL_SETTING_BYTEBEAT_RANGE; + *settings++ = CHANNEL_SETTING_BYTEBEAT_P0; + *settings++ = CHANNEL_SETTING_BYTEBEAT_P1; + *settings++ = CHANNEL_SETTING_BYTEBEAT_P2; + *settings++ = CHANNEL_SETTING_BYTEBEAT_EQUATION_CV_SOURCE; + *settings++ = CHANNEL_SETTING_BYTEBEAT_RANGE_CV_SOURCE; + *settings++ = CHANNEL_SETTING_BYTEBEAT_P0_CV_SOURCE; + *settings++ = CHANNEL_SETTING_BYTEBEAT_P1_CV_SOURCE; + *settings++ = CHANNEL_SETTING_BYTEBEAT_P2_CV_SOURCE; + break; + case CHANNEL_SOURCE_INT_SEQ: + *settings++ = CHANNEL_SETTING_INT_SEQ_INDEX; + *settings++ = CHANNEL_SETTING_INT_SEQ_MODULUS; + *settings++ = CHANNEL_SETTING_INT_SEQ_RANGE; + *settings++ = CHANNEL_SETTING_INT_SEQ_DIRECTION; + *settings++ = CHANNEL_SETTING_INT_SEQ_BROWNIAN_PROB; + *settings++ = CHANNEL_SETTING_INT_SEQ_LOOP_START; + *settings++ = CHANNEL_SETTING_INT_SEQ_LOOP_LENGTH; + *settings++ = CHANNEL_SETTING_INT_SEQ_STRIDE; + *settings++ = CHANNEL_SETTING_INT_SEQ_STRIDE_CV_SOURCE; + *settings++ = CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_PROB; + *settings++ = CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_RANGE; + *settings++ = CHANNEL_SETTING_INT_SEQ_INDEX_CV_SOURCE; + *settings++ = CHANNEL_SETTING_INT_SEQ_MODULUS_CV_SOURCE; + *settings++ = CHANNEL_SETTING_INT_SEQ_RANGE_CV_SOURCE; + *settings++ = CHANNEL_SETTING_INT_SEQ_RESET_TRIGGER; + break; + default: + break; + } + *settings++ = CHANNEL_SETTING_TRIGGER; + if (get_trigger_source() < CHANNEL_TRIGGER_CONTINUOUS_UP) { + *settings++ = CHANNEL_SETTING_CLKDIV; + *settings++ = CHANNEL_SETTING_DELAY; + } + *settings++ = CHANNEL_SETTING_OCTAVE; + *settings++ = CHANNEL_SETTING_TRANSPOSE; + *settings++ = CHANNEL_SETTING_FINE; + + num_enabled_settings_ = settings - enabled_settings_; + } + + static bool indentSetting(ChannelSetting s) { + switch (s) { + case CHANNEL_SETTING_TURING_LENGTH: + case CHANNEL_SETTING_TURING_MODULUS: + case CHANNEL_SETTING_TURING_RANGE: + case CHANNEL_SETTING_TURING_PROB: + case CHANNEL_SETTING_TURING_MODULUS_CV_SOURCE: + case CHANNEL_SETTING_TURING_RANGE_CV_SOURCE: + case CHANNEL_SETTING_TURING_PROB_CV_SOURCE: + case CHANNEL_SETTING_LOGISTIC_MAP_R: + case CHANNEL_SETTING_LOGISTIC_MAP_RANGE: + case CHANNEL_SETTING_LOGISTIC_MAP_R_CV_SOURCE: + case CHANNEL_SETTING_LOGISTIC_MAP_RANGE_CV_SOURCE: + case CHANNEL_SETTING_BYTEBEAT_EQUATION: + case CHANNEL_SETTING_BYTEBEAT_RANGE: + case CHANNEL_SETTING_BYTEBEAT_P0: + case CHANNEL_SETTING_BYTEBEAT_P1: + case CHANNEL_SETTING_BYTEBEAT_P2: + case CHANNEL_SETTING_BYTEBEAT_EQUATION_CV_SOURCE: + case CHANNEL_SETTING_BYTEBEAT_RANGE_CV_SOURCE: + case CHANNEL_SETTING_BYTEBEAT_P0_CV_SOURCE: + case CHANNEL_SETTING_BYTEBEAT_P1_CV_SOURCE: + case CHANNEL_SETTING_BYTEBEAT_P2_CV_SOURCE: + case CHANNEL_SETTING_INT_SEQ_INDEX: + case CHANNEL_SETTING_INT_SEQ_MODULUS: + case CHANNEL_SETTING_INT_SEQ_RANGE: + case CHANNEL_SETTING_INT_SEQ_DIRECTION: + case CHANNEL_SETTING_INT_SEQ_BROWNIAN_PROB: + case CHANNEL_SETTING_INT_SEQ_LOOP_START: + case CHANNEL_SETTING_INT_SEQ_LOOP_LENGTH: + case CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_PROB: + case CHANNEL_SETTING_INT_SEQ_FRAME_SHIFT_RANGE: + case CHANNEL_SETTING_INT_SEQ_STRIDE: + case CHANNEL_SETTING_INT_SEQ_INDEX_CV_SOURCE: + case CHANNEL_SETTING_INT_SEQ_MODULUS_CV_SOURCE: + case CHANNEL_SETTING_INT_SEQ_RANGE_CV_SOURCE: + case CHANNEL_SETTING_INT_SEQ_STRIDE_CV_SOURCE: + case CHANNEL_SETTING_INT_SEQ_RESET_TRIGGER: + case CHANNEL_SETTING_CLKDIV: + case CHANNEL_SETTING_DELAY: + return true; + default: break; + } + return false; + } + + void RenderScreensaver(weegfx::coord_t x) const; + +private: + bool force_update_; + bool instant_update_; + int last_scale_; + uint16_t last_mask_; + int32_t last_sample_; + uint8_t clock_; + bool int_seq_reset_; + int8_t continuous_offset_; + int8_t channel_index_; + int32_t schedule_mask_rotate_; + int8_t prev_destination_; + int8_t prev_octave_cv_; + int8_t prev_transpose_cv_; + int8_t prev_root_cv_; + + util::TriggerDelay trigger_delay_; + util::TuringShiftRegister turing_machine_; + util::LogisticMap logistic_map_; + peaks::ByteBeat bytebeat_ ; + util::IntegerSequence int_seq_ ; + braids::Quantizer quantizer_; + OC::DigitalInputDisplay trigger_display_; + + int num_enabled_settings_; + ChannelSetting enabled_settings_[CHANNEL_SETTING_LAST]; + + OC::vfx::ScrollingHistory scrolling_history_; + + bool update_scale(bool force, int32_t mask_rotate) { + + force_update_ = false; + const int scale = get_scale(DUMMY); + uint16_t mask = get_mask(); + + if (mask_rotate) + mask = OC::ScaleEditor::RotateMask(mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); + + if (force || (last_scale_ != scale || last_mask_ != mask)) { + last_scale_ = scale; + last_mask_ = mask; + quantizer_.Configure(OC::Scales::GetScale(scale), mask); + return true; + } else { + return false; + } + } +}; + +const char* const channel_input_sources[CHANNEL_SOURCE_LAST] = { + "CV1", "CV2", "CV3", "CV4", "Turing", "Lgstc", "ByteB", "IntSq" +}; + +const char* const aux_cv_dest[5] = { + "-", "root", "oct", "trns", "mask" +}; + +SETTINGS_DECLARE(QuantizerChannel, CHANNEL_SETTING_LAST) { + { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "Scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + { 0, 0, 11, "Root", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 65535, 1, 65535, "Active notes", NULL, settings::STORAGE_TYPE_U16 }, + { CHANNEL_SOURCE_CV1, CHANNEL_SOURCE_CV1, CHANNEL_SOURCE_LAST - 1, "CV Source", channel_input_sources, settings::STORAGE_TYPE_U8 }, + { QQ_DEST_NONE, QQ_DEST_NONE, QQ_DEST_LAST - 1, "CV aux >", aux_cv_dest, settings::STORAGE_TYPE_U8 }, + { CHANNEL_TRIGGER_CONTINUOUS_DOWN, 0, CHANNEL_TRIGGER_LAST - 1, "Trigger source", OC::Strings::channel_trigger_sources, settings::STORAGE_TYPE_U8 }, + { 1, 1, 16, "Clock div", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, OC::kNumDelayTimes - 1, "Trigger delay", OC::Strings::trigger_delay_times, settings::STORAGE_TYPE_U8 }, + { 0, -5, 7, "Transpose", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -4, 4, "Octave", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -999, 999, "Fine", NULL, settings::STORAGE_TYPE_I16 }, + { 16, 1, 32, "LFSR length", NULL, settings::STORAGE_TYPE_U8 }, + { 128, 0, 255, "LFSR prb", NULL, settings::STORAGE_TYPE_U8 }, + { 24, 2, 121, "LFSR modulus", NULL, settings::STORAGE_TYPE_U8 }, + { 12, 1, 120, "LFSR range", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 4, "LFSR prb CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "LFSR mod CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "LFSR rng CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 128, 1, 255, "Logistic r", NULL, settings::STORAGE_TYPE_U8 }, + { 12, 1, 120, "Logistic range", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 4, "Log r CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "Log rng CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 15, "Bytebeat eqn", OC::Strings::bytebeat_equation_names, settings::STORAGE_TYPE_U8 }, + { 12, 1, 120, "Bytebeat rng", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 1, 255, "Bytebeat P0", NULL, settings::STORAGE_TYPE_U8 }, + { 12, 1, 255, "Bytebeat P1", NULL, settings::STORAGE_TYPE_U8 }, + { 14, 1, 255, "Bytebeat P2", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 4, "Bb eqn CV src", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "Bb rng CV src", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "Bb P0 CV src", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "Bb P1 CV src", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "Bb P2 CV src", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 11, "IntSeq", OC::Strings::integer_sequence_names, settings::STORAGE_TYPE_U4 }, + { 24, 2, 121, "IntSeq modul.", NULL, settings::STORAGE_TYPE_U8 }, + { 12, 1, 120, "IntSeq range", NULL, settings::STORAGE_TYPE_U8 }, + { 1, 0, 1, "IntSeq dir", OC::Strings::integer_sequence_dirs, settings::STORAGE_TYPE_U4 }, + { 0, 0, 255, "> Brownian prob", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, kIntSeqLen - 2, "IntSeq start", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 2, kIntSeqLen, "IntSeq len", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "IntSeq FS prob", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 5, "IntSeq FS rng", NULL, settings::STORAGE_TYPE_U4 }, + { 1, 1, kIntSeqLen - 1, "Fractal stride", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 4, "IntSeq CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "IntSeq mod CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "IntSeq rng CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "F. stride CV >", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "IntSeq reset", OC::Strings::trigger_input_names_none, settings::STORAGE_TYPE_U4 } +}; + +// WIP refactoring to better encapsulate and for possible app interface change +class QuadQuantizer { +public: + void Init() { + selected_channel = 0; + cursor.Init(CHANNEL_SETTING_SCALE, CHANNEL_SETTING_LAST - 1); + scale_editor.Init(false); + } + + inline bool editing() const { + return cursor.editing(); + } + + inline int cursor_pos() const { + return cursor.cursor_pos(); + } + + int selected_channel; + menu::ScreenCursor cursor; + OC::ScaleEditor scale_editor; +}; + +QuadQuantizer qq_state; +QuantizerChannel quantizer_channels[4]; + +void QQ_init() { + + qq_state.Init(); + for (size_t i = 0; i < 4; ++i) { + quantizer_channels[i].Init(static_cast(CHANNEL_SOURCE_CV1 + i), + static_cast(CHANNEL_TRIGGER_TR1 + i)); + } + + qq_state.cursor.AdjustEnd(quantizer_channels[0].num_enabled_settings() - 1); +} + +size_t QQ_storageSize() { + return 4 * QuantizerChannel::storageSize(); +} + +size_t QQ_save(void *storage) { + size_t used = 0; + for (size_t i = 0; i < 4; ++i) { + used += quantizer_channels[i].Save(static_cast(storage) + used); + } + return used; +} + +size_t QQ_restore(const void *storage) { + size_t used = 0; + for (size_t i = 0; i < 4; ++i) { + used += quantizer_channels[i].Restore(static_cast(storage) + used); + quantizer_channels[i].update_scale_mask(quantizer_channels[i].get_mask(), 0x0); + quantizer_channels[i].update_enabled_settings(); + } + qq_state.cursor.AdjustEnd(quantizer_channels[0].num_enabled_settings() - 1); + return used; +} + +void QQ_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + qq_state.cursor.set_editing(false); + qq_state.scale_editor.Close(); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void QQ_isr() { + uint32_t triggers = OC::DigitalInputs::clocked(); + quantizer_channels[0].Update(triggers, DAC_CHANNEL_A); + quantizer_channels[1].Update(triggers, DAC_CHANNEL_B); + quantizer_channels[2].Update(triggers, DAC_CHANNEL_C); + quantizer_channels[3].Update(triggers, DAC_CHANNEL_D); +} + +void QQ_loop() { +} + +void QQ_menu() { + + menu::QuadTitleBar::Draw(); + for (int i = 0, x = 0; i < 4; ++i, x += 32) { + const QuantizerChannel &channel = quantizer_channels[i]; + menu::QuadTitleBar::SetColumn(i); + graphics.print((char)('A' + i)); + graphics.movePrintPos(2, 0); + int octave = channel.get_octave(); + if (octave) + graphics.pretty_print(octave); + + menu::QuadTitleBar::DrawGateIndicator(i, channel.getTriggerState()); + } + menu::QuadTitleBar::Selected(qq_state.selected_channel); + + + const QuantizerChannel &channel = quantizer_channels[qq_state.selected_channel]; + + menu::SettingsList settings_list(qq_state.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int setting = + channel.enabled_setting_at(settings_list.Next(list_item)); + const int value = channel.get_value(setting); + const settings::value_attr &attr = QuantizerChannel::value_attr(setting); + + switch (setting) { + case CHANNEL_SETTING_SCALE: + list_item.SetPrintPos(); + if (list_item.editing) { + menu::DrawEditIcon(6, list_item.y, value, attr); + graphics.movePrintPos(6, 0); + } + graphics.print(OC::scale_names[value]); + list_item.DrawCustom(); + break; + case CHANNEL_SETTING_MASK: + menu::DrawMask(menu::kDisplayWidth, list_item.y, channel.get_rotated_scale_mask(), OC::Scales::GetScale(channel.get_scale(DUMMY)).num_notes); + list_item.DrawNoValue(value, attr); + break; + case CHANNEL_SETTING_TRIGGER: + { + if (channel.get_source() > CHANNEL_SOURCE_CV4) + list_item.DrawValueMax(value, attr, CHANNEL_TRIGGER_TR4); + else + list_item.DrawDefault(value, attr); + } + break; + case CHANNEL_SETTING_SOURCE: + if (CHANNEL_SOURCE_TURING == channel.get_source()) { + int turing_length = channel.get_turing_length(); + int w = turing_length >= 16 ? 16 * 3 : turing_length * 3; + + menu::DrawMask(menu::kDisplayWidth, list_item.y, channel.get_shift_register(), turing_length); + list_item.valuex = menu::kDisplayWidth - w - 1; + list_item.DrawNoValue(value, attr); + break; + // Fall through if not Turing + } + default: + if (QuantizerChannel::indentSetting(static_cast(setting))) + list_item.x += menu::kIndentDx; + if (setting == CHANNEL_SETTING_SOURCE && channel.get_trigger_source() > CHANNEL_TRIGGER_TR4) + list_item.DrawValueMax(value, attr, CHANNEL_TRIGGER_TR4); + else list_item.DrawDefault(value, attr); + break; + } + } + + if (qq_state.scale_editor.active()) + qq_state.scale_editor.Draw(); +} + +void QQ_handleButtonEvent(const UI::Event &event) { + + if (UI::EVENT_BUTTON_LONG_PRESS == event.type && OC::CONTROL_BUTTON_DOWN == event.control) + QQ_downButtonLong(); + + if (qq_state.scale_editor.active()) { + qq_state.scale_editor.HandleButtonEvent(event); + return; + } + + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + QQ_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + QQ_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + QQ_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + QQ_rightButton(); + break; + } + } else { + if (OC::CONTROL_BUTTON_L == event.control) + QQ_leftButtonLong(); + } +} + +void QQ_handleEncoderEvent(const UI::Event &event) { + if (qq_state.scale_editor.active()) { + qq_state.scale_editor.HandleEncoderEvent(event); + return; + } + + if (OC::CONTROL_ENCODER_L == event.control) { + int selected_channel = qq_state.selected_channel + event.value; + CONSTRAIN(selected_channel, 0, 3); + qq_state.selected_channel = selected_channel; + + QuantizerChannel &selected = quantizer_channels[qq_state.selected_channel]; + qq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + } else if (OC::CONTROL_ENCODER_R == event.control) { + QuantizerChannel &selected = quantizer_channels[qq_state.selected_channel]; + if (qq_state.editing()) { + ChannelSetting setting = selected.enabled_setting_at(qq_state.cursor_pos()); + if (CHANNEL_SETTING_MASK != setting) { + + int event_value = event.value; + + switch (setting) { + case CHANNEL_SETTING_TRIGGER: + { + if (selected.get_trigger_source() == CHANNEL_TRIGGER_TR4 && selected.get_source() > CHANNEL_SOURCE_CV4 && event.value > 0) + event_value = 0x0; + } + break; + case CHANNEL_SETTING_SOURCE: { + if (selected.get_source() == CHANNEL_SOURCE_CV4 && selected.get_trigger_source() > CHANNEL_TRIGGER_TR4 && event.value > 0) + event_value = 0x0; + } + break; + default: + break; + } + + if (selected.change_value(setting, event_value)) + selected.force_update(); + + switch (setting) { + case CHANNEL_SETTING_SCALE: + case CHANNEL_SETTING_TRIGGER: + case CHANNEL_SETTING_SOURCE: + selected.update_enabled_settings(); + qq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + break; + default: + break; + } + } + } else { + qq_state.cursor.Scroll(event.value); + } + } +} + +void QQ_topButton() { + QuantizerChannel &selected = quantizer_channels[qq_state.selected_channel]; + if (selected.change_value(CHANNEL_SETTING_OCTAVE, 1)) { + selected.force_update(); + } +} + +void QQ_lowerButton() { + QuantizerChannel &selected = quantizer_channels[qq_state.selected_channel]; + if (selected.change_value(CHANNEL_SETTING_OCTAVE, -1)) { + selected.force_update(); + } +} + +void QQ_rightButton() { + QuantizerChannel &selected = quantizer_channels[qq_state.selected_channel]; + switch (selected.enabled_setting_at(qq_state.cursor_pos())) { + case CHANNEL_SETTING_MASK: { + int scale = selected.get_scale(DUMMY); + if (OC::Scales::SCALE_NONE != scale) { + qq_state.scale_editor.Edit(&selected, scale); + } + } + break; + default: + qq_state.cursor.toggle_editing(); + break; + } +} + +void QQ_leftButton() { + qq_state.selected_channel = (qq_state.selected_channel + 1) & 3; + QuantizerChannel &selected = quantizer_channels[qq_state.selected_channel]; + qq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); +} + +void QQ_leftButtonLong() { + QuantizerChannel &selected_channel = quantizer_channels[qq_state.selected_channel]; + int scale = selected_channel.get_scale(DUMMY); + int root = selected_channel.get_root(); + for (int i = 0; i < 4; ++i) { + if (i != qq_state.selected_channel) { + quantizer_channels[i].apply_value(CHANNEL_SETTING_ROOT, root); + quantizer_channels[i].set_scale(scale); + } + } +} + +void QQ_downButtonLong() { + + QuantizerChannel &selected_channel = quantizer_channels[qq_state.selected_channel]; + selected_channel.update_scale_mask(0xFFFF, 0x0); +} + +int32_t history[5]; +static const weegfx::coord_t kBottom = 60; + +inline int32_t render_pitch(int32_t pitch, weegfx::coord_t x, weegfx::coord_t width) { + CONSTRAIN(pitch, 0, 120 << 7); + int32_t octave = pitch / (12 << 7); + pitch -= (octave * 12 << 7); + graphics.drawHLine(x, kBottom - ((pitch * 4) >> 7), width); + return octave; +} + +void QuantizerChannel::RenderScreensaver(weegfx::coord_t start_x) const { + + // History + scrolling_history_.Read(history); + weegfx::coord_t scroll_pos = (scrolling_history_.get_scroll_pos() * 6) >> 8; + + // Top: Show gate & CV (or register bits) + menu::DrawGateIndicator(start_x + 1, 2, getTriggerState()); + const ChannelSource source = get_source(); + switch (source) { + case CHANNEL_SOURCE_TURING: + menu::DrawMask(start_x + 31, 1, get_shift_register(), get_turing_length()); + break; + case CHANNEL_SOURCE_LOGISTIC_MAP: + menu::DrawMask(start_x + 31, 1, get_logistic_map_register(), 32); + break; + case CHANNEL_SOURCE_BYTEBEAT: + menu::DrawMask(start_x + 31, 1, get_bytebeat_register(), 8); + break; + case CHANNEL_SOURCE_INT_SEQ: + // graphics.setPrintPos(start_x + 31 - 16, 4); + graphics.setPrintPos(start_x + 8, 4); + graphics.print(get_int_seq_k()); + // menu::DrawMask(start_x + 31, 1, get_int_seq_register(), 8); + break; + default: { + graphics.setPixel(start_x + QQ_OFFSET_X - 16, 4); + int32_t cv = OC::ADC::value(static_cast(source)); + cv = (cv * 24 + 2047) >> 12; + if (cv < 0) + graphics.drawRect(start_x + QQ_OFFSET_X - 16 + cv, 6, -cv, 2); + else if (cv > 0) + graphics.drawRect(start_x + QQ_OFFSET_X - 16, 6, cv, 2); + else + graphics.drawRect(start_x + QQ_OFFSET_X - 16, 6, 1, 2); + } + break; + } + +#ifdef QQ_DEBUG_SCREENSAVER + graphics.drawVLinePattern(start_x + 31, 0, 64, 0x55); +#endif + + // Draw semitone intervals, 4px apart + weegfx::coord_t x = start_x + 26; + weegfx::coord_t y = kBottom; + for (int i = 0; i < 12; ++i, y -= 4) + graphics.setPixel(x, y); + + x = start_x + 1; + render_pitch(history[0], x, scroll_pos); x += scroll_pos; + render_pitch(history[1], x, 6); x += 6; + render_pitch(history[2], x, 6); x += 6; + render_pitch(history[3], x, 6); x += 6; + + int32_t octave = render_pitch(history[4], x, 6 - scroll_pos); + graphics.drawBitmap8(start_x + 28, kBottom - octave * 4 - 1, OC::kBitmapLoopMarkerW, OC::bitmap_loop_markers_8 + OC::kBitmapLoopMarkerW); +} + +void QQ_screensaver() { +#ifdef QQ_DEBUG_SCREENSAVER + debug::CycleMeasurement render_cycles; +#endif + + quantizer_channels[0].RenderScreensaver(0); + quantizer_channels[1].RenderScreensaver(32); + quantizer_channels[2].RenderScreensaver(64); + quantizer_channels[3].RenderScreensaver(96); + +#ifdef QQ_DEBUG_SCREENSAVER + graphics.drawHLine(0, menu::kMenuLineH, menu::kDisplayWidth); + uint32_t us = debug::cycles_to_us(render_cycles.read()); + graphics.setPrintPos(0, 32); + graphics.printf("%u", us); +#endif +} + +#ifdef QQ_DEBUG +void QQ_debug() { + for (int i = 0; i < 4; ++i) { + uint8_t ypos = 10*(i + 1) + 2 ; + graphics.setPrintPos(2, ypos); + graphics.print(quantizer_channels[i].get_int_seq_i()); + graphics.setPrintPos(30, ypos); + graphics.print(quantizer_channels[i].get_int_seq_l()); + graphics.setPrintPos(58, ypos); + graphics.print(quantizer_channels[i].get_int_seq_j()); + graphics.setPrintPos(80, ypos); + graphics.print(quantizer_channels[i].get_int_seq_k()); + graphics.setPrintPos(104, ypos); + graphics.print(quantizer_channels[i].get_int_seq_x()); + } +} +#endif // QQ_DEBUG + +#endif // ENABLE_APP_QUANTERMAIN diff --git a/software/o_c_REV/APP_SEQ.ino b/software/o_c_REV/APP_SEQ.ino new file mode 100644 index 000000000..eb445a128 --- /dev/null +++ b/software/o_c_REV/APP_SEQ.ino @@ -0,0 +1,2635 @@ +// Copyright (c) 2015, 2016 Max Stadler, Patrick Dowling +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifdef ENABLE_APP_SEQUINS + +#include "util/util_settings.h" +#include "util/util_trigger_delay.h" +#include "OC_apps.h" +#include "OC_DAC.h" +#include "OC_menus.h" +#include "OC_ui.h" +#include "OC_strings.h" +#include "OC_visualfx.h" +#include "OC_sequence_edit.h" +#include "OC_patterns.h" +#include "OC_scales.h" +#include "OC_scale_edit.h" +#include "OC_input_map.h" +#include "OC_input_maps.h" +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "extern/dspinst.h" +#include "util/util_arp.h" +#include "peaks_multistage_envelope.h" + + +namespace menu = OC::menu; + +const uint8_t NUM_CHANNELS = 2; +const uint8_t MULT_MAX = 26; // max multiplier +const uint8_t MULT_BY_ONE = 19; // default multiplication +const uint8_t PULSEW_MAX = 255; // max pulse width [ms] + +const uint32_t SCALE_PULSEWIDTH = 58982; // 0.9 for signed_multiply_32x16b +const uint32_t TICKS_TO_MS = 43691; // 0.6667f : fraction, if TU_CORE_TIMER_RATE = 60 us : 65536U * ((1000 / TU_CORE_TIMER_RATE) - 16) +const uint32_t TICK_JITTER = 0xFFFFFFF; // 1/16 : threshold/double triggers reject -> ext_frequency_in_ticks_ +const uint32_t TICK_SCALE = 0xC0000000; // 0.75 for signed_multiply_32x32 +const uint32_t COPYTIMEOUT = 200000; // in ticks + +void SEQ_leftButton(); +void SEQ_leftButtonLong(); +void SEQ_upButtonLong(); +void SEQ_downButtonLong(); +void SEQ_upButton(); +void SEQ_downButton(); +void SEQ_rightButton(); + +uint32_t ticks_src1 = 0; // main clock frequency (top) +uint32_t ticks_src2 = 0; // sec. clock frequency (bottom) + +// copy sequence, global +uint8_t copy_sequence = 0; +uint8_t copy_length = OC::Patterns::kMax; +uint16_t copy_mask = 0xFFFF; +uint64_t copy_timeout = COPYTIMEOUT; + +const uint64_t multipliers_[] = { + + 0xFFFFFFFF, // x1 + 0x80000000, // x2 + 0x55555555, // x3 + 0x40000000, // x4 + 0x33333333, // x5 + 0x2AAAAAAB, // x6 + 0x24924925, // x7 + 0x20000000 // x8 + +}; // = 2^32 / multiplier + +const uint64_t pw_scale_[] = { + + 0xFFFFFFFF, // /64 + 0xFBFFFFFF, // /63 + 0xF7FFFFFF, // /62 + 0xC3FFFFFF, // /49 + 0xC0000000, // /48 + 0xBBFFFFFF, // /47 + 0x83FFFFFF, // /33 + 0x80000000, // /32 + 0x7BFFFFFF, // /31 + 0x43FFFFFF, // /17 + 0x40000000, // /16 + 0x3BFFFFFF, // /15 + 0x20000000, // /8 + 0x1C000000, // /7 + 0x18000000, // /6 + 0x14000000, // /5 + 0x10000000, // /4 + 0xC000000, // /3 + 0x8000000, // /2 + 0x4000000, // x1 + +}; // = 0xFFFFFFFF * divisor / 64 + +const uint8_t divisors_[] = { + 64, + 63, + 62, + 49, + 48, + 47, + 33, + 32, + 31, + 17, + 16, + 15, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1 +}; + +enum SEQ_ChannelSetting { + + SEQ_CHANNEL_SETTING_MODE, // + SEQ_CHANNEL_SETTING_CLOCK, + SEQ_CHANNEL_SETTING_TRIGGER_DELAY, + SEQ_CHANNEL_SETTING_RESET, + SEQ_CHANNEL_SETTING_MULT, + SEQ_CHANNEL_SETTING_PULSEWIDTH, + // + SEQ_CHANNEL_SETTING_SCALE, + SEQ_CHANNEL_SETTING_OCTAVE, + SEQ_CHANNEL_SETTING_ROOT, + SEQ_CHANNEL_SETTING_OCTAVE_AUX, + SEQ_CHANNEL_SETTING_SCALE_MASK, + // + SEQ_CHANNEL_SETTING_MASK1, + SEQ_CHANNEL_SETTING_MASK2, + SEQ_CHANNEL_SETTING_MASK3, + SEQ_CHANNEL_SETTING_MASK4, + SEQ_CHANNEL_SETTING_SEQUENCE, + SEQ_CHANNEL_SETTING_SEQUENCE_LEN1, + SEQ_CHANNEL_SETTING_SEQUENCE_LEN2, + SEQ_CHANNEL_SETTING_SEQUENCE_LEN3, + SEQ_CHANNEL_SETTING_SEQUENCE_LEN4, + SEQ_CHANNEL_SETTING_SEQUENCE_PLAYMODE, + SEQ_CHANNEL_SETTING_SEQUENCE_DIRECTION, + SEQ_CHANNEL_SETTING_SEQUENCE_PLAYMODE_CV_RANGES, + SEQ_CHANNEL_SETTING_SEQUENCE_ARP_DIRECTION, + SEQ_CHANNEL_SETTING_SEQUENCE_ARP_RANGE, + SEQ_CHANNEL_SETTING_BROWNIAN_PROBABILITY, + // cv sources + SEQ_CHANNEL_SETTING_MULT_CV_SOURCE, + SEQ_CHANNEL_SETTING_TRANSPOSE_CV_SOURCE, + SEQ_CHANNEL_SETTING_PULSEWIDTH_CV_SOURCE, + SEQ_CHANNEL_SETTING_OCTAVE_CV_SOURCE, + SEQ_CHANNEL_SETTING_ROOT_CV_SOURCE, + SEQ_CHANNEL_SETTING_OCTAVE_AUX_CV_SOURCE, + SEQ_CHANNEL_SETTING_SEQ_CV_SOURCE, + SEQ_CHANNEL_SETTING_SCALE_MASK_CV_SOURCE, + SEQ_CHANNEL_SETTING_SEQUENCE_ARP_DIRECTION_CV_SOURCE, + SEQ_CHANNEL_SETTING_SEQUENCE_ARP_RANGE_CV_SOURCE, + SEQ_CHANNEL_SETTING_DIRECTION_CV_SOURCE, + SEQ_CHANNEL_SETTING_BROWNIAN_CV_SOURCE, + SEQ_CHANNEL_SETTING_LENGTH_CV_SOURCE, + SEQ_CHANNEL_SETTING_ENV_ATTACK_CV_SOURCE, + SEQ_CHANNEL_SETTING_ENV_DECAY_CV_SOURCE, + SEQ_CHANNEL_SETTING_ENV_SUSTAIN_CV_SOURCE, + SEQ_CHANNEL_SETTING_ENV_RELEASE_CV_SOURCE, + SEQ_CHANNEL_SETTING_ENV_LOOPS_CV_SOURCE, + SEQ_CHANNEL_SETTING_DUMMY, + // aux envelope settings + SEQ_CHANNEL_SETTING_ENV_ATTACK_DURATION, + SEQ_CHANNEL_SETTING_ENV_ATTACK_SHAPE, + SEQ_CHANNEL_SETTING_ENV_DECAY_DURATION, + SEQ_CHANNEL_SETTING_ENV_DECAY_SHAPE, + SEQ_CHANNEL_SETTING_ENV_SUSTAIN_LEVEL, + SEQ_CHANNEL_SETTING_ENV_RELEASE_DURATION, + SEQ_CHANNEL_SETTING_ENV_RELEASE_SHAPE, + SEQ_CHANNEL_SETTING_ENV_MAX_LOOPS, + SEQ_CHANNEL_SETTING_ENV_ATTACK_RESET_BEHAVIOUR, + SEQ_CHANNEL_SETTING_ENV_ATTACK_FALLING_GATE_BEHAVIOUR, + SEQ_CHANNEL_SETTING_ENV_DECAY_RELEASE_RESET_BEHAVIOUR, + // marker + SEQ_CHANNEL_SETTING_LAST +}; + +enum SEQ_ChannelTriggerSource { + SEQ_CHANNEL_TRIGGER_TR1, + SEQ_CHANNEL_TRIGGER_TR2, + SEQ_CHANNEL_TRIGGER_NONE, + SEQ_CHANNEL_TRIGGER_FREEZE_HI2, + SEQ_CHANNEL_TRIGGER_FREEZE_LO2, + SEQ_CHANNEL_TRIGGER_FREEZE_HI4, + SEQ_CHANNEL_TRIGGER_FREEZE_LO4, + SEQ_CHANNEL_TRIGGER_LAST +}; + +enum SEQ_ChannelCV_Mapping { + CHANNEL_CV_MAPPING_CV1, + CHANNEL_CV_MAPPING_CV2, + CHANNEL_CV_MAPPING_CV3, + CHANNEL_CV_MAPPING_CV4, + CHANNEL_CV_MAPPING_LAST +}; + +enum SEQ_CLOCKSTATES { + OFF, + ON = 0xFFFF +}; + +enum MENU_PAGES { + PARAMETERS, + CV_MAPPING +}; + +enum SEQ_UPDATE { + ALL_OK, + WAIT, + CLEAR +}; + +enum PLAY_MODES { + PM_NONE, + PM_SEQ1, + PM_SEQ2, + PM_SEQ3, + PM_TR1, + PM_TR2, + PM_TR3, + PM_ARP, + PM_SH1, + PM_SH2, + PM_SH3, + PM_SH4, + PM_CV1, + PM_CV2, + PM_CV3, + PM_CV4, + PM_LAST +}; + +enum SEQ_DIRECTIONS { + FORWARD, + REVERSE, + PENDULUM1, + PENDULUM2, + RANDOM, + BROWNIAN, + SEQ_DIRECTIONS_LAST +}; + +enum SQ_AUX_MODES { + GATE_OUT, + COPY, + ENV_AD, + ENV_ADR, + ENV_ADSR, + SQ_AUX_MODES_LAST +}; + +uint64_t ext_frequency[SEQ_CHANNEL_TRIGGER_NONE + 1]; + +class SEQ_Channel : public settings::SettingsBase { +public: + + uint8_t get_menu_page() const { + return menu_page_; + } + + bool octave_toggle() { + octave_toggle_ = (~octave_toggle_) & 1u; + return octave_toggle_; + } + + bool poke_octave_toggle() const { + return octave_toggle_; + } + + bool wait_for_EoS() { + return wait_for_EoS_; + } + + void toggle_EoS() { + wait_for_EoS_ = (~wait_for_EoS_) & 1u; + apply_value(SEQ_CHANNEL_SETTING_DUMMY, wait_for_EoS_); + } + + void set_EoS_update() { + wait_for_EoS_ = values_[SEQ_CHANNEL_SETTING_DUMMY]; + } + + void set_menu_page(uint8_t _menu_page) { + menu_page_ = _menu_page; + } + + uint8_t get_aux_mode() const { + return values_[SEQ_CHANNEL_SETTING_MODE]; + } + + uint8_t get_root(uint8_t DUMMY) const { + return values_[SEQ_CHANNEL_SETTING_ROOT]; + } + + uint8_t get_clock_source() const { + return values_[SEQ_CHANNEL_SETTING_CLOCK]; + } + + uint16_t get_trigger_delay() const { + return values_[SEQ_CHANNEL_SETTING_TRIGGER_DELAY]; + } + + void set_clock_source(uint8_t _src) { + apply_value(SEQ_CHANNEL_SETTING_CLOCK, _src); + } + + int get_octave() const { + return values_[SEQ_CHANNEL_SETTING_OCTAVE]; + } + + int get_octave_aux() const { + return values_[SEQ_CHANNEL_SETTING_OCTAVE_AUX]; + } + + int8_t get_multiplier() const { + return values_[SEQ_CHANNEL_SETTING_MULT]; + } + + uint16_t get_pulsewidth() const { + return values_[SEQ_CHANNEL_SETTING_PULSEWIDTH]; + } + + uint8_t get_reset_source() const { + return values_[SEQ_CHANNEL_SETTING_RESET]; + } + + void set_reset_source(uint8_t src) { + apply_value(SEQ_CHANNEL_SETTING_RESET, src); + } + + int get_sequence() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE]; + } + + int get_current_sequence() const { + return display_num_sequence_; + } + + int get_direction() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_DIRECTION]; + } + + int get_direction_cv() const { + return values_[SEQ_CHANNEL_SETTING_DIRECTION_CV_SOURCE]; + } + + int get_playmode() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_PLAYMODE]; + } + + int draw_clock() const { + return get_playmode() != PM_ARP; + } + + int get_arp_direction() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_ARP_DIRECTION]; + } + + int get_arp_range() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_ARP_RANGE]; + } + + int get_display_num_sequence() const { + return display_num_sequence_; + } + + int get_display_length() const { + return active_sequence_length_; + } + + void set_display_num_sequence(uint8_t seq) { + display_num_sequence_ = seq; + } + + int get_display_mask() const { + return display_mask_; + } + + void set_sequence(uint8_t seq) { + apply_value(SEQ_CHANNEL_SETTING_SEQUENCE, seq); + } + + void set_gate(uint16_t _state) { + gate_state_ = _state; + } + + uint8_t get_sequence_length(uint8_t _num_seq) const { + + switch (_num_seq) { + + case 0: + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_LEN1]; + break; + case 1: + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_LEN2]; + break; + case 2: + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_LEN3]; + break; + case 3: + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_LEN4]; + break; + default: + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_LEN1]; + break; + } + } + + uint8_t get_sequence_length_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_LENGTH_CV_SOURCE]; + } + + uint8_t get_mult_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_MULT_CV_SOURCE]; + } + + uint8_t get_transpose_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_TRANSPOSE_CV_SOURCE]; + } + + uint8_t get_pulsewidth_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_PULSEWIDTH_CV_SOURCE]; + } + + uint8_t get_scale_mask_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_SCALE_MASK_CV_SOURCE]; + } + + uint8_t get_sequence_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_SEQ_CV_SOURCE]; + } + + uint8_t get_octave_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_OCTAVE_CV_SOURCE]; + } + + uint8_t get_root_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_ROOT_CV_SOURCE]; + } + + uint8_t get_octave_aux_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_OCTAVE_AUX_CV_SOURCE]; + } + + uint8_t get_arp_direction_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_ARP_DIRECTION_CV_SOURCE]; + } + + uint8_t get_arp_range_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_ARP_RANGE_CV_SOURCE]; + } + + uint8_t get_brownian_probability() const { + return values_[SEQ_CHANNEL_SETTING_BROWNIAN_PROBABILITY]; + } + + int8_t get_brownian_probability_cv() const { + return values_[SEQ_CHANNEL_SETTING_BROWNIAN_CV_SOURCE]; + } + + uint16_t get_attack_duration() const { + return SCALE8_16(values_[SEQ_CHANNEL_SETTING_ENV_ATTACK_DURATION]); + } + + int8_t get_attack_duration_cv() const { + return values_[SEQ_CHANNEL_SETTING_ENV_ATTACK_CV_SOURCE]; + } + + peaks::EnvelopeShape get_attack_shape() const { + return static_cast(values_[SEQ_CHANNEL_SETTING_ENV_ATTACK_SHAPE]); + } + + uint16_t get_decay_duration() const { + return SCALE8_16(values_[SEQ_CHANNEL_SETTING_ENV_DECAY_DURATION]); + } + + int8_t get_decay_duration_cv() const { + return values_[SEQ_CHANNEL_SETTING_ENV_DECAY_CV_SOURCE]; + } + + peaks::EnvelopeShape get_decay_shape() const { + return static_cast(values_[SEQ_CHANNEL_SETTING_ENV_DECAY_SHAPE]); + } + + uint16_t get_sustain_level() const { + return SCALE8_16(values_[SEQ_CHANNEL_SETTING_ENV_SUSTAIN_LEVEL]); + } + + int8_t get_sustain_level_cv() const { + return values_[SEQ_CHANNEL_SETTING_ENV_SUSTAIN_CV_SOURCE]; + } + + uint16_t get_release_duration() const { + return SCALE8_16(values_[SEQ_CHANNEL_SETTING_ENV_RELEASE_DURATION]); + } + + int8_t get_release_duration_cv() const { + return values_[SEQ_CHANNEL_SETTING_ENV_RELEASE_CV_SOURCE]; + } + + peaks::EnvelopeShape get_release_shape() const { + return static_cast(values_[SEQ_CHANNEL_SETTING_ENV_RELEASE_SHAPE]); + } + + peaks::EnvResetBehaviour get_attack_reset_behaviour() const { + return static_cast(values_[SEQ_CHANNEL_SETTING_ENV_ATTACK_RESET_BEHAVIOUR]); + } + + peaks::EnvFallingGateBehaviour get_attack_falling_gate_behaviour() const { + return static_cast(values_[SEQ_CHANNEL_SETTING_ENV_ATTACK_FALLING_GATE_BEHAVIOUR]); + } + + peaks::EnvResetBehaviour get_decay_release_reset_behaviour() const { + return static_cast(values_[SEQ_CHANNEL_SETTING_ENV_DECAY_RELEASE_RESET_BEHAVIOUR]); + } + + uint16_t get_max_loops() const { + return values_[SEQ_CHANNEL_SETTING_ENV_MAX_LOOPS] << 9 ; + } + + uint8_t get_env_loops_cv_source() const { + return values_[SEQ_CHANNEL_SETTING_ENV_LOOPS_CV_SOURCE]; + } + + void update_pattern_mask(uint16_t mask, uint8_t sequence) { + + switch(sequence) { + + case 1: + apply_value(SEQ_CHANNEL_SETTING_MASK2, mask); + break; + case 2: + apply_value(SEQ_CHANNEL_SETTING_MASK3, mask); + break; + case 3: + apply_value(SEQ_CHANNEL_SETTING_MASK4, mask); + break; + default: + apply_value(SEQ_CHANNEL_SETTING_MASK1, mask); + break; + } + } + + int get_mask(uint8_t _this_num_sequence) const { + + switch(_this_num_sequence) { + + case 1: + return values_[SEQ_CHANNEL_SETTING_MASK2]; + break; + case 2: + return values_[SEQ_CHANNEL_SETTING_MASK3]; + break; + case 3: + return values_[SEQ_CHANNEL_SETTING_MASK4]; + break; + default: + return values_[SEQ_CHANNEL_SETTING_MASK1]; + break; + } + } + + void set_sequence_length(uint8_t len, uint8_t seq) { + + switch(seq) { + case 0: + apply_value(SEQ_CHANNEL_SETTING_SEQUENCE_LEN1, len); + break; + case 1: + apply_value(SEQ_CHANNEL_SETTING_SEQUENCE_LEN2, len); + break; + case 2: + apply_value(SEQ_CHANNEL_SETTING_SEQUENCE_LEN3, len); + break; + case 3: + apply_value(SEQ_CHANNEL_SETTING_SEQUENCE_LEN4, len); + break; + default: + break; + } + } + + uint16_t get_clock_cnt() const { + return clk_cnt_; + } + + uint32_t get_step_pitch() const { + return step_pitch_; + } + + uint32_t get_step_pitch_aux() const { + return step_pitch_aux_; + } + + uint32_t get_step_gate() const { + return gate_state_; + } + + uint32_t get_step_state() const { + return step_state_; + } + + int32_t get_pitch_at_step(uint8_t seq, uint8_t step) const { + + uint8_t _channel_offset = !channel_id_ ? 0x0 : OC::Patterns::NUM_PATTERNS; + + OC::Pattern *read_pattern_ = &OC::user_patterns[seq + _channel_offset]; + return read_pattern_->notes[step]; + } + + void set_pitch_at_step(uint8_t seq, uint8_t step, int32_t pitch) { + + uint8_t _channel_offset = !channel_id_ ? 0x0 : OC::Patterns::NUM_PATTERNS; + + OC::Pattern *write_pattern_ = &OC::user_patterns[seq + _channel_offset]; + write_pattern_->notes[step] = pitch; + } + + uint16_t get_rotated_scale_mask() const { + return last_scale_mask_; + } + + void clear_user_pattern(uint8_t seq) { + + uint8_t _channel_offset = !channel_id_ ? 0x0 : OC::Patterns::NUM_PATTERNS; + memcpy(&OC::user_patterns[seq + _channel_offset], &OC::patterns[0], sizeof(OC::Pattern)); + } + + void copy_seq(uint8_t seq, uint8_t len, uint16_t mask) { + + // which sequence ? + copy_sequence = seq + (!channel_id_ ? 0x0 : OC::Patterns::NUM_PATTERNS); + copy_length = len; + copy_mask = mask; + copy_timeout = 0; + } + + uint8_t paste_seq(uint8_t seq) { + + if (copy_timeout < COPYTIMEOUT) { + + // which sequence to copy to ? + uint8_t sequence = seq + (!channel_id_ ? 0x0 : OC::Patterns::NUM_PATTERNS); + // copy length: + set_sequence_length(copy_length, seq); + // copy mask: + update_pattern_mask(copy_mask, seq); + // copy note values: + memcpy(&OC::user_patterns[sequence], &OC::user_patterns[copy_sequence], sizeof(OC::Pattern)); + // give more time for more pasting... + copy_timeout = 0; + + return copy_length; + } + else + return 0; + } + + uint8_t getTriggerState() const { + return clock_display_.getState(); + } + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + void pattern_changed(uint16_t mask, bool force_update) { + + force_update_ = force_update; + if (force_update) + display_mask_ = mask; + + if (get_playmode() == PM_ARP) { + // update note stack + uint8_t seq = active_sequence_; + arpeggiator_.UpdateArpeggiator(channel_id_, seq, get_mask(seq), get_sequence_length(seq)); + } + } + + void sync() { + pending_sync_ = true; + } + + void clear_CV_mapping() { + apply_value(SEQ_CHANNEL_SETTING_PULSEWIDTH_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_MULT_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_OCTAVE_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_ROOT_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_OCTAVE_AUX_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_SEQ_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_SCALE_MASK_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_TRANSPOSE_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_DIRECTION_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_SEQUENCE_ARP_DIRECTION_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_SEQUENCE_ARP_RANGE_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_BROWNIAN_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_LENGTH_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_ENV_ATTACK_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_ENV_DECAY_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_ENV_SUSTAIN_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_ENV_RELEASE_CV_SOURCE, 0); + apply_value(SEQ_CHANNEL_SETTING_ENV_LOOPS_CV_SOURCE, 0); + } + + int get_scale(uint8_t dummy) const { + return values_[SEQ_CHANNEL_SETTING_SCALE]; + } + + void set_scale(int scale) { + apply_value(SEQ_CHANNEL_SETTING_SCALE, scale); + } + + // dummy + int get_scale_select() const { + return 0; + } + + // dummy + void set_scale_at_slot(int scale, uint16_t mask, int root, int transpose, uint8_t scale_slot) { + + } + + // dummy + int get_transpose(uint8_t DUMMY) const { + return 0; + } + + int get_scale_mask(uint8_t scale_select) const { + return values_[SEQ_CHANNEL_SETTING_SCALE_MASK]; + } + + void scale_changed() { + force_scale_update_ = true; + } + + void update_scale_mask(uint16_t mask, uint16_t dummy) { + force_scale_update_ = true; + apply_value(SEQ_CHANNEL_SETTING_SCALE_MASK, mask); // Should automatically be updated + } + + void update_inputmap(int num_slots, uint8_t range) { + input_map_.Configure(OC::InputMaps::GetInputMap(num_slots), range); + } + + int get_cv_input_range() const { + return values_[SEQ_CHANNEL_SETTING_SEQUENCE_PLAYMODE_CV_RANGES]; + } + + void Init(SEQ_ChannelTriggerSource trigger_source, uint8_t id) { + + InitDefaults(); + trigger_delay_.Init(); + channel_id_ = id; + octave_toggle_ = false; + wait_for_EoS_ = false; + note_repeat_ = false; + menu_page_ = PARAMETERS; + apply_value(SEQ_CHANNEL_SETTING_CLOCK, trigger_source); + quantizer_.Init(); + quantizer_.Requantize(); + input_map_.Init(); + env_.Init(); + force_update_ = true; + force_scale_update_ = true; + gate_state_ = step_state_ = OFF; + step_pitch_ = 0; + step_pitch_aux_ = 0; + subticks_ = 0; + tickjitter_ = 10000; + clk_cnt_ = 0; + clk_src_ = get_clock_source(); + prev_reset_state_ = false; + reset_pending_ = false; + prev_multiplier_ = get_multiplier(); + prev_pulsewidth_ = get_pulsewidth(); + prev_input_range_ = 0; + prev_playmode_ = get_playmode(); + pending_sync_ = false; + sequence_change_pending_ = 0x0; + prev_gate_raised_ = 0 ; + env_gate_raised_ = 0 ; + env_gate_state_ = 0 ; + + ext_frequency_in_ticks_ = 0xFFFFFFFF; + channel_frequency_in_ticks_ = 0xFFFFFFFF; + pulse_width_in_ticks_ = get_pulsewidth() << 10; + + display_num_sequence_ = get_sequence(); + display_mask_ = get_mask(display_num_sequence_); + active_sequence_ = display_num_sequence_; + sequence_manual_ = display_num_sequence_; + sequence_advance_state_ = false; + pendulum_fwd_ = true; + uint32_t _seed = OC::ADC::value() + OC::ADC::value() + OC::ADC::value() + OC::ADC::value(); + randomSeed(_seed); + clock_display_.Init(); + arpeggiator_.Init(); + update_enabled_settings(0); + } + + bool rotate_scale(int32_t mask_rotate) { + + uint16_t scale_mask = get_scale_mask(DUMMY); + const int scale = get_scale(DUMMY); + + if (mask_rotate) + scale_mask = OC::ScaleEditor::RotateMask(scale_mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); + + if (last_scale_mask_ != scale_mask) { + + force_scale_update_ = false; + last_scale_ = scale; + last_scale_mask_ = scale_mask; + quantizer_.Configure(OC::Scales::GetScale(scale), scale_mask); + return true; + } else { + return false; + } + } + + bool update_scale(bool force) { + + const int scale = get_scale(DUMMY); + + if (force || last_scale_ != scale) { + + uint16_t scale_mask = get_scale_mask(DUMMY); + force_scale_update_ = false; + last_scale_ = scale; + last_scale_mask_ = scale_mask; + quantizer_.Configure(OC::Scales::GetScale(scale), scale_mask); + return true; + } else { + return false; + } + } + + void force_update() { + force_update_ = true; + } + + bool update_timeout() { + // wait for ~ 1.5 sec + return (subticks_ > 30000) ? true : false; + } + + /* main channel update below: */ + + inline void Update(uint32_t triggers, DAC_CHANNEL dac_channel) { + + // increment channel ticks .. + subticks_++; + + int8_t _clock_source, _reset_source = 0x0, _aux_mode, _playmode; + int8_t _multiplier = 0x0; + bool _none, _triggered, _tock, _sync, _continuous; + uint32_t _subticks = 0x0, prev_channel_frequency_in_ticks_ = 0x0; + + // core channel parameters -- + // 1. clock source: + _clock_source = get_clock_source(); + + // 2. sequencer aux channel / play modes - + _aux_mode = get_aux_mode(); + _playmode = get_playmode(); + _continuous = _playmode >= PM_SH1 ? true : false; + + // 3. update scale? + update_scale(force_scale_update_); + + // clocked ? + _none = SEQ_CHANNEL_TRIGGER_NONE == _clock_source; + // TR1 or TR3? + _triggered = _clock_source ? (!_none && (triggers & (1 << OC::DIGITAL_INPUT_3))) : (!_none && (triggers & (1 << OC::DIGITAL_INPUT_1))); + _tock = false; + _sync = false; + + // trigger delay: + if (_playmode < PM_SH1) { + + trigger_delay_.Update(); + if (_triggered) + trigger_delay_.Push(OC::trigger_delay_ticks[get_trigger_delay()]); + _triggered = trigger_delay_.triggered(); + } + + // new tick frequency: + if (_clock_source <= SEQ_CHANNEL_TRIGGER_TR2) { + + if (_triggered || clk_src_ != _clock_source) { + ext_frequency_in_ticks_ = ext_frequency[_clock_source]; + _tock = true; + div_cnt_--; + } + } + // store clock source: + clk_src_ = _clock_source; + + if (!_continuous) { + + _multiplier = get_multiplier(); + + if (get_mult_cv_source()) { + _multiplier += (OC::ADC::value(static_cast(get_mult_cv_source() - 1)) + 127) >> 8; + CONSTRAIN(_multiplier, 0, MULT_MAX); + } + + // new multiplier ? + if (prev_multiplier_ != _multiplier) + _tock |= true; + prev_multiplier_ = _multiplier; + + // if so, recalculate channel frequency and corresponding jitter-thresholds: + if (_tock) { + + // when multiplying, skip too closely spaced triggers: + if (_multiplier > MULT_BY_ONE) { + prev_channel_frequency_in_ticks_ = multiply_u32xu32_rshift32(channel_frequency_in_ticks_, TICK_SCALE); + // new frequency: + channel_frequency_in_ticks_ = multiply_u32xu32_rshift32(ext_frequency_in_ticks_, multipliers_[_multiplier-MULT_BY_ONE]); + } + else { + prev_channel_frequency_in_ticks_ = 0x0; + // new frequency (used for pulsewidth): + channel_frequency_in_ticks_ = multiply_u32xu32_rshift32(ext_frequency_in_ticks_, pw_scale_[_multiplier]) << 6; + } + + tickjitter_ = multiply_u32xu32_rshift32(channel_frequency_in_ticks_, TICK_JITTER); + } + // limit frequency to > 0 + if (!channel_frequency_in_ticks_) + channel_frequency_in_ticks_ = 1u; + + // reset? + _reset_source = get_reset_source(); + + if (_reset_source < SEQ_CHANNEL_TRIGGER_NONE && !reset_pending_) { + + uint8_t reset_state_ = !_reset_source ? digitalReadFast(TR2) : digitalReadFast(TR4); // TR1, TR3 are main clock sources + + // ? + if (reset_state_ < prev_reset_state_) { + div_cnt_ = 0x0; + reset_pending_ = true; // reset clock counter below + } + prev_reset_state_ = reset_state_; + } + + /* + * brute force ugly sync hack: + * this, presumably, is needlessly complicated. + * but seems to work ok-ish, w/o too much jitter and missing clocks... + */ + + _subticks = subticks_; + // sync? (manual) + div_cnt_ = pending_sync_ ? 0x0 : div_cnt_; + + if (_multiplier <= MULT_BY_ONE && _triggered && div_cnt_ <= 0) { + // division, so we track + _sync = true; + div_cnt_ = divisors_[_multiplier]; + subticks_ = channel_frequency_in_ticks_; // force sync + } + else if (_multiplier <= MULT_BY_ONE && _triggered) { + // division, mute output: + step_state_ = OFF; + } + else if (_multiplier > MULT_BY_ONE && _triggered) { + // multiplication, force sync, if clocked: + _sync = true; + subticks_ = channel_frequency_in_ticks_; + } + else if (_multiplier > MULT_BY_ONE) + _sync = true; + // end of ugly hack + } + else { + // S+H mode + if (_playmode <= PM_SH4) { + + if (_triggered) { + // new frequency (used for pulsewidth): + channel_frequency_in_ticks_ = ext_frequency_in_ticks_; + subticks_ = 0x0; + } + else + // don't trigger, if no trigger - see below + _continuous = _sync = false; + } + } + + // time to output ? + if ((subticks_ >= channel_frequency_in_ticks_ && _sync) || _continuous) { + + if (!_continuous) { + // reset ticks + subticks_ = 0x0; + //reject, if clock is too jittery or skip quasi-double triggers when ext. frequency increases: + if (_subticks < tickjitter_ || (_subticks < prev_channel_frequency_in_ticks_ && !reset_pending_)) + return; + } + + // resync/clear pending sync + if (_triggered && pending_sync_) { + pending_sync_ = false; + clk_cnt_ = 0x0; + } + + // mute output ? + bool mute = 0; + + switch (_reset_source) { + + case SEQ_CHANNEL_TRIGGER_TR1: + case SEQ_CHANNEL_TRIGGER_TR2: + case SEQ_CHANNEL_TRIGGER_NONE: + break; + case SEQ_CHANNEL_TRIGGER_FREEZE_HI2: + mute = !digitalReadFast(TR2); + break; + case SEQ_CHANNEL_TRIGGER_FREEZE_LO2: + mute = digitalReadFast(TR2); + break; + case SEQ_CHANNEL_TRIGGER_FREEZE_HI4: + mute = !digitalReadFast(TR4); + break; + case SEQ_CHANNEL_TRIGGER_FREEZE_LO4: + mute = digitalReadFast(TR4); + break; + default: + break; + } + + if (mute) + return; + + // mask CV ? + if (get_scale_mask_cv_source()) { + int16_t _rotate = (OC::ADC::value(static_cast(get_scale_mask_cv_source() - 1)) + 127) >> 8; + rotate_scale(_rotate); + } + + // finally, process trigger + output: + if (process_num_seq_channel(_playmode, reset_pending_)) { + + // turn on gate + gate_state_ = ON; + + int8_t _octave = get_octave(); + if (get_octave_cv_source()) + _octave += (OC::ADC::value(static_cast(get_octave_cv_source() - 1)) + 255) >> 9; + + int8_t _transpose = 0x0; + if (get_transpose_cv_source()) { + _transpose += (OC::ADC::value(static_cast(get_transpose_cv_source() - 1)) + 64) >> 7; + CONSTRAIN(_transpose, -12, 12); + } + + int8_t _root = get_root(0x0); + if (get_root_cv_source()) { + _root += (OC::ADC::value(static_cast(get_root_cv_source() - 1)) + 127) >> 8; + CONSTRAIN(_root, 0, 11); + } + + if (_playmode != PM_ARP) { + // use the current sequence, updated in process_num_seq_channel(): + step_pitch_ = get_pitch_at_step(display_num_sequence_, clk_cnt_) + (_octave * 12 << 7); + } + else { + + int8_t arp_range = get_arp_range(); + if (get_arp_range_cv_source()) { + arp_range += (OC::ADC::value(static_cast(get_arp_range_cv_source() - 1)) + 255) >> 9; + CONSTRAIN(arp_range, 0, 4); + } + arpeggiator_.set_range(arp_range); + + int8_t arp_direction = get_arp_direction(); + if (get_arp_direction_cv_source()) { + arp_direction += (OC::ADC::value(static_cast(get_arp_direction_cv_source() - 1)) + 255) >> 9; + CONSTRAIN(arp_direction, 0, 4); + } + arpeggiator_.set_direction(arp_direction); + + step_pitch_ = arpeggiator_.ClockArpeggiator() + (_octave * 12 << 7); + // mute ? + if (step_pitch_ == 0xFFFFFF) + gate_state_ = step_state_ = OFF; + } + // update output: + step_pitch_ = quantizer_.Process(step_pitch_, _root << 7, _transpose); + + int32_t _attack = get_attack_duration(); + int32_t _decay = get_decay_duration(); + int32_t _sustain = get_sustain_level(); + int32_t _release = get_release_duration(); + int32_t _loops = get_max_loops(); + + switch (_aux_mode) { + case ENV_AD: + case ENV_ADR: + case ENV_ADSR: + if (get_attack_duration_cv()) { + _attack += OC::ADC::value(static_cast(get_attack_duration_cv() - 1)) << 3; + USAT16(_attack) ; + } + if (get_decay_duration_cv()) { + _decay += OC::ADC::value(static_cast(get_decay_duration_cv() - 1)) << 3; + USAT16(_decay); + } + if (get_sustain_level_cv()) { + _sustain += OC::ADC::value(static_cast(get_sustain_level_cv() - 1)) << 4; + CONSTRAIN(_sustain, 0, 65534); + } + if (get_release_duration_cv()) { + _release += OC::ADC::value(static_cast(get_release_duration_cv() - 1)) << 3; + USAT16(_release) ; + } + if (get_env_loops_cv_source()) { + _loops += OC::ADC::value(static_cast(get_env_loops_cv_source() - 1)) ; + CONSTRAIN(_loops,1<<8, 65534) ; + } + // set the specified reset behaviours + env_.set_attack_reset_behaviour(get_attack_reset_behaviour()); + env_.set_attack_falling_gate_behaviour(get_attack_falling_gate_behaviour()); + env_.set_decay_release_reset_behaviour(get_decay_release_reset_behaviour()); + // set number of loops + env_.set_max_loops(_loops); + break; + default: + break; + } + + switch (_aux_mode) { + + case COPY: + { + int8_t _octave_aux = _octave + get_octave_aux(); + if (get_octave_aux_cv_source()) + _octave_aux += (OC::ADC::value(static_cast(get_octave_aux_cv_source() - 1)) + 255) >> 9; + + if (_playmode != PM_ARP) + step_pitch_aux_ = get_pitch_at_step(display_num_sequence_, clk_cnt_) + (_octave_aux * 12 << 7); + else + // this *might* not be quite a copy... + step_pitch_aux_ = step_pitch_ + (_octave_aux * 12 << 7); + step_pitch_aux_ = quantizer_.Process(step_pitch_aux_, _root << 7, _transpose); + } + break; + case ENV_AD: + { + env_.set_ad(_attack, _decay, 0, 2); + env_.set_attack_shape(get_attack_shape()); + env_.set_decay_shape(get_decay_shape()); + } + break; + case ENV_ADR: + { + env_.set_adr(_attack, _decay, _sustain >> 1, _release, 0, 2); + env_.set_attack_shape(get_attack_shape()); + env_.set_decay_shape(get_decay_shape()); + env_.set_release_shape(get_release_shape()); + } + break; + case ENV_ADSR: + { + env_.set_adsr(_attack, _decay, _sustain >> 1, _release); + env_.set_attack_shape(get_attack_shape()); + env_.set_decay_shape(get_decay_shape()); + env_.set_release_shape(get_release_shape()); + } + break; + default: + break; + } + } + // clear for reset: + reset_pending_ = false; + } + + /* + * below: pulsewidth stuff + */ + + if (_aux_mode != COPY && gate_state_) { + + // pulsewidth setting -- + int16_t _pulsewidth = get_pulsewidth(); + bool _we_cannot_echo = _playmode >= PM_CV1 ? true : false; + + if (_pulsewidth || _multiplier > MULT_BY_ONE || _we_cannot_echo) { + + bool _gates = false; + + // do we echo && multiply? if so, do half-duty cycle: + if (!_pulsewidth) + _pulsewidth = PULSEW_MAX; + + if (_pulsewidth == PULSEW_MAX) + _gates = true; + // CV? + if (get_pulsewidth_cv_source()) { + + _pulsewidth += (OC::ADC::value(static_cast(get_pulsewidth_cv_source() - 1)) + 4) >> 3; + if (!_gates) + CONSTRAIN(_pulsewidth, 1, PULSEW_MAX); + else // CV for 50% duty cycle: + CONSTRAIN(_pulsewidth, 1, (PULSEW_MAX<<1) - 55); // incl margin, max < 2x mult. see below + } + // recalculate (in ticks), if new pulsewidth setting: + if (prev_pulsewidth_ != _pulsewidth || ! subticks_) { + if (!_gates || _we_cannot_echo) { + int32_t _fraction = signed_multiply_32x16b(TICKS_TO_MS, static_cast(_pulsewidth)); // = * 0.6667f + _fraction = signed_saturate_rshift(_fraction, 16, 0); + pulse_width_in_ticks_ = (_pulsewidth << 4) + _fraction; + } + else { // put out gates/half duty cycle: + pulse_width_in_ticks_ = channel_frequency_in_ticks_ >> 1; + + if (_pulsewidth != PULSEW_MAX) { // CV? + pulse_width_in_ticks_ = signed_multiply_32x16b(static_cast(_pulsewidth) << 8, pulse_width_in_ticks_); // + pulse_width_in_ticks_ = signed_saturate_rshift(pulse_width_in_ticks_, 16, 0); + } + } + } + prev_pulsewidth_ = _pulsewidth; + + // limit pulsewidth, if approaching half duty cycle: + if (!_gates && pulse_width_in_ticks_ >= channel_frequency_in_ticks_>>1) + pulse_width_in_ticks_ = (channel_frequency_in_ticks_ >> 1) | 1u; + + // turn off output? + if (subticks_ >= pulse_width_in_ticks_) + gate_state_ = OFF; + else // keep on + gate_state_ = ON; + } + else { + // we simply echo the pulsewidth: + bool _state = (_clock_source == SEQ_CHANNEL_TRIGGER_TR1) ? !digitalReadFast(TR1) : !digitalReadFast(TR3); + + if (_state) + gate_state_ = ON; + else + gate_state_ = OFF; + } + } + } // end update + + /* details re: sequence processing happens (mostly) here: */ + inline bool process_num_seq_channel(uint8_t playmode, uint8_t reset) { + + bool _out = true; + bool _change = true; + bool _reset = reset; + int8_t _playmode, sequence_max, sequence_cnt, _num_seq, num_sequence_cv, sequence_length, sequence_length_cv; + + _num_seq = get_sequence(); + _playmode = playmode; + sequence_max = 0x0; + sequence_cnt = 0x0; + num_sequence_cv = 0x0; + sequence_length = 0x0; + sequence_length_cv = 0x0; + + if (_num_seq != sequence_manual_) { + // setting changed ... + if (!wait_for_EoS_) { + _reset = true; + if (_playmode >= PM_TR1 && _playmode <= PM_TR3) + active_sequence_ = _num_seq; + } + else if (_playmode < PM_SH1) + sequence_change_pending_ = WAIT; + } + sequence_manual_ = _num_seq; + + if (sequence_change_pending_ == WAIT) + _num_seq = active_sequence_; + else if (sequence_change_pending_ == CLEAR) { + _reset = true; + sequence_change_pending_ = ALL_OK; + if (_playmode >= PM_TR1 && _playmode <= PM_TR3) + active_sequence_ = _num_seq; + } + + if (get_sequence_cv_source()) { + num_sequence_cv = _num_seq += (OC::ADC::value(static_cast(get_sequence_cv_source() - 1)) + 255) >> 9; + CONSTRAIN(_num_seq, 0, OC::Patterns::PATTERN_USER_LAST - 0x1); + } + + if (get_sequence_length_cv_source()) + sequence_length_cv = (OC::ADC::value(static_cast(get_sequence_length_cv_source() - 1)) + 64) >> 7; + + switch (_playmode) { + + case PM_NONE: + active_sequence_ = _num_seq; + break; + case PM_ARP: + sequence_change_pending_ = ALL_OK; + sequence_length = get_sequence_length(_num_seq) + sequence_length_cv; + CONSTRAIN(sequence_length, OC::Patterns::kMin, OC::Patterns::kMax); + + if (active_sequence_ != _num_seq || sequence_length != active_sequence_length_ || prev_playmode_ != _playmode) + arpeggiator_.UpdateArpeggiator(channel_id_, _num_seq, get_mask(_num_seq), sequence_length); + active_sequence_ = _num_seq; + active_sequence_length_ = sequence_length; + if (_reset) + arpeggiator_.reset(); + prev_playmode_ = _playmode; + // and skip the stuff below: + _playmode = 0xFF; + break; + case PM_SEQ1: + case PM_SEQ2: + case PM_SEQ3: + { + // concatenate sequences: + sequence_max = _playmode; + + if (sequence_EoS_) { + + // increment sequence # + sequence_cnt_ += sequence_EoS_; + // reset sequence # + sequence_cnt_ = sequence_cnt_ > sequence_max ? 0x0 : sequence_cnt_; + // update + active_sequence_ = _num_seq + sequence_cnt_; + // wrap around: + if (active_sequence_ >= OC::Patterns::PATTERN_USER_LAST) + active_sequence_ -= OC::Patterns::PATTERN_USER_LAST; + // reset + _clock(get_sequence_length(active_sequence_), 0x0, sequence_max, true); + _reset = true; + } + else if (num_sequence_cv) { + active_sequence_ += num_sequence_cv; + CONSTRAIN(active_sequence_, 0, OC::Patterns::PATTERN_USER_LAST - 1); + } + sequence_cnt = sequence_cnt_; + } + break; + case PM_TR1: + case PM_TR2: + case PM_TR3: + { + sequence_max = _playmode - PM_SEQ3; + prev_playmode_ = _playmode; + // trigger? + uint8_t _advance_trig = (channel_id_ == DAC_CHANNEL_A) ? digitalReadFast(TR2) : digitalReadFast(TR4); + + if (_advance_trig < sequence_advance_state_) { + + // increment sequence # + sequence_cnt_++; + // reset sequence # + sequence_cnt_ = sequence_cnt_ > sequence_max ? 0x0 : sequence_cnt_; + // update + active_sequence_ = _num_seq + sequence_cnt_; + // + reset + _reset = true; + // wrap around: + if (active_sequence_ >= OC::Patterns::PATTERN_USER_LAST) + active_sequence_ -= OC::Patterns::PATTERN_USER_LAST; + } + else if (num_sequence_cv) { + active_sequence_ += num_sequence_cv; + CONSTRAIN(active_sequence_, 0, OC::Patterns::PATTERN_USER_LAST - 1); + } + sequence_advance_state_ = _advance_trig; + sequence_max = 0x0; + } + break; + case PM_SH1: + case PM_SH2: + case PM_SH3: + case PM_SH4: + { + int input_range; + sequence_length = get_sequence_length(_num_seq) + sequence_length_cv; + CONSTRAIN(sequence_length, OC::Patterns::kMin, OC::Patterns::kMax); + input_range = get_cv_input_range(); + + // length or range changed ? + if (active_sequence_length_ != sequence_length || input_range != prev_input_range_ || prev_playmode_ != _playmode) + update_inputmap(sequence_length, input_range); + // store values: + active_sequence_ = _num_seq; + active_sequence_length_ = sequence_length; + prev_input_range_ = input_range; + prev_playmode_ = _playmode; + + // process input: + if (!input_range) + clk_cnt_ = input_map_.Process(OC::ADC::value(static_cast(_playmode - PM_SH1))); + else + clk_cnt_ = input_map_.Process(0xFFF - OC::ADC::smoothed_raw_value(static_cast(_playmode - PM_SH1))); + } + break; + case PM_CV1: + case PM_CV2: + case PM_CV3: + case PM_CV4: + { + int input_range; + sequence_length = get_sequence_length(_num_seq) + sequence_length_cv; + CONSTRAIN(sequence_length, OC::Patterns::kMin, OC::Patterns::kMax); + input_range = get_cv_input_range(); + // length changed ? + if (active_sequence_length_ != sequence_length || input_range != prev_input_range_ || prev_playmode_ != _playmode) + update_inputmap(sequence_length, input_range); + // store values: + active_sequence_length_ = sequence_length; + prev_input_range_ = input_range; + active_sequence_ = _num_seq; + prev_playmode_ = _playmode; + + // process input: + if (!input_range) + clk_cnt_ = input_map_.Process(OC::ADC::value(static_cast(_playmode - PM_CV1))); // = 5V + else + clk_cnt_ = input_map_.Process(0xFFF - OC::ADC::smoothed_raw_value(static_cast(_playmode - PM_CV1))); // = 10V + + // update output, if slot # changed: + if (prev_slot_ == clk_cnt_) + _change = false; + else { + subticks_ = 0x0; + prev_slot_ = clk_cnt_; + } + } + break; + default: + break; + } + // end switch + + _num_seq = active_sequence_; + + if (_playmode < PM_SH1) { + + sequence_length = get_sequence_length(_num_seq) + sequence_length_cv; + if (sequence_length_cv) + CONSTRAIN(sequence_length, OC::Patterns::kMin, OC::Patterns::kMax); + + CONSTRAIN(clk_cnt_, 0x0, sequence_length); + sequence_EoS_ = _clock(sequence_length - 0x1, sequence_cnt, sequence_max, _reset); + } + + // this is the current sequence # (USER1-USER4): + display_num_sequence_ = _num_seq; + // and corresponding pattern mask: + display_mask_ = get_mask(_num_seq); + // ... and the length + active_sequence_length_ = sequence_length; + + // slot at current position: + if (_playmode != PM_ARP) { + _out = (display_mask_ >> clk_cnt_) & 1u; + step_state_ = _out ? ON : OFF; + _out = (_out && _change) ? true : false; + } + else { + step_state_ = ON; + clk_cnt_ = 0x0; + } + // return step: + return _out; + } + + // update sequencer clock, return -1, 0, 1 when EoS is reached: + + int8_t _clock(uint8_t sequence_length, uint8_t sequence_count, uint8_t sequence_max, bool _reset) { + + int8_t EoS = 0x0, _clk_cnt, _direction; + bool reset = _reset; + + _clk_cnt = clk_cnt_; + _direction = get_direction(); + + if (get_direction_cv()) { + _direction += (OC::ADC::value(static_cast(get_direction_cv() - 1)) + 255) >> 9; + CONSTRAIN(_direction, 0, SEQ_DIRECTIONS_LAST - 0x1); + } + + switch (_direction) { + + case FORWARD: + { + _clk_cnt++; + if (reset) + _clk_cnt = 0x0; + // end of sequence? + else if (_clk_cnt > sequence_length) + _clk_cnt = 0x0; + else if (_clk_cnt == sequence_length) { + EoS = 0x1; + } + } + break; + case REVERSE: + { + _clk_cnt--; + if (reset) + _clk_cnt = sequence_length; + // end of sequence? + else if (_clk_cnt < 0) + _clk_cnt = sequence_length; + else if (!_clk_cnt) + EoS = 0x1; + } + break; + case PENDULUM1: + case BROWNIAN: + if (BROWNIAN == _direction) { + // Compare Brownian probability and reverse direction if needed + int16_t brown_prb = get_brownian_probability(); + + if (get_brownian_probability_cv()) { + brown_prb += (OC::ADC::value(static_cast(get_brownian_probability_cv() - 1)) + 8) >> 3; + CONSTRAIN(brown_prb, 0, 256); + } + if (random(0,256) < brown_prb) + pendulum_fwd_ = !pendulum_fwd_; + } + { + if (pendulum_fwd_) { + _clk_cnt++; + if (reset) + _clk_cnt = 0x0; + else if (_clk_cnt >= sequence_length) { + + if (sequence_count >= sequence_max) { + pendulum_fwd_ = false; + _clk_cnt = sequence_length; + } + else EoS = 0x1; + // pendulum needs special care (when PM_NONE) + if (!pendulum_fwd_ && sequence_change_pending_ == WAIT) sequence_change_pending_ = CLEAR; + } + } + // reverse direction: + else { + _clk_cnt--; + if (reset) + _clk_cnt = sequence_length; + else if (_clk_cnt <= 0) { + // end of sequence ? + if (sequence_count == 0x0) { + pendulum_fwd_ = true; + _clk_cnt = 0x0; + } + else EoS = -0x1; + if (pendulum_fwd_ && sequence_change_pending_ == WAIT) sequence_change_pending_ = CLEAR; + } + } + } + break; + case PENDULUM2: + { + if (pendulum_fwd_) { + + if (!note_repeat_) + _clk_cnt++; + note_repeat_ = false; + + if (reset) + _clk_cnt = 0x0; + else if (_clk_cnt >= sequence_length) { + // end of sequence ? + if (sequence_count >= sequence_max) { + pendulum_fwd_ = false; + _clk_cnt = sequence_length; + note_repeat_ = true; // repeat last step + } + else EoS = 0x1; + if (!pendulum_fwd_ && sequence_change_pending_ == WAIT) sequence_change_pending_ = CLEAR; + } + } + // reverse direction: + else { + + if (!note_repeat_) + _clk_cnt--; + note_repeat_ = false; + + if (reset) + _clk_cnt = sequence_length; + else if (_clk_cnt <= 0x0) { + // end of sequence ? + if (sequence_count == 0x0) { + pendulum_fwd_ = true; + _clk_cnt = 0x0; + note_repeat_ = true; // repeat first step + } + else EoS = -0x1; + if (pendulum_fwd_ && sequence_change_pending_ == WAIT) sequence_change_pending_ = CLEAR; + } + } + } + break; + case RANDOM: + _clk_cnt = random(sequence_length + 0x1); + if (reset) + _clk_cnt = 0x0; + // jump to next sequence if we happen to hit the last note: + else if (_clk_cnt >= sequence_length) + EoS = random(0x2); + break; + default: + break; + } + clk_cnt_ = _clk_cnt; + + if (EoS && sequence_change_pending_ == WAIT) sequence_change_pending_ = CLEAR; + return EoS; + } + + SEQ_ChannelSetting enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + void update_enabled_settings(uint8_t channel_id) { + + SEQ_ChannelSetting *settings = enabled_settings_; + + switch(get_menu_page()) { + + case PARAMETERS: { + + *settings++ = SEQ_CHANNEL_SETTING_SCALE; + *settings++ = SEQ_CHANNEL_SETTING_SCALE_MASK; + *settings++ = SEQ_CHANNEL_SETTING_SEQUENCE; + + switch (get_sequence()) { + + case 0: + *settings++ = SEQ_CHANNEL_SETTING_MASK1; + break; + case 1: + *settings++ = SEQ_CHANNEL_SETTING_MASK2; + break; + case 2: + *settings++ = SEQ_CHANNEL_SETTING_MASK3; + break; + case 3: + *settings++ = SEQ_CHANNEL_SETTING_MASK4; + break; + default: + break; + } + + *settings++ = SEQ_CHANNEL_SETTING_SEQUENCE_PLAYMODE; + + if (get_playmode() < PM_SH1) { + + *settings++ = (get_playmode() == PM_ARP) ? SEQ_CHANNEL_SETTING_SEQUENCE_ARP_DIRECTION : SEQ_CHANNEL_SETTING_SEQUENCE_DIRECTION; + if (get_playmode() == PM_ARP) + *settings++ = SEQ_CHANNEL_SETTING_SEQUENCE_ARP_RANGE; + else if (get_direction() == BROWNIAN) + *settings++ = SEQ_CHANNEL_SETTING_BROWNIAN_PROBABILITY; + *settings++ = SEQ_CHANNEL_SETTING_MULT; + } + else + *settings++ = SEQ_CHANNEL_SETTING_SEQUENCE_PLAYMODE_CV_RANGES; + + *settings++ = SEQ_CHANNEL_SETTING_OCTAVE; + *settings++ = SEQ_CHANNEL_SETTING_ROOT; + // aux output: + *settings++ = SEQ_CHANNEL_SETTING_MODE; + + switch (get_aux_mode()) { + case GATE_OUT: + *settings++ = SEQ_CHANNEL_SETTING_PULSEWIDTH; + break; + case COPY: + *settings++ = SEQ_CHANNEL_SETTING_OCTAVE_AUX; + break; + case ENV_AD: + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_SHAPE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_SHAPE; + *settings++ = SEQ_CHANNEL_SETTING_PULSEWIDTH; + *settings++ = SEQ_CHANNEL_SETTING_ENV_MAX_LOOPS; + break; + case ENV_ADR: + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_SHAPE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_SHAPE; + *settings++ = SEQ_CHANNEL_SETTING_PULSEWIDTH; + *settings++ = SEQ_CHANNEL_SETTING_ENV_SUSTAIN_LEVEL; + *settings++ = SEQ_CHANNEL_SETTING_ENV_RELEASE_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_RELEASE_SHAPE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_MAX_LOOPS; + break; + case ENV_ADSR: + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_SHAPE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_SHAPE; + *settings++ = SEQ_CHANNEL_SETTING_PULSEWIDTH; + *settings++ = SEQ_CHANNEL_SETTING_ENV_SUSTAIN_LEVEL; + *settings++ = SEQ_CHANNEL_SETTING_ENV_RELEASE_DURATION; + *settings++ = SEQ_CHANNEL_SETTING_ENV_RELEASE_SHAPE; + break; + default: + break; + } + + if (get_playmode() < PM_SH1) { + *settings++ = SEQ_CHANNEL_SETTING_RESET; + *settings++ = SEQ_CHANNEL_SETTING_CLOCK; + } + } + break; + + case CV_MAPPING: { + + *settings++ = SEQ_CHANNEL_SETTING_TRANSPOSE_CV_SOURCE; // = transpose SCALE + *settings++ = SEQ_CHANNEL_SETTING_SCALE_MASK_CV_SOURCE; // = rotate mask + *settings++ = SEQ_CHANNEL_SETTING_SEQ_CV_SOURCE; // sequence # + + switch (get_sequence()) { + + case 0: + *settings++ = SEQ_CHANNEL_SETTING_MASK1; + break; + case 1: + *settings++ = SEQ_CHANNEL_SETTING_MASK2; + break; + case 2: + *settings++ = SEQ_CHANNEL_SETTING_MASK3; + break; + case 3: + *settings++ = SEQ_CHANNEL_SETTING_MASK4; + break; + default: + break; + } + + *settings++ = SEQ_CHANNEL_SETTING_LENGTH_CV_SOURCE; // = playmode + + if (get_playmode() < PM_SH1) { + + if (get_playmode() == PM_ARP) { + *settings++ = SEQ_CHANNEL_SETTING_SEQUENCE_ARP_DIRECTION_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_SEQUENCE_ARP_RANGE_CV_SOURCE; + } + else *settings++ = SEQ_CHANNEL_SETTING_DIRECTION_CV_SOURCE; // = directions + + if (get_playmode() != PM_ARP && get_direction() == BROWNIAN) + *settings++ = SEQ_CHANNEL_SETTING_BROWNIAN_CV_SOURCE; + + *settings++ = SEQ_CHANNEL_SETTING_MULT_CV_SOURCE; + + } + else + *settings++ = SEQ_CHANNEL_SETTING_DUMMY; // = range + + *settings++ = SEQ_CHANNEL_SETTING_OCTAVE_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ROOT_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_DUMMY; // = mode + + switch (get_aux_mode()) { + + case GATE_OUT: + *settings++ = SEQ_CHANNEL_SETTING_PULSEWIDTH_CV_SOURCE; + break; + case COPY: + *settings++ = SEQ_CHANNEL_SETTING_OCTAVE_AUX_CV_SOURCE; + break; + case ENV_AD: + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_LOOPS_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_RESET_BEHAVIOUR; + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_FALLING_GATE_BEHAVIOUR; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_RELEASE_RESET_BEHAVIOUR; + break; + case ENV_ADR: + case ENV_ADSR: + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_SUSTAIN_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_RELEASE_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_LOOPS_CV_SOURCE; + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_RESET_BEHAVIOUR; + *settings++ = SEQ_CHANNEL_SETTING_ENV_ATTACK_FALLING_GATE_BEHAVIOUR; + *settings++ = SEQ_CHANNEL_SETTING_ENV_DECAY_RELEASE_RESET_BEHAVIOUR; + break; + default: + break; + } + + if (get_playmode() < PM_SH1) { + *settings++ = SEQ_CHANNEL_SETTING_TRIGGER_DELAY; // + *settings++ = SEQ_CHANNEL_SETTING_CLOCK; // = reset source + } + + *settings++ = SEQ_CHANNEL_SETTING_DUMMY; // = mode + *settings++ = SEQ_CHANNEL_SETTING_DUMMY; // = mode + *settings++ = SEQ_CHANNEL_SETTING_DUMMY; // = mode + *settings++ = SEQ_CHANNEL_SETTING_DUMMY; // = mode + + } + break; + default: + break; + } + num_enabled_settings_ = settings - enabled_settings_; + } + + template + void update_main_channel() { + int32_t _output = OC::DAC::pitch_to_scaled_voltage_dac(dacChannel, get_step_pitch(), 0, OC::DAC::get_voltage_scaling(dacChannel)); + OC::DAC::set(_output); + + } + + template + void update_aux_channel() + { + + int8_t _mode = get_aux_mode(); + uint32_t _output = 0; + + switch (_mode) { + + case GATE_OUT: // gate + #ifdef BUCHLA_4U + _output = (get_step_gate() == ON) ? OC::DAC::get_octave_offset(dacChannel, OCTAVES - OC::DAC::kOctaveZero - 0x2) : OC::DAC::get_zero_offset(dacChannel); + #else + _output = (get_step_gate() == ON) ? OC::DAC::get_octave_offset(dacChannel, OCTAVES - OC::DAC::kOctaveZero - 0x1) : OC::DAC::get_zero_offset(dacChannel); + #endif + break; + case COPY: // copy + _output = OC::DAC::pitch_to_scaled_voltage_dac(dacChannel, get_step_pitch_aux(), 0, OC::DAC::get_voltage_scaling(dacChannel)); + break; + // code to process envelopes here + case ENV_AD: + case ENV_ADR: + case ENV_ADSR: + env_gate_state_ = 0; + env_gate_raised_ = (get_step_gate() == ON); + if (env_gate_raised_ && !prev_gate_raised_) + env_gate_state_ |= peaks::CONTROL_GATE_RISING; + if (env_gate_raised_) + env_gate_state_ |= peaks::CONTROL_GATE; + else if (prev_gate_raised_) + env_gate_state_ |= peaks::CONTROL_GATE_FALLING; + prev_gate_raised_ = env_gate_raised_; + _output = OC::DAC::get_zero_offset(dacChannel) + env_.ProcessSingleSample(env_gate_state_); + break; + default: + break; + } + OC::DAC::set(_output); + } + + void RenderScreensaver() const; + +private: + + bool channel_id_; + bool octave_toggle_; + bool wait_for_EoS_; + bool note_repeat_; + bool sequence_EoS_; + uint8_t menu_page_; + uint16_t _sync_cnt; + bool force_update_; + bool force_scale_update_; + uint8_t clk_src_; + bool prev_reset_state_; + bool reset_pending_; + uint32_t subticks_; + uint32_t tickjitter_; + int32_t clk_cnt_; + int32_t prev_slot_; + int16_t div_cnt_; + uint32_t ext_frequency_in_ticks_; + uint32_t channel_frequency_in_ticks_; + uint32_t pulse_width_in_ticks_; + uint16_t gate_state_; + uint8_t prev_gate_raised_; + uint8_t env_gate_state_; + uint8_t env_gate_raised_; + uint16_t step_state_; + int32_t step_pitch_; + int32_t step_pitch_aux_; + uint8_t prev_multiplier_; + uint8_t prev_pulsewidth_; + uint8_t display_num_sequence_; + uint16_t display_mask_; + int8_t active_sequence_; + int8_t sequence_manual_; + int8_t active_sequence_length_; + int32_t sequence_cnt_; + int8_t sequence_advance_state_; + int8_t sequence_change_pending_; + int8_t pendulum_fwd_; + int last_scale_; + uint16_t last_scale_mask_; + uint8_t prev_input_range_; + uint8_t prev_playmode_; + bool pending_sync_; + + util::TriggerDelay trigger_delay_; + util::Arpeggiator arpeggiator_; + + int num_enabled_settings_; + SEQ_ChannelSetting enabled_settings_[SEQ_CHANNEL_SETTING_LAST]; + braids::Quantizer quantizer_; + OC::Input_Map input_map_; + OC::DigitalInputDisplay clock_display_; + peaks::MultistageEnvelope env_; + +}; + +const char* const SEQ_CHANNEL_TRIGGER_sources[] = { + "TR1", "TR3", " - " +}; + +const char* const reset_trigger_sources[] = { + "RST2", "RST4", " - ", "=HI2", "=LO2", "=HI4", "=LO4" +}; + +const char* const display_multipliers[] = { + "/64", "/63", "/62", "/49", "/48", "/47", "/33", "/32", "/31", "/17", "/16", "/15", "/8", "/7", "/6", "/5", "/4", "/3", "/2", "-", "x2", "x3", "x4", "x5", "x6", "x7", "x8" +}; + +const char* const modes[] = { + "gate", "copy", "AD", "ADR", "ADSR", +}; + +const char* const cv_ranges[] = { + " 5V", "10V" +}; + +const char* const arp_directions[] = { + "up", "down", "u/d", "rnd" +}; + +const char* const arp_range[] = { + "1", "2", "3", "4" +}; + +SETTINGS_DECLARE(SEQ_Channel, SEQ_CHANNEL_SETTING_LAST) { + + { 0, 0, 4, "aux. mode", modes, settings::STORAGE_TYPE_U4 }, + { SEQ_CHANNEL_TRIGGER_TR1, 0, SEQ_CHANNEL_TRIGGER_NONE, "clock src", SEQ_CHANNEL_TRIGGER_sources, settings::STORAGE_TYPE_U4 }, + { 0, 0, OC::kNumDelayTimes - 1, "trigger delay", OC::Strings::trigger_delay_times, settings::STORAGE_TYPE_U8 }, + { 2, 0, SEQ_CHANNEL_TRIGGER_LAST - 1, "reset/mute", reset_trigger_sources, settings::STORAGE_TYPE_U8 }, + { MULT_BY_ONE, 0, MULT_MAX, "mult/div", display_multipliers, settings::STORAGE_TYPE_U8 }, + { 25, 0, PULSEW_MAX, "--> pw", NULL, settings::STORAGE_TYPE_U8 }, + // + { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names_short, settings::STORAGE_TYPE_U8 }, + { 0, -5, 5, "octave", NULL, settings::STORAGE_TYPE_I8 }, // octave + { 0, 0, 11, "root", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, -5, 5, "--> aux +/-", NULL, settings::STORAGE_TYPE_I8 }, // aux octave + { 65535, 1, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, // mask + // seq + { 65535, 0, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, // seq 1 + { 65535, 0, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, // seq 2 + { 65535, 0, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, // seq 3 + { 65535, 0, 65535, "--> edit", NULL, settings::STORAGE_TYPE_U16 }, // seq 4 + { OC::Patterns::PATTERN_USER_0_1, 0, OC::Patterns::PATTERN_USER_LAST-1, "sequence #", OC::pattern_names_short, settings::STORAGE_TYPE_U8 }, + { OC::Patterns::kMax, OC::Patterns::kMin, OC::Patterns::kMax, "sequence length", NULL, settings::STORAGE_TYPE_U8 }, // seq 1 + { OC::Patterns::kMax, OC::Patterns::kMin, OC::Patterns::kMax, "sequence length", NULL, settings::STORAGE_TYPE_U8 }, // seq 2 + { OC::Patterns::kMax, OC::Patterns::kMin, OC::Patterns::kMax, "sequence length", NULL, settings::STORAGE_TYPE_U8 }, // seq 3 + { OC::Patterns::kMax, OC::Patterns::kMin, OC::Patterns::kMax, "sequence length", NULL, settings::STORAGE_TYPE_U8 }, // seq 4 + { 0, 0, PM_LAST - 1, "playmode", OC::Strings::seq_playmodes, settings::STORAGE_TYPE_U8 }, + { 0, 0, SEQ_DIRECTIONS_LAST - 1, "direction", OC::Strings::seq_directions, settings::STORAGE_TYPE_U8 }, + { 0, 0, 1, "CV adr. range", cv_ranges, settings::STORAGE_TYPE_U4 }, + { 0, 0, 3, "direction", arp_directions, settings::STORAGE_TYPE_U4 }, + { 0, 0, 3, "arp.range", arp_range, settings::STORAGE_TYPE_U8 }, + { 64, 0, 255, "-->brown prob", NULL, settings::STORAGE_TYPE_U8 }, + // cv sources + { 0, 0, 4, "mult/div CV ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "transpose ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "--> pw ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "octave +/- ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "root +/- ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "--> aux +/- ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "sequence # ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "mask rotate ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "direction ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "arp.range ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "direction ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "-->brwn.prb ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "seq.length ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "att dur ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "dec dur ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "sus lvl ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "rel dur ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U4 }, + { 0, 0, 4, "env loops ->", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U8 }, + { 0, 0, 1, "-", NULL, settings::STORAGE_TYPE_U4 }, // DUMMY, use to store update behaviour + // envelope parameters + { 128, 0, 255, "--> att dur", NULL, settings::STORAGE_TYPE_U8 }, + { peaks::ENV_SHAPE_QUARTIC, peaks::ENV_SHAPE_LINEAR, peaks::ENV_SHAPE_LAST - 1, "--> att shape", OC::Strings::envelope_shapes, settings::STORAGE_TYPE_U16 }, + { 128, 0, 255, "--> dec dur", NULL, settings::STORAGE_TYPE_U8 }, + { peaks::ENV_SHAPE_EXPONENTIAL, peaks::ENV_SHAPE_LINEAR, peaks::ENV_SHAPE_LAST - 1, "--> dec shape", OC::Strings::envelope_shapes, settings::STORAGE_TYPE_U16 }, + { 128, 0, 255, "--> sus lvl", NULL, settings::STORAGE_TYPE_U16 }, + { 128, 0, 255, "--> rel dur", NULL, settings::STORAGE_TYPE_U8 }, + { peaks::ENV_SHAPE_EXPONENTIAL, peaks::ENV_SHAPE_LINEAR, peaks::ENV_SHAPE_LAST - 1, "--> rel shape", OC::Strings::envelope_shapes, settings::STORAGE_TYPE_U16 }, + {1, 1, 127, "--> loops", NULL, settings::STORAGE_TYPE_U8 }, + { peaks::RESET_BEHAVIOUR_NULL, peaks::RESET_BEHAVIOUR_NULL, peaks::RESET_BEHAVIOUR_LAST - 1, "att reset", OC::Strings::reset_behaviours, settings::STORAGE_TYPE_U8 }, + { peaks::FALLING_GATE_BEHAVIOUR_IGNORE, peaks::FALLING_GATE_BEHAVIOUR_IGNORE, peaks::FALLING_GATE_BEHAVIOUR_LAST - 1, "att fall gt", OC::Strings::falling_gate_behaviours, settings::STORAGE_TYPE_U8 }, + { peaks::RESET_BEHAVIOUR_SEGMENT_PHASE, peaks::RESET_BEHAVIOUR_NULL, peaks::RESET_BEHAVIOUR_LAST - 1, "dec/rel reset", OC::Strings::reset_behaviours, settings::STORAGE_TYPE_U8 }, +}; + +class SEQ_State { +public: + void Init() { + selected_channel = 0; + cursor.Init(SEQ_CHANNEL_SETTING_MODE, SEQ_CHANNEL_SETTING_LAST - 1); + pattern_editor.Init(); + scale_editor.Init(false); + } + + inline bool editing() const { + return cursor.editing(); + } + + inline int cursor_pos() const { + return cursor.cursor_pos(); + } + + int selected_channel; + menu::ScreenCursor cursor; + menu::ScreenCursor cursor_state; + OC::PatternEditor pattern_editor; + OC::ScaleEditor scale_editor; +}; + +SEQ_State seq_state; +SEQ_Channel seq_channel[NUM_CHANNELS]; + +void SEQ_init() { + + ext_frequency[SEQ_CHANNEL_TRIGGER_TR1] = 0xFFFFFFFF; + ext_frequency[SEQ_CHANNEL_TRIGGER_TR2] = 0xFFFFFFFF; + ext_frequency[SEQ_CHANNEL_TRIGGER_NONE] = 0xFFFFFFFF; + + seq_state.Init(); + for (size_t i = 0; i < NUM_CHANNELS; ++i) + seq_channel[i].Init(static_cast(SEQ_CHANNEL_TRIGGER_TR1), i); + seq_state.cursor.AdjustEnd(seq_channel[0].num_enabled_settings() - 1); +} + +size_t SEQ_storageSize() { + return NUM_CHANNELS * SEQ_Channel::storageSize(); +} + +size_t SEQ_save(void *storage) { + + size_t used = 0; + for (size_t i = 0; i < NUM_CHANNELS; ++i) { + used += seq_channel[i].Save(static_cast(storage) + used); + } + return used; +} + +size_t SEQ_restore(const void *storage) { + + size_t used = 0; + + for (size_t i = 0; i < NUM_CHANNELS; ++i) { + used += seq_channel[i].Restore(static_cast(storage) + used); + // update display + seq_channel[i].pattern_changed(seq_channel[i].get_mask(seq_channel[i].get_sequence()), true); + seq_channel[i].set_display_num_sequence(seq_channel[i].get_sequence()); + seq_channel[i].update_enabled_settings(i); + seq_channel[i].set_EoS_update(); + } + seq_state.cursor.AdjustEnd(seq_channel[0].num_enabled_settings() - 1); + return used; +} + +void SEQ_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + seq_state.cursor.set_editing(false); + seq_state.pattern_editor.Close(); + seq_state.scale_editor.Close(); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + { + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + selected.set_menu_page(PARAMETERS); + selected.update_enabled_settings(seq_state.selected_channel); + } + break; + } +} + +void SEQ_loop() { +} + +void SEQ_isr() { + + ticks_src1++; // src #1 ticks + ticks_src2++; // src #2 ticks + copy_timeout++; + + uint32_t triggers = OC::DigitalInputs::clocked(); + + if (triggers & (1 << OC::DIGITAL_INPUT_1)) { + ext_frequency[SEQ_CHANNEL_TRIGGER_TR1] = ticks_src1; + ticks_src1 = 0x0; + } + if (triggers & (1 << OC::DIGITAL_INPUT_3)) { + ext_frequency[SEQ_CHANNEL_TRIGGER_TR2] = ticks_src2; + ticks_src2 = 0x0; + } + + // update sequencer channels 1, 2: + seq_channel[0].Update(triggers, DAC_CHANNEL_A); + seq_channel[1].Update(triggers, DAC_CHANNEL_B); + // update DAC channels A, B: + seq_channel[0].update_main_channel(); + seq_channel[1].update_main_channel(); + // update DAC channels C, D: + seq_channel[0].update_aux_channel(); + seq_channel[1].update_aux_channel(); +} + +void SEQ_handleButtonEvent(const UI::Event &event) { + + if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + SEQ_upButtonLong(); + break; + case OC::CONTROL_BUTTON_DOWN: + SEQ_downButtonLong(); + break; + case OC::CONTROL_BUTTON_L: + if (!(seq_state.pattern_editor.active())) + SEQ_leftButtonLong(); + break; + default: + break; + } + } + + if (seq_state.pattern_editor.active()) { + seq_state.pattern_editor.HandleButtonEvent(event); + return; + } + else if (seq_state.scale_editor.active()) { + seq_state.scale_editor.HandleButtonEvent(event); + return; + } + + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + SEQ_upButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + SEQ_downButton(); + break; + case OC::CONTROL_BUTTON_L: + SEQ_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + SEQ_rightButton(); + break; + } + } +} + +void SEQ_handleEncoderEvent(const UI::Event &event) { + + if (seq_state.pattern_editor.active()) { + seq_state.pattern_editor.HandleEncoderEvent(event); + return; + } + else if (seq_state.scale_editor.active()) { + seq_state.scale_editor.HandleEncoderEvent(event); + return; + } + + if (OC::CONTROL_ENCODER_L == event.control) { + + int selected_channel = seq_state.selected_channel + event.value; + CONSTRAIN(selected_channel, 0, NUM_CHANNELS-1); + seq_state.selected_channel = selected_channel; + + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + + selected.update_enabled_settings(seq_state.selected_channel); + seq_state.cursor.Init(SEQ_CHANNEL_SETTING_MODE, 0); + seq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + + } else if (OC::CONTROL_ENCODER_R == event.control) { + + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + + if (seq_state.editing()) { + + SEQ_ChannelSetting setting = selected.enabled_setting_at(seq_state.cursor_pos()); + + if (SEQ_CHANNEL_SETTING_SCALE_MASK != setting || SEQ_CHANNEL_SETTING_MASK1 != setting || SEQ_CHANNEL_SETTING_MASK2 != setting || SEQ_CHANNEL_SETTING_MASK3 != setting || SEQ_CHANNEL_SETTING_MASK4 != setting) { + + if (selected.change_value(setting, event.value)) + selected.force_update(); + + switch (setting) { + + case SEQ_CHANNEL_SETTING_SEQUENCE: + { + uint8_t seq = selected.get_sequence(); + uint8_t playmode = selected.get_playmode(); + // details: update mask/sequence, depending on mode. + if (!playmode || playmode >= PM_CV1 || selected.get_current_sequence() == seq || selected.update_timeout()) { + selected.set_display_num_sequence(seq); + selected.pattern_changed(selected.get_mask(seq), true); + } + } + break; + case SEQ_CHANNEL_SETTING_MODE: + case SEQ_CHANNEL_SETTING_SEQUENCE_PLAYMODE: + case SEQ_CHANNEL_SETTING_SEQUENCE_DIRECTION: + selected.update_enabled_settings(seq_state.selected_channel); + seq_state.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + break; + default: + break; + } + } + } else { + seq_state.cursor.Scroll(event.value); + } + } +} + +void SEQ_upButton() { + + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + + if (selected.get_menu_page() == PARAMETERS) { + + if (selected.octave_toggle()) + selected.change_value(SEQ_CHANNEL_SETTING_OCTAVE, 1); + else + selected.change_value(SEQ_CHANNEL_SETTING_OCTAVE, -1); + } + else { + selected.set_menu_page(PARAMETERS); + selected.update_enabled_settings(seq_state.selected_channel); + seq_state.cursor.set_editing(false); + } +} + +void SEQ_downButton() { + + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + + if (!seq_state.pattern_editor.active() && !seq_state.scale_editor.active()) { + + uint8_t _menu_page = selected.get_menu_page(); + + switch (_menu_page) { + + case PARAMETERS: + _menu_page = CV_MAPPING; + break; + default: + _menu_page = PARAMETERS; + break; + + } + + selected.set_menu_page(_menu_page); + selected.update_enabled_settings(seq_state.selected_channel); + seq_state.cursor.set_editing(false); + } + /* + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + if (selected.get_menu_page() == PARAMETERS) + selected.change_value(SEQ_CHANNEL_SETTING_OCTAVE, -1); + else { + selected.set_menu_page(PARAMETERS); + selected.update_enabled_settings(seq_state.selected_channel); + seq_state.cursor.set_editing(false); + } + */ +} + +void SEQ_rightButton() { + + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + + switch (selected.enabled_setting_at(seq_state.cursor_pos())) { + + case SEQ_CHANNEL_SETTING_SCALE: + seq_state.cursor.toggle_editing(); + selected.update_scale(true); + break; + case SEQ_CHANNEL_SETTING_SCALE_MASK: + { + int scale = selected.get_scale(DUMMY); + if (OC::Scales::SCALE_NONE != scale) { + seq_state.scale_editor.Edit(&selected, scale); + } + } + break; + case SEQ_CHANNEL_SETTING_MASK1: + case SEQ_CHANNEL_SETTING_MASK2: + case SEQ_CHANNEL_SETTING_MASK3: + case SEQ_CHANNEL_SETTING_MASK4: + { + int pattern = selected.get_sequence(); + if (OC::Patterns::PATTERN_NONE != pattern) { + seq_state.pattern_editor.Edit(&selected, pattern); + } + } + break; + case SEQ_CHANNEL_SETTING_DUMMY: + selected.set_menu_page(PARAMETERS); + selected.update_enabled_settings(seq_state.selected_channel); + break; + default: + seq_state.cursor.toggle_editing(); + break; + } +} + +void SEQ_leftButton() { + // sync: + for (int i = 0; i < NUM_CHANNELS; ++i) + seq_channel[i].sync(); +} + +void SEQ_leftButtonLong() { + // copy scale + if (!seq_state.pattern_editor.active() && !seq_state.scale_editor.active()) { + + uint8_t this_channel, the_other_channel, scale; + uint16_t mask; + + this_channel = seq_state.selected_channel; + scale = seq_channel[this_channel].get_scale(DUMMY); + mask = seq_channel[this_channel].get_rotated_scale_mask(); + + the_other_channel = (~this_channel) & 1u; + seq_channel[the_other_channel].set_scale(scale); + seq_channel[the_other_channel].update_scale_mask(mask, DUMMY); + seq_channel[the_other_channel].update_scale(true); + } +} + +void SEQ_upButtonLong() { + // screensaver short cut (happens elsewhere) +} + +void SEQ_downButtonLong() { + // clear CV mappings: + SEQ_Channel &selected = seq_channel[seq_state.selected_channel]; + + if (selected.get_menu_page() == CV_MAPPING) { + selected.clear_CV_mapping(); + seq_state.cursor.set_editing(false); + } + else { // toggle update behaviour: + seq_channel[0x0].toggle_EoS(); + seq_channel[0x1].toggle_EoS(); + } +} + +void SEQ_menu() { + + menu::DualTitleBar::Draw(); + + for (int i = 0, x = 0; i < NUM_CHANNELS; ++i, x += 21) { + + const SEQ_Channel &channel = seq_channel[i]; + menu::DualTitleBar::SetColumn(i); + + // draw gate/step indicator + uint8_t gate = 1; + if (channel.get_step_gate() == ON) + gate += 14; + else if (channel.get_step_state() == ON) + gate += 10; + menu::DualTitleBar::DrawGateIndicator(i, gate); + + graphics.movePrintPos(5, 0); + // channel id: + graphics.print("#"); + graphics.print((char)('A' + i)); + // sequence id: + graphics.print("/"); + graphics.print(1 + channel.get_display_num_sequence()); + // octave: + graphics.movePrintPos(22, 0); + if (channel.poke_octave_toggle()) + graphics.print("+"); + } + + const SEQ_Channel &channel = seq_channel[seq_state.selected_channel]; + + menu::DualTitleBar::Selected(seq_state.selected_channel); + + menu::SettingsList settings_list(seq_state.cursor); + + menu::SettingsListItem list_item; + + while (settings_list.available()) { + const int setting = + channel.enabled_setting_at(settings_list.Next(list_item)); + const int value = channel.get_value(setting); + const settings::value_attr &attr = SEQ_Channel::value_attr(setting); + + switch (setting) { + + case SEQ_CHANNEL_SETTING_SCALE: + list_item.SetPrintPos(); + if (list_item.editing) { + menu::DrawEditIcon(6, list_item.y, value, attr); + graphics.movePrintPos(6, 0); + } + graphics.print(OC::scale_names[value]); + list_item.DrawCustom(); + break; + case SEQ_CHANNEL_SETTING_SCALE_MASK: + menu::DrawMask(menu::kDisplayWidth, list_item.y, channel.get_rotated_scale_mask(), OC::Scales::GetScale(channel.get_scale(DUMMY)).num_notes); + list_item.DrawNoValue(value, attr); + break; + case SEQ_CHANNEL_SETTING_MASK1: + case SEQ_CHANNEL_SETTING_MASK2: + case SEQ_CHANNEL_SETTING_MASK3: + case SEQ_CHANNEL_SETTING_MASK4: + { + int clock_indicator = channel.get_clock_cnt(); + if (channel.get_playmode() == PM_ARP) + clock_indicator = 0xFF; + menu::DrawMask(menu::kDisplayWidth, list_item.y, channel.get_display_mask(), channel.get_display_length(), clock_indicator); + list_item.DrawNoValue(value, attr); + } + break; + case SEQ_CHANNEL_SETTING_PULSEWIDTH: + if (channel.get_aux_mode() < ENV_AD) { + list_item.Draw_PW_Value(value, attr); + } else { + list_item.Draw_PW_Value_Char(value, attr, "--> sus dur"); + } + break; + case SEQ_CHANNEL_SETTING_DUMMY: + list_item.DrawNoValue(value, attr); + break; + default: + list_item.DrawDefault(value, attr); + break; + } + } + + if (seq_state.pattern_editor.active()) + seq_state.pattern_editor.Draw(); + else if (seq_state.scale_editor.active()) + seq_state.scale_editor.Draw(); +} + + +void SEQ_Channel::RenderScreensaver() const { + + uint8_t seq_id = channel_id_; + uint8_t clock_x_pos = seq_channel[seq_id].get_clock_cnt(); + int32_t _dac_value = seq_channel[seq_id].get_step_pitch(); + int32_t _dac_overflow = 0, _dac_overflow2 = 0; + + // reposition ARP: + if (seq_channel[seq_id].get_playmode() == PM_ARP) + clock_x_pos = 0x6 + (seq_id << 2); + + clock_x_pos = (seq_id << 6) + (clock_x_pos << 2); + + // clock/step indicator: + if(seq_channel[seq_id].step_state_ == OFF) { + graphics.drawRect(clock_x_pos, 63, 5, 2); + _dac_value = 0; + } + else + graphics.drawRect(clock_x_pos, 60, 5, 5); + + // separate windows ... + graphics.drawVLine(64, 0, 68); + + // display pitch values as squares/frames: + if (_dac_value < 0) { + // display negative values as frame (though they're not negative...) + _dac_value = (_dac_value - (_dac_value << 1 )) >> 6; + _dac_overflow = _dac_value - 40; + _dac_overflow2 = _dac_overflow - 40; + + CONSTRAIN(_dac_value, 1, 40); + + int8_t x = 2 + clock_x_pos - (_dac_value >> 1); + int8_t x_size = 0; + + // limit size of frame to window size + if (seq_id && x < 64) { + x_size = 64 - x; + x = 64; + } + else if (!seq_id && (x + _dac_value > 63)) + x_size = (x + _dac_value) - 64; + // draw + graphics.drawFrame(x, 30 - (_dac_value >> 1), _dac_value - x_size, _dac_value); + + if (_dac_overflow > 0) { + + CONSTRAIN(_dac_overflow, 1, 40); + + x = 2 + clock_x_pos - (_dac_overflow >> 1); + + if (seq_id && x < 64) { + x_size = 64 - x; + x = 64; + } + else if (!seq_id && (x + _dac_overflow > 63)) + x_size = (x + _dac_overflow) - 64; + + graphics.drawRect(x, 30 - (_dac_overflow >> 1), _dac_overflow - x_size, _dac_overflow); + } + + if (_dac_overflow2 > 0) { + + CONSTRAIN(_dac_overflow2, 1, 40); + + x = 2 + clock_x_pos - (_dac_overflow2 >> 1); + + if (seq_id && x < 64) { + x_size = 64 - x; + x = 64; + } + else if (!seq_id && (x + _dac_overflow2 > 63)) + x_size = (x + _dac_overflow2) - 64; + + graphics.clearRect(x, 30 - (_dac_overflow2 >> 1), _dac_overflow2 - x_size, _dac_overflow2); + } + } + else { + // positive output as rectangle + _dac_value = (_dac_value >> 6); + _dac_overflow = _dac_value - 40; + _dac_overflow2 = _dac_overflow - 40; + + CONSTRAIN(_dac_value, 1, 40); + + int8_t x = 2 + clock_x_pos - (_dac_value >> 1); + int8_t x_size = 0; + // limit size of rectangle to window size + if (seq_id && x < 64) { + x_size = 64 - x; + x = 64; + } + else if (!seq_id && (x + _dac_value > 63)) + x_size = (x + _dac_value) - 64; + // draw + graphics.drawRect(x, 30 - (_dac_value >> 1), _dac_value - x_size, _dac_value); + + if (_dac_overflow > 0) { + + CONSTRAIN(_dac_overflow, 1, 40); + + x = 2 + clock_x_pos - (_dac_overflow >> 1); + + if (seq_id && x < 64) { + x_size = 64 - x; + x = 64; + } + else if (!seq_id && (x + _dac_overflow > 63)) + x_size = (x + _dac_overflow) - 64; + + graphics.clearRect(x, 30 - (_dac_overflow >> 1), _dac_overflow - x_size, _dac_overflow); + } + + if (_dac_overflow2 > 0) { + + CONSTRAIN(_dac_overflow2, 1, 40); + + x = 2 + clock_x_pos - (_dac_overflow2 >> 1); + + if (seq_id && x < 64) { + x_size = 64 - x; + x = 64; + } + else if (!seq_id && (x + _dac_overflow2 > 63)) + x_size = (x + _dac_overflow2) - 64; + + graphics.drawRect(x, 30 - (_dac_overflow2 >> 1), _dac_overflow2 - x_size, _dac_overflow2); + } + } +} + +void SEQ_screensaver() { + + seq_channel[0].RenderScreensaver(); + seq_channel[1].RenderScreensaver(); +} + + +#endif // ENABLE_APP_SEQUINS diff --git a/software/o_c_REV/OC_apps.h b/software/o_c_REV/OC_apps.h index 4744e429e..059112aa1 100644 --- a/software/o_c_REV/OC_apps.h +++ b/software/o_c_REV/OC_apps.h @@ -24,6 +24,7 @@ #define OC_APP_H_ #include "UI/ui_events.h" +#include "util/util_turing.h" #include "util/util_misc.h" namespace OC { diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index ef0cfc0e6..9d4ec0278 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -37,6 +37,27 @@ OC::App available_apps[] = { DECLARE_APP('H','S', "Hemisphere", HEMISPHERE), + #ifdef ENABLE_APP_QUANTERMAIN + DECLARE_APP('Q','Q', "Quantermain", QQ), + #endif + #ifdef ENABLE_APP_METAQ + DECLARE_APP('M','!', "Meta-Q", DQ), + #endif + + #ifdef ENABLE_APP_CHORDS + DECLARE_APP('A','C', "Acid Curds", CHORDS), + #endif + #ifdef ENABLE_APP_SEQUINS + DECLARE_APP('S','Q', "Sequins", SEQ), + #endif + + #ifdef ENABLE_APP_POLYLFO + DECLARE_APP('P','L', "Quadraturia", POLYLFO), + #endif + #ifdef ENABLE_APP_H1200 + DECLARE_APP('H','A', "Harrington 1200", H1200), + #endif + #ifdef ENABLE_APP_MIDI DECLARE_APP('M','I', "Captain MIDI", MIDI), #endif diff --git a/software/o_c_REV/OC_chords.cpp b/software/o_c_REV/OC_chords.cpp new file mode 100644 index 000000000..14016c749 --- /dev/null +++ b/software/o_c_REV/OC_chords.cpp @@ -0,0 +1,29 @@ +#include "OC_chords.h" +#include "OC_chords_presets.h" + +namespace OC { + + Chord user_chords[Chords::CHORDS_USER_LAST]; + + /*static*/ + const int Chords::NUM_CHORD_PROGRESSIONS = 0x4; + const int Chords::NUM_CHORDS_TOTAL = OC::Chords::CHORDS_USER_LAST; // = 8 + const int Chords::NUM_CHORDS_PROPERTIES = sizeof(Chord); + const int Chords::NUM_CHORDS = Chords::NUM_CHORDS_TOTAL / Chords::NUM_CHORD_PROGRESSIONS; + + /*static*/ + // + void Chords::Init() { + for (size_t i = 0; i < OC::Chords::CHORDS_USER_LAST; ++i) + memcpy(&user_chords[i], &OC::chords[0], sizeof(Chord)); + } + + const Chord &Chords::GetChord(int index, int progression) { + + uint8_t _index = index + progression * Chords::NUM_CHORDS; + if (_index < CHORDS_USER_LAST) + return user_chords[_index]; + else + return user_chords[0x0]; + } +} // namespace OC diff --git a/software/o_c_REV/OC_chords.h b/software/o_c_REV/OC_chords.h new file mode 100644 index 000000000..c9dec06d1 --- /dev/null +++ b/software/o_c_REV/OC_chords.h @@ -0,0 +1,105 @@ +#ifndef OC_CHORDS_H_ +#define OC_CHORDS_H_ + +#include "OC_chords_presets.h" + +namespace OC { + +typedef OC::Chord Chord; + +class Chords { +public: + + static const int NUM_CHORDS_TOTAL; + static const int NUM_CHORDS; + static const int NUM_CHORDS_PROPERTIES; + static const int NUM_CHORD_PROGRESSIONS; + + enum CHORD_SLOTS + { + CHORDS_USER_0_0, + CHORDS_USER_1_0, + CHORDS_USER_2_0, + CHORDS_USER_3_0, + CHORDS_USER_4_0, + CHORDS_USER_5_0, + CHORDS_USER_6_0, + CHORDS_USER_7_0, + CHORDS_USER_0_1, + CHORDS_USER_1_1, + CHORDS_USER_2_1, + CHORDS_USER_3_1, + CHORDS_USER_4_1, + CHORDS_USER_5_1, + CHORDS_USER_6_1, + CHORDS_USER_7_1, + CHORDS_USER_0_2, + CHORDS_USER_1_2, + CHORDS_USER_2_2, + CHORDS_USER_3_2, + CHORDS_USER_4_2, + CHORDS_USER_5_2, + CHORDS_USER_6_2, + CHORDS_USER_7_2, + CHORDS_USER_0_3, + CHORDS_USER_1_3, + CHORDS_USER_2_3, + CHORDS_USER_3_3, + CHORDS_USER_4_3, + CHORDS_USER_5_3, + CHORDS_USER_6_3, + CHORDS_USER_7_3, + CHORDS_USER_LAST + }; + + enum QUALITY + { + CHORDS_FIFTH, + CHORDS_TRIAD, + CHORDS_SEVENTH, + CHORDS_SUSPENDED, + CHORDS_SUSPENDED_SEVENTH, + CHORDS_SIXTH, + CHORDS_ADDED_NINTH, + CHORDS_ADDED_ELEVENTH, + CHORDS_UNISONO, + CHORDS_QUALITY_LAST + }; + + enum CHORDS_TYPE + { + CHORDS_TYPE_MONAD, + CHORDS_TYPE_DYAD, + CHORDS_TYPE_TRIAD, + CHORDS_TYPE_TETRAD, + CHORDS_TYPE_LAST + }; + + enum VOICING + { + CHORDS_CLOSE, + CHORDS_DROP_1, + CHORDS_DROP_2, + CHORDS_DROP_3, + CHORDS_SPREAD, + CHORDS_VOICING_LAST + }; + + enum INVERSION + { + CHORDS_INVERSION_NONE, + CHORDS_INVERSION_FIRST, + CHORDS_INVERSION_SECOND, + CHORDS_INVERSION_THIRD, + CHORDS_INVERSION_LAST + }; + + static void Init(); + static const Chord &GetChord(int index, int progression); +}; + + +extern Chord user_chords[OC::Chords::CHORDS_USER_LAST]; +}; + +#endif // OC_CHORDS_H_ diff --git a/software/o_c_REV/OC_chords_edit.h b/software/o_c_REV/OC_chords_edit.h new file mode 100644 index 000000000..9f45edde7 --- /dev/null +++ b/software/o_c_REV/OC_chords_edit.h @@ -0,0 +1,450 @@ +// Copyright (c) 2015, 2016, 2017 Patrick Dowling, Max Stadler, Tim Churches +// +// Author of original O+C firmware: Max Stadler (mxmlnstdlr@gmail.com) +// Author of app scaffolding: Patrick Dowling (pld@gurkenkiste.com) +// Modified for bouncing balls: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef OC_CHORDS_EDIT_H_ +#define OC_CHORDS_EDIT_H_ + +#include "OC_bitmaps.h" +#include "OC_chords.h" +#include "OC_chords_presets.h" + +namespace OC { + +template +class ChordEditor { +public: + + enum EDIT_PAGE { + CHORD_SELECT, + CHORD_EDIT, + CHORD_EDIT_PAGE_LAST + }; + + void Init() { + owner_ = nullptr; + cursor_pos_ = 0; + cursor_quality_pos_ = 0; + edit_this_chord_ = 0; + edit_this_progression_ = 0; + edit_page_ = CHORD_SELECT; + // + chord_quality_ = 0; + chord_voicing_ = 0; + chord_inversion_ = 0; + chord_base_note_ = 0; + chord_octave_ = 0; + max_chords_ = OC::Chords::NUM_CHORDS - 1; + } + + bool active() const { + return nullptr != owner_; + } + + void Edit(Owner *owner, int chord, int num_chords, int num_progression) { + + if (chord > OC::Chords::CHORDS_USER_LAST - 1) + return; + + edit_this_progression_ = num_progression; + chord_ = &OC::user_chords[chord + edit_this_progression_ * OC::Chords::NUM_CHORDS]; + max_chords_ = num_chords; + owner_ = owner; + BeginEditing(); + } + + void Close(); + void Draw(); + void HandleButtonEvent(const UI::Event &event); + void HandleEncoderEvent(const UI::Event &event); + +private: + + Owner *owner_; + const OC::Chord *chord_; + int8_t edit_this_chord_; + int8_t edit_this_progression_; + size_t cursor_pos_; + size_t cursor_quality_pos_; + int8_t chord_quality_; + int8_t chord_voicing_; + int8_t chord_inversion_; + int8_t chord_base_note_; + int8_t chord_octave_; + int8_t max_chords_; + bool edit_page_; + + void BeginEditing(); + void move_cursor(int offset, int page); + void update_chord(int8_t chord_num); + void copy_chord(); + void paste_chord(); + + void change_property(size_t chord_property, int delta, bool notify); + void handleButtonLeft(const UI::Event &event); + void handleButtonUp(const UI::Event &event); + void handleButtonDown(const UI::Event &event); +}; + +template +void ChordEditor::Draw() { + + weegfx::coord_t w = 128; + weegfx::coord_t x = 0; + weegfx::coord_t y = 0; + weegfx::coord_t h = 64; + + graphics.clearRect(x, y, w, h); + graphics.drawFrame(x, y, w, h); + + // chord select: + size_t max_chords = max_chords_ + 1; + uint8_t indicator = owner_->active_chord(); + bool active = owner_->get_active_progression() == edit_this_progression_; + + x = 6; + y = 6; + // which progression ? ... + graphics.setPrintPos(x, y + 2); + graphics.print(edit_this_progression_ + 0x1); + // now draw steps: + x += 11; + + for (size_t i = 0; i < max_chords; ++i, x += 13) { + + if (active && i == indicator) { + graphics.drawFrame(x, y, 10, 10); + graphics.drawRect(x + 2, y + 2, 6, 6); + } + else if (edit_page_ == CHORD_SELECT) + graphics.drawRect(x, y, 10, 10); + else + graphics.drawRect(x + 2, y + 2, 6, 6); + + // cursor: + if (i == cursor_pos_) + graphics.drawFrame(x - 2, y - 2, 14, 14); + } + + // end marker: + graphics.drawFrame(x, y, 2, 2); + graphics.drawFrame(x, y + 4, 2, 2); + graphics.drawFrame(x, y + 8, 2, 2); + + if (cursor_pos_ == max_chords) + graphics.drawFrame(x - 2, y - 2, 6, 14); + + // draw extra cv num chords? + if (owner_->get_num_chords_cv()) { + int extra_slots = owner_->get_display_num_chords() - (max_chords - 0x1); + if (active && extra_slots > 0x0) { + x += 5; + indicator -= max_chords; + for (size_t i = 0; i < (uint8_t)extra_slots; ++i, x += 10) { + if (i == indicator) + graphics.drawRect(x, y + 2, 6, 6); + else + graphics.drawFrame(x, y + 2, 6, 6); + } + } + } + + // chord properties: + x = 6; + y = 23; + + for (size_t i = 0; i < sizeof(Chord); ++i, x += 24) { + + // draw values + + switch(i) { + + case 0: // quality + graphics.setPrintPos(x + 1, y + 7); + graphics.print(OC::quality_very_short_names[chord_quality_]); + break; + case 1: // voicing + graphics.setPrintPos(x + 1, y + 7); + graphics.print(voicing_names_short[chord_voicing_]); + break; + case 2: // inversion + graphics.setPrintPos(x + 7, y + 7); + graphics.print(inversion_names[chord_inversion_]); + break; + case 3: // base note + { + if (chord_base_note_ > 9) + graphics.setPrintPos(x + 1, y + 7); + else + graphics.setPrintPos(x + 4, y + 7); + graphics.print(base_note_names[chord_base_note_]); + // indicate if note is out-of-range: + if (chord_base_note_ > (uint8_t)OC::Scales::GetScale(owner_->get_scale(DUMMY)).num_notes) { + graphics.drawBitmap8(x + 3, y + 25, 4, OC::bitmap_indicator_4x8); + graphics.drawBitmap8(x + 14, y + 25, 4, OC::bitmap_indicator_4x8); + } + } + break; + case 4: // octave + { + if (chord_octave_ < 0) + graphics.setPrintPos(x + 4, y + 7); + else + graphics.setPrintPos(x + 7, y + 7); + graphics.print((int)chord_octave_); + } + break; + default: + break; + } + // draw property name + graphics.setPrintPos(x + 7, y + 26); + graphics.print(OC::Strings::chord_property_names[i]); + + // cursor: + if (i == cursor_quality_pos_) { + graphics.invertRect(x, y, 21, 21); + if (edit_page_ == CHORD_EDIT) + graphics.invertRect(x, y + 22, 21, 14); + else + graphics.drawFrame(x, y + 22, 21, 14); + } + else { + graphics.drawFrame(x, y, 21, 21); + graphics.drawFrame(x, y + 22, 21, 14); + } + } +} + +template +void ChordEditor::HandleButtonEvent(const UI::Event &event) { + + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + handleButtonUp(event); + break; + case OC::CONTROL_BUTTON_DOWN: + handleButtonDown(event); + break; + case OC::CONTROL_BUTTON_L: + handleButtonLeft(event); + break; + case OC::CONTROL_BUTTON_R: + Close(); + break; + default: + break; + } + } + else if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + // screensaver // TODO: ideally, needs to be overridden ... invert_mask(); + break; + case OC::CONTROL_BUTTON_DOWN: + break; + case OC::CONTROL_BUTTON_L: + break; + case OC::CONTROL_BUTTON_R: + // app menu + break; + default: + break; + } + } +} + +template +void ChordEditor::HandleEncoderEvent(const UI::Event &event) { + + if (OC::CONTROL_ENCODER_L == event.control) { + move_cursor(event.value, edit_page_); + } + else if (OC::CONTROL_ENCODER_R == event.control) { + + if (cursor_pos_ < (uint8_t)(max_chords_ + 1)) { + + // write to the right slot, at the right index/offset (a nicer struct would be nicer, but well) + OC::Chord *edit_user_chord_ = &OC::user_chords[edit_this_chord_ + edit_this_progression_ * OC::Chords::NUM_CHORDS]; + + switch(cursor_quality_pos_) { + + case 0: // quality: + { + chord_quality_ += event.value; + CONSTRAIN(chord_quality_, 0, OC::Chords::CHORDS_QUALITY_LAST - 1); + edit_user_chord_->quality = chord_quality_; + } + break; + case 1: // voicing: + { + chord_voicing_ += event.value; + CONSTRAIN(chord_voicing_, 0, OC::Chords::CHORDS_VOICING_LAST - 1); + edit_user_chord_->voicing = chord_voicing_; + } + break; + case 2: // inversion: + { + chord_inversion_ += event.value; + CONSTRAIN(chord_inversion_, 0, OC::Chords::CHORDS_INVERSION_LAST - 1); + edit_user_chord_->inversion = chord_inversion_; + } + break; + case 3: // base note + { + chord_base_note_ += event.value; + const OC::Scale &scale_def = OC::Scales::GetScale(owner_->get_scale(DUMMY)); + CONSTRAIN(chord_base_note_, 0, (uint8_t)scale_def.num_notes); + edit_user_chord_->base_note = chord_base_note_; + } + break; + case 4: // octave + { + chord_octave_ += event.value; + CONSTRAIN(chord_octave_, -4, 4); + edit_user_chord_->octave = chord_octave_; + } + break; + default: + break; + } + } + else { + // expand/contract + int max_chords = max_chords_; + max_chords += event.value; + CONSTRAIN(max_chords, 0, OC::Chords::NUM_CHORDS - 0x1); + + max_chords_ = max_chords; + cursor_pos_ = max_chords_ + 1; + owner_->set_num_chords(max_chords, edit_this_progression_); + } + } +} + +template +void ChordEditor::update_chord(int8_t chord_num) { + // update chord properties: + const OC::Chord &chord_def = OC::Chords::GetChord(chord_num, edit_this_progression_); + chord_quality_ = chord_def.quality; + chord_voicing_ = chord_def.voicing; + chord_inversion_ = chord_def.inversion; + chord_base_note_ = chord_def.base_note; + chord_octave_ = chord_def.octave; + max_chords_ = owner_->get_num_chords(edit_this_progression_); +} + +template +void ChordEditor::move_cursor(int offset, int page) { + + if (page == CHORD_SELECT) { + int cursor_pos = cursor_pos_ + offset; + CONSTRAIN(cursor_pos, 0, max_chords_ + 1); + cursor_pos_ = cursor_pos; + edit_this_chord_ = cursor_pos; + // update .. + if (cursor_pos <= max_chords_) + update_chord(cursor_pos); + } + else { + int cursor_quality_pos = cursor_quality_pos_ + offset; + CONSTRAIN(cursor_quality_pos, 0, (int8_t)(sizeof(Chord) - 1)); + cursor_quality_pos_ = cursor_quality_pos; + } +} + +template +void ChordEditor::handleButtonUp(const UI::Event &event) { + // go to next chords progression + edit_this_progression_++; + if (edit_this_progression_ >= OC::Chords::NUM_CHORD_PROGRESSIONS) + edit_this_progression_ = 0x0; + update_chord(cursor_pos_); +} + +template +void ChordEditor::handleButtonDown(const UI::Event &event) { + // go to previous chords progression + edit_this_progression_--; + if (edit_this_progression_ < 0x0) + edit_this_progression_ = OC::Chords::NUM_CHORD_PROGRESSIONS - 1; + update_chord(cursor_pos_); +} + +template +void ChordEditor::handleButtonLeft(const UI::Event &) { + + if (edit_page_ == CHORD_SELECT) { + + if (cursor_pos_ < (uint8_t)(max_chords_ + 1)) { + edit_page_ = CHORD_EDIT; + // edit chord: + edit_this_chord_ = cursor_pos_; + update_chord(cursor_pos_); + } + // select previous chord if clicking on end-marker: + else if (cursor_pos_ == (uint8_t)(max_chords_ + 1)) { + edit_page_ = CHORD_EDIT; + cursor_pos_--; + edit_this_chord_ = cursor_pos_; + } + } + else { + edit_page_ = CHORD_SELECT; + cursor_pos_ = edit_this_chord_; + } +} + +template +void ChordEditor::copy_chord() { + // todo +} + +template +void ChordEditor::paste_chord() { + // todo +} + +template +void ChordEditor::BeginEditing() { + + cursor_pos_ = edit_this_chord_= owner_->get_chord_slot(); + const OC::Chord &chord_def = OC::Chords::GetChord(edit_this_chord_, edit_this_progression_); + chord_quality_ = chord_def.quality; + chord_voicing_ = chord_def.voicing; + chord_inversion_ = chord_def.inversion; + chord_base_note_ = chord_def.base_note; + chord_octave_ = chord_def.octave; + edit_page_ = CHORD_SELECT; +} + +template +void ChordEditor::Close() { + ui.SetButtonIgnoreMask(); + owner_ = nullptr; +} + +}; // namespace OC + +#endif // OC_CHORD_EDIT_H_ diff --git a/software/o_c_REV/OC_chords_presets.h b/software/o_c_REV/OC_chords_presets.h new file mode 100644 index 000000000..2285fa73c --- /dev/null +++ b/software/o_c_REV/OC_chords_presets.h @@ -0,0 +1,84 @@ +#ifndef OC_CHORDS_PRESETS_H_ +#define OC_CHORDS_PRESETS_H_ + +#include "OC_scales.h" +#include "OC_chords.h" +#include + +namespace OC { + + struct Chord { + + int8_t quality; + int8_t inversion; + int8_t voicing; + int8_t base_note; + int8_t octave; + }; + + const Chord chords[] = { + // default + { 0, 0, 0, 0, 0 } + }; + +// these intervals are for notes-in-scale/key, last element is the chord type (monad=1, dyad=2, traid=3, tetrad=4) + + const int8_t qualities[][4] = + { + { 0, 0, 4, 0 }, // fifth + { 0, 2, 2, 0 }, // triad + { 0, 2, 2, 2 }, // seventh + { 0, 3, 1, 0 }, // suspended + { 0, 3, 1, 2 }, // susp. seventh + { 0, 2, 2, 1 }, // sixth + { 0, 2, 2, 4 }, // added ninth + { 0, 2, 2, 6 }, // added eleventh + { 0, 0, 0, 0 }, // unisono + }; + + const int8_t voicing[][4] = + { + // this can't work like this, because it should operate on the inversions, too. + { 0, 0, 0, 0 }, // close + { 0, 0, 0, -1}, // drop 1 + { 0, 0, -1, 0}, // drop 2 + { 0, -1, 0, 0}, // drop 3 + { -1, 1, 1, 1} // spread + }; + + const int8_t inversion[][4] { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 1, 0, 0 }, + { 1, 1, 1, 0 } + }; + + const char* const quality_names[] { + "fifth", "triad", "seventh", "suspended", "susp 7th", "sixth", "added 9th", "added 11th", "unisono" + }; + + const char* const quality_short_names[] { + "5th", "triad", "7th", "susp", "sus7", "6th", "+9th", "+11th", "uni" + }; + + const char* const quality_very_short_names[] { + "5th", "tri", "7th", "sus", "su7", "6th", "9th", "+11", "uni" + }; + + const char* const voicing_names[] { + "close", "drop 1", "drop 2", "drop 3", "spread" + }; + + const char* const voicing_names_short[] { + " - ", "dr1", "dr2", "dr3", "spr" + }; + + const char* const inversion_names[] { + "-", "1", "2", "3" + }; + + const char* const base_note_names[] { + "CV", "#1", "#2", "#3", "#4", "#5", "#6", "#7", "#8", "#9", "#10", "#11", "#12", "#13", "#14", "#15", "#16" + }; +} +#endif // OC_CHORDS_PRESETS_H_ diff --git a/software/o_c_REV/OC_menus.h b/software/o_c_REV/OC_menus.h index 088528c60..8485f4e98 100644 --- a/software/o_c_REV/OC_menus.h +++ b/software/o_c_REV/OC_menus.h @@ -31,6 +31,7 @@ #include "util/util_misc.h" #include "util/util_settings.h" #include "OC_DAC.h" +#include "OC_chords.h" namespace OC { @@ -164,7 +165,6 @@ inline void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, const graphics.drawBitmap8(x - 5, y + 1, OC::kBitmapEditIndicatorW, src); } -/* Removed jej 8/26/2018 inline void DrawChord(weegfx::coord_t x, weegfx::coord_t y, int width, int value, int mem_offset) { OC::Chord *active_chord = &OC::user_chords[value + mem_offset * OC::Chords::NUM_CHORDS]; @@ -190,7 +190,7 @@ inline void DrawChord(weegfx::coord_t x, weegfx::coord_t y, int width, int value y_pos = y + OC::voicing[_voicing][3] * 16; CONSTRAIN(y_pos, 8, 64); graphics.drawFrame(x, y_pos, width, width); -}*/ +} inline void DrawMiniChord(weegfx::coord_t x, weegfx::coord_t y, uint8_t count, uint8_t indicator) { int8_t _x = x - count * 3 - 4; diff --git a/software/o_c_REV/OC_options.h b/software/o_c_REV/OC_options.h index ce7760874..298f62e92 100644 --- a/software/o_c_REV/OC_options.h +++ b/software/o_c_REV/OC_options.h @@ -39,12 +39,21 @@ #endif /* Flags for the full-width apps, these enable/disable them in OC_apps.ino but also zero out the app */ -/* files to prevent them from taking up space. Only Enigma is enabled by default. */ -#define ENABLE_APP_ENIGMA -#define ENABLE_APP_MIDI -#define ENABLE_APP_NEURAL_NETWORK -#define ENABLE_APP_PONG -#define ENABLE_APP_DARKEST_TIMELINE +/* files to prevent them from taking up space. */ +// Sets of these are now defined in platformio.ini for convenience. -NJM + +// Hemisphere apps +// #define ENABLE_APP_ENIGMA +// #define ENABLE_APP_MIDI +// #define ENABLE_APP_NEURAL_NETWORK +// #define ENABLE_APP_PONG +// #define ENABLE_APP_DARKEST_TIMELINE + +// Stock O&C apps +// #define ENABLE_APP_QUANTERMAIN +// #define ENABLE_APP_METAQ +// #define ENABLE_APP_CHORDS +// #define ENABLE_APP_SEQUINS #endif diff --git a/software/o_c_REV/OC_scale_edit.h b/software/o_c_REV/OC_scale_edit.h new file mode 100644 index 000000000..0f56446fc --- /dev/null +++ b/software/o_c_REV/OC_scale_edit.h @@ -0,0 +1,656 @@ +#ifndef OC_SCALE_EDIT_H_ +#define OC_SCALE_EDIT_H_ + +#include "OC_bitmaps.h" +#include "OC_strings.h" +#include "OC_DAC.h" + +namespace OC { + +// Scale editor +// Edits both scale length and note note values, as well as a mask of "active" +// notes that the quantizer uses; some scales are read-only, in which case +// only the mask is editable +// +// The owner class needs to provide callbacks to get notification when values +// change: see ASR, A_SEQ, DQ, etc for details +// + +const bool DUMMY = false; + +enum SCALE_EDIT_PAGES { + _SCALE, + _ROOT, + _TRANSPOSE, + _SCALING +}; + +template +class ScaleEditor { +public: + + void Init(bool mode) { + owner_ = nullptr; + scale_name_ = "?!"; + scale_ = mutable_scale_ = &dummy_scale; + mask_ = 0; + cursor_pos_ = 0; + scaling_cursor_pos_ = 0; + num_notes_ = 0; + edit_this_scale_ = 0; + /* mode: true = Meta-Q; false = scale editor */ + SEQ_MODE = mode; + edit_page_ = _SCALE; + ticks_ = 0; + } + + bool active() const { + return nullptr != owner_; + } + + void Edit(Owner *owner, int scale) { + if (OC::Scales::SCALE_NONE == scale) + return; + + if (scale < OC::Scales::SCALE_USER_LAST) { + scale_ = mutable_scale_ = &OC::user_scales[scale]; + scale_name_ = OC::scale_names_short[scale]; + // Serial.print("Editing mutable scale "); + // Serial.println(scale_name_); + } else { + scale_ = &OC::Scales::GetScale(scale); + mutable_scale_ = nullptr; + scale_name_ = OC::scale_names_short[scale]; + // Serial.print("Editing const scale "); + // Serial.println(scale_name_); + } + owner_ = owner; + BeginEditing(SEQ_MODE); + } + + void Close(); + void Draw(); + void HandleButtonEvent(const UI::Event &event); + void HandleEncoderEvent(const UI::Event &event); + static uint16_t RotateMask(uint16_t mask, int num_notes, int amount); + +private: + + Owner *owner_; + const char * scale_name_; + const braids::Scale *scale_; + Scale *mutable_scale_; + + uint16_t mask_; + size_t cursor_pos_; + size_t scaling_cursor_pos_; + size_t num_notes_; + int8_t edit_this_scale_; + bool SEQ_MODE; + uint8_t edit_page_; + uint32_t ticks_; + + void BeginEditing(bool mode); + void move_cursor(int offset); + + void toggle_mask(); + void invert_mask(); + + void apply_mask(uint16_t mask) { + + if (mask_ != mask) { + mask_ = mask; + owner_->update_scale_mask(mask_, edit_this_scale_); + } + } + + void reset_scale(); + void change_note(size_t pos, int delta, bool notify); + void handleButtonLeft(const UI::Event &event); + void handleButtonUp(const UI::Event &event); + void handleButtonDown(const UI::Event &event); +}; + +template +void ScaleEditor::Draw() { + + if (edit_page_ < _SCALING) { + + size_t num_notes = num_notes_; + static constexpr weegfx::coord_t kMinWidth = 64; + weegfx::coord_t w = + mutable_scale_ ? ((num_notes + 1) * 7) : (num_notes * 7); + + if (w < kMinWidth || (edit_page_ == _ROOT) || (edit_page_ == _TRANSPOSE)) w = kMinWidth; + + weegfx::coord_t x = 64 - (w + 1) / 2; + weegfx::coord_t y = 16 - 3; + weegfx::coord_t h = 36; + + graphics.clearRect(x, y, w + 4, h); + graphics.drawFrame(x, y, w + 5, h); + + x += 2; + y += 3; + + graphics.setPrintPos(x, y); + graphics.print(scale_name_); + + if (SEQ_MODE) { + ticks_++; + uint8_t id = edit_this_scale_; + if (edit_this_scale_ == owner_->get_scale_select()) + id += 4; + graphics.print(OC::Strings::scale_id[id]); + } + + if (SEQ_MODE && edit_page_) { + + switch (edit_page_) { + + case _ROOT: + case _TRANSPOSE: { + x += w / 2 - (25 + 3); y += 10; + graphics.drawFrame(x, y, 25, 20); + graphics.drawFrame(x + 32, y, 25, 20); + // print root: + int root = owner_->get_root(edit_this_scale_); + if (root == 1 || root == 3 || root == 6 || root == 8 || root == 10) + graphics.setPrintPos(x + 7, y + 7); + else + graphics.setPrintPos(x + 9, y + 7); + graphics.print(OC::Strings::note_names_unpadded[root]); + // print transpose: + int transpose = owner_->get_transpose(edit_this_scale_); + graphics.setPrintPos(x + 32 + 5, y + 7); + if (transpose >= 0) + graphics.print("+"); + graphics.print(transpose); + // draw cursor + if (edit_page_ == _ROOT) + graphics.invertRect(x - 1, y - 1, 27, 22); + else + graphics.invertRect(x + 32 - 1, y - 1, 27, 22); + } + break; + default: + break; + } + } + else { + // edit scale + graphics.setPrintPos(x, y + 24); + if (cursor_pos_ != num_notes) { + graphics.movePrintPos(weegfx::Graphics::kFixedFontW, 0); + if (mutable_scale_ && OC::ui.read_immediate(OC::CONTROL_BUTTON_L)) + graphics.drawBitmap8(x + 1, y + 23, kBitmapEditIndicatorW, bitmap_edit_indicators_8); + else if (mutable_scale_) + graphics.drawBitmap8(x + 1, y + 23, 4, bitmap_indicator_4x8); + + uint32_t note_value = scale_->notes[cursor_pos_]; + uint32_t cents = (note_value * 100) >> 7; + uint32_t frac_cents = ((note_value * 100000) >> 7) - cents * 1000; + // move print position, so that things look somewhat nicer + if (cents < 10) + graphics.movePrintPos(weegfx::Graphics::kFixedFontW * -3, 0); + else if (cents < 100) + graphics.movePrintPos(weegfx::Graphics::kFixedFontW * -2, 0); + else if (cents < 1000) + graphics.movePrintPos(weegfx::Graphics::kFixedFontW * -1, 0); + // justify left ... + if (! mutable_scale_) + graphics.movePrintPos(weegfx::Graphics::kFixedFontW * -1, 0); + graphics.printf("%4u.%02uc", cents, (frac_cents + 5) / 10); + + } else { + graphics.print((int)num_notes); + } + + x += mutable_scale_ ? (w >> 0x1) - (((num_notes) * 7 + 4) >> 0x1) : (w >> 0x1) - (((num_notes - 1) * 7 + 4) >> 0x1); + y += 11; + uint16_t mask = mask_; + for (size_t i = 0; i < num_notes; ++i, x += 7, mask >>= 1) { + if (mask & 0x1) + graphics.drawRect(x, y, 4, 8); + else + graphics.drawBitmap8(x, y, 4, bitmap_empty_frame4x8); + + if (i == cursor_pos_) + graphics.drawFrame(x - 2, y - 2, 8, 12); + } + if (mutable_scale_) { + graphics.drawBitmap8(x, y, 4, bitmap_end_marker4x8); + if (cursor_pos_ == num_notes) + graphics.drawFrame(x - 2, y - 2, 8, 12); + } + } + } + else { + // scaling ... + weegfx::coord_t x = 0; + weegfx::coord_t y = 0; + weegfx::coord_t h = 64; + weegfx::coord_t w = 128; + + graphics.clearRect(x, y, w, h); + graphics.drawFrame(x, y, w, h); + + x += 5; + y += 5; + graphics.setPrintPos(x, y); + graphics.print(OC::Strings::scaling_string[0]); + graphics.print(OC::Strings::channel_id[DAC_CHANNEL_A]); + graphics.setPrintPos(x + 87, y); + graphics.print(OC::voltage_scalings[OC::DAC::get_voltage_scaling(DAC_CHANNEL_A)]); + y += 16; + graphics.setPrintPos(x, y); + graphics.print(OC::Strings::scaling_string[0]); + graphics.print(OC::Strings::channel_id[DAC_CHANNEL_B]); + graphics.setPrintPos(x + 87, y); + graphics.print(OC::voltage_scalings[OC::DAC::get_voltage_scaling(DAC_CHANNEL_B)]); + y += 16; + graphics.setPrintPos(x, y); + graphics.print(OC::Strings::scaling_string[0]); + graphics.print(OC::Strings::channel_id[DAC_CHANNEL_C]); + graphics.setPrintPos(x + 87, y); + graphics.print(OC::voltage_scalings[OC::DAC::get_voltage_scaling(DAC_CHANNEL_C)]); + y += 16; + graphics.setPrintPos(x, y); + graphics.print(OC::Strings::scaling_string[0]); + graphics.print(OC::Strings::channel_id[DAC_CHANNEL_D]); + graphics.setPrintPos(x + 87, y); + graphics.print(OC::voltage_scalings[OC::DAC::get_voltage_scaling(DAC_CHANNEL_D)]); + // draw cursor: + graphics.invertRect(x - 2, (scaling_cursor_pos_ << 4) + 3, w - 6, 11); + } +} + +template +void ScaleEditor::HandleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + handleButtonUp(event); + break; + case OC::CONTROL_BUTTON_DOWN: + handleButtonDown(event); + break; + case OC::CONTROL_BUTTON_L: + handleButtonLeft(event); + break; + case OC::CONTROL_BUTTON_R: + { + if (edit_page_ < _SCALING) + Close(); + else + edit_page_ = _SCALE; + } + break; + default: + break; + } + } + else if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { + + switch (event.control) { + case OC::CONTROL_BUTTON_L: + { + if (edit_page_ < _SCALING) + edit_page_ = _SCALING; + } + break; + default: + { + if (edit_page_ == _SCALING) + edit_page_ = _SCALE; + } + break; + } + } +} + +template +void ScaleEditor::HandleEncoderEvent(const UI::Event &event) { + bool scale_changed = false; + uint16_t mask = mask_; + + if (OC::CONTROL_ENCODER_L == event.control) { + + if (SEQ_MODE && OC::ui.read_immediate(OC::CONTROL_BUTTON_UP)) { + + // we're in Meta-Q, so we can change the scale: + + int _scale = owner_->get_scale(edit_this_scale_) + event.value; + CONSTRAIN(_scale, OC::Scales::SCALE_USER_0, OC::Scales::NUM_SCALES-1); + + if (_scale == OC::Scales::SCALE_NONE) { + // just skip this here ... + if (event.value > 0) + _scale++; + else + _scale--; + } + // update active scale with mask/root/transpose settings, and set flag: + owner_->set_scale_at_slot(_scale, mask_, owner_->get_root(edit_this_scale_), owner_->get_transpose(edit_this_scale_), edit_this_scale_); + scale_changed = true; + + if (_scale < OC::Scales::SCALE_USER_LAST) { + scale_ = mutable_scale_ = &OC::user_scales[_scale]; + scale_name_ = OC::scale_names_short[_scale]; + } + else { + scale_ = &OC::Scales::GetScale(_scale); + mutable_scale_ = nullptr; + scale_name_ = OC::scale_names_short[_scale]; + } + cursor_pos_ = 0; + num_notes_ = scale_->num_notes; + mask_ = owner_->get_scale_mask(edit_this_scale_); // ? can go, because the mask didn't change + ticks_ = 0; + } + else { + move_cursor(event.value); + } + } else if (OC::CONTROL_ENCODER_R == event.control) { + + switch (edit_page_) { + + case _SCALE: + { + bool handled = false; + if (mutable_scale_) { + if (cursor_pos_ < num_notes_) { + if (event.mask & OC::CONTROL_BUTTON_L) { + OC::ui.IgnoreButton(OC::CONTROL_BUTTON_L); + change_note(cursor_pos_, event.value, false); + scale_changed = true; + handled = true; + } + } else { + if (cursor_pos_ == num_notes_) { + int num_notes = num_notes_; + num_notes += event.value; + CONSTRAIN(num_notes, kMinScaleLength, kMaxScaleLength); + + num_notes_ = num_notes; + if (event.value > 0) { + for (size_t pos = cursor_pos_; pos < num_notes_; ++pos) + change_note(pos, 0, false); + + // Enable new notes by default + mask |= ~(0xffff << (num_notes_ - cursor_pos_)) << cursor_pos_; + } else { + // scale might be shortened to where no notes are active in mask + if (0 == (mask & ~(0xffff < num_notes_))) + mask |= 0x1; + } + + mutable_scale_->num_notes = num_notes_; + cursor_pos_ = num_notes_; + handled = scale_changed = true; + } + } + } + if (!handled) { + mask = RotateMask(mask_, num_notes_, event.value); + } + } + break; + case _ROOT: + { + int _root = owner_->get_root(edit_this_scale_) + event.value; + CONSTRAIN(_root, 0, 11); + owner_->set_scale_at_slot(owner_->get_scale(edit_this_scale_), mask_, _root, owner_->get_transpose(edit_this_scale_), edit_this_scale_); + } + break; + case _TRANSPOSE: + { + int _transpose = owner_->get_transpose(edit_this_scale_) + event.value; + CONSTRAIN(_transpose, -12, 12); + owner_->set_scale_at_slot(owner_->get_scale(edit_this_scale_), mask_, owner_->get_root(edit_this_scale_), _transpose, edit_this_scale_); + } + break; + case _SCALING: + { + int item = scaling_cursor_pos_ + event.value; + CONSTRAIN(item, DAC_CHANNEL_A, DAC_CHANNEL_LAST - 0x1); + scaling_cursor_pos_ = item; + } + break; + default: + break; + } + } + // This isn't entirely atomic + apply_mask(mask); + if (scale_changed) + owner_->scale_changed(); +} + +template +void ScaleEditor::move_cursor(int offset) { + + switch (edit_page_) { + + case _ROOT: + case _TRANSPOSE: + { + int8_t item = edit_page_ + offset; + CONSTRAIN(item, _ROOT, _TRANSPOSE); + edit_page_ = item; + } + break; + case _SCALING: + { + int8_t scaling = OC::DAC::get_voltage_scaling(scaling_cursor_pos_) + offset; + CONSTRAIN(scaling, VOLTAGE_SCALING_1V_PER_OCT, VOLTAGE_SCALING_LAST - 0x1); + OC::DAC::set_scaling(scaling, scaling_cursor_pos_); + } + break; + default: + { + int cursor_pos = cursor_pos_ + offset; + const int max = mutable_scale_ ? num_notes_ : num_notes_ - 1; + CONSTRAIN(cursor_pos, 0, max); + cursor_pos_ = cursor_pos; + } + break; + } +} + +template +void ScaleEditor::handleButtonUp(const UI::Event &event) { + + if (event.mask & OC::CONTROL_BUTTON_L) { + OC::ui.IgnoreButton(OC::CONTROL_BUTTON_L); + if (cursor_pos_ == num_notes_) + reset_scale(); + else + change_note(cursor_pos_, 128, true); + } else { + if (!SEQ_MODE) + invert_mask(); + else { + + if (ticks_ > 250) { + edit_this_scale_++; + if (edit_this_scale_ > 0x3) + edit_this_scale_ = 0; + + uint8_t scale = owner_->get_scale(edit_this_scale_); + // Serial.println(scale); + if (scale < OC::Scales::SCALE_USER_LAST) { + scale_ = mutable_scale_ = &OC::user_scales[scale]; + scale_name_ = OC::scale_names_short[scale]; + // Serial.print("Editing mutable scale "); + // Serial.println(scale_name_); + } + else { + scale_ = &OC::Scales::GetScale(scale); + mutable_scale_ = nullptr; + scale_name_ = OC::scale_names_short[scale]; + // Serial.print("Editing const scale "); + // Serial.println(scale_name_); + } + cursor_pos_ = 0; + num_notes_ = scale_->num_notes; + mask_ = owner_->get_scale_mask(edit_this_scale_); + } + } + } +} + +template +void ScaleEditor::handleButtonDown(const UI::Event &event) { + if (event.mask & OC::CONTROL_BUTTON_L) { + OC::ui.IgnoreButton(OC::CONTROL_BUTTON_L); + change_note(cursor_pos_, -128, true); + } else { + if (!SEQ_MODE) + invert_mask(); + else { + + if (ticks_ > 250) { + edit_this_scale_--; + if (edit_this_scale_ < 0) + edit_this_scale_ = 3; + + uint8_t scale = owner_->get_scale(edit_this_scale_); + // Serial.println(scale); + if (scale < OC::Scales::SCALE_USER_LAST) { + scale_ = mutable_scale_ = &OC::user_scales[scale]; + scale_name_ = OC::scale_names_short[scale]; + // Serial.print("Editing mutable scale "); + // Serial.println(scale_name_); + } + else { + scale_ = &OC::Scales::GetScale(scale); + mutable_scale_ = nullptr; + scale_name_ = OC::scale_names_short[scale]; + // Serial.print("Editing const scale "); + // Serial.println(scale_name_); + } + cursor_pos_ = 0; + num_notes_ = scale_->num_notes; + mask_ = owner_->get_scale_mask(edit_this_scale_); + } + } + } +} + +template +void ScaleEditor::handleButtonLeft(const UI::Event &) { + + if (SEQ_MODE && OC::ui.read_immediate(OC::CONTROL_BUTTON_UP)) { + + if (edit_page_ == _SCALE) + edit_page_ = _ROOT; // switch to root + // and don't accidentally change scale slot: + ticks_ = 0x0; + } + else { + // edit scale mask + if (edit_page_ > _SCALE) + edit_page_ = _SCALE; + else { + uint16_t m = 0x1 << cursor_pos_; + uint16_t mask = mask_; + + if (cursor_pos_ < num_notes_) { + // toggle note active state; avoid 0 mask + if (mask & m) { + if ((mask & ~(0xffff << num_notes_)) != m) + mask &= ~m; + } else { + mask |= m; + } + apply_mask(mask); + } + } + } +} + +template +void ScaleEditor::invert_mask() { + uint16_t m = ~(0xffffU << num_notes_); + uint16_t mask = mask_; + // Don't invert to zero + if ((mask & m) != m) + mask ^= m; + apply_mask(mask); +} + +template +/*static*/ uint16_t ScaleEditor::RotateMask(uint16_t mask, int num_notes, int amount) { + uint16_t used_bits = ~(0xffffU << num_notes); + mask &= used_bits; + + if (amount < 0) { + amount = -amount % num_notes; + mask = (mask >> amount) | (mask << (num_notes - amount)); + } else { + amount = amount % num_notes; + mask = (mask << amount) | (mask >> (num_notes - amount)); + } + return mask | ~used_bits; // fill upper bits +} + +template +void ScaleEditor::reset_scale() { + // Serial.println("Resetting scale to SEMI"); + + *mutable_scale_ = OC::Scales::GetScale(OC::Scales::SCALE_SEMI); + num_notes_ = mutable_scale_->num_notes; + cursor_pos_ = num_notes_; + mask_ = ~(0xfff << num_notes_); + apply_mask(mask_); +} + +template +void ScaleEditor::change_note(size_t pos, int delta, bool notify) { + if (mutable_scale_ && pos < num_notes_) { + int32_t note = mutable_scale_->notes[pos] + delta; + + const int32_t min = pos > 0 ? mutable_scale_->notes[pos - 1] : 0; + const int32_t max = pos < num_notes_ - 1 ? mutable_scale_->notes[pos + 1] : mutable_scale_->span; + + // TODO It's probably possible to construct a pothological scale, + // maybe factor cursor_pos into it somehow? + if (note <= min) note = pos > 0 ? min + 1 : 0; + if (note >= max) note = max - 1; + mutable_scale_->notes[pos] = note; +// braids::SortScale(*mutable_scale_); // TODO side effects? + + if (notify) + owner_->scale_changed(); + } +} + +template +void ScaleEditor::BeginEditing(bool mode) { + + cursor_pos_ = 0; + num_notes_ = scale_->num_notes; + + if (mode) { // == meta-Q + edit_this_scale_ = owner_->get_scale_select(); + mask_ = owner_->get_scale_mask(edit_this_scale_); + OC::ui._preemptScreensaver(true); + } + else { + mask_ = owner_->get_scale_mask(DUMMY); + } +} + +template +void ScaleEditor::Close() { + ui.SetButtonIgnoreMask(); + OC::ui._preemptScreensaver(false); + owner_ = nullptr; + edit_this_scale_ = 0; + edit_page_ = _SCALE; +} + +}; // namespace OC + +#endif // OC_SCALE_EDIT_H_ diff --git a/software/o_c_REV/OC_sequence_edit.h b/software/o_c_REV/OC_sequence_edit.h new file mode 100644 index 000000000..5c42d5785 --- /dev/null +++ b/software/o_c_REV/OC_sequence_edit.h @@ -0,0 +1,422 @@ +#ifndef OC_SEQUENCE_EDIT_H_ +#define OC_SEQUENCE_EDIT_H_ + +#include "OC_bitmaps.h" +#include "OC_patterns.h" +#include "OC_patterns_presets.h" +#include "OC_options.h" + +namespace OC { + +// Pattern editor +// based on scale editor written by Patrick Dowling, adapted for TU, re-adapted for OC +// + +template +class PatternEditor { +public: + + void Init() { + owner_ = nullptr; + pattern_name_ = "?!"; + pattern_ = mutable_pattern_ = &dummy_pattern; + mask_ = 0; + cursor_pos_ = 0; + num_slots_ = 0; + edit_this_sequence_ = 0; + } + + bool active() const { + return nullptr != owner_; + } + + void Edit(Owner *owner, int pattern) { + if (OC::Patterns::PATTERN_NONE == pattern) + return; + + pattern_ = mutable_pattern_ = &OC::user_patterns[pattern]; + pattern_name_ = OC::pattern_names_short[pattern]; + // Serial.print("Editing user pattern "); + // Serial.println(pattern_name_); + owner_ = owner; + + BeginEditing(); + } + + void Close(); + + void Draw(); + void HandleButtonEvent(const UI::Event &event); + void HandleEncoderEvent(const UI::Event &event); + static uint16_t RotateMask(uint16_t mask, int num_slots, int amount); + +private: + + Owner *owner_; + const char * pattern_name_; + const OC::Pattern *pattern_; + Pattern *mutable_pattern_; + + uint16_t mask_; + int8_t edit_this_sequence_; + size_t cursor_pos_; + size_t num_slots_; + + void BeginEditing(); + + void move_cursor(int offset); + void toggle_mask(); + void invert_mask(); + void clear_mask(); + void copy_sequence(); + void paste_sequence(); + + void apply_mask(uint16_t mask) { + + if (mask_ != mask) { + mask_ = mask; + owner_->update_pattern_mask(mask_, edit_this_sequence_); + } + + bool force = (owner_->get_current_sequence() == edit_this_sequence_); + owner_->pattern_changed(mask, force); + } + + void change_slot(size_t pos, int delta, bool notify); + void handleButtonLeft(const UI::Event &event); + void handleButtonUp(const UI::Event &event); + void handleButtonDown(const UI::Event &event); +}; + +template +void PatternEditor::Draw() { + size_t num_slots = num_slots_; + + weegfx::coord_t const w = 128; + weegfx::coord_t const h = 64; + weegfx::coord_t x = 0; + weegfx::coord_t y = 0; + graphics.clearRect(x, y, w, h); + graphics.drawFrame(x, y, w, h); + + x += 2; + y += 3; + + graphics.setPrintPos(x, y); + + uint8_t id = edit_this_sequence_; + + if (edit_this_sequence_ == owner_->get_sequence()) + graphics.printf("#%d", id + 1); + else { + graphics.drawBitmap8(x, y, 4, OC::bitmap_indicator_4x8); + graphics.setPrintPos(x + 4, y); + graphics.print(id + 1); + } + + if (cursor_pos_ == num_slots) { + // print length + graphics.print(":"); + if (num_slots > 9) + graphics.print((int)num_slots, 2); + else + graphics.print((int)num_slots, 1); + } + else { + // print pitch value at current step ... + // 0 -> 0V, 1536 -> 1V, 3072 -> 2V + int pitch = (int)owner_->get_pitch_at_step(edit_this_sequence_, cursor_pos_); + int32_t octave = pitch / (12 << 7); + int32_t frac = pitch - octave * (12 << 7); + int32_t cents = (int)((float)(frac%128)/1.28); + int32_t pClass = (int)floor(frac/128); + octave += owner_->get_octave(); + if (pitch < 0) { + pClass = (pClass+12)%12; + if (frac < -127) { + octave--; + } + graphics.printf(": %s%d %dc (%d)", OC::Strings::note_names_unpadded[pClass], octave, cents, frac%128); //, (float)frac / 128.0f); + } else { + graphics.printf(": %s%d +%dc (%d)", OC::Strings::note_names_unpadded[pClass], octave, cents, frac%128); //, (float)frac / 128.0f); + } + } + + x += 3 + (w >> 0x1) - (num_slots << 0x2); y += 40; + #ifdef BUCHLA_4U + y += 16; + #endif + + uint8_t clock_pos= owner_->get_clock_cnt(); + bool _draw_clock = (owner_->get_current_sequence() == edit_this_sequence_) && owner_->draw_clock(); + uint16_t mask = mask_; + + for (size_t i = 0; i < num_slots; ++i, x += 7, mask >>= 1) { + + int pitch = (int)owner_->get_pitch_at_step(edit_this_sequence_, i); + + bool _clock = (i == clock_pos && _draw_clock); + + if (mask & 0x1 & (pitch >= 0)) { + pitch += 0x100; + if (_clock) + graphics.drawRect(x - 1, y - (pitch >> 8), 6, pitch >> 8); + else + graphics.drawRect(x, y - (pitch >> 8), 4, pitch >> 8); + } + else if (mask & 0x1) { + pitch -= 0x100; + if (_clock) + graphics.drawRect(x - 1, y, 6, abs(pitch) >> 8); + else + graphics.drawRect(x, y, 4, abs(pitch) >> 8); + } + else if (pitch > - 0x200 && pitch < 0x200) { + // disabled steps not visible otherwise.. + graphics.drawRect(x + 1, y - 1, 2, 2); + } + else if (pitch >= 0) { + pitch += 0x100; + graphics.drawFrame(x, y - (pitch >> 8), 4, pitch >> 8); + } + else { + pitch -= 0x100; + graphics.drawFrame(x, y, 4, abs(pitch) >> 8); + } + + if (i == cursor_pos_) { + if (OC::ui.read_immediate(OC::CONTROL_BUTTON_L)) + graphics.drawFrame(x - 3, y - 5, 10, 10); + else + graphics.drawFrame(x - 2, y - 4, 8, 8); + } + + if (_clock) + graphics.drawRect(x, y + 17, 4, 2); + + } + if (mutable_pattern_) { + graphics.drawFrame(x, y - 2, 4, 4); + if (cursor_pos_ == num_slots) + graphics.drawFrame(x - 2, y - 4, 8, 8); + } +} + +template +void PatternEditor::HandleButtonEvent(const UI::Event &event) { + + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + handleButtonUp(event); + break; + case OC::CONTROL_BUTTON_DOWN: + handleButtonDown(event); + break; + case OC::CONTROL_BUTTON_L: + handleButtonLeft(event); + break; + case OC::CONTROL_BUTTON_R: + Close(); + break; + default: + break; + } + } + else if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + // screensaver // TODO: ideally, needs to be overridden ... invert_mask(); + break; + case OC::CONTROL_BUTTON_DOWN: + { + if (OC::ui.read_immediate(OC::CONTROL_BUTTON_L)) + clear_mask(); + else + paste_sequence(); + } + break; + case OC::CONTROL_BUTTON_L: + { + if (OC::ui.read_immediate(OC::CONTROL_BUTTON_DOWN)) + clear_mask(); + else + copy_sequence(); + } + break; + case OC::CONTROL_BUTTON_R: + // app menu + break; + default: + break; + } + } +} + +template +void PatternEditor::HandleEncoderEvent(const UI::Event &event) { + + uint16_t mask = mask_; + + if (OC::CONTROL_ENCODER_L == event.control) { + move_cursor(event.value); + } else if (OC::CONTROL_ENCODER_R == event.control) { + bool handled = false; + if (mutable_pattern_) { + if (cursor_pos_ >= num_slots_) { + + if (cursor_pos_ == num_slots_) { + int num_slots = num_slots_; + num_slots += event.value; + CONSTRAIN(num_slots, kMinPatternLength, kMaxPatternLength); + + num_slots_ = num_slots; + if (event.value > 0) { + // erase slots when expanding? + if (OC::ui.read_immediate(OC::CONTROL_BUTTON_L)) { + mask &= ~(~(0xffff << (num_slots_ - cursor_pos_)) << cursor_pos_); + owner_->set_pitch_at_step(edit_this_sequence_, num_slots_, 0x0); + } + } + // empty patterns are ok -- + owner_->set_sequence_length(num_slots_, edit_this_sequence_); + cursor_pos_ = num_slots_; + handled = true; + } + } + } + + if (!handled) { + + int32_t pitch = owner_->get_pitch_at_step(edit_this_sequence_, cursor_pos_); + // Q? might be better to actually add whatever is in the scale + // or semitone/finetune? + int16_t delta = event.value; + if (OC::ui.read_immediate(OC::CONTROL_BUTTON_L)) + pitch += delta; // fine + else + pitch += (delta << 7); // semitone + #ifdef BUCHLA_4U + CONSTRAIN(pitch, 0x0, 8 * (12 << 7) - 128); + #else + CONSTRAIN(pitch, -3 * (12 << 7), 5 * (12 << 7) - 128); + #endif + owner_->set_pitch_at_step(edit_this_sequence_, cursor_pos_, pitch); + + } + } + // This isn't entirely atomic + apply_mask(mask); +} + +template +void PatternEditor::move_cursor(int offset) { + + int cursor_pos = cursor_pos_ + offset; + const int max = mutable_pattern_ ? num_slots_ : num_slots_ - 1; + CONSTRAIN(cursor_pos, 0, max); + cursor_pos_ = cursor_pos; +} + +template +void PatternEditor::handleButtonUp(const UI::Event &event) { + + // next pattern / edit 'offline': + edit_this_sequence_++; + if (edit_this_sequence_ > OC::Patterns::PATTERN_USER_LAST-1) + edit_this_sequence_ = 0; + + cursor_pos_ = 0; + num_slots_ = owner_->get_sequence_length(edit_this_sequence_); + mask_ = owner_->get_mask(edit_this_sequence_); +} + +template +void PatternEditor::handleButtonDown(const UI::Event &event) { + + // next pattern / edit 'offline': + edit_this_sequence_--; + if (edit_this_sequence_ < 0) + edit_this_sequence_ = OC::Patterns::PATTERN_USER_LAST-1; + + cursor_pos_ = 0; + num_slots_ = owner_->get_sequence_length(edit_this_sequence_); + mask_ = owner_->get_mask(edit_this_sequence_); +} + +template +void PatternEditor::handleButtonLeft(const UI::Event &) { + uint16_t m = 0x1 << cursor_pos_; + uint16_t mask = mask_; + + if (cursor_pos_ < num_slots_) { + // toggle slot active state; allow 0 mask + if (mask & m) + mask &= ~m; + else + mask |= m; + apply_mask(mask); + } +} + +template +void PatternEditor::invert_mask() { + uint16_t m = ~(0xffffU << num_slots_); + uint16_t mask = mask_ ^ m; + apply_mask(mask); +} + +template +void PatternEditor::clear_mask() { + // clear the mask + apply_mask(0x00); + // and the user pattern: + owner_->clear_user_pattern(edit_this_sequence_); +} + +template +void PatternEditor::copy_sequence() { + owner_->copy_seq(edit_this_sequence_, num_slots_, mask_); +} + +template +void PatternEditor::paste_sequence() { + uint8_t newslots = owner_->paste_seq(edit_this_sequence_); + num_slots_ = newslots ? newslots : num_slots_; + mask_ = owner_->get_mask(edit_this_sequence_); +} + +template +/*static*/ uint16_t PatternEditor::RotateMask(uint16_t mask, int num_slots, int amount) { + uint16_t used_bits = ~(0xffffU << num_slots); + mask &= used_bits; + + if (amount < 0) { + amount = -amount % num_slots; + mask = (mask >> amount) | (mask << (num_slots - amount)); + } else { + amount = amount % num_slots; + mask = (mask << amount) | (mask >> (num_slots - amount)); + } + return mask | ~used_bits; // fill upper bits +} + +template +void PatternEditor::BeginEditing() { + + cursor_pos_ = 0; + uint8_t seq = owner_->get_sequence(); + edit_this_sequence_ = seq; + num_slots_ = owner_->get_sequence_length(seq); + mask_ = owner_->get_mask(seq); +} + +template +void PatternEditor::Close() { + ui.SetButtonIgnoreMask(); + owner_ = nullptr; +} + +}; // namespace OC + +#endif // OC_PATTERN_EDIT_H_ diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index c8680b639..051e346a1 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -2,10 +2,10 @@ #define OC_VERSION_H_ #ifndef OC_VERSION_EXTRA -#define OC_VERSION_EXTRA " main" +#define OC_VERSION_EXTRA "" #endif #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.9" OC_VERSION_EXTRA +#define OC_VERSION "v1.4.99" OC_VERSION_EXTRA #define OC_VERSION_URL "github.com/djphazer" #endif diff --git a/software/o_c_REV/frames_poly_lfo.cpp b/software/o_c_REV/frames_poly_lfo.cpp new file mode 100644 index 000000000..aee41e0a4 --- /dev/null +++ b/software/o_c_REV/frames_poly_lfo.cpp @@ -0,0 +1,257 @@ +// Copyright 2013 Olivier Gillet, 2015, 2016 Patrick Dowling and Tim Churches +// +// Original author: Olivier Gillet (ol.gillet@gmail.com) +// Adapted and modified for use in the Ornament + Crime module by: +// Patrick Dowling (pld@gurkenkiste.com) and Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Poly LFO. + +#include "frames_poly_lfo.h" + +#include +#include + +#include "extern/stmlib_utils_dsp.h" +#include "util/util_math.h" +#include "frames_resources.h" + +namespace frames { + +using namespace std; +using namespace stmlib; + +void PolyLfo::Init() { + freq_range_ = 9; + spread_ = 0; + shape_ = 0; + shape_spread_ = 0; + coupling_ = 0; + attenuation_ = 58880; + offset_ = 0 ; + freq_div_b_ = freq_div_c_ = freq_div_d_ = POLYLFO_FREQ_MULT_NONE ; + b_am_by_a_ = 0 ; + c_am_by_b_ = 0 ; + d_am_by_c_ = 0 ; + phase_reset_flag_ = false; + sync_counter_ = 0 ; + sync_ = false; + period_ = 0 ; + std::fill(&value_[0], &value_[kNumChannels], 0); + std::fill(&wt_value_[0], &wt_value_[kNumChannels], 0); + std::fill(&phase_[0], &phase_[kNumChannels], 0); + last_phase_difference_ = 0; + last_phase_difference_ = 0; + pattern_predictor_.Init(); +} + +/* static */ +uint32_t PolyLfo::FrequencyToPhaseIncrement(int32_t frequency, uint16_t frq_rng) { + int32_t shifts = frequency / 5040; + int32_t index = frequency - shifts * 5040; + uint32_t a; + uint32_t b; + switch(frq_rng){ + // "cosm", "geol", "glacl", "snail", "sloth", "vlazy", "lazy", "vslow", "slow", "med", "fast", "vfast", + + case 0: // cosmological + a = lut_increments_med[index >> 5] >> 11; + b = lut_increments_med[(index >> 5) + 1] >> 11; + break; + case 1: // geological + a = lut_increments_med[index >> 5] >> 9; + b = lut_increments_med[(index >> 5) + 1] >> 9; + break; + case 2: // glacial + a = lut_increments_med[index >> 5] >> 7; + b = lut_increments_med[(index >> 5) + 1] >> 7; + break; + case 3: // snail + a = lut_increments_med[index >> 5] >> 6; + b = lut_increments_med[(index >> 5) + 1] >> 6; + break; + case 4: // sloth + a = lut_increments_med[index >> 5] >> 5; + b = lut_increments_med[(index >> 5) + 1] >> 5; + break; + case 5: // vlazy + a = lut_increments_med[index >> 5] >> 4; + b = lut_increments_med[(index >> 5) + 1] >> 4; + break; + case 6: // lazy + a = lut_increments_med[index >> 5] >> 3; + b = lut_increments_med[(index >> 5) + 1] >> 3; + break; + case 7: // vslow + a = lut_increments_med[index >> 5] >> 2; + b = lut_increments_med[(index >> 5) + 1] >> 2; + break; + case 8: //slow + a = lut_increments_med[index >> 5] >> 1 ; + b = lut_increments_med[(index >> 5) + 1] >> 1 ; + break; + case 9: // medium + a = lut_increments_med[index >> 5] ; + b = lut_increments_med[(index >> 5) + 1]; + break; + case 10: // fast + a = lut_increments_med[index >> 5] << 1; + b = lut_increments_med[(index >> 5) + 1] << 1; + break; + case 11: // vfast + a = lut_increments_med[index >> 5] << 2; + b = lut_increments_med[(index >> 5) + 1] << 2; + break; + default: + a = lut_increments_med[index >> 5]; + b = lut_increments_med[(index >> 5) + 1]; + break; + } + return (a + ((b - a) * (index & 0x1f) >> 5)) << shifts; +} + +void PolyLfo::Render(int32_t frequency, bool reset_phase, bool tempo_sync, uint8_t freq_mult) { + ++sync_counter_; + if (tempo_sync && sync_) { + if (sync_counter_ < kSyncCounterMaxTime) { + uint32_t period = 0; + if (sync_counter_ < 167) { + period = (3 * period_ + sync_counter_) >> 2; + tempo_sync = false; + } else { + period = pattern_predictor_.Predict(sync_counter_); + } + if (period != period_) { + period_ = period; + sync_phase_increment_ = 0xffffffff / period_; + //sync_phase_increment_ = multiply_u32xu32_rshift24((0xffffffff / period_), 16183969) ; + } + } + sync_counter_ = 0; + } + + + // reset phase + if (reset_phase || phase_reset_flag_) { + std::fill(&phase_[0], &phase_[kNumChannels], 0); + phase_reset_flag_ = false ; + } else { + // increment freqs for each LFO + if (sync_) { + phase_increment_ch1_ = sync_phase_increment_; + } else { + phase_increment_ch1_ = FrequencyToPhaseIncrement(frequency, freq_range_); + } + + // double F (via TR4) ? ... "/8", "/4", "/2", "x2", "x4", "x8" + if (freq_mult < 0xFF) { + phase_increment_ch1_ = (freq_mult < 0x3) ? (phase_increment_ch1_ >> (0x3 - freq_mult)) : phase_increment_ch1_ << (freq_mult - 0x2); + } + + phase_[0] += phase_increment_ch1_; + PolyLfoFreqMultipliers FreqDivs[] = {POLYLFO_FREQ_MULT_NONE, freq_div_b_, freq_div_c_ , freq_div_d_} ; + for (uint8_t i = 1; i < kNumChannels; ++i) { + if (FreqDivs[i] == POLYLFO_FREQ_MULT_NONE) { + phase_[i] += phase_increment_ch1_; + } else { + phase_[i] += multiply_u32xu32_rshift24(phase_increment_ch1_, PolyLfoFreqMultNumerators[FreqDivs[i]]) ; + } + } + + // Advance phasors. + if (spread_ >= 0) { + phase_difference_ = static_cast(spread_) << 15; + if (freq_div_b_ == POLYLFO_FREQ_MULT_NONE) { + phase_[1] = phase_[0] + phase_difference_; + } else { + phase_[1] = phase_[1] - last_phase_difference_ + phase_difference_; + } + if (freq_div_c_ == POLYLFO_FREQ_MULT_NONE) { + phase_[2] = phase_[0] + (2 * phase_difference_); + } else { + phase_[2] = phase_[2] - last_phase_difference_ + phase_difference_; + } + if (freq_div_d_ == POLYLFO_FREQ_MULT_NONE) { + phase_[3] = phase_[0] + (3 * phase_difference_); + } else { + phase_[3] = phase_[3] - last_phase_difference_ + phase_difference_; + } + } else { + for (uint8_t i = 1; i < kNumChannels; ++i) { + // phase_[i] += FrequencyToPhaseIncrement(frequency, freq_range_); + phase_[i] -= i * (phase_increment_ch1_ >> 16) * spread_ ; + // frequency -= 5040 * spread_ >> 15; + } + } + last_phase_difference_ = phase_difference_; + } + + const uint8_t* sine = &wt_lfo_waveforms[17 * 257]; + + uint16_t wavetable_index = shape_; + uint8_t xor_depths[] = {0, b_xor_a_, c_xor_a_, d_xor_a_ } ; + uint8_t am_depths[] = {0, b_am_by_a_, c_am_by_b_, d_am_by_c_ } ; + // Wavetable lookup + for (uint8_t i = 0; i < kNumChannels; ++i) { + uint32_t phase = phase_[i]; + if (coupling_ > 0) { + phase += value_[(i + 1) % kNumChannels] * coupling_; + } else { + phase += value_[(i + kNumChannels - 1) % kNumChannels] * -coupling_; + } + const uint8_t* a = &wt_lfo_waveforms[(wavetable_index >> 12) * 257]; + const uint8_t* b = a + 257; + wt_value_[i] = Crossfade(a, b, phase, wavetable_index << 4) ; + value_[i] = Interpolate824(sine, phase); + level_[i] = (wt_value_[i] + 32768) >> 8; + // add bit-XOR + uint8_t depth_xor = xor_depths[i]; + if (depth_xor) { + dac_code_[i] = (wt_value_[i] + 32768) ^ (((wt_value_[0] + 32768) >> depth_xor) << depth_xor) ; + } else { + dac_code_[i] = wt_value_[i] + 32768; //Keyframer::ConvertToDacCode(value + 32768, 0); + } + // cross-channel AM + dac_code_[i] = (dac_code_[i] * (65535 - (((65535 - dac_code_[i-1]) * am_depths[i]) >> 8))) >> 16 ; + // attenuationand offset + dac_code_[i] = ((dac_code_[i] * attenuation_) >> 16) + offset_ ; + wavetable_index += shape_spread_; + } +} + +void PolyLfo::RenderPreview(uint16_t shape, uint16_t *buffer, size_t size) { + uint16_t wavetable_index = shape; + uint32_t phase = 0; + uint32_t phase_increment = (0xff << 24) / size; + while (size--) { + const uint8_t* a = &wt_lfo_waveforms[(wavetable_index >> 12) * 257]; + const uint8_t* b = a + 257; + int16_t value = Crossfade(a, b, phase, wavetable_index << 4); + *buffer++ = value + 32768; + phase += phase_increment; + } +} + + +} // namespace frames diff --git a/software/o_c_REV/frames_poly_lfo.h b/software/o_c_REV/frames_poly_lfo.h new file mode 100644 index 000000000..2925a4689 --- /dev/null +++ b/software/o_c_REV/frames_poly_lfo.h @@ -0,0 +1,310 @@ +// Copyright 2013 Olivier Gillet, 2015, 2016 Patrick Dowling and Tim Churches +// +// Original author: Olivier Gillet (ol.gillet@gmail.com) +// Adapted and modified for use in the Ornament + Crime module by: +// Patrick Dowling (pld@gurkenkiste.com) and Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Poly LFO. + +#ifndef FRAMES_POLY_LFO_H_ +#define FRAMES_POLY_LFO_H_ + +#include "util/util_macros.h" +//#include "stmlib/stmlib.h" +//#include "frames/keyframer.h" +#include "peaks_pattern_predictor.h" + +const uint32_t kSyncCounterMaxTime = 8 * 16667; + +namespace frames { + +const size_t kNumChannels = 4; + +enum PolyLfoFreqMultipliers { + POLYLFO_FREQ_MULT_BY16, // 0 + POLYLFO_FREQ_MULT_BY15, // 1 + POLYLFO_FREQ_MULT_BY14, // 2 + POLYLFO_FREQ_MULT_BY13, // 3 + POLYLFO_FREQ_MULT_BY12, // 4 + POLYLFO_FREQ_MULT_BY11, // 5 + POLYLFO_FREQ_MULT_BY10, // 6 + POLYLFO_FREQ_MULT_BY9, // 7 + POLYLFO_FREQ_MULT_BY8, // 8 + POLYLFO_FREQ_MULT_BY7, // 9 + POLYLFO_FREQ_MULT_BY6, // 10 + POLYLFO_FREQ_MULT_BY5, // 11 + POLYLFO_FREQ_MULT_BY4, // 12 + POLYLFO_FREQ_MULT_BY3, // 13 + POLYLFO_FREQ_MULT_5_OVER_2, // 14 + POLYLFO_FREQ_MULT_BY2, // 15 + POLYLFO_FREQ_MULT_5_OVER_3, // 16 + POLYLFO_FREQ_MULT_3_OVER_2, // 17 + POLYLFO_FREQ_MULT_5_OVER_4, // 18 + POLYLFO_FREQ_MULT_NONE, // 19 + POLYLFO_FREQ_MULT_4_OVER_5, // 20 + POLYLFO_FREQ_MULT_2_OVER_3, // 21 + POLYLFO_FREQ_MULT_3_OVER_5, // 22 + POLYLFO_FREQ_MULT_1_OVER_2, // 23 + POLYLFO_FREQ_MULT_2_OVER_5, // 24 + POLYLFO_FREQ_MULT_1_OVER_3, // 25 + POLYLFO_FREQ_MULT_1_OVER_4, // 26 + POLYLFO_FREQ_MULT_1_OVER_5, // 27 + POLYLFO_FREQ_MULT_1_OVER_6, // 28 + POLYLFO_FREQ_MULT_1_OVER_7, // 29 + POLYLFO_FREQ_MULT_1_OVER_8, // 30 + POLYLFO_FREQ_MULT_1_OVER_9, // 31 + POLYLFO_FREQ_MULT_1_OVER_10, // 32 + POLYLFO_FREQ_MULT_1_OVER_11, // 33 + POLYLFO_FREQ_MULT_1_OVER_12, // 34 + POLYLFO_FREQ_MULT_1_OVER_13, // 35 + POLYLFO_FREQ_MULT_1_OVER_14, // 36 + POLYLFO_FREQ_MULT_1_OVER_15, // 37 + POLYLFO_FREQ_MULT_1_OVER_16, // 38 + POLYLFO_FREQ_MULT_LAST // 39 +}; + +int32_t const PolyLfoFreqMultNumerators[] = { + 268435456, // POLYLFO_FREQ_MULT_BY16, = 0 + 251658240, // POLYLFO_FREQ_MULT_BY15, = 1 + 234881024, // POLYLFO_FREQ_MULT_BY14, = 2 + 218103808, // POLYLFO_FREQ_MULT_BY13, = 3 + 201326592, // POLYLFO_FREQ_MULT_BY12, = 4 + 184549376, // POLYLFO_FREQ_MULT_BY11, = 5 + 167772160, // POLYLFO_FREQ_MULT_BY10, = 6 + 150994944, // POLYLFO_FREQ_MULT_BY9, = 7 + 134217728, // POLYLFO_FREQ_MULT_BY8, = 8 + 117440512, // POLYLFO_FREQ_MULT_BY7, = 9 + 100663296, // POLYLFO_FREQ_MULT_BY6, = 10 + 83886080, // POLYLFO_FREQ_MULT_BY5, = 11 + 67108864, // POLYLFO_FREQ_MULT_BY4, = 12 + 50331648, // POLYLFO_FREQ_MULT_BY3, = 13 + 41943040, // POLYLFO_FREQ_MULT_5_OVER_2, = 14 + 33554432, // POLYLFO_FREQ_MULT_BY2, = 15 + 27962027, // POLYLFO_FREQ_MULT_5_OVER_3, = 16 + 25165824, // POLYLFO_FREQ_MULT_3_OVER_2, = 17 + 20971520, // POLYLFO_FREQ_MULT_5_OVER_4, = 18 + 16777216, // POLYLFO_FREQ_MULT_NONE, = 19 + 13421772, // POLYLFO_FREQ_MULT_4_OVER_5, = 20 + 11184810, // POLYLFO_FREQ_MULT_2_OVER_3, = 21 + 10066329, // POLYLFO_FREQ_MULT_3_OVER_5, = 22 + 8388608, // POLYLFO_FREQ_MULT_1_OVER_2, = 23 + 6710886, // POLYLFO_FREQ_MULT_2_OVER_5, = 24 + 5592405, // POLYLFO_FREQ_MULT_1_OVER_3, = 25 + 4194304, // POLYLFO_FREQ_MULT_1_OVER_4, = 26 + 3355443, // POLYLFO_FREQ_MULT_1_OVER_5, = 27 + 2796202, // POLYLFO_FREQ_MULT_1_OVER_6, = 28 + 2396745, // POLYLFO_FREQ_MULT_1_OVER_7, = 29 + 2097152, // POLYLFO_FREQ_MULT_1_OVER_8, = 30 + 1864135, // POLYLFO_FREQ_MULT_1_OVER_9, = 31 + 1677721, // POLYLFO_FREQ_MULT_1_OVER_10, = 32 + 1525201, // POLYLFO_FREQ_MULT_1_OVER_11, = 33 + 1398101, // POLYLFO_FREQ_MULT_1_OVER_12, = 34 + 1290555, // POLYLFO_FREQ_MULT_1_OVER_13, = 35 + 1198372, // POLYLFO_FREQ_MULT_1_OVER_14, = 36 + 1118481, // POLYLFO_FREQ_MULT_1_OVER_15, = 37 + 1048576, // POLYLFO_FREQ_MULT_1_OVER_16, = 38 +} ; + +class PolyLfo { + public: + PolyLfo() { } + ~PolyLfo() { } + + void Init(); + void Render(int32_t frequency, bool reset_phase, bool tempo_sync, uint8_t freq_mult); + void RenderPreview(uint16_t shape, uint16_t *buffer, size_t size); + + inline void set_freq_range(uint16_t freq_range) { + freq_range_ = freq_range; + } + + inline void set_shape(uint16_t shape) { + shape_ = shape; + } + + inline void set_shape_spread(uint16_t shape_spread) { + shape_spread_ = static_cast(shape_spread - 32767) >> 1; + } + + inline void set_spread(uint16_t spread) { + if (spread < 32768) { + int32_t x = spread - 32768; + int32_t scaled = -(x * x >> 15); + spread_ = (x + 3 * scaled) >> 2; + } else { + spread_ = spread - 32768; + } + } + + inline void set_coupling(uint16_t coupling) { + int32_t x = coupling - 32768; + int32_t scaled = x * x >> 15; + scaled = x > 0 ? scaled : - scaled; + scaled = (x + 3 * scaled) >> 2; + coupling_ = (scaled >> 4) * 10; + + } + + inline void set_attenuation(uint16_t attenuation) { + attenuation_ = attenuation; + // attenuation_ = 65535; + } + + inline void set_offset(int16_t offs) { + offset_ = offs; + } + + inline void set_freq_div_b(PolyLfoFreqMultipliers div) { + if (div != freq_div_b_) { + freq_div_b_ = div; + phase_reset_flag_ = true; + } + } + + inline void set_freq_div_c(PolyLfoFreqMultipliers div) { + if (div != freq_div_c_) { + freq_div_c_ = div; + phase_reset_flag_ = true; + } + } + + inline void set_freq_div_d(PolyLfoFreqMultipliers div) { + if (div != freq_div_d_) { + freq_div_d_ = div; + phase_reset_flag_ = true; + } + } + + inline void set_b_xor_a(uint8_t xor_value) { + if (xor_value) { + b_xor_a_ = 16 - xor_value ; + } else { + b_xor_a_ = 0; + } + } + + inline void set_c_xor_a(uint8_t xor_value) { + if (xor_value) { + c_xor_a_ = 16 - xor_value ; + } else { + c_xor_a_ = 0; + } + } + + inline void set_d_xor_a(uint8_t xor_value) { + if (xor_value) { + d_xor_a_ = 16 - xor_value ; + } else { + d_xor_a_ = 0; + } + } + + inline void set_b_am_by_a(uint8_t am_value) { + b_am_by_a_ = (am_value << 1); + } + + inline void set_c_am_by_b(uint8_t am_value) { + c_am_by_b_ = (am_value << 1); + } + + inline void set_d_am_by_c(uint8_t am_value) { + d_am_by_c_ = (am_value << 1); + } + + inline void set_phase_reset_flag(bool reset) { + phase_reset_flag_ = reset; + } + + inline void set_sync(bool sync) { + sync_ = sync; + } + + inline int get_sync() { + return static_cast(sync_); + } + + inline long get_sync_phase_increment() { + return sync_phase_increment_; + } + + inline float get_freq_ch1() { + return(static_cast(16666.6666666666666666667f * static_cast(phase_increment_ch1_) / static_cast(0xffffffff))); + } + + inline long get_sync_counter() { + return sync_counter_; + } + + inline uint8_t level(uint8_t index) const { + return level_[index]; + } + + inline const uint16_t dac_code(uint8_t index) const { + return dac_code_[index]; + } + + static uint32_t FrequencyToPhaseIncrement(int32_t frequency, uint16_t frq_rng); + + + private: + uint16_t freq_range_ ; + uint16_t shape_; + int16_t shape_spread_; + int32_t spread_; + int16_t coupling_; + int32_t attenuation_; + int32_t offset_; + PolyLfoFreqMultipliers freq_div_b_; + PolyLfoFreqMultipliers freq_div_c_; + PolyLfoFreqMultipliers freq_div_d_; + uint8_t b_xor_a_ ; + uint8_t c_xor_a_ ; + uint8_t d_xor_a_ ; + uint8_t b_am_by_a_ ; + uint8_t c_am_by_b_ ; + uint8_t d_am_by_c_ ; + bool phase_reset_flag_ ; + + int16_t value_[kNumChannels]; + int16_t wt_value_[kNumChannels]; + uint32_t phase_[kNumChannels]; + uint32_t phase_increment_ch1_; + uint8_t level_[kNumChannels]; + uint16_t dac_code_[kNumChannels]; + + bool sync_ ; + uint32_t sync_counter_; + stmlib::PatternPredictor<32, 8> pattern_predictor_; + uint32_t period_; + uint32_t sync_phase_increment_; + uint32_t phase_difference_ ; + uint32_t last_phase_difference_ ; + + DISALLOW_COPY_AND_ASSIGN(PolyLfo); +}; + +} // namespace frames + +#endif // FRAMES_POLY_LFO_H_ diff --git a/software/o_c_REV/frames_resources.cpp b/software/o_c_REV/frames_resources.cpp new file mode 100644 index 000000000..6179a2957 --- /dev/null +++ b/software/o_c_REV/frames_resources.cpp @@ -0,0 +1,2770 @@ +// Copyright 2013 Olivier Gillet. +// +// Author: Olivier Gillet (ol.gillet@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Resources definitions. +// +// Automatically generated with: +// make resources + + +#include "frames_resources.h" + +namespace frames { + +static const char str_dummy[] = "dummy"; + + +const char* string_table[] = { + str_dummy, +}; + +/* +const uint16_t lut_easing_in_quartic[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 2, 2, 2, + 2, 2, 2, 2, + 2, 3, 3, 3, + 3, 3, 3, 4, + 4, 4, 4, 4, + 5, 5, 5, 5, + 5, 6, 6, 6, + 6, 7, 7, 7, + 8, 8, 8, 9, + 9, 9, 10, 10, + 10, 11, 11, 11, + 12, 12, 13, 13, + 14, 14, 15, 15, + 15, 16, 17, 17, + 18, 18, 19, 19, + 20, 20, 21, 22, + 22, 23, 24, 24, + 25, 26, 27, 27, + 28, 29, 30, 30, + 31, 32, 33, 34, + 35, 36, 37, 38, + 39, 40, 41, 42, + 43, 44, 45, 46, + 47, 48, 49, 50, + 52, 53, 54, 55, + 57, 58, 59, 61, + 62, 63, 65, 66, + 68, 69, 71, 72, + 74, 76, 77, 79, + 80, 82, 84, 86, + 87, 89, 91, 93, + 95, 97, 99, 101, + 103, 105, 107, 109, + 111, 113, 115, 118, + 120, 122, 125, 127, + 129, 132, 134, 137, + 139, 142, 144, 147, + 150, 152, 155, 158, + 161, 163, 166, 169, + 172, 175, 178, 181, + 184, 188, 191, 194, + 197, 201, 204, 207, + 211, 214, 218, 221, + 225, 229, 232, 236, + 240, 244, 248, 252, + 255, 260, 264, 268, + 272, 276, 280, 285, + 289, 293, 298, 302, + 307, 312, 316, 321, + 326, 331, 335, 340, + 345, 350, 356, 361, + 366, 371, 376, 382, + 387, 393, 398, 404, + 410, 415, 421, 427, + 433, 439, 445, 451, + 457, 463, 470, 476, + 482, 489, 495, 502, + 509, 515, 522, 529, + 536, 543, 550, 557, + 564, 572, 579, 586, + 594, 601, 609, 617, + 624, 632, 640, 648, + 656, 664, 673, 681, + 689, 698, 706, 715, + 724, 732, 741, 750, + 759, 768, 777, 787, + 796, 805, 815, 824, + 834, 844, 854, 864, + 874, 884, 894, 904, + 915, 925, 936, 946, + 957, 968, 979, 990, + 1001, 1012, 1023, 1034, + 1046, 1057, 1069, 1081, + 1093, 1105, 1117, 1129, + 1141, 1153, 1166, 1178, + 1191, 1204, 1216, 1229, + 1242, 1255, 1269, 1282, + 1295, 1309, 1323, 1336, + 1350, 1364, 1378, 1393, + 1407, 1421, 1436, 1450, + 1465, 1480, 1495, 1510, + 1525, 1541, 1556, 1572, + 1587, 1603, 1619, 1635, + 1651, 1667, 1684, 1700, + 1717, 1734, 1750, 1767, + 1785, 1802, 1819, 1837, + 1854, 1872, 1890, 1908, + 1926, 1944, 1962, 1981, + 2000, 2018, 2037, 2056, + 2075, 2095, 2114, 2134, + 2153, 2173, 2193, 2213, + 2234, 2254, 2274, 2295, + 2316, 2337, 2358, 2379, + 2400, 2422, 2444, 2465, + 2487, 2509, 2532, 2554, + 2577, 2599, 2622, 2645, + 2668, 2692, 2715, 2739, + 2762, 2786, 2810, 2834, + 2859, 2883, 2908, 2933, + 2958, 2983, 3008, 3034, + 3059, 3085, 3111, 3137, + 3164, 3190, 3217, 3243, + 3270, 3297, 3325, 3352, + 3380, 3408, 3436, 3464, + 3492, 3520, 3549, 3578, + 3607, 3636, 3665, 3695, + 3725, 3755, 3785, 3815, + 3845, 3876, 3907, 3938, + 3969, 4000, 4032, 4064, + 4095, 4128, 4160, 4192, + 4225, 4258, 4291, 4324, + 4357, 4391, 4425, 4459, + 4493, 4528, 4562, 4597, + 4632, 4667, 4703, 4738, + 4774, 4810, 4846, 4883, + 4919, 4956, 4993, 5030, + 5068, 5105, 5143, 5181, + 5219, 5258, 5297, 5336, + 5375, 5414, 5454, 5493, + 5533, 5574, 5614, 5655, + 5696, 5737, 5778, 5819, + 5861, 5903, 5945, 5988, + 6031, 6073, 6117, 6160, + 6203, 6247, 6291, 6336, + 6380, 6425, 6470, 6515, + 6560, 6606, 6652, 6698, + 6745, 6791, 6838, 6885, + 6933, 6980, 7028, 7076, + 7124, 7173, 7222, 7271, + 7320, 7370, 7420, 7470, + 7520, 7571, 7622, 7673, + 7724, 7776, 7828, 7880, + 7932, 7985, 8038, 8091, + 8144, 8198, 8252, 8306, + 8361, 8416, 8471, 8526, + 8582, 8638, 8694, 8750, + 8807, 8864, 8921, 8978, + 9036, 9094, 9153, 9211, + 9270, 9329, 9389, 9449, + 9509, 9569, 9630, 9690, + 9752, 9813, 9875, 9937, + 9999, 10062, 10125, 10188, + 10252, 10316, 10380, 10444, + 10509, 10574, 10639, 10705, + 10771, 10837, 10903, 10970, + 11037, 11105, 11173, 11241, + 11309, 11378, 11447, 11516, + 11586, 11656, 11726, 11797, + 11868, 11939, 12010, 12082, + 12154, 12227, 12300, 12373, + 12446, 12520, 12594, 12669, + 12744, 12819, 12894, 12970, + 13046, 13123, 13199, 13277, + 13354, 13432, 13510, 13588, + 13667, 13746, 13826, 13906, + 13986, 14067, 14148, 14229, + 14310, 14392, 14475, 14557, + 14640, 14724, 14807, 14891, + 14976, 15061, 15146, 15231, + 15317, 15403, 15490, 15577, + 15664, 15752, 15840, 15929, + 16017, 16106, 16196, 16286, + 16376, 16467, 16558, 16649, + 16741, 16833, 16926, 17019, + 17112, 17206, 17300, 17394, + 17489, 17585, 17680, 17776, + 17873, 17969, 18067, 18164, + 18262, 18361, 18459, 18559, + 18658, 18758, 18858, 18959, + 19060, 19162, 19264, 19366, + 19469, 19572, 19676, 19780, + 19885, 19989, 20095, 20200, + 20307, 20413, 20520, 20627, + 20735, 20843, 20952, 21061, + 21171, 21280, 21391, 21502, + 21613, 21724, 21836, 21949, + 22062, 22175, 22289, 22403, + 22518, 22633, 22749, 22865, + 22981, 23098, 23215, 23333, + 23451, 23570, 23689, 23809, + 23929, 24049, 24170, 24291, + 24413, 24535, 24658, 24781, + 24905, 25029, 25154, 25279, + 25404, 25530, 25657, 25784, + 25911, 26039, 26168, 26296, + 26426, 26555, 26686, 26816, + 26948, 27079, 27212, 27344, + 27477, 27611, 27745, 27880, + 28015, 28150, 28286, 28423, + 28560, 28698, 28836, 28974, + 29113, 29253, 29393, 29533, + 29674, 29816, 29958, 30101, + 30244, 30387, 30531, 30676, + 30821, 30967, 31113, 31260, + 31407, 31555, 31703, 31852, + 32001, 32151, 32301, 32452, + 32603, 32755, 32908, 33061, + 33214, 33368, 33523, 33678, + 33833, 33990, 34146, 34304, + 34461, 34620, 34779, 34938, + 35098, 35259, 35420, 35581, + 35744, 35906, 36070, 36234, + 36398, 36563, 36728, 36895, + 37061, 37228, 37396, 37565, + 37734, 37903, 38073, 38244, + 38415, 38587, 38759, 38932, + 39106, 39280, 39454, 39630, + 39805, 39982, 40159, 40336, + 40515, 40693, 40873, 41053, + 41233, 41414, 41596, 41778, + 41961, 42145, 42329, 42514, + 42699, 42885, 43072, 43259, + 43447, 43635, 43824, 44014, + 44204, 44395, 44586, 44778, + 44971, 45164, 45358, 45553, + 45748, 45944, 46140, 46337, + 46535, 46733, 46932, 47132, + 47332, 47533, 47735, 47937, + 48140, 48343, 48547, 48752, + 48957, 49163, 49370, 49577, + 49785, 49994, 50203, 50413, + 50624, 50835, 51047, 51260, + 51473, 51687, 51901, 52116, + 52332, 52549, 52766, 52984, + 53203, 53422, 53642, 53863, + 54084, 54306, 54529, 54752, + 54976, 55201, 55426, 55652, + 55879, 56107, 56335, 56564, + 56793, 57024, 57255, 57486, + 57719, 57952, 58186, 58420, + 58655, 58891, 59128, 59365, + 59603, 59842, 60081, 60322, + 60563, 60804, 61047, 61290, + 61534, 61778, 62023, 62269, + 62516, 62764, 63012, 63261, + 63510, 63761, 64012, 64264, + 64516, 64770, 65024, 65279, + 65535, +}; +const uint16_t lut_easing_out_quartic[] = { + 0, 255, 510, 764, + 1018, 1270, 1522, 1773, + 2024, 2273, 2522, 2770, + 3018, 3265, 3511, 3756, + 4000, 4244, 4487, 4730, + 4971, 5212, 5453, 5692, + 5931, 6169, 6406, 6643, + 6879, 7114, 7348, 7582, + 7815, 8048, 8279, 8510, + 8741, 8970, 9199, 9427, + 9655, 9882, 10108, 10333, + 10558, 10782, 11005, 11228, + 11450, 11671, 11892, 12112, + 12331, 12550, 12768, 12985, + 13202, 13418, 13633, 13847, + 14061, 14274, 14487, 14699, + 14910, 15121, 15331, 15540, + 15749, 15957, 16164, 16371, + 16577, 16782, 16987, 17191, + 17394, 17597, 17799, 18001, + 18202, 18402, 18602, 18801, + 18999, 19197, 19394, 19590, + 19786, 19981, 20176, 20370, + 20563, 20756, 20948, 21139, + 21330, 21520, 21710, 21899, + 22087, 22275, 22462, 22649, + 22835, 23020, 23205, 23389, + 23573, 23756, 23938, 24120, + 24301, 24481, 24661, 24841, + 25019, 25198, 25375, 25552, + 25729, 25904, 26080, 26254, + 26428, 26602, 26775, 26947, + 27119, 27290, 27461, 27631, + 27800, 27969, 28138, 28306, + 28473, 28639, 28806, 28971, + 29136, 29300, 29464, 29628, + 29790, 29953, 30114, 30275, + 30436, 30596, 30755, 30914, + 31073, 31230, 31388, 31544, + 31701, 31856, 32011, 32166, + 32320, 32473, 32626, 32779, + 32931, 33082, 33233, 33383, + 33533, 33682, 33831, 33979, + 34127, 34274, 34421, 34567, + 34713, 34858, 35003, 35147, + 35290, 35433, 35576, 35718, + 35860, 36001, 36141, 36281, + 36421, 36560, 36698, 36836, + 36974, 37111, 37248, 37384, + 37519, 37654, 37789, 37923, + 38057, 38190, 38322, 38455, + 38586, 38718, 38848, 38979, + 39108, 39238, 39366, 39495, + 39623, 39750, 39877, 40004, + 40130, 40255, 40380, 40505, + 40629, 40753, 40876, 40999, + 41121, 41243, 41364, 41485, + 41605, 41725, 41845, 41964, + 42083, 42201, 42319, 42436, + 42553, 42669, 42785, 42901, + 43016, 43131, 43245, 43359, + 43472, 43585, 43698, 43810, + 43921, 44032, 44143, 44254, + 44363, 44473, 44582, 44691, + 44799, 44907, 45014, 45121, + 45227, 45334, 45439, 45545, + 45649, 45754, 45858, 45962, + 46065, 46168, 46270, 46372, + 46474, 46575, 46676, 46776, + 46876, 46975, 47075, 47173, + 47272, 47370, 47467, 47565, + 47661, 47758, 47854, 47949, + 48045, 48140, 48234, 48328, + 48422, 48515, 48608, 48701, + 48793, 48885, 48976, 49067, + 49158, 49248, 49338, 49428, + 49517, 49605, 49694, 49782, + 49870, 49957, 50044, 50131, + 50217, 50303, 50388, 50473, + 50558, 50643, 50727, 50810, + 50894, 50977, 51059, 51142, + 51224, 51305, 51386, 51467, + 51548, 51628, 51708, 51788, + 51867, 51946, 52024, 52102, + 52180, 52257, 52335, 52411, + 52488, 52564, 52640, 52715, + 52790, 52865, 52940, 53014, + 53088, 53161, 53234, 53307, + 53380, 53452, 53524, 53595, + 53666, 53737, 53808, 53878, + 53948, 54018, 54087, 54156, + 54225, 54293, 54361, 54429, + 54497, 54564, 54631, 54697, + 54763, 54829, 54895, 54960, + 55025, 55090, 55154, 55218, + 55282, 55346, 55409, 55472, + 55535, 55597, 55659, 55721, + 55782, 55844, 55904, 55965, + 56025, 56085, 56145, 56205, + 56264, 56323, 56381, 56440, + 56498, 56556, 56613, 56670, + 56727, 56784, 56840, 56896, + 56952, 57008, 57063, 57118, + 57173, 57228, 57282, 57336, + 57390, 57443, 57496, 57549, + 57602, 57654, 57706, 57758, + 57810, 57861, 57912, 57963, + 58014, 58064, 58114, 58164, + 58214, 58263, 58312, 58361, + 58410, 58458, 58506, 58554, + 58601, 58649, 58696, 58743, + 58789, 58836, 58882, 58928, + 58974, 59019, 59064, 59109, + 59154, 59198, 59243, 59287, + 59331, 59374, 59417, 59461, + 59503, 59546, 59589, 59631, + 59673, 59715, 59756, 59797, + 59838, 59879, 59920, 59960, + 60001, 60041, 60080, 60120, + 60159, 60198, 60237, 60276, + 60315, 60353, 60391, 60429, + 60466, 60504, 60541, 60578, + 60615, 60651, 60688, 60724, + 60760, 60796, 60831, 60867, + 60902, 60937, 60972, 61006, + 61041, 61075, 61109, 61143, + 61177, 61210, 61243, 61276, + 61309, 61342, 61374, 61406, + 61439, 61470, 61502, 61534, + 61565, 61596, 61627, 61658, + 61689, 61719, 61749, 61779, + 61809, 61839, 61869, 61898, + 61927, 61956, 61985, 62014, + 62042, 62070, 62098, 62126, + 62154, 62182, 62209, 62237, + 62264, 62291, 62317, 62344, + 62370, 62397, 62423, 62449, + 62475, 62500, 62526, 62551, + 62576, 62601, 62626, 62651, + 62675, 62700, 62724, 62748, + 62772, 62795, 62819, 62842, + 62866, 62889, 62912, 62935, + 62957, 62980, 63002, 63025, + 63047, 63069, 63090, 63112, + 63134, 63155, 63176, 63197, + 63218, 63239, 63260, 63280, + 63300, 63321, 63341, 63361, + 63381, 63400, 63420, 63439, + 63459, 63478, 63497, 63516, + 63534, 63553, 63572, 63590, + 63608, 63626, 63644, 63662, + 63680, 63697, 63715, 63732, + 63749, 63767, 63784, 63800, + 63817, 63834, 63850, 63867, + 63883, 63899, 63915, 63931, + 63947, 63962, 63978, 63993, + 64009, 64024, 64039, 64054, + 64069, 64084, 64098, 64113, + 64127, 64141, 64156, 64170, + 64184, 64198, 64211, 64225, + 64239, 64252, 64265, 64279, + 64292, 64305, 64318, 64330, + 64343, 64356, 64368, 64381, + 64393, 64405, 64417, 64429, + 64441, 64453, 64465, 64477, + 64488, 64500, 64511, 64522, + 64533, 64544, 64555, 64566, + 64577, 64588, 64598, 64609, + 64619, 64630, 64640, 64650, + 64660, 64670, 64680, 64690, + 64700, 64710, 64719, 64729, + 64738, 64747, 64757, 64766, + 64775, 64784, 64793, 64802, + 64810, 64819, 64828, 64836, + 64845, 64853, 64861, 64870, + 64878, 64886, 64894, 64902, + 64910, 64917, 64925, 64933, + 64940, 64948, 64955, 64962, + 64970, 64977, 64984, 64991, + 64998, 65005, 65012, 65019, + 65025, 65032, 65039, 65045, + 65052, 65058, 65064, 65071, + 65077, 65083, 65089, 65095, + 65101, 65107, 65113, 65119, + 65124, 65130, 65136, 65141, + 65147, 65152, 65158, 65163, + 65168, 65173, 65178, 65184, + 65189, 65194, 65199, 65203, + 65208, 65213, 65218, 65222, + 65227, 65232, 65236, 65241, + 65245, 65249, 65254, 65258, + 65262, 65266, 65270, 65274, + 65279, 65282, 65286, 65290, + 65294, 65298, 65302, 65305, + 65309, 65313, 65316, 65320, + 65323, 65327, 65330, 65333, + 65337, 65340, 65343, 65346, + 65350, 65353, 65356, 65359, + 65362, 65365, 65368, 65371, + 65373, 65376, 65379, 65382, + 65384, 65387, 65390, 65392, + 65395, 65397, 65400, 65402, + 65405, 65407, 65409, 65412, + 65414, 65416, 65419, 65421, + 65423, 65425, 65427, 65429, + 65431, 65433, 65435, 65437, + 65439, 65441, 65443, 65445, + 65447, 65448, 65450, 65452, + 65454, 65455, 65457, 65458, + 65460, 65462, 65463, 65465, + 65466, 65468, 65469, 65471, + 65472, 65473, 65475, 65476, + 65477, 65479, 65480, 65481, + 65482, 65484, 65485, 65486, + 65487, 65488, 65489, 65490, + 65491, 65492, 65493, 65494, + 65495, 65496, 65497, 65498, + 65499, 65500, 65501, 65502, + 65503, 65504, 65504, 65505, + 65506, 65507, 65507, 65508, + 65509, 65510, 65510, 65511, + 65512, 65512, 65513, 65514, + 65514, 65515, 65515, 65516, + 65516, 65517, 65517, 65518, + 65519, 65519, 65519, 65520, + 65520, 65521, 65521, 65522, + 65522, 65523, 65523, 65523, + 65524, 65524, 65524, 65525, + 65525, 65525, 65526, 65526, + 65526, 65527, 65527, 65527, + 65528, 65528, 65528, 65528, + 65529, 65529, 65529, 65529, + 65529, 65530, 65530, 65530, + 65530, 65530, 65531, 65531, + 65531, 65531, 65531, 65531, + 65532, 65532, 65532, 65532, + 65532, 65532, 65532, 65532, + 65533, 65533, 65533, 65533, + 65533, 65533, 65533, 65533, + 65533, 65533, 65533, 65533, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65534, 65534, 65534, 65534, + 65535, +}; +const uint16_t lut_easing_in_out_sine[] = { + 0, 0, 0, 1, + 2, 3, 5, 7, + 9, 12, 15, 18, + 22, 26, 30, 34, + 39, 44, 49, 55, + 61, 67, 74, 81, + 88, 96, 104, 112, + 120, 129, 138, 148, + 157, 167, 178, 188, + 199, 210, 222, 234, + 246, 258, 271, 284, + 298, 311, 325, 340, + 354, 369, 384, 400, + 416, 432, 448, 465, + 482, 499, 517, 535, + 553, 572, 590, 610, + 629, 649, 669, 689, + 710, 731, 752, 774, + 796, 818, 840, 863, + 886, 910, 933, 957, + 982, 1006, 1031, 1056, + 1082, 1107, 1133, 1160, + 1186, 1213, 1241, 1268, + 1296, 1324, 1353, 1381, + 1410, 1440, 1469, 1499, + 1530, 1560, 1591, 1622, + 1653, 1685, 1717, 1749, + 1782, 1815, 1848, 1881, + 1915, 1949, 1983, 2018, + 2053, 2088, 2123, 2159, + 2195, 2231, 2268, 2305, + 2342, 2380, 2417, 2455, + 2494, 2532, 2571, 2610, + 2650, 2690, 2730, 2770, + 2811, 2852, 2893, 2934, + 2976, 3018, 3060, 3103, + 3146, 3189, 3232, 3276, + 3320, 3364, 3408, 3453, + 3498, 3544, 3589, 3635, + 3681, 3728, 3774, 3821, + 3869, 3916, 3964, 4012, + 4060, 4109, 4158, 4207, + 4256, 4306, 4356, 4406, + 4457, 4508, 4559, 4610, + 4661, 4713, 4765, 4818, + 4870, 4923, 4976, 5030, + 5083, 5137, 5191, 5246, + 5300, 5355, 5411, 5466, + 5522, 5578, 5634, 5691, + 5747, 5804, 5862, 5919, + 5977, 6035, 6093, 6152, + 6210, 6269, 6329, 6388, + 6448, 6508, 6568, 6629, + 6689, 6750, 6812, 6873, + 6935, 6997, 7059, 7121, + 7184, 7247, 7310, 7374, + 7437, 7501, 7565, 7630, + 7694, 7759, 7824, 7890, + 7955, 8021, 8087, 8153, + 8220, 8286, 8353, 8420, + 8488, 8556, 8623, 8691, + 8760, 8828, 8897, 8966, + 9035, 9105, 9174, 9244, + 9314, 9385, 9455, 9526, + 9597, 9668, 9739, 9811, + 9883, 9955, 10027, 10100, + 10172, 10245, 10319, 10392, + 10465, 10539, 10613, 10687, + 10762, 10836, 10911, 10986, + 11061, 11137, 11212, 11288, + 11364, 11440, 11517, 11593, + 11670, 11747, 11824, 11902, + 11980, 12057, 12135, 12214, + 12292, 12371, 12449, 12528, + 12607, 12687, 12766, 12846, + 12926, 13006, 13086, 13167, + 13247, 13328, 13409, 13490, + 13572, 13653, 13735, 13817, + 13899, 13981, 14064, 14147, + 14229, 14312, 14396, 14479, + 14562, 14646, 14730, 14814, + 14898, 14982, 15067, 15152, + 15236, 15321, 15407, 15492, + 15578, 15663, 15749, 15835, + 15921, 16007, 16094, 16181, + 16267, 16354, 16441, 16529, + 16616, 16704, 16791, 16879, + 16967, 17055, 17143, 17232, + 17321, 17409, 17498, 17587, + 17676, 17766, 17855, 17945, + 18034, 18124, 18214, 18304, + 18395, 18485, 18576, 18666, + 18757, 18848, 18939, 19030, + 19122, 19213, 19305, 19396, + 19488, 19580, 19672, 19765, + 19857, 19949, 20042, 20135, + 20227, 20320, 20413, 20507, + 20600, 20693, 20787, 20880, + 20974, 21068, 21162, 21256, + 21350, 21444, 21539, 21633, + 21728, 21823, 21917, 22012, + 22107, 22203, 22298, 22393, + 22488, 22584, 22680, 22775, + 22871, 22967, 23063, 23159, + 23255, 23351, 23448, 23544, + 23641, 23737, 23834, 23931, + 24027, 24124, 24221, 24319, + 24416, 24513, 24610, 24708, + 24805, 24903, 25000, 25098, + 25196, 25294, 25392, 25490, + 25588, 25686, 25784, 25882, + 25980, 26079, 26177, 26276, + 26374, 26473, 26572, 26670, + 26769, 26868, 26967, 27066, + 27165, 27264, 27363, 27462, + 27562, 27661, 27760, 27860, + 27959, 28058, 28158, 28258, + 28357, 28457, 28556, 28656, + 28756, 28856, 28956, 29055, + 29155, 29255, 29355, 29455, + 29555, 29655, 29755, 29855, + 29956, 30056, 30156, 30256, + 30356, 30457, 30557, 30657, + 30758, 30858, 30958, 31059, + 31159, 31260, 31360, 31460, + 31561, 31661, 31762, 31862, + 31963, 32063, 32164, 32264, + 32365, 32465, 32566, 32666, + 32767, 32868, 32968, 33069, + 33169, 33270, 33370, 33471, + 33571, 33672, 33772, 33873, + 33973, 34074, 34174, 34274, + 34375, 34475, 34576, 34676, + 34776, 34877, 34977, 35077, + 35178, 35278, 35378, 35478, + 35578, 35679, 35779, 35879, + 35979, 36079, 36179, 36279, + 36379, 36479, 36578, 36678, + 36778, 36878, 36978, 37077, + 37177, 37276, 37376, 37476, + 37575, 37674, 37774, 37873, + 37972, 38072, 38171, 38270, + 38369, 38468, 38567, 38666, + 38765, 38864, 38962, 39061, + 39160, 39258, 39357, 39455, + 39554, 39652, 39750, 39848, + 39946, 40044, 40142, 40240, + 40338, 40436, 40534, 40631, + 40729, 40826, 40924, 41021, + 41118, 41215, 41313, 41410, + 41507, 41603, 41700, 41797, + 41893, 41990, 42086, 42183, + 42279, 42375, 42471, 42567, + 42663, 42759, 42854, 42950, + 43046, 43141, 43236, 43331, + 43427, 43522, 43617, 43711, + 43806, 43901, 43995, 44090, + 44184, 44278, 44372, 44466, + 44560, 44654, 44747, 44841, + 44934, 45027, 45121, 45214, + 45307, 45399, 45492, 45585, + 45677, 45769, 45862, 45954, + 46046, 46138, 46229, 46321, + 46412, 46504, 46595, 46686, + 46777, 46868, 46958, 47049, + 47139, 47230, 47320, 47410, + 47500, 47589, 47679, 47768, + 47858, 47947, 48036, 48125, + 48213, 48302, 48391, 48479, + 48567, 48655, 48743, 48830, + 48918, 49005, 49093, 49180, + 49267, 49353, 49440, 49527, + 49613, 49699, 49785, 49871, + 49956, 50042, 50127, 50213, + 50298, 50382, 50467, 50552, + 50636, 50720, 50804, 50888, + 50972, 51055, 51138, 51222, + 51305, 51387, 51470, 51553, + 51635, 51717, 51799, 51881, + 51962, 52044, 52125, 52206, + 52287, 52367, 52448, 52528, + 52608, 52688, 52768, 52847, + 52927, 53006, 53085, 53163, + 53242, 53320, 53399, 53477, + 53554, 53632, 53710, 53787, + 53864, 53941, 54017, 54094, + 54170, 54246, 54322, 54397, + 54473, 54548, 54623, 54698, + 54772, 54847, 54921, 54995, + 55069, 55142, 55215, 55289, + 55362, 55434, 55507, 55579, + 55651, 55723, 55795, 55866, + 55937, 56008, 56079, 56149, + 56220, 56290, 56360, 56429, + 56499, 56568, 56637, 56706, + 56774, 56843, 56911, 56978, + 57046, 57114, 57181, 57248, + 57314, 57381, 57447, 57513, + 57579, 57644, 57710, 57775, + 57840, 57904, 57969, 58033, + 58097, 58160, 58224, 58287, + 58350, 58413, 58475, 58537, + 58599, 58661, 58722, 58784, + 58845, 58905, 58966, 59026, + 59086, 59146, 59205, 59265, + 59324, 59382, 59441, 59499, + 59557, 59615, 59672, 59730, + 59787, 59843, 59900, 59956, + 60012, 60068, 60123, 60179, + 60234, 60288, 60343, 60397, + 60451, 60504, 60558, 60611, + 60664, 60716, 60769, 60821, + 60873, 60924, 60975, 61026, + 61077, 61128, 61178, 61228, + 61278, 61327, 61376, 61425, + 61474, 61522, 61570, 61618, + 61665, 61713, 61760, 61806, + 61853, 61899, 61945, 61990, + 62036, 62081, 62126, 62170, + 62214, 62258, 62302, 62345, + 62388, 62431, 62474, 62516, + 62558, 62600, 62641, 62682, + 62723, 62764, 62804, 62844, + 62884, 62924, 62963, 63002, + 63040, 63079, 63117, 63154, + 63192, 63229, 63266, 63303, + 63339, 63375, 63411, 63446, + 63481, 63516, 63551, 63585, + 63619, 63653, 63686, 63719, + 63752, 63785, 63817, 63849, + 63881, 63912, 63943, 63974, + 64004, 64035, 64065, 64094, + 64124, 64153, 64181, 64210, + 64238, 64266, 64293, 64321, + 64348, 64374, 64401, 64427, + 64452, 64478, 64503, 64528, + 64552, 64577, 64601, 64624, + 64648, 64671, 64694, 64716, + 64738, 64760, 64782, 64803, + 64824, 64845, 64865, 64885, + 64905, 64924, 64944, 64962, + 64981, 64999, 65017, 65035, + 65052, 65069, 65086, 65102, + 65118, 65134, 65150, 65165, + 65180, 65194, 65209, 65223, + 65236, 65250, 65263, 65276, + 65288, 65300, 65312, 65324, + 65335, 65346, 65356, 65367, + 65377, 65386, 65396, 65405, + 65414, 65422, 65430, 65438, + 65446, 65453, 65460, 65467, + 65473, 65479, 65485, 65490, + 65495, 65500, 65504, 65508, + 65512, 65516, 65519, 65522, + 65525, 65527, 65529, 65531, + 65532, 65533, 65534, 65534, + 65535, +}; +const uint16_t lut_easing_in_out_bounce[] = { + 0, 0, 1, 4, + 7, 11, 17, 23, + 30, 38, 47, 57, + 68, 79, 92, 106, + 120, 136, 153, 170, + 189, 208, 228, 250, + 272, 295, 319, 344, + 370, 397, 425, 454, + 483, 514, 546, 578, + 612, 647, 682, 718, + 756, 794, 833, 873, + 915, 957, 1000, 1044, + 1088, 1134, 1181, 1229, + 1278, 1327, 1378, 1429, + 1482, 1535, 1589, 1645, + 1701, 1758, 1816, 1875, + 1935, 1996, 2058, 2121, + 2185, 2250, 2315, 2382, + 2450, 2518, 2588, 2658, + 2730, 2802, 2875, 2949, + 3024, 3101, 3178, 3256, + 3335, 3414, 3495, 3577, + 3660, 3743, 3828, 3914, + 4000, 4087, 4176, 4265, + 4355, 4447, 4539, 4632, + 4726, 4821, 4917, 5014, + 5112, 5210, 5310, 5411, + 5512, 5615, 5719, 5823, + 5928, 6035, 6142, 6250, + 6359, 6470, 6581, 6693, + 6806, 6920, 7034, 7150, + 7267, 7385, 7503, 7623, + 7743, 7865, 7987, 8111, + 8235, 8360, 8486, 8614, + 8742, 8871, 9001, 9132, + 9263, 9396, 9530, 9665, + 9800, 9937, 10074, 10213, + 10352, 10493, 10634, 10776, + 10920, 11064, 11209, 11355, + 11502, 11650, 11799, 11949, + 12099, 12251, 12404, 12557, + 12712, 12867, 13024, 13181, + 13340, 13499, 13659, 13820, + 13982, 14145, 14309, 14474, + 14640, 14807, 14975, 15144, + 15313, 15484, 15656, 15828, + 16002, 16176, 16351, 16528, + 16705, 16883, 17062, 17242, + 17423, 17605, 17788, 17972, + 18157, 18343, 18529, 18717, + 18905, 19095, 19285, 19477, + 19669, 19863, 20057, 20252, + 20448, 20645, 20843, 21042, + 21242, 21443, 21645, 21848, + 22051, 22256, 22462, 22668, + 22876, 23084, 23294, 23504, + 23715, 23927, 24141, 24355, + 24570, 24786, 25003, 25221, + 25439, 25659, 25880, 26102, + 26324, 26548, 26772, 26998, + 27224, 27451, 27680, 27909, + 28139, 28370, 28602, 28835, + 29069, 29304, 29540, 29777, + 30015, 30253, 30493, 30734, + 30975, 31217, 31461, 31705, + 31951, 32197, 32444, 32692, + 32941, 33191, 33442, 33694, + 33947, 34201, 34456, 34711, + 34968, 35226, 35484, 35744, + 36004, 36265, 36528, 36791, + 37055, 37320, 37586, 37853, + 38121, 38390, 38660, 38931, + 39203, 39476, 39749, 40024, + 40299, 40576, 40853, 41132, + 41411, 41691, 41973, 42255, + 42538, 42822, 43107, 43393, + 43680, 43968, 44256, 44546, + 44837, 45129, 45421, 45715, + 46009, 46304, 46601, 46898, + 47196, 47496, 47796, 48097, + 48399, 48702, 49006, 49311, + 49616, 49923, 50231, 50539, + 50849, 51160, 51471, 51783, + 52097, 52411, 52726, 53043, + 53360, 53678, 53997, 54317, + 54638, 54960, 55282, 55606, + 55931, 56257, 56583, 56911, + 57239, 57569, 57899, 58230, + 58563, 58896, 59230, 59565, + 59901, 60238, 60576, 60915, + 61255, 61596, 61937, 62280, + 62624, 62968, 63314, 63660, + 64008, 64356, 64705, 65055, + 65407, 65423, 65248, 65074, + 64901, 64729, 64558, 64387, + 64218, 64050, 63882, 63716, + 63551, 63386, 63222, 63060, + 62898, 62737, 62578, 62419, + 62261, 62104, 61948, 61793, + 61639, 61485, 61333, 61182, + 61032, 60882, 60734, 60586, + 60440, 60294, 60149, 60006, + 59863, 59721, 59580, 59440, + 59301, 59163, 59026, 58890, + 58755, 58620, 58487, 58355, + 58223, 58093, 57963, 57835, + 57707, 57580, 57454, 57330, + 57206, 57083, 56961, 56840, + 56720, 56600, 56482, 56365, + 56249, 56133, 56019, 55905, + 55793, 55681, 55571, 55461, + 55352, 55244, 55138, 55032, + 54927, 54823, 54720, 54617, + 54516, 54416, 54317, 54218, + 54121, 54024, 53929, 53834, + 53741, 53648, 53556, 53466, + 53376, 53287, 53199, 53112, + 53026, 52941, 52856, 52773, + 52691, 52610, 52529, 52450, + 52371, 52294, 52217, 52141, + 52067, 51993, 51920, 51848, + 51777, 51707, 51638, 51570, + 51503, 51437, 51371, 51307, + 51244, 51181, 51120, 51059, + 51000, 50941, 50883, 50827, + 50771, 50716, 50662, 50609, + 50557, 50506, 50456, 50407, + 50358, 50311, 50265, 50219, + 50175, 50131, 50089, 50047, + 50006, 49967, 49928, 49890, + 49853, 49817, 49782, 49748, + 49715, 49683, 49651, 49621, + 49592, 49563, 49536, 49509, + 49484, 49459, 49436, 49413, + 49391, 49370, 49350, 49331, + 49313, 49296, 49280, 49265, + 49251, 49237, 49225, 49214, + 49203, 49194, 49185, 49178, + 49171, 49165, 49161, 49157, + 49154, 49152, 49151, 49151, + 49152, 49154, 49156, 49160, + 49165, 49170, 49177, 49185, + 49193, 49202, 49213, 49224, + 49236, 49250, 49264, 49279, + 49295, 49312, 49330, 49349, + 49368, 49389, 49411, 49433, + 49457, 49482, 49507, 49533, + 49561, 49589, 49618, 49649, + 49680, 49712, 49745, 49779, + 49814, 49850, 49887, 49924, + 49963, 50003, 50043, 50085, + 50127, 50171, 50215, 50260, + 50307, 50354, 50402, 50451, + 50501, 50552, 50604, 50657, + 50711, 50766, 50821, 50878, + 50936, 50994, 51054, 51114, + 51176, 51238, 51301, 51366, + 51431, 51497, 51564, 51632, + 51701, 51771, 51842, 51914, + 51986, 52060, 52135, 52210, + 52287, 52364, 52443, 52522, + 52602, 52684, 52766, 52849, + 52933, 53018, 53104, 53191, + 53279, 53368, 53457, 53548, + 53640, 53732, 53826, 53920, + 54016, 54112, 54209, 54308, + 54407, 54507, 54608, 54710, + 54813, 54917, 55022, 55128, + 55235, 55342, 55451, 55561, + 55671, 55783, 55895, 56009, + 56123, 56238, 56354, 56472, + 56590, 56709, 56829, 56950, + 57072, 57194, 57318, 57443, + 57569, 57695, 57823, 57951, + 58081, 58211, 58343, 58475, + 58608, 58742, 58877, 59014, + 59151, 59289, 59427, 59567, + 59708, 59850, 59993, 60136, + 60281, 60426, 60573, 60720, + 60869, 61018, 61168, 61319, + 61472, 61625, 61779, 61934, + 62090, 62246, 62404, 62563, + 62723, 62883, 63045, 63208, + 63371, 63536, 63701, 63867, + 64035, 64203, 64372, 64542, + 64713, 64885, 65058, 65232, + 65407, 65511, 65423, 65337, + 65252, 65167, 65084, 65001, + 64920, 64839, 64759, 64680, + 64603, 64526, 64450, 64375, + 64301, 64228, 64156, 64084, + 64014, 63945, 63876, 63809, + 63743, 63677, 63612, 63549, + 63486, 63424, 63364, 63304, + 63245, 63187, 63130, 63074, + 63019, 62964, 62911, 62859, + 62808, 62757, 62708, 62659, + 62612, 62565, 62519, 62475, + 62431, 62388, 62346, 62305, + 62265, 62226, 62188, 62151, + 62115, 62079, 62045, 62012, + 61979, 61948, 61917, 61887, + 61859, 61831, 61804, 61778, + 61754, 61730, 61707, 61685, + 61664, 61643, 61624, 61606, + 61589, 61572, 61557, 61542, + 61529, 61516, 61505, 61494, + 61484, 61475, 61467, 61461, + 61455, 61450, 61445, 61442, + 61440, 61439, 61439, 61439, + 61441, 61443, 61447, 61451, + 61457, 61463, 61470, 61478, + 61488, 61498, 61509, 61521, + 61534, 61548, 61562, 61578, + 61595, 61612, 61631, 61651, + 61671, 61693, 61715, 61738, + 61763, 61788, 61814, 61841, + 61869, 61898, 61928, 61959, + 61991, 62024, 62057, 62092, + 62128, 62164, 62202, 62240, + 62280, 62320, 62361, 62403, + 62447, 62491, 62536, 62582, + 62629, 62677, 62726, 62775, + 62826, 62878, 62930, 62984, + 63039, 63094, 63150, 63208, + 63266, 63325, 63386, 63447, + 63509, 63572, 63636, 63701, + 63767, 63833, 63901, 63970, + 64040, 64110, 64182, 64254, + 64328, 64402, 64477, 64554, + 64631, 64709, 64788, 64868, + 64949, 65031, 65114, 65198, + 65283, 65368, 65455, 65531, + 65487, 65445, 65403, 65362, + 65323, 65284, 65246, 65209, + 65174, 65139, 65105, 65072, + 65040, 65008, 64978, 64949, + 64921, 64893, 64867, 64841, + 64817, 64793, 64771, 64749, + 64728, 64708, 64689, 64671, + 64655, 64638, 64623, 64609, + 64596, 64584, 64573, 64562, + 64553, 64544, 64537, 64530, + 64525, 64520, 64516, 64513, + 64512, 64511, 64511, 64512, + 64514, 64516, 64520, 64525, + 64531, 64537, 64545, 64554, + 64563, 64574, 64585, 64597, + 64611, 64625, 64640, 64656, + 64673, 64691, 64710, 64730, + 64751, 64773, 64795, 64819, + 64844, 64869, 64896, 64923, + 64952, 64981, 65011, 65042, + 65075, 65108, 65142, 65177, + 65213, 65250, 65288, 65326, + 65366, 65407, 65448, 65491, + 65535, +}; +const uint16_t lut_response_balance[] = { + 0, 8, 23, 42, + 64, 90, 118, 149, + 182, 217, 254, 294, + 335, 377, 422, 467, + 515, 564, 615, 666, + 720, 774, 830, 888, + 946, 1006, 1067, 1129, + 1192, 1257, 1322, 1389, + 1457, 1525, 1595, 1666, + 1738, 1811, 1885, 1960, + 2036, 2113, 2190, 2269, + 2349, 2429, 2511, 2593, + 2676, 2760, 2845, 2931, + 3017, 3105, 3193, 3282, + 3372, 3463, 3554, 3647, + 3740, 3834, 3928, 4024, + 4120, 4217, 4315, 4413, + 4512, 4612, 4713, 4814, + 4916, 5019, 5122, 5227, + 5331, 5437, 5543, 5650, + 5758, 5866, 5975, 6085, + 6195, 6306, 6418, 6530, + 6643, 6756, 6871, 6985, + 7101, 7217, 7334, 7451, + 7569, 7687, 7807, 7926, + 8047, 8168, 8289, 8412, + 8534, 8658, 8782, 8906, + 9032, 9157, 9284, 9410, + 9538, 9666, 9795, 9924, + 10053, 10184, 10315, 10446, + 10578, 10710, 10843, 10977, + 11111, 11246, 11381, 11517, + 11653, 11790, 11927, 12065, + 12204, 12343, 12482, 12622, + 12762, 12903, 13045, 13187, + 13330, 13473, 13616, 13760, + 13905, 14050, 14196, 14342, + 14488, 14635, 14783, 14931, + 15080, 15229, 15378, 15528, + 15679, 15830, 15981, 16133, + 16286, 16439, 16592, 16746, + 16900, 17055, 17210, 17366, + 17522, 17679, 17836, 17994, + 18152, 18310, 18469, 18629, + 18789, 18949, 19110, 19271, + 19433, 19595, 19758, 19921, + 20084, 20248, 20412, 20577, + 20743, 20908, 21074, 21241, + 21408, 21576, 21743, 21912, + 22081, 22250, 22419, 22589, + 22760, 22931, 23102, 23274, + 23446, 23619, 23792, 23965, + 24139, 24313, 24488, 24663, + 24839, 25015, 25191, 25368, + 25545, 25723, 25901, 26079, + 26258, 26437, 26617, 26797, + 26977, 27158, 27339, 27521, + 27703, 27886, 28068, 28252, + 28435, 28619, 28804, 28989, + 29174, 29360, 29546, 29732, + 29919, 30106, 30294, 30481, + 30670, 30859, 31048, 31237, + 31427, 31617, 31808, 31999, + 32190, 32382, 32574, 32767, +}; +const uint16_t lut_vca_linear[] = { + 63130, 50916, 46455, 43664, + 41628, 40024, 38701, 37575, + 36594, 35726, 34946, 34240, + 33594, 32998, 32446, 31931, + 31449, 30996, 30568, 30163, + 29779, 29413, 29064, 28730, + 28411, 28104, 27809, 27525, + 27252, 26988, 26733, 26486, + 26247, 26015, 25790, 25572, + 25359, 25153, 24952, 24756, + 24565, 24379, 24197, 24019, + 23846, 23676, 23510, 23348, + 23189, 23033, 22881, 22731, + 22585, 22441, 22299, 22161, + 22025, 21891, 21760, 21630, + 21503, 21378, 21255, 21135, + 21015, 20898, 20783, 20669, + 20557, 20447, 20338, 20231, + 20125, 20020, 19918, 19816, + 19716, 19617, 19519, 19423, + 19328, 19234, 19141, 19049, + 18959, 18869, 18781, 18693, + 18607, 18521, 18436, 18353, + 18270, 18188, 18107, 18027, + 17948, 17869, 17792, 17715, + 17639, 17564, 17489, 17415, + 17342, 17269, 17198, 17127, + 17056, 16986, 16917, 16849, + 16781, 16713, 16647, 16581, + 16515, 16450, 16386, 16322, + 16258, 16195, 16133, 16071, + 16010, 15949, 15889, 15829, + 15769, 15710, 15652, 15594, + 15536, 15479, 15422, 15366, + 15310, 15255, 15200, 15145, + 15091, 15037, 14983, 14930, + 14877, 14825, 14773, 14721, + 14670, 14619, 14568, 14517, + 14467, 14418, 14368, 14319, + 14271, 14222, 14174, 14126, + 14079, 14032, 13985, 13938, + 13892, 13846, 13800, 13754, + 13709, 13664, 13619, 13575, + 13531, 13487, 13443, 13400, + 13357, 13314, 13271, 13228, + 13186, 13144, 13102, 13061, + 13020, 12979, 12938, 12897, + 12857, 12816, 12776, 12737, + 12697, 12658, 12618, 12579, + 12541, 12502, 12464, 12426, + 12388, 12350, 12312, 12275, + 12237, 12200, 12164, 12127, + 12090, 12054, 12018, 11982, + 11946, 11910, 11875, 11839, + 11804, 11769, 11734, 11700, + 11665, 11631, 11596, 11562, + 11528, 11495, 11461, 11428, + 11394, 11361, 11328, 11295, + 11262, 11230, 11197, 11165, + 11133, 11101, 11069, 11037, + 11005, 10974, 10942, 10911, + 10880, 10849, 10818, 10787, + 10757, 10726, 10696, 10666, + 10635, 10605, 10576, 10546, + 10516, 10486, 10457, 10428, + 10398, 10369, 10340, 10311, + 10283, 10254, 10225, 10197, + 10169, 10140, 10112, 10084, + 10056, 10029, 10001, 9973, + 9946, 9918, 9891, 9864, + 9837, 9810, 9783, 9756, + 9729, 9702, 9676, 9649, + 9623, 9597, 9570, 9544, + 9518, 9492, 9467, 9441, + 9415, 9390, 9364, 9339, + 9313, 9288, 9263, 9238, + 9213, 9188, 9163, 9138, + 9114, 9089, 9065, 9040, + 9016, 8992, 8967, 8943, + 8919, 8895, 8871, 8848, + 8824, 8800, 8777, 8753, + 8730, 8706, 8683, 8660, + 8637, 8614, 8591, 8568, + 8545, 8522, 8499, 8477, + 8454, 8431, 8409, 8386, + 8364, 8342, 8320, 8298, + 8275, 8253, 8231, 8210, + 8188, 8166, 8144, 8123, + 8101, 8080, 8058, 8037, + 8015, 7994, 7973, 7952, + 7931, 7910, 7889, 7868, + 7847, 7826, 7805, 7785, + 7764, 7743, 7723, 7702, + 7682, 7662, 7641, 7621, + 7601, 7581, 7561, 7541, + 7521, 7501, 7481, 7461, + 7441, 7421, 7402, 7382, + 7363, 7343, 7324, 7304, + 7285, 7265, 7246, 7227, + 7208, 7189, 7170, 7150, + 7132, 7113, 7094, 7075, + 7056, 7037, 7019, 7000, + 6981, 6963, 6944, 6926, + 6907, 6889, 6871, 6852, + 6834, 6816, 6798, 6780, + 6761, 6743, 6725, 6707, + 6690, 6672, 6654, 6636, + 6618, 6601, 6583, 6565, + 6548, 6530, 6513, 6495, + 6478, 6460, 6443, 6426, + 6409, 6391, 6374, 6357, + 6340, 6323, 6306, 6289, + 6272, 6255, 6238, 6221, + 6204, 6188, 6171, 6154, + 6138, 6121, 6104, 6088, + 6071, 6055, 6038, 6022, + 6006, 5989, 5973, 5957, + 5941, 5924, 5908, 5892, + 5876, 5860, 5844, 5828, + 5812, 5796, 5780, 5764, + 5749, 5733, 5717, 5701, + 5686, 5670, 5654, 5639, + 5623, 5608, 5592, 5577, + 5561, 5546, 5530, 5515, + 5500, 5485, 5469, 5454, + 5439, 5424, 5409, 5393, + 5378, 5363, 5348, 5333, + 5318, 5304, 5289, 5274, + 5259, 5244, 5229, 5215, + 5200, 5185, 5171, 5156, + 5141, 5127, 5112, 5098, + 5083, 5069, 5054, 5040, + 5026, 5011, 4997, 4983, + 4968, 4954, 4940, 4926, + 4911, 4897, 4883, 4869, + 4855, 4841, 4827, 4813, + 4799, 4785, 4771, 4757, + 4743, 4730, 4716, 4702, + 4688, 4675, 4661, 4647, + 4634, 4620, 4606, 4593, + 4579, 4566, 4552, 4539, + 4525, 4512, 4498, 4485, + 4472, 4458, 4445, 4432, + 4418, 4405, 4392, 4379, + 4365, 4352, 4339, 4326, + 4313, 4300, 4287, 4274, + 4261, 4248, 4235, 4222, + 4209, 4196, 4183, 4170, + 4158, 4145, 4132, 4119, + 4106, 4094, 4081, 4068, + 4056, 4043, 4030, 4018, + 4005, 3993, 3980, 3968, + 3955, 3943, 3930, 3918, + 3905, 3893, 3881, 3868, + 3856, 3844, 3831, 3819, + 3807, 3795, 3783, 3770, + 3758, 3746, 3734, 3722, + 3710, 3698, 3686, 3674, + 3662, 3650, 3638, 3626, + 3614, 3602, 3590, 3578, + 3566, 3554, 3542, 3531, + 3519, 3507, 3495, 3484, + 3472, 3460, 3448, 3437, + 3425, 3414, 3402, 3390, + 3379, 3367, 3356, 3344, + 3333, 3321, 3310, 3298, + 3287, 3275, 3264, 3253, + 3241, 3230, 3219, 3207, + 3196, 3185, 3173, 3162, + 3151, 3140, 3128, 3117, + 3106, 3095, 3084, 3073, + 3062, 3051, 3039, 3028, + 3017, 3006, 2995, 2984, + 2973, 2962, 2952, 2941, + 2930, 2919, 2908, 2897, + 2886, 2875, 2865, 2854, + 2843, 2832, 2821, 2811, + 2800, 2789, 2779, 2768, + 2757, 2747, 2736, 2725, + 2715, 2704, 2694, 2683, + 2672, 2662, 2651, 2641, + 2630, 2620, 2610, 2599, + 2589, 2578, 2568, 2557, + 2547, 2537, 2526, 2516, + 2506, 2495, 2485, 2475, + 2465, 2454, 2444, 2434, + 2424, 2414, 2403, 2393, + 2383, 2373, 2363, 2353, + 2343, 2332, 2322, 2312, + 2302, 2292, 2282, 2272, + 2262, 2252, 2242, 2232, + 2222, 2213, 2203, 2193, + 2183, 2173, 2163, 2153, + 2143, 2134, 2124, 2114, + 2104, 2094, 2085, 2075, + 2065, 2055, 2046, 2036, + 2026, 2017, 2007, 1997, + 1988, 1978, 1969, 1959, + 1949, 1940, 1930, 1921, + 1911, 1902, 1892, 1883, + 1873, 1864, 1854, 1845, + 1835, 1826, 1816, 1807, + 1798, 1788, 1779, 1769, + 1760, 1751, 1741, 1732, + 1723, 1714, 1704, 1695, + 1686, 1676, 1667, 1658, + 1649, 1640, 1630, 1621, + 1612, 1603, 1594, 1585, + 1575, 1566, 1557, 1548, + 1539, 1530, 1521, 1512, + 1503, 1494, 1485, 1476, + 1467, 1458, 1449, 1440, + 1431, 1422, 1413, 1404, + 1395, 1386, 1377, 1369, + 1360, 1351, 1342, 1333, + 1324, 1316, 1307, 1298, + 1289, 1280, 1272, 1263, + 1254, 1245, 1237, 1228, + 1219, 1211, 1202, 1193, + 1184, 1176, 1167, 1159, + 1150, 1141, 1133, 1124, + 1116, 1107, 1098, 1090, + 1081, 1073, 1064, 1056, + 1047, 1039, 1030, 1022, + 1013, 1005, 996, 988, + 979, 971, 963, 954, + 946, 937, 929, 921, + 912, 904, 896, 887, + 879, 871, 862, 854, + 846, 837, 829, 821, + 813, 804, 796, 788, + 780, 772, 763, 755, + 747, 739, 731, 722, + 714, 706, 698, 690, + 682, 674, 666, 658, + 649, 641, 633, 625, + 617, 609, 601, 593, + 585, 577, 569, 561, + 553, 545, 537, 529, + 521, 513, 506, 498, + 490, 482, 474, 466, + 458, 450, 442, 435, + 427, 419, 411, 403, + 395, 388, 380, 372, + 364, 357, 349, 341, + 333, 326, 318, 310, + 302, 295, 287, 279, + 272, 264, 256, 249, + 241, 233, 226, 218, + 210, 203, 195, 188, + 180, 172, 165, 157, + 150, 142, 135, 127, + 119, 112, 104, 97, + 89, 82, 74, 67, + 60, 52, 45, 37, + 30, 22, 15, 7, + 0, +}; +const uint16_t lut_exponential[] = { + 21, 22, 23, 24, + 24, 25, 26, 27, + 28, 29, 30, 31, + 32, 33, 34, 35, + 36, 37, 38, 39, + 41, 42, 43, 45, + 46, 48, 49, 51, + 52, 54, 56, 58, + 59, 61, 63, 65, + 68, 70, 72, 74, + 77, 79, 82, 84, + 87, 90, 93, 96, + 99, 102, 105, 108, + 112, 115, 119, 123, + 127, 131, 135, 139, + 144, 149, 153, 158, + 163, 168, 174, 179, + 185, 191, 197, 203, + 210, 217, 224, 231, + 238, 246, 254, 262, + 270, 279, 287, 297, + 306, 316, 326, 336, + 347, 358, 370, 381, + 394, 406, 419, 432, + 446, 461, 475, 490, + 506, 522, 539, 556, + 574, 592, 611, 630, + 651, 671, 693, 715, + 738, 761, 785, 810, + 836, 863, 890, 919, + 948, 978, 1010, 1042, + 1075, 1109, 1145, 1181, + 1219, 1258, 1298, 1339, + 1382, 1426, 1471, 1518, + 1567, 1617, 1668, 1721, + 1776, 1833, 1891, 1952, + 2014, 2078, 2144, 2212, + 2283, 2356, 2431, 2508, + 2588, 2671, 2756, 2844, + 2934, 3028, 3125, 3224, + 3327, 3433, 3542, 3655, + 3772, 3892, 4016, 4144, + 4276, 4412, 4553, 4698, + 4848, 5002, 5162, 5326, + 5496, 5671, 5852, 6039, + 6231, 6430, 6635, 6846, + 7064, 7290, 7522, 7762, + 8009, 8264, 8528, 8799, + 9080, 9369, 9668, 9976, + 10294, 10622, 10961, 11310, + 11670, 12042, 12426, 12822, + 13231, 13652, 14088, 14537, + 15000, 15478, 15971, 16480, + 17005, 17547, 18107, 18684, + 19279, 19894, 20528, 21182, + 21857, 22554, 23272, 24014, + 24779, 25569, 26384, 27225, + 28093, 28988, 29912, 30865, + 31849, 32864, 33911, 34992, + 36107, 37258, 38445, 39671, + 40935, 42240, 43586, 44975, + 46408, 47887, 49413, 50988, + 52613, 54290, 56020, 57806, + 59648, 61549, 63510, 65535, +}; + + +const uint16_t* lookup_table_table[] = { + lut_easing_in_quartic, + lut_easing_out_quartic, + lut_easing_in_out_sine, + lut_easing_in_out_bounce, + lut_response_balance, + lut_vca_linear, + lut_exponential, +}; +*/ + +// import numpy +// def phase_lut(octave): +// frequency = 110 * 2 ** (numpy.arange(0, 159.0) / 158.0 - octave) +// phase_increment = frequency / 16667 * (1 << 32) +// return(numpy.round(phase_increment).astype(int)) + +/* +// phase_lut(15) +const uint32_t lut_increments_vslow[] = { + 865, 869, 873, 877, 880, 884, 888, 892, 896, 900, 904, + 908, 912, 916, 920, 924, 928, 932, 936, 940, 944, 949, + 953, 957, 961, 965, 970, 974, 978, 982, 987, 991, 995, + 1000, 1004, 1009, 1013, 1018, 1022, 1026, 1031, 1036, 1040, 1045, + 1049, 1054, 1058, 1063, 1068, 1073, 1077, 1082, 1087, 1091, 1096, + 1101, 1106, 1111, 1116, 1121, 1126, 1130, 1135, 1140, 1145, 1151, + 1156, 1161, 1166, 1171, 1176, 1181, 1186, 1192, 1197, 1202, 1207, + 1213, 1218, 1223, 1229, 1234, 1240, 1245, 1251, 1256, 1262, 1267, + 1273, 1278, 1284, 1290, 1295, 1301, 1307, 1312, 1318, 1324, 1330, + 1336, 1341, 1347, 1353, 1359, 1365, 1371, 1377, 1383, 1389, 1395, + 1402, 1408, 1414, 1420, 1426, 1433, 1439, 1445, 1452, 1458, 1464, + 1471, 1477, 1484, 1490, 1497, 1504, 1510, 1517, 1523, 1530, 1537, + 1544, 1550, 1557, 1564, 1571, 1578, 1585, 1592, 1599, 1606, 1613, + 1620, 1627, 1634, 1641, 1649, 1656, 1663, 1670, 1678, 1685, 1693, + 1700, 1707, 1715, 1723, 1730 +}; + +// phase_lut(14) +const uint32_t lut_increments_slow[] = { + 1730, 1738, 1745, 1753, 1761, 1768, 1776, 1784, 1792, 1800, 1808, + 1816, 1824, 1832, 1840, 1848, 1856, 1864, 1872, 1881, 1889, 1897, + 1905, 1914, 1922, 1931, 1939, 1948, 1956, 1965, 1973, 1982, 1991, + 2000, 2008, 2017, 2026, 2035, 2044, 2053, 2062, 2071, 2080, 2089, + 2098, 2108, 2117, 2126, 2136, 2145, 2154, 2164, 2173, 2183, 2193, + 2202, 2212, 2222, 2231, 2241, 2251, 2261, 2271, 2281, 2291, 2301, + 2311, 2321, 2331, 2342, 2352, 2362, 2373, 2383, 2394, 2404, 2415, + 2425, 2436, 2447, 2458, 2468, 2479, 2490, 2501, 2512, 2523, 2534, + 2545, 2556, 2568, 2579, 2590, 2602, 2613, 2625, 2636, 2648, 2659, + 2671, 2683, 2695, 2707, 2718, 2730, 2742, 2754, 2767, 2779, 2791, + 2803, 2816, 2828, 2840, 2853, 2865, 2878, 2891, 2903, 2916, 2929, + 2942, 2955, 2968, 2981, 2994, 3007, 3020, 3034, 3047, 3060, 3074, + 3087, 3101, 3114, 3128, 3142, 3156, 3170, 3184, 3198, 3212, 3226, + 3240, 3254, 3268, 3283, 3297, 3312, 3326, 3341, 3356, 3370, 3385, + 3400, 3415, 3430, 3445, 3460 +}; +*/ + +// phase_lut(13) +const uint32_t lut_increments_med[] = { + 3460, 3475, 3491, 3506, 3521, 3537, 3553, 3568, 3584, 3600, 3615, + 3631, 3647, 3663, 3679, 3696, 3712, 3728, 3745, 3761, 3778, 3794, + 3811, 3828, 3844, 3861, 3878, 3895, 3912, 3930, 3947, 3964, 3982, + 3999, 4017, 4034, 4052, 4070, 4088, 4106, 4124, 4142, 4160, 4179, + 4197, 4215, 4234, 4253, 4271, 4290, 4309, 4328, 4347, 4366, 4385, + 4404, 4424, 4443, 4463, 4482, 4502, 4522, 4542, 4562, 4582, 4602, + 4622, 4643, 4663, 4683, 4704, 4725, 4746, 4766, 4787, 4808, 4830, + 4851, 4872, 4894, 4915, 4937, 4958, 4980, 5002, 5024, 5046, 5068, + 5091, 5113, 5135, 5158, 5181, 5203, 5226, 5249, 5272, 5296, 5319, + 5342, 5366, 5389, 5413, 5437, 5461, 5485, 5509, 5533, 5557, 5582, + 5606, 5631, 5656, 5681, 5706, 5731, 5756, 5781, 5807, 5832, 5858, + 5884, 5909, 5935, 5962, 5988, 6014, 6040, 6067, 6094, 6121, 6147, + 6174, 6202, 6229, 6256, 6284, 6311, 6339, 6367, 6395, 6423, 6451, + 6480, 6508, 6537, 6566, 6594, 6623, 6653, 6682, 6711, 6741, 6770, + 6800, 6830, 6860, 6890, 6920 +}; + +/* +// phase_lut(12) +const uint32_t lut_increments_fast[] = { + 6920, 6951, 6981, 7012, 7043, 7074, 7105, 7136, 7168, + 7199, 7231, 7263, 7295, 7327, 7359, 7391, 7424, 7456, + 7489, 7522, 7555, 7588, 7622, 7655, 7689, 7723, 7757, + 7791, 7825, 7859, 7894, 7929, 7963, 7998, 8034, 8069, + 8104, 8140, 8176, 8212, 8248, 8284, 8321, 8357, 8394, + 8431, 8468, 8505, 8543, 8580, 8618, 8656, 8694, 8732, + 8770, 8809, 8848, 8887, 8926, 8965, 9004, 9044, 9084, + 9124, 9164, 9204, 9244, 9285, 9326, 9367, 9408, 9449, + 9491, 9533, 9575, 9617, 9659, 9702, 9744, 9787, 9830, + 9873, 9917, 9960, 10004, 10048, 10092, 10137, 10181, 10226, + 10271, 10316, 10361, 10407, 10453, 10499, 10545, 10591, 10638, + 10685, 10732, 10779, 10826, 10874, 10921, 10969, 11018, 11066, + 11115, 11164, 11213, 11262, 11312, 11361, 11411, 11461, 11512, + 11562, 11613, 11664, 11716, 11767, 11819, 11871, 11923, 11975, + 12028, 12081, 12134, 12187, 12241, 12295, 12349, 12403, 12458, + 12513, 12568, 12623, 12678, 12734, 12790, 12846, 12903, 12959, + 13016, 13074, 13131, 13189, 13247, 13305, 13364, 13422, 13481, + 13541, 13600, 13660, 13720, 13780, 13841 +}; + +// phase_lut(11) +const uint32_t lut_increments_vfast[] = { + 13841, 13902, 13963, 14024, 14086, 14148, 14210, 14273, 14335, + 14398, 14462, 14525, 14589, 14653, 14718, 14782, 14847, 14913, + 14978, 15044, 15110, 15177, 15243, 15310, 15378, 15445, 15513, + 15581, 15650, 15719, 15788, 15857, 15927, 15997, 16067, 16138, + 16209, 16280, 16352, 16424, 16496, 16568, 16641, 16714, 16788, + 16862, 16936, 17010, 17085, 17160, 17236, 17311, 17388, 17464, + 17541, 17618, 17695, 17773, 17851, 17930, 18009, 18088, 18167, + 18247, 18327, 18408, 18489, 18570, 18652, 18734, 18816, 18899, + 18982, 19066, 19149, 19234, 19318, 19403, 19488, 19574, 19660, + 19747, 19833, 19921, 20008, 20096, 20184, 20273, 20362, 20452, + 20542, 20632, 20723, 20814, 20905, 20997, 21090, 21182, 21276, + 21369, 21463, 21557, 21652, 21747, 21843, 21939, 22035, 22132, + 22230, 22327, 22426, 22524, 22623, 22723, 22823, 22923, 23024, + 23125, 23227, 23329, 23431, 23534, 23638, 23742, 23846, 23951, + 24056, 24162, 24268, 24375, 24482, 24590, 24698, 24806, 24915, + 25025, 25135, 25246, 25357, 25468, 25580, 25692, 25805, 25919, + 26033, 26147, 26262, 26378, 26494, 26610, 26727, 26845, 26963, + 27081, 27200, 27320, 27440, 27561, 27682 +}; +*/ + +const uint32_t* lookup_table_hr_table[] = { + // lut_increments_vslow, + // lut_increments_slow, + lut_increments_med, + // lut_increments_fast, + // lut_increments_vfast, +}; + +const uint8_t wt_lfo_waveforms[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 3, 3, 3, + 3, 3, 3, 3, + 4, 4, 4, 4, + 4, 5, 5, 5, + 5, 5, 6, 6, + 6, 6, 7, 7, + 7, 7, 8, 8, + 8, 9, 9, 9, + 9, 10, 10, 11, + 11, 11, 12, 12, + 13, 13, 13, 14, + 14, 15, 15, 16, + 16, 17, 17, 18, + 18, 19, 19, 20, + 21, 21, 22, 23, + 23, 24, 25, 25, + 26, 27, 27, 28, + 29, 30, 31, 31, + 32, 33, 34, 35, + 36, 37, 38, 39, + 40, 41, 42, 43, + 44, 45, 46, 47, + 48, 49, 50, 52, + 53, 54, 55, 57, + 58, 59, 61, 62, + 63, 65, 66, 68, + 69, 71, 72, 74, + 75, 77, 79, 80, + 82, 84, 85, 87, + 89, 91, 93, 95, + 96, 98, 100, 102, + 104, 107, 109, 111, + 113, 115, 117, 120, + 122, 124, 126, 129, + 131, 134, 136, 139, + 141, 144, 146, 149, + 152, 155, 157, 160, + 163, 166, 169, 172, + 175, 178, 181, 184, + 187, 190, 194, 197, + 200, 203, 207, 210, + 214, 217, 221, 224, + 228, 232, 236, 239, + 243, 247, 251, 255, + 0, 0, 1, 2, + 3, 4, 5, 6, + 7, 8, 9, 10, + 11, 12, 13, 14, + 15, 16, 17, 18, + 19, 20, 21, 22, + 23, 24, 25, 26, + 27, 28, 29, 30, + 31, 32, 33, 34, + 35, 36, 37, 38, + 39, 40, 41, 42, + 43, 44, 45, 46, + 47, 48, 49, 50, + 51, 52, 53, 54, + 55, 56, 57, 58, + 59, 60, 61, 62, + 63, 64, 65, 66, + 67, 68, 69, 70, + 71, 72, 73, 74, + 75, 76, 77, 78, + 79, 80, 81, 82, + 83, 84, 85, 86, + 87, 88, 89, 90, + 91, 92, 93, 94, + 95, 96, 97, 98, + 99, 100, 101, 102, + 103, 104, 105, 106, + 107, 108, 109, 110, + 111, 112, 113, 114, + 115, 116, 117, 118, + 119, 120, 121, 122, + 123, 124, 125, 126, + 127, 128, 129, 130, + 131, 132, 133, 134, + 135, 136, 137, 138, + 139, 140, 141, 142, + 143, 144, 145, 146, + 147, 148, 149, 150, + 151, 152, 153, 154, + 155, 156, 157, 158, + 159, 160, 161, 162, + 163, 164, 165, 166, + 167, 168, 169, 170, + 171, 172, 173, 174, + 175, 176, 177, 178, + 179, 180, 181, 182, + 183, 184, 185, 186, + 187, 188, 189, 190, + 191, 192, 193, 194, + 195, 196, 197, 198, + 199, 200, 201, 202, + 203, 204, 205, 206, + 207, 208, 209, 210, + 211, 212, 213, 214, + 215, 216, 217, 218, + 219, 220, 221, 222, + 223, 224, 225, 226, + 227, 228, 229, 230, + 231, 232, 233, 234, + 235, 236, 237, 238, + 239, 240, 241, 242, + 243, 244, 245, 246, + 247, 248, 249, 250, + 251, 252, 253, 254, + 255, 0, 0, 2, + 4, 6, 8, 10, + 12, 14, 16, 18, + 20, 22, 24, 26, + 28, 30, 32, 34, + 36, 38, 40, 42, + 44, 46, 48, 50, + 52, 54, 56, 58, + 60, 62, 64, 66, + 68, 70, 72, 74, + 76, 78, 80, 82, + 84, 86, 88, 90, + 92, 94, 96, 98, + 100, 102, 104, 106, + 108, 110, 112, 114, + 116, 118, 120, 122, + 124, 126, 128, 129, + 131, 133, 135, 137, + 139, 141, 143, 145, + 147, 149, 151, 153, + 155, 157, 159, 161, + 163, 165, 167, 169, + 171, 173, 175, 177, + 179, 181, 183, 185, + 187, 189, 191, 193, + 195, 197, 199, 201, + 203, 205, 207, 209, + 211, 213, 215, 217, + 219, 221, 223, 225, + 227, 229, 231, 233, + 235, 237, 239, 241, + 243, 245, 247, 249, + 251, 253, 255, 253, + 251, 249, 247, 245, + 243, 241, 239, 237, + 235, 233, 231, 229, + 227, 225, 223, 221, + 219, 217, 215, 213, + 211, 209, 207, 205, + 203, 201, 199, 197, + 195, 193, 191, 189, + 187, 185, 183, 181, + 179, 177, 175, 173, + 171, 169, 167, 165, + 163, 161, 159, 157, + 155, 153, 151, 149, + 147, 145, 143, 141, + 139, 137, 135, 133, + 131, 129, 128, 126, + 124, 122, 120, 118, + 116, 114, 112, 110, + 108, 106, 104, 102, + 100, 98, 96, 94, + 92, 90, 88, 86, + 84, 82, 80, 78, + 76, 74, 72, 70, + 68, 66, 64, 62, + 60, 58, 56, 54, + 52, 50, 48, 46, + 44, 42, 40, 38, + 36, 34, 32, 30, + 28, 26, 24, 22, + 20, 18, 16, 14, + 12, 10, 8, 6, + 4, 2, 0, 255, + 254, 253, 252, 251, + 250, 249, 248, 247, + 246, 245, 244, 243, + 242, 241, 240, 239, + 238, 237, 236, 235, + 234, 233, 232, 231, + 230, 229, 228, 227, + 226, 225, 224, 223, + 222, 221, 220, 219, + 218, 217, 216, 215, + 214, 213, 212, 211, + 210, 209, 208, 207, + 206, 205, 204, 203, + 202, 201, 200, 199, + 198, 197, 196, 195, + 194, 193, 192, 191, + 190, 189, 188, 187, + 186, 185, 184, 183, + 182, 181, 180, 179, + 178, 177, 176, 175, + 174, 173, 172, 171, + 170, 169, 168, 167, + 166, 165, 164, 163, + 162, 161, 160, 159, + 158, 157, 156, 155, + 154, 153, 152, 151, + 150, 149, 148, 147, + 146, 145, 144, 143, + 142, 141, 140, 139, + 138, 137, 136, 135, + 134, 133, 132, 131, + 130, 129, 128, 127, + 126, 125, 124, 123, + 122, 121, 120, 119, + 118, 117, 116, 115, + 114, 113, 112, 111, + 110, 109, 108, 107, + 106, 105, 104, 103, + 102, 101, 100, 99, + 98, 97, 96, 95, + 94, 93, 92, 91, + 90, 89, 88, 87, + 86, 85, 84, 83, + 82, 81, 80, 79, + 78, 77, 76, 75, + 74, 73, 72, 71, + 70, 69, 68, 67, + 66, 65, 64, 63, + 62, 61, 60, 59, + 58, 57, 56, 55, + 54, 53, 52, 51, + 50, 49, 48, 47, + 46, 45, 44, 43, + 42, 41, 40, 39, + 38, 37, 36, 35, + 34, 33, 32, 31, + 30, 29, 28, 27, + 26, 25, 24, 23, + 22, 21, 20, 19, + 18, 17, 16, 15, + 14, 13, 12, 11, + 10, 9, 8, 7, + 6, 5, 4, 3, + 2, 1, 0, 255, + 255, 251, 247, 243, + 239, 236, 232, 228, + 224, 221, 217, 214, + 210, 207, 203, 200, + 197, 194, 190, 187, + 184, 181, 178, 175, + 172, 169, 166, 163, + 160, 157, 155, 152, + 149, 146, 144, 141, + 139, 136, 134, 131, + 129, 126, 124, 122, + 120, 117, 115, 113, + 111, 109, 107, 104, + 102, 100, 98, 96, + 95, 93, 91, 89, + 87, 85, 84, 82, + 80, 79, 77, 75, + 74, 72, 71, 69, + 68, 66, 65, 63, + 62, 61, 59, 58, + 57, 55, 54, 53, + 52, 50, 49, 48, + 47, 46, 45, 44, + 43, 42, 41, 40, + 39, 38, 37, 36, + 35, 34, 33, 32, + 31, 31, 30, 29, + 28, 27, 27, 26, + 25, 25, 24, 23, + 23, 22, 21, 21, + 20, 19, 19, 18, + 18, 17, 17, 16, + 16, 15, 15, 14, + 14, 13, 13, 13, + 12, 12, 11, 11, + 11, 10, 10, 9, + 9, 9, 9, 8, + 8, 8, 7, 7, + 7, 7, 6, 6, + 6, 6, 5, 5, + 5, 5, 5, 4, + 4, 4, 4, 4, + 3, 3, 3, 3, + 3, 3, 3, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 255, 0, 0, 0, + 0, 0, 0, 1, + 1, 2, 3, 4, + 5, 6, 8, 10, + 12, 14, 17, 20, + 23, 27, 31, 35, + 39, 44, 49, 54, + 59, 65, 71, 77, + 84, 90, 97, 104, + 111, 118, 125, 132, + 139, 147, 154, 161, + 168, 175, 182, 188, + 195, 201, 207, 213, + 218, 223, 228, 233, + 237, 241, 244, 247, + 249, 251, 253, 254, + 255, 255, 255, 254, + 253, 251, 249, 247, + 244, 241, 237, 233, + 228, 223, 218, 213, + 207, 201, 195, 188, + 182, 175, 168, 161, + 154, 147, 139, 132, + 125, 118, 111, 104, + 97, 90, 84, 77, + 71, 65, 59, 54, + 49, 44, 39, 35, + 31, 27, 23, 20, + 17, 14, 12, 10, + 8, 6, 5, 4, + 3, 2, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 255, 255, + 255, 255, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 255, 128, + 128, 128, 128, 128, + 128, 128, 128, 128, + 129, 129, 130, 131, + 131, 132, 133, 135, + 136, 137, 139, 141, + 143, 145, 147, 149, + 152, 154, 157, 160, + 163, 166, 169, 173, + 176, 179, 183, 186, + 190, 194, 197, 201, + 204, 208, 211, 215, + 218, 222, 225, 228, + 231, 234, 237, 239, + 242, 244, 246, 248, + 249, 251, 252, 253, + 254, 255, 255, 255, + 255, 255, 254, 253, + 252, 251, 249, 248, + 246, 244, 242, 239, + 237, 234, 231, 228, + 225, 222, 218, 215, + 211, 208, 204, 201, + 197, 194, 190, 186, + 183, 179, 176, 173, + 169, 166, 163, 160, + 157, 154, 152, 149, + 147, 145, 143, 141, + 139, 137, 136, 135, + 133, 132, 131, 131, + 130, 129, 129, 128, + 128, 128, 128, 128, + 128, 128, 128, 128, + 127, 127, 127, 127, + 127, 127, 127, 127, + 126, 126, 125, 124, + 124, 123, 122, 120, + 119, 118, 116, 114, + 112, 110, 108, 106, + 103, 101, 98, 95, + 92, 89, 86, 82, + 79, 76, 72, 69, + 65, 61, 58, 54, + 51, 47, 44, 40, + 37, 33, 30, 27, + 24, 21, 18, 16, + 13, 11, 9, 7, + 6, 4, 3, 2, + 1, 0, 0, 0, + 0, 0, 1, 2, + 3, 4, 6, 7, + 9, 11, 13, 16, + 18, 21, 24, 27, + 30, 33, 37, 40, + 44, 47, 51, 54, + 58, 61, 65, 69, + 72, 76, 79, 82, + 86, 89, 92, 95, + 98, 101, 103, 106, + 108, 110, 112, 114, + 116, 118, 119, 120, + 122, 123, 124, 124, + 125, 126, 126, 127, + 127, 127, 127, 127, + 127, 127, 127, 128, + 128, 131, 134, 137, + 140, 143, 146, 149, + 152, 155, 158, 162, + 165, 167, 170, 173, + 176, 179, 182, 185, + 188, 190, 193, 196, + 198, 201, 203, 206, + 208, 211, 213, 215, + 218, 220, 222, 224, + 226, 228, 230, 232, + 234, 235, 237, 238, + 240, 241, 243, 244, + 245, 246, 248, 249, + 250, 250, 251, 252, + 253, 253, 254, 254, + 254, 255, 255, 255, + 255, 255, 255, 255, + 254, 254, 254, 253, + 253, 252, 251, 250, + 250, 249, 248, 246, + 245, 244, 243, 241, + 240, 238, 237, 235, + 234, 232, 230, 228, + 226, 224, 222, 220, + 218, 215, 213, 211, + 208, 206, 203, 201, + 198, 196, 193, 190, + 188, 185, 182, 179, + 176, 173, 170, 167, + 165, 162, 158, 155, + 152, 149, 146, 143, + 140, 137, 134, 131, + 128, 124, 121, 118, + 115, 112, 109, 106, + 103, 100, 97, 93, + 90, 88, 85, 82, + 79, 76, 73, 70, + 67, 65, 62, 59, + 57, 54, 52, 49, + 47, 44, 42, 40, + 37, 35, 33, 31, + 29, 27, 25, 23, + 21, 20, 18, 17, + 15, 14, 12, 11, + 10, 9, 7, 6, + 5, 5, 4, 3, + 2, 2, 1, 1, + 1, 0, 0, 0, + 0, 0, 0, 0, + 1, 1, 1, 2, + 2, 3, 4, 5, + 5, 6, 7, 9, + 10, 11, 12, 14, + 15, 17, 18, 20, + 21, 23, 25, 27, + 29, 31, 33, 35, + 37, 40, 42, 44, + 47, 49, 52, 54, + 57, 59, 62, 65, + 67, 70, 73, 76, + 79, 82, 85, 88, + 90, 93, 97, 100, + 103, 106, 109, 112, + 115, 118, 121, 124, + 128, 128, 133, 138, + 143, 148, 153, 158, + 163, 167, 172, 177, + 182, 186, 191, 195, + 199, 203, 207, 211, + 215, 218, 222, 225, + 228, 231, 234, 237, + 239, 241, 244, 246, + 247, 249, 250, 251, + 253, 253, 254, 255, + 255, 255, 255, 255, + 254, 254, 253, 252, + 251, 250, 249, 247, + 245, 244, 242, 240, + 237, 235, 233, 230, + 228, 225, 222, 220, + 217, 214, 211, 208, + 205, 202, 198, 195, + 192, 189, 186, 183, + 180, 176, 173, 170, + 167, 164, 162, 159, + 156, 153, 151, 148, + 146, 143, 141, 139, + 137, 135, 133, 131, + 130, 128, 127, 125, + 124, 123, 122, 121, + 120, 120, 119, 119, + 118, 118, 118, 118, + 118, 118, 118, 118, + 119, 119, 119, 120, + 121, 121, 122, 123, + 123, 124, 125, 126, + 127, 128, 128, 129, + 130, 131, 132, 132, + 133, 134, 134, 135, + 136, 136, 136, 137, + 137, 137, 137, 137, + 137, 137, 137, 136, + 136, 135, 135, 134, + 133, 132, 131, 130, + 128, 127, 125, 124, + 122, 120, 118, 116, + 114, 112, 109, 107, + 104, 102, 99, 96, + 93, 91, 88, 85, + 82, 79, 75, 72, + 69, 66, 63, 60, + 57, 53, 50, 47, + 44, 41, 38, 35, + 33, 30, 27, 25, + 22, 20, 18, 15, + 13, 11, 10, 8, + 6, 5, 4, 3, + 2, 1, 1, 0, + 0, 0, 0, 0, + 1, 2, 2, 4, + 5, 6, 8, 9, + 11, 14, 16, 18, + 21, 24, 27, 30, + 33, 37, 40, 44, + 48, 52, 56, 60, + 64, 69, 73, 78, + 83, 88, 92, 97, + 102, 107, 112, 117, + 122, 128, 128, 135, + 143, 151, 158, 166, + 173, 180, 187, 194, + 200, 206, 212, 217, + 223, 228, 232, 236, + 240, 243, 246, 249, + 251, 252, 254, 255, + 255, 255, 255, 254, + 253, 251, 250, 248, + 245, 242, 240, 236, + 233, 230, 226, 222, + 218, 214, 210, 206, + 202, 198, 194, 190, + 187, 183, 180, 176, + 173, 171, 168, 166, + 164, 162, 161, 159, + 159, 158, 158, 158, + 159, 159, 161, 162, + 164, 166, 168, 171, + 173, 176, 180, 183, + 187, 190, 194, 198, + 202, 206, 210, 214, + 218, 222, 226, 230, + 233, 236, 240, 242, + 245, 248, 250, 251, + 253, 254, 255, 255, + 255, 255, 254, 252, + 251, 249, 246, 243, + 240, 236, 232, 228, + 223, 217, 212, 206, + 200, 194, 187, 180, + 173, 166, 158, 151, + 143, 135, 128, 120, + 112, 104, 97, 89, + 82, 75, 68, 61, + 55, 49, 43, 38, + 32, 27, 23, 19, + 15, 12, 9, 6, + 4, 3, 1, 0, + 0, 0, 0, 1, + 2, 4, 5, 7, + 10, 13, 15, 19, + 22, 25, 29, 33, + 37, 41, 45, 49, + 53, 57, 61, 65, + 68, 72, 75, 79, + 82, 84, 87, 89, + 91, 93, 94, 96, + 96, 97, 97, 97, + 96, 96, 94, 93, + 91, 89, 87, 84, + 82, 79, 75, 72, + 68, 65, 61, 57, + 53, 49, 45, 41, + 37, 33, 29, 25, + 22, 19, 15, 13, + 10, 7, 5, 4, + 2, 1, 0, 0, + 0, 0, 1, 3, + 4, 6, 9, 12, + 15, 19, 23, 27, + 32, 38, 43, 49, + 55, 61, 68, 75, + 82, 89, 97, 104, + 112, 120, 128, 128, + 136, 144, 152, 160, + 167, 174, 180, 186, + 191, 195, 199, 202, + 204, 205, 205, 205, + 204, 202, 199, 196, + 193, 189, 184, 179, + 175, 170, 165, 160, + 155, 151, 147, 143, + 140, 138, 136, 135, + 135, 135, 136, 138, + 141, 144, 148, 153, + 158, 164, 170, 177, + 183, 190, 197, 204, + 211, 218, 224, 230, + 236, 241, 245, 248, + 251, 253, 255, 255, + 255, 253, 251, 248, + 245, 241, 236, 230, + 224, 218, 211, 204, + 197, 190, 183, 177, + 170, 164, 158, 153, + 148, 144, 141, 138, + 136, 135, 135, 135, + 136, 138, 140, 143, + 147, 151, 155, 160, + 165, 170, 175, 179, + 184, 189, 193, 196, + 199, 202, 204, 205, + 205, 205, 204, 202, + 199, 195, 191, 186, + 180, 174, 167, 160, + 152, 144, 136, 128, + 119, 111, 103, 95, + 88, 81, 75, 69, + 64, 60, 56, 53, + 51, 50, 50, 50, + 51, 53, 56, 59, + 62, 66, 71, 76, + 80, 85, 90, 95, + 100, 104, 108, 112, + 115, 117, 119, 120, + 120, 120, 119, 117, + 114, 111, 107, 102, + 97, 91, 85, 78, + 72, 65, 58, 51, + 44, 37, 31, 25, + 19, 14, 10, 7, + 4, 2, 0, 0, + 0, 2, 4, 7, + 10, 14, 19, 25, + 31, 37, 44, 51, + 58, 65, 72, 78, + 85, 91, 97, 102, + 107, 111, 114, 117, + 119, 120, 120, 120, + 119, 117, 115, 112, + 108, 104, 100, 95, + 90, 85, 80, 76, + 71, 66, 62, 59, + 56, 53, 51, 50, + 50, 50, 51, 53, + 56, 60, 64, 69, + 75, 81, 88, 95, + 103, 111, 119, 128, + 128, 132, 137, 142, + 146, 151, 156, 161, + 165, 170, 175, 179, + 184, 189, 194, 198, + 203, 208, 212, 217, + 222, 227, 231, 236, + 241, 246, 250, 255, + 250, 246, 241, 236, + 231, 227, 222, 217, + 212, 208, 203, 198, + 194, 189, 184, 179, + 175, 170, 165, 161, + 156, 151, 146, 142, + 137, 132, 128, 123, + 118, 113, 109, 104, + 99, 94, 90, 85, + 80, 85, 90, 94, + 99, 104, 109, 113, + 118, 123, 128, 132, + 137, 142, 146, 151, + 156, 161, 165, 170, + 175, 179, 184, 189, + 194, 198, 203, 208, + 212, 217, 222, 227, + 231, 236, 241, 246, + 250, 255, 250, 246, + 241, 236, 231, 227, + 222, 217, 212, 208, + 203, 198, 194, 189, + 184, 179, 175, 170, + 165, 161, 156, 151, + 146, 142, 137, 132, + 128, 123, 118, 113, + 109, 104, 99, 94, + 90, 85, 80, 76, + 71, 66, 61, 57, + 52, 47, 42, 38, + 33, 28, 24, 19, + 14, 9, 5, 0, + 5, 9, 14, 19, + 24, 28, 33, 38, + 42, 47, 52, 57, + 61, 66, 71, 76, + 80, 85, 90, 94, + 99, 104, 109, 113, + 118, 123, 128, 132, + 137, 142, 146, 151, + 156, 161, 165, 170, + 175, 170, 165, 161, + 156, 151, 146, 142, + 137, 132, 128, 123, + 118, 113, 109, 104, + 99, 94, 90, 85, + 80, 76, 71, 66, + 61, 57, 52, 47, + 42, 38, 33, 28, + 24, 19, 14, 9, + 5, 0, 5, 9, + 14, 19, 24, 28, + 33, 38, 42, 47, + 52, 57, 61, 66, + 71, 76, 80, 85, + 90, 94, 99, 104, + 109, 113, 118, 123, + 128, 128, 124, 120, + 116, 112, 108, 104, + 100, 96, 92, 88, + 84, 80, 76, 72, + 68, 64, 60, 56, + 52, 48, 44, 40, + 36, 32, 28, 24, + 20, 16, 12, 8, + 4, 0, 4, 8, + 12, 16, 20, 24, + 28, 32, 36, 40, + 44, 48, 52, 56, + 60, 64, 68, 72, + 76, 80, 84, 88, + 92, 96, 100, 104, + 108, 112, 116, 120, + 124, 128, 131, 135, + 139, 143, 147, 151, + 155, 159, 163, 167, + 171, 175, 179, 183, + 187, 191, 195, 199, + 203, 207, 211, 215, + 219, 223, 227, 231, + 235, 239, 243, 247, + 251, 255, 251, 247, + 243, 239, 235, 231, + 227, 223, 219, 215, + 211, 207, 203, 199, + 195, 191, 187, 183, + 179, 175, 171, 167, + 163, 159, 155, 151, + 147, 143, 139, 135, + 131, 128, 131, 135, + 139, 143, 147, 151, + 155, 159, 163, 167, + 171, 175, 179, 183, + 187, 191, 195, 199, + 203, 207, 211, 215, + 219, 223, 227, 231, + 235, 239, 243, 247, + 251, 255, 251, 247, + 243, 239, 235, 231, + 227, 223, 219, 215, + 211, 207, 203, 199, + 195, 191, 187, 183, + 179, 175, 171, 167, + 163, 159, 155, 151, + 147, 143, 139, 135, + 131, 128, 124, 120, + 116, 112, 108, 104, + 100, 96, 92, 88, + 84, 80, 76, 72, + 68, 64, 60, 56, + 52, 48, 44, 40, + 36, 32, 28, 24, + 20, 16, 12, 8, + 4, 0, 4, 8, + 12, 16, 20, 24, + 28, 32, 36, 40, + 44, 48, 52, 56, + 60, 64, 68, 72, + 76, 80, 84, 88, + 92, 96, 100, 104, + 108, 112, 116, 120, + 124, 128, 63, 60, + 57, 54, 51, 48, + 45, 42, 39, 36, + 33, 30, 27, 24, + 21, 18, 15, 12, + 9, 6, 3, 0, + 1, 4, 7, 10, + 13, 16, 19, 22, + 25, 28, 31, 34, + 37, 40, 43, 46, + 49, 52, 55, 58, + 61, 64, 67, 70, + 73, 76, 79, 82, + 85, 88, 91, 94, + 97, 100, 103, 106, + 109, 112, 115, 118, + 121, 124, 128, 131, + 134, 137, 140, 143, + 146, 149, 152, 155, + 158, 161, 164, 167, + 170, 173, 176, 179, + 182, 185, 188, 191, + 194, 197, 200, 203, + 206, 209, 212, 215, + 218, 221, 224, 227, + 230, 233, 236, 239, + 242, 245, 248, 251, + 254, 255, 252, 249, + 246, 243, 240, 237, + 234, 231, 228, 225, + 222, 219, 216, 213, + 210, 207, 204, 201, + 198, 195, 192, 195, + 198, 201, 204, 207, + 210, 213, 216, 219, + 222, 225, 228, 231, + 234, 237, 240, 243, + 246, 249, 252, 255, + 254, 251, 248, 245, + 242, 239, 236, 233, + 230, 227, 224, 221, + 218, 215, 212, 209, + 206, 203, 200, 197, + 194, 191, 188, 185, + 182, 179, 176, 173, + 170, 167, 164, 161, + 158, 155, 152, 149, + 146, 143, 140, 137, + 134, 131, 128, 124, + 121, 118, 115, 112, + 109, 106, 103, 100, + 97, 94, 91, 88, + 85, 82, 79, 76, + 73, 70, 67, 64, + 61, 58, 55, 52, + 49, 46, 43, 40, + 37, 34, 31, 28, + 25, 22, 19, 16, + 13, 10, 7, 4, + 1, 0, 3, 6, + 9, 12, 15, 18, + 21, 24, 27, 30, + 33, 36, 39, 42, + 45, 48, 51, 54, + 57, 60, 63, 0, + 0, 1, 1, 2, + 2, 2, 3, 3, + 4, 4, 5, 5, + 6, 6, 7, 7, + 8, 8, 9, 9, + 10, 10, 11, 12, + 12, 13, 14, 14, + 15, 16, 16, 17, + 18, 19, 19, 20, + 21, 22, 23, 23, + 24, 25, 26, 27, + 28, 29, 30, 31, + 32, 33, 34, 35, + 37, 38, 39, 40, + 41, 43, 44, 45, + 47, 48, 50, 51, + 52, 54, 56, 57, + 59, 60, 62, 64, + 66, 67, 69, 71, + 73, 75, 77, 79, + 81, 83, 86, 88, + 90, 93, 95, 97, + 100, 102, 105, 108, + 110, 113, 116, 119, + 122, 125, 128, 131, + 135, 138, 141, 145, + 148, 152, 156, 159, + 163, 167, 171, 175, + 180, 184, 188, 193, + 197, 202, 207, 212, + 217, 222, 227, 232, + 238, 243, 249, 255, + 249, 243, 238, 232, + 227, 222, 217, 212, + 207, 202, 197, 193, + 188, 184, 180, 175, + 171, 167, 163, 159, + 156, 152, 148, 145, + 141, 138, 135, 131, + 128, 125, 122, 119, + 116, 113, 110, 108, + 105, 102, 100, 97, + 95, 93, 90, 88, + 86, 83, 81, 79, + 77, 75, 73, 71, + 69, 67, 66, 64, + 62, 60, 59, 57, + 56, 54, 52, 51, + 50, 48, 47, 45, + 44, 43, 41, 40, + 39, 38, 37, 35, + 34, 33, 32, 31, + 30, 29, 28, 27, + 26, 25, 24, 23, + 23, 22, 21, 20, + 19, 19, 18, 17, + 16, 16, 15, 14, + 14, 13, 12, 12, + 11, 10, 10, 9, + 9, 8, 8, 7, + 7, 6, 6, 5, + 5, 4, 4, 3, + 3, 2, 2, 2, + 1, 1, 0, 0, + 205, 134, 30, 163, + 23, 84, 109, 141, + 160, 178, 202, 33, + 87, 51, 180, 8, + 232, 103, 194, 121, + 73, 192, 24, 105, + 72, 99, 222, 20, + 142, 140, 84, 248, + 73, 130, 37, 50, + 215, 1, 200, 213, + 239, 248, 212, 16, + 103, 95, 129, 250, + 209, 48, 178, 174, + 255, 123, 186, 203, + 66, 41, 178, 230, + 234, 79, 244, 185, + 5, 185, 148, 244, + 189, 50, 242, 219, + 114, 210, 255, 144, + 150, 108, 229, 113, + 147, 168, 77, 5, + 214, 81, 96, 46, + 212, 47, 247, 178, + 154, 126, 181, 66, + 166, 159, 213, 91, + 244, 16, 221, 141, + 227, 191, 53, 124, + 151, 222, 204, 244, + 44, 27, 160, 95, + 166, 175, 121, 170, + 243, 46, 179, 114, + 134, 23, 171, 233, + 84, 247, 44, 14, + 15, 199, 144, 34, + 123, 168, 238, 138, + 72, 55, 189, 127, + 42, 245, 91, 102, + 175, 238, 98, 89, + 95, 43, 45, 164, + 249, 18, 33, 209, + 199, 250, 53, 239, + 31, 178, 233, 133, + 102, 197, 210, 243, + 17, 49, 200, 172, + 69, 0, 151, 209, + 117, 168, 132, 244, + 210, 102, 228, 68, + 138, 64, 135, 124, + 250, 131, 77, 56, + 142, 98, 218, 15, + 22, 128, 93, 190, + 26, 245, 174, 10, + 21, 162, 198, 73, + 76, 69, 71, 19, + 138, 1, 189, 215, + 27, 68, 41, 143, + 244, 48, 239, 131, + 66, 198, 87, 82, + 212, 57, 37, 61, + 46, 68, 89, 209, + 80, 214, 9, 148, + 151, 151, 166, 228, + 51, 10, 86, 144, + 186, 209, 212, 28, + 205, 128, 131, 134, + 137, 140, 143, 146, + 149, 152, 155, 158, + 162, 165, 167, 170, + 173, 176, 179, 182, + 185, 188, 190, 193, + 196, 198, 201, 203, + 206, 208, 211, 213, + 215, 218, 220, 222, + 224, 226, 228, 230, + 232, 234, 235, 237, + 238, 240, 241, 243, + 244, 245, 246, 248, + 249, 250, 250, 251, + 252, 253, 253, 254, + 254, 254, 255, 255, + 255, 255, 255, 255, + 255, 254, 254, 254, + 253, 253, 252, 251, + 250, 250, 249, 248, + 246, 245, 244, 243, + 241, 240, 238, 237, + 235, 234, 232, 230, + 228, 226, 224, 222, + 220, 218, 215, 213, + 211, 208, 206, 203, + 201, 198, 196, 193, + 190, 188, 185, 182, + 179, 176, 173, 170, + 167, 165, 162, 158, + 155, 152, 149, 146, + 143, 140, 137, 134, + 131, 128, 124, 121, + 118, 115, 112, 109, + 106, 103, 100, 97, + 93, 90, 88, 85, + 82, 79, 76, 73, + 70, 67, 65, 62, + 59, 57, 54, 52, + 49, 47, 44, 42, + 40, 37, 35, 33, + 31, 29, 27, 25, + 23, 21, 20, 18, + 17, 15, 14, 12, + 11, 10, 9, 7, + 6, 5, 5, 4, + 3, 2, 2, 1, + 1, 1, 0, 0, + 0, 0, 0, 0, + 0, 1, 1, 1, + 2, 2, 3, 4, + 5, 5, 6, 7, + 9, 10, 11, 12, + 14, 15, 17, 18, + 20, 21, 23, 25, + 27, 29, 31, 33, + 35, 37, 40, 42, + 44, 47, 49, 52, + 54, 57, 59, 62, + 65, 67, 70, 73, + 76, 79, 82, 85, + 88, 90, 93, 97, + 100, 103, 106, 109, + 112, 115, 118, 121, + 124, 128, +}; + + +const uint8_t* wt_table[] = { + wt_lfo_waveforms, +}; + + +} // namespace frames diff --git a/software/o_c_REV/frames_resources.h b/software/o_c_REV/frames_resources.h new file mode 100644 index 000000000..5e7c91666 --- /dev/null +++ b/software/o_c_REV/frames_resources.h @@ -0,0 +1,96 @@ +// Copyright 2013 Olivier Gillet. +// +// Author: Olivier Gillet (ol.gillet@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +// ----------------------------------------------------------------------------- +// +// Resources definitions. +// +// Automatically generated with: +// make resources + + +#ifndef FRAMES_RESOURCES_H_ +#define FRAMES_RESOURCES_H_ + + +#include + + + +namespace frames { + +typedef uint8_t ResourceId; + +extern const char* string_table[]; + +// extern const uint16_t* lookup_table_table[]; + +extern const uint32_t* lookup_table_hr_table[]; + +extern const uint8_t* wt_table[]; + +// extern const uint16_t lut_easing_in_quartic[]; +// extern const uint16_t lut_easing_out_quartic[]; +// extern const uint16_t lut_easing_in_out_sine[]; +// extern const uint16_t lut_easing_in_out_bounce[]; +// extern const uint16_t lut_response_balance[]; +// extern const uint16_t lut_vca_linear[]; +// extern const uint16_t lut_exponential[]; +// extern const uint32_t lut_increments_vslow[]; +// extern const uint32_t lut_increments_slow[]; +extern const uint32_t lut_increments_med[]; +// extern const uint32_t lut_increments_fast[]; +// extern const uint32_t lut_increments_vfast[]; +extern const uint8_t wt_lfo_waveforms[]; +#define STR_DUMMY 0 // dummy +//#define LUT_EASING_IN_QUARTIC 0 +//#define LUT_EASING_IN_QUARTIC_SIZE 1025 +//#define LUT_EASING_OUT_QUARTIC 1 +//#define LUT_EASING_OUT_QUARTIC_SIZE 1025 +//#define LUT_EASING_IN_OUT_SINE 2 +//#define LUT_EASING_IN_OUT_SINE_SIZE 1025 +//#define LUT_EASING_IN_OUT_BOUNCE 3 +//#define LUT_EASING_IN_OUT_BOUNCE_SIZE 1025 +//#define LUT_RESPONSE_BALANCE 4 +//#define LUT_RESPONSE_BALANCE_SIZE 256 +//#define LUT_VCA_LINEAR 5 +//#define LUT_VCA_LINEAR_SIZE 1025 +//#define LUT_EXPONENTIAL 6 +//#define LUT_EXPONENTIAL_SIZE 256 +// #define LUT_INCREMENTS_VSLOW 0 +// #define LUT_INCREMENTS_VSLOW_SIZE 159 +// #define LUT_INCREMENTS_SLOW 1 +// #define LUT_INCREMENTS_SLOW_SIZE 159 +#define LUT_INCREMENTS_MED 0 // was 2 +#define LUT_INCREMENTS_MED_SIZE 159 +// #define LUT_INCREMENTS_FAST 3 +// #define LUT_INCREMENTS_FAST_SIZE 159 +// #define LUT_INCREMENTS_VFAST 4 +// #define LUT_INCREMENTS_VFAST_SIZE 159 +#define WT_LFO_WAVEFORMS 0 +#define WT_LFO_WAVEFORMS_SIZE 4626 + +} // namespace frames + +#endif // FRAMES_RESOURCES_H_ diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index c6633eae5..9c4df05be 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -26,7 +26,6 @@ #include #include "OC_apps.h" -#include "OC_strings.h" #include "OC_core.h" #include "OC_DAC.h" #include "OC_debug.h" @@ -187,8 +186,7 @@ void FASTRUN loop() { #endif } else { - //Blank the screen instead of drawing the screensaver (chysn 9/2/2018) - //OC::apps::current_app->DrawScreensaver(); + OC::apps::current_app->DrawScreensaver(); } MENU_REDRAW = 0; LAST_REDRAW_TIME = millis(); diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index ea7e9b3ff..5c19fea0c 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -1,16 +1,20 @@ ; PlatformIO Project Configuration File ; -; Build options: build flags, source filter, extra scripting -; Upload options: custom port, speed and extra flags +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; ; Please visit documentation for the other options and examples -; http://docs.platformio.org/page/projectconf.html +; https://docs.platformio.org/page/projectconf.html [platformio] src_dir = . default_envs = oc_prod oc_prod_vor + oc_prod_flipped + oc_stock [env] platform = teensy@4.17.0 @@ -28,10 +32,36 @@ build_src_filter = upload_protocol = teensy-gui [env:oc_prod] -build_flags = ${env.build_flags} +build_flags = + ${env.build_flags} + -DENABLE_APP_ENIGMA + -DENABLE_APP_MIDI + -DENABLE_APP_NEURAL_NETWORK + -DENABLE_APP_PONG + -DENABLE_APP_DARKEST_TIMELINE + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_METAQ + -DENABLE_APP_CHORDS + -DENABLE_APP_H1200 + +[env:oc_stock] +build_flags = + ${env.build_flags} + -DENABLE_APP_PONG + -DENABLE_APP_DARKEST_TIMELINE + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_METAQ + -DENABLE_APP_CHORDS + -DENABLE_APP_SEQUINS + -DENABLE_APP_POLYLFO + -DENABLE_APP_H1200 + -DOC_VERSION_EXTRA="\" +stock\"" [env:oc_prod_flipped] -build_flags = ${env.build_flags} -DFLIP_180 +build_flags = + ${env:oc_prod.build_flags} + -DFLIP_180 + -DOC_VERSION_EXTRA="\" flipped\"" [env:oc_prod_vor] build_flags = diff --git a/software/o_c_REV/tonnetz/tonnetz.h b/software/o_c_REV/tonnetz/tonnetz.h index a78a3cf0a..f9cf63e6f 100644 --- a/software/o_c_REV/tonnetz/tonnetz.h +++ b/software/o_c_REV/tonnetz/tonnetz.h @@ -41,9 +41,11 @@ namespace tonnetz { '*', 'P', 'L', 'R', 'N', 'S', 'H', '@' }; + /* static const char *transform_names_str[TRANSFORM_LAST + 1] = { "*", "P", "L", "R", "N", "S", "H", "@" }; + */ static struct transformation { size_t root_shift; // +1 = root -> third, +2 root -> fifth From e71b3a3916eb8705f36e2ab32237b124febc7a72 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 19 Feb 2023 21:22:04 -0500 Subject: [PATCH 163/417] Phazerville Suite v1.5 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 051e346a1..afb6e0dfd 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -6,6 +6,6 @@ #endif #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.4.99" OC_VERSION_EXTRA +#define OC_VERSION "v1.5" OC_VERSION_EXTRA #define OC_VERSION_URL "github.com/djphazer" #endif From 0decef121b4e111e92db23581b89a56842866332 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Sun, 19 Feb 2023 22:06:35 -0500 Subject: [PATCH 164/417] Update README.md more specific build instructions to get you going --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7c43d9894..e3f50dfb6 100755 --- a/README.md +++ b/README.md @@ -3,20 +3,21 @@ ## Phazerville Suite - an active o_C firmware fork -Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible. +Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! -I've merged bleeding-edge features from various other branches, and confirmed that it compiles and runs on my uo_C. +I've also managed to squeeze in several stock O&C firmware apps: **Quantermain, Meta-Q, Acid Curds, Harrington 1200, Sequins** & **Quadraturia** are all here! ### Notable Features in this branch: * Improved internal clock controls, external clock sync, independent multipliers for each Hemisphere, MIDI Clock out via USB + - Note: push/release both UP+DOWN buttons simultaneously to access the Clock Setup screen * LoFi Tape has been transformed into LoFi Echo (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) * ShiftReg has been upgraded to DualTM - two concurrent 32-bit registers governed by the same length/prob/scale/range settings, both outputs configurable to Pitch, Mod, Trig, Gate from either register * EuclidX - AnnularFusion got a makeover, now includes configurable CV input modulation (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) * Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) * EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking * Modal-editing style navigation (push to toggle editing) -* other small tweaks + experimental applets +* lots of other small tweaks + experimental applets ### How do I try it? @@ -24,7 +25,18 @@ Check the [Releases](https://github.com/djphazer/O_C-BenisphereSuite/releases) s ### How do I build it? -Building the code is fairly simple using Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html) or a [plugin within VSCode](https://platformio.org/install/ide?install=vscode). The project lives within the `software/o_c_REV` directory. From there, you can Build and Upload via USB to your module. +Building the code is fairly simple using Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html): +``` +wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -O get-platformio.py +python3 get-platformio.py +``` +...or as a [a full-featured IDE](https://platformio.org/install/ide), as well as a plugin for VSCode and other existing IDEs. + +The project lives within the `software/o_c_REV` directory. From there, you can Build and Upload via USB to your module: +``` +pio run -e oc_prod -t upload +``` +Various build environment configurations exist in `platformio.ini`. To build all the defaults, simply use `pio run` ### Credits From 1fd0d1782d4cb8d740e9f887f344391a3f75696d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 20 Feb 2023 19:42:58 -0500 Subject: [PATCH 165/417] DrumMap: cursor fix --- software/o_c_REV/HEM_DrumMap.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index e0dad03ec..6f006b03b 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -129,6 +129,7 @@ public: void OnButtonPress() { CursorAction(cursor, 7); + if (mode[1] > 2 && cursor == 3) ++cursor; } void OnEncoderMove(int direction) { From ed8fe26ccb234fb88018a1c42a0896dca0196a39 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 27 Feb 2023 23:33:03 -0500 Subject: [PATCH 166/417] LoFiPCM: bigger buffer, less smoothing --- software/o_c_REV/HEM_LoFiPCM.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 3a4b3bd00..f00a8c03e 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define HEM_LOFI_PCM_BUFFER_SIZE 2048 +#define HEM_LOFI_PCM_BUFFER_SIZE 4096 #define HEM_LOFI_PCM_SPEED 4 // #define CLIPLIMIT 32512 @@ -75,8 +75,8 @@ public: countdown = rate_mod; } - SmoothedOut(0, PCM_TO_CV(lofi_pcm_buffer[head]), rate_mod); - SmoothedOut(1, PCM_TO_CV(lofi_pcm_buffer[length-1 - head]), rate_mod); // reverse buffer! + SmoothedOut(0, PCM_TO_CV(lofi_pcm_buffer[head]), (rate_mod+1)/2); + SmoothedOut(1, PCM_TO_CV(lofi_pcm_buffer[length-1 - head]), (rate_mod+1)/2); // reverse buffer! } } From 9b75fc64b58f36bb7787a7097df105a1fda69dc6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 28 Feb 2023 00:12:36 -0500 Subject: [PATCH 167/417] New icons for manual triggers in Clock Setup Thanks to Discord user ph.xyz I also added some arrows as hints for what they do --- software/o_c_REV/HEM_ClockSetup.ino | 19 ++++++++++++++++--- software/o_c_REV/HSicons.h | 3 +++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index ab86362e3..8785fd8cb 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -18,6 +18,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#define FLASH_TICKS 5000 + class ClockSetup : public HemisphereApplet { public: @@ -52,6 +54,8 @@ public: usbMIDI.sendRealTime(usbMIDI.Stop); } if (clock_m->IsRunning() && clock_m->MIDITock()) usbMIDI.sendRealTime(usbMIDI.Clock); + + if (flash_ticker) --flash_ticker; } void View() { @@ -62,7 +66,10 @@ public: if (!EditMode()) { // special cases for toggle buttons if (cursor == PLAY_STOP) PlayStop(); else if (cursor == FORWARDING) clock_m->ToggleForwarding(); - else if (cursor >= TRIG1) clock_m->Boop(cursor-TRIG1); + else if (cursor >= TRIG1) { + clock_m->Boop(cursor-TRIG1); + flash_ticker = FLASH_TICKS; + } else CursorAction(cursor, LAST_SETTING); } else CursorAction(cursor, LAST_SETTING); @@ -88,6 +95,7 @@ public: case TRIG3: case TRIG4: clock_m->Boop(cursor-TRIG1); + flash_ticker = FLASH_TICKS; break; case EXT_PPQN: @@ -144,6 +152,7 @@ private: int cursor; // ClockSetupCursor bool start_q; bool stop_q; + int flash_ticker; ClockManager *clock_m = clock_m->get(); void PlayStop() { @@ -196,7 +205,10 @@ private: } // Manual triggers - for (int i=0; i<4; i++) { gfxIcon(4 + i*32, 49, BURST_ICON); } + for (int i=0; i<4; i++) { + gfxIcon(4 + i*32, 49, (flash_ticker && i == cursor-TRIG1)?BTN_ON_ICON:BTN_OFF_ICON); + gfxIcon(4 + i*32, 56, DOWN_BTN_ICON); + } switch ((ClockSetupCursor)cursor) { case PLAY_STOP: gfxFrame(11, 14, 10, 10); break; @@ -219,7 +231,8 @@ private: case TRIG2: case TRIG3: case TRIG4: - gfxFrame(3 + 32*(cursor-TRIG1), 48, 10, 10); + if (0 == flash_ticker) + gfxFrame(3 + 32*(cursor-TRIG1), 48, 9, 10); break; default: break; diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index b32c557af..8063cb939 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -29,6 +29,9 @@ const uint8_t GAUGE_ICON[8] = {0x38,0x44,0x02,0x32,0x32,0x0a,0x44,0x3a}; const uint8_t CLOSED_ICON[8] = {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; const uint8_t OPEN_ICON[8] = {0x18,0x0c,0x04,0x06,0x03,0x01,0x00,0x18}; +const uint8_t BTN_OFF_ICON[8] = {0x7e,0x8b,0x91,0x91,0x91,0x8b,0x7e,0x00}; +const uint8_t BTN_ON_ICON[8] = {0x71,0xaa,0xa8,0xab,0xa8,0xaa,0x71,0x00}; + // Transport const uint8_t LOOP_ICON[8] = {0x34,0x64,0x4e,0x4e,0xe4,0xe4,0x4c,0x58}; const uint8_t PLAYONCE_ICON[8] = {0x10,0x10,0x10,0x10,0x38,0x38,0x10,0x10}; From 350a9a9d1a7ea86f2f7a50d68e0c7d26025140cc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 1 Mar 2023 01:43:32 -0500 Subject: [PATCH 168/417] EuclidX: UI fixup --- software/o_c_REV/HEM_EuclidX.ino | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 6369a86ab..4d8132f75 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -252,27 +252,28 @@ private: void DrawEditor() { const int spacing = 16; + const int pad_left = 5; if (cursor < CV_DEST1) { - gfxBitmap(8 + 0 * spacing, 15, 8, LOOP_ICON); + gfxBitmap(pad_left + 0 * spacing, 15, 8, LOOP_ICON); } - gfxBitmap(8 + 1 * spacing, 15, 8, X_NOTE_ICON); - gfxBitmap(8 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); - gfxPrint(8 + 3 * spacing, 15, "+"); + gfxBitmap(pad_left + 1 * spacing, 15, 8, X_NOTE_ICON); + gfxBitmap(pad_left + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); + gfxPrint(pad_left + 3 * spacing, 15, "+"); int y = 15; ForEachChannel (ch) { y += 10; - gfxPrint(3 + 0 * spacing + pad(10, actual_length[ch]), y, actual_length[ch]); - gfxPrint(3 + 1 * spacing + pad(10, actual_beats[ch]), y, actual_beats[ch]); - gfxPrint(3 + 2 * spacing + pad(10, actual_offset[ch]), y, actual_offset[ch]); - gfxPrint(3 + 3 * spacing + pad(10, actual_padding[ch]), y, actual_padding[ch]); + gfxPrint(0 * spacing + pad(10, actual_length[ch]), y, actual_length[ch]); + gfxPrint(1 * spacing + pad(10, actual_beats[ch]), y, actual_beats[ch]); + gfxPrint(2 * spacing + pad(10, actual_offset[ch]), y, actual_offset[ch]); + gfxPrint(3 * spacing + pad(10, actual_padding[ch]), y, actual_padding[ch]); // CV assignment indicators ForEachChannel(ch_dest) { int ff = cv_dest[ch_dest] - LENGTH2*ch; if (ff >= 0 && ff < LENGTH2) - gfxBitmap(ff * spacing, y, 3, ch_dest?SUB_TWO:SUP_ONE); + gfxBitmap(13 + ff * spacing, y, 3, ch_dest?SUB_TWO:SUP_ONE); } } @@ -289,7 +290,7 @@ private: case BEATS1: case OFFSET1: case PADDING1: - gfxCursor(3 + f * spacing, y, 13); + gfxCursor(f * spacing, y, 13); break; case CV_DEST1: From d879f8ca3a8a7922daad7a36428e4f2e08d4a5b7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 1 Mar 2023 02:08:52 -0500 Subject: [PATCH 169/417] More icon enhancements: DrumMap and ClockSetup nice pixels from ph.xyz --- software/o_c_REV/HEM_ClockSetup.ino | 2 +- software/o_c_REV/HEM_DrumMap.ino | 10 +++++++--- software/o_c_REV/HSicons.h | 21 ++++++++++++++++----- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 8785fd8cb..3818a0dba 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -232,7 +232,7 @@ private: case TRIG3: case TRIG4: if (0 == flash_ticker) - gfxFrame(3 + 32*(cursor-TRIG1), 48, 9, 10); + gfxIcon(12 + 32*(cursor-TRIG1), 50, LEFT_ICON); break; default: break; diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index 6f006b03b..fef75b9df 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -22,7 +22,7 @@ #include "grids_resources.h" -#define HEM_DRUMMAP_PULSE_ANIMATION_TICKS 500 +#define HEM_DRUMMAP_PULSE_ANIMATION_TICKS 1000 #define HEM_DRUMMAP_VALUE_ANIMATION_TICKS 16000 #define HEM_DRUMMAP_AUTO_RESET_TICKS 30000 @@ -227,6 +227,7 @@ protected: private: const uint8_t *MODE_ICONS[3] = {BD_ICON,SN_ICON,HH_ICON}; + const uint8_t *MODE_PULSE_ICON[3] = {BD_HIT_ICON,SN_HIT_ICON,HH_HIT_ICON}; const char *CV_MODE_NAMES[3] = {"FILL A/B", "X/Y", "FA/CHAOS"}; const int *VALUE_MAP[5] = {&fill[0], &fill[1], &x, &y, &chaos}; int cursor = 0; @@ -271,7 +272,8 @@ private: void DrawInterface() { // output selection gfxPrint(1,15,"A:"); - gfxIcon(15,15,MODE_ICONS[mode[0]]); + gfxIcon(15,15, (pulse_animation[0] > 0)? MODE_PULSE_ICON[mode[0]] : MODE_ICONS[mode[0]] ); + gfxPrint(32,15,"B:"); if (mode[1] == 3) { // accent @@ -279,14 +281,16 @@ private: gfxPrint(53,15,">"); } else { // standard - gfxIcon(46,15,MODE_ICONS[mode[1]]); + gfxIcon(46,15,(pulse_animation[1] > 0)? MODE_PULSE_ICON[mode[1]] : MODE_ICONS[mode[1]]); } + /* // pulse animation per channel ForEachChannel(ch){ if (pulse_animation[ch] > 0) { gfxInvert(1+ch*32,15,8,8); } } + */ // fill gfxPrint(1,25,"F"); diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index 8063cb939..be73a9c82 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -20,9 +20,15 @@ const uint8_t LINK_ICON[8] = {0x70,0xd8,0x88,0xda,0x5b,0x11,0x1b,0x0e}; const uint8_t CHECK_OFF_ICON[8] = {0xff,0x81,0x81,0x81,0x81,0x81,0x81,0xff}; const uint8_t CHECK_ON_ICON[8] = {0xcb,0x99,0xb1,0xb1,0x99,0x8c,0x86,0xf3}; const uint8_t CHECK_ICON[8] = {0x08,0x18,0x30,0x30,0x18,0x0c,0x06,0x03}; -const uint8_t BD_ICON[8] = {0x1C,0xA2,0x41,0x41,0x41,0x41,0xA2,0x1C}; -const uint8_t SN_ICON[8] = {0x0F,0x09,0x89,0x79,0x79,0x89,0x09,0x0F}; -const uint8_t HH_ICON[8] = {0x00,0x0A,0x0A,0xFF,0x4A,0x8A,0x00,0x00}; + +// Drums for DrumMap +const uint8_t BD_ICON[8] = {0x1c,0xa2,0x41,0x41,0x41,0xa2,0x1c,0x00}; +const uint8_t SN_ICON[8] = {0x3e,0xe5,0x25,0x25,0x25,0xe5,0x3e,0x00}; +const uint8_t HH_ICON[8] = {0x04,0x8a,0x4a,0x79,0x4a,0x8a,0x04,0x00}; +const uint8_t BD_HIT_ICON[8] = {0xf3,0x21,0x80,0xf0,0x80,0x21,0xf3,0x00}; +const uint8_t SN_HIT_ICON[8] = {0xc1,0x04,0xc4,0xc4,0xc4,0x04,0xc1,0x00}; +const uint8_t HH_HIT_ICON[8] = {0x0b,0x91,0x51,0x70,0x51,0x91,0x0b,0x00}; + const uint8_t RANDOM_ICON[8] = {0x7c,0x82,0x8a,0x82,0xa2,0x82,0x7c,0x00}; // A die showing '2' const uint8_t BURST_ICON[8] = {0x11,0x92,0x54,0x00,0xd7,0x00,0x54,0x92}; const uint8_t GAUGE_ICON[8] = {0x38,0x44,0x02,0x32,0x32,0x0a,0x44,0x3a}; @@ -47,9 +53,14 @@ const uint8_t DOWN_BTN_ICON[8] = {0x00,0x10,0x30,0x70,0x70,0x30,0x10,0x00}; const uint8_t LEFT_BTN_ICON[8] = {0x00,0x18,0x3c,0x7e,0x00,0x00,0x00,0x00}; const uint8_t RIGHT_BTN_ICON[8] = {0x00,0x00,0x00,0x00,0x7e,0x3c,0x18,0x00}; +const uint8_t UP_ICON[8] = {0x08,0x0c,0x7e,0x7f,0x7e,0x0c,0x08,0x00}; +const uint8_t DOWN_ICON[8] = {0x10,0x30,0x7e,0xfe,0x7e,0x30,0x10,0x00}; +const uint8_t LEFT_ICON[8] = {0x08,0x1c,0x3e,0x7f,0x1c,0x1c,0x1c,0x00}; +const uint8_t RIGHT_ICON[8] = {0x00,0x1c,0x1c,0x7f,0x3e,0x1c,0x08,0x00}; + // Metronome -const uint8_t METRO_L_ICON[8] = {0x00,0xc2,0xb4,0x88,0x94,0x88,0xb0,0xc0}; -const uint8_t METRO_R_ICON[8] = {0x00,0xc0,0xb0,0x88,0x94,0x88,0xb4,0xc2}; +const uint8_t METRO_L_ICON[8] = {0xf3,0x8c,0x9a,0xa2,0x82,0x8c,0xf0,0x00}; +const uint8_t METRO_R_ICON[8] = {0xf0,0x8c,0x82,0xa2,0x9a,0x8c,0xf3,0x00}; // Notes const uint8_t X_NOTE_ICON[8] = {0x00,0xa0,0x40,0xa0,0x1f,0x02,0x0c,0x00}; From 53dc08e91549f83ac4dbdd36340d2121c0c0ce02 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 3 Mar 2023 15:16:47 -0500 Subject: [PATCH 170/417] AttenOff: new down arrow icon --- software/o_c_REV/HEM_AttenuateOffset.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index e11512e04..18267de23 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -130,8 +130,7 @@ private: } if (mix_final) { - gfxLine(3, 24, 3, 31); - gfxIcon(0, 25, DOWN_BTN_ICON); + gfxIcon(1, 25, DOWN_ICON); } if (cursor == 4) { From 548926b8e3b5c25810102fb9cd5801b12695ef52 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 6 Mar 2023 00:09:36 -0500 Subject: [PATCH 171/417] Long-press left encoder starts/stops internal clock No more Pause. Physical clock forwarding is set within ClockSetup; icon appears on the right hemisphere now. --- software/o_c_REV/APP_HEMISPHERE.ino | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 3eb00e628..a3389766c 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -169,9 +169,11 @@ public: if (clock_m->IsRunning() || clock_m->IsPaused()) { // Metronome icon graphics.drawBitmap8(56, 1, 8, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); - } else if (clock_m->IsForwarded()) { + } + + if (clock_m->IsForwarded()) { // CV Forwarding Icon - graphics.drawBitmap8(56, 1, 8, CLOCK_ICON); + graphics.drawBitmap8(120, 1, 8, CLOCK_ICON); } } } @@ -237,9 +239,8 @@ public: } void ToggleClockRun() { - if (clock_m->IsRunning()) clock_m->Pause(); - else if (clock_m->IsPaused()) clock_m->Start(); - else clock_m->ToggleForwarding(); + if (clock_m->IsRunning()) clock_m->Stop(); + else clock_m->Start(); } void ToggleClockSetup() { From 9a674b2774c7aa06d15627ebb9002e05b895caa5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 3 Mar 2023 00:26:29 -0500 Subject: [PATCH 172/417] New Applet: Calibr8 - pitch CV fine-tuning tool --- software/o_c_REV/HEM_Calibr8.ino | 190 +++++++++++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 1 + 2 files changed, 191 insertions(+) create mode 100644 software/o_c_REV/HEM_Calibr8.ino diff --git a/software/o_c_REV/HEM_Calibr8.ino b/software/o_c_REV/HEM_Calibr8.ino new file mode 100644 index 000000000..e09f1aeb0 --- /dev/null +++ b/software/o_c_REV/HEM_Calibr8.ino @@ -0,0 +1,190 @@ +// Copyright (c) 2023, Nicholas J. Michalek +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#define CAL8_PRECISION 10000 + +class Calibr8 : public HemisphereApplet { +public: + enum CalCursor { + SCALEFACTOR_A, + TRANS_A, + OFFSET_A, + SCALEFACTOR_B, + TRANS_B, + OFFSET_B, + + MAX_CURSOR = OFFSET_B + }; + + const char* applet_name() { + return "Calibr8"; + } + + void Start() { + clocked_mode = false; + AllowRestart(); + } + + void Controller() { + bool clocked = Clock(0); + if (clocked) clocked_mode = true; + + ForEachChannel(ch) { + uint8_t input_note = MIDIQuantizer::NoteNumber(In(ch), 0); + + // clocked transpose + if (!clocked_mode || clocked) + transpose_active[ch] = transpose[ch]; + + input_note += transpose_active[ch]; + + int output_cv = MIDIQuantizer::CV(input_note) * (CAL8_PRECISION + scale_factor[ch]) / CAL8_PRECISION; + output_cv += offset[ch]; + + Out(ch, output_cv); + } + } + + void View() { + gfxHeader(applet_name()); + DrawInterface(); + } + + void OnButtonPress() { + CursorAction(cursor, MAX_CURSOR); + } + + void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, MAX_CURSOR); + return; + } + + bool ch = (cursor > OFFSET_A); + switch (cursor) { + case OFFSET_A: + case OFFSET_B: + offset[ch] = constrain(offset[ch] + direction, -100, 100); + break; + + case SCALEFACTOR_A: + case SCALEFACTOR_B: + scale_factor[ch] = constrain(scale_factor[ch] + direction, -500, 500); + break; + + case TRANS_A: + case TRANS_B: + transpose[ch] = constrain(transpose[ch] + direction, -36, 60); + break; + + default: break; + } + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation { 0,10}, scale_factor[0] + 500); + Pack(data, PackLocation {10,10}, scale_factor[1] + 500); + Pack(data, PackLocation {20, 8}, offset[0] + 100); + Pack(data, PackLocation {28, 8}, offset[1] + 100); + Pack(data, PackLocation {36, 7}, transpose[0] + 36); + Pack(data, PackLocation {43, 7}, transpose[1] + 36); + return data; + } + + void OnDataReceive(uint64_t data) { + scale_factor[0] = Unpack(data, PackLocation { 0,10}) - 500; + scale_factor[1] = Unpack(data, PackLocation {10,10}) - 500; + offset[0] = Unpack(data, PackLocation {20, 8}) - 100; + offset[1] = Unpack(data, PackLocation {28, 8}) - 100; + transpose[0] = Unpack(data, PackLocation {36, 7}) - 36; + transpose[1] = Unpack(data, PackLocation {43, 7}) - 36; + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock"; + help[HEMISPHERE_HELP_CVS] = "1,2=Pitch inputs"; + help[HEMISPHERE_HELP_OUTS] = "A,B=Pitch outputs"; + help[HEMISPHERE_HELP_ENCODER] = "Scale/Offset/Trans"; + // "------------------" <-- Size Guide + } + +private: + int cursor; + bool clocked_mode = false; + int scale_factor[2] = {0,0}; // precision of 0.01% as an offset from 100% + int offset[2] = {0,0}; // fine-tuning offset + int transpose[2] = {0,0}; // in semitones + int transpose_active[2] = {0,0}; // held value while waiting for trigger + + void DrawInterface() { + ForEachChannel(ch) { + int y = 14 + ch*21; + gfxPrint(0, y, ch?"B:":"A:"); + + int whole = (scale_factor[ch] + CAL8_PRECISION) / 100; + int decimal = (scale_factor[ch] + CAL8_PRECISION) % 100; + gfxPrint(12 + pad(100, whole), y, whole); + gfxPrint("."); + if (decimal < 10) gfxPrint("0"); + gfxPrint(decimal); + gfxPrint("%"); + + // second line + y += 10; + gfxIcon(0, y, BEND_ICON); + gfxPrint(8, y, transpose[ch]); + gfxIcon(32, y, UP_DOWN_ICON); + gfxPrint(40, y, offset[ch]); + } + gfxLine(0, 33, 63, 33); // gotta keep em separated + + bool ch = (cursor > OFFSET_A); + int param = (cursor % 3); + if (param == 0) // Scaling + gfxCursor(12, 22 + ch*21, 40); + else // Transpose or Fine Tune + gfxCursor(8 + (param-1)*32, 32 + ch*21, 20); + + gfxSkyline(); + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to Calibr8, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +Calibr8 Calibr8_instance[2]; + +void Calibr8_Start(bool hemisphere) {Calibr8_instance[hemisphere].BaseStart(hemisphere);} +void Calibr8_Controller(bool hemisphere, bool forwarding) {Calibr8_instance[hemisphere].BaseController(forwarding);} +void Calibr8_View(bool hemisphere) {Calibr8_instance[hemisphere].BaseView();} +void Calibr8_OnButtonPress(bool hemisphere) {Calibr8_instance[hemisphere].OnButtonPress();} +void Calibr8_OnEncoderMove(bool hemisphere, int direction) {Calibr8_instance[hemisphere].OnEncoderMove(direction);} +void Calibr8_ToggleHelpScreen(bool hemisphere) {Calibr8_instance[hemisphere].HelpScreen();} +uint64_t Calibr8_OnDataRequest(bool hemisphere) {return Calibr8_instance[hemisphere].OnDataRequest();} +void Calibr8_OnDataReceive(bool hemisphere, uint64_t data) {Calibr8_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 34f146d20..d8ed683cb 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -24,6 +24,7 @@ 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), \ From eed4ca07eace8fc7eddadd5c41720bf4b6aa26d9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 6 Mar 2023 17:46:25 -0500 Subject: [PATCH 173/417] New App: Calibr8or - 4-channel pitch CV fine-tuning app Shoutout to Chris Meyer for the great idea! Features per channel: * Voltage scaling in 0.01% increments * DAC Bias offset in 1/128th semitone * Input quantization, Scale & Root Note selection * Transpose adjustment in Octaves and Scale Degrees * Clocked transpose mode * S&H mode * 4x instances w/ separate configs (presets hack) - Single instance for the main build --- software/o_c_REV/APP_CALIBR8OR.ino | 498 +++++++++++++++++++++++++++++ software/o_c_REV/HSicons.h | 3 + software/o_c_REV/OC_apps.ino | 9 + software/o_c_REV/platformio.ini | 15 +- 4 files changed, 520 insertions(+), 5 deletions(-) create mode 100644 software/o_c_REV/APP_CALIBR8OR.ino diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino new file mode 100644 index 000000000..b059c27a5 --- /dev/null +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -0,0 +1,498 @@ +// Copyright (c) 2023, Nicholas J. Michalek +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +/* + * Based on a design spec from Chris Meyer / Alias Zone / Learning Modular + */ + +#if defined(ENABLE_APP_CALIBR8OR) || defined(ENABLE_CALIBR8OR_X4) + +#include "HSApplication.h" +#include "HSMIDI.h" +#include "util/util_settings.h" +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_scales.h" +#include "SegmentDisplay.h" +#include "src/drivers/FreqMeasure/OC_FreqMeasure.h" + +#define CAL8_MAX_TRANSPOSE 60 +const int CAL8OR_PRECISION = 10000; + +// Settings storage spec (per channel?) +enum CAL8SETTINGS { + CAL8_DATA_VALID, // 1 bit + + CAL8_SCALE_A, // 12 bits + CAL8_SCALEFACTOR_A, // 10 bits + CAL8_OFFSET_A, // 8 bits + CAL8_TRANSPOSE_A, // 8 bits + CAL8_ROOTKEY_AND_CLOCKMODE_A, // 4 + 2 bits + + CAL8_SCALE_B, + CAL8_SCALEFACTOR_B, + CAL8_OFFSET_B, + CAL8_TRANSPOSE_B, + CAL8_ROOTKEY_AND_CLOCKMODE_B, + + CAL8_SCALE_C, + CAL8_SCALEFACTOR_C, + CAL8_OFFSET_C, + CAL8_TRANSPOSE_C, + CAL8_ROOTKEY_AND_CLOCKMODE_C, + + CAL8_SCALE_D, + CAL8_SCALEFACTOR_D, + CAL8_OFFSET_D, + CAL8_TRANSPOSE_D, + CAL8_ROOTKEY_AND_CLOCKMODE_D, + + CAL8_SETTING_LAST +}; + +class Calibr8or : public HSApplication, + public settings::SettingsBase { +public: + enum Cal8Channel { + CAL8_CHANNEL_A, + CAL8_CHANNEL_B, + CAL8_CHANNEL_C, + CAL8_CHANNEL_D, + + NR_OF_CHANNELS + }; + enum Cal8EditMode { + TRANSPOSE, + TRACKING, + + NR_OF_EDITMODES + }; + enum Cal8ClockMode { + CONTINUOUS, + TRIG_TRANS, + SAMPLE_AND_HOLD, + + NR_OF_CLOCKMODES + }; + + void set_index(int index_) { index = index_; } + + void Start() { + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + quantizer[ch].Init(); + scale[ch] = OC::Scales::SCALE_SEMI; + quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + + scale_factor[ch] = 0; + offset[ch] = 0; + transpose[ch] = 0; + clocked_mode[ch] = 0; + last_note[ch] = 0; + } + + segment.Init(SegmentSize::BIG_SEGMENTS); + + // make sure to turn this off, just in case? + FreqMeasure.end(); + OC::DigitalInputs::reInit(); + } + + void Resume() { + if (values_[CAL8_DATA_VALID]) + LoadFromEEPROM(); + } + + void Controller() { + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + bool clocked = Clock(ch); + + // clocked transpose + if (CONTINUOUS == clocked_mode[ch] || clocked) { + transpose_active[ch] = transpose[ch]; + } + + // respect S&H mode + if (clocked_mode[ch] != SAMPLE_AND_HOLD || clocked) { + // CV value + int pitch = In(ch); + int quantized = quantizer[ch].Process(pitch, root_note[ch] * 128, transpose_active[ch]); + last_note[ch] = quantized; + } + + int output_cv = last_note[ch] * (CAL8OR_PRECISION + scale_factor[ch]) / CAL8OR_PRECISION; + output_cv += offset[ch]; + + Out(ch, output_cv); + } + } + + void View() { + gfxHeader("Calibr8or"); +#ifdef ENABLE_CALIBR8OR_X4 + const char * cal8_preset_id[4] = {"A", "B", "C", "D"}; + gfxPrint(120, 0, cal8_preset_id[index]); +#endif + DrawInterface(); + } + + void SaveToEEPROM() { + int ix = 0; + + values_[ix++] = 1; // validity flag + + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + values_[ix++] = scale[ch]; + values_[ix++] = scale_factor[ch] + 500; + values_[ix++] = offset[ch] + 63; + values_[ix++] = transpose[ch] + CAL8_MAX_TRANSPOSE; + values_[ix++] = ((clocked_mode[ch] & 0x03) << 4) | (root_note[ch] & 0x0f); + } + } + /* + CAL8_SCALE_A, // 12 bits + CAL8_SCALEFACTOR_A, // 10 bits + CAL8_OFFSET_A, // 8 bits + CAL8_TRANSPOSE_A, // 8 bits + CAL8_CLOCKMODE_A, // 2 bits + */ + void LoadFromEEPROM() { + int ix = 1; // skip validity flag + + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + scale[ch] = values_[ix++]; + scale[ch] = constrain(scale[ch], 0, OC::Scales::NUM_SCALES - 1); + quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + + scale_factor[ch] = values_[ix++] - 500; + offset[ch] = values_[ix++] - 63; + transpose[ch] = values_[ix++] - CAL8_MAX_TRANSPOSE; + + uint32_t root_and_mode = uint32_t(values_[ix++]); + clocked_mode[ch] = ((root_and_mode >> 4) & 0x03) % NR_OF_CLOCKMODES; + root_note[ch] = constrain(int(root_and_mode & 0x0f), 0, 11); + } + } + + ///////////////////////////////////////////////////////////////// + // Control handlers + ///////////////////////////////////////////////////////////////// + void OnLeftButtonPress() { + // Toggle between Transpose mode and Tracking Compensation + ++edit_mode %= NR_OF_EDITMODES; + } + + void OnLeftButtonLongPress() { + // Toggle triggered transpose mode + ++clocked_mode[sel_chan] %= NR_OF_CLOCKMODES; + } + + void OnRightButtonPress() { + // Scale selection + scale_edit = !scale_edit; + } + + void OnUpButtonPress() { + ++sel_chan %= NR_OF_CHANNELS; + } + + void OnDownButtonPress() { + if (--sel_chan < 0) sel_chan += NR_OF_CHANNELS; + } + + void OnDownButtonLongPress() { + } + + // Left encoder: Octave or VScaling + Root Note + void OnLeftEncoderMove(int direction) { + if (scale_edit) { + root_note[sel_chan] = constrain(root_note[sel_chan] + direction, 0, 11); + return; + } + + if (edit_mode == TRANSPOSE) { // Octave jump + int s = OC::Scales::GetScale(scale[sel_chan]).num_notes; + transpose[sel_chan] += (direction * s); + while (transpose[sel_chan] > CAL8_MAX_TRANSPOSE) transpose[sel_chan] -= s; + while (transpose[sel_chan] < -CAL8_MAX_TRANSPOSE) transpose[sel_chan] += s; + } + else { // Tracking compensation + scale_factor[sel_chan] = constrain(scale_factor[sel_chan] + direction, -500, 500); + } + } + + // Right encoder: Semitones or Bias Offset + Scale Select + void OnRightEncoderMove(int direction) { + if (scale_edit) { + scale[sel_chan] += direction; + if (scale[sel_chan] >= OC::Scales::NUM_SCALES) scale[sel_chan] = 0; + if (scale[sel_chan] < 0) scale[sel_chan] = OC::Scales::NUM_SCALES - 1; + quantizer[sel_chan].Configure(OC::Scales::GetScale(scale[sel_chan]), 0xffff); + return; + } + + if (edit_mode == TRANSPOSE) { + transpose[sel_chan] = constrain(transpose[sel_chan] + direction, -CAL8_MAX_TRANSPOSE, CAL8_MAX_TRANSPOSE); + } + else { + offset[sel_chan] = constrain(offset[sel_chan] + direction, -63, 64); + } + } + +private: + int index = 0; + + int sel_chan = 0; + int edit_mode = 0; // Cal8EditMode + bool scale_edit = 0; + + SegmentDisplay segment; + braids::Quantizer quantizer[NR_OF_CHANNELS]; + int scale[NR_OF_CHANNELS]; // Scale per channel + int root_note[NR_OF_CHANNELS]; // in semitones from C + int last_note[NR_OF_CHANNELS]; // for S&H mode + + uint8_t clocked_mode[NR_OF_CHANNELS]; + int scale_factor[NR_OF_CHANNELS] = {0,0,0,0}; // precision of 0.01% as an offset from 100% + int offset[NR_OF_CHANNELS] = {0,0,0,0}; // fine-tuning offset + int transpose[NR_OF_CHANNELS] = {0,0,0,0}; // in semitones + int transpose_active[NR_OF_CHANNELS] = {0,0,0,0}; // held value while waiting for trigger + + void DrawInterface() { + // Draw channel tabs + for (int i = 0; i < NR_OF_CHANNELS; ++i) { + gfxLine(i*32, 13, i*32, 22); // vertical line on left + if (clocked_mode[i]) gfxIcon(2 + i*32, 14, CLOCK_ICON); + if (clocked_mode[i] == SAMPLE_AND_HOLD) gfxIcon(22 + i*32, 14, STAIRS_ICON); + gfxPrint(i*32 + 13, 14, i+1); + + if (i == sel_chan) + gfxInvert(1 + i*32, 13, 31, 11); + } + gfxLine(127, 13, 127, 22); // vertical line + gfxLine(0, 23, 127, 23); + + // Draw parameters for selected channel + int y = 32; + + // Transpose + gfxIcon(9, y, BEND_ICON); + + // -- LCD Display Section -- + gfxFrame(20, y-3, 64, 18); + gfxIcon(23, y+2, (transpose[sel_chan] >= 0)? PLUS_ICON : MINUS_ICON); + + int s = OC::Scales::GetScale(scale[sel_chan]).num_notes; + int octave = transpose[sel_chan] / s; + int semitone = transpose[sel_chan] % s; + segment.PrintWhole(33, y, abs(octave), 10); + gfxPrint(53, y+5, "."); + segment.PrintWhole(61, y, abs(semitone), 10); + + // Scale + gfxIcon(89, y, SCALE_ICON); + gfxPrint(99, y, OC::scale_names_short[scale[sel_chan]]); + if (scale_edit) { + gfxInvert(98, y-1, 29, 9); + gfxIcon(100, y+10, RIGHT_ICON); + } + // Root Note + gfxPrint(110, y+10, OC::Strings::note_names_unpadded[root_note[sel_chan]]); + + // Tracking Compensation + y += 22; + gfxIcon(9, y, ZAP_ICON); + int whole = (scale_factor[sel_chan] + CAL8OR_PRECISION) / 100; + int decimal = (scale_factor[sel_chan] + CAL8OR_PRECISION) % 100; + gfxPrint(20 + pad(100, whole), y, whole); + gfxPrint("."); + if (decimal < 10) gfxPrint("0"); + gfxPrint(decimal); + gfxPrint("% "); + + if (offset[sel_chan] >= 0) gfxPrint("+"); + gfxPrint(offset[sel_chan]); + + // mode indicator + if (!scale_edit) + gfxIcon(0, 32 + edit_mode*22, RIGHT_ICON); + } +}; + +SETTINGS_DECLARE(Calibr8or, CAL8_SETTING_LAST) { + {0, 0, 1, "validity flag", NULL, settings::STORAGE_TYPE_U4}, + + {0, 0, 65535, "Scale A", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "CV Scaling Factor A", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 255, "Offset Bias A", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Transpose A", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Root Key + Mode A", NULL, settings::STORAGE_TYPE_U8}, + + {0, 0, 65535, "Scale B", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "CV Scaling Factor B", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 255, "Offset Bias B", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Transpose B", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Root Key + Mode B", NULL, settings::STORAGE_TYPE_U8}, + + {0, 0, 65535, "Scale C", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "CV Scaling Factor C", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 255, "Offset Bias C", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Transpose C", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Root Key + Mode C", NULL, settings::STORAGE_TYPE_U8}, + + {0, 0, 65535, "Scale D", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "CV Scaling Factor D", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 255, "Offset Bias D", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Transpose D", NULL, settings::STORAGE_TYPE_U8}, + {0, 0, 255, "Root Key + Mode D", NULL, settings::STORAGE_TYPE_U8} +}; + +// To allow FOUR preset configs... I just made 4 copies of everything lol +Calibr8or Calibr8or_instance[4]; + +// App stubs +void Calibr8or_init() { Calibr8or_instance[0].BaseStart(); } +void Calibr8or_init(int index) { + Calibr8or_instance[index].BaseStart(); + Calibr8or_instance[index].set_index(index); +} +void Calibr8orA_init() { Calibr8or_init(0); } +void Calibr8orB_init() { Calibr8or_init(1); } +void Calibr8orC_init() { Calibr8or_init(2); } +void Calibr8orD_init() { Calibr8or_init(3); } + +size_t Calibr8or_storageSize() { return Calibr8or::storageSize(); } +size_t Calibr8orA_storageSize() { return Calibr8or::storageSize(); } +size_t Calibr8orB_storageSize() { return Calibr8or::storageSize(); } +size_t Calibr8orC_storageSize() { return Calibr8or::storageSize(); } +size_t Calibr8orD_storageSize() { return Calibr8or::storageSize(); } + +size_t Calibr8or_save(void *storage) { return Calibr8or_instance[0].Save(storage); } +size_t Calibr8orA_save(void *storage) { return Calibr8or_instance[0].Save(storage); } +size_t Calibr8orB_save(void *storage) { return Calibr8or_instance[1].Save(storage); } +size_t Calibr8orC_save(void *storage) { return Calibr8or_instance[2].Save(storage); } +size_t Calibr8orD_save(void *storage) { return Calibr8or_instance[3].Save(storage); } + +size_t Calibr8or_restore(const void *storage, int index) { + size_t s = Calibr8or_instance[index].Restore(storage); + Calibr8or_instance[index].Resume(); + return s; +} +size_t Calibr8or_restore(const void *storage) { return Calibr8or_restore(storage, 0); } +size_t Calibr8orA_restore(const void *storage) { return Calibr8or_restore(storage, 0); } +size_t Calibr8orB_restore(const void *storage) { return Calibr8or_restore(storage, 1); } +size_t Calibr8orC_restore(const void *storage) { return Calibr8or_restore(storage, 2); } +size_t Calibr8orD_restore(const void *storage) { return Calibr8or_restore(storage, 3); } + +void Calibr8or_isr() { return Calibr8or_instance[0].BaseController(); } +void Calibr8orA_isr() { return Calibr8or_instance[0].BaseController(); } +void Calibr8orB_isr() { return Calibr8or_instance[1].BaseController(); } +void Calibr8orC_isr() { return Calibr8or_instance[2].BaseController(); } +void Calibr8orD_isr() { return Calibr8or_instance[3].BaseController(); } + +void Calibr8or_handleAppEvent(OC::AppEvent event, int index) { + switch (event) { + case OC::APP_EVENT_RESUME: + Calibr8or_instance[index].Resume(); + break; + + // The idea is to auto-save when the screen times out... + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + Calibr8or_instance[index].SaveToEEPROM(); + // TODO: initiate actual EEPROM save + // app_data_save(); + break; + + default: break; + } +} +void Calibr8or_handleAppEvent(OC::AppEvent event) { + Calibr8or_handleAppEvent(event, 0); +} +void Calibr8orA_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 0); } +void Calibr8orB_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 1); } +void Calibr8orC_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 2); } +void Calibr8orD_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 3); } + +void Calibr8or_loop() {} // Deprecated +void Calibr8orA_loop() {} // Deprecated +void Calibr8orB_loop() {} // Deprecated +void Calibr8orC_loop() {} // Deprecated +void Calibr8orD_loop() {} // Deprecated + +void Calibr8or_menu() { Calibr8or_instance[0].BaseView(); } +void Calibr8orA_menu() { Calibr8or_instance[0].BaseView(); } +void Calibr8orB_menu() { Calibr8or_instance[1].BaseView(); } +void Calibr8orC_menu() { Calibr8or_instance[2].BaseView(); } +void Calibr8orD_menu() { Calibr8or_instance[3].BaseView(); } + +void Calibr8or_screensaver() { + // XXX: Consider a view like Quantermain + // other ideas: Actual note being played, current transpose setting + // ...for all 4 channels at once. +} +void Calibr8orA_screensaver() {} +void Calibr8orB_screensaver() {} +void Calibr8orC_screensaver() {} +void Calibr8orD_screensaver() {} + +void Calibr8or_handleButtonEvent(const UI::Event &event, int index) { + // For left encoder, handle press and long press + if (event.control == OC::CONTROL_BUTTON_L) { + if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance[index].OnLeftButtonLongPress(); + else Calibr8or_instance[index].OnLeftButtonPress(); + } + + // For right encoder, only handle press (long press is reserved) + if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance[index].OnRightButtonPress(); + + // For up button, handle only press (long press is reserved) + if (event.control == OC::CONTROL_BUTTON_UP) Calibr8or_instance[index].OnUpButtonPress(); + + // For down button, handle press and long press + if (event.control == OC::CONTROL_BUTTON_DOWN) { + if (event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance[index].OnDownButtonPress(); + if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance[index].OnDownButtonLongPress(); + } +} +void Calibr8or_handleButtonEvent(const UI::Event &event) { + Calibr8or_handleButtonEvent(event, 0); +} +void Calibr8orA_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 0); } +void Calibr8orB_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 1); } +void Calibr8orC_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 2); } +void Calibr8orD_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 3); } + +void Calibr8or_handleEncoderEvent(const UI::Event &event, int index) { + // Left encoder turned + if (event.control == OC::CONTROL_ENCODER_L) Calibr8or_instance[index].OnLeftEncoderMove(event.value); + + // Right encoder turned + if (event.control == OC::CONTROL_ENCODER_R) Calibr8or_instance[index].OnRightEncoderMove(event.value); +} +void Calibr8or_handleEncoderEvent(const UI::Event &event) { + Calibr8or_handleEncoderEvent(event, 0); +} +void Calibr8orA_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 0); } +void Calibr8orB_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 1); } +void Calibr8orC_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 2); } +void Calibr8orD_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 3); } + +#endif // ENABLE_APP_CALIBR8OR diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index be73a9c82..1d5898ef1 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -37,6 +37,7 @@ const uint8_t OPEN_ICON[8] = {0x18,0x0c,0x04,0x06,0x03,0x01,0x00,0x18}; const uint8_t BTN_OFF_ICON[8] = {0x7e,0x8b,0x91,0x91,0x91,0x8b,0x7e,0x00}; const uint8_t BTN_ON_ICON[8] = {0x71,0xaa,0xa8,0xab,0xa8,0xaa,0x71,0x00}; +const uint8_t ZAP_ICON[8] = {0x00,0x08,0x9c,0x5e,0x7a,0x39,0x10,0x00}; // Transport const uint8_t LOOP_ICON[8] = {0x34,0x64,0x4e,0x4e,0xe4,0xe4,0x4c,0x58}; @@ -82,5 +83,7 @@ const uint8_t SUB_TWO[3] = {0x90,0xd0,0xa0}; // Units const uint8_t HERTZ_ICON[8] = {0xfe,0x10,0x10,0xfe,0x00,0xc8,0xa8,0x98}; +const uint8_t PLUS_ICON[8] = {0x10,0x18,0x18,0xfe,0x7f,0x18,0x18,0x08}; +const uint8_t MINUS_ICON[8] = {0x10,0x18,0x18,0x18,0x18,0x18,0x18,0x08}; #endif // HS_ICON_SET diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 9d4ec0278..d1bdcc183 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -36,6 +36,15 @@ } OC::App available_apps[] = { + #ifdef ENABLE_CALIBR8OR_X4 + DECLARE_APP('C','1', "Calibr8or A", Calibr8orA), + DECLARE_APP('C','2', "Calibr8or B", Calibr8orB), + DECLARE_APP('C','4', "Calibr8or C", Calibr8orC), + DECLARE_APP('C','8', "Calibr8or D", Calibr8orD), + #elif defined(ENABLE_APP_CALIBR8OR) + DECLARE_APP('C','1', "Calibr8or", Calibr8or), + #endif + DECLARE_APP('H','S', "Hemisphere", HEMISPHERE), #ifdef ENABLE_APP_QUANTERMAIN DECLARE_APP('Q','Q', "Quantermain", QQ), diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 5c19fea0c..cd7120cc5 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -10,11 +10,9 @@ [platformio] src_dir = . -default_envs = - oc_prod - oc_prod_vor - oc_prod_flipped - oc_stock +default_envs = + oc_prod + cal8 [env] platform = teensy@4.17.0 @@ -31,9 +29,16 @@ build_src_filter = upload_protocol = teensy-gui +[env:cal8] +build_flags = + ${env.build_flags} + -DENABLE_CALIBR8OR_X4 + -DOC_VERSION_EXTRA="\" CAL8-0316\"" + [env:oc_prod] build_flags = ${env.build_flags} + -DENABLE_APP_CALIBR8OR -DENABLE_APP_ENIGMA -DENABLE_APP_MIDI -DENABLE_APP_NEURAL_NETWORK From 617dd46b4fbb75a62747c87b5ae2b982c8e45d0f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 14 Mar 2023 16:27:59 -0400 Subject: [PATCH 174/417] Add Piqued app from stock O_C enabled with build flag ENABLE_APP_PIQUED --- software/o_c_REV/APP_ENVGEN.ino | 1262 +++++++++++++++++++++++++++++++ software/o_c_REV/OC_apps.ino | 4 + 2 files changed, 1266 insertions(+) create mode 100644 software/o_c_REV/APP_ENVGEN.ino diff --git a/software/o_c_REV/APP_ENVGEN.ino b/software/o_c_REV/APP_ENVGEN.ino new file mode 100644 index 000000000..0441bd509 --- /dev/null +++ b/software/o_c_REV/APP_ENVGEN.ino @@ -0,0 +1,1262 @@ +// +// Initial app implementation: Patrick Dowling (pld@gurkenkiste.com) +// Modifications by: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Quad enevelope generator app, based on the multistage envelope implementation +// from Peaks by Emilie Gillet (see peaks_multistage_envelope.h/cpp) + +#ifdef ENABLE_APP_PIQUED + +#include "OC_apps.h" +#include "OC_bitmaps.h" +#include "OC_digital_inputs.h" +#include "OC_menus.h" +#include "OC_strings.h" +#include "util/util_math.h" +#include "util/util_settings.h" +#include "peaks_multistage_envelope.h" +#include "bjorklund.h" +#include "OC_euclidean_mask_draw.h" + +// peaks::MultistageEnvelope allow setting of more parameters per stage, but +// that will involve more editing code, so keeping things simple for now +// with one value per stage. +// +// MultistageEnvelope maps times to lut_env_increments directly, so only 256 discrete values (no interpolation) +// Levels are 0-32767 to be positive on Peaks' bipolar output + +enum EnvelopeSettings { + ENV_SETTING_TYPE, + ENV_SETTING_SEG1_VALUE, + ENV_SETTING_SEG2_VALUE, + ENV_SETTING_SEG3_VALUE, + ENV_SETTING_SEG4_VALUE, + ENV_SETTING_TRIGGER_INPUT, + ENV_SETTING_TRIGGER_DELAY_MODE, + ENV_SETTING_TRIGGER_DELAY_COUNT, + ENV_SETTING_TRIGGER_DELAY_MILLISECONDS, + ENV_SETTING_TRIGGER_DELAY_SECONDS, + ENV_SETTING_EUCLIDEAN_LENGTH, + ENV_SETTING_EUCLIDEAN_FILL, + ENV_SETTING_EUCLIDEAN_OFFSET, + ENV_SETTING_EUCLIDEAN_RESET_INPUT, + ENV_SETTING_EUCLIDEAN_RESET_CLOCK_DIV, + ENV_SETTING_CV1, + ENV_SETTING_CV2, + ENV_SETTING_CV3, + ENV_SETTING_CV4, + ENV_SETTING_ATTACK_RESET_BEHAVIOUR, + ENV_SETTING_ATTACK_FALLING_GATE_BEHAVIOUR, + ENV_SETTING_DECAY_RELEASE_RESET_BEHAVIOUR, + ENV_SETTING_GATE_HIGH, + ENV_SETTING_ATTACK_SHAPE, + ENV_SETTING_DECAY_SHAPE, + ENV_SETTING_RELEASE_SHAPE, + ENV_SETTING_ATTACK_TIME_MULTIPLIER, + ENV_SETTING_DECAY_TIME_MULTIPLIER, + ENV_SETTING_RELEASE_TIME_MULTIPLIER, + ENV_SETTING_AMPLITUDE, + ENV_SETTING_SAMPLED_AMPLITUDE, + ENV_SETTING_MAX_LOOPS, + ENV_SETTING_INVERTED, + ENV_SETTING_LAST +}; + +enum CVMapping { + CV_MAPPING_NONE, + CV_MAPPING_SEG1, + CV_MAPPING_SEG2, + CV_MAPPING_SEG3, + CV_MAPPING_SEG4, + CV_MAPPING_ADR, + CV_MAPPING_EUCLIDEAN_LENGTH, + CV_MAPPING_EUCLIDEAN_FILL, + CV_MAPPING_EUCLIDEAN_OFFSET, + CV_MAPPING_DELAY_MSEC, + CV_MAPPING_AMPLITUDE, + CV_MAPPING_MAX_LOOPS, + CV_MAPPING_LAST +}; + +enum EnvelopeType { + ENV_TYPE_AD, + ENV_TYPE_ADSR, + ENV_TYPE_ADR, + ENV_TYPE_AR, + ENV_TYPE_ADSAR, + ENV_TYPE_ADAR, + ENV_TYPE_ADL2, + ENV_TYPE_ADRL3, + ENV_TYPE_ADL2R, + ENV_TYPE_ADAL2R, + ENV_TYPE_ADARL4, + ENV_TYPE_LAST, ENV_TYPE_FIRST = ENV_TYPE_AD +}; + +enum TriggerDelayMode { + TRIGGER_DELAY_OFF, + TRIGGER_DELAY_QUEUE, // Queue up to kMaxDelayedTriggers delays, additional triggers ignored + TRIGGER_DELAY_RING, // Queue up to kMaxDelayedTriggers delays, additional triggers overwrite oldest + TRIGGER_DELAY_LAST +}; + +// With only one type, the U4 setting size still works. Each of the these maps to 3 values in the +// setting, 1 for each channel other than the current. +// Ordering here ideally maps to peaks::EnvStateBitMask shifts +enum IntTriggerType { + INT_TRIGGER_EOC, + INT_TRIGGER_LAST +}; + +inline int TriggerSettingToChannel(int setting_value) __attribute__((always_inline)); +inline int TriggerSettingToChannel(int setting_value) { + return (setting_value - OC::DIGITAL_INPUT_LAST) / INT_TRIGGER_LAST; +} + +/* redefined as a macro because of compiler complaints -NJM +inline IntTriggerType TriggerSettingToType(int setting_value, int channel) __attribute__((always_inline)); +inline IntTriggerType TriggerSettingToType(int setting_value, int channel) { + return static_cast((setting_value - OC::DIGITAL_INPUT_LAST) - channel * INT_TRIGGER_LAST); +} +*/ +#define TriggerSettingToType(setting_value, channel) static_cast((setting_value - OC::DIGITAL_INPUT_LAST) - channel * INT_TRIGGER_LAST) + +namespace menu = OC::menu; + +class EnvelopeGenerator : public settings::SettingsBase { +public: + + static constexpr int kMaxSegments = 4; + static constexpr int kEuclideanParams = 3; + static constexpr int kDelayParams = 1; + static constexpr int kAmplitudeParams = 2; // incremented to 2 to cover the MAX_LOOPS parameter + static constexpr size_t kMaxDelayedTriggers = 24; + + struct DelayedTrigger { + uint32_t delay; + uint32_t time_left; + + inline void Activate(uint32_t t) { + delay = time_left = t; + } + + inline void Reset() { + delay = time_left = 0; + } + }; + + void Init(OC::DigitalInput default_trigger); + + EnvelopeType get_type() const { + return static_cast(values_[ENV_SETTING_TYPE]); + } + + int get_trigger_input() const { + return values_[ENV_SETTING_TRIGGER_INPUT]; + } + + int32_t get_trigger_delay_ms() const { + return 1000U * values_[ENV_SETTING_TRIGGER_DELAY_SECONDS] + values_[ENV_SETTING_TRIGGER_DELAY_MILLISECONDS] ; + } + + TriggerDelayMode get_trigger_delay_mode() const { + return static_cast(values_[ENV_SETTING_TRIGGER_DELAY_MODE]); + } + + peaks::EnvResetBehaviour get_attack_reset_behaviour() const { + return static_cast(values_[ENV_SETTING_ATTACK_RESET_BEHAVIOUR]); + } + + peaks::EnvFallingGateBehaviour get_attack_falling_gate_behaviour() const { + return static_cast(values_[ENV_SETTING_ATTACK_FALLING_GATE_BEHAVIOUR]); + } + + peaks::EnvResetBehaviour get_decay_release_reset_behaviour() const { + return static_cast(values_[ENV_SETTING_DECAY_RELEASE_RESET_BEHAVIOUR]); + } + + uint8_t get_euclidean_length() const { + return values_[ENV_SETTING_EUCLIDEAN_LENGTH]; + } + + uint8_t get_euclidean_fill() const { + return values_[ENV_SETTING_EUCLIDEAN_FILL]; + } + + uint8_t get_euclidean_offset() const { + return values_[ENV_SETTING_EUCLIDEAN_OFFSET]; + } + + uint8_t get_euclidean_reset_trigger_input() const { + return values_[ENV_SETTING_EUCLIDEAN_RESET_INPUT]; + } + + uint8_t get_euclidean_reset_clock_div() const { + return values_[ENV_SETTING_EUCLIDEAN_RESET_CLOCK_DIV]; + } + + uint32_t get_euclidean_counter() const { + return euclidean_counter_; + } + + uint16_t get_amplitude() const { + return values_[ENV_SETTING_AMPLITUDE] << 9 ; + } + + bool is_amplitude_sampled() const { + return static_cast(values_[ENV_SETTING_SAMPLED_AMPLITUDE]); + } + + uint16_t get_max_loops() const { + return values_[ENV_SETTING_MAX_LOOPS] << 9 ; + } + + bool is_inverted() const { + return static_cast(values_[ENV_SETTING_INVERTED]); + } + + uint8_t get_s_euclidean_length() const { + return s_euclidean_length_; + } + + uint8_t get_s_euclidean_fill() const { + return s_euclidean_fill_; + } + + uint8_t get_s_euclidean_offset() const { + return s_euclidean_offset_; + } + + uint32_t get_trigger_delay_count() const { + return values_[ENV_SETTING_TRIGGER_DELAY_COUNT]; + } + + CVMapping get_cv1_mapping() const { + return static_cast(values_[ENV_SETTING_CV1]); + } + + CVMapping get_cv2_mapping() const { + return static_cast(values_[ENV_SETTING_CV2]); + } + + CVMapping get_cv3_mapping() const { + return static_cast(values_[ENV_SETTING_CV3]); + } + + CVMapping get_cv4_mapping() const { + return static_cast(values_[ENV_SETTING_CV4]); + } + +// bool get_hard_reset() const { +// return values_[ENV_SETTING_HARD_RESET]; +// } + + bool get_gate_high() const { + return values_[ENV_SETTING_GATE_HIGH]; + } + + peaks::EnvelopeShape get_attack_shape() const { + return static_cast(values_[ENV_SETTING_ATTACK_SHAPE]); + } + + peaks::EnvelopeShape get_decay_shape() const { + return static_cast(values_[ENV_SETTING_DECAY_SHAPE]); + } + + peaks::EnvelopeShape get_release_shape() const { + return static_cast(values_[ENV_SETTING_RELEASE_SHAPE]); + } + + uint16_t get_attack_time_multiplier() const { + return static_cast(values_[ENV_SETTING_ATTACK_TIME_MULTIPLIER]); + } + + uint16_t get_decay_time_multiplier() const { + return static_cast(values_[ENV_SETTING_DECAY_TIME_MULTIPLIER]); + } + + uint16_t get_release_time_multiplier() const { + return static_cast(values_[ENV_SETTING_RELEASE_TIME_MULTIPLIER]); + } + + // Utils + uint16_t get_segment_value(int segment) const { + return values_[ENV_SETTING_SEG1_VALUE + segment]; + } + + int num_editable_segments() const { + switch (get_type()) { + case ENV_TYPE_AD: + case ENV_TYPE_AR: + case ENV_TYPE_ADL2: + return 2; + case ENV_TYPE_ADR: + case ENV_TYPE_ADSR: + case ENV_TYPE_ADSAR: + case ENV_TYPE_ADAR: + case ENV_TYPE_ADRL3: + case ENV_TYPE_ADL2R: + case ENV_TYPE_ADARL4: + case ENV_TYPE_ADAL2R: + return 4; + default: break; + } + return 0; + } + + inline void apply_cv_mapping(EnvelopeSettings cv_setting, const int32_t cvs[ADC_CHANNEL_LAST], int32_t segments[CV_MAPPING_LAST]) { + // segments is indexed directly with CVMapping enum values + int mapping = values_[cv_setting]; + switch (mapping) { + case CV_MAPPING_SEG1: + case CV_MAPPING_SEG2: + case CV_MAPPING_SEG3: + case CV_MAPPING_SEG4: + segments[mapping] += (cvs[cv_setting - ENV_SETTING_CV1] * 65536) >> 12; + break; + case CV_MAPPING_ADR: + segments[CV_MAPPING_SEG1] += (cvs[cv_setting - ENV_SETTING_CV1] * 65536) >> 12; + segments[CV_MAPPING_SEG2] += (cvs[cv_setting - ENV_SETTING_CV1] * 65536) >> 12; + segments[CV_MAPPING_SEG4] += (cvs[cv_setting - ENV_SETTING_CV1] * 65536) >> 12; + break; + case CV_MAPPING_EUCLIDEAN_LENGTH: + case CV_MAPPING_EUCLIDEAN_FILL: + case CV_MAPPING_EUCLIDEAN_OFFSET: + segments[mapping] += cvs[cv_setting - ENV_SETTING_CV1] >> 6; + break; + case CV_MAPPING_DELAY_MSEC: + segments[mapping] += cvs[cv_setting - ENV_SETTING_CV1] >> 2; + break; + case CV_MAPPING_AMPLITUDE: + segments[mapping] += cvs[cv_setting - ENV_SETTING_CV1] << 5 ; + break; + case CV_MAPPING_MAX_LOOPS: + segments[mapping] += cvs[cv_setting - ENV_SETTING_CV1] << 2 ; + break; + default: + break; + } + } + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + EnvelopeSettings enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + void update_enabled_settings() { + EnvelopeSettings *settings = enabled_settings_; + + *settings++ = ENV_SETTING_TYPE; + *settings++ = ENV_SETTING_TRIGGER_INPUT; + *settings++ = ENV_SETTING_TRIGGER_DELAY_MODE; + if (get_trigger_delay_mode()) { + *settings++ = ENV_SETTING_TRIGGER_DELAY_COUNT; + *settings++ = ENV_SETTING_TRIGGER_DELAY_MILLISECONDS; + *settings++ = ENV_SETTING_TRIGGER_DELAY_SECONDS; + } + + *settings++ = ENV_SETTING_EUCLIDEAN_LENGTH; + if (get_euclidean_length()) { + //*settings++ = ENV_SETTING_EUCLIDEAN_FILL; + *settings++ = ENV_SETTING_EUCLIDEAN_OFFSET; + *settings++ = ENV_SETTING_EUCLIDEAN_RESET_INPUT; + *settings++ = ENV_SETTING_EUCLIDEAN_RESET_CLOCK_DIV; + } + + *settings++ = ENV_SETTING_ATTACK_SHAPE; + *settings++ = ENV_SETTING_DECAY_SHAPE; + *settings++ = ENV_SETTING_RELEASE_SHAPE; + + *settings++ = ENV_SETTING_ATTACK_TIME_MULTIPLIER; + *settings++ = ENV_SETTING_DECAY_TIME_MULTIPLIER; + *settings++ = ENV_SETTING_RELEASE_TIME_MULTIPLIER; + + *settings++ = ENV_SETTING_CV1; + *settings++ = ENV_SETTING_CV2; + *settings++ = ENV_SETTING_CV3; + *settings++ = ENV_SETTING_CV4; + *settings++ = ENV_SETTING_ATTACK_RESET_BEHAVIOUR; + *settings++ = ENV_SETTING_ATTACK_FALLING_GATE_BEHAVIOUR; + *settings++ = ENV_SETTING_DECAY_RELEASE_RESET_BEHAVIOUR; + *settings++ = ENV_SETTING_GATE_HIGH; + *settings++ = ENV_SETTING_AMPLITUDE; + *settings++ = ENV_SETTING_SAMPLED_AMPLITUDE; + *settings++ = ENV_SETTING_MAX_LOOPS; + *settings++ = ENV_SETTING_INVERTED; + + num_enabled_settings_ = settings - enabled_settings_; + } + + static bool indentSetting(EnvelopeSettings setting) { + switch (setting) { + case ENV_SETTING_TRIGGER_DELAY_COUNT: + case ENV_SETTING_TRIGGER_DELAY_SECONDS: + case ENV_SETTING_TRIGGER_DELAY_MILLISECONDS: + case ENV_SETTING_EUCLIDEAN_FILL: + case ENV_SETTING_EUCLIDEAN_OFFSET: + case ENV_SETTING_EUCLIDEAN_RESET_INPUT: + case ENV_SETTING_EUCLIDEAN_RESET_CLOCK_DIV: + return true; + default: + break; + } + return false; + } + + template + void Update(uint32_t triggers, uint32_t internal_trigger_mask, const int32_t cvs[ADC_CHANNEL_LAST]) { + int32_t s[CV_MAPPING_LAST]; + s[CV_MAPPING_NONE] = 0; // unused, but needs a placeholder to align with enum CVMapping + s[CV_MAPPING_SEG1] = SCALE8_16(static_cast(get_segment_value(0))); + s[CV_MAPPING_SEG2] = SCALE8_16(static_cast(get_segment_value(1))); + s[CV_MAPPING_SEG3] = SCALE8_16(static_cast(get_segment_value(2))); + s[CV_MAPPING_SEG4] = SCALE8_16(static_cast(get_segment_value(3))); + s[CV_MAPPING_ADR] = 0; // unused, but needs a placeholder to align with enum CVMapping + s[CV_MAPPING_EUCLIDEAN_LENGTH] = static_cast(get_euclidean_length()); + s[CV_MAPPING_EUCLIDEAN_FILL] = static_cast(get_euclidean_fill()); + s[CV_MAPPING_EUCLIDEAN_OFFSET] = static_cast(get_euclidean_offset()); + s[CV_MAPPING_DELAY_MSEC] = get_trigger_delay_ms(); + s[CV_MAPPING_AMPLITUDE] = get_amplitude(); + s[CV_MAPPING_MAX_LOOPS] = get_max_loops(); + + apply_cv_mapping(ENV_SETTING_CV1, cvs, s); + apply_cv_mapping(ENV_SETTING_CV2, cvs, s); + apply_cv_mapping(ENV_SETTING_CV3, cvs, s); + apply_cv_mapping(ENV_SETTING_CV4, cvs, s); + + s[CV_MAPPING_SEG1] = USAT16(s[CV_MAPPING_SEG1]); + s[CV_MAPPING_SEG2] = USAT16(s[CV_MAPPING_SEG2]); + s[CV_MAPPING_SEG3] = USAT16(s[CV_MAPPING_SEG3]); + s[CV_MAPPING_SEG4] = USAT16(s[CV_MAPPING_SEG4]); + CONSTRAIN(s[CV_MAPPING_EUCLIDEAN_LENGTH], 0, 31); + CONSTRAIN(s[CV_MAPPING_EUCLIDEAN_FILL], 0, 32); + CONSTRAIN(s[CV_MAPPING_EUCLIDEAN_OFFSET], 0, 32); + CONSTRAIN(s[CV_MAPPING_DELAY_MSEC], 0, 65535); + #ifdef VOR + CONSTRAIN(s[CV_MAPPING_AMPLITUDE], 0, 62850); + #else + CONSTRAIN(s[CV_MAPPING_AMPLITUDE], 0, 65535); + #endif + CONSTRAIN(s[CV_MAPPING_MAX_LOOPS], 0, 65535); + + EnvelopeType type = get_type(); + switch (type) { + case ENV_TYPE_AD: env_.set_ad(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], 0, 0); break; + case ENV_TYPE_ADSR: env_.set_adsr(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4]); break; + case ENV_TYPE_ADR: env_.set_adr(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4], 0, 0 ); break; + case ENV_TYPE_AR: env_.set_ar(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2]); break; + case ENV_TYPE_ADSAR: env_.set_adsar(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4]); break; + case ENV_TYPE_ADAR: env_.set_adar(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4], 0, 0); break; + case ENV_TYPE_ADL2: env_.set_ad(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], 0, 2); break; + case ENV_TYPE_ADRL3: env_.set_adr(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4], 0, 3); break; + case ENV_TYPE_ADL2R: env_.set_adr(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4], 0, 2); break; + case ENV_TYPE_ADARL4: env_.set_adar(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4], 0, 4); break; + case ENV_TYPE_ADAL2R: env_.set_adar(s[CV_MAPPING_SEG1], s[CV_MAPPING_SEG2], s[CV_MAPPING_SEG3]>>1, s[CV_MAPPING_SEG4], 1, 3); break; // was 2, 4 + default: + break; + } + + // set the amplitude + env_.set_amplitude(s[CV_MAPPING_AMPLITUDE], is_amplitude_sampled()) ; + + if (type != last_type_) { + last_type_ = type; + env_.reset(); + } + + // set the specified reset behaviours + env_.set_attack_reset_behaviour(get_attack_reset_behaviour()); + env_.set_attack_falling_gate_behaviour(get_attack_falling_gate_behaviour()); + env_.set_decay_release_reset_behaviour(get_decay_release_reset_behaviour()); + + // set the envelope segment shapes + env_.set_attack_shape(get_attack_shape()); + env_.set_decay_shape(get_decay_shape()); + env_.set_release_shape(get_release_shape()); + + // set the envelope segment time multipliers + env_.set_attack_time_multiplier(get_attack_time_multiplier()); + env_.set_decay_time_multiplier(get_decay_time_multiplier()); + env_.set_release_time_multiplier(get_release_time_multiplier()); + + // set the looping envelope maximum number of loops + env_.set_max_loops(s[CV_MAPPING_MAX_LOOPS]); + + int trigger_input = get_trigger_input(); + bool triggered = false; + bool gate_raised = false; + if (trigger_input < OC::DIGITAL_INPUT_LAST) { + triggered = triggers & DIGITAL_INPUT_MASK(trigger_input); + gate_raised = OC::DigitalInputs::read_immediate(static_cast(trigger_input)); + } else { + const int trigger_channel = TriggerSettingToChannel(trigger_input); + const IntTriggerType trigger_type = TriggerSettingToType(trigger_input, trigger_channel); + + triggered = (internal_trigger_mask >> (trigger_setting_to_channel_index(trigger_channel) * 8)) & (0x1 << trigger_type); + gate_raised = triggered; + } + + trigger_display_.Update(1, triggered || gate_raised_); + + if (triggered) ++euclidean_counter_; + uint8_t euclidean_length = static_cast(s[CV_MAPPING_EUCLIDEAN_LENGTH]); + uint8_t euclidean_fill = static_cast(s[CV_MAPPING_EUCLIDEAN_FILL]); + uint8_t euclidean_offset = static_cast(s[CV_MAPPING_EUCLIDEAN_OFFSET]); + + // Process Euclidean pattern reset + uint8_t euclidean_reset_trigger_input = get_euclidean_reset_trigger_input(); + if (euclidean_reset_trigger_input) { + if (triggers & DIGITAL_INPUT_MASK(static_cast(euclidean_reset_trigger_input - 1))) { + ++euclidean_reset_counter_; + if (euclidean_reset_counter_ >= get_euclidean_reset_clock_div()) { + euclidean_counter_ = 0; + euclidean_reset_counter_= 0; + } + } + } + + if (triggered && get_euclidean_length() && !EuclideanFilter(euclidean_length, euclidean_fill, euclidean_offset, euclidean_counter_)) { + triggered = false; + } + + s_euclidean_length_ = euclidean_length; + s_euclidean_fill_ = euclidean_fill; + s_euclidean_offset_ = euclidean_offset; + + if (triggered) { + TriggerDelayMode delay_mode = get_trigger_delay_mode(); + // uint32_t delay = get_trigger_delay_ms() * 1000U; + uint32_t delay = static_cast(s[CV_MAPPING_DELAY_MSEC] * 1000U); + if (delay_mode && delay) { + triggered = false; + if (TRIGGER_DELAY_QUEUE == delay_mode) { + if (delayed_triggers_free_ < get_trigger_delay_count()) + delayed_triggers_[delayed_triggers_free_].Activate(delay); + } else { // TRIGGER_DELAY_RING + // Assume these are mostly in order, so the "next" is also the oldest + if (delayed_triggers_free_ < get_trigger_delay_count()) + delayed_triggers_[delayed_triggers_free_].Activate(delay); + else + delayed_triggers_[delayed_triggers_next_].Activate(delay); + } + } + } + + if (DelayedTriggers()) + triggered = true; + + uint8_t gate_state = 0; + if (triggered) + gate_state |= peaks::CONTROL_GATE_RISING; + + if (gate_raised || get_gate_high()) + gate_state |= peaks::CONTROL_GATE; + else if (gate_raised_) + gate_state |= peaks::CONTROL_GATE_FALLING; + gate_raised_ = gate_raised; + + // TODO Scale range or offset? + uint32_t value ; + if (!is_inverted()) + value = OC::DAC::get_zero_offset(dac_channel) + env_.ProcessSingleSample(gate_state); + else + value = OC::DAC::get_zero_offset(dac_channel) + 32767 - env_.ProcessSingleSample(gate_state); + + OC::DAC::set(value); + } + + uint16_t RenderPreview(int16_t *values, uint16_t *segment_start_points, uint16_t *loop_points, uint16_t ¤t_phase) const { + return env_.RenderPreview(values, segment_start_points, loop_points, current_phase); + } + + uint16_t RenderFastPreview(int16_t *values) const { + return env_.RenderFastPreview(values); + } + + uint8_t getTriggerState() const { + return trigger_display_.getState(); + } + + inline void get_next_trigger(DelayedTrigger &trigger) const { + trigger = delayed_triggers_[delayed_triggers_next_]; + } + +#ifdef ENVGEN_DEBUG + inline uint16_t get_amplitude_value() { + return(env_.get_amplitude_value()) ; + } + + inline uint16_t get_sampled_amplitude_value() { + return(env_.get_sampled_amplitude_value()) ; + } + + inline bool get_is_amplitude_sampled() { + return(env_.get_is_amplitude_sampled()) ; + } +#endif + + inline int trigger_setting_to_channel_index(int s) const { + return s < channel_index_ ? s : s + 1; + } + + uint32_t internal_trigger_mask() const { + return env_.get_state_mask(); + } + +private: + + int channel_index_; + + peaks::MultistageEnvelope env_; + EnvelopeType last_type_; + bool gate_raised_; + uint32_t euclidean_counter_; + uint32_t euclidean_reset_counter_; + + // debug/live-view only + uint8_t s_euclidean_length_; + uint8_t s_euclidean_fill_; + uint8_t s_euclidean_offset_; + + DelayedTrigger delayed_triggers_[kMaxDelayedTriggers]; + size_t delayed_triggers_free_; + size_t delayed_triggers_next_; + + int num_enabled_settings_; + EnvelopeSettings enabled_settings_[ENV_SETTING_LAST]; + + OC::DigitalInputDisplay trigger_display_; + + bool DelayedTriggers() { + bool triggered = false; + + delayed_triggers_free_ = kMaxDelayedTriggers; + delayed_triggers_next_ = 0; + uint32_t min_time_left = -1; + + size_t i = kMaxDelayedTriggers; + while (i--) { + DelayedTrigger &trigger = delayed_triggers_[i]; + uint32_t time_left = trigger.time_left; + if (time_left) { + if (time_left > OC_CORE_TIMER_RATE) { + time_left -= OC_CORE_TIMER_RATE; + if (time_left < min_time_left) { + min_time_left = time_left; + delayed_triggers_next_ = i; + } + trigger.time_left = time_left; + } else { + trigger.Reset(); + delayed_triggers_free_ = i; + triggered = true; + } + } else { + delayed_triggers_free_ = i; + } + } + + return triggered; + } +}; + +void EnvelopeGenerator::Init(OC::DigitalInput default_trigger) { + InitDefaults(); + apply_value(ENV_SETTING_TRIGGER_INPUT, default_trigger); + env_.Init(); + channel_index_ = default_trigger; + last_type_ = ENV_TYPE_LAST; + gate_raised_ = false; + euclidean_counter_ = 0; + euclidean_reset_counter_ = 0; + + memset(delayed_triggers_, 0, sizeof(delayed_triggers_)); + delayed_triggers_free_ = delayed_triggers_next_ = 0; + + trigger_display_.Init(); + + update_enabled_settings(); +} + +const char* const envelope_types[ENV_TYPE_LAST] = { + "AD", "ADSR", "ADR", "ASR", "ADSAR", "ADAR", "ADL2", "ADRL3", "ADL2R", "ADAL2R", "ADARL4" +}; + +const char* const segment_names[] = { + "Attack", "Decay", "Sustain/Level", "Release" +}; + +const char* const cv_mapping_names[CV_MAPPING_LAST] = { + "None", "Att", "Dec", "Sus", "Rel", "ADR", "Eleng", "Efill", "Eoffs", "Delay", "Ampl", "Loops" +}; + +const char* const trigger_delay_modes[TRIGGER_DELAY_LAST] = { + "Off", "Queue", "Ring" +}; + +const char* const euclidean_lengths[] = { + "Off", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", " 10", + " 11", " 12", " 13", " 14", " 15", " 16", " 17", " 18", " 19", " 20", + " 21", " 22", " 23", " 24", " 25", " 26", " 27", " 28", " 29", " 30", + " 31", " 32", +}; + +const char* const time_multipliers[] = { + "1", " 2", " 4", " 8", " 16", " 32", " 64", " 128", " 256", " 512", "1024", "2048", "4096", "8192" +}; + +const char* const internal_trigger_types[INT_TRIGGER_LAST] = { + "EOC", // Keep length == 3 +}; + +SETTINGS_DECLARE(EnvelopeGenerator, ENV_SETTING_LAST) { + { ENV_TYPE_AD, ENV_TYPE_FIRST, ENV_TYPE_LAST-1, "TYPE", envelope_types, settings::STORAGE_TYPE_U8 }, + { 128, 0, 255, "S1", NULL, settings::STORAGE_TYPE_U16 }, // u16 in case resolution proves insufficent + { 128, 0, 255, "S2", NULL, settings::STORAGE_TYPE_U16 }, + { 128, 0, 255, "S3", NULL, settings::STORAGE_TYPE_U16 }, + { 128, 0, 255, "S4", NULL, settings::STORAGE_TYPE_U16 }, + { OC::DIGITAL_INPUT_1, OC::DIGITAL_INPUT_1, OC::DIGITAL_INPUT_4 + 3 * INT_TRIGGER_LAST, "Trigger input", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U4 }, + { TRIGGER_DELAY_OFF, TRIGGER_DELAY_OFF, TRIGGER_DELAY_LAST - 1, "Tr delay mode", trigger_delay_modes, settings::STORAGE_TYPE_U4 }, + { 1, 1, EnvelopeGenerator::kMaxDelayedTriggers, "Tr delay count", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 999, "Tr delay msecs", NULL, settings::STORAGE_TYPE_U16 }, + { 0, 0, 64, "Tr delay secs", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 31, "Eucl length", euclidean_lengths, settings::STORAGE_TYPE_U8 }, + { 1, 0, 32, "Fill", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 32, "Offset", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 4, "Eucl reset", OC::Strings::trigger_input_names_none, settings::STORAGE_TYPE_U8 }, + { 1, 1, 255, "Eucl reset div", NULL, settings::STORAGE_TYPE_U8 }, + { CV_MAPPING_NONE, CV_MAPPING_NONE, CV_MAPPING_LAST - 1, "CV1 -> ", cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { CV_MAPPING_NONE, CV_MAPPING_NONE, CV_MAPPING_LAST - 1, "CV2 -> ", cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { CV_MAPPING_NONE, CV_MAPPING_NONE, CV_MAPPING_LAST - 1, "CV3 -> ", cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { CV_MAPPING_NONE, CV_MAPPING_NONE, CV_MAPPING_LAST - 1, "CV4 -> ", cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { peaks::RESET_BEHAVIOUR_NULL, peaks::RESET_BEHAVIOUR_NULL, peaks::RESET_BEHAVIOUR_LAST - 1, "Attack reset", OC::Strings::reset_behaviours, settings::STORAGE_TYPE_U4 }, + { peaks::FALLING_GATE_BEHAVIOUR_IGNORE, peaks::FALLING_GATE_BEHAVIOUR_IGNORE, peaks::FALLING_GATE_BEHAVIOUR_LAST - 1, "Att fall gt", OC::Strings::falling_gate_behaviours, settings::STORAGE_TYPE_U8 }, + { peaks::RESET_BEHAVIOUR_SEGMENT_PHASE, peaks::RESET_BEHAVIOUR_NULL, peaks::RESET_BEHAVIOUR_LAST - 1, "DecRel reset", OC::Strings::reset_behaviours, settings::STORAGE_TYPE_U4 }, + { 0, 0, 1, "Gate high", OC::Strings::no_yes, settings::STORAGE_TYPE_U4 }, + { peaks::ENV_SHAPE_QUARTIC, peaks::ENV_SHAPE_LINEAR, peaks::ENV_SHAPE_LAST - 1, "Attack shape", OC::Strings::envelope_shapes, settings::STORAGE_TYPE_U4 }, + { peaks::ENV_SHAPE_EXPONENTIAL, peaks::ENV_SHAPE_LINEAR, peaks::ENV_SHAPE_LAST - 1, "Decay shape", OC::Strings::envelope_shapes, settings::STORAGE_TYPE_U4 }, + { peaks::ENV_SHAPE_EXPONENTIAL, peaks::ENV_SHAPE_LINEAR, peaks::ENV_SHAPE_LAST - 1, "Release shape", OC::Strings::envelope_shapes, settings::STORAGE_TYPE_U4 }, + { 0, 0, 13, "Attack mult", time_multipliers, settings::STORAGE_TYPE_U4 }, + { 0, 0, 13, "Decay mult", time_multipliers, settings::STORAGE_TYPE_U4 }, + {0, 0, 13, "Release mult", time_multipliers, settings::STORAGE_TYPE_U4 }, + {127, 0, 127, "Amplitude", NULL, settings::STORAGE_TYPE_U8 }, + {0, 0, 1, "Sampled Ampl", OC::Strings::no_yes, settings::STORAGE_TYPE_U4 }, + {0, 0, 127, "Max loops", NULL, settings::STORAGE_TYPE_U8 }, + {0, 0, 1, "Inverted", OC::Strings::no_yes, settings::STORAGE_TYPE_U8 }, +}; + +class QuadEnvelopeGenerator { +public: + static constexpr int32_t kCvSmoothing = 16; + + void Init() { + int input = OC::DIGITAL_INPUT_1; + for (auto &env : envelopes_) { + env.Init(static_cast(input)); + ++input; + } + + ui.edit_mode = MODE_EDIT_SEGMENTS; + ui.selected_channel = 0; + ui.selected_segment = 0; + ui.segment_editing = false; + ui.cursor.Init(0, envelopes_[0].num_enabled_settings() - 1); + ui.euclidean_mask_draw.Init(); + ui.euclidean_edit_length = false; + } + + void ISR() { + cv1.push(OC::ADC::value()); + cv2.push(OC::ADC::value()); + cv3.push(OC::ADC::value()); + cv4.push(OC::ADC::value()); + + const int32_t cvs[ADC_CHANNEL_LAST] = { cv1.value(), cv2.value(), cv3.value(), cv4.value() }; + uint32_t triggers = OC::DigitalInputs::clocked(); + + uint32_t internal_trigger_mask = + envelopes_[0].internal_trigger_mask() | + envelopes_[1].internal_trigger_mask() << 8 | + envelopes_[2].internal_trigger_mask() << 16 | + envelopes_[3].internal_trigger_mask() << 24; + + envelopes_[0].Update(triggers, internal_trigger_mask, cvs); + envelopes_[1].Update(triggers, internal_trigger_mask, cvs); + envelopes_[2].Update(triggers, internal_trigger_mask, cvs); + envelopes_[3].Update(triggers, internal_trigger_mask, cvs); + } + + bool euclidean_edit_active() const { + return + ui.cursor.editing() && + ENV_SETTING_EUCLIDEAN_OFFSET == selected().enabled_setting_at(ui.cursor.cursor_pos()); + } + + enum EnvEditMode { + MODE_EDIT_SEGMENTS, + MODE_EDIT_SETTINGS + }; + + struct { + EnvEditMode edit_mode; + + int selected_channel; + int selected_segment; + bool segment_editing; + + menu::ScreenCursor cursor; + OC::EuclideanMaskDraw euclidean_mask_draw; + bool euclidean_edit_length; + } ui; + + EnvelopeGenerator &selected() { + return envelopes_[ui.selected_channel]; + } + + const EnvelopeGenerator &selected() const { + return envelopes_[ui.selected_channel]; + } + + EnvelopeGenerator envelopes_[4]; + + SmoothedValue cv1; + SmoothedValue cv2; + SmoothedValue cv3; + SmoothedValue cv4; +}; + +QuadEnvelopeGenerator envgen; + +void ENVGEN_init() { + envgen.Init(); +} + +size_t ENVGEN_storageSize() { + return 4 * EnvelopeGenerator::storageSize(); +} + +size_t ENVGEN_save(void *storage) { + size_t s = 0; + for (auto &env : envgen.envelopes_) + s += env.Save(static_cast(storage) + s); + return s; +} + +size_t ENVGEN_restore(const void *storage) { + size_t s = 0; + for (auto &env : envgen.envelopes_) { + s += env.Restore(static_cast(storage) + s); + env.update_enabled_settings(); + } + + envgen.ui.cursor.AdjustEnd(envgen.envelopes_[0].num_enabled_settings() - 1); + return s; +} + +void ENVGEN_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void ENVGEN_loop() { +} + +static constexpr weegfx::coord_t kPreviewH = 32; +static constexpr weegfx::coord_t kPreviewTopY = 32; +static constexpr weegfx::coord_t kPreviewBottomY = 32 + kPreviewH - 1; + +static constexpr weegfx::coord_t kLoopMarkerY = 28; +static constexpr weegfx::coord_t kCurrentSegmentCursorY = 26; + +int16_t preview_values[128 + 64]; +uint16_t preview_segment_starts[peaks::kMaxNumSegments]; +uint16_t preview_loop_points[peaks::kMaxNumSegments]; +static constexpr uint16_t kPreviewTerminator = 0xffff; + +settings::value_attr segment_editing_attr = { 128, 0, 255, "DOH!", NULL, settings::STORAGE_TYPE_U16 }; + +void ENVGEN_menu_preview() { + auto const &env = envgen.selected(); + + menu::SettingsListItem list_item; + menu::SettingsList::AbsoluteLine(0, list_item); + list_item.selected = false; + list_item.editing = envgen.ui.segment_editing; + const int selected_segment = envgen.ui.selected_segment; + + segment_editing_attr.name = segment_names[selected_segment]; + list_item.DrawDefault(env.get_segment_value(selected_segment), segment_editing_attr); + + // Current envelope shape + uint16_t current_phase = 0; + weegfx::coord_t x = 0; + weegfx::coord_t w = env.RenderPreview(preview_values, preview_segment_starts, preview_loop_points, current_phase); + const int16_t *data = preview_values; + while (x <= static_cast(current_phase)) { + const int16_t value = *data++ >> 10; + graphics.drawVLine(x++, kPreviewBottomY - value, value + 1); + } + + while (x < w) { + const int16_t value = *data++ >> 10; + graphics.setPixel(x++, kPreviewBottomY - value); + } + + if (x < menu::kDisplayWidth) + graphics.drawHLine(x, kPreviewBottomY, menu::kDisplayWidth - x); + + // Minimal cursor thang (x is end of preview) + weegfx::coord_t start = preview_segment_starts[selected_segment]; + weegfx::coord_t end = preview_segment_starts[selected_segment + 1]; + w = kPreviewTerminator == end ? x - start + 1 : end - start + 1; + if (w < 4) w = 4; + graphics.drawRect(start, kCurrentSegmentCursorY, w, 2); + + // Current types only loop over full envelope, so just pixel dust + uint16_t *loop_points = preview_loop_points; + uint_fast8_t i = 0; + while (*loop_points != kPreviewTerminator) { + // odd: end marker, even: start marker + if (i++ & 1) + graphics.drawBitmap8(*loop_points++ - 1, kLoopMarkerY, OC::kBitmapLoopMarkerW, OC::bitmap_loop_markers_8 + OC::kBitmapLoopMarkerW); + else + graphics.drawBitmap8(*loop_points++, kLoopMarkerY, OC::kBitmapLoopMarkerW, OC::bitmap_loop_markers_8); + } + + // Brute-force way of handling "pathological" cases where A/D has no visible + // pixels instead of line-drawing between points + uint16_t *segment_start = preview_segment_starts; + while (*segment_start != kPreviewTerminator) { + weegfx::coord_t x = *segment_start++; + weegfx::coord_t value = preview_values[x] >> 10; + graphics.drawVLine(x, kPreviewBottomY - value, value); + } +} + +void ENVGEN_menu_settings() { + auto const &env = envgen.selected(); + + bool draw_euclidean_editor = false; + + menu::SettingsList settings_list(envgen.ui.cursor); + menu::SettingsListItem list_item; + + while (settings_list.available()) { + const int setting = + env.enabled_setting_at(settings_list.Next(list_item)); + const int value = env.get_value(setting); + const settings::value_attr &attr = EnvelopeGenerator::value_attr(setting); + + switch (setting) { + case ENV_SETTING_TYPE: + list_item.SetPrintPos(); + if (list_item.editing) { + menu::DrawEditIcon(6, list_item.y, value, attr); + graphics.movePrintPos(6, 0); + } + graphics.print(attr.value_names[value]); + list_item.DrawCustom(); + break; + case ENV_SETTING_TRIGGER_INPUT: + if (EnvelopeGenerator::indentSetting(static_cast(setting))) + list_item.x += menu::kIndentDx; + if (value < OC::DIGITAL_INPUT_LAST) { + list_item.DrawDefault(value, attr); + } else { + const int trigger_channel = TriggerSettingToChannel(value); + const IntTriggerType trigger_type = TriggerSettingToType(value, trigger_channel); + + char s[6] = "_ xxx"; + s[0] = 'A' + env.trigger_setting_to_channel_index(trigger_channel); + memcpy(s + 2, internal_trigger_types[trigger_type], 3); + list_item.DrawDefault(s, value, attr); + } + break; + case ENV_SETTING_EUCLIDEAN_OFFSET: + if (!list_item.editing) { + // Use the live values + envgen.ui.euclidean_mask_draw.Render(menu::kDisplayWidth, list_item.y, + env.get_s_euclidean_length(), env.get_s_euclidean_fill(), env.get_s_euclidean_offset(), + env.get_euclidean_counter()); + list_item.DrawCustom(); + } else { + draw_euclidean_editor = true; + } + break; + + default: + if (EnvelopeGenerator::indentSetting(static_cast(setting))) + list_item.x += menu::kIndentDx; + list_item.DrawDefault(value, attr); + break; + } + } + + // Ugly. With a capital blargh. + if (draw_euclidean_editor) { + weegfx::coord_t y = 32 - menu::kMenuLineH / 2 - 1; + graphics.clearRect(0, y, menu::kDisplayWidth, menu::kMenuLineH * 2 + 2); + graphics.drawFrame(0, y, menu::kDisplayWidth, menu::kMenuLineH * 2 + 2); + + y += 2; + envgen.ui.euclidean_mask_draw.Render(menu::kDisplayWidth - 2, y, + env.get_euclidean_length(), env.get_euclidean_fill(), env.get_euclidean_offset(), + env.get_euclidean_counter()); + + y += menu::kMenuLineH; + menu::SettingsListItem list_item; + list_item.selected = false; + list_item.editing = true; + list_item.y = y; + + list_item.x = 1; + list_item.valuex = 38; + list_item.endx = 60 - 2; + if (envgen.ui.euclidean_edit_length) { + auto attr = EnvelopeGenerator::value_attr(ENV_SETTING_EUCLIDEAN_LENGTH); + attr.min_ = 1; + attr.name = "Len"; + list_item.DrawDefault(env.get_euclidean_length(), attr); + } else { + auto attr = EnvelopeGenerator::value_attr(ENV_SETTING_EUCLIDEAN_FILL); + list_item.DrawValueMax(env.get_euclidean_fill(), attr, env.get_euclidean_length() + 0x1); + } + + list_item.editing = true; + list_item.x = 60; + list_item.valuex = 106; + list_item.endx = menu::kDisplayWidth - 2; + list_item.DrawValueMax(env.get_euclidean_offset(), EnvelopeGenerator::value_attr(ENV_SETTING_EUCLIDEAN_OFFSET), env.get_euclidean_length()); + } +} + +void ENVGEN_menu() { + + menu::QuadTitleBar::Draw(); + for (uint_fast8_t i = 0; i < 4; ++i) { + menu::QuadTitleBar::SetColumn(i); + graphics.print((char)('A' + i)); + menu::QuadTitleBar::DrawGateIndicator(i, envgen.envelopes_[i].getTriggerState()); + + + EnvelopeGenerator::DelayedTrigger trigger; + envgen.envelopes_[i].get_next_trigger(trigger); + if (trigger.delay) { + weegfx::coord_t x = menu::QuadTitleBar::ColumnStartX(i) + 28; + weegfx::coord_t h = (trigger.time_left * 8) / trigger.delay; + graphics.drawRect(x, menu::QuadTitleBar::kTextY + 7 - h, 2, 1 + h); + } + } + // If settings mode, draw level in title bar? + menu::QuadTitleBar::Selected(envgen.ui.selected_channel); + + if (QuadEnvelopeGenerator::MODE_EDIT_SEGMENTS == envgen.ui.edit_mode) + ENVGEN_menu_preview(); + else + ENVGEN_menu_settings(); +} + +void ENVGEN_topButton() { + auto &selected_env = envgen.selected(); + selected_env.change_value(ENV_SETTING_SEG1_VALUE + envgen.ui.selected_segment, 32); +} + +void ENVGEN_lowerButton() { + auto &selected_env = envgen.selected(); + selected_env.change_value(ENV_SETTING_SEG1_VALUE + envgen.ui.selected_segment, -32); +} + +void ENVGEN_rightButton() { + + if (QuadEnvelopeGenerator::MODE_EDIT_SEGMENTS == envgen.ui.edit_mode) { + envgen.ui.segment_editing = !envgen.ui.segment_editing; + } else { + envgen.ui.cursor.toggle_editing(); + envgen.ui.euclidean_edit_length = false; + } +} + +void ENVGEN_leftButton() { + if (QuadEnvelopeGenerator::MODE_EDIT_SETTINGS == envgen.ui.edit_mode) { + if (!envgen.euclidean_edit_active()) { + envgen.ui.edit_mode = QuadEnvelopeGenerator::MODE_EDIT_SEGMENTS; + envgen.ui.cursor.set_editing(false); + } else { + envgen.ui.euclidean_edit_length = !envgen.ui.euclidean_edit_length; + } + } else { + envgen.ui.edit_mode = QuadEnvelopeGenerator::MODE_EDIT_SETTINGS; + envgen.ui.segment_editing = false; + } +} + +void ENVGEN_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + ENVGEN_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + ENVGEN_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + ENVGEN_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + ENVGEN_rightButton(); + break; + } + } +} + +void ENVGEN_handleEncoderEvent(const UI::Event &event) { + + if (OC::CONTROL_ENCODER_L == event.control) { + if (envgen.euclidean_edit_active()) { + if (envgen.ui.euclidean_edit_length) { + // Artificially constrain length here + int length = envgen.selected().get_euclidean_length() + event.value; + if (length > 0) { + envgen.selected().apply_value(ENV_SETTING_EUCLIDEAN_LENGTH, length); + // constrain k, offset: + if (length < envgen.selected().get_euclidean_fill()) + envgen.selected().apply_value(ENV_SETTING_EUCLIDEAN_FILL, length + 0x1); + if (length < envgen.selected().get_euclidean_offset()) + envgen.selected().apply_value(ENV_SETTING_EUCLIDEAN_OFFSET, length); + } + } else { + // constrain k: + if (envgen.selected().get_euclidean_fill() <= envgen.selected().get_euclidean_length()) + envgen.selected().change_value(ENV_SETTING_EUCLIDEAN_FILL, event.value); + else if (event.value < 0) + envgen.selected().change_value(ENV_SETTING_EUCLIDEAN_FILL, event.value); + } + } else { + int left_value = envgen.ui.selected_channel + event.value; + CONSTRAIN(left_value, 0, 3); + envgen.ui.selected_channel = left_value; + auto &selected_env = envgen.selected(); + CONSTRAIN(envgen.ui.selected_segment, 0, selected_env.num_editable_segments() - 1); + envgen.ui.cursor.AdjustEnd(selected_env.num_enabled_settings() - 1); + } + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (QuadEnvelopeGenerator::MODE_EDIT_SEGMENTS == envgen.ui.edit_mode) { + auto &selected_env = envgen.selected(); + if (envgen.ui.segment_editing) { + selected_env.change_value(ENV_SETTING_SEG1_VALUE + envgen.ui.selected_segment, event.value); + } else { + int selected_segment = envgen.ui.selected_segment + event.value; + CONSTRAIN(selected_segment, 0, selected_env.num_editable_segments() - 1); + envgen.ui.selected_segment = selected_segment; + } + } else { + if (envgen.ui.cursor.editing()) { + auto &selected_env = envgen.selected(); + EnvelopeSettings setting = selected_env.enabled_setting_at(envgen.ui.cursor.cursor_pos()); + + if (ENV_SETTING_EUCLIDEAN_OFFSET == setting) { + // constrain offset + if (selected_env.get_euclidean_offset() < selected_env.get_euclidean_length()) + selected_env.change_value(ENV_SETTING_EUCLIDEAN_OFFSET, event.value); + else if (event.value < 0) + selected_env.change_value(ENV_SETTING_EUCLIDEAN_OFFSET, event.value); + } + else { + selected_env.change_value(setting, event.value); + } + + if (ENV_SETTING_TRIGGER_DELAY_MODE == setting || ENV_SETTING_EUCLIDEAN_LENGTH == setting) + selected_env.update_enabled_settings(); + envgen.ui.cursor.AdjustEnd(selected_env.num_enabled_settings() - 1); + } else { + envgen.ui.cursor.Scroll(event.value); + } + } + } +} + +int16_t fast_preview_values[peaks::kFastPreviewWidth + 32]; + +template +void RenderFastPreview() { + uint16_t w = envgen.envelopes_[index].RenderFastPreview(fast_preview_values); + CONSTRAIN(w, 0, peaks::kFastPreviewWidth); // Just-in-case + weegfx::coord_t x = startx; + const int16_t *values = fast_preview_values; + while (w--) { + const int16_t value = 1 + ((*values++ >> 10) & 0x1f); + graphics.drawVLine(x++, y + 32 - value, value); + } +} + +void ENVGEN_screensaver() { +#ifdef ENVGEN_DEBUG_SCREENSAVER + debug::CycleMeasurement render_cycles; +#endif + + #ifdef BUCHLA_4U + RenderFastPreview<0, 0, 32>(); + RenderFastPreview<1, 64, 32>(); + RenderFastPreview<2, 0, 0>(); + RenderFastPreview<3, 64, 0>(); + #else + RenderFastPreview<0, 0, 0>(); + RenderFastPreview<1, 64, 0>(); + RenderFastPreview<2, 0, 32>(); + RenderFastPreview<3, 64, 32>(); + #endif + OC::scope_render(); + +#ifdef ENVGEN_DEBUG_SCREENSAVER + uint32_t us = debug::cycles_to_us(render_cycles.read()); + graphics.setPrintPos(2, 56); + graphics.printf("%u", us); +#endif +} + +#ifdef ENVGEN_DEBUG +void ENVGEN_debug() { + for (int i = 0; i < 4; ++i) { + uint8_t ypos = 10*(i + 1) + 2 ; + graphics.setPrintPos(2, ypos); + graphics.print(envgen.envelopes_[i].get_amplitude_value()) ; + graphics.setPrintPos(50, ypos); + graphics.print(envgen.envelopes_[i].get_sampled_amplitude_value()) ; + graphics.setPrintPos(100, ypos); + graphics.print(envgen.envelopes_[i].get_is_amplitude_sampled()) ; + } +} +#endif // ENVGEN_DEBUG + +void FASTRUN ENVGEN_isr() { + envgen.ISR(); +} + +#endif // ENABLE_APP_PIQUED diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index d1bdcc183..a2ba817ff 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -53,6 +53,10 @@ OC::App available_apps[] = { DECLARE_APP('M','!', "Meta-Q", DQ), #endif + #ifdef ENABLE_APP_PIQUED + DECLARE_APP('E','G', "Piqued", ENVGEN), + #endif + #ifdef ENABLE_APP_CHORDS DECLARE_APP('A','C', "Acid Curds", CHORDS), #endif From 4756ffcd88968d99722c8f13697330f2fec0b6f2 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 14 Mar 2023 16:38:09 -0400 Subject: [PATCH 175/417] Build config updates --- software/o_c_REV/APP_SETTINGS.ino | 4 ---- software/o_c_REV/platformio.ini | 20 ++++++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/APP_SETTINGS.ino b/software/o_c_REV/APP_SETTINGS.ino index 2d90e57d8..c02219c82 100644 --- a/software/o_c_REV/APP_SETTINGS.ino +++ b/software/o_c_REV/APP_SETTINGS.ino @@ -76,10 +76,6 @@ public: gfxPrint(0, 35, OC_VERSION_URL); gfxPrint(0, 55, "[CALIBRATE] [RESET]"); -#ifdef BUCHLA_4U - gfxPrint(60, 25, "Buchla"); -#endif - //DrawQRAt(103, 15); } diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index cd7120cc5..92d70b9af 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -12,7 +12,9 @@ src_dir = . default_envs = oc_prod - cal8 + oc_stock + oc_prod_vor + oc_prod_flipped [env] platform = teensy@4.17.0 @@ -45,21 +47,19 @@ build_flags = -DENABLE_APP_PONG -DENABLE_APP_DARKEST_TIMELINE -DENABLE_APP_QUANTERMAIN - -DENABLE_APP_METAQ - -DENABLE_APP_CHORDS - -DENABLE_APP_H1200 + -DENABLE_APP_PIQUED [env:oc_stock] build_flags = ${env.build_flags} -DENABLE_APP_PONG - -DENABLE_APP_DARKEST_TIMELINE -DENABLE_APP_QUANTERMAIN -DENABLE_APP_METAQ + -DENABLE_APP_PIQUED -DENABLE_APP_CHORDS -DENABLE_APP_SEQUINS -DENABLE_APP_POLYLFO - -DENABLE_APP_H1200 +; -DENABLE_APP_H1200 -DOC_VERSION_EXTRA="\" +stock\"" [env:oc_prod_flipped] @@ -72,8 +72,16 @@ build_flags = build_flags = ${env:oc_prod.build_flags} -DVOR +; -DVOR_NO_RANGE_BUTTON -DOC_VERSION_EXTRA="\"+VOR\"" +[env:buchla] +build_flags = + ${env:oc_prod.build_flags} + -DBUCHLA_SUPPORT + -DBUCHLA_4U + -DOC_VERSION_EXTRA="\" Buchla\"" + [env:oc_dev] build_flags = ${env.build_flags} -DOC_DEV ; -DPRINT_DEBUG From a2c42faf04e4e3a794279a01db6e2b4848022dfa Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 15 Mar 2023 16:14:27 -0400 Subject: [PATCH 176/417] LoFi Echo: smaller buffer again this was never a very good idea, lol --- software/o_c_REV/HEM_LoFiPCM.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index f00a8c03e..2794f6719 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define HEM_LOFI_PCM_BUFFER_SIZE 4096 +#define HEM_LOFI_PCM_BUFFER_SIZE 2048 #define HEM_LOFI_PCM_SPEED 4 // #define CLIPLIMIT 32512 From 73e44fe27927bcec1eddcedb210e2edffe6bbc6d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 14 Mar 2023 18:24:32 -0400 Subject: [PATCH 177/417] DualTM: fix Trig output, fix transpose Trig stays high for default Clock duration, then decays. Transpose is now V/Oct, sampled when clocked. ...and some refactoring of my laziness --- software/o_c_REV/HEM_TM2.ino | 105 +++++++++++++++++------------------ 1 file changed, 50 insertions(+), 55 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index e8d2b1156..8630659ed 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -85,8 +85,8 @@ public: } void Start() { - reg = random(0, 65535); - reg2 = ~reg; + reg[0] = random(0, 65535); + reg[1] = ~reg[0]; quantizer.Init(); quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone @@ -104,9 +104,7 @@ public: len_mod = length; range_mod = range; smooth_mod = smoothing; - int note_trans1 = 0; - int note_trans2 = 0; - int note_trans3 = 0; + int trans_mod[3] = {0, 0, 0}; // default transpose // process CV inputs ForEachChannel(ch) { @@ -128,13 +126,10 @@ public: // bi-polar transpose before quantize case TRANSPOSE1: - note_trans1 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); - break; case TRANSPOSE2: - note_trans2 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); - break; case TRANSPOSE_BOTH: - note_trans3 = Proportion(cv_data[ch], HEMISPHERE_MAX_CV, range_mod); + if (clk) // S&H style transpose + trans_mod[cvmode[ch] - TRANSPOSE1] = MIDIQuantizer::NoteNumber(cv_data[ch], 0) - 60; // constrain to range_mod? break; default: break; @@ -143,70 +138,66 @@ public: // Advance the register on clock, flipping bits as necessary if (clk) { + // Update transpose values + for (int i = 0; i < 3; ++i) { note_trans[i] = trans_mod[i]; } + // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same int prob = (cursor == PROB || Gate(1)) ? p_mod : 0; - // Grab the bit that's about to be shifted away - int last = (reg >> (len_mod - 1)) & 0x01; - int last2 = (reg2 >> (len_mod - 1)) & 0x01; + for (int i = 0; i < 2; ++i) { + // Grab the bit that's about to be shifted away + int last = (reg[i] >> (len_mod - 1)) & 0x01; - // Does it change? - if (random(0, 99) < prob) last = 1 - last; - if (random(0, 99) < prob) last2 = 1 - last2; + // Does it change? + if (random(0, 99) < prob) last = 1 - last; - // Shift left, then potentially add the bit from the other side - reg = (reg << 1) + last; - reg2 = (reg2 << 1) + last2; + // Shift left, then potentially add the bit from the other side + reg[i] = (reg[i] << 1) + last; + } } // Send 8-bit scaled and quantized CV - // scaled = note * range / 0x1f - int32_t note = Proportion(reg & 0xff, 0xff, range_mod); - int32_t note2 = Proportion(reg2 & 0xff, 0xff, range_mod); - - /* - note *= range; - simfloat x = int2simfloat(note) / (int32_t)0x1f; - note = simfloat2int(x); - */ + int32_t note = Proportion(reg[0] & 0xff, 0xff, range_mod); + int32_t note2 = Proportion(reg[1] & 0xff, 0xff, range_mod); ForEachChannel(ch) { switch (outmode[ch]) { case PITCH_SUM: - Output[ch] = slew(Output[ch], quantizer.Lookup(note + note2 + note_trans3 + 64)); + Output[ch] = slew(Output[ch], quantizer.Lookup(note + note2 + note_trans[2] + 64)); break; case PITCH1: - Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans1 + note_trans3 + 64)); + Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans[0] + note_trans[2] + 64)); break; case PITCH2: - Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans2 + note_trans3 + 64)); + Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans[1] + note_trans[2] + 64)); break; case MOD1: // 8-bit bi-polar proportioned CV - Output[ch] = slew(Output[ch], Proportion( int(reg & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); + Output[ch] = slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case MOD2: - Output[ch] = slew(Output[ch], Proportion( int(reg2 & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); + Output[ch] = slew(Output[ch], Proportion( int(reg[1] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case TRIG1: - if (clk && (reg & 0x01) == 1) // trigger if 1st bit is high + case TRIG2: + if (clk && (reg[outmode[ch]-TRIG1] & 0x01) == 1) // trigger if 1st bit is high + { Output[ch] = HEMISPHERE_MAX_CV; //ClockOut(ch); + trigpulse[ch] = HEMISPHERE_CLOCK_TICKS; + } else // decay - Output[ch] = slew(Output[ch]); - break; - case TRIG2: - if (clk && (reg2 & 0x01) == 1) - Output[ch] = HEMISPHERE_MAX_CV; - else - Output[ch] = slew(Output[ch]); + { + // hold until it's time to pull it down + if (--trigpulse[ch] < 0) + Output[ch] = slew(Output[ch]); + } break; case GATE1: - Output[ch] = slew(Output[ch], (reg & 0x01)*HEMISPHERE_MAX_CV ); - break; case GATE2: - Output[ch] = slew(Output[ch], (reg2 & 0x01)*HEMISPHERE_MAX_CV ); + Output[ch] = slew(Output[ch], (reg[outmode[ch] - GATE1] & 0x01)*HEMISPHERE_MAX_CV ); break; + case GATE_SUM: - Output[ch] = slew(Output[ch], ((reg & 0x01)+(reg2 & 0x01))*HEMISPHERE_3V_CV ); + Output[ch] = slew(Output[ch], ((reg[0] & 0x01)+(reg[1] & 0x01))*HEMISPHERE_3V_CV ); break; default: break; @@ -296,8 +287,8 @@ public: cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); - reg = Unpack(data, PackLocation {32,32}); - reg2 = Unpack(data, PackLocation {0, 32}); // lol it could be fun + reg[0] = Unpack(data, PackLocation {32,32}); + reg[1] = Unpack(data, PackLocation {0, 32}); // lol it could be fun } protected: @@ -311,25 +302,29 @@ protected: } private: - int length = 16; // Sequence length - int len_mod; // actual length after CV mod int cursor; // TM2Cursor + braids::Quantizer quantizer; + int scale = OC::Scales::SCALE_SEMI; // Scale used for quantized output + int root_note; // TODO - // Settings - uint32_t reg; // 32-bit sequence register - uint32_t reg2; // DJP + // TODO: consider using the TuringMachine class or whatev + uint32_t reg[2]; // 32-bit sequence registers // most recent output values int Output[2] = {0, 0}; + int trigpulse[2] = {0, 0}; // tick timer for Trig output modes + // Settings and modulated copies + int length = 16; // Sequence length + int len_mod; // actual length after CV mod int p = 0; // Probability of bit flipping on each cycle int p_mod; - int scale = OC::Scales::SCALE_SEMI; // Scale used for quantized output int range = 24; int range_mod; int smoothing = 4; int smooth_mod; + int note_trans[3] = {0, 0, 0}; // transpose from CV input OutputMode outmode[2] = {PITCH1, TRIG2}; InputMode cvmode[2] = {LENGTH_MOD, RANGE_MOD}; @@ -458,8 +453,8 @@ private: gfxLine(0, 62, 63, 62); for (int b = 0; b < 16; b++) { - int v = (reg >> b) & 0x01; - int v2 = (reg2 >> b) & 0x01; + int v = (reg[0] >> b) & 0x01; + int v2 = (reg[1] >> b) & 0x01; if (v) gfxRect(60 - (4 * b), 47, 3, 7); if (v2) gfxRect(60 - (4 * b), 54, 3, 7); } From 56186b9f345129863629e6b57336e974001e2638 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 15 Mar 2023 22:34:15 -0400 Subject: [PATCH 178/417] Shorter timeout for button long-press --- software/o_c_REV/OC_ui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_ui.h b/software/o_c_REV/OC_ui.h index f0a288f47..28790488f 100644 --- a/software/o_c_REV/OC_ui.h +++ b/software/o_c_REV/OC_ui.h @@ -62,7 +62,7 @@ enum UiMode { class Ui { public: static const size_t kEventQueueDepth = 16; - static const uint32_t kLongPressTicks = 1000; + static const uint32_t kLongPressTicks = 500; Ui() { } From caed891d6d89b118e43a530cb37c27056c67bf77 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 16 Mar 2023 00:31:32 -0400 Subject: [PATCH 179/417] Fix compile errors when disabling Hemisphere --- software/o_c_REV/OC_apps.ino | 2 ++ software/o_c_REV/OC_calibration.ino | 1 + 2 files changed, 3 insertions(+) diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index a2ba817ff..3726414fd 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -23,7 +23,9 @@ #include "OC_apps.h" #include "OC_digital_inputs.h" #include "OC_autotune.h" +#include "OC_patterns.h" #include "enigma/TuringMachine.h" +#include "src/drivers/FreqMeasure/OC_FreqMeasure.h" #define DECLARE_APP(a, b, name, prefix) \ { TWOCC::value, name, \ diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index 93d095658..630ec5c9d 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -7,6 +7,7 @@ */ #include "OC_calibration.h" +namespace menu = OC::menu; using OC::DAC; From 828fc2713db7949ffba484b7f82bbf4b5e1663cd Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 16 Mar 2023 22:09:48 -0400 Subject: [PATCH 180/417] Set default applets: DualTM + EuclidX Ideal for generating two CV sequences + two trigger patterns or 4 trigger patterns... ;) --- software/o_c_REV/APP_HEMISPHERE.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index a3389766c..f7fc2ad01 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -90,8 +90,8 @@ public: help_hemisphere = -1; clock_setup = 0; - SetApplet(0, get_applet_index_by_id(57)); // ADSR - SetApplet(1, get_applet_index_by_id(58)); // Scale Duet + SetApplet(0, get_applet_index_by_id(18)); // DualTM + SetApplet(1, get_applet_index_by_id(15)); // EuclidX } void Resume() { From 2e846565a9d2e52c0155c81029c5a44d05ea4db2 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Wed, 15 Mar 2023 16:53:10 -0700 Subject: [PATCH 181/417] EbbAndLfo: Stop clock on startup from setting freq --- software/o_c_REV/HEM_EbbAndLfo.ino | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 576640ae9..42bbc1348 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -9,10 +9,13 @@ public: void Controller() { if (Clock(1)) phase = 0; if (Clock(0)) { + clocks_received++; //uint32_t next_tick = predictor.Predict(ClockCycleTicks(0)); - int new_freq = 0xffffffff / ClockCycleTicks(0); - pitch = ComputePitch(new_freq); - phase = 0; + if (clocks_received > 1) { + int new_freq = 0xffffffff / ClockCycleTicks(0); + pitch = ComputePitch(new_freq); + phase = 0; + } } // handle CV inputs @@ -205,6 +208,7 @@ public: } void OnDataReceive(uint64_t data) { + clocks_received = 0; pitch = Unpack(data, PackLocation { 0, 16 }) - (1 << 15); slope = Unpack(data, PackLocation { 16, 7 }); shape = Unpack(data, PackLocation { 23, 7 }); @@ -252,6 +256,8 @@ private: int slope_mod; int shape_mod; int fold_mod; + + int clocks_received = 0; uint8_t out = 0b0001; // Unipolar on A, bipolar on B uint8_t cv = 0b0001; // Freq on 1, shape on 2 From ed0ec32b28e977a7348ce40063abe72de5bcba5c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Mar 2023 02:42:12 -0400 Subject: [PATCH 182/417] Re-fix quantizer transpose bug --- software/o_c_REV/braids_quantizer.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/braids_quantizer.cpp b/software/o_c_REV/braids_quantizer.cpp index b417ca40b..d758ec851 100644 --- a/software/o_c_REV/braids_quantizer.cpp +++ b/software/o_c_REV/braids_quantizer.cpp @@ -89,28 +89,33 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { q = num_notes_ - 1; } - q += transpose; - octave += q / num_notes_; - q %= num_notes_; - if (q < 0) { - q += num_notes_; - octave--; - } - - note_number_ = octave * num_notes_ + q; + // set boundaries for hysteresis codeword_ = notes_[q] + octave * span_; previous_boundary_ = q == 0 ? notes_[num_notes_ - 1] + (octave - 1) * span_ : notes_[q - 1] + octave * span_; - previous_boundary_ = (NEIGHBOR_WEIGHT * previous_boundary_ + CUR_WEIGHT * codeword_) >> 4; + next_boundary_ = q == num_notes_ - 1 ? notes_[0] + (octave + 1) * span_ : notes_[q + 1] + octave * span_; next_boundary_ = (NEIGHBOR_WEIGHT * next_boundary_ + CUR_WEIGHT * codeword_) >> 4; + // apply transpose after setting boundaries + q += transpose; + octave += q / num_notes_; + q %= num_notes_; + if (q < 0) { + q += num_notes_; + octave--; + } + + // set final values + note_number_ = octave * num_notes_ + q; + codeword_ = notes_[q] + octave * span_; + transpose_ = transpose; pitch = codeword_; } From e4f72946d0d1b75b564919950d37f71bbd47913a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Mar 2023 02:45:38 -0400 Subject: [PATCH 183/417] Calibr8or: requantize when switching settings --- software/o_c_REV/APP_CALIBR8OR.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index b059c27a5..5f650d2dc 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -223,6 +223,7 @@ public: void OnLeftEncoderMove(int direction) { if (scale_edit) { root_note[sel_chan] = constrain(root_note[sel_chan] + direction, 0, 11); + quantizer[sel_chan].Requantize(); return; } @@ -244,6 +245,7 @@ public: if (scale[sel_chan] >= OC::Scales::NUM_SCALES) scale[sel_chan] = 0; if (scale[sel_chan] < 0) scale[sel_chan] = OC::Scales::NUM_SCALES - 1; quantizer[sel_chan].Configure(OC::Scales::GetScale(scale[sel_chan]), 0xffff); + quantizer[sel_chan].Requantize(); return; } From 5e20948b21ed9c0cec348db9a541745820877d54 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 19 Mar 2023 00:02:00 -0400 Subject: [PATCH 184/417] Internal Clock can trigger all 4 digital inputs with 4 separate multipliers. Also tweaked BugCrack so it can be internally clocked. --- software/o_c_REV/HEM_BugCrack.ino | 4 ++-- software/o_c_REV/HEM_ClockSetup.ino | 18 ++++++++++++------ software/o_c_REV/HEM_Metronome.ino | 2 +- software/o_c_REV/HSClockManager.h | 21 ++++++++++++--------- software/o_c_REV/HemisphereApplet.h | 27 ++++++++++++++++++--------- 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/software/o_c_REV/HEM_BugCrack.ino b/software/o_c_REV/HEM_BugCrack.ino index ecb091580..4a439ec12 100644 --- a/software/o_c_REV/HEM_BugCrack.ino +++ b/software/o_c_REV/HEM_BugCrack.ino @@ -121,7 +121,7 @@ public: } else { _decay_punch = decay_punch; } - if (Clock(CH_KICK, 1)) { + if (Clock(CH_KICK, 0)) { SetEnvDecayKick(_decay_kick); SetEnvDecayPunch(_decay_punch); env_kick.Start(); @@ -170,7 +170,7 @@ public: } else { _blend_snare = blend_snare; } - if (Clock(CH_SNARE, 1)) { + if (Clock(CH_SNARE, 0)) { SetEnvDecaySnare(_decay_snare); SetEnvDecaySnap(_decay_snare); env_snare.Start(); diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 3818a0dba..c9a7c3972 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define FLASH_TICKS 5000 +#define FLASH_TICKS 1000 class ClockSetup : public HemisphereApplet { public: @@ -30,6 +30,8 @@ public: TEMPO, MULT1, MULT2, + MULT3, + MULT4, TRIG1, TRIG2, TRIG3, @@ -107,6 +109,8 @@ public: case MULT1: case MULT2: + case MULT3: + case MULT4: clock_m->SetMultiply(clock_m->GetMultiply(cursor - MULT1) + direction, cursor - MULT1); break; @@ -120,7 +124,7 @@ public: Pack(data, PackLocation { 1, 1 }, clock_m->IsForwarded()); Pack(data, PackLocation { 2, 9 }, clock_m->GetTempo()); Pack(data, PackLocation { 11, 6 }, clock_m->GetMultiply(0)+32); - Pack(data, PackLocation { 17, 6 }, clock_m->GetMultiply(1)+32); + Pack(data, PackLocation { 17, 6 }, clock_m->GetMultiply(2)+32); Pack(data, PackLocation { 23, 5 }, clock_m->GetClockPPQN()); return data; } @@ -134,7 +138,7 @@ public: clock_m->SetForwarding(Unpack(data, PackLocation { 1, 1 })); clock_m->SetTempoBPM(Unpack(data, PackLocation { 2, 9 })); clock_m->SetMultiply(Unpack(data, PackLocation { 11, 6 })-32,0); - clock_m->SetMultiply(Unpack(data, PackLocation { 17, 6 })-32,1); + clock_m->SetMultiply(Unpack(data, PackLocation { 17, 6 })-32,2); clock_m->SetClockPPQN(Unpack(data, PackLocation { 23, 5 })); } @@ -198,9 +202,9 @@ private: gfxPrint(" BPM"); // Multiply - ForEachChannel(ch) { + for (int ch=0; ch<4; ++ch) { int mult = clock_m->GetMultiply(ch); - gfxPrint(1 + ch*64, 37, (mult >= 0) ? "x" : "/"); + gfxPrint(1 + ch*32, 37, (mult >= 0) ? "x" : "/"); gfxPrint( (mult >= 0) ? mult : 1 - mult ); } @@ -224,7 +228,9 @@ private: case MULT1: case MULT2: - gfxCursor(8 + 64*(cursor-MULT1), 45, 12); + case MULT3: + case MULT4: + gfxCursor(8 + 32*(cursor-MULT1), 45, 12); break; case TRIG1: diff --git a/software/o_c_REV/HEM_Metronome.ino b/software/o_c_REV/HEM_Metronome.ino index 5ca557005..c2fac1a80 100644 --- a/software/o_c_REV/HEM_Metronome.ino +++ b/software/o_c_REV/HEM_Metronome.ino @@ -34,7 +34,7 @@ public: // Outputs if (clock_m->IsRunning()) { - if (clock_m->Tock(hemisphere)) { + if (clock_m->Tock(hemisphere*2)) { ClockOut(0); if (clock_m->EndOfBeat(hemisphere)) ClockOut(1); } diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index c28cb4ee9..7c3090eab 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -39,8 +39,10 @@ class ClockManager { static ClockManager *instance; enum ClockOutput { - LEFT_CLOCK, - RIGHT_CLOCK, + LEFT_CLOCK1, + LEFT_CLOCK2, + RIGHT_CLOCK1, + RIGHT_CLOCK2, MIDI_CLOCK, NR_OF_CLOCKS }; @@ -53,11 +55,12 @@ class ClockManager { uint32_t clock_tick = 0; // tick when a physical clock was received on DIGITAL 1 uint32_t beat_tick = 0; // The tick to count from - bool tock[NR_OF_CLOCKS] = {0,0,0}; // The current tock value - int tocks_per_beat[NR_OF_CLOCKS] = {1, 1, MIDI_OUT_PPQN}; // Multiplier + bool tock[NR_OF_CLOCKS] = {0,0,0,0,0}; // The current tock value + int16_t tocks_per_beat[NR_OF_CLOCKS] = {4,0, 8,0, MIDI_OUT_PPQN}; // Multiplier + int count[NR_OF_CLOCKS] = {0,0,0,0,0}; // Multiple counter, 0 is a special case when first starting the clock + int clock_ppqn = 4; // external clock multiple bool cycle = 0; // Alternates for each tock, for display purposes - int count[NR_OF_CLOCKS] = {0,0,0}; // Multiple counter, 0 is a special case when first starting the clock bool boop[4]; // Manual triggers @@ -71,7 +74,7 @@ class ClockManager { return instance; } - void SetMultiply(int multiply, bool ch = 0) { + void SetMultiply(int multiply, int ch = 0) { multiply = constrain(multiply, CLOCK_MIN_MULTIPLE, CLOCK_MAX_MULTIPLE); tocks_per_beat[ch] = multiply; } @@ -91,7 +94,7 @@ class ClockManager { tempo = bpm; } - int GetMultiply(bool ch = 0) {return tocks_per_beat[ch];} + int GetMultiply(int ch = 0) {return tocks_per_beat[ch];} int GetClockPPQN() { return clock_ppqn; } /* Gets the current tempo. This can be used between client processes, like two different @@ -231,9 +234,9 @@ class ClockManager { return Tock(MIDI_CLOCK); } - bool EndOfBeat(bool ch = 0) {return count[ch] == 1;} + bool EndOfBeat(int ch = 0) {return count[ch] == 1;} - bool Cycle(bool ch = 0) {return cycle;} + bool Cycle(int ch = 0) {return cycle;} }; ClockManager *ClockManager::instance = 0; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index c7fdcc5df..3594ee023 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -380,26 +380,35 @@ class HemisphereApplet { bool Clock(int ch, bool physical = 0) { bool clocked = 0; ClockManager *clock_m = clock_m->get(); + bool useTock = (!physical && clock_m->IsRunning()); if (ch == 0) { // clock triggers if (hemisphere == LEFT_HEMISPHERE) { - if (!physical && clock_m->IsRunning() && clock_m->GetMultiply(hemisphere) != 0) - clocked = clock_m->Tock(hemisphere); + if (useTock && clock_m->GetMultiply(0) != 0) + clocked = clock_m->Tock(0); else clocked = OC::DigitalInputs::clocked(); } else { // right side is special - if (!physical && clock_m->IsRunning() && clock_m->GetMultiply(hemisphere) != 0) - clocked = clock_m->Tock(hemisphere); + if (useTock && clock_m->GetMultiply(2) != 0) + clocked = clock_m->Tock(2); else if (master_clock_bus) // forwarding from left clocked = OC::DigitalInputs::clocked(); else clocked = OC::DigitalInputs::clocked(); } - } else if (ch == 1) { // simple physical trig check - if (hemisphere == LEFT_HEMISPHERE) - clocked = OC::DigitalInputs::clocked(); - else - clocked = OC::DigitalInputs::clocked(); + } else if (ch == 1) { // TR2 and TR4 + if (hemisphere == LEFT_HEMISPHERE) { + if (useTock && clock_m->GetMultiply(1) != 0) + clocked = clock_m->Tock(1); + else + clocked = OC::DigitalInputs::clocked(); + } + else { + if (useTock && clock_m->GetMultiply(3) != 0) + clocked = clock_m->Tock(3); + else + clocked = OC::DigitalInputs::clocked(); + } } clocked = clocked || clock_m->Beep(io_offset + ch); From 051d47c294e518f76a2dc0b1559b664206a28c33 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 19 Mar 2023 00:37:28 -0400 Subject: [PATCH 185/417] Calibr8or: preset switcher; Long-press DOWN button to access --- software/o_c_REV/APP_CALIBR8OR.ino | 466 ++++++++++++++++------------- software/o_c_REV/OC_apps.ino | 9 +- software/o_c_REV/platformio.ini | 4 +- 3 files changed, 267 insertions(+), 212 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index 5f650d2dc..e7c317d9a 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -22,7 +22,7 @@ * Based on a design spec from Chris Meyer / Alias Zone / Learning Modular */ -#if defined(ENABLE_APP_CALIBR8OR) || defined(ENABLE_CALIBR8OR_X4) +#ifdef ENABLE_APP_CALIBR8OR #include "HSApplication.h" #include "HSMIDI.h" @@ -36,8 +36,21 @@ #define CAL8_MAX_TRANSPOSE 60 const int CAL8OR_PRECISION = 10000; -// Settings storage spec (per channel?) -enum CAL8SETTINGS { +// channel configs +struct Cal8ChannelConfig { + int scale; + int root_note; + int last_note; // for S&H mode + + uint8_t clocked_mode; + int8_t offset; // fine-tuning offset + int16_t scale_factor; // precision of 0.01% as an offset from 100% + int8_t transpose; // in semitones + int8_t transpose_active; // held value while waiting for trigger +}; + +// Preset storage spec +enum Cal8Settings { CAL8_DATA_VALID, // 1 bit CAL8_SCALE_A, // 12 bits @@ -66,78 +79,150 @@ enum CAL8SETTINGS { CAL8_SETTING_LAST }; +enum Cal8Presets { + CAL8_PRESET_A, + CAL8_PRESET_B, + CAL8_PRESET_C, + CAL8_PRESET_D, + + NR_OF_PRESETS +}; + +enum Cal8Channel { + CAL8_CHANNEL_A, + CAL8_CHANNEL_B, + CAL8_CHANNEL_C, + CAL8_CHANNEL_D, + + NR_OF_CHANNELS +}; -class Calibr8or : public HSApplication, - public settings::SettingsBase { +enum Cal8ClockMode { + CONTINUOUS, + TRIG_TRANS, + SAMPLE_AND_HOLD, + + NR_OF_CLOCKMODES +}; + +const char * cal8_preset_id[4] = {"A", "B", "C", "D"}; + +class Calibr8orPreset : public settings::SettingsBase { public: - enum Cal8Channel { - CAL8_CHANNEL_A, - CAL8_CHANNEL_B, - CAL8_CHANNEL_C, - CAL8_CHANNEL_D, + bool is_valid() { + return values_[CAL8_DATA_VALID]; + } + bool load_preset(Cal8ChannelConfig *channel) { + if (!is_valid()) return false; // don't try to load a blank - NR_OF_CHANNELS - }; + int ix = 1; // skip validity flag + + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + channel[ch].scale = values_[ix++]; + channel[ch].scale = constrain(channel[ch].scale, 0, OC::Scales::NUM_SCALES - 1); + + channel[ch].scale_factor = values_[ix++] - 500; + channel[ch].offset = values_[ix++] - 63; + channel[ch].transpose = values_[ix++] - CAL8_MAX_TRANSPOSE; + + uint32_t root_and_mode = uint32_t(values_[ix++]); + channel[ch].clocked_mode = ((root_and_mode >> 4) & 0x03) % NR_OF_CLOCKMODES; + channel[ch].root_note = constrain(int(root_and_mode & 0x0f), 0, 11); + } + + return true; + } + void save_preset(Cal8ChannelConfig *channel) { + int ix = 0; + + values_[ix++] = 1; // validity flag + + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + values_[ix++] = channel[ch].scale; + values_[ix++] = channel[ch].scale_factor + 500; + values_[ix++] = channel[ch].offset + 63; + values_[ix++] = channel[ch].transpose + CAL8_MAX_TRANSPOSE; + values_[ix++] = ((channel[ch].clocked_mode & 0x03) << 4) | (channel[ch].root_note & 0x0f); + } + } + +}; + +Calibr8orPreset cal8_presets[NR_OF_PRESETS]; + +class Calibr8or : public HSApplication { +public: enum Cal8EditMode { TRANSPOSE, TRACKING, NR_OF_EDITMODES }; - enum Cal8ClockMode { - CONTINUOUS, - TRIG_TRANS, - SAMPLE_AND_HOLD, - - NR_OF_CLOCKMODES - }; - - void set_index(int index_) { index = index_; } void Start() { - for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { - quantizer[ch].Init(); - scale[ch] = OC::Scales::SCALE_SEMI; - quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); - - scale_factor[ch] = 0; - offset[ch] = 0; - transpose[ch] = 0; - clocked_mode[ch] = 0; - last_note[ch] = 0; - } - segment.Init(SegmentSize::BIG_SEGMENTS); - // make sure to turn this off, just in case? + // make sure to turn this off, just in case FreqMeasure.end(); OC::DigitalInputs::reInit(); + + ClearPreset(); } + void ClearPreset() { + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + quantizer[ch].Init(); + channel[ch].scale = OC::Scales::SCALE_SEMI; + quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); + + channel[ch].scale_factor = 0; + channel[ch].offset = 0; + channel[ch].root_note = 0; + channel[ch].transpose = 0; + channel[ch].clocked_mode = 0; + channel[ch].last_note = 0; + } + } + void LoadPreset() { + bool success = cal8_presets[index].load_preset(channel); + if (success) { + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); + quantizer[ch].Requantize(); + } + preset_modified = 0; + } + else + ClearPreset(); + } + void SavePreset() { + cal8_presets[index].save_preset(channel); + preset_modified = 0; + } + void Resume() { - if (values_[CAL8_DATA_VALID]) - LoadFromEEPROM(); } void Controller() { for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { bool clocked = Clock(ch); + Cal8ChannelConfig &c = channel[ch]; // clocked transpose - if (CONTINUOUS == clocked_mode[ch] || clocked) { - transpose_active[ch] = transpose[ch]; + if (CONTINUOUS == c.clocked_mode || clocked) { + c.transpose_active = c.transpose; } // respect S&H mode - if (clocked_mode[ch] != SAMPLE_AND_HOLD || clocked) { + if (c.clocked_mode != SAMPLE_AND_HOLD || clocked) { // CV value int pitch = In(ch); - int quantized = quantizer[ch].Process(pitch, root_note[ch] * 128, transpose_active[ch]); - last_note[ch] = quantized; + int quantized = quantizer[ch].Process(pitch, c.root_note * 128, c.transpose_active); + c.last_note = quantized; } - int output_cv = last_note[ch] * (CAL8OR_PRECISION + scale_factor[ch]) / CAL8OR_PRECISION; - output_cv += offset[ch]; + int output_cv = c.last_note * (CAL8OR_PRECISION + c.scale_factor) / CAL8OR_PRECISION; + output_cv += c.offset; Out(ch, output_cv); } @@ -145,115 +230,125 @@ public: void View() { gfxHeader("Calibr8or"); -#ifdef ENABLE_CALIBR8OR_X4 - const char * cal8_preset_id[4] = {"A", "B", "C", "D"}; - gfxPrint(120, 0, cal8_preset_id[index]); -#endif - DrawInterface(); - } - void SaveToEEPROM() { - int ix = 0; - - values_[ix++] = 1; // validity flag - - for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { - values_[ix++] = scale[ch]; - values_[ix++] = scale_factor[ch] + 500; - values_[ix++] = offset[ch] + 63; - values_[ix++] = transpose[ch] + CAL8_MAX_TRANSPOSE; - values_[ix++] = ((clocked_mode[ch] & 0x03) << 4) | (root_note[ch] & 0x0f); + if (preset_select) { + gfxPrint(64, 1, "- Presets"); + DrawPresetSelector(); } - } - /* - CAL8_SCALE_A, // 12 bits - CAL8_SCALEFACTOR_A, // 10 bits - CAL8_OFFSET_A, // 8 bits - CAL8_TRANSPOSE_A, // 8 bits - CAL8_CLOCKMODE_A, // 2 bits - */ - void LoadFromEEPROM() { - int ix = 1; // skip validity flag - - for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { - scale[ch] = values_[ix++]; - scale[ch] = constrain(scale[ch], 0, OC::Scales::NUM_SCALES - 1); - quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); - - scale_factor[ch] = values_[ix++] - 500; - offset[ch] = values_[ix++] - 63; - transpose[ch] = values_[ix++] - CAL8_MAX_TRANSPOSE; + else { + gfxPos(110, 1); + if (preset_modified) gfxPrint("*"); + if (cal8_presets[index].is_valid()) gfxPrint(cal8_preset_id[index]); - uint32_t root_and_mode = uint32_t(values_[ix++]); - clocked_mode[ch] = ((root_and_mode >> 4) & 0x03) % NR_OF_CLOCKMODES; - root_note[ch] = constrain(int(root_and_mode & 0x0f), 0, 11); + DrawInterface(); } } + ///////////////////////////////////////////////////////////////// // Control handlers ///////////////////////////////////////////////////////////////// void OnLeftButtonPress() { // Toggle between Transpose mode and Tracking Compensation + // also doubles as Load or Save for preset select ++edit_mode %= NR_OF_EDITMODES; + + // prevent saving to the (clear) slot + if (edit_mode && preset_select == 5) preset_select = 4; } void OnLeftButtonLongPress() { + if (preset_select) return; + // Toggle triggered transpose mode - ++clocked_mode[sel_chan] %= NR_OF_CLOCKMODES; + ++channel[sel_chan].clocked_mode %= NR_OF_CLOCKMODES; + preset_modified = 1; } void OnRightButtonPress() { + if (preset_select) { + // special case to clear values + if (!edit_mode && preset_select == NR_OF_PRESETS + 1) { + ClearPreset(); + preset_modified = 1; + } + else { + index = preset_select - 1; + if (edit_mode) SavePreset(); + else LoadPreset(); + } + + preset_select = 0; + return; + } + // Scale selection scale_edit = !scale_edit; } void OnUpButtonPress() { + preset_select = 0; + ++sel_chan %= NR_OF_CHANNELS; } void OnDownButtonPress() { + preset_select = 0; + if (--sel_chan < 0) sel_chan += NR_OF_CHANNELS; } void OnDownButtonLongPress() { + // show preset screen, select last loaded + preset_select = 1 + index; } // Left encoder: Octave or VScaling + Root Note void OnLeftEncoderMove(int direction) { + if (preset_select) return; + + preset_modified = 1; if (scale_edit) { - root_note[sel_chan] = constrain(root_note[sel_chan] + direction, 0, 11); + channel[sel_chan].root_note = constrain(channel[sel_chan].root_note + direction, 0, 11); quantizer[sel_chan].Requantize(); return; } if (edit_mode == TRANSPOSE) { // Octave jump - int s = OC::Scales::GetScale(scale[sel_chan]).num_notes; - transpose[sel_chan] += (direction * s); - while (transpose[sel_chan] > CAL8_MAX_TRANSPOSE) transpose[sel_chan] -= s; - while (transpose[sel_chan] < -CAL8_MAX_TRANSPOSE) transpose[sel_chan] += s; + int s = OC::Scales::GetScale(channel[sel_chan].scale).num_notes; + channel[sel_chan].transpose += (direction * s); + while (channel[sel_chan].transpose > CAL8_MAX_TRANSPOSE) channel[sel_chan].transpose -= s; + while (channel[sel_chan].transpose < -CAL8_MAX_TRANSPOSE) channel[sel_chan].transpose += s; } else { // Tracking compensation - scale_factor[sel_chan] = constrain(scale_factor[sel_chan] + direction, -500, 500); + channel[sel_chan].scale_factor = constrain(channel[sel_chan].scale_factor + direction, -500, 500); } } // Right encoder: Semitones or Bias Offset + Scale Select void OnRightEncoderMove(int direction) { + if (preset_select) { + preset_select = constrain(preset_select + direction, 1, NR_OF_PRESETS + (1-edit_mode)); + return; + } + + preset_modified = 1; if (scale_edit) { - scale[sel_chan] += direction; - if (scale[sel_chan] >= OC::Scales::NUM_SCALES) scale[sel_chan] = 0; - if (scale[sel_chan] < 0) scale[sel_chan] = OC::Scales::NUM_SCALES - 1; - quantizer[sel_chan].Configure(OC::Scales::GetScale(scale[sel_chan]), 0xffff); + int s_ = channel[sel_chan].scale + direction; + if (s_ >= OC::Scales::NUM_SCALES) s_ = 0; + if (s_ < 0) s_ = OC::Scales::NUM_SCALES - 1; + + channel[sel_chan].scale = s_; + quantizer[sel_chan].Configure(OC::Scales::GetScale(s_), 0xffff); quantizer[sel_chan].Requantize(); return; } if (edit_mode == TRANSPOSE) { - transpose[sel_chan] = constrain(transpose[sel_chan] + direction, -CAL8_MAX_TRANSPOSE, CAL8_MAX_TRANSPOSE); + channel[sel_chan].transpose = constrain(channel[sel_chan].transpose + direction, -CAL8_MAX_TRANSPOSE, CAL8_MAX_TRANSPOSE); } else { - offset[sel_chan] = constrain(offset[sel_chan] + direction, -63, 64); + channel[sel_chan].offset = constrain(channel[sel_chan].offset + direction, -63, 64); } } @@ -263,25 +358,38 @@ private: int sel_chan = 0; int edit_mode = 0; // Cal8EditMode bool scale_edit = 0; + int preset_select = 0; // both a flag and an index + bool preset_modified = 0; SegmentDisplay segment; braids::Quantizer quantizer[NR_OF_CHANNELS]; - int scale[NR_OF_CHANNELS]; // Scale per channel - int root_note[NR_OF_CHANNELS]; // in semitones from C - int last_note[NR_OF_CHANNELS]; // for S&H mode - - uint8_t clocked_mode[NR_OF_CHANNELS]; - int scale_factor[NR_OF_CHANNELS] = {0,0,0,0}; // precision of 0.01% as an offset from 100% - int offset[NR_OF_CHANNELS] = {0,0,0,0}; // fine-tuning offset - int transpose[NR_OF_CHANNELS] = {0,0,0,0}; // in semitones - int transpose_active[NR_OF_CHANNELS] = {0,0,0,0}; // held value while waiting for trigger + Cal8ChannelConfig channel[NR_OF_CHANNELS]; + + void DrawPresetSelector() { + // TODO: Preset selection screen + // index is the currently loaded preset (0-3) + // preset_select is current selection (1-4, 5=clear) + int y = 5 + 10*preset_select; + gfxPrint(25, y, edit_mode ? "Save" : "Load"); + gfxIcon(50, y, RIGHT_ICON); + + for (int i = 0; i < NR_OF_PRESETS; ++i) { + gfxPrint(60, 15 + i*10, cal8_preset_id[i]); + if (!cal8_presets[i].is_valid()) + gfxPrint(" (empty)"); + else if (i == index) + gfxPrint(" *"); + } + if (!edit_mode) + gfxPrint(60, 55, "[CLEAR]"); + } void DrawInterface() { // Draw channel tabs for (int i = 0; i < NR_OF_CHANNELS; ++i) { gfxLine(i*32, 13, i*32, 22); // vertical line on left - if (clocked_mode[i]) gfxIcon(2 + i*32, 14, CLOCK_ICON); - if (clocked_mode[i] == SAMPLE_AND_HOLD) gfxIcon(22 + i*32, 14, STAIRS_ICON); + if (channel[i].clocked_mode) gfxIcon(2 + i*32, 14, CLOCK_ICON); + if (channel[i].clocked_mode == SAMPLE_AND_HOLD) gfxIcon(22 + i*32, 14, STAIRS_ICON); gfxPrint(i*32 + 13, 14, i+1); if (i == sel_chan) @@ -298,38 +406,38 @@ private: // -- LCD Display Section -- gfxFrame(20, y-3, 64, 18); - gfxIcon(23, y+2, (transpose[sel_chan] >= 0)? PLUS_ICON : MINUS_ICON); + gfxIcon(23, y+2, (channel[sel_chan].transpose >= 0)? PLUS_ICON : MINUS_ICON); - int s = OC::Scales::GetScale(scale[sel_chan]).num_notes; - int octave = transpose[sel_chan] / s; - int semitone = transpose[sel_chan] % s; + int s = OC::Scales::GetScale(channel[sel_chan].scale).num_notes; + int octave = channel[sel_chan].transpose / s; + int semitone = channel[sel_chan].transpose % s; segment.PrintWhole(33, y, abs(octave), 10); gfxPrint(53, y+5, "."); segment.PrintWhole(61, y, abs(semitone), 10); // Scale gfxIcon(89, y, SCALE_ICON); - gfxPrint(99, y, OC::scale_names_short[scale[sel_chan]]); + gfxPrint(99, y, OC::scale_names_short[channel[sel_chan].scale]); if (scale_edit) { gfxInvert(98, y-1, 29, 9); gfxIcon(100, y+10, RIGHT_ICON); } // Root Note - gfxPrint(110, y+10, OC::Strings::note_names_unpadded[root_note[sel_chan]]); + gfxPrint(110, y+10, OC::Strings::note_names_unpadded[channel[sel_chan].root_note]); // Tracking Compensation y += 22; gfxIcon(9, y, ZAP_ICON); - int whole = (scale_factor[sel_chan] + CAL8OR_PRECISION) / 100; - int decimal = (scale_factor[sel_chan] + CAL8OR_PRECISION) % 100; + int whole = (channel[sel_chan].scale_factor + CAL8OR_PRECISION) / 100; + int decimal = (channel[sel_chan].scale_factor + CAL8OR_PRECISION) % 100; gfxPrint(20 + pad(100, whole), y, whole); gfxPrint("."); if (decimal < 10) gfxPrint("0"); gfxPrint(decimal); gfxPrint("% "); - if (offset[sel_chan] >= 0) gfxPrint("+"); - gfxPrint(offset[sel_chan]); + if (channel[sel_chan].offset >= 0) gfxPrint("+"); + gfxPrint(channel[sel_chan].offset); // mode indicator if (!scale_edit) @@ -337,7 +445,7 @@ private: } }; -SETTINGS_DECLARE(Calibr8or, CAL8_SETTING_LAST) { +SETTINGS_DECLARE(Calibr8orPreset, CAL8_SETTING_LAST) { {0, 0, 1, "validity flag", NULL, settings::STORAGE_TYPE_U4}, {0, 0, 65535, "Scale A", NULL, settings::STORAGE_TYPE_U16}, @@ -365,59 +473,44 @@ SETTINGS_DECLARE(Calibr8or, CAL8_SETTING_LAST) { {0, 0, 255, "Root Key + Mode D", NULL, settings::STORAGE_TYPE_U8} }; -// To allow FOUR preset configs... I just made 4 copies of everything lol -Calibr8or Calibr8or_instance[4]; + +Calibr8or Calibr8or_instance; // App stubs -void Calibr8or_init() { Calibr8or_instance[0].BaseStart(); } -void Calibr8or_init(int index) { - Calibr8or_instance[index].BaseStart(); - Calibr8or_instance[index].set_index(index); +void Calibr8or_init() { Calibr8or_instance.BaseStart(); } + +size_t Calibr8or_storageSize() { + return Calibr8orPreset::storageSize() * NR_OF_PRESETS; +} + +size_t Calibr8or_save(void *storage) { + size_t used = 0; + for (int i = 0; i < 4; ++i) { + used += cal8_presets[i].Save(static_cast(storage) + used); + } + return used; } -void Calibr8orA_init() { Calibr8or_init(0); } -void Calibr8orB_init() { Calibr8or_init(1); } -void Calibr8orC_init() { Calibr8or_init(2); } -void Calibr8orD_init() { Calibr8or_init(3); } - -size_t Calibr8or_storageSize() { return Calibr8or::storageSize(); } -size_t Calibr8orA_storageSize() { return Calibr8or::storageSize(); } -size_t Calibr8orB_storageSize() { return Calibr8or::storageSize(); } -size_t Calibr8orC_storageSize() { return Calibr8or::storageSize(); } -size_t Calibr8orD_storageSize() { return Calibr8or::storageSize(); } - -size_t Calibr8or_save(void *storage) { return Calibr8or_instance[0].Save(storage); } -size_t Calibr8orA_save(void *storage) { return Calibr8or_instance[0].Save(storage); } -size_t Calibr8orB_save(void *storage) { return Calibr8or_instance[1].Save(storage); } -size_t Calibr8orC_save(void *storage) { return Calibr8or_instance[2].Save(storage); } -size_t Calibr8orD_save(void *storage) { return Calibr8or_instance[3].Save(storage); } - -size_t Calibr8or_restore(const void *storage, int index) { - size_t s = Calibr8or_instance[index].Restore(storage); - Calibr8or_instance[index].Resume(); - return s; + +size_t Calibr8or_restore(const void *storage) { + size_t used = 0; + for (int i = 0; i < 4; ++i) { + used += cal8_presets[i].Restore(static_cast(storage) + used); + } + Calibr8or_instance.LoadPreset(); + return used; } -size_t Calibr8or_restore(const void *storage) { return Calibr8or_restore(storage, 0); } -size_t Calibr8orA_restore(const void *storage) { return Calibr8or_restore(storage, 0); } -size_t Calibr8orB_restore(const void *storage) { return Calibr8or_restore(storage, 1); } -size_t Calibr8orC_restore(const void *storage) { return Calibr8or_restore(storage, 2); } -size_t Calibr8orD_restore(const void *storage) { return Calibr8or_restore(storage, 3); } - -void Calibr8or_isr() { return Calibr8or_instance[0].BaseController(); } -void Calibr8orA_isr() { return Calibr8or_instance[0].BaseController(); } -void Calibr8orB_isr() { return Calibr8or_instance[1].BaseController(); } -void Calibr8orC_isr() { return Calibr8or_instance[2].BaseController(); } -void Calibr8orD_isr() { return Calibr8or_instance[3].BaseController(); } - -void Calibr8or_handleAppEvent(OC::AppEvent event, int index) { + +void Calibr8or_isr() { return Calibr8or_instance.BaseController(); } + +void Calibr8or_handleAppEvent(OC::AppEvent event) { switch (event) { case OC::APP_EVENT_RESUME: - Calibr8or_instance[index].Resume(); + Calibr8or_instance.Resume(); break; // The idea is to auto-save when the screen times out... case OC::APP_EVENT_SUSPEND: case OC::APP_EVENT_SCREENSAVER_ON: - Calibr8or_instance[index].SaveToEEPROM(); // TODO: initiate actual EEPROM save // app_data_save(); break; @@ -425,76 +518,43 @@ void Calibr8or_handleAppEvent(OC::AppEvent event, int index) { default: break; } } -void Calibr8or_handleAppEvent(OC::AppEvent event) { - Calibr8or_handleAppEvent(event, 0); -} -void Calibr8orA_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 0); } -void Calibr8orB_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 1); } -void Calibr8orC_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 2); } -void Calibr8orD_handleAppEvent(OC::AppEvent event) { Calibr8or_handleAppEvent(event, 3); } void Calibr8or_loop() {} // Deprecated -void Calibr8orA_loop() {} // Deprecated -void Calibr8orB_loop() {} // Deprecated -void Calibr8orC_loop() {} // Deprecated -void Calibr8orD_loop() {} // Deprecated -void Calibr8or_menu() { Calibr8or_instance[0].BaseView(); } -void Calibr8orA_menu() { Calibr8or_instance[0].BaseView(); } -void Calibr8orB_menu() { Calibr8or_instance[1].BaseView(); } -void Calibr8orC_menu() { Calibr8or_instance[2].BaseView(); } -void Calibr8orD_menu() { Calibr8or_instance[3].BaseView(); } +void Calibr8or_menu() { Calibr8or_instance.BaseView(); } void Calibr8or_screensaver() { // XXX: Consider a view like Quantermain // other ideas: Actual note being played, current transpose setting // ...for all 4 channels at once. } -void Calibr8orA_screensaver() {} -void Calibr8orB_screensaver() {} -void Calibr8orC_screensaver() {} -void Calibr8orD_screensaver() {} -void Calibr8or_handleButtonEvent(const UI::Event &event, int index) { +void Calibr8or_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { - if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance[index].OnLeftButtonLongPress(); - else Calibr8or_instance[index].OnLeftButtonPress(); + if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance.OnLeftButtonLongPress(); + else Calibr8or_instance.OnLeftButtonPress(); } // For right encoder, only handle press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance[index].OnRightButtonPress(); + if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance.OnRightButtonPress(); // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) Calibr8or_instance[index].OnUpButtonPress(); + if (event.control == OC::CONTROL_BUTTON_UP) Calibr8or_instance.OnUpButtonPress(); // For down button, handle press and long press if (event.control == OC::CONTROL_BUTTON_DOWN) { - if (event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance[index].OnDownButtonPress(); - if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance[index].OnDownButtonLongPress(); + if (event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance.OnDownButtonPress(); + if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance.OnDownButtonLongPress(); } } -void Calibr8or_handleButtonEvent(const UI::Event &event) { - Calibr8or_handleButtonEvent(event, 0); -} -void Calibr8orA_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 0); } -void Calibr8orB_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 1); } -void Calibr8orC_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 2); } -void Calibr8orD_handleButtonEvent(const UI::Event &event) { Calibr8or_handleButtonEvent(event, 3); } -void Calibr8or_handleEncoderEvent(const UI::Event &event, int index) { +void Calibr8or_handleEncoderEvent(const UI::Event &event) { // Left encoder turned - if (event.control == OC::CONTROL_ENCODER_L) Calibr8or_instance[index].OnLeftEncoderMove(event.value); + if (event.control == OC::CONTROL_ENCODER_L) Calibr8or_instance.OnLeftEncoderMove(event.value); // Right encoder turned - if (event.control == OC::CONTROL_ENCODER_R) Calibr8or_instance[index].OnRightEncoderMove(event.value); -} -void Calibr8or_handleEncoderEvent(const UI::Event &event) { - Calibr8or_handleEncoderEvent(event, 0); + if (event.control == OC::CONTROL_ENCODER_R) Calibr8or_instance.OnRightEncoderMove(event.value); } -void Calibr8orA_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 0); } -void Calibr8orB_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 1); } -void Calibr8orC_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 2); } -void Calibr8orD_handleEncoderEvent(const UI::Event &event) { Calibr8or_handleEncoderEvent(event, 3); } #endif // ENABLE_APP_CALIBR8OR diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 3726414fd..b7eaf779e 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -38,13 +38,8 @@ } OC::App available_apps[] = { - #ifdef ENABLE_CALIBR8OR_X4 - DECLARE_APP('C','1', "Calibr8or A", Calibr8orA), - DECLARE_APP('C','2', "Calibr8or B", Calibr8orB), - DECLARE_APP('C','4', "Calibr8or C", Calibr8orC), - DECLARE_APP('C','8', "Calibr8or D", Calibr8orD), - #elif defined(ENABLE_APP_CALIBR8OR) - DECLARE_APP('C','1', "Calibr8or", Calibr8or), + #ifdef ENABLE_APP_CALIBR8OR + DECLARE_APP('C','8', "Calibr8or", Calibr8or), #endif DECLARE_APP('H','S', "Hemisphere", HEMISPHERE), diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 92d70b9af..2fcc7a821 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -34,8 +34,8 @@ upload_protocol = teensy-gui [env:cal8] build_flags = ${env.build_flags} - -DENABLE_CALIBR8OR_X4 - -DOC_VERSION_EXTRA="\" CAL8-0316\"" + -DENABLE_APP_CALIBR8OR + -DOC_VERSION_EXTRA="\" CAL8-0320\"" [env:oc_prod] build_flags = From e0566a27c525d6635ec17a7367715999be6ae996 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 20 Mar 2023 22:19:56 -0400 Subject: [PATCH 186/417] UI: Long-press fires while button is held --- software/o_c_REV/OC_ui.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index 58d91e558..d9deedd82 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -91,8 +91,10 @@ void FASTRUN Ui::Poll() { } else if (button.released()) { if (now - button_press_time_[i] < kLongPressTicks) PushEvent(UI::EVENT_BUTTON_PRESS, control_mask(i), 0, button_state); - else - PushEvent(UI::EVENT_BUTTON_LONG_PRESS, control_mask(i), 0, button_state); + button_press_time_[i] = 0; + } else if (button.pressed() && (now - button_press_time_[i] == kLongPressTicks)) { + button_state &= ~control_mask(i); + PushEvent(UI::EVENT_BUTTON_LONG_PRESS, control_mask(i), 0, button_state); } } From 00f40781ccc59447766201948c6cbfbc8c3ff824 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Mar 2023 05:56:58 -0400 Subject: [PATCH 187/417] New UI event type for BUTTON_DOWN Enables actions to fire immediately when pressing a button, instead of when you release it. Existing functionality for BUTTON_PRESS and BUTTON_LONG_PRESS works the same. I tried to fix all naive event.type assumptions in legacy apps. Hemisphere applets now use the new event type, which might be weird in some cases, but we're gonna try it! --- software/o_c_REV/APP_ENIGMA.ino | 6 ++--- software/o_c_REV/APP_HEMISPHERE.ino | 26 +++++++++++++++------ software/o_c_REV/APP_MIDI.ino | 7 +++--- software/o_c_REV/APP_NeuralNetwork.ino | 6 ++--- software/o_c_REV/APP_SCALEEDITOR.ino | 4 ++-- software/o_c_REV/APP_SETTINGS.ino | 4 ++-- software/o_c_REV/APP_THEDARKESTTIMELINE.ino | 6 ++--- software/o_c_REV/APP_WAVEFORMEDITOR.ino | 4 ++-- software/o_c_REV/OC_apps.ino | 22 ++++++++++++----- software/o_c_REV/OC_ui.cpp | 20 +++++++++------- software/o_c_REV/UI/ui_events.h | 1 + 11 files changed, 66 insertions(+), 40 deletions(-) diff --git a/software/o_c_REV/APP_ENIGMA.ino b/software/o_c_REV/APP_ENIGMA.ino index 069cac1ef..dbc7a62bc 100644 --- a/software/o_c_REV/APP_ENIGMA.ino +++ b/software/o_c_REV/APP_ENIGMA.ino @@ -1211,14 +1211,14 @@ void EnigmaTMWS_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { if (event.type == UI::EVENT_BUTTON_LONG_PRESS) EnigmaTMWS_instance.OnLeftButtonLongPress(); - else EnigmaTMWS_instance.OnLeftButtonPress(); + if (event.type == UI::EVENT_BUTTON_PRESS) EnigmaTMWS_instance.OnLeftButtonPress(); } // For right encoder, only handle press (long press is reserved) if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) EnigmaTMWS_instance.OnRightButtonPress(); // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) EnigmaTMWS_instance.OnUpButtonPress(); + if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) EnigmaTMWS_instance.OnUpButtonPress(); // For down button, handle press and long press if (event.control == OC::CONTROL_BUTTON_DOWN) { @@ -1236,4 +1236,4 @@ void EnigmaTMWS_handleEncoderEvent(const UI::Event &event) { } -#endif \ No newline at end of file +#endif diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index f7fc2ad01..3a4ddf4ff 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -191,12 +191,18 @@ public: select_mode = -1; // Pushing a button for the selected side turns off select mode } else { int index = my_applet[h]; - if (event.type == UI::EVENT_BUTTON_PRESS) { + if (event.type == UI::EVENT_BUTTON_DOWN) { available_applets[index].OnButtonPress(h); } } } + /* + void DelegateButtonRelease(const UI::Event &event) { + // TODO: + } + */ + void DelegateSelectButtonPush(int hemisphere) { if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME) { // This is a double-click, so activate corresponding help screen, leave @@ -220,10 +226,8 @@ public: } click_tick = OC::CORE::ticks; first_click = hemisphere; - } - - if (click_tick) clock_setup = 0; // Turn off clock setup with any single button press + } } void DelegateEncoderMovement(const UI::Event &event) { @@ -430,18 +434,26 @@ void HEMISPHERE_menu() { void HEMISPHERE_screensaver() {} // Deprecated in favor of screen blanking void HEMISPHERE_handleButtonEvent(const UI::Event &event) { - if (event.type == UI::EVENT_BUTTON_PRESS) { + switch (event.type) { + case UI::EVENT_BUTTON_DOWN: if (event.control == OC::CONTROL_BUTTON_UP || event.control == OC::CONTROL_BUTTON_DOWN) { int hemisphere = (event.control == OC::CONTROL_BUTTON_UP) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; manager.DelegateSelectButtonPush(hemisphere); } else { manager.DelegateEncoderPush(event); } - } + break; + case UI::EVENT_BUTTON_PRESS: + // TODO: + //manager.DelegateButtonRelease(event); + break; - if (event.type == UI::EVENT_BUTTON_LONG_PRESS) { + case UI::EVENT_BUTTON_LONG_PRESS: if (event.control == OC::CONTROL_BUTTON_DOWN) HemisphereApplet::CycleEditMode(); if (event.control == OC::CONTROL_BUTTON_L) manager.ToggleClockRun(); + break; + + default: break; } } diff --git a/software/o_c_REV/APP_MIDI.ino b/software/o_c_REV/APP_MIDI.ino index 04191eec7..a92410c89 100644 --- a/software/o_c_REV/APP_MIDI.ino +++ b/software/o_c_REV/APP_MIDI.ino @@ -884,10 +884,11 @@ void MIDI_handleButtonEvent(const UI::Event &event) { captain_midi_instance.ToggleCursor(); if (event.control == OC::CONTROL_BUTTON_L) { if (event.type == UI::EVENT_BUTTON_LONG_PRESS) captain_midi_instance.Panic(); - else captain_midi_instance.ToggleDisplay(); + if (event.type == UI::EVENT_BUTTON_PRESS) captain_midi_instance.ToggleDisplay(); } - if (event.control == OC::CONTROL_BUTTON_UP) captain_midi_instance.SwitchSetup(1); + if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) + captain_midi_instance.SwitchSetup(1); if (event.control == OC::CONTROL_BUTTON_DOWN) { if (event.type == UI::EVENT_BUTTON_PRESS) captain_midi_instance.SwitchSetup(-1); if (event.type == UI::EVENT_BUTTON_LONG_PRESS) captain_midi_instance.ToggleCopyMode(); @@ -909,4 +910,4 @@ void MIDI_handleEncoderEvent(const UI::Event &event) { } -#endif \ No newline at end of file +#endif diff --git a/software/o_c_REV/APP_NeuralNetwork.ino b/software/o_c_REV/APP_NeuralNetwork.ino index 17c0513c7..129f2949f 100644 --- a/software/o_c_REV/APP_NeuralNetwork.ino +++ b/software/o_c_REV/APP_NeuralNetwork.ino @@ -628,14 +628,14 @@ void NeuralNetwork_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { if (event.type == UI::EVENT_BUTTON_LONG_PRESS) NeuralNetwork_instance.OnLeftButtonLongPress(); - else NeuralNetwork_instance.OnLeftButtonPress(); + if (event.type == UI::EVENT_BUTTON_PRESS) NeuralNetwork_instance.OnLeftButtonPress(); } // For right encoder, only handle press (long press is reserved) if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) NeuralNetwork_instance.OnRightButtonPress(); // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) NeuralNetwork_instance.OnUpButtonPress(); + if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) NeuralNetwork_instance.OnUpButtonPress(); // For down button, handle press and long press if (event.control == OC::CONTROL_BUTTON_DOWN) { @@ -652,4 +652,4 @@ void NeuralNetwork_handleEncoderEvent(const UI::Event &event) { if (event.control == OC::CONTROL_ENCODER_R) NeuralNetwork_instance.OnRightEncoderMove(event.value); } -#endif \ No newline at end of file +#endif diff --git a/software/o_c_REV/APP_SCALEEDITOR.ino b/software/o_c_REV/APP_SCALEEDITOR.ino index 81a34a92e..e0d1cbdf7 100644 --- a/software/o_c_REV/APP_SCALEEDITOR.ino +++ b/software/o_c_REV/APP_SCALEEDITOR.ino @@ -374,15 +374,15 @@ void SCALEEDITOR_screensaver() {} void SCALEEDITOR_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { + if (event.type == UI::EVENT_BUTTON_PRESS) scale_editor_instance.OnLeftButtonPress(); if (event.type == UI::EVENT_BUTTON_LONG_PRESS) scale_editor_instance.OnLeftButtonLongPress(); - else scale_editor_instance.OnLeftButtonPress(); } // For right encoder, only handle press (long press is reserved) if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) scale_editor_instance.OnRightButtonPress(); // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) scale_editor_instance.OnUpButtonPress(); + if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) scale_editor_instance.OnUpButtonPress(); // For down button, handle press and long press if (event.control == OC::CONTROL_BUTTON_DOWN) { diff --git a/software/o_c_REV/APP_SETTINGS.ino b/software/o_c_REV/APP_SETTINGS.ino index c02219c82..b67db9dd9 100644 --- a/software/o_c_REV/APP_SETTINGS.ino +++ b/software/o_c_REV/APP_SETTINGS.ino @@ -158,14 +158,14 @@ void Settings_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Settings_instance.OnLeftButtonLongPress(); - else Settings_instance.OnLeftButtonPress(); + if (event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.OnLeftButtonPress(); } // For right encoder, only handle press (long press is reserved) if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.OnRightButtonPress(); // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) Settings_instance.OnUpButtonPress(); + if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.OnUpButtonPress(); // For down button, handle press and long press if (event.control == OC::CONTROL_BUTTON_DOWN) { diff --git a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino index 81379c838..ce7c31025 100644 --- a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino +++ b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino @@ -568,14 +568,14 @@ void TheDarkestTimeline_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { if (event.type == UI::EVENT_BUTTON_LONG_PRESS) TheDarkestTimeline_instance.OnLeftButtonLongPress(); - else TheDarkestTimeline_instance.OnLeftButtonPress(); + if (event.type == UI::EVENT_BUTTON_PRESS) TheDarkestTimeline_instance.OnLeftButtonPress(); } // For right encoder, only handle press (long press is reserved) if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) TheDarkestTimeline_instance.OnRightButtonPress(); // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) TheDarkestTimeline_instance.OnUpButtonPress(); + if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) TheDarkestTimeline_instance.OnUpButtonPress(); // For down button, handle press and long press if (event.control == OC::CONTROL_BUTTON_DOWN) { @@ -592,4 +592,4 @@ void TheDarkestTimeline_handleEncoderEvent(const UI::Event &event) { if (event.control == OC::CONTROL_ENCODER_R) TheDarkestTimeline_instance.OnRightEncoderMove(event.value); } -#endif \ No newline at end of file +#endif diff --git a/software/o_c_REV/APP_WAVEFORMEDITOR.ino b/software/o_c_REV/APP_WAVEFORMEDITOR.ino index dc7d12a55..2d7e927e0 100644 --- a/software/o_c_REV/APP_WAVEFORMEDITOR.ino +++ b/software/o_c_REV/APP_WAVEFORMEDITOR.ino @@ -400,14 +400,14 @@ void WaveformEditor_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { if (event.type == UI::EVENT_BUTTON_LONG_PRESS) WaveformEditor_instance.OnLeftButtonLongPress(); - else WaveformEditor_instance.OnLeftButtonPress(); + if (event.type == UI::EVENT_BUTTON_PRESS) WaveformEditor_instance.OnLeftButtonPress(); } // For right encoder, only handle press (long press is reserved) if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) WaveformEditor_instance.OnRightButtonPress(); // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) WaveformEditor_instance.OnUpButtonPress(); + if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) WaveformEditor_instance.OnUpButtonPress(); // For down button, handle press and long press if (event.control == OC::CONTROL_BUTTON_DOWN) { diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index b7eaf779e..21bafeb94 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -392,18 +392,28 @@ void Ui::AppSettings() { if (IgnoreEvent(event)) continue; - if (UI::EVENT_ENCODER == event.type && CONTROL_ENCODER_R == event.control) { - cursor.Scroll(event.value); - } else if (CONTROL_BUTTON_R == event.control) { + switch (event.control) { + case CONTROL_ENCODER_R: + if (UI::EVENT_ENCODER == event.type) + cursor.Scroll(event.value); + break; + + case CONTROL_BUTTON_R: save = event.type == UI::EVENT_BUTTON_LONG_PRESS; - change_app = true; - } else if (CONTROL_BUTTON_L == event.control) { + change_app = event.type != UI::EVENT_BUTTON_DOWN; // true on button release + break; + case CONTROL_BUTTON_L: ui.DebugStats(); - } else if (CONTROL_BUTTON_UP == event.control) { + break; + case CONTROL_BUTTON_UP: { bool enabled = !global_settings.encoders_enable_acceleration; SERIAL_PRINTLN("Encoder acceleration: %s", enabled ? "enabled" : "disabled"); ui.encoders_enable_acceleration(enabled); global_settings.encoders_enable_acceleration = enabled; + break; + } + + default: break; } } diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index d9deedd82..cfa05625a 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -88,6 +88,7 @@ void FASTRUN Ui::Poll() { auto &button = buttons_[i]; if (button.just_pressed()) { button_press_time_[i] = now; + PushEvent(UI::EVENT_BUTTON_DOWN, control_mask(i), 0, button_state); } else if (button.released()) { if (now - button_press_time_[i] < kLongPressTicks) PushEvent(UI::EVENT_BUTTON_PRESS, control_mask(i), 0, button_state); @@ -122,31 +123,32 @@ UiMode Ui::DispatchEvents(App *app) { switch (event.type) { case UI::EVENT_BUTTON_PRESS: - #ifdef VOR - #ifdef VOR_NO_RANGE_BUTTON +#ifdef VOR + #ifdef VOR_NO_RANGE_BUTTON if (OC::CONTROL_BUTTON_UP == event.control) { VBiasManager *vbias_m = vbias_m->get(); if (vbias_m->IsEditing()) vbias_m->AdvanceBias(); else app->HandleButtonEvent(event); } else app->HandleButtonEvent(event); - #else + #else if (OC::CONTROL_BUTTON_M == event.control) { VBiasManager *vbias_m = vbias_m->get(); vbias_m->AdvanceBias(); } else app->HandleButtonEvent(event); - #endif - #else + #endif +#else + case UI::EVENT_BUTTON_DOWN: app->HandleButtonEvent(event); - #endif +#endif break; case UI::EVENT_BUTTON_LONG_PRESS: if (OC::CONTROL_BUTTON_UP == event.control) { - #ifdef VOR_NO_RANGE_BUTTON +#ifdef VOR_NO_RANGE_BUTTON VBiasManager *vbias_m = vbias_m->get(); vbias_m->AdvanceBias(); - #else +#else if (!preempt_screensaver_) screensaver_ = true; - #endif +#endif } else if (OC::CONTROL_BUTTON_R == event.control) return UI_MODE_APP_SETTINGS; diff --git a/software/o_c_REV/UI/ui_events.h b/software/o_c_REV/UI/ui_events.h index 8b870c93c..1b6f73a24 100644 --- a/software/o_c_REV/UI/ui_events.h +++ b/software/o_c_REV/UI/ui_events.h @@ -27,6 +27,7 @@ namespace UI { enum EventType { EVENT_NONE, + EVENT_BUTTON_DOWN, EVENT_BUTTON_PRESS, EVENT_BUTTON_LONG_PRESS, EVENT_ENCODER From 57a68dd28f0dfccc6b105bfef912a4cfc25b3536 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 23 Mar 2023 14:18:20 -0400 Subject: [PATCH 188/417] Calibr8or: Add Clock Setup, UP+DOWN buttons to access Shares the ClockManager and ClockSetup applet with Hemispheres. Some refactoring was necessary. --- software/o_c_REV/APP_CALIBR8OR.ino | 140 ++++++++++++++++++++++------ software/o_c_REV/APP_HEMISPHERE.ino | 70 +++++--------- software/o_c_REV/HEM_ClockSetup.ino | 6 +- software/o_c_REV/HSApplication.h | 23 +++-- software/o_c_REV/HSClockManager.h | 12 ++- software/o_c_REV/HemisphereApplet.h | 38 ++++++++ 6 files changed, 196 insertions(+), 93 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index e7c317d9a..10768c016 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -26,12 +26,14 @@ #include "HSApplication.h" #include "HSMIDI.h" +#include "HSClockManager.h" #include "util/util_settings.h" #include "braids_quantizer.h" #include "braids_quantizer_scales.h" #include "OC_scales.h" #include "SegmentDisplay.h" #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" +#include "HemisphereApplet.h" #define CAL8_MAX_TRANSPOSE 60 const int CAL8OR_PRECISION = 10000; @@ -152,12 +154,6 @@ Calibr8orPreset cal8_presets[NR_OF_PRESETS]; class Calibr8or : public HSApplication { public: - enum Cal8EditMode { - TRANSPOSE, - TRACKING, - - NR_OF_EDITMODES - }; void Start() { segment.Init(SegmentSize::BIG_SEGMENTS); @@ -204,6 +200,20 @@ public: } void Controller() { + bool clock_sync = OC::DigitalInputs::clocked(); + bool reset = OC::DigitalInputs::clocked(); + + // flush MIDI input and catch incoming Clock + while (usbMIDI.read()) { + if (usbMIDI.getType() == usbMIDI.Clock) clock_sync = 1; + } + + // Advance internal clock, sync to external clock / reset + if (clock_m->IsRunning()) clock_m->SyncTrig( clock_sync, reset ); + + // ClockSetup applet handles MIDI Clock Out + HS::clock_setup_applet.Controller(0, 0); + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { bool clocked = Clock(ch); Cal8ChannelConfig &c = channel[ch]; @@ -229,7 +239,18 @@ public: } void View() { + if (clock_setup) { + HS::clock_setup_applet.View(0); + return; + } + gfxHeader("Calibr8or"); + /* TODO + if (clock_m->IsRunning() || clock_m->IsPaused()) { + // Metronome icon + graphics.drawBitmap8(56, 1, 8, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); + } + */ if (preset_select) { gfxPrint(64, 1, "- Presets"); @@ -249,9 +270,13 @@ public: // Control handlers ///////////////////////////////////////////////////////////////// void OnLeftButtonPress() { + if (clock_setup) { + HS::clock_setup_applet.OnButtonPress(0); + return; + } // Toggle between Transpose mode and Tracking Compensation // also doubles as Load or Save for preset select - ++edit_mode %= NR_OF_EDITMODES; + edit_mode = !edit_mode; // prevent saving to the (clear) slot if (edit_mode && preset_select == 5) preset_select = 4; @@ -266,6 +291,11 @@ public: } void OnRightButtonPress() { + if (clock_setup) { + HS::clock_setup_applet.OnButtonPress(0); + return; + } + if (preset_select) { // special case to clear values if (!edit_mode && preset_select == NR_OF_PRESETS + 1) { @@ -286,16 +316,29 @@ public: scale_edit = !scale_edit; } - void OnUpButtonPress() { - preset_select = 0; - - ++sel_chan %= NR_OF_CHANNELS; + void UpOrDownButtonPress(bool up) { + if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME && up != first_click) { + // show clock setup if both buttons pressed quickly + clock_setup = 1; + click_tick = 0; + } else { + click_tick = OC::CORE::ticks; + first_click = up; + } } - void OnDownButtonPress() { - preset_select = 0; + // fires on button release + void SwitchChannel(bool up) { + if (!clock_setup && !preset_select) { + sel_chan += (up? 1 : -1) + NR_OF_CHANNELS; + sel_chan %= NR_OF_CHANNELS; + } - if (--sel_chan < 0) sel_chan += NR_OF_CHANNELS; + if (click_tick) { + // always cancel clock setup and preset select on single click + clock_setup = 0; + preset_select = 0; + } } void OnDownButtonLongPress() { @@ -305,7 +348,14 @@ public: // Left encoder: Octave or VScaling + Root Note void OnLeftEncoderMove(int direction) { - if (preset_select) return; + if (clock_setup) { + HS::clock_setup_applet.OnEncoderMove(0, direction); + return; + } + if (preset_select) { + edit_mode = (direction>0); + return; + } preset_modified = 1; if (scale_edit) { @@ -314,7 +364,7 @@ public: return; } - if (edit_mode == TRANSPOSE) { // Octave jump + if (!edit_mode) { // Octave jump int s = OC::Scales::GetScale(channel[sel_chan].scale).num_notes; channel[sel_chan].transpose += (direction * s); while (channel[sel_chan].transpose > CAL8_MAX_TRANSPOSE) channel[sel_chan].transpose -= s; @@ -327,6 +377,10 @@ public: // Right encoder: Semitones or Bias Offset + Scale Select void OnRightEncoderMove(int direction) { + if (clock_setup) { + HS::clock_setup_applet.OnEncoderMove(0, direction); + return; + } if (preset_select) { preset_select = constrain(preset_select + direction, 1, NR_OF_PRESETS + (1-edit_mode)); return; @@ -344,7 +398,7 @@ public: return; } - if (edit_mode == TRANSPOSE) { + if (!edit_mode) { channel[sel_chan].transpose = constrain(channel[sel_chan].transpose + direction, -CAL8_MAX_TRANSPOSE, CAL8_MAX_TRANSPOSE); } else { @@ -356,15 +410,21 @@ private: int index = 0; int sel_chan = 0; - int edit_mode = 0; // Cal8EditMode + bool edit_mode = 0; bool scale_edit = 0; int preset_select = 0; // both a flag and an index bool preset_modified = 0; + uint32_t click_tick = 0; + bool first_click = 0; + bool clock_setup = 0; + SegmentDisplay segment; braids::Quantizer quantizer[NR_OF_CHANNELS]; Cal8ChannelConfig channel[NR_OF_CHANNELS]; + ClockManager *clock_m = clock_m->get(); + void DrawPresetSelector() { // TODO: Preset selection screen // index is the currently loaded preset (0-3) @@ -531,21 +591,41 @@ void Calibr8or_screensaver() { void Calibr8or_handleButtonEvent(const UI::Event &event) { // For left encoder, handle press and long press - if (event.control == OC::CONTROL_BUTTON_L) { - if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance.OnLeftButtonLongPress(); - else Calibr8or_instance.OnLeftButtonPress(); - } - // For right encoder, only handle press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance.OnRightButtonPress(); - // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP) Calibr8or_instance.OnUpButtonPress(); - // For down button, handle press and long press - if (event.control == OC::CONTROL_BUTTON_DOWN) { - if (event.type == UI::EVENT_BUTTON_PRESS) Calibr8or_instance.OnDownButtonPress(); - if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Calibr8or_instance.OnDownButtonLongPress(); + switch (event.type) { + case UI::EVENT_BUTTON_DOWN: + // check for clock setup secret combo (dual press) + if ( event.control == OC::CONTROL_BUTTON_DOWN || event.control == OC::CONTROL_BUTTON_UP) + Calibr8or_instance.UpOrDownButtonPress(event.control == OC::CONTROL_BUTTON_UP); + + break; + case UI::EVENT_BUTTON_PRESS: { + switch (event.control) { + case OC::CONTROL_BUTTON_L: + Calibr8or_instance.OnLeftButtonPress(); + break; + case OC::CONTROL_BUTTON_R: + Calibr8or_instance.OnRightButtonPress(); + break; + case OC::CONTROL_BUTTON_DOWN: + case OC::CONTROL_BUTTON_UP: + Calibr8or_instance.SwitchChannel(event.control == OC::CONTROL_BUTTON_UP); + break; + default: break; + } + } break; + case UI::EVENT_BUTTON_LONG_PRESS: + if (event.control == OC::CONTROL_BUTTON_L) { + Calibr8or_instance.OnLeftButtonLongPress(); + } + if (event.control == OC::CONTROL_BUTTON_DOWN) { + Calibr8or_instance.OnDownButtonLongPress(); + } + break; + + default: break; } } diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 3a4ddf4ff..ad3c1c144 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -25,35 +25,11 @@ #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" namespace menu = OC::menu; -#include "hemisphere_config.h" #include "HemisphereApplet.h" #include "HSicons.h" #include "HSMIDI.h" #include "HSClockManager.h" -#define DECLARE_APPLET(id, categories, class_name) \ -{ id, categories, class_name ## _Start, class_name ## _Controller, class_name ## _View, \ - class_name ## _OnButtonPress, class_name ## _OnEncoderMove, class_name ## _ToggleHelpScreen, \ - class_name ## _OnDataRequest, class_name ## _OnDataReceive \ -} - -#define HEMISPHERE_DOUBLE_CLICK_TIME 8000 -#define HEMISPHERE_PULSE_ANIMATION_TIME 500 -#define HEMISPHERE_PULSE_ANIMATION_TIME_LONG 1200 - -typedef struct Applet { - int id; - uint8_t categories; - void (*Start)(bool); // Initialize when selected - void (*Controller)(bool, bool); // Interrupt Service Routine - void (*View)(bool); // Draw main view - void (*OnButtonPress)(bool); // Encoder button has been pressed - void (*OnEncoderMove)(bool, int); // Encoder has been rotated - void (*ToggleHelpScreen)(bool); // Help Screen has been requested - uint64_t (*OnDataRequest)(bool); // Get a data int from the applet - void (*OnDataReceive)(bool, uint64_t); // Send a data int to the applet -} Applet; - // The settings specify the selected applets, and 64 bits of data for each applet enum HEMISPHERE_SETTINGS { HEMISPHERE_SELECTED_LEFT_ID, @@ -71,8 +47,7 @@ enum HEMISPHERE_SETTINGS { HEMISPHERE_SETTING_LAST }; -Applet available_applets[] = HEMISPHERE_APPLETS; -static constexpr int HEMISPHERE_AVAILABLE_APPLETS = ARRAY_SIZE(available_applets); +static constexpr int HEMISPHERE_AVAILABLE_APPLETS = ARRAY_SIZE(HS::available_applets); //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Manager @@ -85,8 +60,6 @@ public: select_mode = -1; // Not selecting midi_in_hemisphere = -1; // No MIDI In - ClockSetup = DECLARE_APPLET(9999, 0x01, ClockSetup); - help_hemisphere = -1; clock_setup = 0; @@ -104,18 +77,18 @@ public: (uint64_t(values_[6 + h]) << 32) | (uint64_t(values_[4 + h]) << 16) | (uint64_t(values_[2 + h])); - available_applets[index].OnDataReceive(h, data); + HS::available_applets[index].OnDataReceive(h, data); } - ClockSetup.OnDataReceive(0, (uint64_t(values_[HEMISPHERE_CLOCK_DATA2]) << 16) | + HS::clock_setup_applet.OnDataReceive(0, (uint64_t(values_[HEMISPHERE_CLOCK_DATA2]) << 16) | uint64_t(values_[HEMISPHERE_CLOCK_DATA1])); } void SetApplet(int hemisphere, int index) { my_applet[hemisphere] = index; if (midi_in_hemisphere == hemisphere) midi_in_hemisphere = -1; - if (available_applets[index].id & 0x80) midi_in_hemisphere = hemisphere; - available_applets[index].Start(hemisphere); - apply_value(hemisphere, available_applets[index].id); + if (HS::available_applets[index].id & 0x80) midi_in_hemisphere = hemisphere; + HS::available_applets[index].Start(hemisphere); + apply_value(hemisphere, HS::available_applets[index].id); } void ChangeApplet(int dir) { @@ -145,26 +118,26 @@ public: clock_m->SyncTrig( OC::DigitalInputs::clocked() ); // NJM: always execute ClockSetup controller - it handles MIDI clock out - ClockSetup.Controller(LEFT_HEMISPHERE, clock_m->IsForwarded()); + HS::clock_setup_applet.Controller(LEFT_HEMISPHERE, clock_m->IsForwarded()); for (int h = 0; h < 2; h++) { int index = my_applet[h]; - available_applets[index].Controller(h, clock_m->IsForwarded()); + HS::available_applets[index].Controller(h, clock_m->IsForwarded()); } } void DrawViews() { if (clock_setup) { - ClockSetup.View(LEFT_HEMISPHERE); + HS::clock_setup_applet.View(LEFT_HEMISPHERE); } else if (help_hemisphere > -1) { int index = my_applet[help_hemisphere]; - available_applets[index].View(help_hemisphere); + HS::available_applets[index].View(help_hemisphere); } else { for (int h = 0; h < 2; h++) { int index = my_applet[h]; - available_applets[index].View(h); + HS::available_applets[index].View(h); if (h == 0) { if (clock_m->IsRunning() || clock_m->IsPaused()) { // Metronome icon @@ -186,13 +159,13 @@ public: void DelegateEncoderPush(const UI::Event &event) { int h = (event.control == OC::CONTROL_BUTTON_L) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; if (clock_setup) { - ClockSetup.OnButtonPress(LEFT_HEMISPHERE); + HS::clock_setup_applet.OnButtonPress(LEFT_HEMISPHERE); } else if (select_mode == h) { select_mode = -1; // Pushing a button for the selected side turns off select mode } else { int index = my_applet[h]; if (event.type == UI::EVENT_BUTTON_DOWN) { - available_applets[index].OnButtonPress(h); + HS::available_applets[index].OnButtonPress(h); } } } @@ -233,12 +206,12 @@ public: void DelegateEncoderMovement(const UI::Event &event) { int h = (event.control == OC::CONTROL_ENCODER_L) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; if (clock_setup) { - ClockSetup.OnEncoderMove(LEFT_HEMISPHERE, event.value); + HS::clock_setup_applet.OnEncoderMove(LEFT_HEMISPHERE, event.value); } else if (select_mode == h) { ChangeApplet(event.value); } else { int index = my_applet[h]; - available_applets[index].OnEncoderMove(h, event.value); + HS::available_applets[index].OnEncoderMove(h, event.value); } } @@ -254,12 +227,12 @@ public: void SetHelpScreen(int hemisphere) { if (help_hemisphere > -1) { // Turn off the previous help screen int index = my_applet[help_hemisphere]; - available_applets[index].ToggleHelpScreen(help_hemisphere); + HS::available_applets[index].ToggleHelpScreen(help_hemisphere); } if (hemisphere > -1) { // Turn on the next hemisphere's screen int index = my_applet[hemisphere]; - available_applets[index].ToggleHelpScreen(hemisphere); + HS::available_applets[index].ToggleHelpScreen(hemisphere); } help_hemisphere = hemisphere; @@ -269,13 +242,13 @@ public: for (int h = 0; h < 2; h++) { int index = my_applet[h]; - uint64_t data = available_applets[index].OnDataRequest(h); + uint64_t data = HS::available_applets[index].OnDataRequest(h); apply_value(2 + h, data & 0xffff); apply_value(4 + h, (data >> 16) & 0xffff); apply_value(6 + h, (data >> 32) & 0xffff); apply_value(8 + h, (data >> 48) & 0xffff); } - uint64_t data = ClockSetup.OnDataRequest(0); + uint64_t data = HS::clock_setup_applet.OnDataRequest(0); apply_value(HEMISPHERE_CLOCK_DATA1, data & 0xffff); apply_value(HEMISPHERE_CLOCK_DATA2, (data >> 16) & 0xffff); } @@ -330,7 +303,6 @@ public: } private: - Applet ClockSetup; int my_applet[2]; // Indexes to available_applets int select_mode; bool clock_setup; @@ -348,7 +320,7 @@ private: int index = 0; for (int i = 0; i < HEMISPHERE_AVAILABLE_APPLETS; i++) { - if (available_applets[i].id == id) index = i; + if (HS::available_applets[i].id == id) index = i; } return index; } @@ -360,7 +332,7 @@ private: // If an applet uses MIDI In, it can only be selected in one // hemisphere, and is designated by bit 7 set in its id. - if (available_applets[index].id & 0x80) { + if (HS::available_applets[index].id & 0x80) { if (midi_in_hemisphere == (1 - select_mode)) { return get_next_applet_index(index, dir); } diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index c9a7c3972..87a969d6b 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -18,8 +18,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define FLASH_TICKS 1000 - class ClockSetup : public HemisphereApplet { public: @@ -70,7 +68,7 @@ public: else if (cursor == FORWARDING) clock_m->ToggleForwarding(); else if (cursor >= TRIG1) { clock_m->Boop(cursor-TRIG1); - flash_ticker = FLASH_TICKS; + flash_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; } else CursorAction(cursor, LAST_SETTING); } @@ -97,7 +95,7 @@ public: case TRIG3: case TRIG4: clock_m->Boop(cursor-TRIG1); - flash_ticker = FLASH_TICKS; + flash_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; break; case EXT_PPQN: diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 2150d8aa6..6968f945a 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -32,6 +32,7 @@ typedef int32_t simfloat; #endif #include "HSicons.h" +#include "HSClockManager.h" #ifndef HSAPPLICATION_H_ #define HSAPPLICATION_H_ @@ -133,13 +134,23 @@ class HSApplication { bool Clock(int ch) { bool clocked = 0; - if (ch == 0) clocked = OC::DigitalInputs::clocked(); - if (ch == 1) clocked = OC::DigitalInputs::clocked(); - if (ch == 2) clocked = OC::DigitalInputs::clocked(); - if (ch == 3) clocked = OC::DigitalInputs::clocked(); + ClockManager *clock_m = clock_m->get(); + + if (clock_m->IsRunning() && clock_m->GetMultiply(ch) != 0) + clocked = clock_m->Tock(ch); + else { + if (ch == 0) clocked = OC::DigitalInputs::clocked(); + if (ch == 1) clocked = OC::DigitalInputs::clocked(); + if (ch == 2) clocked = OC::DigitalInputs::clocked(); + if (ch == 3) clocked = OC::DigitalInputs::clocked(); + } + + // manual triggers + clocked = clocked || clock_m->Beep(ch); + if (clocked) { - cycle_ticks[ch] = OC::CORE::ticks - last_clock[ch]; - last_clock[ch] = OC::CORE::ticks; + cycle_ticks[ch] = OC::CORE::ticks - last_clock[ch]; + last_clock[ch] = OC::CORE::ticks; } return clocked; } diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 7c3090eab..6c6c7448c 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -102,7 +102,7 @@ class ClockManager { */ uint16_t GetTempo() {return tempo;} - // Resync multipliers, optionally skipping the first tock + // Reset - Resync multipliers, optionally skipping the first tock void Reset(bool count_skip = 0) { beat_tick = OC::CORE::ticks; for (int ch = 0; ch < NR_OF_CLOCKS; ch++) { @@ -111,15 +111,19 @@ class ClockManager { cycle = 1 - cycle; } - // used to align the internal clock with incoming clock pulses + // Nudge - Used to align the internal clock with incoming clock pulses + // The rationale is that it's better to be short by 1 than to overshoot by 1 void Nudge(int diff) { if (diff > 0) diff--; if (diff < 0) diff++; beat_tick += diff; } - // called on every tick when clock is running, before all Controllers - void SyncTrig(bool clocked) { + // call this on every tick when clock is running, before all Controllers + void SyncTrig(bool clocked, bool hard_reset = false) { + //if (!IsRunning()) return; + if (hard_reset) Reset(); + uint32_t now = OC::CORE::ticks; // Reset only when all multipliers have been met diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 3594ee023..9b3cc59a8 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -25,8 +25,12 @@ //// Hemisphere Applet Base Class //////////////////////////////////////////////////////////////////////////////// +#ifndef HEMISPHEREAPPLET_H_ +#define HEMISPHEREAPPLET_H_ + #include "HSicons.h" #include "HSClockManager.h" +#include "src/drivers/FreqMeasure/OC_FreqMeasure.h" #define LEFT_HEMISPHERE 0 #define RIGHT_HEMISPHERE 1 @@ -69,6 +73,38 @@ typedef int32_t simfloat; #define gfx_offset (hemisphere * 64) // Graphics offset, based on the side #define io_offset (hemisphere * 2) // Input/Output offset, based on the side +#define HEMISPHERE_DOUBLE_CLICK_TIME 8000 +#define HEMISPHERE_PULSE_ANIMATION_TIME 500 +#define HEMISPHERE_PULSE_ANIMATION_TIME_LONG 1200 + +#define DECLARE_APPLET(id, categories, class_name) \ +{ id, categories, class_name ## _Start, class_name ## _Controller, class_name ## _View, \ + class_name ## _OnButtonPress, class_name ## _OnEncoderMove, class_name ## _ToggleHelpScreen, \ + class_name ## _OnDataRequest, class_name ## _OnDataReceive \ +} + +#include "hemisphere_config.h" + +namespace HS { + +typedef struct Applet { + int id; + uint8_t categories; + void (*Start)(bool); // Initialize when selected + void (*Controller)(bool, bool); // Interrupt Service Routine + void (*View)(bool); // Draw main view + void (*OnButtonPress)(bool); // Encoder button has been pressed + void (*OnEncoderMove)(bool, int); // Encoder has been rotated + void (*ToggleHelpScreen)(bool); // Help Screen has been requested + uint64_t (*OnDataRequest)(bool); // Get a data int from the applet + void (*OnDataReceive)(bool, uint64_t); // Send a data int to the applet +} Applet; + +Applet available_applets[] = HEMISPHERE_APPLETS; +Applet clock_setup_applet = DECLARE_APPLET(9999, 0x01, ClockSetup); + +} + // Specifies where data goes in flash storage for each selcted applet, and how big it is typedef struct PackLocation { size_t location; @@ -546,3 +582,5 @@ uint32_t HemisphereApplet::cycle_ticks[4]; bool HemisphereApplet::changed_cv[4]; int HemisphereApplet::last_cv[4]; int HemisphereApplet::cursor_countdown[2]; + +#endif // HEMISPHEREAPPLET_H_ From e8c48bfbaff7b736deae8478993d87e65080906b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 23 Mar 2023 21:18:45 -0400 Subject: [PATCH 189/417] Calibr8or: Add Screensaver --- software/o_c_REV/APP_CALIBR8OR.ino | 42 +++++++++++++++++++++++------- software/o_c_REV/HSApplication.h | 5 ++++ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index 10768c016..c8d9aa42f 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -214,6 +214,7 @@ public: // ClockSetup applet handles MIDI Clock Out HS::clock_setup_applet.Controller(0, 0); + // -- core processing -- for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { bool clocked = Clock(ch); Cal8ChannelConfig &c = channel[ch]; @@ -235,6 +236,10 @@ public: output_cv += c.offset; Out(ch, output_cv); + + // for UI flashers + if (clocked) trigger_flash[ch] = HEMISPHERE_PULSE_ANIMATION_TIME; + else if (trigger_flash[ch]) --trigger_flash[ch]; } } @@ -245,18 +250,16 @@ public: } gfxHeader("Calibr8or"); - /* TODO + + // Metronome icon if (clock_m->IsRunning() || clock_m->IsPaused()) { - // Metronome icon graphics.drawBitmap8(56, 1, 8, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); } - */ if (preset_select) { - gfxPrint(64, 1, "- Presets"); + gfxPrint(70, 1, "- Presets"); DrawPresetSelector(); - } - else { + } else { gfxPos(110, 1); if (preset_modified) gfxPrint("*"); if (cal8_presets[index].is_valid()) gfxPrint(cal8_preset_id[index]); @@ -265,6 +268,27 @@ public: } } + void Screensaver() { + gfxDottedLine(0, 32, 127, 32); // horizontal baseline + for (int ch = 0; ch < 4; ++ch) + { + gfxPrint(8 + 32*ch, 55, midi_note_numbers[MIDIQuantizer::NoteNumber(channel[ch].last_note)] ); + if (trigger_flash[ch] > 0) gfxIcon(11 + 32*ch, 0, CLOCK_ICON); + + // input + int height = ProportionCV(ViewIn(ch), 32); + int y = constrain(32 - height, 0, 32); + gfxFrame(3 + (32 * ch), y, 6, abs(height)); + + // output + height = ProportionCV(ViewOut(ch), 32); + y = constrain(32 - height, 0, 32); + gfxInvert(11 + (32 * ch), y, 12, abs(height)); + + gfxLine(32 * ch, 0, 32*ch, 63); // vertical divider, left side + } + gfxLine(127, 0, 127, 63); // vertical line, right side + } ///////////////////////////////////////////////////////////////// // Control handlers @@ -419,6 +443,8 @@ private: bool first_click = 0; bool clock_setup = 0; + int trigger_flash[NR_OF_CHANNELS]; + SegmentDisplay segment; braids::Quantizer quantizer[NR_OF_CHANNELS]; Cal8ChannelConfig channel[NR_OF_CHANNELS]; @@ -584,9 +610,7 @@ void Calibr8or_loop() {} // Deprecated void Calibr8or_menu() { Calibr8or_instance.BaseView(); } void Calibr8or_screensaver() { - // XXX: Consider a view like Quantermain - // other ideas: Actual note being played, current transpose setting - // ...for all 4 channels at once. + Calibr8or_instance.Screensaver(); } void Calibr8or_handleButtonEvent(const UI::Event &event) { diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 6968f945a..0a02a6afb 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -271,6 +271,11 @@ class HSApplication { gfxLine(0, 12, 127, 12); } + int ProportionCV(int cv_value, int max_pixels) { + int prop = constrain(Proportion(cv_value, HSAPPLICATION_5V, max_pixels), -max_pixels, max_pixels); + return prop; + } + protected: // Check cursor blink cycle bool CursorBlink() { From 84e08d8ff94e46a9c707fe7572ef1bd0d71c11b8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 26 Mar 2023 20:14:01 -0400 Subject: [PATCH 190/417] UI event tweaks/fixes (button PRESS vs button DOWN) Clock Setup uses button-down for more immediacy. Regular Applets still use button-release because it gets complicated otherwise. At least the framework is there for both actions. --- software/o_c_REV/APP_CALIBR8OR.ino | 25 ++++---- software/o_c_REV/APP_HEMISPHERE.ino | 97 ++++++++++++++++------------- software/o_c_REV/OC_apps.ino | 20 +++--- software/o_c_REV/OC_calibration.ino | 2 +- software/o_c_REV/OC_debug.cpp | 4 +- software/o_c_REV/OC_ui.cpp | 4 +- 6 files changed, 86 insertions(+), 66 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index c8d9aa42f..c3702db31 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -294,10 +294,9 @@ public: // Control handlers ///////////////////////////////////////////////////////////////// void OnLeftButtonPress() { - if (clock_setup) { - HS::clock_setup_applet.OnButtonPress(0); - return; - } + // handled on button down + if (clock_setup) return; + // Toggle between Transpose mode and Tracking Compensation // also doubles as Load or Save for preset select edit_mode = !edit_mode; @@ -315,10 +314,8 @@ public: } void OnRightButtonPress() { - if (clock_setup) { - HS::clock_setup_applet.OnButtonPress(0); - return; - } + // handled on button down + if (clock_setup) return; if (preset_select) { // special case to clear values @@ -340,6 +337,14 @@ public: scale_edit = !scale_edit; } + void OnButtonDown(const UI::Event &event) { + // check for clock setup secret combo (dual press) + if ( event.control == OC::CONTROL_BUTTON_DOWN || event.control == OC::CONTROL_BUTTON_UP) + UpOrDownButtonPress(event.control == OC::CONTROL_BUTTON_UP); + else if (clock_setup) // pass button down to Clock Setup + HS::clock_setup_applet.OnButtonPress(0); + } + void UpOrDownButtonPress(bool up) { if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME && up != first_click) { // show clock setup if both buttons pressed quickly @@ -620,9 +625,7 @@ void Calibr8or_handleButtonEvent(const UI::Event &event) { // For down button, handle press and long press switch (event.type) { case UI::EVENT_BUTTON_DOWN: - // check for clock setup secret combo (dual press) - if ( event.control == OC::CONTROL_BUTTON_DOWN || event.control == OC::CONTROL_BUTTON_UP) - Calibr8or_instance.UpOrDownButtonPress(event.control == OC::CONTROL_BUTTON_UP); + Calibr8or_instance.OnButtonDown(event); break; case UI::EVENT_BUTTON_PRESS: { diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index ad3c1c144..331c03a88 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -157,50 +157,67 @@ public: } void DelegateEncoderPush(const UI::Event &event) { + bool down = (event.type == UI::EVENT_BUTTON_DOWN); int h = (event.control == OC::CONTROL_BUTTON_L) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; - if (clock_setup) { - HS::clock_setup_applet.OnButtonPress(LEFT_HEMISPHERE); - } else if (select_mode == h) { + + // button down + if (down) { + // Clock Setup is more immediate for manual triggers + if (clock_setup) HS::clock_setup_applet.OnButtonPress(LEFT_HEMISPHERE); + // TODO: consider a new OnButtonDown handler for applets + return; + } + + // button release + if (select_mode == h) { select_mode = -1; // Pushing a button for the selected side turns off select mode - } else { + } else if (!clock_setup) { + // regular applets get button release int index = my_applet[h]; - if (event.type == UI::EVENT_BUTTON_DOWN) { - HS::available_applets[index].OnButtonPress(h); - } + HS::available_applets[index].OnButtonPress(h); } } - /* - void DelegateButtonRelease(const UI::Event &event) { - // TODO: - } - */ - - void DelegateSelectButtonPush(int hemisphere) { - if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME) { - // This is a double-click, so activate corresponding help screen, leave - // Select Mode, and reset the double-click timer - if (hemisphere == first_click) - SetHelpScreen(hemisphere); - else // up + down simultaneous - clock_setup = 1; - select_mode = -1; - click_tick = 0; - } else { - // This is a single click. If a help screen is already selected, and the - // button is for the opposite one, go to the other help screen - if (help_hemisphere > -1) { - if (help_hemisphere != hemisphere) SetHelpScreen(hemisphere); - else SetHelpScreen(-1); // Leave help screen if corresponding button is clicked - } else if (!clock_setup) { - // If we're in the clock setup screen, we want to exit the setup without turning on Select Mode - if (hemisphere == select_mode) select_mode = -1; // Leave Select Mode is same button is pressed - else select_mode = hemisphere; // Otherwise, set Select Mode + void DelegateSelectButtonPush(const UI::Event &event) { + bool down = (event.type == UI::EVENT_BUTTON_DOWN); + int hemisphere = (event.control == OC::CONTROL_BUTTON_UP) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; + + // -- button down + if (down) { + if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME) { + // This is a double-click. Activate corresponding help screen or Clock Setup + if (hemisphere == first_click) + SetHelpScreen(hemisphere); + else // up + down simultaneous + clock_setup = 1; + + // leave Select Mode, and reset the double-click timer + select_mode = -1; + click_tick = 0; + } else { + // If a help screen is already selected, and the button is for + // the opposite one, go to the other help screen + if (help_hemisphere > -1) { + if (help_hemisphere != hemisphere) SetHelpScreen(hemisphere); + else SetHelpScreen(-1); // Leave help screen if corresponding button is clicked + } + + // mark this single click + click_tick = OC::CORE::ticks; + first_click = hemisphere; } - click_tick = OC::CORE::ticks; - first_click = hemisphere; - clock_setup = 0; // Turn off clock setup with any single button press + return; + } + + // -- button release + if (!clock_setup) { + // Select Mode + if (hemisphere == select_mode) select_mode = -1; // Exit Select Mode if same button is pressed + else select_mode = hemisphere; // Otherwise, set Select Mode } + + if (click_tick) + clock_setup = 0; // Turn off clock setup with any single-click button release } void DelegateEncoderMovement(const UI::Event &event) { @@ -408,17 +425,13 @@ void HEMISPHERE_screensaver() {} // Deprecated in favor of screen blanking void HEMISPHERE_handleButtonEvent(const UI::Event &event) { switch (event.type) { case UI::EVENT_BUTTON_DOWN: + case UI::EVENT_BUTTON_PRESS: if (event.control == OC::CONTROL_BUTTON_UP || event.control == OC::CONTROL_BUTTON_DOWN) { - int hemisphere = (event.control == OC::CONTROL_BUTTON_UP) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; - manager.DelegateSelectButtonPush(hemisphere); + manager.DelegateSelectButtonPush(event); } else { manager.DelegateEncoderPush(event); } break; - case UI::EVENT_BUTTON_PRESS: - // TODO: - //manager.DelegateButtonRelease(event); - break; case UI::EVENT_BUTTON_LONG_PRESS: if (event.control == OC::CONTROL_BUTTON_DOWN) HemisphereApplet::CycleEditMode(); diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 21bafeb94..84e44e262 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -403,15 +403,17 @@ void Ui::AppSettings() { change_app = event.type != UI::EVENT_BUTTON_DOWN; // true on button release break; case CONTROL_BUTTON_L: - ui.DebugStats(); - break; - case CONTROL_BUTTON_UP: { - bool enabled = !global_settings.encoders_enable_acceleration; - SERIAL_PRINTLN("Encoder acceleration: %s", enabled ? "enabled" : "disabled"); - ui.encoders_enable_acceleration(enabled); - global_settings.encoders_enable_acceleration = enabled; + if (UI::EVENT_BUTTON_PRESS == event.type) + ui.DebugStats(); break; + case CONTROL_BUTTON_UP: + if (UI::EVENT_BUTTON_PRESS == event.type) { + bool enabled = !global_settings.encoders_enable_acceleration; + SERIAL_PRINTLN("Encoder acceleration: %s", enabled ? "enabled" : "disabled"); + ui.encoders_enable_acceleration(enabled); + global_settings.encoders_enable_acceleration = enabled; } + break; default: break; } @@ -459,10 +461,10 @@ bool Ui::ConfirmReset() { UI::Event event = event_queue_.PullEvent(); if (IgnoreEvent(event)) continue; - if (CONTROL_BUTTON_R == event.control) { + if (CONTROL_BUTTON_R == event.control && UI::EVENT_BUTTON_PRESS == event.type) { confirm = true; done = true; - } else if (CONTROL_BUTTON_L == event.control) { + } else if (CONTROL_BUTTON_L == event.control && UI::EVENT_BUTTON_PRESS == event.type) { confirm = false; done = true; } diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index 630ec5c9d..16327ab42 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -441,7 +441,7 @@ void OC::Ui::Calibrate() { while (event_queue_.available()) { const UI::Event event = event_queue_.PullEvent(); - if (IgnoreEvent(event)) + if (IgnoreEvent(event) || event.type == UI::EVENT_BUTTON_DOWN) continue; switch (event.control) { diff --git a/software/o_c_REV/OC_debug.cpp b/software/o_c_REV/OC_debug.cpp index d9628edc5..470e0bd4f 100644 --- a/software/o_c_REV/OC_debug.cpp +++ b/software/o_c_REV/OC_debug.cpp @@ -159,9 +159,9 @@ void Ui::DebugStats() { while (event_queue_.available()) { UI::Event event = event_queue_.PullEvent(); - if (CONTROL_BUTTON_R == event.control) { + if (CONTROL_BUTTON_R == event.control && UI::EVENT_BUTTON_PRESS == event.type) { exit_loop = true; - } else if (CONTROL_BUTTON_L == event.control) { + } else if (CONTROL_BUTTON_L == event.control && UI::EVENT_BUTTON_PRESS == event.type) { ++current_menu; if (!current_menu->title || !current_menu->display_fn) current_menu = &debug_menus[0]; diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index cfa05625a..ed3500379 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -123,6 +123,9 @@ UiMode Ui::DispatchEvents(App *app) { switch (event.type) { case UI::EVENT_BUTTON_PRESS: + app->HandleButtonEvent(event); + break; + case UI::EVENT_BUTTON_DOWN: #ifdef VOR #ifdef VOR_NO_RANGE_BUTTON if (OC::CONTROL_BUTTON_UP == event.control) { @@ -137,7 +140,6 @@ UiMode Ui::DispatchEvents(App *app) { } else app->HandleButtonEvent(event); #endif #else - case UI::EVENT_BUTTON_DOWN: app->HandleButtonEvent(event); #endif break; From ed3eba1df6748ad359ac2a511dc8375603440c1e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 24 Mar 2023 22:52:47 -0400 Subject: [PATCH 191/417] Cursor behavior locked to "modal w/ wraparound" Long-press DOWN button goes to Clock Setup (for now) --- software/o_c_REV/APP_HEMISPHERE.ino | 2 +- software/o_c_REV/HemisphereApplet.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 331c03a88..81774a9d8 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -434,7 +434,7 @@ void HEMISPHERE_handleButtonEvent(const UI::Event &event) { break; case UI::EVENT_BUTTON_LONG_PRESS: - if (event.control == OC::CONTROL_BUTTON_DOWN) HemisphereApplet::CycleEditMode(); + if (event.control == OC::CONTROL_BUTTON_DOWN) manager.ToggleClockSetup(); if (event.control == OC::CONTROL_BUTTON_L) manager.ToggleClockRun(); break; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 9b3cc59a8..6feed2644 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -125,9 +125,11 @@ class HemisphereApplet { static int cursor_countdown[2]; static uint8_t modal_edit_mode; + /* we might need this again... static void CycleEditMode() { ++modal_edit_mode %= 3; } + */ virtual const char* applet_name(); // Maximum of 9 characters virtual void Start(); @@ -571,7 +573,7 @@ class HemisphereApplet { bool help_active; }; -uint8_t HemisphereApplet::modal_edit_mode = 1; // 0=old behavior, 1=modal editing, 2=modal with wraparound +uint8_t HemisphereApplet::modal_edit_mode = 2; // 0=old behavior, 1=modal editing, 2=modal with wraparound int HemisphereApplet::inputs[4]; int HemisphereApplet::outputs[4]; int HemisphereApplet::outputs_smooth[4]; From 92b94ea1550ee21812a7be779972f893c5a35751 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 25 Mar 2023 21:48:50 -0400 Subject: [PATCH 192/417] Calibr8or: sync to external MIDI Start/Stop/Clock --- software/o_c_REV/APP_CALIBR8OR.ino | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index c3702db31..b6f77d943 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -202,11 +202,27 @@ public: void Controller() { bool clock_sync = OC::DigitalInputs::clocked(); bool reset = OC::DigitalInputs::clocked(); + bool midi_sync = 0; // flush MIDI input and catch incoming Clock while (usbMIDI.read()) { - if (usbMIDI.getType() == usbMIDI.Clock) clock_sync = 1; + switch (usbMIDI.getType()) { + case usbMIDI.Clock: + clock_sync = 1; + midi_sync = 1; + break; + case usbMIDI.Start: + clock_m->Start(); + break; + case usbMIDI.Stop: + clock_m->Stop(); + break; + } + // TODO: do we need to handle any other MIDI input? + // We will have to in Hemisphere... for the MIDI In applet. + // Might need to delegate other messages or something } + if (midi_sync) clock_m->SetClockPPQN(24); // rudely snap to MIDI clock sync speed // Advance internal clock, sync to external clock / reset if (clock_m->IsRunning()) clock_m->SyncTrig( clock_sync, reset ); From b0e3662e5968830a5246762071412403353f81ce Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 27 Mar 2023 00:09:13 -0400 Subject: [PATCH 193/417] better FLIP_180 calibration fix Values are swapped on load / save --- software/o_c_REV/OC_ADC.h | 12 ------------ software/o_c_REV/OC_DAC.h | 19 +------------------ software/o_c_REV/OC_calibration.ino | 25 +++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/software/o_c_REV/OC_ADC.h b/software/o_c_REV/OC_ADC.h index 8b2f2fcbe..4d1a7de29 100644 --- a/software/o_c_REV/OC_ADC.h +++ b/software/o_c_REV/OC_ADC.h @@ -55,19 +55,11 @@ class ADC { template static int32_t value() { -#ifdef FLIP_180 - return calibration_data_->offset[ADC_CHANNEL_LAST-1 - channel] - (smoothed_[channel] >> kAdcValueShift); -#else return calibration_data_->offset[channel] - (smoothed_[channel] >> kAdcValueShift); -#endif } static int32_t value(ADC_CHANNEL channel) { -#ifdef FLIP_180 - return calibration_data_->offset[ADC_CHANNEL_LAST-1 - channel] - (smoothed_[channel] >> kAdcValueShift); -#else return calibration_data_->offset[channel] - (smoothed_[channel] >> kAdcValueShift); -#endif } static uint32_t raw_value(ADC_CHANNEL channel) { @@ -83,11 +75,7 @@ class ADC { } static int32_t raw_pitch_value(ADC_CHANNEL channel) { -#ifdef FLIP_180 - int32_t value = calibration_data_->offset[ADC_CHANNEL_LAST-1 - channel] - raw_value(channel); -#else int32_t value = calibration_data_->offset[channel] - raw_value(channel); -#endif return (value * calibration_data_->pitch_cv_scale) >> 12; } diff --git a/software/o_c_REV/OC_DAC.h b/software/o_c_REV/OC_DAC.h index 4eee7dce3..765b4e9dc 100644 --- a/software/o_c_REV/OC_DAC.h +++ b/software/o_c_REV/OC_DAC.h @@ -111,9 +111,6 @@ class DAC { const int32_t octave = pitch / (12 << 7); const int32_t fractional = pitch - octave * (12 << 7); -#ifdef FLIP_180 - channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); -#endif int32_t sample = calibration_data_->calibrated_octaves[channel][octave]; if (fractional) { int32_t span = calibration_data_->calibrated_octaves[channel][octave + 1] - sample; @@ -171,9 +168,6 @@ class DAC { const int32_t octave = pitch / (12 << 7); const int32_t fractional = pitch - octave * (12 << 7); -#ifdef FLIP_180 - channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); -#endif int32_t sample = calibration_data_->calibrated_octaves[channel][octave]; if (fractional) { int32_t span = calibration_data_->calibrated_octaves[channel][octave + 1] - sample; @@ -202,12 +196,7 @@ class DAC { // Set integer voltage value, where 0 = 0V, 1 = 1V static void set_octave(DAC_CHANNEL channel, int v) { -#ifdef FLIP_180 - int cal_ch = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); -#else - int cal_ch = channel; -#endif - set(channel, calibration_data_->calibrated_octaves[cal_ch][kOctaveZero + v]); + set(channel, calibration_data_->calibrated_octaves[channel][kOctaveZero + v]); } // Set all channels to integer voltage value, where 0 = 0V, 1 = 1V @@ -219,16 +208,10 @@ class DAC { } static uint32_t get_zero_offset(DAC_CHANNEL channel) { -#ifdef FLIP_180 - channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); -#endif return calibration_data_->calibrated_octaves[channel][kOctaveZero]; } static uint32_t get_octave_offset(DAC_CHANNEL channel, int octave) { -#ifdef FLIP_180 - channel = DAC_CHANNEL(DAC_CHANNEL_LAST - channel - 1); -#endif return calibration_data_->calibrated_octaves[channel][kOctaveZero + octave]; } diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index 16327ab42..0931e2c4c 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -79,6 +79,22 @@ void calibration_reset() { } } +#ifdef FLIP_180 +void calibration_flip() { + uint16_t flip_dac[OCTAVES + 1]; + uint16_t flip_adc; + for (int i = 0; i < 2; ++i) { + flip_adc = OC::calibration_data.adc.offset[i]; + OC::calibration_data.adc.offset[i] = OC::calibration_data.adc.offset[ADC_CHANNEL_LAST-1 - i]; + OC::calibration_data.adc.offset[ADC_CHANNEL_LAST-1 - i] = flip_adc; + + memcpy(flip_dac, OC::calibration_data.dac.calibrated_octaves[i], sizeof(flip_dac)); + memcpy(OC::calibration_data.dac.calibrated_octaves[i], OC::calibration_data.dac.calibrated_octaves[DAC_CHANNEL_LAST-1 - i], sizeof(flip_dac)); + memcpy(OC::calibration_data.dac.calibrated_octaves[DAC_CHANNEL_LAST-1 - i], flip_dac, sizeof(flip_dac)); + } +} +#endif + void calibration_load() { SERIAL_PRINTLN("Cal.Storage: PAGESIZE=%u, PAGES=%u, LENGTH=%u", OC::CalibrationStorage::PAGESIZE, OC::CalibrationStorage::PAGES, OC::CalibrationStorage::LENGTH); @@ -97,6 +113,9 @@ void calibration_load() { SERIAL_PRINTLN("No calibration data, using defaults"); #endif } else { +#ifdef FLIP_180 + calibration_flip(); +#endif SERIAL_PRINTLN("Calibration data loaded..."); } @@ -112,7 +131,13 @@ void calibration_load() { void calibration_save() { SERIAL_PRINTLN("Saving calibration data"); +#ifdef FLIP_180 + calibration_flip(); +#endif OC::calibration_storage.Save(OC::calibration_data); +#ifdef FLIP_180 + calibration_flip(); +#endif } enum CALIBRATION_STEP { From 5ceb009a59fc681ef77ef43b6ec6cd248fc742d0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 27 Mar 2023 14:24:58 -0400 Subject: [PATCH 194/417] ClockSetup: internal trigger flashers --- software/o_c_REV/HEM_ClockSetup.ino | 34 +++++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 87a969d6b..06d9f358f 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -55,7 +55,15 @@ public: } if (clock_m->IsRunning() && clock_m->MIDITock()) usbMIDI.sendRealTime(usbMIDI.Clock); - if (flash_ticker) --flash_ticker; + // 4 internal clock flashers + for (int i = 0; i < 4; ++i) { + if (clock_m->Tock(i)) + flash_ticker[i] = HEMISPHERE_PULSE_ANIMATION_TIME; + else if (flash_ticker[i]) + --flash_ticker[i]; + } + + if (button_ticker) --button_ticker; } void View() { @@ -68,7 +76,7 @@ public: else if (cursor == FORWARDING) clock_m->ToggleForwarding(); else if (cursor >= TRIG1) { clock_m->Boop(cursor-TRIG1); - flash_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; + button_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; } else CursorAction(cursor, LAST_SETTING); } @@ -95,7 +103,7 @@ public: case TRIG3: case TRIG4: clock_m->Boop(cursor-TRIG1); - flash_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; + button_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; break; case EXT_PPQN: @@ -154,7 +162,8 @@ private: int cursor; // ClockSetupCursor bool start_q; bool stop_q; - int flash_ticker; + int flash_ticker[4]; + int button_ticker; ClockManager *clock_m = clock_m->get(); void PlayStop() { @@ -199,17 +208,18 @@ private: gfxPrint(pad(100, clock_m->GetTempo()), clock_m->GetTempo()); gfxPrint(" BPM"); - // Multiply for (int ch=0; ch<4; ++ch) { int mult = clock_m->GetMultiply(ch); - gfxPrint(1 + ch*32, 37, (mult >= 0) ? "x" : "/"); + int x = ch * 32; + + // Multipliers + gfxPrint(1 + x, 37, (mult >= 0) ? "x" : "/"); gfxPrint( (mult >= 0) ? mult : 1 - mult ); - } - // Manual triggers - for (int i=0; i<4; i++) { - gfxIcon(4 + i*32, 49, (flash_ticker && i == cursor-TRIG1)?BTN_ON_ICON:BTN_OFF_ICON); - gfxIcon(4 + i*32, 56, DOWN_BTN_ICON); + // Manual triggers + gfxIcon(4 + x, 47, (button_ticker && ch == cursor-TRIG1)?BTN_ON_ICON:BTN_OFF_ICON); + gfxIcon(4 + x, 54, DOWN_BTN_ICON); + if (flash_ticker[ch]) gfxInvert(3 + x, 56, 9, 8); } switch ((ClockSetupCursor)cursor) { @@ -235,7 +245,7 @@ private: case TRIG2: case TRIG3: case TRIG4: - if (0 == flash_ticker) + if (0 == button_ticker) gfxIcon(12 + 32*(cursor-TRIG1), 50, LEFT_ICON); break; From 4cc6ab0cbe12837c0c248fa6c9e6baab49c73c06 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 27 Mar 2023 15:44:32 -0400 Subject: [PATCH 195/417] Build config updates --- software/o_c_REV/APP_H1200.ino | 2 ++ software/o_c_REV/platformio.ini | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/APP_H1200.ino b/software/o_c_REV/APP_H1200.ino index 3a88a6c43..7a184f436 100644 --- a/software/o_c_REV/APP_H1200.ino +++ b/software/o_c_REV/APP_H1200.ino @@ -33,6 +33,8 @@ #include "util/util_ringbuffer.h" #include "bjorklund.h" +namespace menu = OC::menu; + // NOTE: H1200 state is updated in the ISR, and we're accessing shared state // (e.g. outputs) without any sync mechanism. So there is a chance of the // display being slightly inconsistent but the risk seems acceptable. diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 2fcc7a821..80338342b 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -35,7 +35,7 @@ upload_protocol = teensy-gui build_flags = ${env.build_flags} -DENABLE_APP_CALIBR8OR - -DOC_VERSION_EXTRA="\" CAL8-0320\"" + -DOC_VERSION_EXTRA="\" CAL8-0327\"" [env:oc_prod] build_flags = @@ -46,20 +46,21 @@ build_flags = -DENABLE_APP_NEURAL_NETWORK -DENABLE_APP_PONG -DENABLE_APP_DARKEST_TIMELINE - -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_H1200 -DENABLE_APP_PIQUED + -DENABLE_APP_POLYLFO [env:oc_stock] build_flags = ${env.build_flags} -DENABLE_APP_PONG -DENABLE_APP_QUANTERMAIN - -DENABLE_APP_METAQ +; -DENABLE_APP_METAQ -DENABLE_APP_PIQUED -DENABLE_APP_CHORDS -DENABLE_APP_SEQUINS -DENABLE_APP_POLYLFO -; -DENABLE_APP_H1200 + -DENABLE_APP_H1200 -DOC_VERSION_EXTRA="\" +stock\"" [env:oc_prod_flipped] From 9d847c44b7590a90e3761bd45475c77739b969f0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 27 Mar 2023 19:04:09 -0400 Subject: [PATCH 196/417] Quadraturia: fix freqency display printf does not handle floats when using TEENSY_OPT_SMALLEST_CODE --- software/o_c_REV/APP_POLYLFO.ino | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/APP_POLYLFO.ino b/software/o_c_REV/APP_POLYLFO.ino index 75c1ab831..402a5f30d 100644 --- a/software/o_c_REV/APP_POLYLFO.ino +++ b/software/o_c_REV/APP_POLYLFO.ino @@ -379,12 +379,19 @@ void POLYLFO_menu() { graphics.print("(T) Ch A: tap tempo") ; } else { float menu_freq_ = poly_lfo.lfo.get_freq_ch1(); + if (poly_lfo.freq_mult() < 0xFF) graphics.drawBitmap8(122, menu::DefaultTitleBar::kTextY, 4, OC::bitmap_indicator_4x8); if (menu_freq_ >= 0.1f) { - graphics.printf("(%s) Ch A: %6.2f Hz", PolyLfo::value_attr(poly_lfo_state.left_edit_mode).name, menu_freq_); + const int f = int(floor(menu_freq_ * 100)); + const int value = f / 100; + const int cents = f % 100; + graphics.printf("(%s) Ch A: %3u.%02u Hz", PolyLfo::value_attr(poly_lfo_state.left_edit_mode).name, value, cents); } else { - graphics.printf("(%s) Ch A: %6.3fs", PolyLfo::value_attr(poly_lfo_state.left_edit_mode).name, 1.0f / menu_freq_); + const int f = int(floor(1.0f / menu_freq_ * 1000)); + const int value = f / 1000; + const int cents = f % 1000; + graphics.printf("(%s) Ch A: %6u.%03us", PolyLfo::value_attr(poly_lfo_state.left_edit_mode).name, value, cents); } } menu::SettingsList settings_list(poly_lfo_state.cursor); From 14da3e7f64aa607059e2d0a65f8e52cf243df9c8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 27 Mar 2023 18:06:03 -0400 Subject: [PATCH 197/417] MIDI updates, using latest usbMIDI spec for Teensy --- software/o_c_REV/APP_HEMISPHERE.ino | 2 +- software/o_c_REV/APP_MIDI.ino | 16 +-- software/o_c_REV/APP_THEDARKESTTIMELINE.ino | 4 +- software/o_c_REV/HEM_hMIDIIn.ino | 115 ++++++++++++-------- software/o_c_REV/HSMIDI.h | 20 ++-- 5 files changed, 93 insertions(+), 64 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 81774a9d8..8dc325c1b 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -108,7 +108,7 @@ public: // for another MIDI In applet before looking for sysex. Note that applets // that use MIDI In should check for sysex themselves; see Midi In for an // example. - if (usbMIDI.read() && usbMIDI.getType() == 7) { + if (usbMIDI.read() && usbMIDI.getType() == usbMIDI.SystemExclusive) { OnReceiveSysEx(); } } diff --git a/software/o_c_REV/APP_MIDI.ino b/software/o_c_REV/APP_MIDI.ino index a92410c89..1c76769ed 100644 --- a/software/o_c_REV/APP_MIDI.ino +++ b/software/o_c_REV/APP_MIDI.ino @@ -638,10 +638,10 @@ private: int data2 = usbMIDI.getData2(); // Handle system exclusive dump for Setup data - if (message == MIDI_MSG_SYSEX) OnReceiveSysEx(); + if (message == HEM_MIDI_SYSEX) OnReceiveSysEx(); // Listen for incoming clock - if (message == MIDI_MSG_REALTIME && data1 == 0) { + if (message == HEM_MIDI_CLOCK) { if (++clock_count >= 24) clock_count = 0; } @@ -655,7 +655,7 @@ private: int in_fn = get_in_assign(ch); int in_ch = get_in_channel(ch); bool indicator = 0; - if (message == MIDI_MSG_NOTE_ON && in_ch == channel) { + if (message == HEM_MIDI_NOTE_ON && in_ch == channel) { if (note_in[ch] == -1) { // If this channel isn't already occupied with another note, handle Note On if (in_fn == MIDI_IN_NOTE && !note_captured) { // Send quantized pitch CV. Isolate transposition to quantizer so that it notes off aren't @@ -694,7 +694,7 @@ private: } } - if (message == MIDI_MSG_NOTE_OFF && in_ch == channel) { + if (message == HEM_MIDI_NOTE_OFF && in_ch == channel) { if (note_in[ch] == data1) { // If the note off matches the note on assingned to this output note_in[ch] = -1; if (in_fn == MIDI_IN_GATE) { @@ -711,7 +711,7 @@ private: } bool cc = (in_fn == MIDI_IN_MOD || in_fn >= MIDI_IN_EXPRESSION); - if (cc && message == MIDI_MSG_MIDI_CC && in_ch == channel) { + if (cc && message == HEM_MIDI_CC && in_ch == channel) { uint8_t cc = 1; // Modulation wheel if (in_fn == MIDI_IN_EXPRESSION) cc = 11; if (in_fn == MIDI_IN_PAN) cc = 10; @@ -728,14 +728,14 @@ private: } } - if (message == MIDI_MSG_AFTERTOUCH && in_fn == MIDI_IN_AFTERTOUCH && in_ch == channel) { + if (message == HEM_MIDI_AFTERTOUCH && in_fn == MIDI_IN_AFTERTOUCH && in_ch == channel) { // Send aftertouch to CV - Out(ch, Proportion(data2, 127, HSAPPLICATION_5V)); + Out(ch, Proportion(data1, 127, HSAPPLICATION_5V)); UpdateLog(1, ch, 3, in_ch, data1, data2); indicator = 1; } - if (message == MIDI_MSG_PITCHBEND && in_fn == MIDI_IN_PITCHBEND && in_ch == channel) { + if (message == HEM_MIDI_PITCHBEND && in_fn == MIDI_IN_PITCHBEND && in_ch == channel) { // Send pitch bend to CV int data = (data2 << 7) + data1 - 8192; Out(ch, Proportion(data, 0x7fff, HSAPPLICATION_3V)); diff --git a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino index ce7c31025..be4f3ab0f 100644 --- a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino +++ b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino @@ -81,9 +81,9 @@ public: int data2 = usbMIDI.getData2(); // Handle system exclusive dump for Setup data - if (message == MIDI_MSG_SYSEX) OnReceiveSysEx(); + if (message == HEM_MIDI_SYSEX) OnReceiveSysEx(); - if (message == MIDI_MSG_NOTE_ON && channel == midi_channel_in()) { + if (message == HEM_MIDI_NOTE_ON && channel == midi_channel_in()) { note_on = 1; in_note_number = data1; in_velocity = data2; diff --git a/software/o_c_REV/HEM_hMIDIIn.ino b/software/o_c_REV/HEM_hMIDIIn.ino index bb17f85cb..1df5d5948 100644 --- a/software/o_c_REV/HEM_hMIDIIn.ino +++ b/software/o_c_REV/HEM_hMIDIIn.ino @@ -22,24 +22,6 @@ #define HEM_MIDI_CLOCK_DIVISOR 12 -#define HEM_MIDI_NOTE_ON 1 -#define HEM_MIDI_NOTE_OFF 0 -#define HEM_MIDI_CC 3 -#define HEM_MIDI_AFTERTOUCH 5 -#define HEM_MIDI_PITCHBEND 6 -#define HEM_MIDI_SYSEX 7 -#define HEM_MIDI_REALTIME 8 - -// The functions available for each output -#define HEM_MIDI_NOTE_OUT 0 -#define HEM_MIDI_TRIG_OUT 1 -#define HEM_MIDI_GATE_OUT 2 -#define HEM_MIDI_VEL_OUT 3 -#define HEM_MIDI_CC_OUT 4 -#define HEM_MIDI_AT_OUT 5 -#define HEM_MIDI_PB_OUT 6 -#define HEM_MIDI_REALTIME_OUT 7 - struct MIDILogEntry { int message; int data1; @@ -49,6 +31,23 @@ struct MIDILogEntry { class hMIDIIn : public HemisphereApplet { public: +// The functions available for each output +// TODO: make this an enum + enum hMIDIFunctions { + HEM_MIDI_NOTE_OUT, + HEM_MIDI_TRIG_OUT, + HEM_MIDI_GATE_OUT, + HEM_MIDI_VEL_OUT, + HEM_MIDI_CC_OUT, + HEM_MIDI_AT_OUT, + HEM_MIDI_PB_OUT, + HEM_MIDI_CLOCK_OUT, + HEM_MIDI_START_OUT, + + HEM_MIDI_MAX_FUNCTION = HEM_MIDI_START_OUT + }; + const char* fn_name[HEM_MIDI_MAX_FUNCTION + 1] = {"Note#", "Trig", "Gate", "Veloc", "Mod", "Aft", "Bend", "Clock", "Start"}; + const char* applet_name() { return "MIDIIn"; } @@ -68,32 +67,51 @@ public: } void Controller() { - if (usbMIDI.read()) { + while (usbMIDI.read()) { int message = usbMIDI.getType(); int data1 = usbMIDI.getData1(); int data2 = usbMIDI.getData2(); - if (message == HEM_MIDI_SYSEX) ReceiveManagerSysEx(); + if (message == HEM_MIDI_SYSEX) { + ReceiveManagerSysEx(); + continue; + } // Listen for incoming clock - if (message == HEM_MIDI_REALTIME && data1 == 0) { + if (HEM_MIDI_CLOCK == message) { if (++clock_count == 1) { ForEachChannel(ch) { - if (function[ch] == HEM_MIDI_REALTIME_OUT) { + if (function[ch] == HEM_MIDI_CLOCK_OUT) { ClockOut(ch); } } } if (clock_count == HEM_MIDI_CLOCK_DIVISOR) clock_count = 0; + continue; + } + + if (HEM_MIDI_START == message) { + ForEachChannel(ch) + { + if (function[ch] == HEM_MIDI_START_OUT) { + ClockOut(ch); + } + } + + UpdateLog(message, data1, data2); + continue; } - if (usbMIDI.getChannel() == (channel + 1)) { + // all other messages are filtered by MIDI channel + if (usbMIDI.getChannel() == (channel + 1)) + { last_tick = OC::CORE::ticks; bool log_this = false; if (message == HEM_MIDI_NOTE_ON) { // Note on - if (first_note == -1) first_note = data1; + // only track the most recent note + first_note = data1; // Should this message go out on any channel? ForEachChannel(ch) @@ -114,8 +132,10 @@ public: log_this = 1; // Log all MIDI notes. Other stuff is conditional. } - if (message == HEM_MIDI_NOTE_OFF) { // Note off - if (data1 == first_note) first_note = -1; + // Note off - only for most recent note + if (message == HEM_MIDI_NOTE_OFF && data1 == first_note) + { + first_note = -1; // Should this message go out on any channel? ForEachChannel(ch) @@ -131,8 +151,7 @@ public: ForEachChannel(ch) { if (function[ch] == HEM_MIDI_CC_OUT && data1 == 1) { - int data = data2 << 8; - Out(ch, Proportion(data, 0x7fff, HEMISPHERE_MAX_CV)); + Out(ch, Proportion(data2, 127, HEMISPHERE_MAX_CV)); log_this = 1; } } @@ -143,13 +162,10 @@ public: ForEachChannel(ch) { if (function[ch] == HEM_MIDI_AT_OUT) { - int data = data2 << 8; - Out(ch, Proportion(data, 0x7fff, HEMISPHERE_MAX_CV)); + Out(ch, Proportion(data1, 127, HEMISPHERE_MAX_CV)); log_this = 1; } } - - UpdateLog(message, data1, data2); } if (message == HEM_MIDI_PITCHBEND) { // Pitch Bend @@ -165,7 +181,7 @@ public: if (log_this) UpdateLog(message, data1, data2); } - } + } // while } void View() { @@ -189,7 +205,7 @@ public: if (cursor == 0) channel = constrain(channel + direction, 0, 15); else { int ch = cursor - 1; - function[ch] = constrain(function[ch] + direction, 0, 7); + function[ch] = constrain(function[ch] + direction, 0, HEM_MIDI_MAX_FUNCTION); clock_count = 0; } ResetCursor(); @@ -228,7 +244,6 @@ private: int cursor; // 0=MIDI channel, 1=A/C function, 2=B/D function int last_tick; // Tick of last received message int first_note; // First note received, for awaiting Note Off - const char* fn_name[8] = {"Note#", "Trig", "Gate", "Veloc", "Mod", "Aft", "Bend", "Clock"}; uint8_t clock_count; // MIDI clock counter (24ppqn) // Logging @@ -287,31 +302,41 @@ private: } void log_entry(int y, int index) { - if (log[index].message == HEM_MIDI_NOTE_ON) { + switch ( log[index].message ) { + case HEM_MIDI_NOTE_ON: gfxBitmap(1, y, 8, NOTE_ICON); gfxPrint(10, y, midi_note_numbers[log[index].data1]); gfxPrint(40, y, log[index].data2); - } + break; - if (log[index].message == HEM_MIDI_NOTE_OFF) { + case HEM_MIDI_NOTE_OFF: gfxPrint(1, y, "-"); gfxPrint(10, y, midi_note_numbers[log[index].data1]); - } + break; - if (log[index].message == HEM_MIDI_CC) { + case HEM_MIDI_CC: gfxBitmap(1, y, 8, MOD_ICON); gfxPrint(10, y, log[index].data2); - } + break; - if (log[index].message == HEM_MIDI_AFTERTOUCH) { + case HEM_MIDI_AFTERTOUCH: gfxBitmap(1, y, 8, AFTERTOUCH_ICON); - gfxPrint(10, y, log[index].data2); - } + gfxPrint(10, y, log[index].data1); + break; - if (log[index].message == HEM_MIDI_PITCHBEND) { + case HEM_MIDI_PITCHBEND: { int data = (log[index].data2 << 7) + log[index].data1 - 8192; gfxBitmap(1, y, 8, BEND_ICON); gfxPrint(10, y, data); + break; + } + + default: + gfxPrint(1, y, "?"); + gfxPrint(10, y, log[index].data1); + gfxPrint(" "); + gfxPrint(log[index].data2); + break; } } }; diff --git a/software/o_c_REV/HSMIDI.h b/software/o_c_REV/HSMIDI.h index c96643329..ec42cad39 100644 --- a/software/o_c_REV/HSMIDI.h +++ b/software/o_c_REV/HSMIDI.h @@ -32,13 +32,17 @@ // Teensyduino USB MIDI Library message numbers // See https://www.pjrc.com/teensy/td_midi.html -const uint8_t MIDI_MSG_NOTE_ON = 1; -const uint8_t MIDI_MSG_NOTE_OFF = 0; -const uint8_t MIDI_MSG_MIDI_CC = 3; -const uint8_t MIDI_MSG_AFTERTOUCH = 5; -const uint8_t MIDI_MSG_PITCHBEND = 6; -const uint8_t MIDI_MSG_SYSEX = 7; -const uint8_t MIDI_MSG_REALTIME = 8; + +#define HEM_MIDI_NOTE_ON usbMIDI.NoteOn +#define HEM_MIDI_NOTE_OFF usbMIDI.NoteOff +#define HEM_MIDI_CC usbMIDI.ControlChange +#define HEM_MIDI_AFTERTOUCH usbMIDI.AfterTouchChannel +#define HEM_MIDI_PITCHBEND usbMIDI.PitchBend +#define HEM_MIDI_SYSEX usbMIDI.SystemExclusive + +#define HEM_MIDI_CLOCK usbMIDI.Clock +#define HEM_MIDI_START usbMIDI.Start +#define HEM_MIDI_STOP usbMIDI.Stop const char* const midi_note_numbers[128] = { "C-1","C#-1","D-1","D#-1","E-1","F-1","F#-1","G-1","G#-1","A-1","A#-1","B-1", @@ -228,7 +232,7 @@ class SystemExclusiveHandler { bool ListenForSysEx() { bool heard_sysex = 0; if (usbMIDI.read()) { - if (usbMIDI.getType() == 7) { + if (usbMIDI.getType() == usbMIDI.SystemExclusive) { OnReceiveSysEx(); heard_sysex = 1; } From 820a77eaca92ab5ed068ca5061c6e3fc5e1c4b78 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 27 Mar 2023 19:15:24 -0400 Subject: [PATCH 198/417] Send MIDI Start/Stop when Toggling Clock by long-press --- software/o_c_REV/APP_HEMISPHERE.ino | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 8dc325c1b..a667a5f82 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -233,8 +233,13 @@ public: } void ToggleClockRun() { - if (clock_m->IsRunning()) clock_m->Stop(); - else clock_m->Start(); + if (clock_m->IsRunning()) { + clock_m->Stop(); + usbMIDI.sendRealTime(usbMIDI.Stop); + } else { + clock_m->Start(); + usbMIDI.sendRealTime(usbMIDI.Start); + } } void ToggleClockSetup() { From e11f1b899b2598e7d88e18f5e05954eeb61c64dc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 25 Mar 2023 20:35:22 -0400 Subject: [PATCH 199/417] Documentation! words words words --- API-notes.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 33 +++++++++++++++++++-------- TODO.md | 14 ++++++++++++ 3 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 API-notes.md create mode 100644 TODO.md diff --git a/API-notes.md b/API-notes.md new file mode 100644 index 000000000..f0a97c147 --- /dev/null +++ b/API-notes.md @@ -0,0 +1,63 @@ +API Notes +=== + +### Basics + +The o_C firmware operates with several "concurrent" threads. +(really they interrupt each other I guess) + +There is a main _loop_ as well as an _ISR_ that fires from a timer. +The _loop_ acts as a watchdog while the _ISR_ does all the work. +UI events are on a seperate _ISR_ timer. +The display driver and input polling are also on independent interrupts. +So that's at least 4? separate processes happening in a cascade +for every cycle, which is 60us (nanoseconds) or 16.666khz + +That's the general idea. + +Anyway, we have top-level *Apps* and then there are *Applets* as implemented +in the _Hemisphere_ *App*. An *Applet* inherits the base class _HemisphereApplet_ +and gains the superpowers necessary to live on half of your module's screen. + +### Applets + +There are a few different things an Applet must do: +* Controller - the main logic computed every tick (every time the ISR fires) +* View - draw the pixels on the screen (there are many helpful _gfx*_ functions) +* OnButtonPress - what to do when the encoder button is pressed +* OnEncoderMove - what to do when the encoder rotated +* OnDataRequest / OnDataReceive - how to save / load state + +There is also a `Start()` function for initializing things at runtime, +plus some Help text. That's about it. + +You can easily try it out by copying the `HEM_Boilerplate.ino.txt` file into place, +and then adding your computations to its skeleton. + +### Applet Functions + +Function? or Method? Either way, this is how you do the things. + +#### I/O Functions +The main argument of each is the channel to operate on - each half of the +screen gets 2 channels. So _n_ is typically either 0 or 1. + +* Clock(n) - has the digital input received a new clock pulse? +* Gate(n) - is the digital input held high? +* In(n) - Raw value of the CV input +* DetentedIn(n) - this one reads 0 until it's past a threshold ~ a quartertone +* ClockOut(n) - hold the output high for a pulse length +* GateOut(n, on_off) - set the output high or low +* Out(n, raw) - set the output to an explicit value + +#### gfx Functions +There are many strategies for drawing things on the screen, and therefore, many +graphics related functions. You can see them for yourself in `HemisphereApplet.h` +All of them typically take _x_ and _y_ coordinates for the first two arguments, +followed by _width_ and _height_, or another _x,y_ pair. +_x_ is how many pixels from the left edge of the screen. +_y_ is how many pixels from the top edge of the screen. + +Some essentials: *gfxPrint*, *gfxPixel*, *gfxLine*, *gfxCursor*, + *gfxFrame*, *gfxBitmap*, *gfxInvert* + diff --git a/README.md b/README.md index e3f50dfb6..022b6f6c9 100755 --- a/README.md +++ b/README.md @@ -3,20 +3,30 @@ ## Phazerville Suite - an active o_C firmware fork -Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! +Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! -I've also managed to squeeze in several stock O&C firmware apps: **Quantermain, Meta-Q, Acid Curds, Harrington 1200, Sequins** & **Quadraturia** are all here! +I've also managed to squeeze in several stock O&C firmware apps: **Quantermain, Piqued, Acid Curds, Harrington 1200, Sequins** & **Quadraturia** are all here! Check the [Wiki](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. ### Notable Features in this branch: -* Improved internal clock controls, external clock sync, independent multipliers for each Hemisphere, MIDI Clock out via USB - - Note: push/release both UP+DOWN buttons simultaneously to access the Clock Setup screen -* LoFi Tape has been transformed into LoFi Echo (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) -* ShiftReg has been upgraded to DualTM - two concurrent 32-bit registers governed by the same length/prob/scale/range settings, both outputs configurable to Pitch, Mod, Trig, Gate from either register -* EuclidX - AnnularFusion got a makeover, now includes configurable CV input modulation (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) -* Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) -* EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking +* A new App called [Calibr8or](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) + - quad performance quantizer + pitch CV fine-tuning tool, 4 preset banks +* Expanded internal clock + - Note: press both UP+DOWN buttons quickly to access the Clock Setup screen + - Syncs to external clock on TR1, configurable PPQN + - MIDI Clock out via USB + - Independent multipliers for each internal trigger + - Manual triggers (convenient for jogging or resetting a sequencer, testing) * Modal-editing style navigation (push to toggle editing) +* DualTM - ShiftReg has been upgraded to two concurrent 32-bit registers governed by the same length/prob/scale/range settings + - outputs assignable to Pitch, Mod, Trig, Gate from either register. Assignable CV inputs. Massive modulation potential! +* EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) + - mini implementation of MI Tides, with v/oct tracking +* EuclidX - AnnularFusion got a makeover, now includes padding, configurable CV input modulation + - (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) +* LoFi Tape has been transformed into LoFi Echo - a crazy bitcrushing digital delay line + - (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) +* Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) * lots of other small tweaks + experimental applets ### How do I try it? @@ -40,6 +50,11 @@ Various build environment configurations exist in `platformio.ini`. To build all ### Credits +Shoutout to Logarhythm for the fantastic **TB-3PO** sequencer. +To herrkami and Beni for their work on **BugCrack**. +Beni also gets massive props for **DrumMap** and the **ProbDiv / ProbMelo** applets. +And of course thank you to Chysn for the fantastic framework from which we've all drawn inspiration. + This is a fork of [Benisphere Suite](https://github.com/benirose/O_C-BenisphereSuite) which is a fork of [Hemisphere Suite](https://github.com/Chysn/O_C-HemisphereSuite) by Jason Justian (aka chysn). ornament**s** & crime**s** is a collaborative project by Patrick Dowling (aka pld), mxmxmx and Tim Churches (aka bennelong.bicyclist) (though mostly by pld and bennelong.bicyclist). it **(considerably) extends** the original firmware for the o_C / ASR eurorack module, designed by mxmxmx. diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..b10a38663 --- /dev/null +++ b/TODO.md @@ -0,0 +1,14 @@ +TODO +=== + +* Update Boilerplates +* General Config screen (long-press right button) + +[APP IDEAS] +* QUADRANTS +* Two Spheres + +[DONE] +X Fix FLIP_180 calibration +X Add Clock Setup to Calibr8or +X Calibr8or screensaver From 809c4bc093284e274c651dff94f7f7edb55431c9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 25 Mar 2023 20:14:25 -0400 Subject: [PATCH 200/417] Version bump; v1.5.1 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index afb6e0dfd..174064bd3 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -6,6 +6,6 @@ #endif #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.5" OC_VERSION_EXTRA +#define OC_VERSION "v1.5.1" OC_VERSION_EXTRA #define OC_VERSION_URL "github.com/djphazer" #endif From d3e2a604d28dfba5d3d1c4505b70d8491a3baff2 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Tue, 28 Mar 2023 03:17:35 -0400 Subject: [PATCH 201/417] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 022b6f6c9..5d7b271c6 100755 --- a/README.md +++ b/README.md @@ -9,24 +9,24 @@ I've also managed to squeeze in several stock O&C firmware apps: **Quantermain, ### Notable Features in this branch: -* A new App called [Calibr8or](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) +* A new App called [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) - quad performance quantizer + pitch CV fine-tuning tool, 4 preset banks * Expanded internal clock - - Note: press both UP+DOWN buttons quickly to access the Clock Setup screen + - Note: press both UP+DOWN buttons quickly to access the [**Clock Setup**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Clock-Setup) screen - Syncs to external clock on TR1, configurable PPQN - MIDI Clock out via USB - Independent multipliers for each internal trigger - Manual triggers (convenient for jogging or resetting a sequencer, testing) * Modal-editing style navigation (push to toggle editing) -* DualTM - ShiftReg has been upgraded to two concurrent 32-bit registers governed by the same length/prob/scale/range settings +* **DualTM** - ShiftReg has been upgraded to two concurrent 32-bit registers governed by the same length/prob/scale/range settings - outputs assignable to Pitch, Mod, Trig, Gate from either register. Assignable CV inputs. Massive modulation potential! -* EbbAndLfo (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) +* **EbbAndLfo** (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking -* EuclidX - AnnularFusion got a makeover, now includes padding, configurable CV input modulation +* **EuclidX** - AnnularFusion got a makeover, now includes padding, configurable CV input modulation - (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) -* LoFi Tape has been transformed into LoFi Echo - a crazy bitcrushing digital delay line +* LoFi Tape has been transformed into **LoFi Echo** - a crazy bitcrushing digital delay line - (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) -* Sequence5 -> SequenceX (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) +* Sequence5 -> **SequenceX** (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) * lots of other small tweaks + experimental applets ### How do I try it? @@ -46,11 +46,11 @@ The project lives within the `software/o_c_REV` directory. From there, you can B ``` pio run -e oc_prod -t upload ``` -Various build environment configurations exist in `platformio.ini`. To build all the defaults, simply use `pio run` +Alternate build environment configurations exist in `platformio.ini` for VOR, Buchla, etc. To build all the defaults, simply use `pio run` ### Credits -Shoutout to Logarhythm for the fantastic **TB-3PO** sequencer. +Shoutout to Logarhythm for the incredible **TB-3PO** sequencer. To herrkami and Beni for their work on **BugCrack**. Beni also gets massive props for **DrumMap** and the **ProbDiv / ProbMelo** applets. And of course thank you to Chysn for the fantastic framework from which we've all drawn inspiration. From 23ee44d7f8aecc11f356ac85a899a1ead792e061 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 28 Mar 2023 01:25:58 -0400 Subject: [PATCH 202/417] cleanup --- software/o_c_REV/APP_CALIBR8OR.ino | 1 - software/o_c_REV/HEM_hMIDIIn.ino | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index b6f77d943..ef0e457d5 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -473,7 +473,6 @@ private: ClockManager *clock_m = clock_m->get(); void DrawPresetSelector() { - // TODO: Preset selection screen // index is the currently loaded preset (0-3) // preset_select is current selection (1-4, 5=clear) int y = 5 + 10*preset_select; diff --git a/software/o_c_REV/HEM_hMIDIIn.ino b/software/o_c_REV/HEM_hMIDIIn.ino index 1df5d5948..091e1f6ea 100644 --- a/software/o_c_REV/HEM_hMIDIIn.ino +++ b/software/o_c_REV/HEM_hMIDIIn.ino @@ -31,8 +31,7 @@ struct MIDILogEntry { class hMIDIIn : public HemisphereApplet { public: -// The functions available for each output -// TODO: make this an enum + // The functions available for each output enum hMIDIFunctions { HEM_MIDI_NOTE_OUT, HEM_MIDI_TRIG_OUT, From 156874ef9e1ca14e26947f37dd06a550bcfd72a9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 28 Mar 2023 01:32:10 -0400 Subject: [PATCH 203/417] update TODO --- TODO.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index b10a38663..98b3cafea 100644 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,17 @@ TODO === +* Pull in Automatonnetz * Update Boilerplates * General Config screen (long-press right button) +* Stepped Mode for internal Clock [APP IDEAS] * QUADRANTS * Two Spheres +* Snake Game [DONE] -X Fix FLIP_180 calibration -X Add Clock Setup to Calibr8or -X Calibr8or screensaver +* - Fix FLIP_180 calibration +* - Add Clock Setup to Calibr8or +* - Calibr8or screensaver From 39ad87ad3acde2c826029d8bb1fbcd7a3554854a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 28 Mar 2023 05:17:12 -0400 Subject: [PATCH 204/417] Ignore button release when canceling screensaver --- software/o_c_REV/OC_ui.h | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/OC_ui.h b/software/o_c_REV/OC_ui.h index 28790488f..3aa89159d 100644 --- a/software/o_c_REV/OC_ui.h +++ b/software/o_c_REV/OC_ui.h @@ -166,6 +166,7 @@ class Ui { } if (screensaver_) { screensaver_ = false; + SetButtonIgnoreMask(); // ignore whatever button is about to be released ignore = true; } From 5816fb95855b5f3943bb71eb455707686108a30d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 9 Apr 2023 04:06:18 -0400 Subject: [PATCH 205/417] TODO: frequency display is broken in autotuner code just in case anyone wants to bring back the References app --- software/o_c_REV/OC_autotuner.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index 2b8c526b4..3558a933a 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -160,6 +160,7 @@ class Autotuner { if (_freq == 0.0f) graphics.printf("wait ..."); else + // TODO: printf doesn't handle floats with TEENSY_OPT_SMALLEST_CODE graphics.printf("%7.3f", _freq); } break; @@ -177,6 +178,7 @@ class Autotuner { if (!owner_->_ready()) graphics.print(" "); else + // TODO: printf doesn't handle floats with TEENSY_OPT_SMALLEST_CODE graphics.printf(" > %7.3f", owner_->get_auto_frequency()); } } From 3facfa2f022f1e5828b35b96becbef5c13a45712 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 31 Mar 2023 15:24:10 -0400 Subject: [PATCH 206/417] Build option for flipped +stock --- software/o_c_REV/platformio.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 80338342b..589e27827 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -69,6 +69,12 @@ build_flags = -DFLIP_180 -DOC_VERSION_EXTRA="\" flipped\"" +[env:oc_stock_flipped] +build_flags = + ${env:oc_stock.build_flags} + -DFLIP_180 +; -DOC_VERSION_EXTRA="\"+stock,flipped\"" + [env:oc_prod_vor] build_flags = ${env:oc_prod.build_flags} From 7bc28626014ffb6f3fec80056cd87b8535f4019d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 4 Apr 2023 22:58:59 -0400 Subject: [PATCH 207/417] ClockSetup: detect multiple taps on the Tempo parameter --- software/o_c_REV/HEM_ClockSetup.ino | 26 ++++++++++++++++++++++++++ software/o_c_REV/HSClockManager.h | 12 ++++++++++++ 2 files changed, 38 insertions(+) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 06d9f358f..44c761451 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -81,9 +81,29 @@ public: else CursorAction(cursor, LAST_SETTING); } else CursorAction(cursor, LAST_SETTING); + + if (cursor == TEMPO) { + // Tap Tempo detection + if (last_tap_tick) { + tap_time[taps++] = OC::CORE::ticks - last_tap_tick; + + if (tap_time[taps-1] > CLOCK_TICKS_MAX) { + taps = 0; + last_tap_tick = 0; + } + + if (taps == NR_OF_TAPS) + clock_m->SetTempoFromTaps(tap_time, taps); + + taps %= NR_OF_TAPS; + } else { + last_tap_tick = OC::CORE::ticks; + } + } } void OnEncoderMove(int direction) { + taps = 0; if (!EditMode()) { MoveCursor(cursor, direction, LAST_SETTING); return; @@ -166,6 +186,12 @@ private: int button_ticker; ClockManager *clock_m = clock_m->get(); + static const int NR_OF_TAPS = 3; + + int taps = 0; // tap tempo + uint32_t tap_time[NR_OF_TAPS]; // buffer of past tap tempo measurements + uint32_t last_tap_tick = 0; + void PlayStop() { if (clock_m->IsRunning()) { stop_q = 1; diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 6c6c7448c..f3f4c371e 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -93,6 +93,18 @@ class ClockManager { ticks_per_beat = 1000000 / bpm; tempo = bpm; } + + void SetTempoFromTaps(uint32_t *taps, int count) { + uint32_t total = 0; + for (int i = 0; i < count; ++i) { + total += taps[i]; + } + + // update the tempo + uint32_t clock_diff = total / count; + ticks_per_beat = constrain(clock_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); // time since last clock is new tempo + tempo = 1000000 / ticks_per_beat; // imprecise, for display purposes + } int GetMultiply(int ch = 0) {return tocks_per_beat[ch];} int GetClockPPQN() { return clock_ppqn; } From 011612463bb43b55c9138607cc1e6ae8709ab355 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 8 Apr 2023 18:13:54 -0400 Subject: [PATCH 208/417] EuclidX: allow hits to be 0; expand storage --- software/o_c_REV/HEM_EuclidX.ino | 42 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 4d8132f75..068cc7a0c 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -31,7 +31,7 @@ #include "bjorklund.h" const int NUM_PARAMS = 5; -const int PARAM_SIZE = 5; +const int PARAM_SIZE = 6; class EuclidX : public HemisphereApplet { public: @@ -89,7 +89,7 @@ public: break; case BEATS1: - actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); + actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 0, actual_length[ch]); break; case OFFSET1: actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); @@ -153,7 +153,7 @@ public: break; case BEATS1: case BEATS2: - actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); + actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 0, length[ch]); break; case OFFSET1: case OFFSET2: @@ -174,30 +174,26 @@ public: uint64_t OnDataRequest() { uint64_t data = 0; - Pack(data, PackLocation {0 * PARAM_SIZE, PARAM_SIZE}, length[0] - 1); - Pack(data, PackLocation {1 * PARAM_SIZE, PARAM_SIZE}, beats[0] - 1); - Pack(data, PackLocation {2 * PARAM_SIZE, PARAM_SIZE}, length[1] - 1); - Pack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}, beats[1] - 1); - Pack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}, offset[0]); - Pack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}, offset[1]); - Pack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}, cv_dest[0]); - Pack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}, cv_dest[1]); - Pack(data, PackLocation {8 * PARAM_SIZE, PARAM_SIZE}, padding[0]); - Pack(data, PackLocation {9 * PARAM_SIZE, PARAM_SIZE}, padding[1]); + size_t idx = 0; + ForEachChannel(ch) { + Pack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}, length[ch] - 1); + Pack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}, beats[ch]); + Pack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}, offset[ch]); + Pack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}, padding[ch]); + Pack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}, cv_dest[ch]); + } return data; } void OnDataReceive(uint64_t data) { - actual_length[0] = length[0] = Unpack(data, PackLocation {0 * PARAM_SIZE, PARAM_SIZE}) + 1; - actual_beats[0] = beats[0] = Unpack(data, PackLocation {1 * PARAM_SIZE, PARAM_SIZE}) + 1; - actual_length[1] = length[1] = Unpack(data, PackLocation {2 * PARAM_SIZE, PARAM_SIZE}) + 1; - actual_beats[1] = beats[1] = Unpack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}) + 1; - actual_offset[0] = offset[0] = Unpack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}); - actual_offset[1] = offset[1] = Unpack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}); - cv_dest[0] = (EuclidXParam) Unpack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}); - cv_dest[1] = (EuclidXParam) Unpack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}); - actual_padding[0] = padding[0] = Unpack(data, PackLocation {8 * PARAM_SIZE, PARAM_SIZE}); - actual_padding[1] = padding[1] = Unpack(data, PackLocation {9 * PARAM_SIZE, PARAM_SIZE}); + size_t idx = 0; + ForEachChannel(ch) { + actual_length[ch] = length[ch] = Unpack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}) + 1; + actual_beats[ch] = beats[ch] = Unpack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}); + actual_offset[ch] = offset[ch] = Unpack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}); + actual_padding[ch] = padding[ch] = Unpack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}); + cv_dest[ch] = (EuclidXParam) Unpack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}); + } } protected: From e747c2bcbda0ea6dae72db1f75b998bcad622abd Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 13 Apr 2023 03:01:59 -0400 Subject: [PATCH 209/417] DualTM: save/restore Slew --- software/o_c_REV/HEM_TM2.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 8630659ed..bb355e824 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -269,6 +269,7 @@ public: Pack(data, PackLocation {25,8}, constrain(scale, 0, 255)); Pack(data, PackLocation {33,4}, cvmode[0]); Pack(data, PackLocation {37,4}, cvmode[1]); + Pack(data, PackLocation {41,6}, smoothing); // maybe don't bother saving the damn register //Pack(data, PackLocation {32,32}, reg); @@ -286,6 +287,7 @@ public: quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); + smoothing = Unpack(data, PackLocation {41,6}); reg[0] = Unpack(data, PackLocation {32,32}); reg[1] = Unpack(data, PackLocation {0, 32}); // lol it could be fun From da848cb9286762065c97d7e0cea7baee3d7ebf81 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 8 Apr 2023 02:59:05 -0400 Subject: [PATCH 210/417] Config + Presets for Hemisphere Initial working version. 4 preset banks. Trig Length & Cursor Mode are also stored per preset. ZAP icon marks last loaded preset. --- software/o_c_REV/APP_HEMISPHERE.ino | 441 +++++++++++++++++++++------- software/o_c_REV/HEM_ClockSetup.ino | 26 +- software/o_c_REV/HEM_TM2.ino | 4 +- software/o_c_REV/HemisphereApplet.h | 10 +- 4 files changed, 356 insertions(+), 125 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index a667a5f82..69598b54b 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -26,6 +26,7 @@ namespace menu = OC::menu; #include "HemisphereApplet.h" +#include "HSApplication.h" #include "HSicons.h" #include "HSMIDI.h" #include "HSClockManager.h" @@ -44,19 +45,128 @@ enum HEMISPHERE_SETTINGS { HEMISPHERE_RIGHT_DATA_B4, HEMISPHERE_CLOCK_DATA1, HEMISPHERE_CLOCK_DATA2, + HEMISPHERE_CLOCK_DATA3, + HEMISPHERE_CLOCK_DATA4, HEMISPHERE_SETTING_LAST }; static constexpr int HEMISPHERE_AVAILABLE_APPLETS = ARRAY_SIZE(HS::available_applets); +static const int HEM_NR_OF_PRESETS = 4; + +static const char * hem_preset_name[HEM_NR_OF_PRESETS] = { "A", "B", "C", "D" }; + +/* Hemisphere Preset + * - conveniently store/recall multiple configurations + */ +class HemispherePreset : public SystemExclusiveHandler, + public settings::SettingsBase { +public: + int GetAppletId(int h) { + return (h == LEFT_HEMISPHERE) ? values_[HEMISPHERE_SELECTED_LEFT_ID] + : values_[HEMISPHERE_SELECTED_RIGHT_ID]; + } + void SetAppletId(int h, int id) { + apply_value(h, id); + } + bool is_valid() { + return values_[HEMISPHERE_SELECTED_LEFT_ID] != 0; + } + + // restore state by setting applets and giving them data + void LoadClockData() { + HS::clock_setup_applet.OnDataReceive(0, (uint64_t(values_[HEMISPHERE_CLOCK_DATA4]) << 48) | + (uint64_t(values_[HEMISPHERE_CLOCK_DATA3]) << 32) | + (uint64_t(values_[HEMISPHERE_CLOCK_DATA2]) << 16) | + uint64_t(values_[HEMISPHERE_CLOCK_DATA1])); + } + void StoreClockData() { + uint64_t data = HS::clock_setup_applet.OnDataRequest(0); + apply_value(HEMISPHERE_CLOCK_DATA1, data & 0xffff); + apply_value(HEMISPHERE_CLOCK_DATA2, (data >> 16) & 0xffff); + apply_value(HEMISPHERE_CLOCK_DATA3, (data >> 32) & 0xffff); + apply_value(HEMISPHERE_CLOCK_DATA4, (data >> 48) & 0xffff); + } + + + // Manually get data for one side + uint64_t GetData(int h) { + return (uint64_t(values_[8 + h]) << 48) | + (uint64_t(values_[6 + h]) << 32) | + (uint64_t(values_[4 + h]) << 16) | + (uint64_t(values_[2 + h])); + } + + /* Manually store state data for one side */ + void SetData(int h, uint64_t data) { + apply_value(2 + h, data & 0xffff); + apply_value(4 + h, (data >> 16) & 0xffff); + apply_value(6 + h, (data >> 32) & 0xffff); + apply_value(8 + h, (data >> 48) & 0xffff); + } + + void OnSendSysEx() { + // Set the values_ array prior to packing it + //StoreClockData(); + + // Describe the data structure for the audience + uint8_t V[18]; + V[0] = (uint8_t)values_[HEMISPHERE_SELECTED_LEFT_ID]; + V[1] = (uint8_t)values_[HEMISPHERE_SELECTED_RIGHT_ID]; + V[2] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B1] & 0xff); + V[3] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B1] >> 8) & 0xff); + V[4] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B1] & 0xff); + V[5] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B1] >> 8) & 0xff); + V[6] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B2] & 0xff); + V[7] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B2] >> 8) & 0xff); + V[8] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B2] & 0xff); + V[9] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B2] >> 8) & 0xff); + V[10] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B3] & 0xff); + V[11] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B3] >> 8) & 0xff); + V[12] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B3] & 0xff); + V[13] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B3] >> 8) & 0xff); + V[14] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B4] & 0xff); + V[15] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B4] >> 8) & 0xff); + V[16] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B4] & 0xff); + V[17] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B4] >> 8) & 0xff); + + // Pack it up, ship it out + UnpackedData unpacked; + unpacked.set_data(18, V); + PackedData packed = unpacked.pack(); + SendSysEx(packed, 'H'); + } + + void OnReceiveSysEx() { + uint8_t V[18]; + if (ExtractSysExData(V, 'H')) { + values_[HEMISPHERE_SELECTED_LEFT_ID] = V[0]; + values_[HEMISPHERE_SELECTED_RIGHT_ID] = V[1]; + values_[HEMISPHERE_LEFT_DATA_B1] = ((uint16_t)V[3] << 8) + V[2]; + values_[HEMISPHERE_RIGHT_DATA_B1] = ((uint16_t)V[5] << 8) + V[4]; + values_[HEMISPHERE_LEFT_DATA_B2] = ((uint16_t)V[7] << 8) + V[6]; + values_[HEMISPHERE_RIGHT_DATA_B2] = ((uint16_t)V[9] << 8) + V[8]; + values_[HEMISPHERE_LEFT_DATA_B3] = ((uint16_t)V[11] << 8) + V[10]; + values_[HEMISPHERE_RIGHT_DATA_B3] = ((uint16_t)V[13] << 8) + V[12]; + values_[HEMISPHERE_LEFT_DATA_B4] = ((uint16_t)V[15] << 8) + V[14]; + values_[HEMISPHERE_RIGHT_DATA_B4] = ((uint16_t)V[17] << 8) + V[16]; + //LoadClockData(); + } + } + +}; + +// HemispherePreset hem_config; // special place for Clock data and Config data, 64 bits each + +HemispherePreset hem_presets[HEM_NR_OF_PRESETS]; +HemispherePreset *hem_active_preset; //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Manager //////////////////////////////////////////////////////////////////////////////// -class HemisphereManager : public SystemExclusiveHandler, - public settings::SettingsBase { +class HemisphereManager : public HSApplication { public: - void Init() { + void Start() { select_mode = -1; // Not selecting midi_in_hemisphere = -1; // No MIDI In @@ -68,48 +178,68 @@ public: } void Resume() { + if (!hem_active_preset) + LoadFromPreset(0); + } + void Suspend() { + if (hem_active_preset) + hem_active_preset->OnSendSysEx(); + } + + void StoreToPreset(int id) { + hem_active_preset = (HemispherePreset*)(hem_presets + id); for (int h = 0; h < 2; h++) { - int index = get_applet_index_by_id(values_[h]); - SetApplet(h, index); - uint64_t data = - (uint64_t(values_[8 + h]) << 48) | - (uint64_t(values_[6 + h]) << 32) | - (uint64_t(values_[4 + h]) << 16) | - (uint64_t(values_[2 + h])); - HS::available_applets[index].OnDataReceive(h, data); + int index = my_applet[h]; + hem_active_preset->SetAppletId(h, HS::available_applets[index].id); + + uint64_t data = HS::available_applets[index].OnDataRequest(h); + hem_active_preset->SetData(h, data); + } + hem_active_preset->StoreClockData(); + preset_id = id; + } + void LoadFromPreset(int id) { + hem_active_preset = (HemispherePreset*)(hem_presets + id); + if (hem_active_preset->is_valid()) { + hem_active_preset->LoadClockData(); + for (int h = 0; h < 2; h++) + { + int index = get_applet_index_by_id( hem_active_preset->GetAppletId(h) ); + SetApplet(h, index); + HS::available_applets[index].OnDataReceive(h, hem_active_preset->GetData(h)); + } } - HS::clock_setup_applet.OnDataReceive(0, (uint64_t(values_[HEMISPHERE_CLOCK_DATA2]) << 16) | - uint64_t(values_[HEMISPHERE_CLOCK_DATA1])); + preset_id = id; } + // does not modify the preset, only the manager void SetApplet(int hemisphere, int index) { my_applet[hemisphere] = index; if (midi_in_hemisphere == hemisphere) midi_in_hemisphere = -1; if (HS::available_applets[index].id & 0x80) midi_in_hemisphere = hemisphere; HS::available_applets[index].Start(hemisphere); - apply_value(hemisphere, HS::available_applets[index].id); } - void ChangeApplet(int dir) { - if (SelectModeEnabled() and help_hemisphere == -1) { - int index = get_next_applet_index(my_applet[select_mode], dir); - SetApplet(select_mode, index); - } + void ChangeApplet(int h, int dir) { + int index = get_next_applet_index(my_applet[h], dir); + SetApplet(select_mode, index); } bool SelectModeEnabled() { return select_mode > -1; } - void ExecuteControllers() { + void Controller() { + // TODO: eliminate the need for this with top-level MIDI handling if (midi_in_hemisphere == -1) { // Only one ISR can look for MIDI messages at a time, so we need to check // for another MIDI In applet before looking for sysex. Note that applets // that use MIDI In should check for sysex themselves; see Midi In for an // example. if (usbMIDI.read() && usbMIDI.getType() == usbMIDI.SystemExclusive) { - OnReceiveSysEx(); + if (hem_active_preset) + hem_active_preset->OnReceiveSysEx(); } } @@ -127,10 +257,18 @@ public: } } - void DrawViews() { + void View() { + if (config_menu) { + DrawConfigMenu(); + return; + } + if (clock_setup) { HS::clock_setup_applet.View(LEFT_HEMISPHERE); - } else if (help_hemisphere > -1) { + return; + } + + if (help_hemisphere > -1) { int index = my_applet[help_hemisphere]; HS::available_applets[index].View(help_hemisphere); } else { @@ -138,17 +276,16 @@ public: { int index = my_applet[h]; HS::available_applets[index].View(h); - if (h == 0) { - if (clock_m->IsRunning() || clock_m->IsPaused()) { - // Metronome icon - graphics.drawBitmap8(56, 1, 8, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); - } - - if (clock_m->IsForwarded()) { - // CV Forwarding Icon - graphics.drawBitmap8(120, 1, 8, CLOCK_ICON); - } - } + } + + if (clock_m->IsRunning() || clock_m->IsPaused()) { + // Metronome icon + graphics.drawBitmap8(56, 1, 8, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); + } + + if (clock_m->IsForwarded()) { + // CV Forwarding Icon + graphics.drawBitmap8(120, 1, 8, CLOCK_ICON); } if (select_mode == LEFT_HEMISPHERE) graphics.drawFrame(0, 0, 64, 64); @@ -160,6 +297,12 @@ public: bool down = (event.type == UI::EVENT_BUTTON_DOWN); int h = (event.control == OC::CONTROL_BUTTON_L) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; + if (config_menu) { + // button release for config screen + if (!down) ConfigButtonPush(h); + return; + } + // button down if (down) { // Clock Setup is more immediate for manual triggers @@ -182,6 +325,17 @@ public: bool down = (event.type == UI::EVENT_BUTTON_DOWN); int hemisphere = (event.control == OC::CONTROL_BUTTON_UP) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; + if (config_menu) { + // cancel preset select, or config screen on select button release + if (!down) { + if (preset_cursor) { + preset_cursor = 0; + } + else config_menu = 0; + } + return; + } + // -- button down if (down) { if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME) { @@ -222,10 +376,16 @@ public: void DelegateEncoderMovement(const UI::Event &event) { int h = (event.control == OC::CONTROL_ENCODER_L) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; + if (config_menu) { + // TODO + ConfigEncoderAction(h, event.value); + return; + } + if (clock_setup) { HS::clock_setup_applet.OnEncoderMove(LEFT_HEMISPHERE, event.value); } else if (select_mode == h) { - ChangeApplet(event.value); + ChangeApplet(h, event.value); } else { int index = my_applet[h]; HS::available_applets[index].OnEncoderMove(h, event.value); @@ -246,6 +406,10 @@ public: clock_setup = 1 - clock_setup; } + void ToggleConfigMenu() { + config_menu = !config_menu; + } + void SetHelpScreen(int hemisphere) { if (help_hemisphere > -1) { // Turn off the previous help screen int index = my_applet[help_hemisphere]; @@ -260,82 +424,124 @@ public: help_hemisphere = hemisphere; } - void RequestAppletData() { - for (int h = 0; h < 2; h++) - { - int index = my_applet[h]; - uint64_t data = HS::available_applets[index].OnDataRequest(h); - apply_value(2 + h, data & 0xffff); - apply_value(4 + h, (data >> 16) & 0xffff); - apply_value(6 + h, (data >> 32) & 0xffff); - apply_value(8 + h, (data >> 48) & 0xffff); - } - uint64_t data = HS::clock_setup_applet.OnDataRequest(0); - apply_value(HEMISPHERE_CLOCK_DATA1, data & 0xffff); - apply_value(HEMISPHERE_CLOCK_DATA2, (data >> 16) & 0xffff); - } - - void OnSendSysEx() { - // Set the values_ array prior to packing it - RequestAppletData(); - - // Describe the data structure for the audience - uint8_t V[18]; - V[0] = (uint8_t)values_[HEMISPHERE_SELECTED_LEFT_ID]; - V[1] = (uint8_t)values_[HEMISPHERE_SELECTED_RIGHT_ID]; - V[2] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B1] & 0xff); - V[3] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B1] >> 8) & 0xff); - V[4] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B1] & 0xff); - V[5] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B1] >> 8) & 0xff); - V[6] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B2] & 0xff); - V[7] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B2] >> 8) & 0xff); - V[8] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B2] & 0xff); - V[9] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B2] >> 8) & 0xff); - V[10] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B3] & 0xff); - V[11] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B3] >> 8) & 0xff); - V[12] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B3] & 0xff); - V[13] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B3] >> 8) & 0xff); - V[14] = (uint8_t)(values_[HEMISPHERE_LEFT_DATA_B4] & 0xff); - V[15] = (uint8_t)((values_[HEMISPHERE_LEFT_DATA_B4] >> 8) & 0xff); - V[16] = (uint8_t)(values_[HEMISPHERE_RIGHT_DATA_B4] & 0xff); - V[17] = (uint8_t)((values_[HEMISPHERE_RIGHT_DATA_B4] >> 8) & 0xff); - - // Pack it up, ship it out - UnpackedData unpacked; - unpacked.set_data(18, V); - PackedData packed = unpacked.pack(); - SendSysEx(packed, 'H'); - } - - void OnReceiveSysEx() { - uint8_t V[18]; - if (ExtractSysExData(V, 'H')) { - values_[HEMISPHERE_SELECTED_LEFT_ID] = V[0]; - values_[HEMISPHERE_SELECTED_RIGHT_ID] = V[1]; - values_[HEMISPHERE_LEFT_DATA_B1] = ((uint16_t)V[3] << 8) + V[2]; - values_[HEMISPHERE_RIGHT_DATA_B1] = ((uint16_t)V[5] << 8) + V[4]; - values_[HEMISPHERE_LEFT_DATA_B2] = ((uint16_t)V[7] << 8) + V[6]; - values_[HEMISPHERE_RIGHT_DATA_B2] = ((uint16_t)V[9] << 8) + V[8]; - values_[HEMISPHERE_LEFT_DATA_B3] = ((uint16_t)V[11] << 8) + V[10]; - values_[HEMISPHERE_RIGHT_DATA_B3] = ((uint16_t)V[13] << 8) + V[12]; - values_[HEMISPHERE_LEFT_DATA_B4] = ((uint16_t)V[15] << 8) + V[14]; - values_[HEMISPHERE_RIGHT_DATA_B4] = ((uint16_t)V[17] << 8) + V[16]; - Resume(); - } - } - private: + int preset_id = 0; + int preset_cursor = 0; int my_applet[2]; // Indexes to available_applets int select_mode; bool clock_setup; + bool config_menu; + bool isEditing = false; + int config_cursor = 0; + int help_hemisphere; // Which of the hemispheres (if any) is in help mode, or -1 if none int midi_in_hemisphere; // Which of the hemispheres (if any) is using MIDI In uint32_t click_tick; // Measure time between clicks for double-click int first_click; // The first button pushed of a double-click set, to see if the same one is pressed ClockManager *clock_m = clock_m->get(); - void DrawClockSetup() { + enum HEMConfigCursor { + LOAD_PRESET, SAVE_PRESET, + TRIG_LENGTH, + CURSOR_MODE, + + MAX_CURSOR = CURSOR_MODE + }; + void ConfigEncoderAction(int h, int dir) { + if (!isEditing && !preset_cursor) { + config_cursor += dir; + config_cursor = constrain(config_cursor, 0, MAX_CURSOR); + ResetCursor(); + return; + } + + if (config_cursor == TRIG_LENGTH) { + HemisphereApplet::trig_length = (uint32_t) constrain( int(HemisphereApplet::trig_length + dir), 1, 127); + } + else if (config_cursor == SAVE_PRESET || config_cursor == LOAD_PRESET) { + preset_cursor = constrain(preset_cursor + dir, 1, HEM_NR_OF_PRESETS); + } + } + void ConfigButtonPush(int h) { + if (preset_cursor) { + // Save or Load on button push + if (config_cursor == SAVE_PRESET) + StoreToPreset(preset_cursor-1); + else + LoadFromPreset(preset_cursor-1); + + preset_cursor = 0; // deactivate preset selection + config_menu = 0; + isEditing = false; + return; + } + + switch (config_cursor) { + case SAVE_PRESET: + case LOAD_PRESET: + preset_cursor = preset_id + 1; + break; + + case TRIG_LENGTH: + isEditing = !isEditing; + break; + + case CURSOR_MODE: + HemisphereApplet::CycleEditMode(); + break; + } + } + + void DrawConfigMenu() { + // --- Preset Selector + if (preset_cursor) { + DrawPresetSelector(); + return; + } + + // --- Config Selection + gfxHeader("Hemisphere Config"); + gfxPrint(1, 15, "Preset: "); + gfxPrint(48, 15, "Load / Save"); + + gfxPrint(1, 35, "Trig Length: "); + gfxPrint(HemisphereApplet::trig_length); + + const char * cursor_mode_name[3] = { "legacy", "modal", "modal+wrap" }; + gfxPrint(1, 45, "Cursor: "); + gfxPrint(cursor_mode_name[HemisphereApplet::modal_edit_mode]); + + switch (config_cursor) { + case LOAD_PRESET: + case SAVE_PRESET: + gfxIcon(55 + (config_cursor - LOAD_PRESET)*45, 25, UP_ICON); + break; + + case TRIG_LENGTH: + if (isEditing) gfxInvert(79, 34, 25, 9); + else gfxCursor(80, 43, 24); + break; + case CURSOR_MODE: + gfxIcon(43, 45, RIGHT_ICON); + break; + } + } + + void DrawPresetSelector() { + gfxHeader("Hemisphere Presets"); + int y = 5 + preset_cursor*10; + gfxPrint(1, y, (config_cursor == SAVE_PRESET) ? "Save" : "Load"); + gfxIcon(26, y, RIGHT_ICON); + for (int i = 0; i < HEM_NR_OF_PRESETS; ++i) { + y = 15 + i*10; + gfxPrint(35, y, hem_preset_name[i]); + + if (!hem_presets[i].is_valid()) + gfxPrint(" (empty)"); + else if (i == preset_id) + gfxIcon(45, y, ZAP_ICON); + } } int get_applet_index_by_id(int id) { @@ -364,7 +570,7 @@ private: } }; -SETTINGS_DECLARE(HemisphereManager, HEMISPHERE_SETTING_LAST) { +SETTINGS_DECLARE(HemispherePreset, HEMISPHERE_SETTING_LAST) { {0, 0, 255, "Applet ID L", NULL, settings::STORAGE_TYPE_U8}, {0, 0, 255, "Applet ID R", NULL, settings::STORAGE_TYPE_U8}, {0, 0, 65535, "Data L block 1", NULL, settings::STORAGE_TYPE_U16}, @@ -376,13 +582,16 @@ SETTINGS_DECLARE(HemisphereManager, HEMISPHERE_SETTING_LAST) { {0, 0, 65535, "Data L block 4", NULL, settings::STORAGE_TYPE_U16}, {0, 0, 65535, "Data R block 4", NULL, settings::STORAGE_TYPE_U16}, {0, 0, 65535, "Clock data 1", NULL, settings::STORAGE_TYPE_U16}, - {0, 0, 65535, "Clock data 2", NULL, settings::STORAGE_TYPE_U16} + {0, 0, 65535, "Clock data 2", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Clock data 3", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Clock data 4", NULL, settings::STORAGE_TYPE_U16} }; HemisphereManager manager; void ReceiveManagerSysEx() { - manager.OnReceiveSysEx(); + if (hem_active_preset) + hem_active_preset->OnReceiveSysEx(); } //////////////////////////////////////////////////////////////////////////////// @@ -391,38 +600,44 @@ void ReceiveManagerSysEx() { // App stubs void HEMISPHERE_init() { - manager.Init(); + manager.BaseStart(); } size_t HEMISPHERE_storageSize() { - return HemisphereManager::storageSize(); + return HemispherePreset::storageSize() * HEM_NR_OF_PRESETS; } size_t HEMISPHERE_save(void *storage) { - manager.RequestAppletData(); - return manager.Save(storage); + size_t used = 0; + for (int i = 0; i < HEM_NR_OF_PRESETS; ++i) { + used += hem_presets[i].Save(static_cast(storage) + used); + } + return used; } size_t HEMISPHERE_restore(const void *storage) { - size_t s = manager.Restore(storage); + size_t used = 0; + for (int i = 0; i < HEM_NR_OF_PRESETS; ++i) { + used += hem_presets[i].Restore(static_cast(storage) + used); + } manager.Resume(); - return s; + return used; } void FASTRUN HEMISPHERE_isr() { - manager.ExecuteControllers(); + manager.BaseController(); } void HEMISPHERE_handleAppEvent(OC::AppEvent event) { if (event == OC::APP_EVENT_SUSPEND) { - manager.OnSendSysEx(); + manager.Suspend(); } } void HEMISPHERE_loop() {} // Essentially deprecated in favor of ISR void HEMISPHERE_menu() { - manager.DrawViews(); + manager.View(); } void HEMISPHERE_screensaver() {} // Deprecated in favor of screen blanking @@ -439,7 +654,7 @@ void HEMISPHERE_handleButtonEvent(const UI::Event &event) { break; case UI::EVENT_BUTTON_LONG_PRESS: - if (event.control == OC::CONTROL_BUTTON_DOWN) manager.ToggleClockSetup(); + if (event.control == OC::CONTROL_BUTTON_DOWN) manager.ToggleConfigMenu(); if (event.control == OC::CONTROL_BUTTON_L) manager.ToggleClockRun(); break; diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 44c761451..bfbf1dc9e 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -149,23 +149,35 @@ public: Pack(data, PackLocation { 0, 1 }, clock_m->IsRunning() || clock_m->IsPaused()); Pack(data, PackLocation { 1, 1 }, clock_m->IsForwarded()); Pack(data, PackLocation { 2, 9 }, clock_m->GetTempo()); - Pack(data, PackLocation { 11, 6 }, clock_m->GetMultiply(0)+32); - Pack(data, PackLocation { 17, 6 }, clock_m->GetMultiply(2)+32); - Pack(data, PackLocation { 23, 5 }, clock_m->GetClockPPQN()); + Pack(data, PackLocation { 11, 5 }, clock_m->GetClockPPQN()); + for (size_t i = 0; i < 4; ++i) { + Pack(data, PackLocation { 16+i*6, 6 }, clock_m->GetMultiply(i)+32); + } + + // other config settings are kept here as well, it's convenient + Pack(data, PackLocation { 50, 2 }, HemisphereApplet::modal_edit_mode); + Pack(data, PackLocation { 52, 7 }, HemisphereApplet::trig_length); + return data; } void OnDataReceive(uint64_t data) { + /* leave Play state unchanged - Start/Stop will always be manual if (Unpack(data, PackLocation { 0, 1 })) { clock_m->Start(1); // start paused } else { clock_m->Stop(); } + */ clock_m->SetForwarding(Unpack(data, PackLocation { 1, 1 })); clock_m->SetTempoBPM(Unpack(data, PackLocation { 2, 9 })); - clock_m->SetMultiply(Unpack(data, PackLocation { 11, 6 })-32,0); - clock_m->SetMultiply(Unpack(data, PackLocation { 17, 6 })-32,2); - clock_m->SetClockPPQN(Unpack(data, PackLocation { 23, 5 })); + clock_m->SetClockPPQN(Unpack(data, PackLocation { 11, 5 })); + for (size_t i = 0; i < 4; ++i) { + clock_m->SetMultiply(Unpack(data, PackLocation { 16+i*6, 6 })-32, i); + } + + HemisphereApplet::modal_edit_mode = Unpack(data, PackLocation { 50, 2 }); + HemisphereApplet::trig_length = constrain( Unpack(data, PackLocation { 52, 7 }), 1, 127); } protected: @@ -272,7 +284,7 @@ private: case TRIG3: case TRIG4: if (0 == button_ticker) - gfxIcon(12 + 32*(cursor-TRIG1), 50, LEFT_ICON); + gfxIcon(12 + 32*(cursor-TRIG1), 49, LEFT_ICON); break; default: break; diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index bb355e824..cbe0121ed 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -182,7 +182,7 @@ public: if (clk && (reg[outmode[ch]-TRIG1] & 0x01) == 1) // trigger if 1st bit is high { Output[ch] = HEMISPHERE_MAX_CV; //ClockOut(ch); - trigpulse[ch] = HEMISPHERE_CLOCK_TICKS; + trigpulse[ch] = HEMISPHERE_CLOCK_TICKS * trig_length; } else // decay { @@ -289,8 +289,10 @@ public: cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); smoothing = Unpack(data, PackLocation {41,6}); + /* XXX: registers could be saved/loaded from global storage instead? reg[0] = Unpack(data, PackLocation {32,32}); reg[1] = Unpack(data, PackLocation {0, 32}); // lol it could be fun + */ } protected: diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 6feed2644..04078799c 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -43,7 +43,7 @@ #endif #define HEMISPHERE_CENTER_DETENT 80 #define HEMISPHERE_3V_CV 4608 -#define HEMISPHERE_CLOCK_TICKS 100 +#define HEMISPHERE_CLOCK_TICKS 50 #define HEMISPHERE_CURSOR_TICKS 12000 #define HEMISPHERE_ADC_LAG 33 #define HEMISPHERE_CHANGE_THRESHOLD 32 @@ -124,12 +124,13 @@ class HemisphereApplet { static int last_cv[4]; // For change detection static int cursor_countdown[2]; + static uint8_t trig_length; static uint8_t modal_edit_mode; - /* we might need this again... + static void CycleEditMode() { ++modal_edit_mode %= 3; } - */ + virtual const char* applet_name(); // Maximum of 9 characters virtual void Start(); @@ -459,7 +460,7 @@ class HemisphereApplet { } void ClockOut(int ch, int ticks = HEMISPHERE_CLOCK_TICKS) { - clock_countdown[io_offset + ch] = ticks; + clock_countdown[io_offset + ch] = ticks * trig_length; Out(ch, 0, PULSE_VOLTAGE); } @@ -574,6 +575,7 @@ class HemisphereApplet { }; uint8_t HemisphereApplet::modal_edit_mode = 2; // 0=old behavior, 1=modal editing, 2=modal with wraparound +uint8_t HemisphereApplet::trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS int HemisphereApplet::inputs[4]; int HemisphereApplet::outputs[4]; int HemisphereApplet::outputs_smooth[4]; From b999c3d88fed4b96cae2a30db42464e785e1847e Mon Sep 17 00:00:00 2001 From: Edward Crouch Date: Thu, 23 Mar 2023 20:12:13 -0500 Subject: [PATCH 211/417] Restore remaining fullsize O_C apps (via edcrouch) --- software/o_c_REV/APP_ASR.ino | 1104 ++++++++++++++++++++++++ software/o_c_REV/APP_AUTOMATONNETZ.ino | 737 ++++++++++++++++ software/o_c_REV/APP_BBGEN.ino | 410 +++++++++ software/o_c_REV/APP_BYTEBEATGEN.ino | 608 +++++++++++++ software/o_c_REV/APP_LORENZ.ino | 370 ++++++++ software/o_c_REV/OC_apps.ino | 46 +- software/o_c_REV/tonnetz/tonnetz.h | 4 +- 7 files changed, 3260 insertions(+), 19 deletions(-) create mode 100644 software/o_c_REV/APP_ASR.ino create mode 100644 software/o_c_REV/APP_AUTOMATONNETZ.ino create mode 100644 software/o_c_REV/APP_BBGEN.ino create mode 100644 software/o_c_REV/APP_BYTEBEATGEN.ino create mode 100644 software/o_c_REV/APP_LORENZ.ino diff --git a/software/o_c_REV/APP_ASR.ino b/software/o_c_REV/APP_ASR.ino new file mode 100644 index 000000000..ebddabe7d --- /dev/null +++ b/software/o_c_REV/APP_ASR.ino @@ -0,0 +1,1104 @@ +// Copyright (c) 2014-2017 Max Stadler, Patrick Dowling +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifdef ENABLE_APP_ASR + +#include "util/util_settings.h" +#include "util/util_trigger_delay.h" +#include "util/util_turing.h" +#include "util/util_ringbuffer.h" +#include "util/util_integer_sequences.h" +#include "OC_DAC.h" +#include "OC_menus.h" +#include "OC_scales.h" +#include "OC_scale_edit.h" +#include "OC_strings.h" +#include "OC_visualfx.h" +#include "peaks_bytebeat.h" +#include "extern/dspinst.h" + +namespace menu = OC::menu; // Ugh. This works for all .ino files + +#define NUM_ASR_CHANNELS 0x4 +#define ASR_MAX_ITEMS 256 // = ASR ring buffer size. +#define ASR_HOLD_BUF_SIZE ASR_MAX_ITEMS / NUM_ASR_CHANNELS // max. delay size +#define NUM_INPUT_SCALING 40 // # steps for input sample scaling (sb) + +// CV input gain multipliers +const int32_t multipliers[NUM_INPUT_SCALING] = { + // 0.0 / 2.0 in 0.05 steps + 3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491, 32768, + 36045, 39322, 42598, 45875, 49152, 52429, 55706, 58982, 62259, 65536, + 68813, 72090, 75366, 78643, 81920, 85197, 88474, 91750, 95027, 98304, + 101581, 104858, 108134, 111411, 114688, 117964, 121242, 124518, 127795, 131072 +}; + +const uint8_t MULT_ONE = 19; // == 65536, see above + +enum ASRSettings { + ASR_SETTING_SCALE, + ASR_SETTING_OCTAVE, + ASR_SETTING_ROOT, + ASR_SETTING_MASK, + ASR_SETTING_INDEX, + ASR_SETTING_MULT, + ASR_SETTING_DELAY, + ASR_SETTING_BUFFER_LENGTH, + ASR_SETTING_CV_SOURCE, + ASR_SETTING_CV4_DESTINATION, + ASR_SETTING_TURING_LENGTH, + ASR_SETTING_TURING_PROB, + ASR_SETTING_TURING_CV_SOURCE, + ASR_SETTING_BYTEBEAT_EQUATION, + ASR_SETTING_BYTEBEAT_P0, + ASR_SETTING_BYTEBEAT_P1, + ASR_SETTING_BYTEBEAT_P2, + ASR_SETTING_BYTEBEAT_CV_SOURCE, + ASR_SETTING_INT_SEQ_INDEX, + ASR_SETTING_INT_SEQ_MODULUS, + ASR_SETTING_INT_SEQ_START, + ASR_SETTING_INT_SEQ_LENGTH, + ASR_SETTING_INT_SEQ_DIR, + ASR_SETTING_FRACTAL_SEQ_STRIDE, + ASR_SETTING_INT_SEQ_CV_SOURCE, + ASR_SETTING_LAST +}; + +enum ASRChannelSource { + ASR_CHANNEL_SOURCE_CV1, + ASR_CHANNEL_SOURCE_TURING, + ASR_CHANNEL_SOURCE_BYTEBEAT, + ASR_CHANNEL_SOURCE_INTEGER_SEQUENCES, + ASR_CHANNEL_SOURCE_LAST +}; + +enum ASR_CV4_DEST { + ASR_DEST_OCTAVE, + ASR_DEST_ROOT, + ASR_DEST_TRANSPOSE, + ASR_DEST_BUFLEN, + ASR_DEST_INPUT_SCALING, + ASR_DEST_LAST +}; + +typedef int16_t ASR_pitch; + +class ASRApp : public settings::SettingsBase { +public: + static constexpr size_t kHistoryDepth = 5; + + int get_scale(uint8_t dummy) const { + return values_[ASR_SETTING_SCALE]; + } + + uint8_t get_buffer_length() const { + return values_[ASR_SETTING_BUFFER_LENGTH]; + } + + void set_scale(int scale) { + if (scale != get_scale(DUMMY)) { + const OC::Scale &scale_def = OC::Scales::GetScale(scale); + uint16_t mask = get_mask(); + if (0 == (mask & ~(0xffff << scale_def.num_notes))) + mask |= 0x1; + apply_value(ASR_SETTING_MASK, mask); + apply_value(ASR_SETTING_SCALE, scale); + } + } + + // dummy + int get_scale_select() const { + return 0; + } + + // dummy + void set_scale_at_slot(int scale, uint16_t mask, int root, int transpose, uint8_t scale_slot) { + + } + + // dummy + int get_transpose(uint8_t DUMMY) const { + return 0; + } + + uint16_t get_mask() const { + return values_[ASR_SETTING_MASK]; + } + + int get_root() const { + return values_[ASR_SETTING_ROOT]; + } + + int get_root(uint8_t DUMMY) const { + return 0x0; // dummy + } + + int get_index() const { + return values_[ASR_SETTING_INDEX]; + } + + int get_octave() const { + return values_[ASR_SETTING_OCTAVE]; + } + + bool octave_toggle() { + octave_toggle_ = (~octave_toggle_) & 1u; + return octave_toggle_; + } + + bool poke_octave_toggle() const { + return octave_toggle_; + } + + int get_mult() const { + return values_[ASR_SETTING_MULT]; + } + + int get_cv_source() const { + return values_[ASR_SETTING_CV_SOURCE]; + } + + uint8_t get_cv4_destination() const { + return values_[ASR_SETTING_CV4_DESTINATION]; + } + + uint8_t get_turing_length() const { + return values_[ASR_SETTING_TURING_LENGTH]; + } + + uint8_t get_turing_display_length() { + return turing_display_length_; + } + + uint8_t get_turing_probability() const { + return values_[ASR_SETTING_TURING_PROB]; + } + + uint8_t get_turing_CV() const { + return values_[ASR_SETTING_TURING_CV_SOURCE]; + } + + uint32_t get_shift_register() const { + return turing_machine_.get_shift_register(); + } + + uint16_t get_trigger_delay() const { + return values_[ASR_SETTING_DELAY]; + } + + uint8_t get_bytebeat_equation() const { + return values_[ASR_SETTING_BYTEBEAT_EQUATION]; + } + + uint8_t get_bytebeat_p0() const { + return values_[ASR_SETTING_BYTEBEAT_P0]; + } + + uint8_t get_bytebeat_p1() const { + return values_[ASR_SETTING_BYTEBEAT_P1]; + } + + uint8_t get_bytebeat_p2() const { + return values_[ASR_SETTING_BYTEBEAT_P2]; + } + + uint8_t get_bytebeat_CV() const { + return values_[ASR_SETTING_BYTEBEAT_CV_SOURCE]; + } + + uint8_t get_int_seq_index() const { + return values_[ ASR_SETTING_INT_SEQ_INDEX]; + } + + uint8_t get_int_seq_modulus() const { + return values_[ ASR_SETTING_INT_SEQ_MODULUS]; + } + + int16_t get_int_seq_start() const { + return static_cast(values_[ASR_SETTING_INT_SEQ_START]); + } + + int16_t get_int_seq_length() const { + return static_cast(values_[ASR_SETTING_INT_SEQ_LENGTH] - 1); + } + + bool get_int_seq_dir() const { + return static_cast(values_[ASR_SETTING_INT_SEQ_DIR]); + } + + int16_t get_fractal_seq_stride() const { + return static_cast(values_[ASR_SETTING_FRACTAL_SEQ_STRIDE]); + } + + uint8_t get_int_seq_CV() const { + return values_[ASR_SETTING_INT_SEQ_CV_SOURCE]; + } + + int16_t get_int_seq_k() const { + return int_seq_.get_k(); + } + + int16_t get_int_seq_l() const { + return int_seq_.get_l(); + } + + int16_t get_int_seq_i() const { + return int_seq_.get_i(); + } + + int16_t get_int_seq_j() const { + return int_seq_.get_j(); + } + + int16_t get_int_seq_n() const { + return int_seq_.get_n(); + } + + void toggle_delay_mechanics() { + delay_type_ = (~delay_type_) & 1u; + } + + bool get_delay_type() const { + return delay_type_; + } + + void manual_freeze() { + freeze_switch_ = (~freeze_switch_) & 1u; + } + + void clear_freeze() { + freeze_switch_ = false; + } + + bool freeze_state() { + return freeze_switch_; + } + + ASRSettings enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + void init() { + + force_update_ = false; + last_scale_ = -0x1; + delay_type_ = false; + octave_toggle_ = false; + freeze_switch_ = false; + TR2_state_ = 0x1; + set_scale(OC::Scales::SCALE_SEMI); + last_mask_ = 0x0; + quantizer_.Init(); + update_scale(true, 0x0); + + _ASR.Init(); + clock_display_.Init(); + for (auto &sh : scrolling_history_) + sh.Init(); + update_enabled_settings(); + + trigger_delay_.Init(); + turing_machine_.Init(); + turing_display_length_ = get_turing_length(); + bytebeat_.Init(); + int_seq_.Init(get_int_seq_start(), get_int_seq_length()); + } + + bool update_scale(bool force, int32_t mask_rotate) { + + const int scale = get_scale(DUMMY); + uint16_t mask = get_mask(); + if (mask_rotate) + mask = OC::ScaleEditor::RotateMask(mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); + + if (force || (last_scale_ != scale || last_mask_ != mask)) { + + last_scale_ = scale; + last_mask_ = mask; + quantizer_.Configure(OC::Scales::GetScale(scale), mask); + return true; + } else { + return false; + } + } + + void force_update() { + force_update_ = true; + } + + // Wrappers for ScaleEdit + void scale_changed() { + force_update_ = true; + } + + uint16_t get_scale_mask(uint8_t scale_select) const { + return get_mask(); + } + + void update_scale_mask(uint16_t mask, uint16_t dummy) { + apply_value(ASR_SETTING_MASK, mask); // Should automatically be updated + } + // + + uint16_t get_rotated_mask() const { + return last_mask_; + } + + void set_display_mask(uint16_t mask) { + last_mask_ = mask; + } + + void update_enabled_settings() { + + ASRSettings *settings = enabled_settings_; + + *settings++ = ASR_SETTING_ROOT; + *settings++ = ASR_SETTING_MASK; + *settings++ = ASR_SETTING_OCTAVE; + *settings++ = ASR_SETTING_INDEX; + *settings++ = ASR_SETTING_BUFFER_LENGTH; + *settings++ = ASR_SETTING_DELAY; + *settings++ = ASR_SETTING_MULT; + + *settings++ = ASR_SETTING_CV4_DESTINATION; + *settings++ = ASR_SETTING_CV_SOURCE; + + switch (get_cv_source()) { + case ASR_CHANNEL_SOURCE_TURING: + *settings++ = ASR_SETTING_TURING_LENGTH; + *settings++ = ASR_SETTING_TURING_PROB; + *settings++ = ASR_SETTING_TURING_CV_SOURCE; + break; + case ASR_CHANNEL_SOURCE_BYTEBEAT: + *settings++ = ASR_SETTING_BYTEBEAT_EQUATION; + *settings++ = ASR_SETTING_BYTEBEAT_P0; + *settings++ = ASR_SETTING_BYTEBEAT_P1; + *settings++ = ASR_SETTING_BYTEBEAT_P2; + *settings++ = ASR_SETTING_BYTEBEAT_CV_SOURCE; + break; + case ASR_CHANNEL_SOURCE_INTEGER_SEQUENCES: + *settings++ = ASR_SETTING_INT_SEQ_INDEX; + *settings++ = ASR_SETTING_INT_SEQ_MODULUS; + *settings++ = ASR_SETTING_INT_SEQ_START; + *settings++ = ASR_SETTING_INT_SEQ_LENGTH; + *settings++ = ASR_SETTING_INT_SEQ_DIR; + *settings++ = ASR_SETTING_FRACTAL_SEQ_STRIDE; + *settings++ = ASR_SETTING_INT_SEQ_CV_SOURCE; + break; + default: + break; + } + + num_enabled_settings_ = settings - enabled_settings_; + } + + void updateASR_indexed(int32_t *_asr_buf, int32_t _sample, int16_t _index, bool _freeze) { + + int16_t _delay = _index, _offset; + + if (_freeze) { + + int8_t _buflen = get_buffer_length(); + if (get_cv4_destination() == ASR_DEST_BUFLEN) { + _buflen += ((OC::ADC::value() + 31) >> 6); + CONSTRAIN(_buflen, NUM_ASR_CHANNELS, ASR_HOLD_BUF_SIZE - 0x1); + } + _ASR.Freeze(_buflen); + } + else + _ASR.Write(_sample); + + // update outputs: + _offset = _delay; + *(_asr_buf + DAC_CHANNEL_A) = _ASR.Poke(_offset++); + // delay mechanics ... + _delay = delay_type_ ? 0x0 : _delay; + // continue updating + _offset +=_delay; + *(_asr_buf + DAC_CHANNEL_B) = _ASR.Poke(_offset++); + _offset +=_delay; + *(_asr_buf + DAC_CHANNEL_C) = _ASR.Poke(_offset++); + _offset +=_delay; + *(_asr_buf + DAC_CHANNEL_D) = _ASR.Poke(_offset++); + } + + inline void update() { + + bool update = OC::DigitalInputs::clocked(); + clock_display_.Update(1, update); + + trigger_delay_.Update(); + + if (update) + trigger_delay_.Push(OC::trigger_delay_ticks[get_trigger_delay()]); + + update = trigger_delay_.triggered(); + + if (update) { + + bool _freeze_switch, _freeze = digitalReadFast(TR2); + int8_t _root = get_root(); + int8_t _index = get_index() + ((OC::ADC::value() + 31) >> 6); + int8_t _octave = get_octave(); + int8_t _transpose = 0; + int8_t _mult = get_mult(); + int32_t _pitch = OC::ADC::raw_pitch_value(ADC_CHANNEL_1); + int32_t _asr_buffer[NUM_ASR_CHANNELS]; + + bool forced_update = force_update_; + force_update_ = false; + update_scale(forced_update, (OC::ADC::value() + 127) >> 8); + + // cv4 destination, defaults to octave: + switch(get_cv4_destination()) { + + case ASR_DEST_OCTAVE: + _octave += (OC::ADC::value() + 255) >> 9; + break; + case ASR_DEST_ROOT: + _root += (OC::ADC::value() + 127) >> 8; + CONSTRAIN(_root, 0, 11); + break; + case ASR_DEST_TRANSPOSE: + _transpose += (OC::ADC::value() + 63) >> 7; + CONSTRAIN(_transpose, -12, 12); + break; + case ASR_DEST_INPUT_SCALING: + _mult += (OC::ADC::value() + 63) >> 7; + CONSTRAIN(_mult, 0, NUM_INPUT_SCALING - 1); + break; + // CV for buffer length happens in updateASR_indexed + default: + break; + } + + // freeze ? + if (_freeze < TR2_state_) + freeze_switch_ = true; + else if (_freeze > TR2_state_) { + freeze_switch_ = false; + } + TR2_state_ = _freeze; + // + _freeze_switch = freeze_switch_; + + // use built in CV sources? + if (!_freeze_switch) { + + switch (get_cv_source()) { + + case ASR_CHANNEL_SOURCE_TURING: + { + int16_t _length = get_turing_length(); + int16_t _probability = get_turing_probability(); + + // _pitch can do other things now -- + switch (get_turing_CV()) { + + case 1: // mult + _mult += ((_pitch + 63) >> 7); + break; + case 2: // LEN, 1-32 + _length += ((_pitch + 255) >> 9); + CONSTRAIN(_length, 1, 32); + break; + case 3: // P + _probability += ((_pitch + 7) >> 4); + CONSTRAIN(_probability, 0, 255); + break; + default: + break; + } + + turing_machine_.set_length(_length); + turing_machine_.set_probability(_probability); + turing_display_length_ = _length; + + _pitch = turing_machine_.Clock(); + + // scale LFSR output (0 - 4095) / compensate for length + if (_length < 12) + _pitch = _pitch << (12 -_length); + else + _pitch = _pitch >> (_length - 12); + _pitch &= 0xFFF; + } + break; + case ASR_CHANNEL_SOURCE_BYTEBEAT: + { + int32_t _bytebeat_eqn = get_bytebeat_equation() << 12; + int32_t _bytebeat_p0 = get_bytebeat_p0() << 8; + int32_t _bytebeat_p1 = get_bytebeat_p1() << 8; + int32_t _bytebeat_p2 = get_bytebeat_p2() << 8; + + // _pitch can do other things now -- + switch (get_bytebeat_CV()) { + + case 1: // 0-15 + _bytebeat_eqn += (_pitch << 4); + _bytebeat_eqn = USAT16(_bytebeat_eqn); + break; + case 2: // P0 + _bytebeat_p0 += (_pitch << 4); + _bytebeat_p0 = USAT16(_bytebeat_p0); + break; + case 3: // P1 + _bytebeat_p1 += (_pitch << 4); + _bytebeat_p1 = USAT16(_bytebeat_p1); + break; + case 4: // P4 + _bytebeat_p2 += (_pitch << 4); + _bytebeat_p2 = USAT16(_bytebeat_p2); + break; + default: // mult + _mult += ((_pitch + 63) >> 7); + break; + } + + bytebeat_.set_equation(_bytebeat_eqn); + bytebeat_.set_p0(_bytebeat_p0); + bytebeat_.set_p0(_bytebeat_p1); + bytebeat_.set_p0(_bytebeat_p2); + + int32_t _bb = (static_cast(bytebeat_.Clock()) & 0xFFF); + _pitch = _bb; + } + break; + case ASR_CHANNEL_SOURCE_INTEGER_SEQUENCES: + { + int16_t _int_seq_index = get_int_seq_index() ; + int16_t _int_seq_modulus = get_int_seq_modulus() ; + int16_t _int_seq_start = get_int_seq_start() ; + int16_t _int_seq_length = get_int_seq_length(); + int16_t _fractal_seq_stride = get_fractal_seq_stride(); + bool _int_seq_dir = get_int_seq_dir(); + + // _pitch can do other things now -- + switch (get_int_seq_CV()) { + + case 1: // integer sequence, 0-8 + _int_seq_index += ((_pitch + 255) >> 9); + CONSTRAIN(_int_seq_index, 0, 8); + break; + case 2: // sequence start point, 0 to kIntSeqLen - 2 + _int_seq_start += ((_pitch + 15) >> 5); + CONSTRAIN(_int_seq_start, 0, kIntSeqLen - 2); + break; + case 3: // sequence loop length, 1 to kIntSeqLen - 1 + _int_seq_length += ((_pitch + 15) >> 5); + CONSTRAIN(_int_seq_length, 1, kIntSeqLen - 1); + break; + case 4: // fractal sequence stride length, 1 to kIntSeqLen - 1 + _fractal_seq_stride += ((_pitch + 15) >> 5); + CONSTRAIN(_fractal_seq_stride, 1, kIntSeqLen - 1); + break; + case 5: // fractal sequence modulus + _int_seq_modulus += ((_pitch + 15) >> 5); + CONSTRAIN(_int_seq_modulus, 2, 121); + break; + default: // mult + _mult += ((_pitch + 63) >> 7); + break; + } + + int_seq_.set_loop_start(_int_seq_start); + int_seq_.set_loop_length(_int_seq_length); + int_seq_.set_int_seq(_int_seq_index); + int_seq_.set_int_seq_modulus(_int_seq_modulus); + int_seq_.set_loop_direction(_int_seq_dir); + int_seq_.set_fractal_stride(_fractal_seq_stride); + + int32_t _is = (static_cast(int_seq_.Clock()) & 0xFFF); + _pitch = _is; + } + break; + default: + break; + } + } + else { + // we hold, so we only need the multiplication CV: + switch (get_cv_source()) { + + case ASR_CHANNEL_SOURCE_TURING: + if (get_turing_CV() == 0x0) + _mult += ((_pitch + 63) >> 7); + break; + case ASR_CHANNEL_SOURCE_INTEGER_SEQUENCES: + if (get_int_seq_CV() == 0x0) + _mult += ((_pitch + 63) >> 7); + break; + case ASR_CHANNEL_SOURCE_BYTEBEAT: + if (get_bytebeat_CV() == 0x0) + _mult += ((_pitch + 63) >> 7); + break; + default: + break; + } + } + // limit gain factor. + CONSTRAIN(_mult, 0, NUM_INPUT_SCALING - 0x1); + // .. and index + CONSTRAIN(_index, 0, ASR_HOLD_BUF_SIZE - 0x1); + // push sample into ring-buffer and/or freeze buffer: + updateASR_indexed(_asr_buffer, _pitch, _index, _freeze_switch); + + // get octave offset : + if (!digitalReadFast(TR3)) + _octave++; + else if (!digitalReadFast(TR4)) + _octave--; + + // quantize buffer outputs: + for (int i = 0; i < NUM_ASR_CHANNELS; ++i) { + + int32_t _sample = _asr_buffer[i]; + + // scale sample + if (_mult != MULT_ONE) { + _sample = signed_multiply_32x16b(multipliers[_mult], _sample); + _sample = signed_saturate_rshift(_sample, 16, 0); + } + + _sample = quantizer_.Process(_sample, _root << 7, _transpose); + _sample = OC::DAC::pitch_to_scaled_voltage_dac(static_cast(i), _sample, _octave, OC::DAC::get_voltage_scaling(i)); + scrolling_history_[i].Push(_sample); + _asr_buffer[i] = _sample; + } + + // ... and write to DAC + for (int i = 0; i < NUM_ASR_CHANNELS; ++i) + OC::DAC::set(static_cast(i), _asr_buffer[i]); + + MENU_REDRAW = 0x1; + } + for (auto &sh : scrolling_history_) + sh.Update(); + } + + uint8_t clockState() const { + return clock_display_.getState(); + } + + const OC::vfx::ScrollingHistory &history(int i) const { + return scrolling_history_[i]; + } + +private: + bool force_update_; + bool delay_type_; + bool octave_toggle_; + bool freeze_switch_; + int8_t TR2_state_; + int last_scale_; + uint16_t last_mask_; + braids::Quantizer quantizer_; + OC::DigitalInputDisplay clock_display_; + util::TriggerDelay trigger_delay_; + util::TuringShiftRegister turing_machine_; + util::RingBuffer _ASR; + int8_t turing_display_length_; + peaks::ByteBeat bytebeat_ ; + util::IntegerSequence int_seq_ ; + OC::vfx::ScrollingHistory scrolling_history_[NUM_ASR_CHANNELS]; + int num_enabled_settings_; + ASRSettings enabled_settings_[ASR_SETTING_LAST]; +}; + +const char* const asr_input_sources[] = { + "CV1", "TM", "ByteB", "IntSq" +}; + +const char* const asr_cv4_destinations[] = { + "oct", "root", "trns", "buf.l", "igain" +}; + +const char* const bb_CV_destinations[] = { + "igain", "eqn", "P0", "P1", "P2" +}; + +const char* const int_seq_CV_destinations[] = { + "igain", "seq", "strt", "len", "strd", "mod" +}; + + +SETTINGS_DECLARE(ASRApp, ASR_SETTING_LAST) { + { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "Scale", OC::scale_names_short, settings::STORAGE_TYPE_U8 }, + { 0, -5, 5, "octave", NULL, settings::STORAGE_TYPE_I8 }, // octave + { 0, 0, 11, "root", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 65535, 1, 65535, "mask", NULL, settings::STORAGE_TYPE_U16 }, // mask + { 0, 0, ASR_HOLD_BUF_SIZE - 1, "buf.index", NULL, settings::STORAGE_TYPE_U8 }, + { MULT_ONE, 0, NUM_INPUT_SCALING - 1, "input gain", OC::Strings::mult, settings::STORAGE_TYPE_U8 }, + { 0, 0, OC::kNumDelayTimes - 1, "trigger delay", OC::Strings::trigger_delay_times, settings::STORAGE_TYPE_U8 }, + { 4, 4, ASR_HOLD_BUF_SIZE - 1, "hold (buflen)", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, ASR_CHANNEL_SOURCE_LAST -1, "CV source", asr_input_sources, settings::STORAGE_TYPE_U4 }, + { 0, 0, ASR_DEST_LAST - 1, "CV4 dest. ->", asr_cv4_destinations, settings::STORAGE_TYPE_U4 }, + { 16, 1, 32, "> LFSR length", NULL, settings::STORAGE_TYPE_U8 }, + { 128, 0, 255, "> LFSR p", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 3, "> LFSR CV1", OC::Strings::TM_aux_cv_destinations, settings::STORAGE_TYPE_U8 }, // ?? + { 0, 0, 15, "> BB eqn", OC::Strings::bytebeat_equation_names, settings::STORAGE_TYPE_U8 }, + { 8, 1, 255, "> BB P0", NULL, settings::STORAGE_TYPE_U8 }, + { 12, 1, 255, "> BB P1", NULL, settings::STORAGE_TYPE_U8 }, + { 14, 1, 255, "> BB P2", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 4, "> BB CV1", bb_CV_destinations, settings::STORAGE_TYPE_U4 }, + { 0, 0, 9, "> IntSeq", OC::Strings::integer_sequence_names, settings::STORAGE_TYPE_U4 }, + { 24, 2, 121, "> IntSeq modul", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, kIntSeqLen - 2, "> IntSeq start", NULL, settings::STORAGE_TYPE_U8 }, + { 8, 2, kIntSeqLen, "> IntSeq len", NULL, settings::STORAGE_TYPE_U8 }, + { 1, 0, 1, "> IntSeq dir", OC::Strings::integer_sequence_dirs, settings::STORAGE_TYPE_U4 }, + { 1, 1, kIntSeqLen - 1, "> Fract stride", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 5, "> IntSeq CV1", int_seq_CV_destinations, settings::STORAGE_TYPE_U4 } +}; + +/* -------------------------------------------------------------------*/ + +class ASRState { +public: + + void Init() { + cursor.Init(ASR_SETTING_SCALE, ASR_SETTING_LAST - 1); + scale_editor.Init(false); + left_encoder_value = OC::Scales::SCALE_SEMI; + } + + inline bool editing() const { + return cursor.editing(); + } + + inline int cursor_pos() const { + return cursor.cursor_pos(); + } + + int left_encoder_value; + menu::ScreenCursor cursor; + menu::ScreenCursor cursor_state; + OC::ScaleEditor scale_editor; +}; + +ASRState asr_state; +ASRApp asr; + +void ASR_init() { + + asr.InitDefaults(); + asr.init(); + asr_state.Init(); + asr.update_enabled_settings(); + asr_state.cursor.AdjustEnd(asr.num_enabled_settings() - 1); +} + +size_t ASR_storageSize() { + return ASRApp::storageSize(); +} + +size_t ASR_restore(const void *storage) { + // init nicely + size_t storage_size = asr.Restore(storage); + asr_state.left_encoder_value = asr.get_scale(DUMMY); + asr.set_scale(asr_state.left_encoder_value); + asr.clear_freeze(); + asr.set_display_mask(asr.get_mask()); + asr.update_enabled_settings(); + asr_state.cursor.AdjustEnd(asr.num_enabled_settings() - 1); + return storage_size; +} + +void ASR_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + asr_state.cursor.set_editing(false); + asr_state.scale_editor.Close(); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + asr.update_enabled_settings(); + asr_state.cursor.AdjustEnd(asr.num_enabled_settings() - 1); + break; + } +} + +void ASR_loop() { +} + +void ASR_isr() { + asr.update(); +} + +void ASR_handleButtonEvent(const UI::Event &event) { + if (asr_state.scale_editor.active()) { + asr_state.scale_editor.HandleButtonEvent(event); + return; + } + + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + ASR_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + ASR_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + ASR_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + ASR_rightButton(); + break; + } + } else { + if (OC::CONTROL_BUTTON_L == event.control) + ASR_leftButtonLong(); + else if (OC::CONTROL_BUTTON_DOWN == event.control) + ASR_downButtonLong(); + } +} + +void ASR_handleEncoderEvent(const UI::Event &event) { + + if (asr_state.scale_editor.active()) { + asr_state.scale_editor.HandleEncoderEvent(event); + return; + } + + if (OC::CONTROL_ENCODER_L == event.control) { + + int value = asr_state.left_encoder_value + event.value; + CONSTRAIN(value, 0, OC::Scales::NUM_SCALES - 1); + asr_state.left_encoder_value = value; + + } else if (OC::CONTROL_ENCODER_R == event.control) { + + if (asr_state.editing()) { + + ASRSettings setting = asr.enabled_setting_at(asr_state.cursor_pos()); + + if (ASR_SETTING_MASK != setting) { + + if (asr.change_value(setting, event.value)) + asr.force_update(); + + switch(setting) { + + case ASR_SETTING_CV_SOURCE: + asr.update_enabled_settings(); + asr_state.cursor.AdjustEnd(asr.num_enabled_settings() - 1); + // hack/hide extra options when default CV source is selected + if (!asr.get_cv_source()) + asr_state.cursor.Scroll(asr_state.cursor_pos()); + break; + default: + break; + } + } + } else { + asr_state.cursor.Scroll(event.value); + } + } +} + + +void ASR_topButton() { + if (asr.octave_toggle()) + asr.change_value(ASR_SETTING_OCTAVE, 1); + else + asr.change_value(ASR_SETTING_OCTAVE, -1); +} + +void ASR_lowerButton() { + asr.manual_freeze(); +} + +void ASR_rightButton() { + + switch (asr.enabled_setting_at(asr_state.cursor_pos())) { + + case ASR_SETTING_MASK: { + int scale = asr.get_scale(DUMMY); + if (OC::Scales::SCALE_NONE != scale) + asr_state.scale_editor.Edit(&asr, scale); + } + break; + default: + asr_state.cursor.toggle_editing(); + break; + } +} + +void ASR_leftButton() { + + if (asr_state.left_encoder_value != asr.get_scale(DUMMY)) + asr.set_scale(asr_state.left_encoder_value); +} + +void ASR_leftButtonLong() { + + int scale = asr_state.left_encoder_value; + asr.set_scale(asr_state.left_encoder_value); + if (scale != OC::Scales::SCALE_NONE) + asr_state.scale_editor.Edit(&asr, scale); +} + +void ASR_downButtonLong() { + asr.toggle_delay_mechanics(); +} + +size_t ASR_save(void *storage) { + return asr.Save(storage); +} + +void ASR_menu() { + + menu::TitleBar<0, 4, 0>::Draw(); + + int scale = asr_state.left_encoder_value; + graphics.movePrintPos(weegfx::Graphics::kFixedFontW, 0); + graphics.print(OC::scale_names[scale]); + + if (asr.freeze_state()) + graphics.drawBitmap8(1, menu::QuadTitleBar::kTextY, 4, OC::bitmap_hold_indicator_4x8); + else if (asr.get_scale(DUMMY) == scale) + graphics.drawBitmap8(1, menu::QuadTitleBar::kTextY, 4, OC::bitmap_indicator_4x8); + + if (asr.poke_octave_toggle()) { + graphics.setPrintPos(110, 2); + graphics.print("+"); + } + + if (asr.get_delay_type()) + graphics.drawBitmap8(118, menu::QuadTitleBar::kTextY, 4, OC::bitmap_hold_indicator_4x8); + else + graphics.drawBitmap8(118, menu::QuadTitleBar::kTextY, 4, OC::bitmap_indicator_4x8); + + uint8_t clock_state = (asr.clockState() + 3) >> 2; + if (clock_state) + graphics.drawBitmap8(124, 2, 4, OC::bitmap_gate_indicators_8 + (clock_state << 2)); + + menu::SettingsList settings_list(asr_state.cursor); + menu::SettingsListItem list_item; + + while (settings_list.available()) { + + const int setting = asr.enabled_setting_at(settings_list.Next(list_item)); + const int value = asr.get_value(setting); + const settings::value_attr &attr = ASRApp::value_attr(setting); + + switch (setting) { + + case ASR_SETTING_MASK: + menu::DrawMask(menu::kDisplayWidth, list_item.y, asr.get_rotated_mask(), OC::Scales::GetScale(asr.get_scale(DUMMY)).num_notes); + list_item.DrawNoValue(value, attr); + break; + case ASR_SETTING_CV_SOURCE: + if (asr.get_cv_source() == ASR_CHANNEL_SOURCE_TURING) { + + int turing_length = asr.get_turing_display_length(); + int w = turing_length >= 16 ? 16 * 3 : turing_length * 3; + + menu::DrawMask(menu::kDisplayWidth, list_item.y, asr.get_shift_register(), turing_length); + list_item.valuex = menu::kDisplayWidth - w - 1; + list_item.DrawNoValue(value, attr); + } else + list_item.DrawDefault(value, attr); + break; + default: + list_item.DrawDefault(value, attr); + break; + } + } + if (asr_state.scale_editor.active()) + asr_state.scale_editor.Draw(); +} + +uint16_t channel_history[ASRApp::kHistoryDepth]; + +void ASR_screensaver() { + +// Possible variants (w x h) +// 4 x 32x64 px +// 4 x 64x32 px +// "Somehow" overlapping? +// Normalize history to within one octave? That would make steps more visisble for small ranges +// "Zoomed view" to fit range of history... + + for (int i = 0; i < NUM_ASR_CHANNELS; ++i) { + asr.history(i).Read(channel_history); + uint32_t scroll_pos = asr.history(i).get_scroll_pos() >> 5; + + int pos = 0; + weegfx::coord_t x = i * 32, y; + + y = 63 - ((channel_history[pos++] >> 10) & 0x3f); + graphics.drawHLine(x, y, scroll_pos); + x += scroll_pos; + graphics.drawVLine(x, y, 3); + + weegfx::coord_t last_y = y; + for (int c = 0; c < 3; ++c) { + y = 63 - ((channel_history[pos++] >> 10) & 0x3f); + graphics.drawHLine(x, y, 8); + if (y == last_y) + graphics.drawVLine(x, y, 2); + else if (y < last_y) + graphics.drawVLine(x, y, last_y - y + 1); + else + graphics.drawVLine(x, last_y, y - last_y + 1); + x += 8; + last_y = y; +// graphics.drawVLine(x, y, 3); + } + + y = 63 - ((channel_history[pos++] >> 10) & 0x3f); +// graphics.drawHLine(x, y, 8 - scroll_pos); + graphics.drawRect(x, y, 8 - scroll_pos, 2); + if (y == last_y) + graphics.drawVLine(x, y, 3); + else if (y < last_y) + graphics.drawVLine(x, y, last_y - y + 1); + else + graphics.drawVLine(x, last_y, y - last_y + 1); +// x += 8 - scroll_pos; +// graphics.drawVLine(x, y, 3); + } +} + +#ifdef ASR_DEBUG +void ASR_debug() { + for (int i = 0; i < 1; ++i) { + uint8_t ypos = 10*(i + 1) + 2 ; + graphics.setPrintPos(2, ypos); + graphics.print(asr.get_int_seq_i()); + graphics.setPrintPos(32, ypos); + graphics.print(asr.get_int_seq_l()); + graphics.setPrintPos(62, ypos); + graphics.print(asr.get_int_seq_j()); + graphics.setPrintPos(92, ypos); + graphics.print(asr.get_int_seq_k()); + graphics.setPrintPos(122, ypos); + graphics.print(asr.get_int_seq_n()); + } +} +#endif // ASR_DEBUG + +#endif // ENABLE_APP_ASR \ No newline at end of file diff --git a/software/o_c_REV/APP_AUTOMATONNETZ.ino b/software/o_c_REV/APP_AUTOMATONNETZ.ino new file mode 100644 index 000000000..ee627edf4 --- /dev/null +++ b/software/o_c_REV/APP_AUTOMATONNETZ.ino @@ -0,0 +1,737 @@ +// Copyright (c) 2015, 2016 Patrick Dowling, Tim Churches +// +// Initial app implementation: Patrick Dowling (pld@gurkenkiste.com) +// Modifications by: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// Drive the tonnetz transformations from a grid of cells that contain the type +// of transformation and other goodies. Instead of stepping through the grid +// linearly on each clock, at vector (dx, dy) is used to determine the next +// cell. The cell coordinates use fixed-point internally with a configurable +// sub-step accuracy. +// +// Since the grid is 5x5 and the sub-step accuracy a power-of-two, they might +// not align correctly so there is a cumulative error. As a work-around, the +// grid is given an epsilon and the selectable steps are restricted. Using odd +// dimensions should mean that each cell is reached eventually. +// +// "Vector sequencer" inspired by fcd72, see +// https://dmachinery.wordpress.com/2013/01/05/the-vector-sequencer/ +// +// NOTES +// The initial implementation was using a polling loop (since that's what the +// firmware paradigm was at the time) but has now been updated to be clocked by +// the core ISR. This means some additional hoops are necessary to be able to +// write settings/state from UI and ISR. The main use cases are: +// - Clearing the grid (left long press) +// - Cell event types that modify the cell itself +// +// So these have been wrapped in a minimal critical section/mutex style locking, +// which seems preferable than brute-forcey __disable_irq/__enable_irq, and the +// clear function shouldn't take long anyway. This doesn't cover all cases, but +// but editing while clocking could be classified as "undefined behaviour" ;) +// +// It's also possible for the displayed state to be slightly inconsistent; but +// these should be temporary glitches if they are even noticeable, so the risk +// seems acceptable there. +// +// TODO With fast clocking, trigger out mode might not provide a rising edge + +#ifdef ENABLE_APP_AUTOMATONNETZ + +#include "util/util_grid.h" +#include "util/util_ringbuffer.h" +#include "util/util_settings.h" +#include "util/util_sync.h" +#include "tonnetz/tonnetz_state.h" +#include "OC_bitmaps.h" +#include "OC_menus.h" +#include "OC_trigger_delays.h" + +#define FRACTIONAL_BITS 24 +#define CLOCK_STEP_RES (0x1 << FRACTIONAL_BITS) +#define GRID_EPSILON 6 +#define GRID_DIMENSION 5 +#define GRID_CELLS (GRID_DIMENSION * GRID_DIMENSION) + +// TODO Better selection, e.g. .125, .15 .2 or 3 digits with encoder acceleration + +const size_t clock_fraction[] = { + 0, CLOCK_STEP_RES/8, 1+CLOCK_STEP_RES/7, CLOCK_STEP_RES/6, 1+CLOCK_STEP_RES/5, 1+CLOCK_STEP_RES/4, 1+CLOCK_STEP_RES/3, CLOCK_STEP_RES/2 +}; + +const char *clock_fraction_names[] = { + " \0\0\0\0", " 1/8", " 1/7", " 1/6", " 1/5", " 1/4", " 1/3", " 1/2" +}; + +static constexpr uint32_t TRIGGER_MASK_GRID = OC::DIGITAL_INPUT_1_MASK; +static constexpr uint32_t TRIGGER_MASK_ARP = OC::DIGITAL_INPUT_2_MASK; +static constexpr uint32_t kTriggerOutTicks = 1000U / OC_CORE_TIMER_RATE; + +enum CellSettings { + CELL_SETTING_TRANSFORM, + CELL_SETTING_TRANSPOSE, + CELL_SETTING_INVERSION, + CELL_SETTING_EVENT, + CELL_SETTING_LAST +}; + +enum CellEventMasks { + CELL_EVENT_NONE, + CELL_EVENT_RAND_TRANFORM = 0x1, + CELL_EVENT_RAND_TRANSPOSE = 0x2, + CELL_EVENT_RAND_INVERSION = 0x4, + CELL_EVENT_ALL = 0x7 +}; + +#define CELL_MAX_INVERSION 3 +#define CELL_MIN_INVERSION -3 + +namespace menu = OC::menu; + +class TransformCell : public settings::SettingsBase { +public: + TransformCell() { } + + tonnetz::ETransformType transform() const { + return static_cast(values_[CELL_SETTING_TRANSFORM]); + } + + int transpose() const { + return values_[CELL_SETTING_TRANSPOSE]; + } + + int inversion() const { + return values_[CELL_SETTING_INVERSION]; + } + + CellEventMasks event_masks() const { + return static_cast(values_[CELL_SETTING_EVENT]); + } + + void apply_event_masks() { + int masks = values_[CELL_SETTING_EVENT]; + if (masks & CELL_EVENT_RAND_TRANFORM) + apply_value(CELL_SETTING_TRANSFORM, random(tonnetz::TRANSFORM_LAST + 1)); + if (masks & CELL_EVENT_RAND_TRANSPOSE) + apply_value(CELL_SETTING_TRANSPOSE, -12 + random(24 + 1)); + if (masks & CELL_EVENT_RAND_INVERSION) + apply_value(CELL_SETTING_INVERSION, CELL_MIN_INVERSION + random(2*CELL_MAX_INVERSION + 1)); + } +}; + +const char *cell_event_masks[] = { + "none", + "rT__", // 0x1 + "r_O_", // 0x2 + "rTO_", // 0x1 + 0x2 + "r__I", // 0x4 + "rT_I", // 0x4 + 0x1 + "r_OI", // 0x4 + 0x2 + "rTOI", // 0x4 + 0x2 + 0x1 +}; + +SETTINGS_DECLARE(TransformCell, CELL_SETTING_LAST) { + {0, tonnetz::TRANSFORM_NONE, tonnetz::TRANSFORM_LAST, "Trfm", tonnetz::transform_names_str, settings::STORAGE_TYPE_U8}, + {0, -12, 12, "Offs", NULL, settings::STORAGE_TYPE_I8}, + {0, CELL_MIN_INVERSION, CELL_MAX_INVERSION, "Inv", NULL, settings::STORAGE_TYPE_I8}, + {0, CELL_EVENT_NONE, CELL_EVENT_ALL, "Muta", cell_event_masks, settings::STORAGE_TYPE_U8} +}; + +enum GridSettings { + GRID_SETTING_DX, + GRID_SETTING_DY, + GRID_SETTING_MODE, + GRID_SETTING_OCTAVE, + GRID_SETTING_TRIGGER_DELAY, + GRID_SETTING_OUTPUTMODE, + GRID_SETTING_CLEARMODE, + GRID_SETTING_LAST +}; + +enum EOutputAMode { + OUTPUTA_MODE_ROOT, + OUTPUTA_MODE_TRIG, + OUTPUTA_MODE_ARP, + OUTPUTA_MODE_STRUM, + OUTPUTA_MODE_LAST +}; + +// What happens on long left press +enum ClearMode { + CLEAR_MODE_ZERO, // empty cells + CLEAR_MODE_RAND_TRANSFORM, // random transform + CLEAR_MODE_RAND_TRANSFORM_EV, // random transform event + CLEAR_MODE_LAST +}; + +enum UserAction { + USER_ACTION_RESET, + USER_ACTION_CLOCK, +}; + +class AutomatonnetzState : public settings::SettingsBase { +public: + + void Init() { + InitDefaults(); + memset(cells_, 0, sizeof(cells_)); + grid.Init(cells_); + + quantizer.Init(); + tonnetz_state.init(); + + trigger_delays_.Init(); + strum_inhibit_ = false; + + memset(&ui, 0, sizeof(ui)); + ui.cell_cursor.Init(CELL_SETTING_TRANSFORM, CELL_SETTING_LAST - 1); + ui.grid_cursor.Init(GRID_SETTING_DX, GRID_SETTING_LAST - 1); + + history_ = 0; + cell_transpose_ = cell_inversion_ = 0; + user_actions_.Init(); + critical_section_.Init(); + } + + void ClearGrid() { + util::Lock lock(critical_section_); + ClearGrid(clear_mode()); + } + + void ClearGrid(ClearMode mode) { + memset(cells_, 0, sizeof(cells_)); + switch (mode) { + case CLEAR_MODE_RAND_TRANSFORM: + for (auto &cell : cells_) + cell.apply_value(CELL_SETTING_TRANSFORM, random(tonnetz::TRANSFORM_LAST + 1)); + break; + case CLEAR_MODE_RAND_TRANSFORM_EV: + for (auto &cell : cells_) + cell.apply_value(CELL_SETTING_EVENT, CELL_EVENT_RAND_TRANFORM); + break; + case CLEAR_MODE_ZERO: + default: + break; + } + } + + // Settings wrappers + + size_t dx() const { + const int value = values_[GRID_SETTING_DX]; + return ((value / 8) * CLOCK_STEP_RES) + clock_fraction[value % 8]; + } + + size_t dy() const { + const int value = values_[GRID_SETTING_DY]; + return ((value / 8) * CLOCK_STEP_RES) + clock_fraction[value % 8]; + } + + EMode mode() const { + return static_cast(values_[GRID_SETTING_MODE]); + } + + int octave() const { + return values_[GRID_SETTING_OCTAVE]; + } + + uint16_t get_trigger_delay() const { + return values_[GRID_SETTING_TRIGGER_DELAY]; + } + + EOutputAMode output_mode() const { + return static_cast(values_[GRID_SETTING_OUTPUTMODE]); + } + + ClearMode clear_mode() const { + return static_cast(values_[GRID_SETTING_CLEARMODE]); + } + + // End of settings + + void ISR(); + void Reset(); + + inline void AddUserAction(UserAction action) { + user_actions_.Write(action); + } + + // history length is fixed since it's kept as 4xuint8_t + static const size_t HISTORY_LENGTH = 4; + + uint32_t history() const { + return history_; + } + + TransformCell cells_[GRID_CELLS]; + CellGrid grid; + + OC::SemitoneQuantizer quantizer; + TonnetzState tonnetz_state; + + struct { + int selected_cell; + bool edit_cell; + + menu::ScreenCursor grid_cursor; + menu::ScreenCursor cell_cursor; + } ui; + +private: + + enum CriticalSectionIDs { + CRITICAL_SECTION_ID_MAIN, + CRITICAL_SECTION_ID_ISR + }; + + uint32_t trigger_out_ticks_; + uint_fast8_t arp_index_; + int cell_transpose_, cell_inversion_; + uint32_t history_; + + OC::TriggerDelays trigger_delays_; + bool strum_inhibit_ ; + + util::RingBuffer user_actions_; + util::CriticalSection critical_section_; + + void update_trigger_out(); + void update_outputs(bool chord_changed, int transpose, int inversion); + void push_history(uint32_t pos) { + history_ = (history_ << 8) | (pos & 0xff); + } +}; + +// needed for Automatonnetz to work with or without Harrington 1200 compiled in the build +#ifdef ENABLE_APP_H1200 +extern const char * const mode_names[]; +#else +const char * const mode_names[] = { + "Maj", "Min" +}; +#endif + +const char * const outputa_mode_names[] = { + "root", + "trig", + "arp", + "strm" +}; + +const char * const clear_mode_names[] = { + "zero", "rT", "rTev" +}; + +SETTINGS_DECLARE(AutomatonnetzState, GRID_SETTING_LAST) { + {8, 0, 8*GRID_DIMENSION - 1, "dx", NULL, settings::STORAGE_TYPE_I8}, + {4, 0, 8*GRID_DIMENSION - 1, "dy", NULL, settings::STORAGE_TYPE_I8}, + {MODE_MAJOR, 0, MODE_LAST-1, "Mode", mode_names, settings::STORAGE_TYPE_U8}, + #ifdef BUCHLA_4U + {0, 0, 7, "Oct", NULL, settings::STORAGE_TYPE_I8}, + #else + {0, -3, 3, "Oct", NULL, settings::STORAGE_TYPE_I8}, + #endif + { 0, 0, OC::kNumDelayTimes - 1, "TrDly", OC::Strings::trigger_delay_times, settings::STORAGE_TYPE_U8 }, + {OUTPUTA_MODE_ROOT, OUTPUTA_MODE_ROOT, OUTPUTA_MODE_LAST - 1, "OutA", outputa_mode_names, settings::STORAGE_TYPE_U4}, + {CLEAR_MODE_ZERO, CLEAR_MODE_ZERO, CLEAR_MODE_LAST - 1, "Clr", clear_mode_names, settings::STORAGE_TYPE_U4}, +}; + +AutomatonnetzState automatonnetz_state; + +void Automatonnetz_init() { + automatonnetz_state.Init(); + automatonnetz_state.ClearGrid(CLEAR_MODE_RAND_TRANSFORM); + automatonnetz_state.Reset(); +} + +size_t Automatonnetz_storageSize() { + return AutomatonnetzState::storageSize() + + GRID_CELLS * TransformCell::storageSize(); +} + +void FASTRUN AutomatonnetzState::ISR() { + update_trigger_out(); + + uint32_t triggers = OC::DigitalInputs::clocked(); + triggers = trigger_delays_.Process(triggers, OC::trigger_delay_ticks[get_trigger_delay()]); + + bool reset = false; + while (user_actions_.readable()) { + switch (user_actions_.Read()) { + case USER_ACTION_RESET: + reset = true; + break; + case USER_ACTION_CLOCK: + triggers |= TRIGGER_MASK_GRID; + break; + } + } + + if ((triggers & TRIGGER_MASK_GRID) && OC::DigitalInputs::read_immediate()) + reset = true; + + bool update = false; + if (reset) { + grid.MoveToOrigin(); + arp_index_ = 0; + update = true; + } else if (triggers & TRIGGER_MASK_GRID) { + update = grid.move(dx(), dy()); + } + + bool chord_changed = false; + { + util::TryLock lock(critical_section_); + // Minimal safeguard against update while ClearGrid is active. + // We can't use a blocking Lock here since the ISR interrupts the main + // loop, so the lock would never be relinquished at things implode. + // A user-triggered clear will force a reset anyway and update next ISR, + // so we can just skip it. + + if (update && lock.locked()) { + TransformCell ¤t_cell = grid.mutable_current_cell(); + push_history(grid.current_pos_index()); + tonnetz::ETransformType transform = current_cell.transform(); + if (reset || transform >= tonnetz::TRANSFORM_LAST) { + tonnetz_state.reset(mode()); + chord_changed = true; + } else if (transform != tonnetz::TRANSFORM_NONE) { + tonnetz_state.apply_transformation(transform); + chord_changed = true; + } + + cell_transpose_ = current_cell.transpose(); + cell_inversion_ = current_cell.inversion(); + current_cell.apply_event_masks(); + } + } + + // Arp/strum + if (chord_changed && OUTPUTA_MODE_STRUM == output_mode()) { + arp_index_ = 0; + strum_inhibit_ = false; + } else if ((triggers & TRIGGER_MASK_ARP) && + !reset && + !OC::DigitalInputs::read_immediate()) { + ++arp_index_; + if (arp_index_ >= 3) { + arp_index_ = 0; + strum_inhibit_ = true; + } + } + + if ((triggers & TRIGGER_MASK_GRID) || (triggers & TRIGGER_MASK_ARP)) + update_outputs(chord_changed, cell_transpose_, cell_inversion_); +} + +void AutomatonnetzState::Reset() { + // Assumed to be called w/o ISR active! + grid.MoveToOrigin(); + push_history(grid.current_pos_index()); + tonnetz_state.reset(mode()); + arp_index_ = 0; + const TransformCell ¤t_cell = grid.current_cell(); + cell_transpose_ = current_cell.transpose(); + cell_inversion_ = current_cell.inversion(); + update_outputs(true, cell_transpose_, cell_inversion_); +} + +void AutomatonnetzState::update_outputs(bool chord_changed, int transpose, int inversion) { + + int32_t root = + quantizer.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_1)) + transpose; + + inversion += ((OC::ADC::value() + 255) >> 9); + CONSTRAIN(inversion, CELL_MIN_INVERSION * 2, CELL_MAX_INVERSION * 2); + + tonnetz_state.render(root, inversion); + + switch (output_mode()) { + case OUTPUTA_MODE_ROOT: + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(0), octave(), OC::DAC::get_voltage_scaling(DAC_CHANNEL_A)); + break; + case OUTPUTA_MODE_TRIG: + if (chord_changed) { + trigger_out_ticks_ = kTriggerOutTicks; + OC::DAC::set_octave(DAC_CHANNEL_A, 5); + } + break; + case OUTPUTA_MODE_ARP: + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(arp_index_ + 1), octave(), OC::DAC::get_voltage_scaling(DAC_CHANNEL_A)); + case OUTPUTA_MODE_STRUM: + if (!strum_inhibit_) + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(arp_index_ + 1), octave(), OC::DAC::get_voltage_scaling(DAC_CHANNEL_A)); + break; + case OUTPUTA_MODE_LAST: + default: + break; + } + + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(1), octave(), OC::DAC::get_voltage_scaling(DAC_CHANNEL_B)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(2), octave(), OC::DAC::get_voltage_scaling(DAC_CHANNEL_C)); + OC::DAC::set_voltage_scaled_semitone(tonnetz_state.outputs(3), octave(), OC::DAC::get_voltage_scaling(DAC_CHANNEL_D)); +} + +void AutomatonnetzState::update_trigger_out() { + if (trigger_out_ticks_) { + uint32_t ticks = trigger_out_ticks_; + --ticks; + if (!ticks) + OC::DAC::set_octave(DAC_CHANNEL_A, 0); + trigger_out_ticks_ = ticks; + } +} + +void Automatonnetz_loop() { +} + +void FASTRUN Automatonnetz_isr() { + // All user actions, etc. handled in ::Update + automatonnetz_state.ISR(); +} + +static const weegfx::coord_t kGridXStart = 0; +static const weegfx::coord_t kGridYStart = 2; +static const weegfx::coord_t kGridH = 12; +static const weegfx::coord_t kGridW = 12; +static const weegfx::coord_t kMenuStartX = 62; +static const weegfx::coord_t kLineHeight = 11; + +namespace automatonnetz { + +void draw_cell_menu() { + + menu::TitleBar::Draw(); + graphics.print("CELL "); + graphics.print(automatonnetz_state.ui.selected_cell / 5 + 1); + graphics.print(','); + graphics.print(automatonnetz_state.ui.selected_cell % 5 + 1); + + const TransformCell &cell = automatonnetz_state.grid.at(automatonnetz_state.ui.selected_cell); + + menu::SettingsList settings_list(automatonnetz_state.ui.cell_cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int current = settings_list.Next(list_item); + list_item.DrawDefault(cell.get_value(current), TransformCell::value_attr(current)); + } +} + +void draw_grid_menu() { + EMode mode = automatonnetz_state.tonnetz_state.current_chord().mode(); + int outputs[4]; + automatonnetz_state.tonnetz_state.get_outputs(outputs); + + menu::TitleBar::Draw(); + for (size_t i=1; i < 4; ++i) { + graphics.print(note_name(outputs[i])); + graphics.movePrintPos(weegfx::Graphics::kFixedFontW/2, 0); + } + if (MODE_MAJOR == mode) + graphics.print('+'); + else + graphics.print('-'); + + menu::SettingsList settings_list(automatonnetz_state.ui.grid_cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int current = settings_list.Next(list_item); + const int value = automatonnetz_state.get_value(current); + const settings::value_attr &attr = AutomatonnetzState::value_attr(current); + + if (current <= GRID_SETTING_DY) { + const int integral = value / 8; + const int fraction = value % 8; + char value_str[6]; + memcpy(value_str, clock_fraction_names[fraction], 6); + if (integral || !fraction) + value_str[0] = (char)('0' + integral); + + list_item.valuex = list_item.endx - 30; + list_item.DrawDefault(value_str, value, attr); + } else { + list_item.DrawDefault(value, attr); + } + } +} + +}; // namespace automatonnetz + +void Automatonnetz_menu() { + uint16_t row = 0, col = 0; + for (int i = 0; i < GRID_CELLS; ++i) { + + const TransformCell &cell = automatonnetz_state.grid.at(i); + weegfx::coord_t x = kGridXStart + col * kGridW; + weegfx::coord_t y = kGridYStart + row * kGridH; + + graphics.setPrintPos(x + 3, y + 3); + graphics.print(tonnetz::transform_names[cell.transform()]); + + if (i == automatonnetz_state.ui.selected_cell) + graphics.drawFrame(x, y, kGridW, kGridH); + + if (col < GRID_DIMENSION - 1) { + ++col; + } else { + ++row; + col = 0; + } + } + + const vec2 current_pos = automatonnetz_state.grid.current_pos(); + graphics.invertRect(kGridXStart + current_pos.x * kGridW + 1, + kGridYStart + current_pos.y * kGridH + 1, + kGridW - 2, kGridH - 2); + + if (automatonnetz_state.ui.edit_cell) + automatonnetz::draw_cell_menu(); + else + automatonnetz::draw_grid_menu(); +} + +static const weegfx::coord_t kScreenSaverGridX = kGridXStart + kGridW / 2; +static const weegfx::coord_t kScreenSaverGridY = kGridYStart + kGridH / 2; +static const weegfx::coord_t kScreenSaverGridW = kGridW; +static const weegfx::coord_t kScreenSaverGridH = kGridH; + +static constexpr weegfx::coord_t kNoteCircleX = 96; +static constexpr weegfx::coord_t kNoteCircleY = 32; + +inline vec2 extract_pos(uint32_t history) { + return vec2((history & 0xff) / GRID_DIMENSION, (history & 0xff) % GRID_DIMENSION); +} + +void Automatonnetz_screensaver() { + int outputs[4]; + automatonnetz_state.tonnetz_state.get_outputs(outputs); + uint32_t cell_history = automatonnetz_state.history(); + + uint8_t normalized[3]; + for (size_t i=0; i < 3; ++i) + normalized[i] = (outputs[i + 1] + 120) % 12; + OC::visualize_pitch_classes(normalized, kNoteCircleX, kNoteCircleY); + + vec2 last_pos = extract_pos(cell_history); + cell_history >>= 8; + graphics.drawBitmap8(kScreenSaverGridX + last_pos.x * kScreenSaverGridW - 3, + kScreenSaverGridY + last_pos.y * kScreenSaverGridH - 3, + 8, OC::circle_disk_bitmap_8x8); + for (size_t i = 1; i < AutomatonnetzState::HISTORY_LENGTH; ++i, cell_history >>= 8) { + const vec2 current = extract_pos(cell_history); + graphics.drawLine(kScreenSaverGridX + last_pos.x * kScreenSaverGridW, kScreenSaverGridY + last_pos.y * kScreenSaverGridH, + kScreenSaverGridX + current.x * kScreenSaverGridW, kScreenSaverGridY + current.y * kScreenSaverGridH); + + graphics.drawBitmap8(kScreenSaverGridX + current.x * kScreenSaverGridW - 3, + kScreenSaverGridY + current.y * kScreenSaverGridH - 3, + 8, OC::circle_bitmap_8x8); + last_pos = current; + } +} + +size_t Automatonnetz_save(void *dest) { + char *storage = static_cast(dest); + size_t used = automatonnetz_state.Save(storage); + for (size_t cell = 0; cell < GRID_CELLS; ++cell) + used += automatonnetz_state.cells_[cell].Save(storage + used); + + return used; +} + +size_t Automatonnetz_restore(const void *dest) { + const char *storage = static_cast(dest); + size_t used = automatonnetz_state.Restore(storage); + for (size_t cell = 0; cell < GRID_CELLS; ++cell) + used += automatonnetz_state.cells_[cell].Restore(storage + used); + + return used; +} + +void Automatonnetz_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + OC::ui.encoder_enable_acceleration(OC::CONTROL_ENCODER_L, false); + automatonnetz_state.AddUserAction(USER_ACTION_RESET); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void Automatonnetz_rightButton() { +} + +void Automatonnetz_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + automatonnetz_state.AddUserAction(USER_ACTION_RESET); + break; + case OC::CONTROL_BUTTON_DOWN: + automatonnetz_state.AddUserAction(USER_ACTION_CLOCK); + break; + case OC::CONTROL_BUTTON_L: + automatonnetz_state.ui.edit_cell = !automatonnetz_state.ui.edit_cell; + break; + case OC::CONTROL_BUTTON_R: + if (automatonnetz_state.ui.edit_cell) + automatonnetz_state.ui.cell_cursor.toggle_editing(); + else + automatonnetz_state.ui.grid_cursor.toggle_editing(); + break; + } + } else { + if (OC::CONTROL_BUTTON_L == event.control) { + automatonnetz_state.ClearGrid(); + // Forcing reset might make critical section even less necesary... + automatonnetz_state.AddUserAction(USER_ACTION_RESET); + } + } +} + +void Automatonnetz_handleEncoderEvent(const UI::Event &event) { + + if (OC::CONTROL_ENCODER_L == event.control) { + int selected = automatonnetz_state.ui.selected_cell + event.value; + CONSTRAIN(selected, 0, GRID_CELLS - 1); + automatonnetz_state.ui.selected_cell = selected; + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (automatonnetz_state.ui.edit_cell) { + if (automatonnetz_state.ui.cell_cursor.editing()) { + TransformCell &cell = automatonnetz_state.grid.mutable_cell(automatonnetz_state.ui.selected_cell); + cell.change_value(automatonnetz_state.ui.cell_cursor.cursor_pos(), event.value); + } else { + automatonnetz_state.ui.cell_cursor.Scroll(event.value); + } + } else { + if (automatonnetz_state.ui.grid_cursor.editing()) { + automatonnetz_state.change_value(automatonnetz_state.ui.grid_cursor.cursor_pos(), event.value); + } else { + automatonnetz_state.ui.grid_cursor.Scroll(event.value); + } + } + } +} + +#endif // ENABLE_APP_AUTOMATONNETZ \ No newline at end of file diff --git a/software/o_c_REV/APP_BBGEN.ino b/software/o_c_REV/APP_BBGEN.ino new file mode 100644 index 000000000..704793b27 --- /dev/null +++ b/software/o_c_REV/APP_BBGEN.ino @@ -0,0 +1,410 @@ +// Copyright (c) 2015, 2016 Patrick Dowling, Max Stadler, Tim Churches +// +// Author of original O+C firmware: Max Stadler (mxmlnstdlr@gmail.com) +// Author of app scaffolding: Patrick Dowling (pld@gurkenkiste.com) +// Modified for bouncing balls: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Bouncing balls app + +#ifdef ENABLE_APP_BBGEN + +#include "OC_apps.h" +#include "OC_bitmaps.h" +#include "OC_digital_inputs.h" +#include "OC_strings.h" +#include "util/util_math.h" +#include "util/util_settings.h" +#include "OC_menus.h" +#include "peaks_bouncing_balls.h" + +enum BouncingBallSettings { + BB_SETTING_GRAVITY, + BB_SETTING_BOUNCE_LOSS, + BB_SETTING_INITIAL_AMPLITUDE, + BB_SETTING_INITIAL_VELOCITY, + BB_SETTING_TRIGGER_INPUT, + BB_SETTING_RETRIGGER_BOUNCES, + BB_SETTING_CV1, + BB_SETTING_CV2, + BB_SETTING_CV3, + BB_SETTING_CV4, + BB_SETTING_HARD_RESET, + BB_SETTING_LAST +}; + +enum BallCVMapping { + BB_CV_MAPPING_NONE, + BB_CV_MAPPING_GRAVITY, + BB_CV_MAPPING_BOUNCE_LOSS, + BB_CV_MAPPING_INITIAL_AMPLITUDE, + BB_CV_MAPPING_INITIAL_VELOCITY, + BB_CV_MAPPING_RETRIGGER_BOUNCES, + BB_CV_MAPPING_LAST +}; + +namespace menu = OC::menu; + +class BouncingBall : public settings::SettingsBase { +public: + + static constexpr int kMaxBouncingBallParameters = 5; + + void Init(OC::DigitalInput default_trigger); + + OC::DigitalInput get_trigger_input() const { + return static_cast(values_[BB_SETTING_TRIGGER_INPUT]); + } + + BallCVMapping get_cv1_mapping() const { + return static_cast(values_[BB_SETTING_CV1]); + } + + BallCVMapping get_cv2_mapping() const { + return static_cast(values_[BB_SETTING_CV2]); + } + + BallCVMapping get_cv3_mapping() const { + return static_cast(values_[BB_SETTING_CV3]); + } + + BallCVMapping get_cv4_mapping() const { + return static_cast(values_[BB_SETTING_CV4]); + } + + bool get_hard_reset() const { + return values_[BB_SETTING_HARD_RESET]; + } + + uint8_t get_initial_amplitude() const { + return values_[BB_SETTING_INITIAL_AMPLITUDE]; + } + + uint8_t get_initial_velocity() const { + return values_[BB_SETTING_INITIAL_VELOCITY]; + } + + uint8_t get_gravity() const { + return values_[BB_SETTING_GRAVITY]; + } + + uint8_t get_bounce_loss() const { + return values_[BB_SETTING_BOUNCE_LOSS]; + } + + int32_t get_retrigger_bounces() const { + return static_cast(values_[BB_SETTING_RETRIGGER_BOUNCES]); + } + +#ifdef BBGEN_DEBUG + uint16_t get_channel_parameter_value(uint8_t param) { + return s[param]; + } + + int16_t get_channel_retrigger_bounces() { + return(bb_.get_retrigger_bounces()) ; + } +#endif // BBGEN_DEBUG + + inline void apply_cv_mapping(BouncingBallSettings cv_setting, const int32_t cvs[ADC_CHANNEL_LAST], int32_t segments[kMaxBouncingBallParameters]) { + int mapping = values_[cv_setting]; + uint8_t bb_cv_rshift = 13 ; + switch (mapping) { + case BB_CV_MAPPING_GRAVITY: + case BB_CV_MAPPING_BOUNCE_LOSS: + case BB_CV_MAPPING_INITIAL_VELOCITY: + bb_cv_rshift = 13 ; + break ; + case BB_CV_MAPPING_INITIAL_AMPLITUDE: + bb_cv_rshift = 12 ; + break; + case BB_CV_MAPPING_RETRIGGER_BOUNCES: + bb_cv_rshift = 14 ; + break; + default: + bb_cv_rshift = 13 ; + break; + } + if (mapping) + segments[mapping - BB_CV_MAPPING_GRAVITY] += (cvs[cv_setting - BB_SETTING_CV1]) << (16 - bb_cv_rshift) ; + } + + template + void Update(uint32_t triggers, const int32_t cvs[ADC_CHANNEL_LAST]) { + + s[0] = SCALE8_16(static_cast(get_gravity())); + s[1] = SCALE8_16(static_cast(get_bounce_loss())); + s[2] = SCALE8_16(static_cast(get_initial_amplitude())); + s[3] = SCALE8_16(static_cast(get_initial_velocity())); + s[4] = SCALE8_16(static_cast(get_retrigger_bounces())); + + apply_cv_mapping(BB_SETTING_CV1, cvs, s); + apply_cv_mapping(BB_SETTING_CV2, cvs, s); + apply_cv_mapping(BB_SETTING_CV3, cvs, s); + apply_cv_mapping(BB_SETTING_CV4, cvs, s); + + s[0] = USAT16(s[0]); + s[1] = USAT16(s[1]); + s[2] = USAT16(s[2]); + s[3] = USAT16(s[3]); + s[4] = USAT16(s[4]); + + bb_.Configure(s) ; + + // hard reset forces the bouncing ball to start at level_[0] on rising gate. + bb_.set_hard_reset(get_hard_reset()); + + OC::DigitalInput trigger_input = get_trigger_input(); + uint8_t gate_state = 0; + if (triggers & DIGITAL_INPUT_MASK(trigger_input)) + gate_state |= peaks::CONTROL_GATE_RISING; + + bool gate_raised = OC::DigitalInputs::read_immediate(trigger_input); + if (gate_raised) + gate_state |= peaks::CONTROL_GATE; + else if (gate_raised_) + gate_state |= peaks::CONTROL_GATE_FALLING; + gate_raised_ = gate_raised; + + // TODO Scale range or offset? + uint32_t value = OC::DAC::get_zero_offset(dac_channel) + bb_.ProcessSingleSample(gate_state, OC::DAC::MAX_VALUE - OC::DAC::get_zero_offset(dac_channel)); + OC::DAC::set(value); + } + + +private: + peaks::BouncingBall bb_; + bool gate_raised_; + int32_t s[kMaxBouncingBallParameters]; + +}; + +void BouncingBall::Init(OC::DigitalInput default_trigger) { + InitDefaults(); + apply_value(BB_SETTING_TRIGGER_INPUT, default_trigger); + bb_.Init(); + gate_raised_ = false; +} + +const char* const bb_cv_mapping_names[BB_CV_MAPPING_LAST] = { + "off", "grav", "bnce", "ampl", "vel", "retr" +}; + +SETTINGS_DECLARE(BouncingBall, BB_SETTING_LAST) { + { 128, 0, 255, "Gravity", NULL, settings::STORAGE_TYPE_U8 }, + { 96, 0, 255, "Bounce loss", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Amplitude", NULL, settings::STORAGE_TYPE_U8 }, + { 228, 0, 255, "Velocity", NULL, settings::STORAGE_TYPE_U8 }, + { OC::DIGITAL_INPUT_1, OC::DIGITAL_INPUT_1, OC::DIGITAL_INPUT_4, "Trigger input", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Retrigger", NULL, settings::STORAGE_TYPE_U8 }, + { BB_CV_MAPPING_NONE, BB_CV_MAPPING_NONE, BB_CV_MAPPING_LAST - 1, "CV1 -> ", bb_cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { BB_CV_MAPPING_NONE, BB_CV_MAPPING_NONE, BB_CV_MAPPING_LAST - 1, "CV2 -> ", bb_cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { BB_CV_MAPPING_NONE, BB_CV_MAPPING_NONE, BB_CV_MAPPING_LAST - 1, "CV3 -> ", bb_cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { BB_CV_MAPPING_NONE, BB_CV_MAPPING_NONE, BB_CV_MAPPING_LAST - 1, "CV4 -> ", bb_cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { 0, 0, 1, "Hard reset", OC::Strings::no_yes, settings::STORAGE_TYPE_U8 }, +}; + +class QuadBouncingBalls { +public: + static constexpr int32_t kCvSmoothing = 16; + + // bb = env, balls_ = envelopes_, BouncingBall = EnvelopeGenerator + // QuadBouncingBalls = QuadEnvelopeGenerator, bbgen = envgen, BBGEN = ENVGEN + + void Init() { + int input = OC::DIGITAL_INPUT_1; + for (auto &bb : balls_) { + bb.Init(static_cast(input)); + ++input; + } + + ui.left_encoder_value = 0; + ui.left_edit_mode = MODE_EDIT_SETTINGS; + ui.selected_channel = 0; + ui.selected_segment = 0; + ui.cursor.Init(BB_SETTING_GRAVITY, BB_SETTING_LAST - 1); + } + + void ISR() { + cv1.push(OC::ADC::value()); + cv2.push(OC::ADC::value()); + cv3.push(OC::ADC::value()); + cv4.push(OC::ADC::value()); + + const int32_t cvs[ADC_CHANNEL_LAST] = { cv1.value(), cv2.value(), cv3.value(), cv4.value() }; + uint32_t triggers = OC::DigitalInputs::clocked(); + + balls_[0].Update(triggers, cvs); + balls_[1].Update(triggers, cvs); + balls_[2].Update(triggers, cvs); + balls_[3].Update(triggers, cvs); + } + + enum LeftEditMode { + MODE_SELECT_CHANNEL, + MODE_EDIT_SETTINGS + }; + + struct { + LeftEditMode left_edit_mode; + int left_encoder_value; + + int selected_channel; + int selected_segment; + menu::ScreenCursor cursor; + } ui; + + BouncingBall &selected() { + return balls_[ui.selected_channel]; + } + + BouncingBall balls_[4]; + + SmoothedValue cv1; + SmoothedValue cv2; + SmoothedValue cv3; + SmoothedValue cv4; +}; + +QuadBouncingBalls bbgen; + +void BBGEN_init() { + bbgen.Init(); +} + +size_t BBGEN_storageSize() { + return 4 * BouncingBall::storageSize(); +} + +size_t BBGEN_save(void *storage) { + size_t s = 0; + for (auto &bb : bbgen.balls_) + s += bb.Save(static_cast(storage) + s); + return s; +} + +size_t BBGEN_restore(const void *storage) { + size_t s = 0; + for (auto &bb : bbgen.balls_) + s += bb.Restore(static_cast(storage) + s); + return s; +} + +void BBGEN_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + bbgen.ui.cursor.set_editing(false); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void BBGEN_loop() { +} + +void BBGEN_menu() { + + menu::QuadTitleBar::Draw(); + for (uint_fast8_t i = 0; i < 4; ++i) { + menu::QuadTitleBar::SetColumn(i); + graphics.print((char)('A' + i)); + } + menu::QuadTitleBar::Selected(bbgen.ui.selected_channel); + + auto const &bb = bbgen.selected(); + menu::SettingsList settings_list(bbgen.ui.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int current = settings_list.Next(list_item); + list_item.DrawDefault(bb.get_value(current), BouncingBall::value_attr(current)); + } +} + +void BBGEN_topButton() { + auto &selected_bb = bbgen.selected(); + selected_bb.change_value(BB_SETTING_GRAVITY + bbgen.ui.selected_segment, 32); +} + +void BBGEN_lowerButton() { + auto &selected_bb = bbgen.selected(); + selected_bb.change_value(BB_SETTING_GRAVITY + bbgen.ui.selected_segment, -32); +} + +void BBGEN_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + BBGEN_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + BBGEN_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + break; + case OC::CONTROL_BUTTON_R: + bbgen.ui.cursor.toggle_editing(); + break; + } + } +} + +void BBGEN_handleEncoderEvent(const UI::Event &event) { + + if (OC::CONTROL_ENCODER_L == event.control) { + int left_value = bbgen.ui.selected_channel + event.value; + CONSTRAIN(left_value, 0, 3); + bbgen.ui.selected_channel = left_value; + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (bbgen.ui.cursor.editing()) { + auto &selected = bbgen.selected(); + selected.change_value(bbgen.ui.cursor.cursor_pos(), event.value); + } else { + bbgen.ui.cursor.Scroll(event.value); + } + } +} + +#ifdef BBGEN_DEBUG +void BBGEN_debug() { + graphics.setPrintPos(2, 12); + graphics.print(bbgen.cv1.value()); + graphics.setPrintPos(32, 12); + graphics.print(bbgen.balls_[0].get_channel_retrigger_bounces()); + graphics.setPrintPos(2, 22); + graphics.print(bbgen.cv2.value()); + graphics.setPrintPos(2, 32); + graphics.print(bbgen.cv3.value()); + graphics.setPrintPos(2, 42); + graphics.print(bbgen.cv4.value()); +} +#endif // BBGEN_DEBUG + +void BBGEN_screensaver() { + OC::scope_render(); +} + +void FASTRUN BBGEN_isr() { + bbgen.ISR(); +} + +#endif // ENABLE_APP_BBGEN \ No newline at end of file diff --git a/software/o_c_REV/APP_BYTEBEATGEN.ino b/software/o_c_REV/APP_BYTEBEATGEN.ino new file mode 100644 index 000000000..e2ad64c6f --- /dev/null +++ b/software/o_c_REV/APP_BYTEBEATGEN.ino @@ -0,0 +1,608 @@ +// Copyright (c) 2015, 2016 Patrick Dowling, Max Stadler, Tim Churches +// +// Author of original O+C firmware: Max Stadler (mxmlnstdlr@gmail.com) +// Author of app scaffolding: Patrick Dowling (pld@gurkenkiste.com) +// Modified for byte beats: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Byte beats app + +#ifdef ENABLE_APP_BYTEBEATGEN + +#include "OC_apps.h" +#include "OC_bitmaps.h" +#include "OC_digital_inputs.h" +#include "OC_strings.h" +#include "util/util_history.h" +#include "util/util_math.h" +#include "util/util_settings.h" +#include "OC_menus.h" +#include "peaks_bytebeat.h" + +enum ByteBeatSettings { + BYTEBEAT_SETTING_EQUATION, + BYTEBEAT_SETTING_SPEED, + BYTEBEAT_SETTING_PITCH, + BYTEBEAT_SETTING_P0, + BYTEBEAT_SETTING_P1, + BYTEBEAT_SETTING_P2, + BYTEBEAT_SETTING_LOOP_MODE, + BYTEBEAT_SETTING_LOOP_START, + BYTEBEAT_SETTING_LOOP_START_MED, + BYTEBEAT_SETTING_LOOP_START_FINE, + BYTEBEAT_SETTING_LOOP_END, + BYTEBEAT_SETTING_LOOP_END_MED, + BYTEBEAT_SETTING_LOOP_END_FINE, + BYTEBEAT_SETTING_TRIGGER_INPUT, + BYTEBEAT_SETTING_STEP_MODE, + BYTEBEAT_SETTING_CV1, + BYTEBEAT_SETTING_CV2, + BYTEBEAT_SETTING_CV3, + BYTEBEAT_SETTING_CV4, + BYTEBEAT_SETTING_LAST, + BYTEBEAT_SETTING_FIRST=BYTEBEAT_SETTING_EQUATION, +}; + +enum ByteBeatCVMapping { + BYTEBEAT_CV_MAPPING_NONE, + BYTEBEAT_CV_MAPPING_EQUATION, + BYTEBEAT_CV_MAPPING_SPEED, + BYTEBEAT_CV_MAPPING_P0, + BYTEBEAT_CV_MAPPING_P1, + BYTEBEAT_CV_MAPPING_P2, + BYTEBEAT_CV_MAPPING_LOOP_START, + BYTEBEAT_CV_MAPPING_LOOP_START_MED, + BYTEBEAT_CV_MAPPING_LOOP_START_FINE, + BYTEBEAT_CV_MAPPING_LOOP_END, + BYTEBEAT_CV_MAPPING_LOOP_END_MED, + BYTEBEAT_CV_MAPPING_LOOP_END_FINE, + BYTEBEAT_CV_MAPPING_PITCH, + BYTEBEAT_CV_MAPPING_LAST, + BYTEBEAT_CV_MAPPING_FIRST=BYTEBEAT_CV_MAPPING_EQUATION + +}; + +namespace menu = OC::menu; + +class ByteBeat : public settings::SettingsBase { +public: + + static constexpr size_t kHistoryDepth = 64; + static constexpr int kMaxByteBeatParameters = 12; + + void Init(OC::DigitalInput default_trigger); + + OC::DigitalInput get_trigger_input() const { + return static_cast(values_[BYTEBEAT_SETTING_TRIGGER_INPUT]); + } + + ByteBeatCVMapping get_cv1_mapping() const { + return static_cast(values_[BYTEBEAT_SETTING_CV1]); + } + + ByteBeatCVMapping get_cv2_mapping() const { + return static_cast(values_[BYTEBEAT_SETTING_CV2]); + } + + ByteBeatCVMapping get_cv3_mapping() const { + return static_cast(values_[BYTEBEAT_SETTING_CV3]); + } + + ByteBeatCVMapping get_cv4_mapping() const { + return static_cast(values_[BYTEBEAT_SETTING_CV4]); + } + + uint8_t get_equation() const { + return values_[BYTEBEAT_SETTING_EQUATION]; + } + + bool get_step_mode() const { + return values_[BYTEBEAT_SETTING_STEP_MODE]; + } + + uint8_t get_speed() const { + return values_[BYTEBEAT_SETTING_SPEED]; + } + + uint8_t get_pitch() const { + return values_[BYTEBEAT_SETTING_PITCH]; + } + + uint8_t get_p0() const { + return values_[BYTEBEAT_SETTING_P0]; + } + + uint8_t get_p1() const { + return values_[BYTEBEAT_SETTING_P1]; + } + + uint8_t get_p2() const { + return values_[BYTEBEAT_SETTING_P2]; + } + + bool get_loop_mode() const { + return values_[BYTEBEAT_SETTING_LOOP_MODE]; + } + + uint8_t get_loop_start() const { + return values_[BYTEBEAT_SETTING_LOOP_START]; + } + + uint8_t get_loop_start_med() const { + return values_[BYTEBEAT_SETTING_LOOP_START_MED]; + } + + uint8_t get_loop_start_fine() const { + return values_[BYTEBEAT_SETTING_LOOP_START_FINE]; + } + + uint8_t get_loop_end() const { + return values_[BYTEBEAT_SETTING_LOOP_END]; + } + + uint8_t get_loop_end_med() const { + return values_[BYTEBEAT_SETTING_LOOP_END_MED]; + } + + uint8_t get_loop_end_fine() const { + return values_[BYTEBEAT_SETTING_LOOP_END_FINE]; + } + + int32_t get_s(uint8_t param) { + return s_[param]; + } + + uint32_t get_t() { + return static_cast(bytebeat_.get_t()); + } + + uint32_t get_eqn_num() { + return static_cast(bytebeat_.get_eqn_num()); + } + + uint32_t get_phase() { + return static_cast(bytebeat_.get_phase()); + } + + uint32_t get_instance_loop_start() { + return static_cast(bytebeat_.get_loop_start()); + } + + uint32_t get_instance_loop_end() { + return static_cast(bytebeat_.get_loop_end()); + } + + uint16_t get_bytepitch() { + return static_cast(bytebeat_.get_bytepitch()); + } + + // Begin conditional menu items infrastructure + // Maintain an internal list of currently available settings, since some are + // dependent on others. It's kind of brute force, but eh, works :) If other + // apps have a similar need, it can be moved to a common wrapper + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + ByteBeatSettings enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + void update_enabled_settings() { + ByteBeatSettings *settings = enabled_settings_; + *settings++ = BYTEBEAT_SETTING_EQUATION; + *settings++ = BYTEBEAT_SETTING_SPEED; + *settings++ = BYTEBEAT_SETTING_PITCH; + *settings++ = BYTEBEAT_SETTING_P0; + *settings++ = BYTEBEAT_SETTING_P1; + *settings++ = BYTEBEAT_SETTING_P2; + *settings++ = BYTEBEAT_SETTING_LOOP_MODE; + if (get_loop_mode()) { + *settings++ = BYTEBEAT_SETTING_LOOP_START; + *settings++ = BYTEBEAT_SETTING_LOOP_START_MED; + *settings++ = BYTEBEAT_SETTING_LOOP_START_FINE; + *settings++ = BYTEBEAT_SETTING_LOOP_END; + *settings++ = BYTEBEAT_SETTING_LOOP_END_MED; + *settings++ = BYTEBEAT_SETTING_LOOP_END_FINE; + } + *settings++ = BYTEBEAT_SETTING_TRIGGER_INPUT; + *settings++ = BYTEBEAT_SETTING_STEP_MODE; + *settings++ = BYTEBEAT_SETTING_CV1; + *settings++ = BYTEBEAT_SETTING_CV2; + *settings++ = BYTEBEAT_SETTING_CV3; + *settings++ = BYTEBEAT_SETTING_CV4; + + num_enabled_settings_ = settings - enabled_settings_; + } + + static bool indentSetting(ByteBeatSettings s) { + switch (s) { + case BYTEBEAT_SETTING_LOOP_START: + case BYTEBEAT_SETTING_LOOP_START_MED: + case BYTEBEAT_SETTING_LOOP_START_FINE: + case BYTEBEAT_SETTING_LOOP_END: + case BYTEBEAT_SETTING_LOOP_END_MED: + case BYTEBEAT_SETTING_LOOP_END_FINE: + return true; + default: break; + } + return false; + } + // end conditional menu items infrastructure + + inline void apply_cv_mapping(ByteBeatSettings cv_setting, const int32_t cvs[ADC_CHANNEL_LAST], int32_t segments[kMaxByteBeatParameters]) { + int mapping = values_[cv_setting]; + uint8_t bytebeat_cv_rshift = 12; + switch (mapping) { + case BYTEBEAT_CV_MAPPING_EQUATION: + case BYTEBEAT_CV_MAPPING_SPEED: + case BYTEBEAT_CV_MAPPING_PITCH: + case BYTEBEAT_CV_MAPPING_P0: + case BYTEBEAT_CV_MAPPING_P1: + case BYTEBEAT_CV_MAPPING_P2: + bytebeat_cv_rshift = 12; + break; + case BYTEBEAT_CV_MAPPING_LOOP_START: + case BYTEBEAT_CV_MAPPING_LOOP_START_MED: + case BYTEBEAT_CV_MAPPING_LOOP_START_FINE: + case BYTEBEAT_CV_MAPPING_LOOP_END: + case BYTEBEAT_CV_MAPPING_LOOP_END_MED: + case BYTEBEAT_CV_MAPPING_LOOP_END_FINE: + bytebeat_cv_rshift = 12; + default: + break; + } + if (mapping) + segments[mapping - BYTEBEAT_CV_MAPPING_FIRST] += (cvs[cv_setting - BYTEBEAT_SETTING_CV1] * 65536) >> bytebeat_cv_rshift; + } + + template + void Update(uint32_t triggers, const int32_t cvs[ADC_CHANNEL_LAST]) { + + int32_t s[kMaxByteBeatParameters]; + s[0] = SCALE8_16(static_cast(get_equation() << 4)); + s[1] = SCALE8_16(static_cast(get_speed())); + s[2] = SCALE8_16(static_cast(get_p0())); + s[3] = SCALE8_16(static_cast(get_p1())); + s[4] = SCALE8_16(static_cast(get_p2())); + s[5] = SCALE8_16(static_cast(get_loop_start())); + s[6] = SCALE8_16(static_cast(get_loop_start_med())); + s[7] = SCALE8_16(static_cast(get_loop_start_fine())); + s[8] = SCALE8_16(static_cast(get_loop_end())); + s[9] = SCALE8_16(static_cast(get_loop_end_med())); + s[10] = SCALE8_16(static_cast(get_loop_end_fine())); + s[11] = SCALE8_16(static_cast(get_pitch())); + + apply_cv_mapping(BYTEBEAT_SETTING_CV1, cvs, s); + apply_cv_mapping(BYTEBEAT_SETTING_CV2, cvs, s); + apply_cv_mapping(BYTEBEAT_SETTING_CV3, cvs, s); + apply_cv_mapping(BYTEBEAT_SETTING_CV4, cvs, s); + + for (uint_fast8_t i = 0; i < 12; ++i) { + s[i] = USAT16(s[i]) ; + s_[i] = s[i] ; + } + + bytebeat_.Configure(s, get_step_mode(), get_loop_mode()) ; + + OC::DigitalInput trigger_input = get_trigger_input(); + uint8_t gate_state = 0; + if (triggers & DIGITAL_INPUT_MASK(trigger_input)) + gate_state |= peaks::CONTROL_GATE_RISING; + + bool gate_raised = OC::DigitalInputs::read_immediate(trigger_input); + if (gate_raised) + gate_state |= peaks::CONTROL_GATE; + else if (gate_raised_) + gate_state |= peaks::CONTROL_GATE_FALLING; + gate_raised_ = gate_raised; + + // TODO Scale range or offset? + uint16_t b = bytebeat_.ProcessSingleSample(gate_state); + #ifdef BUCHLA_4U + uint32_t value = OC::DAC::get_zero_offset(dac_channel) + b; + #else + uint32_t value = OC::DAC::get_zero_offset(dac_channel) + (int16_t)b; + #endif + OC::DAC::set(value); + + + b >>= 8; + if (b != history_.last()) // This make the effect a bit different + history_.Push(b); + } + + inline void ReadHistory(uint8_t *history) const { + history_.Read(history); + } + +private: + peaks::ByteBeat bytebeat_; + bool gate_raised_; + int32_t s_[kMaxByteBeatParameters]; + + int num_enabled_settings_; + ByteBeatSettings enabled_settings_[BYTEBEAT_SETTING_LAST]; + + util::History history_; +}; + +void ByteBeat::Init(OC::DigitalInput default_trigger) { + InitDefaults(); + apply_value(BYTEBEAT_SETTING_TRIGGER_INPUT, default_trigger); + bytebeat_.Init(); + gate_raised_ = false; + update_enabled_settings(); + history_.Init(0); +} + +const char* const bytebeat_cv_mapping_names[BYTEBEAT_CV_MAPPING_LAST] = { + "off", "equ", "spd", "p0", "p1", "p2", "beg++", "beg+", "beg", "end++", "end+", "end","pitch" +}; + +SETTINGS_DECLARE(ByteBeat, BYTEBEAT_SETTING_LAST) { + { 0, 0, 15, "Equation", OC::Strings::bytebeat_equation_names, settings::STORAGE_TYPE_U8 }, + { 255, 0, 255, "Speed", NULL, settings::STORAGE_TYPE_U8 }, + { 1, 1, 255, "Pitch", NULL, settings::STORAGE_TYPE_U8 }, + { 126, 0, 255, "Parameter 0", NULL, settings::STORAGE_TYPE_U8 }, + { 126, 0, 255, "Parameter 1", NULL, settings::STORAGE_TYPE_U8 }, + { 127, 0, 255, "Parameter 2", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 1, "Loop mode", OC::Strings::no_yes, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Loop begin ++", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Loop begin +", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Loop begin", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Loop end ++", NULL, settings::STORAGE_TYPE_U8 }, + { 1, 0, 255, "Loop end +", NULL, settings::STORAGE_TYPE_U8 }, + { 255, 0, 255, "Loop end", NULL, settings::STORAGE_TYPE_U8 }, + { OC::DIGITAL_INPUT_1, OC::DIGITAL_INPUT_1, OC::DIGITAL_INPUT_4, "Trigger input", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U4 }, + { 0, 0, 1, "Step mode", OC::Strings::no_yes, settings::STORAGE_TYPE_U4 }, + { BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_LAST - 1, "CV1 -> ", bytebeat_cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_LAST - 1, "CV2 -> ", bytebeat_cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_LAST - 1, "CV3 -> ", bytebeat_cv_mapping_names, settings::STORAGE_TYPE_U4 }, + { BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_NONE, BYTEBEAT_CV_MAPPING_LAST - 1, "CV4 -> ", bytebeat_cv_mapping_names, settings::STORAGE_TYPE_U4 }, +}; + +class QuadByteBeats { +public: + static constexpr int32_t kCvSmoothing = 16; + + // bb = bytebeat, balls_ = bytebeats_, BouncingBall = Bytebeat + // QuadBouncingBalls = QuadByteBeats, bbgen = bytebeatgen, BBGEN = BYTEBEATGEN + + void Init() { + int input = OC::DIGITAL_INPUT_1; + for (auto &bytebeat : bytebeats_) { + bytebeat.Init(static_cast(input)); + ++input; + } + + ui.left_encoder_value = 0; + ui.left_edit_mode = MODE_EDIT_SETTINGS; + + ui.selected_channel = 0; + ui.selected_segment = 0; + ui.cursor.Init(BYTEBEAT_SETTING_EQUATION, BYTEBEAT_SETTING_LAST - 1); + ui.cursor.AdjustEnd(bytebeats_[0].num_enabled_settings() - 1); + } + + void ISR() { + cv1.push(OC::ADC::value()); + cv2.push(OC::ADC::value()); + cv3.push(OC::ADC::value()); + cv4.push(OC::ADC::value()); + + const int32_t cvs[ADC_CHANNEL_LAST] = { cv1.value(), cv2.value(), cv3.value(), cv4.value() }; + uint32_t triggers = OC::DigitalInputs::clocked(); + + bytebeats_[0].Update(triggers, cvs); + bytebeats_[1].Update(triggers, cvs); + bytebeats_[2].Update(triggers, cvs); + bytebeats_[3].Update(triggers, cvs); + } + + enum LeftEditMode { + MODE_SELECT_CHANNEL, + MODE_EDIT_SETTINGS + }; + + struct { + LeftEditMode left_edit_mode; + int left_encoder_value; + // bool editing; + + int selected_channel; + int selected_segment; + menu::ScreenCursor cursor; + } ui; + + ByteBeat &selected() { + return bytebeats_[ui.selected_channel]; + } + + ByteBeat bytebeats_[4]; + + SmoothedValue cv1; + SmoothedValue cv2; + SmoothedValue cv3; + SmoothedValue cv4; +}; + +QuadByteBeats bytebeatgen; + +void BYTEBEATGEN_init() { + bytebeatgen.Init(); +} + +size_t BYTEBEATGEN_storageSize() { + return 4 * ByteBeat::storageSize(); +} + +size_t BYTEBEATGEN_save(void *storage) { + size_t s = 0; + for (auto &bytebeat : bytebeatgen.bytebeats_) + s += bytebeat.Save(static_cast(storage) + s); + return s; +} + +size_t BYTEBEATGEN_restore(const void *storage) { + size_t s = 0; + for (auto &bytebeat : bytebeatgen.bytebeats_) { + s += bytebeat.Restore(static_cast(storage) + s); + bytebeat.update_enabled_settings(); + } + bytebeatgen.ui.cursor.AdjustEnd(bytebeatgen.bytebeats_[0].num_enabled_settings() - 1); + return s; +} + + +void BYTEBEATGEN_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + bytebeatgen.ui.cursor.set_editing(false); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void BYTEBEATGEN_loop() { +} + +void BYTEBEATGEN_menu() { + + menu::QuadTitleBar::Draw(); + for (uint_fast8_t i = 0; i < 4; ++i) { + menu::QuadTitleBar::SetColumn(i); + graphics.print((char)('A' + i)); + } + menu::QuadTitleBar::Selected(bytebeatgen.ui.selected_channel); + + auto const &bytebeat = bytebeatgen.selected(); + menu::SettingsList settings_list(bytebeatgen.ui.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int setting = bytebeat.enabled_setting_at(settings_list.Next(list_item)); + const int value = bytebeat.get_value(setting); + const settings::value_attr &attr = ByteBeat::value_attr(setting); + if (ByteBeat::indentSetting(static_cast(setting))) + list_item.x += menu::kIndentDx; + list_item.DrawDefault(value, attr); + } +} + +void BYTEBEATGEN_topButton() { + auto &selected_bytebeat = bytebeatgen.selected(); + selected_bytebeat.change_value(BYTEBEAT_SETTING_EQUATION + bytebeatgen.ui.selected_segment, 1); +} + +void BYTEBEATGEN_lowerButton() { + auto &selected_bytebeat = bytebeatgen.selected(); + selected_bytebeat.change_value(BYTEBEAT_SETTING_EQUATION + bytebeatgen.ui.selected_segment, -1); +} + +void BYTEBEATGEN_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + BYTEBEATGEN_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + BYTEBEATGEN_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + break; + case OC::CONTROL_BUTTON_R: + bytebeatgen.ui.cursor.toggle_editing(); + break; + } + } +} + +void BYTEBEATGEN_handleEncoderEvent(const UI::Event &event) { + + if (OC::CONTROL_ENCODER_L == event.control) { + int left_value = bytebeatgen.ui.selected_channel + event.value; + CONSTRAIN(left_value, 0, 3); + bytebeatgen.ui.selected_channel = left_value; + auto &selected = bytebeatgen.selected(); + bytebeatgen.ui.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (bytebeatgen.ui.cursor.editing()) { + auto &selected = bytebeatgen.selected(); + ByteBeatSettings setting = selected.enabled_setting_at(bytebeatgen.ui.cursor.cursor_pos()); + selected.change_value(setting, event.value); + switch (setting) { + case BYTEBEAT_SETTING_LOOP_MODE: + selected.update_enabled_settings(); + bytebeatgen.ui.cursor.AdjustEnd(selected.num_enabled_settings() - 1); + break; + default: break; + } + } else { + bytebeatgen.ui.cursor.Scroll(event.value); + } + } +} + +#ifdef BYTEBEAT_DEBUG +void BYTEBEATGEN_debug() { + for (int i = 0; i < 4; ++i) { + uint8_t ypos = 10*(i + 1) + 2 ; + graphics.setPrintPos(2, ypos); + graphics.print(i) ; + graphics.setPrintPos(12, ypos); + graphics.print("t=") ; + graphics.setPrintPos(40, ypos); + graphics.print(bytebeatgen.bytebeats_[i].get_eqn_num(), 12); + } +} +#endif // BYTEBEATGEN_DEBUG + +uint8_t bb_history[ByteBeat::kHistoryDepth]; + +void BYTEBEATGEN_screensaver() { + + // Display raw history values "radiating" from center point by mirroring + // on x and y. Oldest value is at start of buffer after reading history. + + weegfx::coord_t y = 0; + for (const auto & bb : bytebeatgen.bytebeats_) { + bb.ReadHistory(bb_history); + const uint8_t *history = bb_history + ByteBeat::kHistoryDepth - 1; + for (int i = 0; i < 64; ++i) { + uint8_t b = *history-- ; + graphics.drawAlignedByte(64 + i, y + 8, b); + graphics.drawAlignedByte(64 - i -1, y + 8, b); + b = util::reverse_byte(b); + graphics.drawAlignedByte(64 + i, y, b); + graphics.drawAlignedByte(64 - i -1, y, b); + } + y += 16; + } +} + +void FASTRUN BYTEBEATGEN_isr() { + bytebeatgen.ISR(); +} + +#endif // ENABLE_APP_BYTEBEATGEN \ No newline at end of file diff --git a/software/o_c_REV/APP_LORENZ.ino b/software/o_c_REV/APP_LORENZ.ino new file mode 100644 index 000000000..0c1294f4f --- /dev/null +++ b/software/o_c_REV/APP_LORENZ.ino @@ -0,0 +1,370 @@ +// Copyright (c) 2015, 2016 Patrick Dowling, Max Stadler, Tim Churches +// +// Author of original O+C firmware: Max Stadler (mxmlnstdlr@gmail.com) +// Author of app scaffolding: Patrick Dowling (pld@gurkenkiste.com) +// Modified for Lorenz and Rössler generators: Tim Churches (tim.churches@gmail.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Lorenz and Rössler generator app + +#ifdef ENABLE_APP_LORENZ + +#include "streams_lorenz_generator.h" +#include "util/util_math.h" +#include "OC_digital_inputs.h" + +enum LORENZ_SETTINGS { + LORENZ_SETTING_FREQ1, + LORENZ_SETTING_FREQ2, + LORENZ_SETTING_RHO1, + LORENZ_SETTING_RHO2, + LORENZ_SETTING_FREQ_RANGE1, + LORENZ_SETTING_FREQ_RANGE2, + LORENZ_SETTING_OUT_A, + LORENZ_SETTING_OUT_B, + LORENZ_SETTING_OUT_C, + LORENZ_SETTING_OUT_D, + LORENZ_SETTING_LAST +}; + +const char * const lorenz_output_names[] = { + "Lx1", + "Ly1", + "Lz1", + "Lx2", + "Ly2", + "Lz2", + "Rx1", + "Ry1", + "Rz1", + "Rx2", + "Ry2", + "Rz2", + "Lx1+Rx1", + "Lx1+Rz1", + "Lx1+Ly2", + "Lx1+Lz2", + "Lx1+Rx2", + "Lx1+Rz2", + "Lx1xLy1", + "Lx1xLx2", + "Lx1xRx1", + "Lx1xRx2", +}; + +class LorenzGenerator : public settings::SettingsBase { +public: + + uint16_t get_freq1() const { + return values_[LORENZ_SETTING_FREQ1]; + } + + uint16_t get_freq2() const { + return values_[LORENZ_SETTING_FREQ2]; + } + + uint8_t get_freq_range1() const { + return values_[LORENZ_SETTING_FREQ_RANGE1]; + } + + uint8_t get_freq_range2() const { + return values_[LORENZ_SETTING_FREQ_RANGE2]; + } + + uint16_t get_rho1() const { + return values_[LORENZ_SETTING_RHO1]; + } + + uint16_t get_rho2() const { + return values_[LORENZ_SETTING_RHO2]; + } + + uint8_t get_out_a() const { + return values_[LORENZ_SETTING_OUT_A]; + } + + uint8_t get_out_b() const { + return values_[LORENZ_SETTING_OUT_B]; + } + + uint8_t get_out_c() const { + return values_[LORENZ_SETTING_OUT_C]; + } + + uint8_t get_out_d() const { + return values_[LORENZ_SETTING_OUT_D]; + } + + void Init(); + + void freeze() { + frozen_ = true; + } + + void thaw() { + frozen_ = false; + } + + bool frozen() const { + return frozen_; + } + + streams::LorenzGenerator lorenz; + bool frozen_; + + // ISR update is at 16.666kHz, we don't need it that fast so smooth the values to ~1Khz + static constexpr int32_t kSmoothing = 16; + + SmoothedValue cv_freq1; + SmoothedValue cv_freq2; + SmoothedValue cv_rho1; + SmoothedValue cv_rho2; +}; + +void LorenzGenerator::Init() { + InitDefaults(); + lorenz.Init(0); + lorenz.Init(1); + frozen_= false; +} + +const char* const lorenz_freq_range_names[5] = { + "sloth", "lazy", "slow", "med", "fast", +}; + +SETTINGS_DECLARE(LorenzGenerator, LORENZ_SETTING_LAST) { + #ifdef BUCHLA_4U + { 0, 0, 255, "Freq 1", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 255, "Freq 2", NULL, settings::STORAGE_TYPE_U8 }, + #else + { 128, 0, 255, "Freq 1", NULL, settings::STORAGE_TYPE_U8 }, + { 128, 0, 255, "Freq 2", NULL, settings::STORAGE_TYPE_U8 }, + #endif + { 63, 4, 127, "Rho/c 1", NULL, settings::STORAGE_TYPE_U8 }, + { 63, 4, 127, "Rho/c 2", NULL, settings::STORAGE_TYPE_U8 }, + { 2, 0, 4, "LFreq 1 Rng", lorenz_freq_range_names, settings::STORAGE_TYPE_U4 }, + { 2, 0, 4, "LFreq 2 Rng", lorenz_freq_range_names, settings::STORAGE_TYPE_U4 }, + {streams::LORENZ_OUTPUT_X1, streams::LORENZ_OUTPUT_X1, streams::LORENZ_OUTPUT_LAST - 1, "Out A ", lorenz_output_names, settings::STORAGE_TYPE_U8}, + {streams::LORENZ_OUTPUT_Y1, streams::LORENZ_OUTPUT_X1, streams::LORENZ_OUTPUT_LAST - 1, "Out B ", lorenz_output_names, settings::STORAGE_TYPE_U8}, + {streams::LORENZ_OUTPUT_X2, streams::LORENZ_OUTPUT_X1, streams::LORENZ_OUTPUT_LAST - 1, "Out C ", lorenz_output_names, settings::STORAGE_TYPE_U8}, + {streams::LORENZ_OUTPUT_Y2, streams::LORENZ_OUTPUT_X1, streams::LORENZ_OUTPUT_LAST - 1, "Out D ", lorenz_output_names, settings::STORAGE_TYPE_U8}, +}; + +LorenzGenerator lorenz_generator; +struct { + bool selected_generator; + + menu::ScreenCursor cursor; +} lorenz_generator_state; + +void FASTRUN LORENZ_isr() { + + bool reset1_phase = OC::DigitalInputs::clocked(); + bool reset2_phase = OC::DigitalInputs::clocked(); + bool reset_both_phase = OC::DigitalInputs::clocked(); + bool freeze = OC::DigitalInputs::read_immediate(); + + lorenz_generator.cv_freq1.push(OC::ADC::value()); + lorenz_generator.cv_rho1.push(OC::ADC::value()); + lorenz_generator.cv_freq2.push(OC::ADC::value()); + lorenz_generator.cv_rho2.push(OC::ADC::value()); + + // Range in settings is (0-256] so this gets scaled to (0,65535] + // CV value is 12 bit so also needs scaling + + int32_t freq1 = SCALE8_16(lorenz_generator.get_freq1()) + (lorenz_generator.cv_freq1.value() * 16); + freq1 = USAT16(freq1); + + int32_t freq2 = SCALE8_16(lorenz_generator.get_freq2()) + (lorenz_generator.cv_freq2.value() * 16); + freq2 = USAT16(freq2); + + const int32_t rho_lower_limit = 4 << 8 ; + const int32_t rho_upper_limit = 127 << 8 ; + + int32_t rho1 = SCALE8_16(lorenz_generator.get_rho1()) + (lorenz_generator.cv_rho1.value() * 16) ; + if (rho1 < rho_lower_limit) rho1 = rho_lower_limit; + else if (rho1 > rho_upper_limit) rho1 = rho_upper_limit ; + lorenz_generator.lorenz.set_rho1(USAT16(rho1)); + + int32_t rho2 = SCALE8_16(lorenz_generator.get_rho2()) + (lorenz_generator.cv_rho2.value() * 16) ; + if (rho2 < rho_lower_limit) rho2 = rho_lower_limit; + else if (rho2 > rho_upper_limit) rho2 = rho_upper_limit ; + lorenz_generator.lorenz.set_rho2(USAT16(rho2)); + + uint8_t out_a = lorenz_generator.get_out_a() ; + lorenz_generator.lorenz.set_out_a(out_a); + + uint8_t out_b = lorenz_generator.get_out_b() ; + lorenz_generator.lorenz.set_out_b(out_b); + + uint8_t out_c = lorenz_generator.get_out_c() ; + lorenz_generator.lorenz.set_out_c(out_c); + + uint8_t out_d = lorenz_generator.get_out_d() ; + lorenz_generator.lorenz.set_out_d(out_d); + + if (reset_both_phase) { + reset1_phase = true ; + reset2_phase = true ; + } + if (!freeze && !lorenz_generator.frozen()) + lorenz_generator.lorenz.Process(freq1, freq2, reset1_phase, reset2_phase, lorenz_generator.get_freq_range1(), lorenz_generator.get_freq_range2()); + + OC::DAC::set(lorenz_generator.lorenz.dac_code(0)); + OC::DAC::set(lorenz_generator.lorenz.dac_code(1)); + OC::DAC::set(lorenz_generator.lorenz.dac_code(2)); + OC::DAC::set(lorenz_generator.lorenz.dac_code(3)); +} + +void LORENZ_init() { + lorenz_generator_state.selected_generator = 0; + lorenz_generator_state.cursor.Init(LORENZ_SETTING_RHO1, LORENZ_SETTING_LAST - 1); + lorenz_generator.Init(); +} + +size_t LORENZ_storageSize() { + return LorenzGenerator::storageSize(); +} + +size_t LORENZ_save(void *storage) { + return lorenz_generator.Save(storage); +} + +size_t LORENZ_restore(const void *storage) { + return lorenz_generator.Restore(storage); +} + +void LORENZ_loop() { +} + +void LORENZ_menu() { + + menu::DualTitleBar::Draw(); + graphics.print("Freq1 "); + int32_t freq1 = SCALE8_16(lorenz_generator.get_freq1()) + (lorenz_generator.cv_freq1.value() * 16); + freq1 = USAT16(freq1); + graphics.print(freq1 >> 8); + + menu::DualTitleBar::SetColumn(1); + graphics.print("Freq2 "); + int32_t freq2 = SCALE8_16(lorenz_generator.get_freq2()) + (lorenz_generator.cv_freq2.value() * 16); + freq2 = USAT16(freq2); + graphics.print(freq2 >> 8); + + menu::DualTitleBar::Selected(lorenz_generator_state.selected_generator); + + menu::SettingsList settings_list(lorenz_generator_state.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) { + const int current = settings_list.Next(list_item); + list_item.DrawDefault(lorenz_generator.get_value(current), LorenzGenerator::value_attr(current)); + } +} + +void LORENZ_screensaver() { + OC::vectorscope_render(); +} + +void LORENZ_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void LORENZ_topButton() { + if (lorenz_generator_state.selected_generator) { + lorenz_generator.change_value(LORENZ_SETTING_FREQ2, 32); + } else { + lorenz_generator.change_value(LORENZ_SETTING_FREQ1, 32); + } +} + +void LORENZ_lowerButton() { + if (lorenz_generator_state.selected_generator) { + lorenz_generator.change_value(LORENZ_SETTING_FREQ2, -32); + } else { + lorenz_generator.change_value(LORENZ_SETTING_FREQ1, -32); + } +} + +void LORENZ_rightButton() { + lorenz_generator_state.cursor.toggle_editing(); +} + +void LORENZ_leftButton() { + lorenz_generator_state.selected_generator = 1 - lorenz_generator_state.selected_generator; +} + +void LORENZ_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + LORENZ_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + LORENZ_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + LORENZ_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + LORENZ_rightButton(); + break; + } + } +} + +void LORENZ_handleEncoderEvent(const UI::Event &event) { + + if (OC::CONTROL_ENCODER_L == event.control) { + if (lorenz_generator_state.selected_generator) { + lorenz_generator.change_value(LORENZ_SETTING_FREQ2, event.value); + } else { + lorenz_generator.change_value(LORENZ_SETTING_FREQ1, event.value); + } + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (lorenz_generator_state.cursor.editing()) { + lorenz_generator.change_value(lorenz_generator_state.cursor.cursor_pos(), event.value); + } else { + lorenz_generator_state.cursor.Scroll(event.value); + } + } +} + +void LORENZ_debug() { + graphics.setPrintPos(2, 12); + graphics.print(lorenz_generator.cv_freq1.value()); + graphics.print(" "); + int32_t value = SCALE8_16(lorenz_generator.get_freq1()); + graphics.print(value); + graphics.setPrintPos(2, 22); + graphics.print(lorenz_generator.cv_freq2.value()); + graphics.print(" "); + value = SCALE8_16(lorenz_generator.get_freq2()); + graphics.print(value); + +} + +#endif // ENABLE_APP_LORENZ \ No newline at end of file diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 84e44e262..1aae26830 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -38,36 +38,47 @@ } OC::App available_apps[] = { + #ifdef ENABLE_APP_CALIBR8OR DECLARE_APP('C','8', "Calibr8or", Calibr8or), #endif - DECLARE_APP('H','S', "Hemisphere", HEMISPHERE), + #ifdef ENABLE_APP_ASR + DECLARE_APP('A','S', "CopierMaschine", ASR), + #endif + #ifdef ENABLE_APP_H1200 + DECLARE_APP('H','A', "Harrington 1200", H1200), + #endif + #ifdef ENABLE_APP_AUTOMATONNETZ + DECLARE_APP('A','T', "Automatonnetz", Automatonnetz), + #endif #ifdef ENABLE_APP_QUANTERMAIN DECLARE_APP('Q','Q', "Quantermain", QQ), #endif #ifdef ENABLE_APP_METAQ DECLARE_APP('M','!', "Meta-Q", DQ), #endif - + #ifdef ENABLE_APP_POLYLFO + DECLARE_APP('P','L', "Quadraturia", POLYLFO), + #endif + #ifdef ENABLE_APP_LORENZ + DECLARE_APP('L','R', "Low-rents", LORENZ), + #endif #ifdef ENABLE_APP_PIQUED DECLARE_APP('E','G', "Piqued", ENVGEN), #endif - - #ifdef ENABLE_APP_CHORDS - DECLARE_APP('A','C', "Acid Curds", CHORDS), - #endif #ifdef ENABLE_APP_SEQUINS DECLARE_APP('S','Q', "Sequins", SEQ), #endif - - #ifdef ENABLE_APP_POLYLFO - DECLARE_APP('P','L', "Quadraturia", POLYLFO), + #ifdef ENABLE_APP_BBGEN + DECLARE_APP('B','B', "Dialectic Pong", BBGEN), #endif - #ifdef ENABLE_APP_H1200 - DECLARE_APP('H','A', "Harrington 1200", H1200), + #ifdef ENABLE_APP_BYTEBEATGEN + DECLARE_APP('B','Y', "Viznutcracker", BYTEBEATGEN), + #endif + #ifdef ENABLE_APP_CHORDS + DECLARE_APP('A','C', "Acid Curds", CHORDS), #endif - #ifdef ENABLE_APP_MIDI DECLARE_APP('M','I', "Captain MIDI", MIDI), #endif @@ -103,7 +114,7 @@ struct GlobalSettings { bool reserved1; uint32_t DAC_scaling; uint16_t current_app_id; - + OC::Scale user_scales[OC::Scales::SCALE_USER_LAST]; OC::Pattern user_patterns[OC::Patterns::PATTERN_USER_ALL]; HS::TuringMachine user_turing_machines[HS::TURING_MACHINE_COUNT]; @@ -151,7 +162,7 @@ void save_global_settings() { memcpy(global_settings.auto_calibration_data, OC::auto_calibration_data, sizeof(OC::auto_calibration_data)); // scaling settings: global_settings.DAC_scaling = OC::DAC::store_scaling(); - + global_settings_storage.Save(global_settings); SERIAL_PRINTLN("Saved global settings: page_index %d", global_settings_storage.page_index()); } @@ -270,7 +281,7 @@ void Init(bool reset_settings) { global_settings.encoders_enable_acceleration = OC_ENCODERS_ENABLE_ACCELERATION_DEFAULT; global_settings.reserved0 = false; global_settings.reserved1 = false; - global_settings.DAC_scaling = VOLTAGE_SCALING_1V_PER_OCT; + global_settings.DAC_scaling = VOLTAGE_SCALING_1V_PER_OCT; if (reset_settings) { if (ui.ConfirmReset()) { @@ -354,8 +365,9 @@ void draw_app_menu(const menu::ScreenCursor<5> &cursor) { item.SetPrintPos(); graphics.movePrintPos(weegfx::Graphics::kFixedFontW, 0); graphics.print(available_apps[current].name); -// if (global_settings.current_app_id == available_apps[current].id) -// graphics.drawBitmap8(item.x + 2, item.y + 1, 4, bitmap_indicator_4x8); + + // if (global_settings.current_app_id == available_apps[current].id) + // graphics.drawBitmap8(item.x + 2, item.y + 1, 4, bitmap_indicator_4x8); graphics.drawBitmap8(0, item.y + 1, 8, global_settings.current_app_id == available_apps[current].id ? CHECK_ON_ICON : CHECK_OFF_ICON); item.DrawCustom(); diff --git a/software/o_c_REV/tonnetz/tonnetz.h b/software/o_c_REV/tonnetz/tonnetz.h index f9cf63e6f..37d7e9d1b 100644 --- a/software/o_c_REV/tonnetz/tonnetz.h +++ b/software/o_c_REV/tonnetz/tonnetz.h @@ -41,11 +41,11 @@ namespace tonnetz { '*', 'P', 'L', 'R', 'N', 'S', 'H', '@' }; - /* + static const char *transform_names_str[TRANSFORM_LAST + 1] = { "*", "P", "L", "R", "N", "S", "H", "@" }; - */ + static struct transformation { size_t root_shift; // +1 = root -> third, +2 root -> fifth From 25f2c03587a331bfa67df1362f78e61b1c8d59db Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 14 Apr 2023 14:37:42 -0400 Subject: [PATCH 212/417] Restore GameOfLife applet adapted for 64-bit save data, fix type warnings --- software/o_c_REV/HEM_GameOfLife.ino | 234 +++++++++++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 1 + 2 files changed, 235 insertions(+) create mode 100644 software/o_c_REV/HEM_GameOfLife.ino diff --git a/software/o_c_REV/HEM_GameOfLife.ino b/software/o_c_REV/HEM_GameOfLife.ino new file mode 100644 index 000000000..185385219 --- /dev/null +++ b/software/o_c_REV/HEM_GameOfLife.ino @@ -0,0 +1,234 @@ +#define GOL_ABS(X) (X < 0 ? -X : X) + +class GameOfLife : public HemisphereApplet { +public: + + const char* applet_name() { + return "Game/Life"; + } + + void Start() { + for (int i = 0; i < 80; i++) board[i] = 0; + weight = 30; + tx = 0; + ty = 0; + + // Start off with a sweet-looking board + for (int x = 0; x < 6; x++) + { + AddToBoard(x + 26, x + 23); + AddToBoard(x + 33, (5 - x) + 23); + } + AddToBoard(32, 28); + } + + void Controller() { + tx = ProportionCV(In(0), 63); + ty = ProportionCV(In(1), 39); + + if (Clock(0)) { + ProcessGameBoard(tx, ty); + } + if (Gate(1)) AddToBoard(tx, ty); + + int global_density_cv = Proportion(global_density, 1200 - (weight * 10), HEMISPHERE_MAX_CV); + int local_density_cv = Proportion(local_density, 225, HEMISPHERE_MAX_CV); + Out(0, constrain(global_density_cv, 0, HEMISPHERE_MAX_CV)); + Out(1, constrain(local_density_cv, 0, HEMISPHERE_MAX_CV)); + } + + void View() { + gfxHeader(applet_name()); + DrawBoard(); + DrawIndicator(); + DrawCrosshairs(); + } + + void ScreensaverView() { + DrawBoard(); + DrawIndicator(); + } + + void OnButtonPress() { + for (int i = 0; i < 80; i++) board[i] = 0; + } + + void OnEncoderMove(int direction) { + weight = constrain(weight += direction, 0, 100); + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation {0,6}, weight); + return data; + } + + void OnDataReceive(uint64_t data) { + weight = Unpack(data, PackLocation {0,6}); + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Draw"; + help[HEMISPHERE_HELP_CVS] = "1=X pos 2=Y pos"; + help[HEMISPHERE_HELP_OUTS] = "A=Global B=Local"; + help[HEMISPHERE_HELP_ENCODER] = "T=Weight P=Clear"; + // "------------------" <-- Size Guide + } + +private: + uint64_t board[80]; // 64x40 board + int weight; // Weight of each cell + int global_density; // Count of all live cells + int local_density; // Count of cells in the vicinity of the Traveler + int tx; + int ty; + + void DrawBoard() { + for (int y = 0; y < 40; y++) + { + for (int b = 0; b < 2; b++) + { + for (int c = 0; c < 32; c++) + { + if ((board[(y * 2) + b] >> c) & 0x01) gfxPixel(c + (32 * b), y + 22); + } + } + } + } + + void DrawIndicator() { + // Output indicators + ForEachChannel(ch) + { + gfxRect(1, 15 + (ch * 4), ProportionCV(ViewOut(ch), 62), 2); + } + } + + void DrawCrosshairs() { + gfxLine(tx, 23, tx, 63); + gfxLine(0, ty + 22, 62, ty + 22); + } + + void ProcessGameBoard(int tx, int ty) { + uint64_t next_gen[80]; + global_density = 0; + local_density = 0; + for (int y = 0; y < 40; y++) + { + next_gen[y * 2] = 0; // Clear next generation board + next_gen[y * 2 + 1] = 0; + for (int x = 0; x < 64; x++) + { + bool live = ValueAtCell(x, y); + int ln = CountLiveNeighborsAt(x, y); + + // 1: Any live cell with fewer than two live neighbours dies (referred to as underpopulation or exposure). + // 2: Any live cell with more than three live neighbours dies (referred to as overpopulation or overcrowding). + // Nothing to do to observe these rules, since we start with an empty board + + // 3: Any live cell with two or three live neighbours lives, unchanged, to the next generation. + // 4: Any dead cell with exactly three live neighbours will come to life. + if (((ln == 2 || ln == 3) && live) || (ln == 3 && !live)) { + int i = y * 2; + int xb = x; + if (x > 31) { + i += 1; + xb -= 32; + } + next_gen[i] = next_gen[i] | (0x01 << xb); + global_density++; + + if (GOL_ABS(tx - x) < 8 && GOL_ABS(ty - y) < 8) local_density++; + } + } + } + + memcpy(&board, &next_gen, sizeof(next_gen)); + } + + int CountLiveNeighborsAt(int x, int y) { + int count = 0; + for (int nx = -1; nx < 2; nx++) + { + for (int ny = -1; ny < 2; ny++) + { + if (!(nx == 0 && ny == 0)) count += ValueAtCell(x + nx, y + ny); + } + } + return count; + } + + bool ValueAtCell(int x, int y) { + // Toroid operation + if (x > 63) x -= 64; + if (x < 0) x += 64; + if (y > 39) y -= 40; + if (y < 0) y += 40; + + int i = y * 2; + if (x > 31) { + i += 1; + x -= 32; + } + return ((board[i] >> x) & 0x01); + } + + void AddToBoard(int x, int y) { + int i = y * 2; + int xb = x; + if (x > 31) { + i += 1; + xb -= 32; + } + board[i] = board[i] | (0x01 << xb); + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to GameOfLife, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +GameOfLife GameOfLife_instance[2]; + +void GameOfLife_Start(bool hemisphere) { + GameOfLife_instance[hemisphere].BaseStart(hemisphere); +} + +void GameOfLife_Controller(bool hemisphere, bool forwarding) { + GameOfLife_instance[hemisphere].BaseController(forwarding); +} + +void GameOfLife_View(bool hemisphere) { + GameOfLife_instance[hemisphere].BaseView(); +} + +void GameOfLife_Screensaver(bool hemisphere) { + GameOfLife_instance[hemisphere].BaseScreensaverView(); +} + +void GameOfLife_OnButtonPress(bool hemisphere) { + GameOfLife_instance[hemisphere].OnButtonPress(); +} + +void GameOfLife_OnEncoderMove(bool hemisphere, int direction) { + GameOfLife_instance[hemisphere].OnEncoderMove(direction); +} + +void GameOfLife_ToggleHelpScreen(bool hemisphere) { + GameOfLife_instance[hemisphere].HelpScreen(); +} + +uint64_t GameOfLife_OnDataRequest(bool hemisphere) { + return GameOfLife_instance[hemisphere].OnDataRequest(); +} + +void GameOfLife_OnDataReceive(bool hemisphere, uint64_t data) { + GameOfLife_instance[hemisphere].OnDataReceive(data); +} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index d8ed683cb..bc08e710c 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -38,6 +38,7 @@ DECLARE_APPLET( 45, 0x02, EnigmaJr), \ 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( 16, 0x80, LoFiPCM), \ From d0a26396c505798e931be59c4774746946dd9d55 Mon Sep 17 00:00:00 2001 From: kfirmanty Date: Sun, 5 May 2019 23:39:14 +0200 Subject: [PATCH 213/417] Add Harmonizer applet (via kfirmanty) adapted for 64-bit save data; modal edit cursor; fix type warnings --- software/o_c_REV/HEM_Harmonizer.ino | 250 +++++++++++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 1 + 2 files changed, 251 insertions(+) create mode 100644 software/o_c_REV/HEM_Harmonizer.ino diff --git a/software/o_c_REV/HEM_Harmonizer.ino b/software/o_c_REV/HEM_Harmonizer.ino new file mode 100644 index 000000000..3e12b22d6 --- /dev/null +++ b/software/o_c_REV/HEM_Harmonizer.ino @@ -0,0 +1,250 @@ +// Harmonizer for o_c hemisphere by Karol Firmanty. +// Outputs chord voicing based on user selected scale and CV input. +// +// Based on DualQuant, Copyright 2018 Jason Justian +// Based on Braids Quantizer, Copyright 2015 Émilie Gillet. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_scales.h" + +class Harmonizer : public HemisphereApplet { + public: + + const char* applet_name() { // Maximum 10 characters + return "Harmonizer"; + } + + void Start() { + cursor = 0; + quantizer.Init(); + scale = 5; + scale_data = OC::Scales::GetScale(scale); + quantizer.Configure(scale_data, 0xffff); + last_note[0] = 0; + last_note[1] = 0; + } + + void Controller() { + int32_t pitch = In(0); + int32_t quantized = quantizer.Process(pitch, root << 7, 0); + if (last_quantized_input != quantized) { + last_quantized_input = quantized; + int scale_step = FindScaleStep(quantized); + + ForEachChannel(ch) + { + int32_t offset_voltage = FindOffsetVoltage(scale_step, offset[ch]); + last_note[ch] = quantized + offset_voltage; + } + } + ForEachChannel(ch) + { + Out(ch, last_note[ch]); + } + } + + void View() { + gfxHeader(applet_name()); + DrawSelector(); + } + + void OnButtonPress() { + CursorAction(cursor, 3); + } + + void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } + + last_quantized_input = -1; //invalidate last_quantized_input to force refresh CV outs + switch (cursor) { + case 0: + scale += direction; + if (scale >= OC::Scales::NUM_SCALES) scale = 0; + if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; + scale_data = OC::Scales::GetScale(scale); + quantizer.Configure(scale_data, 0xffff); + ForEachChannel(ch) + { + offset[ch] = constrain(offset[ch], 0, (int)scale_data.num_notes); + } + break; + case 1: + root = constrain(root + direction, 0, 11); + break; + case 2: + offset[0] = constrain(offset[0] + direction, 0, (int)scale_data.num_notes); + break; + case 3: + offset[1] = constrain(offset[1] + direction, 0, (int)scale_data.num_notes); + break; + } + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation {0, 8}, scale); + Pack(data, PackLocation {8, 4}, root); + ForEachChannel(ch) + { + Pack(data, PackLocation { size_t(12 + 4 * ch), 4}, offset[ch]); + } + return data; + } + + void OnDataReceive(uint64_t data) { + scale = Unpack(data, PackLocation {0, 8}); + root = Unpack(data, PackLocation {8, 4}); + + root = constrain(root, 0, 11); + scale_data = OC::Scales::GetScale(scale); + ForEachChannel(ch) + { + offset[ch] = Unpack(data, PackLocation { size_t(12 + 4 * ch), 4}); + offset[ch] = constrain(root, 0, (int)scale_data.num_notes); + } + quantizer.Configure(scale_data, 0xffff); + } + + protected: + /* Set help text. Each help section can have up to 18 characters. Be concise! */ + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = ""; + help[HEMISPHERE_HELP_CVS] = "CV 1=Pitch"; + help[HEMISPHERE_HELP_OUTS] = "OUT A=V2 B=V3"; + help[HEMISPHERE_HELP_ENCODER] = "Scale/Root/Offset"; + // "------------------" <-- Size Guide + } + + private: + braids::Quantizer quantizer; + int last_note[2]; + int last_quantized_input; + int offset[2]; + int cursor; + + // Settings + int scale; + uint8_t root; + braids::Scale scale_data; + + void DrawSelector() + { + // Draw settings + gfxPrint(0, 15, OC::scale_names_short[scale]); + gfxPrint(10, 25, OC::Strings::note_names_unpadded[root]); + + // Draw cursor + switch (cursor) { + case 0: + gfxCursor(0, 23, 30); + break; + case 1: + gfxCursor(10, 33, 12); + break; + case 2: + gfxCursor(31, 23, 12); + break; + case 3: + gfxCursor(31, 33, 12); + break; + } + + ForEachChannel(ch) + { + // Draw offsets + gfxPrint(31, 15 + 10 * ch, offset[ch]); + + // Print semitones + int semitone = (last_note[ch] / 128) % 12; + gfxPrint(10, 41 + (10 * ch), semitone); + } + } + + int FindScaleStep(int32_t quantized_pitch) { + int16_t base_pitch = quantized_pitch % scale_data.span; + int index = 0; + for (size_t i = 0; i < scale_data.num_notes; i++) { + if (scale_data.notes[i] == base_pitch) { + index = i; + break; + } + } + return index; + } + + int32_t FindOffsetVoltage(int scale_step, int offset) { + int16_t base_pitch = scale_data.notes[scale_step]; + int step = scale_step + offset; + if (step < (int)scale_data.num_notes) { + return scale_data.notes[step] - base_pitch; + } else { + return (scale_data.notes[step % scale_data.num_notes] + scale_data.span) - base_pitch; + } + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to Harmonizer, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +Harmonizer Harmonizer_instance[2]; + +void Harmonizer_Start(bool hemisphere) { + Harmonizer_instance[hemisphere].BaseStart(hemisphere); +} + +void Harmonizer_Controller(bool hemisphere, bool forwarding) { + Harmonizer_instance[hemisphere].BaseController(forwarding); +} + +void Harmonizer_View(bool hemisphere) { + Harmonizer_instance[hemisphere].BaseView(); +} + +void Harmonizer_OnButtonPress(bool hemisphere) { + Harmonizer_instance[hemisphere].OnButtonPress(); +} + +void Harmonizer_OnEncoderMove(bool hemisphere, int direction) { + Harmonizer_instance[hemisphere].OnEncoderMove(direction); +} + +void Harmonizer_ToggleHelpScreen(bool hemisphere) { + Harmonizer_instance[hemisphere].HelpScreen(); +} + +uint64_t Harmonizer_OnDataRequest(bool hemisphere) { + return Harmonizer_instance[hemisphere].OnDataRequest(); +} + +void Harmonizer_OnDataReceive(bool hemisphere, uint64_t data) { + Harmonizer_instance[hemisphere].OnDataReceive(data); +} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index bc08e710c..159162bf4 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -41,6 +41,7 @@ DECLARE_APPLET( 22, 0x01, GameOfLife), \ DECLARE_APPLET( 29, 0x04, GateDelay), \ DECLARE_APPLET( 17, 0x50, GatedVCA), \ + DECLARE_APPLET( 63, 0x08, Harmonizer), \ DECLARE_APPLET( 16, 0x80, LoFiPCM), \ DECLARE_APPLET( 10, 0x44, Logic), \ DECLARE_APPLET( 21, 0x01, LowerRenz), \ From 4aaa22d6d006b927660da908900a89f1f1520950 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 14 Apr 2023 14:24:00 -0400 Subject: [PATCH 214/417] Update build config --- software/o_c_REV/platformio.ini | 64 ++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 589e27827..e3831ebab 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -11,10 +11,13 @@ [platformio] src_dir = . default_envs = - oc_prod - oc_stock - oc_prod_vor - oc_prod_flipped + main + main_vor + main_flipped + oc_stock1 + oc_stock2 + oc_stock1_flipped + oc_stock2_flipped [env] platform = teensy@4.17.0 @@ -37,7 +40,7 @@ build_flags = -DENABLE_APP_CALIBR8OR -DOC_VERSION_EXTRA="\" CAL8-0327\"" -[env:oc_prod] +[env:main] build_flags = ${env.build_flags} -DENABLE_APP_CALIBR8OR @@ -46,45 +49,72 @@ build_flags = -DENABLE_APP_NEURAL_NETWORK -DENABLE_APP_PONG -DENABLE_APP_DARKEST_TIMELINE - -DENABLE_APP_H1200 -DENABLE_APP_PIQUED -DENABLE_APP_POLYLFO + -DENABLE_APP_LORENZ -[env:oc_stock] +[env:oc_stock1] build_flags = ${env.build_flags} -DENABLE_APP_PONG + -DENABLE_APP_ASR -DENABLE_APP_QUANTERMAIN -; -DENABLE_APP_METAQ +; -DENABLE_APP_METAQ -DENABLE_APP_PIQUED -DENABLE_APP_CHORDS -DENABLE_APP_SEQUINS +; -DENABLE_APP_POLYLFO +; -DENABLE_APP_H1200 + -DENABLE_APP_AUTOMATONNETZ + -DENABLE_APP_LORENZ +; -DENABLE_APP_BBGEN +; -DENABLE_APP_BYTEBEATGEN + -DOC_VERSION_EXTRA="\" +stock1\"" + +[env:oc_stock2] +build_flags = + ${env.build_flags} + -DENABLE_APP_PONG + -DENABLE_APP_ASR + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_METAQ +; -DENABLE_APP_PIQUED + -DENABLE_APP_CHORDS +; -DENABLE_APP_SEQUINS -DENABLE_APP_POLYLFO -DENABLE_APP_H1200 - -DOC_VERSION_EXTRA="\" +stock\"" + -DENABLE_APP_AUTOMATONNETZ + -DENABLE_APP_LORENZ + -DENABLE_APP_BBGEN + -DENABLE_APP_BYTEBEATGEN + -DOC_VERSION_EXTRA="\" +stock2\"" -[env:oc_prod_flipped] +[env:main_flipped] build_flags = - ${env:oc_prod.build_flags} + ${env:main.build_flags} -DFLIP_180 -DOC_VERSION_EXTRA="\" flipped\"" -[env:oc_stock_flipped] +[env:oc_stock1_flipped] +build_flags = + ${env:oc_stock1.build_flags} + -DFLIP_180 + +[env:oc_stock2_flipped] build_flags = - ${env:oc_stock.build_flags} + ${env:oc_stock2.build_flags} -DFLIP_180 -; -DOC_VERSION_EXTRA="\"+stock,flipped\"" -[env:oc_prod_vor] +[env:main_vor] build_flags = - ${env:oc_prod.build_flags} + ${env:main.build_flags} -DVOR ; -DVOR_NO_RANGE_BUTTON -DOC_VERSION_EXTRA="\"+VOR\"" [env:buchla] build_flags = - ${env:oc_prod.build_flags} + ${env:main.build_flags} -DBUCHLA_SUPPORT -DBUCHLA_4U -DOC_VERSION_EXTRA="\" Buchla\"" From 0fbf1daf3eba9ef8de9639581f01cbccc39917bc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 14 Apr 2023 13:49:11 -0400 Subject: [PATCH 215/417] Version bump v1.6 beta --- software/o_c_REV/OC_version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 174064bd3..3e7e27da0 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -2,10 +2,10 @@ #define OC_VERSION_H_ #ifndef OC_VERSION_EXTRA -#define OC_VERSION_EXTRA "" +#define OC_VERSION_EXTRA " beta" #endif #define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.5.1" OC_VERSION_EXTRA +#define OC_VERSION "v1.6" OC_VERSION_EXTRA #define OC_VERSION_URL "github.com/djphazer" #endif From 93d05631a2d12a2ec5bcaaf225b87898223a7006 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 17 Apr 2023 03:21:43 -0400 Subject: [PATCH 216/417] Config Menu cancels Help Screen --- software/o_c_REV/APP_HEMISPHERE.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 69598b54b..285ffda0e 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -408,6 +408,7 @@ public: void ToggleConfigMenu() { config_menu = !config_menu; + if (config_menu) SetHelpScreen(-1); } void SetHelpScreen(int hemisphere) { From e49f5c8f5cd5bfd88201cd896d38554cba2d6c68 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 17 Apr 2023 04:07:21 -0400 Subject: [PATCH 217/417] DualTM: sanity check when loading; rework transpose Special case to "crossfade" between registers for combo pitch --- software/o_c_REV/HEM_TM2.ino | 40 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index cbe0121ed..cffcf472a 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -94,6 +94,8 @@ public: void Controller() { bool clk = Clock(0); + if (clk) StartADCLag(0); + bool update_cv = EndOfADCLag(0); int cv_data[2]; cv_data[0] = DetentedIn(0); @@ -128,7 +130,7 @@ public: case TRANSPOSE1: case TRANSPOSE2: case TRANSPOSE_BOTH: - if (clk) // S&H style transpose + if (update_cv) // S&H style transpose trans_mod[cvmode[ch] - TRANSPOSE1] = MIDIQuantizer::NoteNumber(cv_data[ch], 0) - 60; // constrain to range_mod? break; @@ -136,11 +138,13 @@ public: } } - // Advance the register on clock, flipping bits as necessary - if (clk) { + if (update_cv) { // Update transpose values for (int i = 0; i < 3; ++i) { note_trans[i] = trans_mod[i]; } + } + // Advance the register on clock, flipping bits as necessary + if (clk) { // If the cursor is not on the p value, and Digital 2 is not gated, the sequence remains the same int prob = (cursor == PROB || Gate(1)) ? p_mod : 0; @@ -157,19 +161,24 @@ public: } // Send 8-bit scaled and quantized CV - int32_t note = Proportion(reg[0] & 0xff, 0xff, range_mod); - int32_t note2 = Proportion(reg[1] & 0xff, 0xff, range_mod); + int32_t note = Proportion(reg[0] & 0xff, 0xff, range_mod) + 60; + int32_t note2 = Proportion(reg[1] & 0xff, 0xff, range_mod) + 60; ForEachChannel(ch) { switch (outmode[ch]) { - case PITCH_SUM: - Output[ch] = slew(Output[ch], quantizer.Lookup(note + note2 + note_trans[2] + 64)); + case PITCH_SUM: { + // this is the unique case where input CV crossfades between the two melodies + int x = constrain(note_trans[2], -range_mod, range_mod); + int y = range_mod; + int n = (note * (y + x) + note2 * (y - x)) / (2*y); + Output[ch] = slew(Output[ch], quantizer.Lookup(n)); break; + } case PITCH1: - Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans[0] + note_trans[2] + 64)); + Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans[0] + note_trans[2])); break; case PITCH2: - Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans[1] + note_trans[2] + 64)); + Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans[1] + note_trans[2])); break; case MOD1: // 8-bit bi-polar proportioned CV Output[ch] = slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); @@ -269,10 +278,9 @@ public: Pack(data, PackLocation {25,8}, constrain(scale, 0, 255)); Pack(data, PackLocation {33,4}, cvmode[0]); Pack(data, PackLocation {37,4}, cvmode[1]); - Pack(data, PackLocation {41,6}, smoothing); + Pack(data, PackLocation {41,6}, smoothing - 1); - // maybe don't bother saving the damn register - //Pack(data, PackLocation {32,32}, reg); + // TODO: utilize enigma's global turing machine storage for the registers return data; } @@ -287,12 +295,8 @@ public: quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); - smoothing = Unpack(data, PackLocation {41,6}); - - /* XXX: registers could be saved/loaded from global storage instead? - reg[0] = Unpack(data, PackLocation {32,32}); - reg[1] = Unpack(data, PackLocation {0, 32}); // lol it could be fun - */ + smoothing = Unpack(data, PackLocation {41,6}) + 1; + smoothing = constrain(smoothing, 1, 128); } protected: From b18c188923571bec95444c7aa9ad351e75defcd7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 17 Apr 2023 12:10:47 -0400 Subject: [PATCH 218/417] Remove Harmonizer - Squanch does the same thing --- software/o_c_REV/HEM_Harmonizer.ino | 250 --------------------------- software/o_c_REV/hemisphere_config.h | 1 - 2 files changed, 251 deletions(-) delete mode 100644 software/o_c_REV/HEM_Harmonizer.ino diff --git a/software/o_c_REV/HEM_Harmonizer.ino b/software/o_c_REV/HEM_Harmonizer.ino deleted file mode 100644 index 3e12b22d6..000000000 --- a/software/o_c_REV/HEM_Harmonizer.ino +++ /dev/null @@ -1,250 +0,0 @@ -// Harmonizer for o_c hemisphere by Karol Firmanty. -// Outputs chord voicing based on user selected scale and CV input. -// -// Based on DualQuant, Copyright 2018 Jason Justian -// Based on Braids Quantizer, Copyright 2015 Émilie Gillet. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#include "braids_quantizer.h" -#include "braids_quantizer_scales.h" -#include "OC_scales.h" - -class Harmonizer : public HemisphereApplet { - public: - - const char* applet_name() { // Maximum 10 characters - return "Harmonizer"; - } - - void Start() { - cursor = 0; - quantizer.Init(); - scale = 5; - scale_data = OC::Scales::GetScale(scale); - quantizer.Configure(scale_data, 0xffff); - last_note[0] = 0; - last_note[1] = 0; - } - - void Controller() { - int32_t pitch = In(0); - int32_t quantized = quantizer.Process(pitch, root << 7, 0); - if (last_quantized_input != quantized) { - last_quantized_input = quantized; - int scale_step = FindScaleStep(quantized); - - ForEachChannel(ch) - { - int32_t offset_voltage = FindOffsetVoltage(scale_step, offset[ch]); - last_note[ch] = quantized + offset_voltage; - } - } - ForEachChannel(ch) - { - Out(ch, last_note[ch]); - } - } - - void View() { - gfxHeader(applet_name()); - DrawSelector(); - } - - void OnButtonPress() { - CursorAction(cursor, 3); - } - - void OnEncoderMove(int direction) { - if (!EditMode()) { - MoveCursor(cursor, direction, 3); - return; - } - - last_quantized_input = -1; //invalidate last_quantized_input to force refresh CV outs - switch (cursor) { - case 0: - scale += direction; - if (scale >= OC::Scales::NUM_SCALES) scale = 0; - if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - scale_data = OC::Scales::GetScale(scale); - quantizer.Configure(scale_data, 0xffff); - ForEachChannel(ch) - { - offset[ch] = constrain(offset[ch], 0, (int)scale_data.num_notes); - } - break; - case 1: - root = constrain(root + direction, 0, 11); - break; - case 2: - offset[0] = constrain(offset[0] + direction, 0, (int)scale_data.num_notes); - break; - case 3: - offset[1] = constrain(offset[1] + direction, 0, (int)scale_data.num_notes); - break; - } - } - - uint64_t OnDataRequest() { - uint64_t data = 0; - Pack(data, PackLocation {0, 8}, scale); - Pack(data, PackLocation {8, 4}, root); - ForEachChannel(ch) - { - Pack(data, PackLocation { size_t(12 + 4 * ch), 4}, offset[ch]); - } - return data; - } - - void OnDataReceive(uint64_t data) { - scale = Unpack(data, PackLocation {0, 8}); - root = Unpack(data, PackLocation {8, 4}); - - root = constrain(root, 0, 11); - scale_data = OC::Scales::GetScale(scale); - ForEachChannel(ch) - { - offset[ch] = Unpack(data, PackLocation { size_t(12 + 4 * ch), 4}); - offset[ch] = constrain(root, 0, (int)scale_data.num_notes); - } - quantizer.Configure(scale_data, 0xffff); - } - - protected: - /* Set help text. Each help section can have up to 18 characters. Be concise! */ - void SetHelp() { - // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = ""; - help[HEMISPHERE_HELP_CVS] = "CV 1=Pitch"; - help[HEMISPHERE_HELP_OUTS] = "OUT A=V2 B=V3"; - help[HEMISPHERE_HELP_ENCODER] = "Scale/Root/Offset"; - // "------------------" <-- Size Guide - } - - private: - braids::Quantizer quantizer; - int last_note[2]; - int last_quantized_input; - int offset[2]; - int cursor; - - // Settings - int scale; - uint8_t root; - braids::Scale scale_data; - - void DrawSelector() - { - // Draw settings - gfxPrint(0, 15, OC::scale_names_short[scale]); - gfxPrint(10, 25, OC::Strings::note_names_unpadded[root]); - - // Draw cursor - switch (cursor) { - case 0: - gfxCursor(0, 23, 30); - break; - case 1: - gfxCursor(10, 33, 12); - break; - case 2: - gfxCursor(31, 23, 12); - break; - case 3: - gfxCursor(31, 33, 12); - break; - } - - ForEachChannel(ch) - { - // Draw offsets - gfxPrint(31, 15 + 10 * ch, offset[ch]); - - // Print semitones - int semitone = (last_note[ch] / 128) % 12; - gfxPrint(10, 41 + (10 * ch), semitone); - } - } - - int FindScaleStep(int32_t quantized_pitch) { - int16_t base_pitch = quantized_pitch % scale_data.span; - int index = 0; - for (size_t i = 0; i < scale_data.num_notes; i++) { - if (scale_data.notes[i] == base_pitch) { - index = i; - break; - } - } - return index; - } - - int32_t FindOffsetVoltage(int scale_step, int offset) { - int16_t base_pitch = scale_data.notes[scale_step]; - int step = scale_step + offset; - if (step < (int)scale_data.num_notes) { - return scale_data.notes[step] - base_pitch; - } else { - return (scale_data.notes[step % scale_data.num_notes] + scale_data.span) - base_pitch; - } - } -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// Hemisphere Applet Functions -/// -/// Once you run the find-and-replace to make these refer to Harmonizer, -/// it's usually not necessary to do anything with these functions. You -/// should prefer to handle things in the HemisphereApplet child class -/// above. -//////////////////////////////////////////////////////////////////////////////// -Harmonizer Harmonizer_instance[2]; - -void Harmonizer_Start(bool hemisphere) { - Harmonizer_instance[hemisphere].BaseStart(hemisphere); -} - -void Harmonizer_Controller(bool hemisphere, bool forwarding) { - Harmonizer_instance[hemisphere].BaseController(forwarding); -} - -void Harmonizer_View(bool hemisphere) { - Harmonizer_instance[hemisphere].BaseView(); -} - -void Harmonizer_OnButtonPress(bool hemisphere) { - Harmonizer_instance[hemisphere].OnButtonPress(); -} - -void Harmonizer_OnEncoderMove(bool hemisphere, int direction) { - Harmonizer_instance[hemisphere].OnEncoderMove(direction); -} - -void Harmonizer_ToggleHelpScreen(bool hemisphere) { - Harmonizer_instance[hemisphere].HelpScreen(); -} - -uint64_t Harmonizer_OnDataRequest(bool hemisphere) { - return Harmonizer_instance[hemisphere].OnDataRequest(); -} - -void Harmonizer_OnDataReceive(bool hemisphere, uint64_t data) { - Harmonizer_instance[hemisphere].OnDataReceive(data); -} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 159162bf4..bc08e710c 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -41,7 +41,6 @@ DECLARE_APPLET( 22, 0x01, GameOfLife), \ DECLARE_APPLET( 29, 0x04, GateDelay), \ DECLARE_APPLET( 17, 0x50, GatedVCA), \ - DECLARE_APPLET( 63, 0x08, Harmonizer), \ DECLARE_APPLET( 16, 0x80, LoFiPCM), \ DECLARE_APPLET( 10, 0x44, Logic), \ DECLARE_APPLET( 21, 0x01, LowerRenz), \ From 765f74253c3fd566eb05335e22e4e0477973166b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 17 Apr 2023 14:08:51 -0400 Subject: [PATCH 219/417] Squanch: add Root Note; fix quantizer slippage; rework UI --- software/o_c_REV/HEM_Squanch.ino | 96 +++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/software/o_c_REV/HEM_Squanch.ino b/software/o_c_REV/HEM_Squanch.ino index daa949ec0..fc4a3b52e 100644 --- a/software/o_c_REV/HEM_Squanch.ino +++ b/software/o_c_REV/HEM_Squanch.ino @@ -26,15 +26,23 @@ class Squanch : public HemisphereApplet { public: + enum SquanchCursor { + SHIFT1, SHIFT2, + SCALE, ROOT_NOTE, + + LAST_SETTING = ROOT_NOTE + }; const char* applet_name() { return "Squanch"; } void Start() { - quantizer.Init(); scale = 5; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + ForEachChannel(ch) { + quantizer[ch].Init(); + quantizer[ch].Configure(OC::Scales::GetScale(scale), 0xffff); + } } void Controller() { @@ -52,8 +60,8 @@ public: // output, the output is raised by one octave when Digital 2 is gated. int32_t shift_alt = (ch == 1) ? DetentedIn(1) : Gate(1) * (12 << 7); - int32_t quantized = quantizer.Process(pitch, 0, shift[ch]); - Out(ch, quantized + shift_alt); + int32_t quantized = quantizer[ch].Process(pitch + shift_alt, root << 7, shift[ch]); + Out(ch, quantized); last_note[ch] = quantized; } } @@ -66,23 +74,33 @@ public: } void OnButtonPress() { - CursorAction(cursor, 2); + CursorAction(cursor, LAST_SETTING); } void OnEncoderMove(int direction) { if (!EditMode()) { - MoveCursor(cursor, direction, 2); + MoveCursor(cursor, direction, LAST_SETTING); return; } - if (cursor == 2) { // Scale selection + switch (cursor) { + case SHIFT1: + case SHIFT2: + shift[cursor] = constrain(shift[cursor] + direction, -48, 48); + break; + + case SCALE: scale += direction; if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + ForEachChannel(ch) + quantizer[ch].Configure(OC::Scales::GetScale(scale), 0xffff); continuous = 1; // Re-enable continuous mode when scale is changed - } else { - shift[cursor] = constrain(shift[cursor] + direction, -48, 48); + break; + + case ROOT_NOTE: + root = constrain(root + direction, 0, 11); + break; } } @@ -91,6 +109,7 @@ public: Pack(data, PackLocation {0,8}, scale); Pack(data, PackLocation {8,8}, shift[0] + 48); Pack(data, PackLocation {16,8}, shift[1] + 48); + Pack(data, PackLocation {24,4}, root); return data; } @@ -98,7 +117,10 @@ public: scale = Unpack(data, PackLocation {0,8}); shift[0] = Unpack(data, PackLocation {8,8}) - 48; shift[1] = Unpack(data, PackLocation {16,8}) - 48; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + root = Unpack(data, PackLocation {24,4}); + root = constrain(root, 0, 11); + ForEachChannel(ch) + quantizer[ch].Configure(OC::Scales::GetScale(scale), 0xffff); } protected: @@ -115,36 +137,47 @@ private: int cursor; // 0=A shift, 1=B shift, 2=Scale bool continuous = 1; int last_note[2]; // Last quantized note - braids::Quantizer quantizer; + + // Both channels use the same quantizer settings, but we need two instances + // to respect hysteresis independently on each, or else things get slippy + braids::Quantizer quantizer[2]; // Settings int scale; + uint8_t root; int16_t shift[2]; void DrawInterface() { const uint8_t * notes[2] = {NOTE_ICON, NOTE2_ICON}; - // Display icon if clocked - if (!continuous) gfxIcon(56, 25, CLOCK_ICON); - // Shift for A/C - gfxIcon(1, 14, notes[0]); - gfxPrint(11, 15, shift[0] > -1 ? "+" : ""); - gfxPrint(shift[0]); + ForEachChannel(ch) { + gfxIcon(1 + ch*32, 14, notes[ch]); + gfxPrint(10 + pad(10, shift[ch]) + ch*32, 15, shift[ch] > -1 ? "+" : ""); + gfxPrint(shift[ch]); + } - // Shift for B/D - gfxIcon(32, 14, notes[1]); - gfxPrint(43 + pad(10, shift[1]), 15, shift[1] > -1 ? "+" : ""); - gfxPrint(shift[1]); + // Scale & Root Note + gfxIcon(1, 24, SCALE_ICON); + gfxPrint(10, 25, OC::scale_names_short[scale]); + gfxPrint(40, 25, OC::Strings::note_names_unpadded[root]); - // Scale - gfxBitmap(1, 24, 8, SCALE_ICON); - gfxPrint(12, 25, OC::scale_names_short[scale]); + // Display icon if clocked + if (!continuous) gfxIcon(56, 25, CLOCK_ICON); // Cursors - if (cursor == 0) gfxCursor(10, 23, 18); - if (cursor == 1) gfxCursor(44, 23, 18); - if (cursor == 2) gfxCursor(12, 33, 30); // Scale Cursor + switch (cursor) { + case SHIFT1: + case SHIFT2: + gfxCursor(10 + (cursor - SHIFT1)*32, 23, 19); + break; + case SCALE: + gfxCursor(10, 33, 25); + break; + case ROOT_NOTE: + gfxCursor(40, 33, 13); + break; + } // Little note display @@ -153,9 +186,10 @@ private: ForEachChannel(ch) { int semitone = (last_note[ch] / 128) % 12; - int note_x = semitone * 4; // 4 pixels per semitone - if (note_x < 0) note_x = 0; - gfxIcon(10 + note_x, 41 + (10 * ch), notes[ch]); + while (semitone < 0) semitone += 12; + int note_x = semitone * 3; // pixels per semitone + gfxPrint(0, 41 + 10*ch, midi_note_numbers[MIDIQuantizer::NoteNumber(last_note[ch])] ); + gfxIcon(19 + note_x, 41 + (10 * ch), notes[ch]); } } From fc561442705807b2317f6ac7f5bdc936cace74eb Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 17 Apr 2023 02:10:55 -0400 Subject: [PATCH 220/417] Internal Clock: Paused state == Clock Sync Start --- software/o_c_REV/APP_HEMISPHERE.ino | 20 ++++++++++++++------ software/o_c_REV/HEM_ClockSetup.ino | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 285ffda0e..4b86090ba 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -243,9 +243,15 @@ public: } } - // Advance internal clock, sync to external pulse - if (clock_m->IsRunning()) - clock_m->SyncTrig( OC::DigitalInputs::clocked() ); + bool clock_sync = OC::DigitalInputs::clocked(); + bool reset = OC::DigitalInputs::clocked(); + + // Paused means wait for clock-sync to start + if (clock_m->IsPaused() && clock_sync) clock_m->Start(); + // TODO: automatically stop... + + // Advance internal clock, sync to external clock / reset + if (clock_m->IsRunning()) clock_m->SyncTrig( clock_sync, reset ); // NJM: always execute ClockSetup controller - it handles MIDI clock out HS::clock_setup_applet.Controller(LEFT_HEMISPHERE, clock_m->IsForwarded()); @@ -278,14 +284,16 @@ public: HS::available_applets[index].View(h); } - if (clock_m->IsRunning() || clock_m->IsPaused()) { + if (clock_m->IsRunning()) { // Metronome icon - graphics.drawBitmap8(56, 1, 8, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); + gfxIcon(56, 1, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); + } else if (clock_m->IsPaused()) { + gfxIcon(56, 1, PAUSE_ICON); } if (clock_m->IsForwarded()) { // CV Forwarding Icon - graphics.drawBitmap8(120, 1, 8, CLOCK_ICON); + gfxIcon(120, 1, CLOCK_ICON); } if (select_mode == LEFT_HEMISPHERE) graphics.drawFrame(0, 0, 64, 64); diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index bfbf1dc9e..51e48461f 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -210,7 +210,7 @@ private: clock_m->Stop(); } else { start_q = 1; - clock_m->Start(); + clock_m->Start( !clock_m->IsPaused() ); // stop->pause->start } } From e40a6ef254eab8346002c0cf749e4021f3315456 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 26 Apr 2023 20:58:12 -0400 Subject: [PATCH 221/417] cleanup --- Hemisphere_Suite.ino | 12 ------------ software/o_c_REV/APP_HEMISPHERE.ino | 1 - 2 files changed, 13 deletions(-) delete mode 100644 Hemisphere_Suite.ino diff --git a/Hemisphere_Suite.ino b/Hemisphere_Suite.ino deleted file mode 100644 index ca7367d5e..000000000 --- a/Hemisphere_Suite.ino +++ /dev/null @@ -1,12 +0,0 @@ -#include "Arduino.h" -//The setup function is called once at startup of the sketch -void setup() -{ -// Add your initialization code here -} - -// The loop function is called in an endless loop -void loop() -{ -//Add your repeated code here -} diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 4b86090ba..e62f0abd9 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -385,7 +385,6 @@ public: void DelegateEncoderMovement(const UI::Event &event) { int h = (event.control == OC::CONTROL_ENCODER_L) ? LEFT_HEMISPHERE : RIGHT_HEMISPHERE; if (config_menu) { - // TODO ConfigEncoderAction(h, event.value); return; } From e320abc3ee0ec804fcb77f5f470cc456d29ac77f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 26 Apr 2023 20:52:40 -0400 Subject: [PATCH 222/417] Clock sync-start tweaks Sync-start for Calibr8or; only send USB MIDI Start msg when actually starting clock TODO: maybe move this stuff into HSClockManager --- software/o_c_REV/APP_CALIBR8OR.ino | 3 +++ software/o_c_REV/APP_HEMISPHERE.ino | 5 +++-- software/o_c_REV/HEM_ClockSetup.ino | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index ef0e457d5..4c61f912f 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -224,6 +224,9 @@ public: } if (midi_sync) clock_m->SetClockPPQN(24); // rudely snap to MIDI clock sync speed + // Paused means wait for clock-sync to start + if (clock_m->IsPaused() && clock_sync) clock_m->Start(); + // Advance internal clock, sync to external clock / reset if (clock_m->IsRunning()) clock_m->SyncTrig( clock_sync, reset ); diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index e62f0abd9..3209b489a 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -404,8 +404,9 @@ public: clock_m->Stop(); usbMIDI.sendRealTime(usbMIDI.Stop); } else { - clock_m->Start(); - usbMIDI.sendRealTime(usbMIDI.Start); + bool p = clock_m->IsPaused(); + clock_m->Start( !p ); + if (p) usbMIDI.sendRealTime(usbMIDI.Start); } } diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 51e48461f..88a21571a 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -209,8 +209,8 @@ private: stop_q = 1; clock_m->Stop(); } else { - start_q = 1; - clock_m->Start( !clock_m->IsPaused() ); // stop->pause->start + start_q = clock_m->IsPaused(); + clock_m->Start( !start_q ); // stop->pause->start } } From 4eccb7be9cd0d9d7eec99c9eb2d2b890126ecfcc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 27 Apr 2023 01:17:25 -0400 Subject: [PATCH 223/417] Calibr8or: Clock Pause icon; fix preset Save vulnerability --- software/o_c_REV/APP_CALIBR8OR.ino | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index 4c61f912f..c24e47fd3 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -271,8 +271,10 @@ public: gfxHeader("Calibr8or"); // Metronome icon - if (clock_m->IsRunning() || clock_m->IsPaused()) { - graphics.drawBitmap8(56, 1, 8, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); + if (clock_m->IsRunning()) { + gfxIcon(56, 1, clock_m->Cycle() ? METRO_L_ICON : METRO_R_ICON); + } else if (clock_m->IsPaused()) { + gfxIcon(56, 1, PAUSE_ICON); } if (preset_select) { @@ -402,6 +404,8 @@ public: } if (preset_select) { edit_mode = (direction>0); + // prevent saving to the (clear) slot + if (edit_mode && preset_select == 5) preset_select = 4; return; } From c314432dead5fd3c5a63e2a613071600ab305adb Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 30 Apr 2023 13:26:47 -0400 Subject: [PATCH 224/417] ClockSetup: Fix tap tempo detection --- software/o_c_REV/HEM_ClockSetup.ino | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 88a21571a..87b763034 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -85,25 +85,24 @@ public: if (cursor == TEMPO) { // Tap Tempo detection if (last_tap_tick) { - tap_time[taps++] = OC::CORE::ticks - last_tap_tick; + tap_time[taps] = OC::CORE::ticks - last_tap_tick; - if (tap_time[taps-1] > CLOCK_TICKS_MAX) { + if (tap_time[taps] > CLOCK_TICKS_MAX) { taps = 0; last_tap_tick = 0; } - - if (taps == NR_OF_TAPS) + else if (++taps == NR_OF_TAPS) clock_m->SetTempoFromTaps(tap_time, taps); taps %= NR_OF_TAPS; - } else { - last_tap_tick = OC::CORE::ticks; } + last_tap_tick = OC::CORE::ticks; } } void OnEncoderMove(int direction) { taps = 0; + last_tap_tick = 0; if (!EditMode()) { MoveCursor(cursor, direction, LAST_SETTING); return; From 30bdebdadc68d1b0086e7c714926e50ea4757b27 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 30 Apr 2023 18:11:44 -0400 Subject: [PATCH 225/417] DualTM: fix pitch regression Braids quantizer Lookup function is offset by 64, rather than the MIDI Note Number 60 for middle C4 @ 0V. I intended to generate only positive voltages for Pitch before transpose. --- software/o_c_REV/HEM_TM2.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index cffcf472a..72c9c7972 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -161,8 +161,8 @@ public: } // Send 8-bit scaled and quantized CV - int32_t note = Proportion(reg[0] & 0xff, 0xff, range_mod) + 60; - int32_t note2 = Proportion(reg[1] & 0xff, 0xff, range_mod) + 60; + int32_t note = Proportion(reg[0] & 0xff, 0xff, range_mod) + 64; + int32_t note2 = Proportion(reg[1] & 0xff, 0xff, range_mod) + 64; ForEachChannel(ch) { switch (outmode[ch]) { From da99577748d8cc8a0ae308f8601a529edfac913b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 2 May 2023 03:24:43 -0400 Subject: [PATCH 226/417] Update TODO --- TODO.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index 98b3cafea..d31df15d9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,13 @@ TODO === -* Pull in Automatonnetz +* better MIDI input message delegation (event listeners?) +* global output quantizer setting for Hemispheres +* Add Root Note to DualTM +* import alternative grids_resources patterns for DrumMap2 +* Subharmonicon applets * Update Boilerplates -* General Config screen (long-press right button) -* Stepped Mode for internal Clock +* Automatic stop for internal Clock [APP IDEAS] * QUADRANTS @@ -15,3 +18,6 @@ TODO * - Fix FLIP_180 calibration * - Add Clock Setup to Calibr8or * - Calibr8or screensaver +* Pull in Automatonnetz +* Sync-Start for internal Clock +* General Config screen (long-press right button) From f2f404dc228992e5346bf2f56394229cc2e8ded4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 3 May 2023 15:25:36 -0400 Subject: [PATCH 227/417] Code comments for EEPROM storage size of each App --- software/o_c_REV/APP_ASR.ino | 4 ++-- software/o_c_REV/APP_AUTOMATONNETZ.ino | 4 +++- software/o_c_REV/APP_BBGEN.ino | 3 ++- software/o_c_REV/APP_BYTEBEATGEN.ino | 3 ++- software/o_c_REV/APP_CALIBR8OR.ino | 1 + software/o_c_REV/APP_CHORDS.ino | 1 + software/o_c_REV/APP_DQ.ino | 1 + software/o_c_REV/APP_ENIGMA.ino | 1 + software/o_c_REV/APP_ENVGEN.ino | 1 + software/o_c_REV/APP_H1200.ino | 1 + software/o_c_REV/APP_HEMISPHERE.ino | 1 + software/o_c_REV/APP_LORENZ.ino | 3 ++- software/o_c_REV/APP_MIDI.ino | 1 + software/o_c_REV/APP_NeuralNetwork.ino | 2 +- software/o_c_REV/APP_POLYLFO.ino | 1 + software/o_c_REV/APP_QQ.ino | 1 + software/o_c_REV/APP_SEQ.ino | 1 + software/o_c_REV/APP_THEDARKESTTIMELINE.ino | 1 + 18 files changed, 24 insertions(+), 7 deletions(-) diff --git a/software/o_c_REV/APP_ASR.ino b/software/o_c_REV/APP_ASR.ino index ebddabe7d..b04742679 100644 --- a/software/o_c_REV/APP_ASR.ino +++ b/software/o_c_REV/APP_ASR.ino @@ -742,7 +742,7 @@ const char* const int_seq_CV_destinations[] = { "igain", "seq", "strt", "len", "strd", "mod" }; - +// TOTAL EEPROM SIZE: 23 bytes SETTINGS_DECLARE(ASRApp, ASR_SETTING_LAST) { { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "Scale", OC::scale_names_short, settings::STORAGE_TYPE_U8 }, { 0, -5, 5, "octave", NULL, settings::STORAGE_TYPE_I8 }, // octave @@ -1101,4 +1101,4 @@ void ASR_debug() { } #endif // ASR_DEBUG -#endif // ENABLE_APP_ASR \ No newline at end of file +#endif // ENABLE_APP_ASR diff --git a/software/o_c_REV/APP_AUTOMATONNETZ.ino b/software/o_c_REV/APP_AUTOMATONNETZ.ino index ee627edf4..ef6314143 100644 --- a/software/o_c_REV/APP_AUTOMATONNETZ.ino +++ b/software/o_c_REV/APP_AUTOMATONNETZ.ino @@ -149,6 +149,7 @@ const char *cell_event_masks[] = { "rTOI", // 0x4 + 0x2 + 0x1 }; +// TOTAL EEPROM SIZE: 25 * 4 bytes SETTINGS_DECLARE(TransformCell, CELL_SETTING_LAST) { {0, tonnetz::TRANSFORM_NONE, tonnetz::TRANSFORM_LAST, "Trfm", tonnetz::transform_names_str, settings::STORAGE_TYPE_U8}, {0, -12, 12, "Offs", NULL, settings::STORAGE_TYPE_I8}, @@ -341,6 +342,7 @@ const char * const clear_mode_names[] = { "zero", "rT", "rTev" }; +// TOTAL EEPROM SIZE: 6 bytes SETTINGS_DECLARE(AutomatonnetzState, GRID_SETTING_LAST) { {8, 0, 8*GRID_DIMENSION - 1, "dx", NULL, settings::STORAGE_TYPE_I8}, {4, 0, 8*GRID_DIMENSION - 1, "dy", NULL, settings::STORAGE_TYPE_I8}, @@ -734,4 +736,4 @@ void Automatonnetz_handleEncoderEvent(const UI::Event &event) { } } -#endif // ENABLE_APP_AUTOMATONNETZ \ No newline at end of file +#endif // ENABLE_APP_AUTOMATONNETZ diff --git a/software/o_c_REV/APP_BBGEN.ino b/software/o_c_REV/APP_BBGEN.ino index 704793b27..8d293cf46 100644 --- a/software/o_c_REV/APP_BBGEN.ino +++ b/software/o_c_REV/APP_BBGEN.ino @@ -207,6 +207,7 @@ const char* const bb_cv_mapping_names[BB_CV_MAPPING_LAST] = { "off", "grav", "bnce", "ampl", "vel", "retr" }; +// TOTAL EEPROM SIZE: 4 * 9 bytes SETTINGS_DECLARE(BouncingBall, BB_SETTING_LAST) { { 128, 0, 255, "Gravity", NULL, settings::STORAGE_TYPE_U8 }, { 96, 0, 255, "Bounce loss", NULL, settings::STORAGE_TYPE_U8 }, @@ -407,4 +408,4 @@ void FASTRUN BBGEN_isr() { bbgen.ISR(); } -#endif // ENABLE_APP_BBGEN \ No newline at end of file +#endif // ENABLE_APP_BBGEN diff --git a/software/o_c_REV/APP_BYTEBEATGEN.ino b/software/o_c_REV/APP_BYTEBEATGEN.ino index e2ad64c6f..b64302ff2 100644 --- a/software/o_c_REV/APP_BYTEBEATGEN.ino +++ b/software/o_c_REV/APP_BYTEBEATGEN.ino @@ -358,6 +358,7 @@ const char* const bytebeat_cv_mapping_names[BYTEBEAT_CV_MAPPING_LAST] = { "off", "equ", "spd", "p0", "p1", "p2", "beg++", "beg+", "beg", "end++", "end+", "end","pitch" }; +// TOTAL EEPROM SIZE: 4 * 16 bytes SETTINGS_DECLARE(ByteBeat, BYTEBEAT_SETTING_LAST) { { 0, 0, 15, "Equation", OC::Strings::bytebeat_equation_names, settings::STORAGE_TYPE_U8 }, { 255, 0, 255, "Speed", NULL, settings::STORAGE_TYPE_U8 }, @@ -605,4 +606,4 @@ void FASTRUN BYTEBEATGEN_isr() { bytebeatgen.ISR(); } -#endif // ENABLE_APP_BYTEBEATGEN \ No newline at end of file +#endif // ENABLE_APP_BYTEBEATGEN diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index c24e47fd3..858b45716 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -558,6 +558,7 @@ private: } }; +// TOTAL EEPROM SIZE: 4 * 29 bytes SETTINGS_DECLARE(Calibr8orPreset, CAL8_SETTING_LAST) { {0, 0, 1, "validity flag", NULL, settings::STORAGE_TYPE_U4}, diff --git a/software/o_c_REV/APP_CHORDS.ino b/software/o_c_REV/APP_CHORDS.ino index e8b41986f..7238a261d 100644 --- a/software/o_c_REV/APP_CHORDS.ino +++ b/software/o_c_REV/APP_CHORDS.ino @@ -996,6 +996,7 @@ const char* const chord_playmodes[] = { "-", "SEQ+1", "SEQ+2", "SEQ+3", "TR3+1", "TR3+2", "TR3+3", "S+H#1", "S+H#2", "S+H#3", "S+H#4", "CV#1", "CV#2", "CV#3", "CV#4" }; +// TOTAL EEPROM SIZE: 25 bytes SETTINGS_DECLARE(Chords, CHORDS_SETTING_LAST) { { OC::Scales::SCALE_SEMI, OC::Scales::SCALE_SEMI, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, { 0, 0, 11, "root", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, diff --git a/software/o_c_REV/APP_DQ.ino b/software/o_c_REV/APP_DQ.ino index 142139e95..826f8a210 100644 --- a/software/o_c_REV/APP_DQ.ino +++ b/software/o_c_REV/APP_DQ.ino @@ -1113,6 +1113,7 @@ const char* const dq_tm_trig_out[] = { "echo", "lsb", "chng" }; +// TOTAL EEPROM SIZE: 2 * 34 bytes SETTINGS_DECLARE(DQ_QuantizerChannel, DQ_CHANNEL_SETTING_LAST) { { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, diff --git a/software/o_c_REV/APP_ENIGMA.ino b/software/o_c_REV/APP_ENIGMA.ino index dbc7a62bc..7572bbdf9 100644 --- a/software/o_c_REV/APP_ENIGMA.ino +++ b/software/o_c_REV/APP_ENIGMA.ino @@ -1158,6 +1158,7 @@ private: // The first 32 song steps @ 4 bytes each = 128 bytes // Four track settings @ 1 byte each = 4 bytes // Song length = 1 byte +// TOTAL EEPROM SIZE: 150 bytes #define ENIGMA_EEPROM_DATA {0,0,255,"St",NULL,settings::STORAGE_TYPE_U8}, #define ENIGMA_DO_THIRTY_TIMES(A) A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A SETTINGS_DECLARE(EnigmaTMWS, ENIGMA_SETTING_LAST) { diff --git a/software/o_c_REV/APP_ENVGEN.ino b/software/o_c_REV/APP_ENVGEN.ino index 0441bd509..eb78b35a7 100644 --- a/software/o_c_REV/APP_ENVGEN.ino +++ b/software/o_c_REV/APP_ENVGEN.ino @@ -730,6 +730,7 @@ const char* const internal_trigger_types[INT_TRIGGER_LAST] = { "EOC", // Keep length == 3 }; +// TOTAL EEPROM SIZE: 4 * 30 bytes SETTINGS_DECLARE(EnvelopeGenerator, ENV_SETTING_LAST) { { ENV_TYPE_AD, ENV_TYPE_FIRST, ENV_TYPE_LAST-1, "TYPE", envelope_types, settings::STORAGE_TYPE_U8 }, { 128, 0, 255, "S1", NULL, settings::STORAGE_TYPE_U16 }, // u16 in case resolution proves insufficent diff --git a/software/o_c_REV/APP_H1200.ino b/software/o_c_REV/APP_H1200.ino index 7a184f436..fa9e37bbb 100644 --- a/software/o_c_REV/APP_H1200.ino +++ b/software/o_c_REV/APP_H1200.ino @@ -434,6 +434,7 @@ const char* const h1200_eucl_cv_mappings[] = { "Hlen", "Hfil", "Hoff", }; +// TOTAL EEPROM SIZE: 37 bytes SETTINGS_DECLARE(H1200Settings, H1200_SETTING_LAST) { {0, -11, 11, "Transpose", NULL, settings::STORAGE_TYPE_I8}, {H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_NONE, H1200_CV_SOURCE_LAST-1, "Transpose CV", OC::Strings::cv_input_names_none, settings::STORAGE_TYPE_U8}, diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 3209b489a..76f042e99 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -579,6 +579,7 @@ private: } }; +// TOTAL EEPROM SIZE: 4 * 26 bytes SETTINGS_DECLARE(HemispherePreset, HEMISPHERE_SETTING_LAST) { {0, 0, 255, "Applet ID L", NULL, settings::STORAGE_TYPE_U8}, {0, 0, 255, "Applet ID R", NULL, settings::STORAGE_TYPE_U8}, diff --git a/software/o_c_REV/APP_LORENZ.ino b/software/o_c_REV/APP_LORENZ.ino index 0c1294f4f..bfee5a194 100644 --- a/software/o_c_REV/APP_LORENZ.ino +++ b/software/o_c_REV/APP_LORENZ.ino @@ -149,6 +149,7 @@ const char* const lorenz_freq_range_names[5] = { "sloth", "lazy", "slow", "med", "fast", }; +// TOTAL EEPROM SIZE: 9 bytes SETTINGS_DECLARE(LorenzGenerator, LORENZ_SETTING_LAST) { #ifdef BUCHLA_4U { 0, 0, 255, "Freq 1", NULL, settings::STORAGE_TYPE_U8 }, @@ -367,4 +368,4 @@ void LORENZ_debug() { } -#endif // ENABLE_APP_LORENZ \ No newline at end of file +#endif // ENABLE_APP_LORENZ diff --git a/software/o_c_REV/APP_MIDI.ino b/software/o_c_REV/APP_MIDI.ino index 1c76769ed..8ff44d482 100644 --- a/software/o_c_REV/APP_MIDI.ino +++ b/software/o_c_REV/APP_MIDI.ino @@ -830,6 +830,7 @@ private: } }; +// TOTAL EEPROM SIZE: 40*4 + 1 == 161 bytes SETTINGS_DECLARE(CaptainMIDI, MIDI_SETTING_LAST) { MIDI_SETUP_PARAMETER_LIST MIDI_SETUP_PARAMETER_LIST diff --git a/software/o_c_REV/APP_NeuralNetwork.ino b/software/o_c_REV/APP_NeuralNetwork.ino index 129f2949f..7fb73d8e8 100644 --- a/software/o_c_REV/APP_NeuralNetwork.ino +++ b/software/o_c_REV/APP_NeuralNetwork.ino @@ -576,7 +576,7 @@ private: } }; -// Declare 216 bytes for storage +// TOTAL EEPROM SIZE: 216 bytes #define NN_EEPROM_DATA {0,0,255,"St",NULL,settings::STORAGE_TYPE_U8}, #define NN_DO_TWENTYFOUR_TIMES(A) A A A A A A A A A A A A A A A A A A A A A A A A SETTINGS_DECLARE(NeuralNetwork, NN_SETTING_LAST) { diff --git a/software/o_c_REV/APP_POLYLFO.ino b/software/o_c_REV/APP_POLYLFO.ino index 402a5f30d..327ce3c59 100644 --- a/software/o_c_REV/APP_POLYLFO.ino +++ b/software/o_c_REV/APP_POLYLFO.ino @@ -210,6 +210,7 @@ const char* const tr4_multiplier[6] = { "/8", "/4", "/2", "x2", "x4", "x8" }; +// TOTAL EEPROM SIZE: 22 bytes SETTINGS_DECLARE(PolyLfo, POLYLFO_SETTING_LAST) { { 64, 0, 255, "C", NULL, settings::STORAGE_TYPE_U8 }, { 0, -128, 127, "F", NULL, settings::STORAGE_TYPE_I16 }, diff --git a/software/o_c_REV/APP_QQ.ino b/software/o_c_REV/APP_QQ.ino index 56375237b..a5356577a 100644 --- a/software/o_c_REV/APP_QQ.ino +++ b/software/o_c_REV/APP_QQ.ino @@ -1126,6 +1126,7 @@ const char* const aux_cv_dest[5] = { "-", "root", "oct", "trns", "mask" }; +// TOTAL EEPROM SIZE: 4 * 40 bytes SETTINGS_DECLARE(QuantizerChannel, CHANNEL_SETTING_LAST) { { OC::Scales::SCALE_SEMI, 0, OC::Scales::NUM_SCALES - 1, "Scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, { 0, 0, 11, "Root", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, diff --git a/software/o_c_REV/APP_SEQ.ino b/software/o_c_REV/APP_SEQ.ino index eb445a128..39e75b686 100644 --- a/software/o_c_REV/APP_SEQ.ino +++ b/software/o_c_REV/APP_SEQ.ino @@ -1972,6 +1972,7 @@ const char* const arp_range[] = { "1", "2", "3", "4" }; +// TOTAL EEPROM SIZE: 2 * 54 bytes SETTINGS_DECLARE(SEQ_Channel, SEQ_CHANNEL_SETTING_LAST) { { 0, 0, 4, "aux. mode", modes, settings::STORAGE_TYPE_U4 }, diff --git a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino index be4f3ab0f..554ea3fd6 100644 --- a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino +++ b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino @@ -513,6 +513,7 @@ private: }; // MIDI channels are U8 instead of U4 because the channel number is not zero-indexed; 0 means "off" +// TOTAL EEPROM SIZE: 8 bytes SETTINGS_DECLARE(TheDarkestTimeline, DT_SETTING_LAST) { {16, 1, 32, "Length", NULL, settings::STORAGE_TYPE_U8}, {0, 0, 31, "Index", NULL, settings::STORAGE_TYPE_U8}, From f18833323d976569c610efcd633c2a4373f22ca6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 5 May 2023 03:35:27 -0400 Subject: [PATCH 228/417] Version 1.6 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 3e7e27da0..d5140a226 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -2,7 +2,7 @@ #define OC_VERSION_H_ #ifndef OC_VERSION_EXTRA -#define OC_VERSION_EXTRA " beta" +#define OC_VERSION_EXTRA "" #endif #define OC_VERSION_TITLE "Phazerville Suite" From 872ec73eb36a9583a6a3e3196814cf5f5989b0f8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 10 May 2023 03:06:46 -0400 Subject: [PATCH 229/417] VOR/flipped build config permutations --- software/o_c_REV/platformio.ini | 34 +++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index e3831ebab..1c274fc09 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -12,12 +12,17 @@ src_dir = . default_envs = main - main_vor main_flipped + main_vor + main_vor_flipped oc_stock1 - oc_stock2 oc_stock1_flipped + oc_stock1_vor + oc_stock1_vor_flipped + oc_stock2 oc_stock2_flipped + oc_stock2_vor + oc_stock2_vor_flipped [env] platform = teensy@4.17.0 @@ -112,6 +117,31 @@ build_flags = ; -DVOR_NO_RANGE_BUTTON -DOC_VERSION_EXTRA="\"+VOR\"" +[env:main_vor_flipped] +build_flags = + ${env:main_flipped.build_flags} + -DVOR + +[env:oc_stock1_vor] +build_flags = + ${env:oc_stock1.build_flags} + -DVOR + +[env:oc_stock1_vor_flipped] +build_flags = + ${env:oc_stock1_flipped.build_flags} + -DVOR + +[env:oc_stock2_vor] +build_flags = + ${env:oc_stock2.build_flags} + -DVOR + +[env:oc_stock2_vor_flipped] +build_flags = + ${env:oc_stock2_flipped.build_flags} + -DVOR + [env:buchla] build_flags = ${env:main.build_flags} From 77a97b896ec4d64b43ada5270099f8af3974b188 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 10 May 2023 03:19:32 -0400 Subject: [PATCH 230/417] Preset A will auto-save --- software/o_c_REV/APP_HEMISPHERE.ino | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 76f042e99..e96563873 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -104,10 +104,9 @@ public: apply_value(8 + h, (data >> 48) & 0xffff); } + // TODO: I haven't updated the SysEx data structure here because I don't use it. + // Clock data would probably be useful if it's not too big. -NJM void OnSendSysEx() { - // Set the values_ array prior to packing it - //StoreClockData(); - // Describe the data structure for the audience uint8_t V[18]; V[0] = (uint8_t)values_[HEMISPHERE_SELECTED_LEFT_ID]; @@ -182,12 +181,15 @@ public: LoadFromPreset(0); } void Suspend() { - if (hem_active_preset) + if (hem_active_preset) { + // Preset A will auto-save + if (preset_id == 0) StoreToPreset(0); hem_active_preset->OnSendSysEx(); + } } - void StoreToPreset(int id) { - hem_active_preset = (HemispherePreset*)(hem_presets + id); + void StoreToPreset(HemispherePreset* preset) { + hem_active_preset = preset; for (int h = 0; h < 2; h++) { int index = my_applet[h]; @@ -197,6 +199,9 @@ public: hem_active_preset->SetData(h, data); } hem_active_preset->StoreClockData(); + } + void StoreToPreset(int id) { + StoreToPreset( (HemispherePreset*)(hem_presets + id) ); preset_id = id; } void LoadFromPreset(int id) { From 420e2e497544e13e28c12fbfefc2da73c6d4dd2a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 17 May 2023 03:33:46 -0400 Subject: [PATCH 231/417] TB-3PO: Store pattern length --- software/o_c_REV/HEM_TB3PO.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 6370235e1..57999771d 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -378,6 +378,7 @@ class TB_3PO : public HemisphereApplet Pack(data, PackLocation {12,4}, density_encoder); Pack(data, PackLocation {16,16}, seed); Pack(data, PackLocation {32,8}, octave_offset); + Pack(data, PackLocation {40,5}, num_steps - 1); return data; } @@ -388,6 +389,7 @@ class TB_3PO : public HemisphereApplet density_encoder = Unpack(data, PackLocation {12,4}); seed = Unpack(data, PackLocation {16,16}); octave_offset = Unpack(data, PackLocation {32,8}); + num_steps = Unpack(data, PackLocation {40,5}) + 1; //const braids::Scale & quant_scale = OC::Scales::GetScale(scale); set_quantizer_scale(scale); From 568fbe8c3fee940808f11e43f551ccdf810600d6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 19 May 2023 20:31:17 -0400 Subject: [PATCH 232/417] DualTM: combo pitch blend/xfade refactor This no longer acts as a transpose for the regular pitch outputs --- software/o_c_REV/HEM_TM2.ino | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 72c9c7972..6afdf3c02 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -56,7 +56,7 @@ public: }; enum OutputMode { - PITCH_SUM, + PITCH_BLEND, PITCH1, PITCH2, MOD1, @@ -76,7 +76,7 @@ public: RANGE_MOD, TRANSPOSE1, TRANSPOSE2, - TRANSPOSE_BOTH, + BLEND_XFADE, // actually crossfade blend of both pitches INMODE_LAST }; @@ -129,7 +129,7 @@ public: // bi-polar transpose before quantize case TRANSPOSE1: case TRANSPOSE2: - case TRANSPOSE_BOTH: + case BLEND_XFADE: if (update_cv) // S&H style transpose trans_mod[cvmode[ch] - TRANSPOSE1] = MIDIQuantizer::NoteNumber(cv_data[ch], 0) - 60; // constrain to range_mod? break; @@ -166,7 +166,7 @@ public: ForEachChannel(ch) { switch (outmode[ch]) { - case PITCH_SUM: { + case PITCH_BLEND: { // this is the unique case where input CV crossfades between the two melodies int x = constrain(note_trans[2], -range_mod, range_mod); int y = range_mod; @@ -175,10 +175,10 @@ public: break; } case PITCH1: - Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans[0] + note_trans[2])); + Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans[0])); break; case PITCH2: - Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans[1] + note_trans[2])); + Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans[1])); break; case MOD1: // 8-bit bi-polar proportioned CV Output[ch] = slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); @@ -350,7 +350,7 @@ private: gfxPrint(":"); switch (outmode[ch]) { - case PITCH_SUM: gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + case PITCH_BLEND: gfxBitmap(24+ch*32, 35, 3, SUP_ONE); case PITCH1: case PITCH2: gfxBitmap(15 + ch*32, 35, 8, NOTE_ICON); @@ -397,7 +397,7 @@ private: gfxIcon(15 + ch*32, 35, BEND_ICON); gfxBitmap(24+ch*32, 35, 3, SUP_ONE); break; - case TRANSPOSE_BOTH: + case BLEND_XFADE: gfxBitmap(24+ch*32, 35, 3, SUP_ONE); case TRANSPOSE2: gfxIcon(15 + ch*32, 35, BEND_ICON); From 754c0bff540b92e30c82b5488d2f36eecb393717 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 22 May 2023 03:31:12 -0400 Subject: [PATCH 233/417] Fix applet help screen / select mode bug --- software/o_c_REV/APP_HEMISPHERE.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index e96563873..17bf38844 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -380,7 +380,8 @@ public: if (!clock_setup) { // Select Mode if (hemisphere == select_mode) select_mode = -1; // Exit Select Mode if same button is pressed - else select_mode = hemisphere; // Otherwise, set Select Mode + else if (help_hemisphere < 0) // Otherwise, set Select Mode - UNLESS there's a help screen + select_mode = hemisphere; } if (click_tick) From a35c76ed936cd2f24e3b63bd40c2f961d4db249e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 26 May 2023 18:05:57 -0400 Subject: [PATCH 234/417] Build config updates, adapted from pld's dev/build branch --- software/o_c_REV/APP_SETTINGS.ino | 7 ++++--- software/o_c_REV/OC_debug.cpp | 22 +++++++++++++++++++++ software/o_c_REV/OC_strings.cpp | 23 ++++++++++++++++++++++ software/o_c_REV/OC_strings.h | 5 +++++ software/o_c_REV/OC_ui.cpp | 1 - software/o_c_REV/OC_version.h | 13 ++---------- software/o_c_REV/o_c_REV.ino | 1 - software/o_c_REV/platformio.ini | 9 +++++---- software/o_c_REV/resources/oc_build_tag.sh | 10 ++++++++++ software/o_c_REV/resources/oc_version.sh | 11 +++-------- software/o_c_REV/resources/progname.py | 22 +++++++++++++++++++++ 11 files changed, 96 insertions(+), 28 deletions(-) create mode 100755 software/o_c_REV/resources/oc_build_tag.sh create mode 100755 software/o_c_REV/resources/progname.py diff --git a/software/o_c_REV/APP_SETTINGS.ino b/software/o_c_REV/APP_SETTINGS.ino index b67db9dd9..61004393b 100644 --- a/software/o_c_REV/APP_SETTINGS.ino +++ b/software/o_c_REV/APP_SETTINGS.ino @@ -19,6 +19,7 @@ // SOFTWARE. #include "HSApplication.h" +#include "OC_strings.h" // Bitmap representation of QR code for access to http://www.beigemaze.com/hs, which // redirects to Hemisphere Suite documentation. @@ -71,9 +72,9 @@ public: void View() { gfxHeader("Setup / About"); - gfxPrint(0, 15, OC_VERSION_TITLE); - gfxPrint(0, 25, OC_VERSION); - gfxPrint(0, 35, OC_VERSION_URL); + gfxPrint(0, 15, "Phazerville Suite"); + gfxPrint(0, 25, OC::Strings::VERSION); + gfxPrint(0, 35, "github.com/djphazer"); gfxPrint(0, 55, "[CALIBRATE] [RESET]"); //DrawQRAt(103, 15); diff --git a/software/o_c_REV/OC_debug.cpp b/software/o_c_REV/OC_debug.cpp index 470e0bd4f..e2471cd32 100644 --- a/software/o_c_REV/OC_debug.cpp +++ b/software/o_c_REV/OC_debug.cpp @@ -5,6 +5,7 @@ #include "OC_debug.h" #include "OC_menus.h" #include "OC_ui.h" +#include "OC_strings.h" #include "util/util_misc.h" #include "extern/dspinst.h" @@ -78,6 +79,26 @@ static void debug_menu_core() { #endif } +static void debug_menu_version() +{ + graphics.setPrintPos(2, 12); + graphics.print(Strings::NAME); + graphics.setPrintPos(2, 22); + graphics.print(Strings::VERSION); + + weegfx::coord_t y = 32; + graphics.setPrintPos(2, y); y += 10; +#ifdef OC_DEV + graphics.print("DEV"); +#else + graphics.print("PROD"); +#endif +#ifdef USB_SERIAL + graphics.setPrintPos(2, y); y += 10; + graphics.print("USB_SERIAL"); +#endif +} + static void debug_menu_gfx() { graphics.drawFrame(0, 0, 128, 64); @@ -117,6 +138,7 @@ struct DebugMenu { static const DebugMenu debug_menus[] = { { " CORE", debug_menu_core }, + { " VERS", debug_menu_version }, { " GFX", debug_menu_gfx }, { " ADC", debug_menu_adc }, #ifdef POLYLFO_DEBUG diff --git a/software/o_c_REV/OC_strings.cpp b/software/o_c_REV/OC_strings.cpp index e96f2c7b9..d1761dd21 100644 --- a/software/o_c_REV/OC_strings.cpp +++ b/software/o_c_REV/OC_strings.cpp @@ -4,6 +4,27 @@ namespace OC { namespace Strings { + const char * const VERSION = +#include "OC_version.h" +#ifdef OC_VERSION_EXTRA + OC_VERSION_EXTRA +#endif + "-" +#ifdef OC_BUILD_TAG + OC_BUILD_TAG +#endif + ; +#ifdef BUCHLA_cOC + const char * const NAME = "NLM card O_C"; + const char * const SHORT_NAME = "cOC"; +#elif defined(VOR) + const char * const NAME = "Plum Audio O_C+"; + const char * const SHORT_NAME = "OC+"; +#else + const char * const NAME = "Ornaments & Crimes"; + const char * const SHORT_NAME = "o_C"; +#endif + const char * const seq_playmodes[] = {" -", "SEQ+1", "SEQ+2", "SEQ+3", "TR+1", "TR+2", "TR+3", "ARP", "S+H#1", "S+H#2", "S+H#3", "S+H#4", "CV#1", "CV#2", "CV#3", "CV#4"}; const char * const channel_trigger_sources[] = {"TR1", "TR2", "TR3", "TR4", "cnt+", "cnt-"}; @@ -16,6 +37,8 @@ namespace OC { const char * const note_names_unpadded[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; + const char * const VOR_offsets[3] = { "-5V", "-3V", "0V"}; + const char * const trigger_input_names[4] = { "TR1", "TR2", "TR3", "TR4" }; const char * const trigger_input_names_none[5] = { " - ", "TR1", "TR2", "TR3", "TR4" }; diff --git a/software/o_c_REV/OC_strings.h b/software/o_c_REV/OC_strings.h index d69a7d9dc..916c537aa 100644 --- a/software/o_c_REV/OC_strings.h +++ b/software/o_c_REV/OC_strings.h @@ -10,6 +10,10 @@ namespace OC { static const int kNumDelayTimes = 8; namespace Strings { + extern const char * const NAME; + extern const char * const SHORT_NAME; + extern const char * const VERSION; + extern const char * const seq_playmodes[]; extern const char * const channel_trigger_sources[]; extern const char * const seq_directions[]; @@ -17,6 +21,7 @@ namespace OC { extern const char * const channel_id[]; extern const char * const note_names[]; extern const char * const note_names_unpadded[]; + extern const char * const VOR_offsets[]; extern const char * const trigger_input_names[]; extern const char * const trigger_input_names_none[]; extern const char * const cv_input_names[]; diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index ed3500379..d0d4137c7 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -9,7 +9,6 @@ #include "OC_gpio.h" #include "OC_menus.h" #include "OC_ui.h" -#include "OC_version.h" #include "OC_options.h" #include "src/drivers/display.h" diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index d5140a226..e348383a2 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,11 +1,2 @@ -#ifndef OC_VERSION_H_ -#define OC_VERSION_H_ - -#ifndef OC_VERSION_EXTRA -#define OC_VERSION_EXTRA "" -#endif - -#define OC_VERSION_TITLE "Phazerville Suite" -#define OC_VERSION "v1.6" OC_VERSION_EXTRA -#define OC_VERSION_URL "github.com/djphazer" -#endif +// NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION +"v1.6.1" diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 9c4df05be..2527cf066 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -36,7 +36,6 @@ #include "OC_menus.h" #include "OC_strings.h" #include "OC_ui.h" -#include "OC_version.h" #include "OC_options.h" #include "src/drivers/display.h" #include "src/drivers/ADC/OC_util_ADC.h" diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 1c274fc09..94156b714 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -33,10 +33,13 @@ lib_deps = EEPROM build_flags = -DTEENSY_OPT_SMALLEST_CODE -DUSB_MIDI + -Iextern build_src_filter = +<*> - +extra_scripts = pre:resources/progname.py + upload_protocol = teensy-gui [env:cal8] @@ -74,7 +77,7 @@ build_flags = -DENABLE_APP_LORENZ ; -DENABLE_APP_BBGEN ; -DENABLE_APP_BYTEBEATGEN - -DOC_VERSION_EXTRA="\" +stock1\"" + -DOC_VERSION_EXTRA="\"+stock1\"" [env:oc_stock2] build_flags = @@ -92,13 +95,12 @@ build_flags = -DENABLE_APP_LORENZ -DENABLE_APP_BBGEN -DENABLE_APP_BYTEBEATGEN - -DOC_VERSION_EXTRA="\" +stock2\"" + -DOC_VERSION_EXTRA="\"+stock2\"" [env:main_flipped] build_flags = ${env:main.build_flags} -DFLIP_180 - -DOC_VERSION_EXTRA="\" flipped\"" [env:oc_stock1_flipped] build_flags = @@ -115,7 +117,6 @@ build_flags = ${env:main.build_flags} -DVOR ; -DVOR_NO_RANGE_BUTTON - -DOC_VERSION_EXTRA="\"+VOR\"" [env:main_vor_flipped] build_flags = diff --git a/software/o_c_REV/resources/oc_build_tag.sh b/software/o_c_REV/resources/oc_build_tag.sh new file mode 100755 index 000000000..2cb9deb14 --- /dev/null +++ b/software/o_c_REV/resources/oc_build_tag.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +git_rev="$(git rev-parse --short HEAD)" +git_status="$(git status -s --untracked-files=no)" + +if [ -n "$git_status" ] ; then + suffix="dirty" +fi + +echo "${git_rev}${suffix}" diff --git a/software/o_c_REV/resources/oc_version.sh b/software/o_c_REV/resources/oc_version.sh index b071eaace..3ed7891d2 100755 --- a/software/o_c_REV/resources/oc_version.sh +++ b/software/o_c_REV/resources/oc_version.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Run from source directory, e.g. ./resources/oc_version.sh "1.0.0 $(git rev-parse --short HEAD)" +# Run from source directory, e.g. ./resources/oc_version.sh "1.0.0" if [ -z "$1" ]; then echo "Please specify version string" @@ -7,11 +7,6 @@ if [ -z "$1" ]; then fi cat > OC_version.h < Date: Sun, 2 Jan 2022 01:55:49 -0500 Subject: [PATCH 235/417] Alternate DrumMap patterns from KittenVillage Selected via build flag DRUMMAP_GRIDS2 --- software/o_c_REV/HEM_DrumMap.ino | 4 + software/o_c_REV/grids2_resources.h | 492 ++++++++++++++++++++++++++++ software/o_c_REV/platformio.ini | 1 + 3 files changed, 497 insertions(+) create mode 100644 software/o_c_REV/grids2_resources.h diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index fef75b9df..fb0b68659 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -20,7 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#ifdef DRUMMAP_GRIDS2 +#include "grids2_resources.h" +#else #include "grids_resources.h" +#endif #define HEM_DRUMMAP_PULSE_ANIMATION_TICKS 1000 #define HEM_DRUMMAP_VALUE_ANIMATION_TICKS 16000 diff --git a/software/o_c_REV/grids2_resources.h b/software/o_c_REV/grids2_resources.h new file mode 100644 index 000000000..568367e13 --- /dev/null +++ b/software/o_c_REV/grids2_resources.h @@ -0,0 +1,492 @@ +// Copyright 2012 Emilie Gillet. +// +// Author: Emilie Gillet (emilie.o.gillet@gmail.com) +// +// This program 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. +// This program 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 this program. If not, see . +// +// ----------------------------------------------------------------------------- +// +// Resources definitions. +// +// Automatically generated with: +// make resources + +#include + +#ifndef GRIDS2_RESOURCES_H_ +#define GRIDS2_RESOURCES_H_ + +namespace grids { + +const uint8_t node_0[] = { + // KICKS + 255, 10, 170, 10, 90, 10, 40, 10, + 230, 10, 160, 10, 90, 10, 40, 10, + 240, 10, 170, 10, 90, 10, 40, 10, + 230, 10, 160, 10, 90, 10, 180, 10, + // SNARES + 40, 10, 40, 10, 40, 10, 40, 10, + 255, 10, 140, 10, 40, 10, 40, 10, + 40, 10, 40, 10, 40, 10, 40, 10, + 255, 10, 140, 10, 40, 40, 180, 10, + // HI-HATS + 90, 20, 60, 20, 250, 20, 140, 20, + 90, 20, 60, 20, 250, 20, 140, 20, + 90, 20, 60, 20, 250, 20, 140, 20, + 90, 20, 60, 20, 250, 20, 140, 20, +}; +// GET CONNECTED +const uint8_t node_1[] = { + // KICK + 200, 0, 0, 0, 0, 0, 0, 0, + 160, 0, 0, 0, 0, 0, 0, 0, + 200, 0, 0, 0, 0, 0, 160, 0, + 160, 0, 160, 0, 0, 0, 160, 0, + // SNARE + 60, 0, 40, 0, 60, 0, 40, 0, + 200, 0, 40, 0, 60, 0, 40, 0, + 60, 0, 40, 0, 60, 0, 40, 0, + 200, 0, 40, 0, 60, 0, 40, 0, + // HIHAT + 0, 0, 90, 0, 190, 0, 0, 0, + 0, 0, 90, 0, 190, 0, 0, 0, + 0, 0, 90, 0, 190, 0, 0, 0, + 0, 0, 90, 0, 190, 0, 0, 0, +}; +// EMPTY: +/* + // KICK + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + // SNARE + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + // HIHAT + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +*/ +// SHE'S JUST TOO MUCH +const uint8_t node_2[] = { + // KICK + 200, 0, 0, 100, 200, 0, 100, 0, + 0 , 0, 0, 0, 0, 0, 0, 0, + 200, 0, 0, 150, 150, 0, 200, 0, + 0, 0, 0, 60, 60, 0, 60, 0, + // SNARE + 0, 0, 0, 0, 0, 0, 0, 0, + 150, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 150, 0, 0, 0, 0, 0, 0, 0, + // HIHAT + 0, 0, 200, 0, 0, 0, 170, 0, + 150, 160, 150, 160, 150, 0, 150, 0, + 0, 0, 150, 150, 150, 0, 0, 0, + 0, 0, 150, 150, 150, 0, 150, 0, +}; +// COMPUTER SCREEN +const uint8_t node_3[] = { + // KICK + 200, 0, 0, 0, 0, 0, 0, 0, + 200, 0, 0, 0, 0, 0, 0, 0, + 200, 0, 0, 0, 0, 0, 0, 0, + 200, 0, 0, 0, 0, 0, 0, 0, + // SNARE + 100, 0, 0, 0, 80, 0, 0, 0, + 200, 0, 0, 0, 80, 0, 0, 0, + 100, 0, 0, 0, 80, 0, 0, 0, + 200, 0, 0, 0, 80, 0, 0, 0, + // HIHAT + 120, 0, 120, 0, 180, 0, 120, 0, + 0, 0, 0, 0, 180, 0, 0, 0, + 0, 0, 0, 0, 180, 0, 0, 0, + 100, 0, 100, 0, 180, 0, 100, 0, +}; +const uint8_t node_4[] = { + // KICK + 240, 0, 100, 0, 0, 0, 0, 40, + 200, 0, 0, 0, 100, 0, 0, 0, + 220, 0, 100, 0, 0, 0, 0, 40, + 200, 0, 0, 0, 80, 0, 0, 0, + // SNARE + 100, 0, 100, 0, 0, 0, 0, 50, + 200, 0, 100, 0, 0, 0, 0, 50, + 100, 0, 100, 0, 0, 0, 0, 150, + 200, 0, 100, 50, 150, 0, 50, 50, + // HIHAT + 0, 0, 0, 100, 200, 0, 0, 0, + 0, 0, 0, 0, 200, 0, 0, 0, + 0, 0, 0, 100, 200, 0, 0, 0, + 0, 0, 0, 0, 200, 0, 0, 0, +}; +// YOU CAN DO WITHOUT - A +const uint8_t node_5[] = { + // KICK + 200, 10, 100, 0, 200, 0, 100, 20, + 50, 20, 200, 0, 100, 0, 0, 100, + 200, 20, 100, 0, 100, 0, 200, 0, + 50, 10, 200, 0, 100, 0, 200, 20, + + // SNARE + 80, 60,0,100,20,20,100,50, + 200,20,0,100,20,20,100,50, + 70,20, 80,100, 200,40, 100,50, + 70,20, 80,100, 200,40, 100,50, + + // HIHAT + 200, 50, 150, 50,180,50,150,50, + 200, 50, 150, 50,180,50,150,50, + 200, 50, 150, 50,180,50,150,50, + 200, 50, 150, 50,180,50,150,50, + +}; +// YOU CAN DO WITHOUT - B +const uint8_t node_6[] = { + // KICK + 200, 10, 50, 40, 160, 10, 50, 100, + 100, 10, 150, 40, 100, 10, 170, 70, + 200, 10, 50, 40, 150, 10, 50, 10, + 200, 10, 150, 40, 100, 10, 170, 80, + // SNARE + 10, 10, 10, 40, 100, 10, 10, 10, + 200, 10, 10, 40, 100, 10, 10, 60, + 10, 10, 10, 40, 100, 10, 10, 10, + 30, 10, 10, 40, 200, 10, 10, 80, + // HIHAT + 120, 80, 120, 80, 180, 80, 180, 80, + 120, 80, 120, 80, 180, 80, 180, 80, + 120, 80, 120, 80, 180, 80, 180, 80, + 120, 80, 120, 80, 150, 80, 180, 80, + +}; +// 22ND FLOOR - A +const uint8_t node_7[] = { + // KICK + 150, 30, 20, 30, 50, 10, 20, 10, + 200, 30, 20, 30, 100, 10, 20, 10, + 150, 30, 20, 30, 50, 10, 20, 10, + 200, 30, 20, 30, 100, 10, 20, 10, + // SNARE + 200, 10, 50, 10, 100, 10, 200, 10, + 100, 10, 50, 10, 200, 10, 100, 10, + 200, 10, 50, 10, 100, 10, 200, 10, + 100, 10, 50, 10, 200, 10, 100, 10, + // HIHAT + 200,40,80,40,180,40,80,40, + 200,40,80,40,180,40,80,40, + 200,40,80,40,180,40,80, 40, + 200,40,80,40,180,40,80, 40, + +}; +// 22ND FLOOR - B +const uint8_t node_8[] = { + // KICK + 250, 0, 0, 0, 100, 0, 50, 0, + 250, 0, 0, 0, 100, 0, 50, 0, + 250, 0, 0, 0, 100, 0, 50, 0, + 250, 0, 0, 0, 100, 0, 195, 0, + // SNARE + 150, 20, 150, 20, 50, 20, 50, 20, + 200, 20, 150, 20, 50, 20, 100, 20, + 150, 20, 150, 20, 50, 20, 50, 20, + 200, 20, 150, 20, 50, 20, 170, 20, + // HIHAT + 10, 10, 180, 10, 200, 10, 180, 10, + 10, 10, 180, 10, 200, 10, 200, 10, + 10, 10, 180, 10, 200, 10, 180, 10, + 10, 10, 180, 10, 200, 10, 200, 10, + +}; +// FIRE ON THE FLOOR - A +// TRIPLETS! +const uint8_t node_9[] = { + // KICK + 200, 0, 60, 0, 60, 0, + 190, 0, 60, 0, 60, 0, + 180, 0, 60, 0, 150, 0, + // 200, 0, 0, 0, 0, 0, + // SNARE + 180, 50, 150, 50, 150, 50, + 180, 50, 150, 50, 150, 50, + 200, 50, 150, 50, 150, 50, + // 150, 0, 150, 0, 150, 0, + // HIHAT + 200, 50, 180, 50, 180, 50, + 50, 50, 180, 50, 180, 50, + 50, 50, 180, 50, 180, 50, + // 0, 0, 180, 0, 180, 0, + +}; +// FIRE ON THE FLOOR - B +const uint8_t node_10[] = { + // KICK + 200, 0, 50, 0, 100, 0, 50, 0, + 200, 0, 50, 0, 100, 0, 50, 0, + 200, 0, 50, 0, 100, 0, 50, 0, + 200, 0, 50, 0, 100, 0, 150, 0, + // SNARE + 0, 0, 0, 0, 0, 0, 0, 0, + 200, 0, 0, 0, 0, 0, 100, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 200, 0, 100, 0, 0, 0, 150, 0, + // HIHAT + 180, 0, 120, 0, 240, 0, 100, 0, + 180, 0, 120, 0, 240, 0, 100, 0, + 180, 0, 120, 0, 240, 0, 100, 0, + 180, 0, 120, 0, 240, 0, 100, 0, + +}; +// IF I HAVE TO - A +const uint8_t node_11[] = { + // KICK + 200, 0, 0, 0, 0, 0, 70, 0, + 130, 0, 0, 0, 0, 0, 100, 0, + 130, 0, 0, 0, 100, 0, 0, 0, + 130, 0, 0, 0, 100, 0, 70, 0, + // SNARE + 250, 120, 180, 120, 180, 120, 180, 120, + 200, 120, 180, 120, 180, 120, 180, 120, + 250, 120, 180, 120, 180, 120, 180, 120, + 200, 120, 180, 120, 180, 120, 180, 120, + // HIHAT + 160, 50, 120, 50, 200, 50, 120, 50, + 160, 50, 120, 50, 200, 50, 120, 50, + 160, 50, 120, 50, 200, 50, 120, 50, + 160, 50, 120, 50, 150, 50, 120, 50, + +}; +// IF I HAVE TO - B +const uint8_t node_12[] = { + // KICK + 250, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 250, 0, + 0, 0, 0, 0, 250, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + // SNARE + 100, 0, 0, 0, 100, 0, 0, 0, + 200, 0, 0, 0, 100, 0, 0, 0, + 100, 0, 0, 0, 100, 0, 0, 0, + 200, 0, 150, 0, 100, 0, 150, 0, + // HIHAT + 50, 20, 150, 150, 150, 20, 150, 20, + 50, 20, 150, 50, 200, 20, 150, 20, + 150, 20, 200, 50, 150, 20, 200, 20, + 50, 20, 100, 100, 100, 20, 100, 20, + +}; +// CHOCOLATE - A +const uint8_t node_13[] = { + // KICK + 200, 0, 0, 0, 0, 0, 180, 0, + 0, 0, 180, 0, 0, 0, 0, 0, + 200, 0, 0, 0, 0, 0, 180, 0, + 0, 0, 180, 0, 0, 0, 0, 0, + // SNARE + 50, 0, 50, 0, 50, 0, 50, 0, + 250, 0, 150, 0, 50, 0, 100, 0, + 50, 0, 100, 0, 100, 0, 50, 0, + 250, 0, 150, 0, 50, 0, 180, 0, + // HIHAT + 200, 50, 180, 50, 250, 50, 180, 50, + 180, 50, 250, 50, 180, 50, 180, 50, + 250, 50, 180, 50, 180, 50, 250, 50, + 180, 50, 180, 50, 250, 50, 180, 50, +}; + +// CHOCOLATE - B +const uint8_t node_14[] = { + // KICK + 250, 0, 50, 10, 250, 0, 50, 0, + 50, 0, 200, 10, 50, 0, 180, 10, + 190, 0, 50, 10, 100, 0, 50, 10, + 50, 0, 100, 10, 50, 0, 100, 10, + // SNARE + 100, 10, 60, 10, 100, 10, 60, 10, + 250, 10, 60, 10, 100, 10, 60, 10, + 100, 10, 60, 10, 100, 10, 60, 10, + 250, 30, 200, 30, 100, 30, 190, 30, + // HIHAT + 50, 20, 100, 20, 250, 20, 200, 20, + 50, 20, 100, 20, 250, 20, 200, 20, + 50, 20, 100, 20, 250, 20, 200, 20, + 50, 20, 100, 20, 250, 20, 200, 20, + +}; +// SHE DOESN'T MIND THING +const uint8_t node_15[] = { + // KICK + 250, 0, 50, 0, 140, 0, 250, 0, + 140, 0, 50, 0, 250, 0, 50, 0, + 250, 0, 50, 0, 200, 0, 50, 0, + 200, 0, 50, 0, 140, 0, 50, 120, + // SNARE + 100, 100, 0, 0, 100, 100, 0, 0, + 250, 100, 0, 130, 130, 100, 130, 0, + 100, 100, 0, 0, 100, 100, 0, 0, + 250, 100, 0, 160, 160, 0, 160, 160, + // HIHAT + 80, 30, 180, 200, 180, 30, 180, 100, + 80, 30, 180, 200, 180, 30, 180, 100, + 80, 30, 180, 200, 180, 30, 180, 100, + 80, 30, 180, 200, 180, 30, 180, 180, + +}; + +// PARTY NEXT DOOR +// SHE DOESN'T MIND +// GARAGE +// DOUBLE SHARP POP +// NOT USED ANY MORE: +const uint8_t node_16[] = { + 255, 0, 0, 0, 0, 0, 95, 0, + 0, 0, 127, 0, 0, 0, 0, 0, + 223, 0, 95, 0, 63, 0, 31, 0, + 191, 0, 0, 0, 159, 0, 0, 0, + 0, 0, 31, 0, 255, 0, 0, 0, + 0, 0, 95, 0, 223, 0, 0, 0, + 0, 0, 63, 0, 191, 0, 0, 0, + 0, 0, 0, 0, 159, 0, 127, 0, + 141, 0, 28, 0, 28, 0, 28, 0, + 113, 0, 8, 0, 8, 0, 8, 0, + 255, 0, 0, 0, 226, 0, 0, 0, + 198, 0, 56, 0, 170, 0, 85, 0, +}; +const uint8_t node_17[] = { + 255, 0, 0, 0, 8, 0, 0, 0, + 182, 0, 0, 0, 72, 0, 0, 0, + 218, 0, 0, 0, 36, 0, 0, 0, + 145, 0, 0, 0, 109, 0, 0, 0, + 0, 0, 51, 25, 76, 25, 25, 0, + 153, 0, 0, 0, 127, 102, 178, 0, + 204, 0, 0, 0, 0, 0, 255, 0, + 0, 0, 102, 0, 229, 0, 76, 0, + 113, 0, 0, 0, 141, 0, 85, 0, + 0, 0, 0, 0, 170, 0, 0, 0, + 56, 28, 255, 0, 0, 0, 0, 0, + 198, 0, 0, 0, 226, 0, 0, 0, +}; +const uint8_t node_18[] = { + 255, 0, 8, 0, 28, 0, 28, 0, + 198, 0, 56, 0, 56, 0, 85, 0, + 255, 0, 85, 0, 113, 0, 113, 0, + 226, 0, 141, 0, 170, 0, 141, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 255, 0, 0, 0, 127, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 63, 0, 0, 0, 191, 0, 0, 0, + 255, 0, 0, 0, 255, 0, 127, 0, + 0, 0, 85, 0, 0, 0, 212, 0, + 0, 0, 212, 0, 42, 0, 170, 0, + 0, 0, 127, 0, 0, 0, 0, 0, +}; +const uint8_t node_19[] = { + 255, 0, 0, 0, 0, 0, 218, 0, + 182, 0, 0, 0, 0, 0, 145, 0, + 145, 0, 36, 0, 0, 0, 109, 0, + 109, 0, 0, 0, 72, 0, 36, 0, + 0, 0, 0, 0, 109, 0, 8, 0, + 72, 0, 0, 0, 255, 0, 182, 0, + 0, 0, 0, 0, 145, 0, 8, 0, + 36, 0, 8, 0, 218, 0, 182, 0, + 255, 0, 0, 0, 0, 0, 226, 0, + 85, 0, 0, 0, 141, 0, 0, 0, + 0, 0, 0, 0, 170, 0, 56, 0, + 198, 0, 0, 0, 113, 0, 28, 0, +}; +const uint8_t node_20[] = { + 255, 0, 0, 0, 113, 0, 0, 0, + 198, 0, 56, 0, 85, 0, 28, 0, + 255, 0, 0, 0, 226, 0, 0, 0, + 170, 0, 0, 0, 141, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 255, 0, 145, 0, 109, 0, 218, 0, + 36, 0, 182, 0, 72, 0, 72, 0, + 255, 0, 0, 0, 0, 0, 109, 0, + 36, 0, 36, 0, 145, 0, 0, 0, + 72, 0, 72, 0, 182, 0, 0, 0, + 72, 0, 72, 0, 218, 0, 0, 0, + 109, 0, 109, 0, 255, 0, 0, 0, +}; +const uint8_t node_21[] = { + 255, 0, 0, 0, 218, 0, 0, 0, + 145, 0, 0, 0, 36, 0, 0, 0, + 218, 0, 0, 0, 36, 0, 0, 0, + 182, 0, 72, 0, 0, 0, 109, 0, + 0, 0, 0, 0, 8, 0, 0, 0, + 255, 0, 85, 0, 212, 0, 42, 0, + 0, 0, 0, 0, 8, 0, 0, 0, + 85, 0, 170, 0, 127, 0, 42, 0, + 109, 0, 109, 0, 255, 0, 0, 0, + 72, 0, 72, 0, 218, 0, 0, 0, + 145, 0, 182, 0, 255, 0, 0, 0, + 36, 0, 36, 0, 218, 0, 8, 0, +}; +const uint8_t node_22[] = { + 255, 0, 0, 0, 42, 0, 0, 0, + 212, 0, 0, 0, 8, 0, 212, 0, + 170, 0, 0, 0, 85, 0, 0, 0, + 212, 0, 8, 0, 127, 0, 8, 0, + 255, 0, 85, 0, 0, 0, 0, 0, + 226, 0, 85, 0, 0, 0, 198, 0, + 0, 0, 141, 0, 56, 0, 0, 0, + 170, 0, 28, 0, 0, 0, 113, 0, + 113, 0, 56, 0, 255, 0, 0, 0, + 85, 0, 56, 0, 226, 0, 0, 0, + 0, 0, 170, 0, 0, 0, 141, 0, + 28, 0, 28, 0, 198, 0, 28, 0, +}; +const uint8_t node_23[] = { + 255, 0, 0, 0, 229, 0, 0, 0, + 204, 0, 204, 0, 0, 0, 76, 0, + 178, 0, 153, 0, 51, 0, 178, 0, + 178, 0, 127, 0, 102, 51, 51, 25, + 0, 0, 0, 0, 0, 0, 0, 31, + 0, 0, 0, 0, 255, 0, 0, 31, + 0, 0, 8, 0, 0, 0, 191, 159, + 127, 95, 95, 0, 223, 0, 63, 0, + 255, 0, 255, 0, 204, 204, 204, 204, + 0, 0, 51, 51, 51, 51, 0, 0, + 204, 0, 204, 0, 153, 153, 153, 153, + 153, 0, 0, 0, 102, 102, 102, 102, +}; +const uint8_t node_24[] = { + 170, 0, 0, 0, 0, 255, 0, 0, + 198, 0, 0, 0, 0, 28, 0, 0, + 141, 0, 0, 0, 0, 226, 0, 0, + 56, 0, 0, 113, 0, 85, 0, 0, + 255, 0, 0, 0, 0, 113, 0, 0, + 85, 0, 0, 0, 0, 226, 0, 0, + 141, 0, 0, 8, 0, 170, 56, 56, + 198, 0, 0, 56, 0, 141, 28, 0, + 255, 0, 0, 0, 0, 191, 0, 0, + 159, 0, 0, 0, 0, 223, 0, 0, + 95, 0, 0, 0, 0, 63, 0, 0, + 127, 0, 0, 0, 0, 31, 0, 0, +}; + + +const uint8_t* drum_map[5][5] = { + { node_0, node_1, node_2, node_3, node_4 }, + { node_5, node_6, node_7, node_8, node_9 }, + { node_10, node_11, node_12, node_13, node_14 }, + { node_15, node_16, node_17, node_18, node_19 }, + { node_20, node_21, node_22, node_23, node_24 }, +}; + +} // namespace grids + +#endif // GRIDS_RESOURCES_H_ diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 94156b714..24f2b3aa7 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -51,6 +51,7 @@ build_flags = [env:main] build_flags = ${env.build_flags} + -DDRUMMAP_GRIDS2 -DENABLE_APP_CALIBR8OR -DENABLE_APP_ENIGMA -DENABLE_APP_MIDI From 35b076b4c7d0adb450f10decaf7fb4fd0043889f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 16 May 2023 07:19:01 -0400 Subject: [PATCH 236/417] VOR calibration fixes; Hemisphere adjustments Set VBias to UNIPOLAR when entering calibration DAC::kOctaveZero is dynamic now; updated with VBias. HEMISPHERE_MAX_CV is also dynamic. --- software/o_c_REV/HSApplication.h | 10 ++++----- software/o_c_REV/HemisphereApplet.h | 17 +++++++------- software/o_c_REV/OC_DAC.cpp | 4 ++++ software/o_c_REV/OC_DAC.h | 2 +- software/o_c_REV/OC_calibration.ino | 6 +++++ software/o_c_REV/VBiasManager.h | 35 +++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 14 deletions(-) diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 0a02a6afb..a00f6bd35 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -42,10 +42,10 @@ typedef int32_t simfloat; #define HSAPPLICATION_3V 4608 #define HSAPPLICATION_CHANGE_THRESHOLD 32 -#ifdef BUCHLA_4U -#define PULSE_VOLTAGE 8 +#if defined(BUCHLA_4U) || defined(VOR) +#define HSAPP_PULSE_VOLTAGE 8 #else -#define PULSE_VOLTAGE 5 +#define HSAPP_PULSE_VOLTAGE 5 #endif class HSApplication { @@ -129,7 +129,7 @@ class HSApplication { } void GateOut(int ch, bool high) { - Out(ch, 0, (high ? PULSE_VOLTAGE : 0)); + Out(ch, 0, (high ? HSAPP_PULSE_VOLTAGE : 0)); } bool Clock(int ch) { @@ -157,7 +157,7 @@ class HSApplication { void ClockOut(int ch, int ticks = 100) { clock_countdown[ch] = ticks; - Out(ch, 0, PULSE_VOLTAGE); + Out(ch, 0, HSAPP_PULSE_VOLTAGE); } // Buffered I/O functions for use in Views diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 04078799c..b0efb894c 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -35,25 +35,25 @@ #define LEFT_HEMISPHERE 0 #define RIGHT_HEMISPHERE 1 #ifdef BUCHLA_4U +#define PULSE_VOLTAGE 8 #define HEMISPHERE_MAX_CV 15360 -#define HEMISPHERE_CENTER_CV 7680 +#define HEMISPHERE_CENTER_CV 7680 // 5V +#elif defined(VOR) +#define PULSE_VOLTAGE 8 +#define HEMISPHERE_MAX_CV (HS::octave_max * 12 << 7) +#define HEMISPHERE_CENTER_CV 0 #else +#define PULSE_VOLTAGE 5 #define HEMISPHERE_MAX_CV 7680 #define HEMISPHERE_CENTER_CV 0 #endif +#define HEMISPHERE_3V_CV (HEMISPHERE_CENTER_CV + 4608) #define HEMISPHERE_CENTER_DETENT 80 -#define HEMISPHERE_3V_CV 4608 #define HEMISPHERE_CLOCK_TICKS 50 #define HEMISPHERE_CURSOR_TICKS 12000 #define HEMISPHERE_ADC_LAG 33 #define HEMISPHERE_CHANGE_THRESHOLD 32 -#ifdef BUCHLA_4U -#define PULSE_VOLTAGE 8 -#else -#define PULSE_VOLTAGE 5 -#endif - // Codes for help system sections #define HEMISPHERE_HELP_DIGITALS 0 #define HEMISPHERE_HELP_CVS 1 @@ -103,6 +103,7 @@ typedef struct Applet { Applet available_applets[] = HEMISPHERE_APPLETS; Applet clock_setup_applet = DECLARE_APPLET(9999, 0x01, ClockSetup); +int octave_max = 5; } // Specifies where data goes in flash storage for each selcted applet, and how big it is diff --git a/software/o_c_REV/OC_DAC.cpp b/software/o_c_REV/OC_DAC.cpp index 78bf621f4..e5cdb4d26 100644 --- a/software/o_c_REV/OC_DAC.cpp +++ b/software/o_c_REV/OC_DAC.cpp @@ -45,6 +45,10 @@ namespace OC { +#ifdef VOR +int DAC::kOctaveZero = 0; +#endif + /*static*/ void DAC::Init(CalibrationData *calibration_data) { diff --git a/software/o_c_REV/OC_DAC.h b/software/o_c_REV/OC_DAC.h index 765b4e9dc..cc7e98ef7 100644 --- a/software/o_c_REV/OC_DAC.h +++ b/software/o_c_REV/OC_DAC.h @@ -42,7 +42,7 @@ class DAC { #ifdef BUCHLA_4U static constexpr int kOctaveZero = 0; #elif defined(VOR) - static constexpr int kOctaveZero = 5; + static int kOctaveZero; static constexpr int VBiasUnipolar = 3900; // onboard DAC @ Vref 1.2V (internal), 1.75x gain static constexpr int VBiasBipolar = 2000; // onboard DAC @ Vref 1.2V (internal), 1.75x gain static constexpr int VBiasAsymmetric = 2760; // onboard DAC @ Vref 1.2V (internal), 1.75x gain diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index 0931e2c4c..0c0026e37 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -454,6 +454,12 @@ void OC::Ui::Calibrate() { tick_count.Init(); encoder_enable_acceleration(CONTROL_ENCODER_R, true); + #ifdef VOR + { + VBiasManager *vb = vb->get(); + vb->ChangeBiasToState(VBiasManager::UNI); + } + #endif bool calibration_complete = false; while (!calibration_complete) { diff --git a/software/o_c_REV/VBiasManager.h b/software/o_c_REV/VBiasManager.h index 04d4c04e8..69855565b 100644 --- a/software/o_c_REV/VBiasManager.h +++ b/software/o_c_REV/VBiasManager.h @@ -34,6 +34,10 @@ #define BIAS_EDITOR_TIMEOUT 20000 +namespace HS { +extern int octave_max; +} + class VBiasManager { static VBiasManager *instance; int bias_state; @@ -48,6 +52,8 @@ class VBiasManager { static const int BI = 0; static const int ASYM = 1; static const int UNI = 2; + const int OCTAVE_BIAS[3] = {5, 3, 0}; + const int OCTAVE_MAX[3] = {5, 7, 10}; static VBiasManager *get() { if (!instance) instance = new VBiasManager; @@ -88,6 +94,35 @@ class VBiasManager { if (new_bias_state == VBiasManager::ASYM) new_bias_value = (OC::calibration_data.v_bias >> 16); // asym. = upper 2 bytes OC::DAC::set_Vbias(new_bias_value); bias_state = new_bias_state; + + OC::DAC::kOctaveZero = OCTAVE_BIAS[bias_state]; + HS::octave_max = OCTAVE_MAX[bias_state]; + } + int GetState() { + return bias_state; + } + + void SetStateForApp(int app_index) { + /* TODO: something more dynamic + int new_state = VBiasManager::ASYM; + switch (app_index) + { + case 0: new_state = VBiasManager::ASYM; break; // CopierMachine (or) ASR + case 1: new_state = VBiasManager::ASYM; break; // Harrington 1200 (or) Triads + case 2: new_state = VBiasManager::ASYM; break; // Automatonnetz (or) Vectors + case 3: new_state = VBiasManager::ASYM; break; // Quantermain (or) 4x Quantizer + case 4: new_state = VBiasManager::ASYM; break; // Meta-Q (or) 2x Quantizer + case 5: new_state = VBiasManager::BI; break; // Quadraturia (or) Quadrature LFO + case 6: new_state = VBiasManager::BI; break; // Low-rents (or) Lorenz + case 7: new_state = VBiasManager::UNI; break; // Piqued (or) 4x EG + case 8: new_state = VBiasManager::ASYM; break; // Sequins (or) 2x Sequencer + case 9: new_state = VBiasManager::UNI; break; // Dialectic Ping Pong (or) Balls + case 10: new_state = VBiasManager::UNI; break; // Viznutcracker sweet (or) Bytebeats + case 11: new_state = VBiasManager::ASYM; break; // Acid Curds (or) Chords + case 12: new_state = VBiasManager::UNI; break; // References (or) Voltages + } + instance->ChangeBiasToState(new_state); + */ } /* From 5841824735e93c26162550ad7ebc55e3c443472e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 28 May 2023 00:45:17 -0400 Subject: [PATCH 237/417] HEM Applets: Treat CV input range independent of Vbias Input range on all hardware is the same: approx -3.3V..6.6V New macro HEMISPHERE_MAX_INPUT_CV is set at 6V Old macro HEMISPHERE_MAX_CV applies only to output, changes dynamically on VOR units. Previous calculations assumed 5V max, so some applets may scale inputs differently now, even on non-VOR units. --- software/o_c_REV/HEM_ADEG.ino | 4 ++-- software/o_c_REV/HEM_ADSREG.ino | 2 +- software/o_c_REV/HEM_ASR.ino | 2 +- software/o_c_REV/HEM_AttenuateOffset.ino | 2 +- software/o_c_REV/HEM_Brancher.ino | 2 +- software/o_c_REV/HEM_BugCrack.ino | 4 ++-- software/o_c_REV/HEM_Burst.ino | 2 +- software/o_c_REV/HEM_Calculate.ino | 2 +- software/o_c_REV/HEM_Carpeggio.ino | 2 +- software/o_c_REV/HEM_ClockDivider.ino | 4 ++-- software/o_c_REV/HEM_ClockSkip.ino | 2 +- software/o_c_REV/HEM_DrumMap.ino | 4 ++-- software/o_c_REV/HEM_EbbAndLfo.ino | 6 +++--- software/o_c_REV/HEM_EuclidX.ino | 8 ++++---- software/o_c_REV/HEM_GateDelay.ino | 2 +- software/o_c_REV/HEM_LoFiPCM.ino | 2 +- software/o_c_REV/HEM_LowerRenz.ino | 4 ++-- software/o_c_REV/HEM_RndWalk.ino | 6 +++--- software/o_c_REV/HEM_Scope.ino | 2 +- software/o_c_REV/HEM_Shuffle.ino | 6 +++--- software/o_c_REV/HEM_Slew.ino | 4 ++-- software/o_c_REV/HEM_TB3PO.ino | 6 +++--- software/o_c_REV/HEM_TLNeuron.ino | 2 +- software/o_c_REV/HEM_TM2.ino | 10 +++++----- software/o_c_REV/HEM_TrigSeq.ino | 2 +- software/o_c_REV/HEM_TrigSeq16.ino | 2 +- software/o_c_REV/HEM_VectorMorph.ino | 2 +- software/o_c_REV/HemisphereApplet.h | 10 +++++++--- 28 files changed, 55 insertions(+), 51 deletions(-) diff --git a/software/o_c_REV/HEM_ADEG.ino b/software/o_c_REV/HEM_ADEG.ino index 72b7efd2c..78a2db070 100644 --- a/software/o_c_REV/HEM_ADEG.ino +++ b/software/o_c_REV/HEM_ADEG.ino @@ -55,8 +55,8 @@ public: //if (signal != target) { // Logarhythm fix 8/2020 int segment = phase == 1 - ? effective_attack + Proportion(DetentedIn(0), HEMISPHERE_MAX_CV, HEM_ADEG_MAX_VALUE) - : effective_decay + Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, HEM_ADEG_MAX_VALUE); + ? effective_attack + Proportion(DetentedIn(0), HEMISPHERE_MAX_INPUT_CV, HEM_ADEG_MAX_VALUE) + : effective_decay + Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, HEM_ADEG_MAX_VALUE); segment = constrain(segment, 0, HEM_ADEG_MAX_VALUE); simfloat remaining = target - signal; diff --git a/software/o_c_REV/HEM_ADSREG.ino b/software/o_c_REV/HEM_ADSREG.ino index 05735c431..dea6f19fe 100644 --- a/software/o_c_REV/HEM_ADSREG.ino +++ b/software/o_c_REV/HEM_ADSREG.ino @@ -256,7 +256,7 @@ private: int get_modification_with_input(int in) { int mod = 0; - mod = Proportion(DetentedIn(in), HEMISPHERE_MAX_CV, HEM_EG_MAX_VALUE / 2); + mod = Proportion(DetentedIn(in), HEMISPHERE_MAX_INPUT_CV, HEM_EG_MAX_VALUE / 2); return mod; } }; diff --git a/software/o_c_REV/HEM_ASR.ino b/software/o_c_REV/HEM_ASR.ino index 1367dc7b7..d8f2d855b 100644 --- a/software/o_c_REV/HEM_ASR.ino +++ b/software/o_c_REV/HEM_ASR.ino @@ -45,7 +45,7 @@ public: int cv = In(0); buffer_m->WriteValueToBuffer(cv, hemisphere); } - index_mod = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 32); + index_mod = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, 32); ForEachChannel(ch) { int cv = buffer_m->ReadNextValue(ch, hemisphere, index_mod); diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index 18267de23..e51137b23 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -45,7 +45,7 @@ public: // use the unconstrained signal for mixing prevSignal = signal; - signal = constrain(signal, -HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV); + signal = constrain(signal, -HEMISPHERE_MAX_CV, HEMISPHERE_MAX_CV); Out(ch, signal); } } diff --git a/software/o_c_REV/HEM_Brancher.ino b/software/o_c_REV/HEM_Brancher.ino index b2b86c379..0759b7576 100644 --- a/software/o_c_REV/HEM_Brancher.ino +++ b/software/o_c_REV/HEM_Brancher.ino @@ -33,7 +33,7 @@ public: void Controller() { // handles physical and logical clock if (Clock(0)) { - int prob = p + Proportion(DetentedIn(0), HEMISPHERE_MAX_CV, 100); + int prob = p + Proportion(DetentedIn(0), HEMISPHERE_MAX_INPUT_CV, 100); choice = (random(1, 100) <= prob) ? 0 : 1; // will be true only for logical clocks diff --git a/software/o_c_REV/HEM_BugCrack.ino b/software/o_c_REV/HEM_BugCrack.ino index 4a439ec12..3995fbd80 100644 --- a/software/o_c_REV/HEM_BugCrack.ino +++ b/software/o_c_REV/HEM_BugCrack.ino @@ -97,8 +97,8 @@ public: int32_t bd_signal = 0; int32_t sd_signal = 0; int32_t ns_signal = 0; - cv_kick = Proportion(DetentedIn(CH_KICK), HEMISPHERE_MAX_CV, BNC_MAX_PARAM); - cv_snare = Proportion(DetentedIn(CH_SNARE), HEMISPHERE_MAX_CV, BNC_MAX_PARAM); + cv_kick = Proportion(DetentedIn(CH_KICK), HEMISPHERE_MAX_INPUT_CV, BNC_MAX_PARAM); + cv_snare = Proportion(DetentedIn(CH_SNARE), HEMISPHERE_MAX_INPUT_CV, BNC_MAX_PARAM); // Kick drum if (cv_mode_kick == CV_MODE_TONE) { diff --git a/software/o_c_REV/HEM_Burst.ino b/software/o_c_REV/HEM_Burst.ino index 5f35e30be..5144c495d 100644 --- a/software/o_c_REV/HEM_Burst.ino +++ b/software/o_c_REV/HEM_Burst.ino @@ -54,7 +54,7 @@ public: number = constrain(number, 1, HEM_BURST_NUMBER_MAX); last_number_cv_tick = OC::CORE::ticks; } - int spacing_mod = clocked ? 0 : Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 500); + int spacing_mod = clocked ? 0 : Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, 500); // Get timing information if (Clock(0)) { diff --git a/software/o_c_REV/HEM_Calculate.ino b/software/o_c_REV/HEM_Calculate.ino index 2bb5eeea4..b5aaea37c 100644 --- a/software/o_c_REV/HEM_Calculate.ino +++ b/software/o_c_REV/HEM_Calculate.ino @@ -22,7 +22,7 @@ #define HEMISPHERE_NUMBER_OF_CALC 7 int hem_MIN(int v1, int v2) {return (v1 < v2) ? v1 : v2;} int hem_MAX(int v1, int v2) {return (v1 > v2) ? v1 : v2;} -int hem_SUM(int v1, int v2) {return constrain(v1 + v2, -HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV);} +int hem_SUM(int v1, int v2) {return constrain(v1 + v2, -HEMISPHERE_MAX_CV, HEMISPHERE_MAX_CV);} int hem_DIFF(int v1, int v2) {return hem_MAX(v1, v2) - hem_MIN(v1, v2);} int hem_MEAN(int v1, int v2) {return (v1 + v2) / 2;} typedef int(*CalcFunction)(int, int); diff --git a/software/o_c_REV/HEM_Carpeggio.ino b/software/o_c_REV/HEM_Carpeggio.ino index de57b3d19..5bbd16a35 100644 --- a/software/o_c_REV/HEM_Carpeggio.ino +++ b/software/o_c_REV/HEM_Carpeggio.ino @@ -81,7 +81,7 @@ public: } // Modulation output - int xy = (In(0) * In(1)) / HEMISPHERE_MAX_CV; + int xy = (In(0) * In(1)) / HEMISPHERE_MAX_INPUT_CV; Out(1, xy); // Handle imprint confirmation animation diff --git a/software/o_c_REV/HEM_ClockDivider.ino b/software/o_c_REV/HEM_ClockDivider.ino index 85c744576..e3a5b2a62 100644 --- a/software/o_c_REV/HEM_ClockDivider.ino +++ b/software/o_c_REV/HEM_ClockDivider.ino @@ -35,9 +35,9 @@ public: // Set division via CV ForEachChannel(ch) { - int input = DetentedIn(ch) - HEMISPHERE_CENTER_CV; + int input = DetentedIn(ch); if (input) { - div[ch] = Proportion(input, HEMISPHERE_MAX_CV / 2, HEM_CLOCKDIV_MAX); + div[ch] = Proportion(input, HEMISPHERE_MAX_INPUT_CV / 2, HEM_CLOCKDIV_MAX); div[ch] = constrain(div[ch], -HEM_CLOCKDIV_MAX, HEM_CLOCKDIV_MAX); if (div[ch] == 0 || div[ch] == -1) div[ch] = 1; } diff --git a/software/o_c_REV/HEM_ClockSkip.ino b/software/o_c_REV/HEM_ClockSkip.ino index 3d95cf68c..ef93be042 100644 --- a/software/o_c_REV/HEM_ClockSkip.ino +++ b/software/o_c_REV/HEM_ClockSkip.ino @@ -37,7 +37,7 @@ public: ForEachChannel(ch) { if (Clock(ch)) { - int prob = p[ch] + Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 100); + int prob = p[ch] + Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 100); if (random(1, 100) <= prob) { ClockOut(ch); trigger_countdown[ch] = 1667; diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index fb0b68659..00de79163 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -43,8 +43,8 @@ public: } void Controller() { - cv1 = Proportion(DetentedIn(0), HEMISPHERE_MAX_CV, 255); - cv2 = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, 255); + cv1 = Proportion(DetentedIn(0), HEMISPHERE_MAX_INPUT_CV, 255); + cv2 = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, 255); int _fill[2] = {fill[0], fill[1]}; if (cv_mode == 0) { diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 42bbc1348..4b076dd7f 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -30,13 +30,13 @@ public: pitch_mod += In(ch); break; case SLOPE: - slope_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 127); + slope_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 127); break; case SHAPE: - shape_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 127); + shape_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 127); break; case FOLD: - fold_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 127); + fold_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 127); break; } } diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 068cc7a0c..2d6adf493 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -78,7 +78,7 @@ public: ForEachChannel(cv_ch) { switch (cv_dest[cv_ch] - ch * LENGTH2) { // this is dumb, but efficient case LENGTH1: - actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 2, 32); + actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, 31), 2, 32); if (actual_beats[ch] > actual_length[ch]) actual_beats[ch] = actual_length[ch]; @@ -89,13 +89,13 @@ public: break; case BEATS1: - actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 0, actual_length[ch]); + actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, actual_length[ch]), 0, actual_length[ch]); break; case OFFSET1: - actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); + actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); break; case PADDING1: - actual_padding[ch] = constrain(actual_padding[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 32 - actual_length[ch]), 0, 32 - actual_length[ch]); + actual_padding[ch] = constrain(actual_padding[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, 32 - actual_length[ch]), 0, 32 - actual_length[ch]); if (actual_offset[ch] >= actual_length[ch] + actual_padding[ch]) actual_offset[ch] = actual_length[ch] + actual_padding[ch] - 1; break; diff --git a/software/o_c_REV/HEM_GateDelay.ino b/software/o_c_REV/HEM_GateDelay.ino index 930e46a61..32be83ce7 100644 --- a/software/o_c_REV/HEM_GateDelay.ino +++ b/software/o_c_REV/HEM_GateDelay.ino @@ -42,7 +42,7 @@ public: ForEachChannel(ch) { record(ch, Gate(ch)); - int mod_time = Proportion(DetentedIn(ch), HEMISPHERE_MAX_CV, 1000) + time[ch]; + int mod_time = Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 1000) + time[ch]; mod_time = constrain(mod_time, 0, 2000); bool p = play(ch, mod_time); diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 2794f6719..e60e72837 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -70,7 +70,7 @@ public: int fbmix = PCM_TO_CV(lofi_pcm_buffer[head]) * fdbk_g / 100 + cv; lofi_pcm_buffer[head_w] = CV_TO_PCM(fbmix); - rate_mod = constrain( rate + Proportion(cv2, HEMISPHERE_MAX_CV, 32), 1, 64); + rate_mod = constrain( rate + Proportion(cv2, HEMISPHERE_MAX_INPUT_CV, 32), 1, 64); countdown = rate_mod; } diff --git a/software/o_c_REV/HEM_LowerRenz.ino b/software/o_c_REV/HEM_LowerRenz.ino index af2adc633..b4f087f7b 100644 --- a/software/o_c_REV/HEM_LowerRenz.ino +++ b/software/o_c_REV/HEM_LowerRenz.ino @@ -39,8 +39,8 @@ public: void Controller() { if (!Gate(1)) { // Freeze if gated - int freq_cv = Proportion(In(0), HEMISPHERE_MAX_CV, 63); - int rho_cv = Proportion(In(1), HEMISPHERE_MAX_CV, 31); + int freq_cv = Proportion(In(0), HEMISPHERE_MAX_INPUT_CV, 63); + int rho_cv = Proportion(In(1), HEMISPHERE_MAX_INPUT_CV, 31); int32_t freq_h = SCALE8_16(constrain(freq + freq_cv, 0, 255)); freq_h = USAT16(freq_h); diff --git a/software/o_c_REV/HEM_RndWalk.ino b/software/o_c_REV/HEM_RndWalk.ino index a57a9d92d..fb62e73bf 100644 --- a/software/o_c_REV/HEM_RndWalk.ino +++ b/software/o_c_REV/HEM_RndWalk.ino @@ -69,8 +69,8 @@ public: default: break; } - int rangeCv = Proportion(In(0), HEMISPHERE_MAX_CV, MAX_RANGE); - int stepCv = Proportion(In(1), HEMISPHERE_MAX_CV, MAX_STEP); + int rangeCv = Proportion(In(0), HEMISPHERE_MAX_INPUT_CV, MAX_RANGE); + int stepCv = Proportion(In(1), HEMISPHERE_MAX_INPUT_CV, MAX_STEP); ForEachChannel(ch) { // OUTPUT @@ -88,7 +88,7 @@ public: } currentOut[ch] = alpha*currentOut[ch] + (1-alpha)*(float)currentVal[ch]; - Out(ch, constrain((int)currentOut[ch], -HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV)); + Out(ch, constrain((int)currentOut[ch], -HEMISPHERE_MAX_CV, HEMISPHERE_MAX_CV)); } } diff --git a/software/o_c_REV/HEM_Scope.ino b/software/o_c_REV/HEM_Scope.ino index 7fe24c4ce..b4692dd8d 100644 --- a/software/o_c_REV/HEM_Scope.ino +++ b/software/o_c_REV/HEM_Scope.ino @@ -63,7 +63,7 @@ public: sample_num = LoopInt(++sample_num, 63); for (int n = 0; n < 2; n++) { - int sample = Proportion(In(n) + HEMISPHERE_MAX_CV, 2*HEMISPHERE_MAX_CV, 255); + int sample = Proportion(In(n) + HEMISPHERE_MAX_INPUT_CV, 2*HEMISPHERE_MAX_INPUT_CV, 255); sample = constrain(sample, 0, 255); snapshot[n][sample_num] = (uint8_t)sample; } diff --git a/software/o_c_REV/HEM_Shuffle.ino b/software/o_c_REV/HEM_Shuffle.ino index 3114b5c9d..9910dfa11 100644 --- a/software/o_c_REV/HEM_Shuffle.ino +++ b/software/o_c_REV/HEM_Shuffle.ino @@ -65,7 +65,7 @@ public: which = 1 - which; if (last_tick) { tempo = tick - last_tick; - int16_t d = delay[which] + Proportion(DetentedIn(which), HEMISPHERE_MAX_CV, 100); + int16_t d = delay[which] + Proportion(DetentedIn(which), HEMISPHERE_MAX_INPUT_CV, 100); d = constrain(d, 0, 100); uint32_t delay_ticks = Proportion(d, 100, tempo); next_trigger = tick + delay_ticks; @@ -145,7 +145,7 @@ private: void DrawSelector() { for (int i = 0; i < 2; i++) { - int16_t d = delay[i] + Proportion(DetentedIn(i), HEMISPHERE_MAX_CV, 100); + int16_t d = delay[i] + Proportion(DetentedIn(i), HEMISPHERE_MAX_INPUT_CV, 100); d = constrain(d, 0, 100); gfxPrint(32 + pad(10, d), 15 + (i * 10), d); gfxPrint("%"); @@ -172,7 +172,7 @@ private: for (int n = 0; n < 2; n++) { - int16_t d = delay[n] + Proportion(DetentedIn(n), HEMISPHERE_MAX_CV, 100); + int16_t d = delay[n] + Proportion(DetentedIn(n), HEMISPHERE_MAX_INPUT_CV, 100); d = constrain(d, 0, 100); int x = Proportion(d, 100, 20) + (n * 20) + 4; gfxBitmap(x, 48 - (which == n ? 3 : 0), 8, which == n ? NOTE_ICON : X_NOTE_ICON); diff --git a/software/o_c_REV/HEM_Slew.ino b/software/o_c_REV/HEM_Slew.ino index cd07c6138..0f5afaf56 100644 --- a/software/o_c_REV/HEM_Slew.ino +++ b/software/o_c_REV/HEM_Slew.ino @@ -44,11 +44,11 @@ public: int segment = (input > signal[ch]) ? rise : fall; simfloat remaining = input - signal[ch]; - // The number of ticks it would take to get from 0 to HEMISPHERE_MAX_CV + // The number of ticks it would take to get from 0 to HEMISPHERE_MAX_INPUT_CV int max_change = Proportion(segment, HEM_SLEW_MAX_VALUE, HEM_SLEW_MAX_TICKS); // The number of ticks it would take to move the remaining amount at max_change - int ticks_to_remaining = Proportion(simfloat2int(remaining), HEMISPHERE_MAX_CV, max_change); + int ticks_to_remaining = Proportion(simfloat2int(remaining), HEMISPHERE_MAX_INPUT_CV, max_change); if (ticks_to_remaining < 0) ticks_to_remaining = -ticks_to_remaining; simfloat delta; diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 57999771d..6bd541c9e 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -133,8 +133,8 @@ class TB_3PO : public HemisphereApplet { // -2.5v to +5v (HEMISPHERE_MAX_CV), giving about -8 to +15 added to encoder density value // Note: DetentedIn is used to cut out noise near 0, even though it's being quantized to int below (primarily to make the cv icon work better) - int signal = constrain(DetentedIn(1), -HEMISPHERE_3V_CV, HEMISPHERE_MAX_CV); // Allow negative to go about as far as it will reach - density_cv = Proportion(abs(signal), HEMISPHERE_MAX_CV, 15); // Apply proportion uniformly to +- voltages as + for symmetry (Avoids rounding differences) + int signal = constrain(DetentedIn(1), -HEMISPHERE_3V_CV, HEMISPHERE_MAX_INPUT_CV); // Allow negative to go about as far as it will reach + density_cv = Proportion(abs(signal), HEMISPHERE_MAX_INPUT_CV, 15); // Apply proportion uniformly to +- voltages as + for symmetry (Avoids rounding differences) if(signal <0) { density_cv *= -1; // Restore negative sign if -v @@ -207,7 +207,7 @@ class TB_3PO : public HemisphereApplet // Do nothing if the current step should be slid if(!step_is_slid(step)) { - curr_gate_cv = 0;//HEMISPHERE_CENTER_CV; + curr_gate_cv = 0; } } diff --git a/software/o_c_REV/HEM_TLNeuron.ino b/software/o_c_REV/HEM_TLNeuron.ino index fe353ef03..22f0f96b7 100644 --- a/software/o_c_REV/HEM_TLNeuron.ino +++ b/software/o_c_REV/HEM_TLNeuron.ino @@ -44,7 +44,7 @@ public: dendrite_activated[ch] = 0; } } - if (In(0) > (HEMISPHERE_MAX_CV / 2)) { + if (In(0) > (HEMISPHERE_MAX_INPUT_CV / 2)) { sum += dendrite_weight[2]; dendrite_activated[2] = 1; } else { diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 6afdf3c02..208291ca2 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -112,18 +112,18 @@ public: ForEachChannel(ch) { switch (cvmode[ch]) { case SLEW_MOD: - smooth_mod = constrain(smooth_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 128), 1, 128); + smooth_mod = constrain(smooth_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, 128), 1, 128); break; case LENGTH_MOD: - len_mod = constrain(len_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); + len_mod = constrain(len_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); break; case P_MOD: - p_mod = constrain(p_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 100), 0, 100); + p_mod = constrain(p_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, 100), 0, 100); break; case RANGE_MOD: - range_mod = constrain(range_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_CV, 32), 1, 32); + range_mod = constrain(range_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, 32), 1, 32); break; // bi-polar transpose before quantize @@ -206,7 +206,7 @@ public: break; case GATE_SUM: - Output[ch] = slew(Output[ch], ((reg[0] & 0x01)+(reg[1] & 0x01))*HEMISPHERE_3V_CV ); + Output[ch] = slew(Output[ch], ((reg[0] & 0x01)+(reg[1] & 0x01))*HEMISPHERE_MAX_CV/2 ); break; default: break; diff --git a/software/o_c_REV/HEM_TrigSeq.ino b/software/o_c_REV/HEM_TrigSeq.ino index a7ea2d4f8..29557b6da 100644 --- a/software/o_c_REV/HEM_TrigSeq.ino +++ b/software/o_c_REV/HEM_TrigSeq.ino @@ -131,7 +131,7 @@ private: } int Offset(int ch) { - int offset = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, end_step[ch]); + int offset = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, end_step[ch]); if (offset < 0) offset += Length(ch); offset %= Length(ch); return offset; diff --git a/software/o_c_REV/HEM_TrigSeq16.ino b/software/o_c_REV/HEM_TrigSeq16.ino index fa057a695..be7923538 100644 --- a/software/o_c_REV/HEM_TrigSeq16.ino +++ b/software/o_c_REV/HEM_TrigSeq16.ino @@ -118,7 +118,7 @@ private: int cursor; // 0=ch1 low, 1=ch1 hi, 2=ch2 low, 3=ch3 hi, 4=end_step int Offset() { - int offset = Proportion(DetentedIn(1), HEMISPHERE_MAX_CV, end_step); + int offset = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, end_step); if (offset < 0) offset += Length(); offset %= Length(); return offset; diff --git a/software/o_c_REV/HEM_VectorMorph.ino b/software/o_c_REV/HEM_VectorMorph.ino index df748e593..1485e033b 100644 --- a/software/o_c_REV/HEM_VectorMorph.ino +++ b/software/o_c_REV/HEM_VectorMorph.ino @@ -45,7 +45,7 @@ public: ForEachChannel(ch) { if (!linked || ch == 0) { - cv_phase = Proportion(In(ch), HEMISPHERE_MAX_CV, 3599); + cv_phase = Proportion(In(ch), HEMISPHERE_MAX_INPUT_CV, 3599); cv_phase = constrain(cv_phase, -3599, 3599); } last_phase[ch] = (phase[ch] * 10) + cv_phase; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index b0efb894c..0ee305bf9 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -38,16 +38,20 @@ #define PULSE_VOLTAGE 8 #define HEMISPHERE_MAX_CV 15360 #define HEMISPHERE_CENTER_CV 7680 // 5V +#define HEMISPHERE_MIN_CV 0 #elif defined(VOR) #define PULSE_VOLTAGE 8 #define HEMISPHERE_MAX_CV (HS::octave_max * 12 << 7) #define HEMISPHERE_CENTER_CV 0 +#define HEMISPHERE_MIN_CV (HEMISPHERE_MAX_CV - 15360) #else #define PULSE_VOLTAGE 5 -#define HEMISPHERE_MAX_CV 7680 +#define HEMISPHERE_MAX_CV 9216 // 6V #define HEMISPHERE_CENTER_CV 0 +#define HEMISPHERE_MIN_CV -4608 // -3V #endif -#define HEMISPHERE_3V_CV (HEMISPHERE_CENTER_CV + 4608) +#define HEMISPHERE_3V_CV 4608 +#define HEMISPHERE_MAX_INPUT_CV 9216 // 6V #define HEMISPHERE_CENTER_DETENT 80 #define HEMISPHERE_CLOCK_TICKS 50 #define HEMISPHERE_CURSOR_TICKS 12000 @@ -530,7 +534,7 @@ class HemisphereApplet { * HEMISPHERE_MAX_CV max_pixels */ int ProportionCV(int cv_value, int max_pixels) { - int prop = constrain(Proportion(cv_value, HEMISPHERE_MAX_CV, max_pixels), 0, max_pixels); + int prop = constrain(Proportion(cv_value, HEMISPHERE_MAX_INPUT_CV, max_pixels), 0, max_pixels); return prop; } From a442a879adf7a49fea877a70e1eb3f3f198c310f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 28 May 2023 01:27:33 -0400 Subject: [PATCH 238/417] Button handler fixes --- software/o_c_REV/APP_ASR.ino | 2 +- software/o_c_REV/APP_AUTOMATONNETZ.ino | 7 +------ software/o_c_REV/APP_DQ.ino | 2 +- software/o_c_REV/APP_H1200.ino | 4 +--- software/o_c_REV/APP_HEMISPHERE.ino | 2 +- software/o_c_REV/APP_QQ.ino | 2 +- 6 files changed, 6 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/APP_ASR.ino b/software/o_c_REV/APP_ASR.ino index b04742679..33ce712aa 100644 --- a/software/o_c_REV/APP_ASR.ino +++ b/software/o_c_REV/APP_ASR.ino @@ -867,7 +867,7 @@ void ASR_handleButtonEvent(const UI::Event &event) { ASR_rightButton(); break; } - } else { + } else if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { if (OC::CONTROL_BUTTON_L == event.control) ASR_leftButtonLong(); else if (OC::CONTROL_BUTTON_DOWN == event.control) diff --git a/software/o_c_REV/APP_AUTOMATONNETZ.ino b/software/o_c_REV/APP_AUTOMATONNETZ.ino index ef6314143..5a85abc59 100644 --- a/software/o_c_REV/APP_AUTOMATONNETZ.ino +++ b/software/o_c_REV/APP_AUTOMATONNETZ.ino @@ -681,9 +681,6 @@ void Automatonnetz_handleAppEvent(OC::AppEvent event) { } } -void Automatonnetz_rightButton() { -} - void Automatonnetz_handleButtonEvent(const UI::Event &event) { if (UI::EVENT_BUTTON_PRESS == event.type) { switch (event.control) { @@ -703,12 +700,10 @@ void Automatonnetz_handleButtonEvent(const UI::Event &event) { automatonnetz_state.ui.grid_cursor.toggle_editing(); break; } - } else { - if (OC::CONTROL_BUTTON_L == event.control) { + } else if (UI::EVENT_BUTTON_LONG_PRESS == event.type && OC::CONTROL_BUTTON_L == event.control) { automatonnetz_state.ClearGrid(); // Forcing reset might make critical section even less necesary... automatonnetz_state.AddUserAction(USER_ACTION_RESET); - } } } diff --git a/software/o_c_REV/APP_DQ.ino b/software/o_c_REV/APP_DQ.ino index 826f8a210..f2e21002e 100644 --- a/software/o_c_REV/APP_DQ.ino +++ b/software/o_c_REV/APP_DQ.ino @@ -1353,7 +1353,7 @@ void DQ_handleButtonEvent(const UI::Event &event) { DQ_rightButton(); break; } - } else { + } else if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { if (OC::CONTROL_BUTTON_L == event.control) DQ_leftButtonLong(); } diff --git a/software/o_c_REV/APP_H1200.ino b/software/o_c_REV/APP_H1200.ino index fa9e37bbb..c17d6d5d1 100644 --- a/software/o_c_REV/APP_H1200.ino +++ b/software/o_c_REV/APP_H1200.ino @@ -1085,11 +1085,9 @@ void H1200_handleButtonEvent(const UI::Event &event) { h1200_state.cursor.toggle_editing(); break; } - } else { - if (OC::CONTROL_BUTTON_L == event.control) { + } else if (UI::EVENT_BUTTON_LONG_PRESS == event.type && OC::CONTROL_BUTTON_L == event.control) { h1200_settings.InitDefaults(); h1200_state.manual_reset(); - } } } diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 17bf38844..1f7cd5628 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -664,7 +664,7 @@ void HEMISPHERE_handleButtonEvent(const UI::Event &event) { case UI::EVENT_BUTTON_PRESS: if (event.control == OC::CONTROL_BUTTON_UP || event.control == OC::CONTROL_BUTTON_DOWN) { manager.DelegateSelectButtonPush(event); - } else { + } else if (event.control == OC::CONTROL_BUTTON_L || event.control == OC::CONTROL_BUTTON_R) { manager.DelegateEncoderPush(event); } break; diff --git a/software/o_c_REV/APP_QQ.ino b/software/o_c_REV/APP_QQ.ino index a5356577a..c99a6d5b2 100644 --- a/software/o_c_REV/APP_QQ.ino +++ b/software/o_c_REV/APP_QQ.ino @@ -1359,7 +1359,7 @@ void QQ_handleButtonEvent(const UI::Event &event) { QQ_rightButton(); break; } - } else { + } else if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { if (OC::CONTROL_BUTTON_L == event.control) QQ_leftButtonLong(); } From f45dcb515e41777871059b09e3cb46b99132b548 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 28 May 2023 02:10:53 -0400 Subject: [PATCH 239/417] Shorter timing for dual-press Clock Setup gesture via jtomasrl This mitigates accidentally pressing one after the other too quickly. An even more graceful solution would be some kind of DUAL_PRESS button event type / logic that detects when the second button is pressed before the first one is released... --- software/o_c_REV/APP_HEMISPHERE.ino | 2 +- software/o_c_REV/HemisphereApplet.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 1f7cd5628..2bd4c0a8c 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -355,7 +355,7 @@ public: // This is a double-click. Activate corresponding help screen or Clock Setup if (hemisphere == first_click) SetHelpScreen(hemisphere); - else // up + down simultaneous + else if (OC::CORE::ticks - click_tick < HEMISPHERE_SIM_CLICK_TIME) // dual press for clock setup uses shorter timing clock_setup = 1; // leave Select Mode, and reset the double-click timer diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 0ee305bf9..c98e3a697 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -77,6 +77,7 @@ typedef int32_t simfloat; #define gfx_offset (hemisphere * 64) // Graphics offset, based on the side #define io_offset (hemisphere * 2) // Input/Output offset, based on the side +#define HEMISPHERE_SIM_CLICK_TIME 1000 #define HEMISPHERE_DOUBLE_CLICK_TIME 8000 #define HEMISPHERE_PULSE_ANIMATION_TIME 500 #define HEMISPHERE_PULSE_ANIMATION_TIME_LONG 1200 From b89293f4b607d6c895bb3269ec2cccff3a2a30ed Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 28 May 2023 23:37:25 -0400 Subject: [PATCH 240/417] re-fix compiler fix --- software/o_c_REV/APP_ENVGEN.ino | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/APP_ENVGEN.ino b/software/o_c_REV/APP_ENVGEN.ino index eb78b35a7..e4a781633 100644 --- a/software/o_c_REV/APP_ENVGEN.ino +++ b/software/o_c_REV/APP_ENVGEN.ino @@ -131,13 +131,10 @@ inline int TriggerSettingToChannel(int setting_value) { return (setting_value - OC::DIGITAL_INPUT_LAST) / INT_TRIGGER_LAST; } -/* redefined as a macro because of compiler complaints -NJM -inline IntTriggerType TriggerSettingToType(int setting_value, int channel) __attribute__((always_inline)); -inline IntTriggerType TriggerSettingToType(int setting_value, int channel) { +static inline IntTriggerType TriggerSettingToType(int setting_value, int channel) __attribute__((always_inline)); +static inline IntTriggerType TriggerSettingToType(int setting_value, int channel) { return static_cast((setting_value - OC::DIGITAL_INPUT_LAST) - channel * INT_TRIGGER_LAST); } -*/ -#define TriggerSettingToType(setting_value, channel) static_cast((setting_value - OC::DIGITAL_INPUT_LAST) - channel * INT_TRIGGER_LAST) namespace menu = OC::menu; From 924924851d6fee4480882ee6991eaf055d34c9f7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 29 May 2023 00:05:00 -0400 Subject: [PATCH 241/417] ENVGEN: Automatic full-range scaling --- software/o_c_REV/APP_ENVGEN.ino | 16 ++++++++-------- software/o_c_REV/peaks_multistage_envelope.cpp | 6 +----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/APP_ENVGEN.ino b/software/o_c_REV/APP_ENVGEN.ino index e4a781633..9935c5d52 100644 --- a/software/o_c_REV/APP_ENVGEN.ino +++ b/software/o_c_REV/APP_ENVGEN.ino @@ -450,11 +450,7 @@ public: CONSTRAIN(s[CV_MAPPING_EUCLIDEAN_FILL], 0, 32); CONSTRAIN(s[CV_MAPPING_EUCLIDEAN_OFFSET], 0, 32); CONSTRAIN(s[CV_MAPPING_DELAY_MSEC], 0, 65535); - #ifdef VOR - CONSTRAIN(s[CV_MAPPING_AMPLITUDE], 0, 62850); - #else CONSTRAIN(s[CV_MAPPING_AMPLITUDE], 0, 65535); - #endif CONSTRAIN(s[CV_MAPPING_MAX_LOOPS], 0, 65535); EnvelopeType type = get_type(); @@ -573,12 +569,16 @@ public: gate_state |= peaks::CONTROL_GATE_FALLING; gate_raised_ = gate_raised; - // TODO Scale range or offset? - uint32_t value ; + // Scale range and offset + uint32_t value = env_.ProcessSingleSample(gate_state); // 0 to 32767 + uint32_t max_val = OC::DAC::get_octave_offset(dac_channel, OCTAVES - OC::DAC::kOctaveZero); + uint32_t range = max_val - OC::DAC::get_zero_offset(dac_channel); + value = value * range / 32767; + if (!is_inverted()) - value = OC::DAC::get_zero_offset(dac_channel) + env_.ProcessSingleSample(gate_state); + value += OC::DAC::get_zero_offset(dac_channel); else - value = OC::DAC::get_zero_offset(dac_channel) + 32767 - env_.ProcessSingleSample(gate_state); + value = max_val - value; OC::DAC::set(value); } diff --git a/software/o_c_REV/peaks_multistage_envelope.cpp b/software/o_c_REV/peaks_multistage_envelope.cpp index 9bf452c36..ca7d49711 100644 --- a/software/o_c_REV/peaks_multistage_envelope.cpp +++ b/software/o_c_REV/peaks_multistage_envelope.cpp @@ -135,11 +135,7 @@ uint16_t MultistageEnvelope::ProcessSingleSample(uint8_t control) { } else { scaled_value_ = (value_ * amplitude_) >> 16; } - #ifdef BUCHLA_4U - return(static_cast(scaled_value_ << 1)); - #else - return(static_cast(scaled_value_)); - #endif + return(static_cast(scaled_value_)); } uint16_t MultistageEnvelope::RenderPreview( From 4e79090ebeac8819cfe8e90fdec193c8e36fc7c1 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 29 May 2023 06:05:44 -0400 Subject: [PATCH 242/417] Non-VOR build still initializes Vbias to ASYM range This ensures correct behavior on VOR units for all builds --- software/o_c_REV/OC_DAC.cpp | 13 +++++-------- software/o_c_REV/OC_DAC.h | 2 -- software/o_c_REV/OC_gpio.h | 2 -- software/o_c_REV/platformio.ini | 8 +++++++- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/OC_DAC.cpp b/software/o_c_REV/OC_DAC.cpp index e5cdb4d26..5e8620634 100644 --- a/software/o_c_REV/OC_DAC.cpp +++ b/software/o_c_REV/OC_DAC.cpp @@ -57,17 +57,16 @@ void DAC::Init(CalibrationData *calibration_data) { restore_scaling(0x0); // set up DAC pins -#ifdef VOR OC::pinMode(DAC_CS, OUTPUT); - //OC::pinMode(DAC_RST,OUTPUT); - // set Vbias, using onboard DAC: + // set Vbias, using onboard DAC - does nothing on non-VOR hardware init_Vbias(); + set_Vbias(2760); // default to Asymmetric delay(10); -#else - pinMode(DAC_CS, OUTPUT); - pinMode(DAC_RST,OUTPUT); +#ifndef VOR + // VOR button uses the same pin as DAC_RST + OC::pinMode(DAC_RST,OUTPUT); #ifdef DAC8564 // A0 = 0, A1 = 0 digitalWrite(DAC_RST, LOW); #else // default to DAC8565 - pull RST high @@ -207,7 +206,6 @@ uint32_t DAC::store_scaling() { return _scaling; } -#ifdef VOR /*static*/ void DAC::init_Vbias() { /* using MK20 DAC0 for Vbias*/ @@ -219,7 +217,6 @@ void DAC::init_Vbias() { void DAC::set_Vbias(uint32_t data) { *(volatile int16_t *)&(DAC0_DAT0L) = data; } -#endif /*static*/ DAC::CalibrationData *DAC::calibration_data_ = nullptr; diff --git a/software/o_c_REV/OC_DAC.h b/software/o_c_REV/OC_DAC.h index cc7e98ef7..a3a300c80 100644 --- a/software/o_c_REV/OC_DAC.h +++ b/software/o_c_REV/OC_DAC.h @@ -67,10 +67,8 @@ class DAC { static void restore_scaling(uint32_t scaling); static uint8_t get_voltage_scaling(uint8_t channel_id); static uint32_t store_scaling(); -#ifdef VOR static void set_Vbias(uint32_t data); static void init_Vbias(); -#endif static void set_all(uint32_t value) { for (int i = DAC_CHANNEL_A; i < DAC_CHANNEL_LAST; ++i) diff --git a/software/o_c_REV/OC_gpio.h b/software/o_c_REV/OC_gpio.h index 50417b182..c9e14446b 100644 --- a/software/o_c_REV/OC_gpio.h +++ b/software/o_c_REV/OC_gpio.h @@ -74,7 +74,6 @@ #define OC_GPIO_TRx_PINMODE INPUT_PULLUP #define OC_GPIO_ENC_PINMODE INPUT_PULLUP -#ifdef VOR /* local copy of pinMode (cf. cores/pins_teensy.c), using faster slew rate */ namespace OC { @@ -117,6 +116,5 @@ void inline pinMode(uint8_t pin, uint8_t mode) { } } } -#endif // VOR #endif // OC_GPIO_H_ diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 24f2b3aa7..24c053fb0 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -117,7 +117,13 @@ build_flags = build_flags = ${env:main.build_flags} -DVOR -; -DVOR_NO_RANGE_BUTTON + +[env:main_vor_4robots] +build_flags = + ${env:main.build_flags} + -DVOR + -DVOR_NO_RANGE_BUTTON + -DOC_VERSION_EXTRA="\"+4ROBOTS\"" [env:main_vor_flipped] build_flags = From 218a5ff7291cd64b5a68792eda4c5fedc32f9f18 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 29 May 2023 18:22:36 -0400 Subject: [PATCH 243/417] VOR: UP button on App Menu for Vbias menu This eliminates the need for VOR_NO_RANGE_BUTTON - one VOR build for all! --- software/o_c_REV/OC_apps.ino | 14 ++++++++++++++ software/o_c_REV/OC_options.h | 7 ++----- software/o_c_REV/OC_ui.cpp | 15 +-------------- software/o_c_REV/OC_ui.h | 10 ++++------ software/o_c_REV/platformio.ini | 7 ------- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 1aae26830..8793a9422 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -373,6 +373,11 @@ void draw_app_menu(const menu::ScreenCursor<5> &cursor) { item.DrawCustom(); } +#ifdef VOR + VBiasManager *vbias_m = vbias_m->get(); + vbias_m->DrawPopupPerhaps(); +#endif + GRAPHICS_END_FRAME(); } @@ -419,6 +424,15 @@ void Ui::AppSettings() { ui.DebugStats(); break; case CONTROL_BUTTON_UP: +#ifdef VOR + // VBias menu for units without Range button + if (UI::EVENT_BUTTON_LONG_PRESS == event.type || UI::EVENT_BUTTON_DOWN == event.type) { + VBiasManager *vbias_m = vbias_m->get(); + vbias_m->AdvanceBias(); + } +#endif + break; + case CONTROL_BUTTON_DOWN: if (UI::EVENT_BUTTON_PRESS == event.type) { bool enabled = !global_settings.encoders_enable_acceleration; SERIAL_PRINTLN("Encoder acceleration: %s", enabled ? "enabled" : "disabled"); diff --git a/software/o_c_REV/OC_options.h b/software/o_c_REV/OC_options.h index 298f62e92..430c0c4a2 100644 --- a/software/o_c_REV/OC_options.h +++ b/software/o_c_REV/OC_options.h @@ -25,13 +25,10 @@ //#define DAC8564 -// Phazerville Suite includes basic support for the 10V OC Plus and VOR -// -// The VOR flag is set in platformio.ini. The other one is here if you need it. -NJM +// Phazerville Suite includes full support for the Plum Audio hardware variants with VOR +// The VOR flag is set in platformio.ini. /* ------------ uncomment for use with Plum Audio VOR anabled versions (OCP, 1uO_c v2, 4Robots) --------------------------------------------------------- */ //#define VOR -/* ------------ uncomment for use with Plum Audio 1uO_c 4Robots (To use Up button long press to change VOR instead activate screensaver) ---------------- */ -//#define VOR_NO_RANGE_BUTTON // idk what this means so I'm keeping it -NJM #if defined(VOR) diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index d0d4137c7..f4b0ee43c 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -27,7 +27,7 @@ void Ui::Init() { ticks_ = 0; set_screensaver_timeout(SCREENSAVER_TIMEOUT_S); -#if defined(VOR) && !defined(VOR_NO_RANGE_BUTTON) +#if defined(VOR) static const int button_pins[] = { but_top, but_bot, butL, butR, but_mid }; #else static const int button_pins[] = { but_top, but_bot, butL, butR }; @@ -126,30 +126,17 @@ UiMode Ui::DispatchEvents(App *app) { break; case UI::EVENT_BUTTON_DOWN: #ifdef VOR - #ifdef VOR_NO_RANGE_BUTTON - if (OC::CONTROL_BUTTON_UP == event.control) { - VBiasManager *vbias_m = vbias_m->get(); - if (vbias_m->IsEditing()) vbias_m->AdvanceBias(); - else app->HandleButtonEvent(event); - } else app->HandleButtonEvent(event); - #else if (OC::CONTROL_BUTTON_M == event.control) { VBiasManager *vbias_m = vbias_m->get(); vbias_m->AdvanceBias(); } else app->HandleButtonEvent(event); - #endif #else app->HandleButtonEvent(event); #endif break; case UI::EVENT_BUTTON_LONG_PRESS: if (OC::CONTROL_BUTTON_UP == event.control) { -#ifdef VOR_NO_RANGE_BUTTON - VBiasManager *vbias_m = vbias_m->get(); - vbias_m->AdvanceBias(); -#else if (!preempt_screensaver_) screensaver_ = true; -#endif } else if (OC::CONTROL_BUTTON_R == event.control) return UI_MODE_APP_SETTINGS; diff --git a/software/o_c_REV/OC_ui.h b/software/o_c_REV/OC_ui.h index 3aa89159d..8ef46bf94 100644 --- a/software/o_c_REV/OC_ui.h +++ b/software/o_c_REV/OC_ui.h @@ -33,19 +33,17 @@ enum UiControl { CONTROL_BUTTON_M = 0x10, CONTROL_ENCODER_L = 0x20, CONTROL_ENCODER_R = 0x40, + + CONTROL_LAST = 6, + CONTROL_BUTTON_LAST = 5, #else CONTROL_BUTTON_MASK = 0xf, CONTROL_ENCODER_L = 0x10, CONTROL_ENCODER_R = 0x20, -#endif - #if defined(VOR) && !defined(VOR_NO_RANGE_BUTTON) - CONTROL_LAST = 6, - CONTROL_BUTTON_LAST = 5, - #else CONTROL_LAST = 5, CONTROL_BUTTON_LAST = 4, - #endif +#endif }; static inline uint16_t control_mask(unsigned i) { diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 24c053fb0..ea27793d7 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -118,13 +118,6 @@ build_flags = ${env:main.build_flags} -DVOR -[env:main_vor_4robots] -build_flags = - ${env:main.build_flags} - -DVOR - -DVOR_NO_RANGE_BUTTON - -DOC_VERSION_EXTRA="\"+4ROBOTS\"" - [env:main_vor_flipped] build_flags = ${env:main_flipped.build_flags} From cb06389fe4106c3e5bbf7e150fe334ecfa4f1de9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 2 Jun 2023 20:42:59 -0400 Subject: [PATCH 244/417] Inverted encoder config for flipped build --- software/o_c_REV/OC_calibration.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_calibration.h b/software/o_c_REV/OC_calibration.h index 275de48ac..263e38408 100644 --- a/software/o_c_REV/OC_calibration.h +++ b/software/o_c_REV/OC_calibration.h @@ -52,7 +52,11 @@ struct CalibrationData { #endif EncoderConfig encoder_config() const { - return static_cast(flags & CALIBRATION_FLAG_ENCODER_MASK); +#ifdef FLIP_180 + return static_cast(~flags & CALIBRATION_FLAG_ENCODER_MASK); +#else + return static_cast(flags & CALIBRATION_FLAG_ENCODER_MASK); +#endif } EncoderConfig next_encoder_config() { From eb6d1f123abed5df978f7f327de1352ab40fd668 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Thu, 8 Jun 2023 20:30:31 -0400 Subject: [PATCH 245/417] Update README.md --- README.md | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5d7b271c6..21049380f 100755 --- a/README.md +++ b/README.md @@ -5,20 +5,23 @@ Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! -I've also managed to squeeze in several stock O&C firmware apps: **Quantermain, Piqued, Acid Curds, Harrington 1200, Sequins** & **Quadraturia** are all here! Check the [Wiki](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. +I've also included all of the stock O&C firmware apps, although they don't all fit in one build. I provide 3 different builds with various combinations of apps, listed in the [Release Notes](https://github.com/djphazer/O_C-BenisphereSuite/releases). + +Check the [Wiki](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. ### Notable Features in this branch: -* A new App called [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) - - quad performance quantizer + pitch CV fine-tuning tool, 4 preset banks +* 4 Preset banks for Hemisphere (long-press DOWN button) +* Modal-editing style navigation (push to toggle editing) * Expanded internal clock - Note: press both UP+DOWN buttons quickly to access the [**Clock Setup**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Clock-Setup) screen - Syncs to external clock on TR1, configurable PPQN - MIDI Clock out via USB - Independent multipliers for each internal trigger - Manual triggers (convenient for jogging or resetting a sequencer, testing) -* Modal-editing style navigation (push to toggle editing) -* **DualTM** - ShiftReg has been upgraded to two concurrent 32-bit registers governed by the same length/prob/scale/range settings +* A new App called [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) + - quad performance quantizer + pitch CV fine-tuning tool, 4 preset banks +* **[DualTM](https://github.com/djphazer/O_C-BenisphereSuite/wiki/DualTM)** - ShiftReg has been upgraded to two concurrent 32-bit registers governed by the same length/prob/scale/range settings - outputs assignable to Pitch, Mod, Trig, Gate from either register. Assignable CV inputs. Massive modulation potential! * **EbbAndLfo** (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking @@ -35,25 +38,29 @@ Check the [Releases](https://github.com/djphazer/O_C-BenisphereSuite/releases) s ### How do I build it? -Building the code is fairly simple using Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html): +I've abandoned the old Arduino IDE in favor of Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html): ``` wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -O get-platformio.py python3 get-platformio.py ``` ...or as a [a full-featured IDE](https://platformio.org/install/ide), as well as a plugin for VSCode and other existing IDEs. -The project lives within the `software/o_c_REV` directory. From there, you can Build and Upload via USB to your module: +The project lives within the `software/o_c_REV` directory. From there, you can Build the desired configuration and Upload via USB to your module: ``` -pio run -e oc_prod -t upload +pio run -e main -t upload ``` -Alternate build environment configurations exist in `platformio.ini` for VOR, Buchla, etc. To build all the defaults, simply use `pio run` +Alternate build environment configurations exist in `platformio.ini` for VOR, Buchla, flipped screen, etc. To build all the defaults consecutively, simply use `pio run` ### Credits -Shoutout to Logarhythm for the incredible **TB-3PO** sequencer. -To herrkami and Beni for their work on **BugCrack**. -Beni also gets massive props for **DrumMap** and the **ProbDiv / ProbMelo** applets. -And of course thank you to Chysn for the fantastic framework from which we've all drawn inspiration. +Many minds before me have made this project possible. Attribution is present in the git commit log and within individual files. +Shoutouts: +* Logarhythm1 for the incredible **TB-3PO** sequencer. +* herrkami and Beni Rose for their work on **BugCrack**. +* Ben also gets massive props for **DrumMap** and the **ProbDiv / ProbMelo** applets. +* qiemem (Bryan Head) for the **Ebb&LFO** applet and its _tideslite_ backend + +And, of course, thank you to Chysn for the clever applet framework from which we've all drawn inspiration. This is a fork of [Benisphere Suite](https://github.com/benirose/O_C-BenisphereSuite) which is a fork of [Hemisphere Suite](https://github.com/Chysn/O_C-HemisphereSuite) by Jason Justian (aka chysn). From ecc8f8717d41bdf47a4211fabec73638dce1d581 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 18 Jun 2023 07:32:12 -0400 Subject: [PATCH 246/417] Fix for unwanted triggers on startup --- software/o_c_REV/HSClockManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index f3f4c371e..ae7b40bc3 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -62,7 +62,7 @@ class ClockManager { int clock_ppqn = 4; // external clock multiple bool cycle = 0; // Alternates for each tock, for display purposes - bool boop[4]; // Manual triggers + bool boop[4] = {0,0,0,0}; // Manual triggers ClockManager() { SetTempoBPM(120); From 6b455375b922fe58c172dfd59d48e8f90ce58a19 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 18 Jun 2023 20:28:04 -0400 Subject: [PATCH 247/417] EbbAndLfo: fix Gate High/Low outputs, update Help text --- software/o_c_REV/HEM_EbbAndLfo.ino | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 4b076dd7f..f46fda017 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -51,12 +51,6 @@ public: // COMPUTE int s = constrain(slope_mod * 65535 / 127, 0, 65535); ProcessSample(s, shape_mod * 65535 / 127, fold_mod * 32767 / 127, phase, sample); - if (phase < phase_increment) { - eoa_reached = false; - } else { - eoa_reached = eoa_reached || (sample.flags & FLAG_EOA); - } - ForEachChannel(ch) { switch (output(ch)) { @@ -67,10 +61,10 @@ public: Out(ch, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); break; case EOA: - GateOut(ch, eoa_reached); + GateOut(ch, sample.flags & FLAG_EOA); break; case EOR: - GateOut(ch, !eoa_reached); + GateOut(ch, sample.flags & FLAG_EOR); break; } } @@ -221,7 +215,7 @@ protected: void SetHelp() { // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; - help[HEMISPHERE_HELP_CVS] = "1=V/Oct 2=Slope"; + help[HEMISPHERE_HELP_CVS] = "1,2=Assignable"; help[HEMISPHERE_HELP_OUTS] = "A=OutA B=OutB"; help[HEMISPHERE_HELP_ENCODER] = "Select/Edit params"; // "------------------" <-- Size Guide @@ -263,7 +257,6 @@ private: uint8_t cv = 0b0001; // Freq on 1, shape on 2 TidesLiteSample disp_sample; TidesLiteSample sample; - bool eoa_reached = false; int knob_accel = 1 << 8; From db5d27990b2253ef44c1d8f8e7f63a2de6360cec Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 18 Jun 2023 18:38:08 -0400 Subject: [PATCH 248/417] Fixes to mitigate VOR calibration offset (~10mV) --- software/o_c_REV/OC_apps.ino | 1 + software/o_c_REV/OC_calibration.ino | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 8793a9422..654911545 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -446,6 +446,7 @@ void Ui::AppSettings() { } draw_app_menu(cursor); + delay(2); // VOR calibration hack } event_queue_.Flush(); diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index 0c0026e37..c03d4df52 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -550,6 +550,10 @@ void OC::Ui::Calibrate() { case CALIBRATE_OCTAVE: calibration_state.encoder_value = OC::calibration_data.dac.calibrated_octaves[step_to_channel(next_step->step)][next_step->index + DAC::kOctaveZero]; + #ifdef VOR + /* set 0V @ unipolar range */ + DAC::set_Vbias(DAC::VBiasUnipolar); + #endif break; #ifdef VOR @@ -563,6 +567,9 @@ void OC::Ui::Calibrate() { case CALIBRATE_ADC_OFFSET: calibration_state.encoder_value = OC::calibration_data.adc.offset[next_step->index]; + #ifdef VOR + DAC::set_Vbias(DAC::VBiasUnipolar); + #endif break; case CALIBRATE_DISPLAY: calibration_state.encoder_value = OC::calibration_data.display_offset; @@ -597,6 +604,7 @@ void OC::Ui::Calibrate() { calibration_update(calibration_state); calibration_draw(calibration_state); + delay(2); // VOR calibration hack } if (calibration_state.encoder_value) { @@ -731,10 +739,6 @@ void calibration_update(CalibrationState &state) { OC::calibration_data.dac.calibrated_octaves[step_to_channel(step->step)][step->index + DAC::kOctaveZero] = state.encoder_value; DAC::set_all_octave(step->index); - #ifdef VOR - /* set 0V @ unipolar range */ - DAC::set_Vbias(DAC::VBiasUnipolar); - #endif break; #ifdef VOR case CALIBRATE_VBIAS_BIPOLAR: @@ -753,10 +757,6 @@ void calibration_update(CalibrationState &state) { case CALIBRATE_ADC_OFFSET: OC::calibration_data.adc.offset[step->index] = state.encoder_value; DAC::set_all_octave(0); - #ifdef VOR - /* set 0V @ unipolar range */ - DAC::set_Vbias(DAC::VBiasUnipolar); - #endif break; case CALIBRATE_ADC_1V: DAC::set_all_octave(1); From bba987353f0c60b6a11dd0ad432c94b4cb2954e6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 18 Jun 2023 20:00:32 -0400 Subject: [PATCH 249/417] VOR: Re-implement Vbias auto-config --- software/o_c_REV/OC_apps.ino | 4 +++ software/o_c_REV/VBiasManager.h | 48 ++++++++++++++++++++------------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 654911545..605ac1072 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -251,6 +251,10 @@ namespace apps { void set_current_app(int index) { current_app = &available_apps[index]; global_settings.current_app_id = current_app->id; + #ifdef VOR + VBiasManager *vbias_m = vbias_m->get(); + vbias_m->SetStateForApp(current_app); + #endif } App *current_app = &available_apps[DEFAULT_APP_INDEX]; diff --git a/software/o_c_REV/VBiasManager.h b/software/o_c_REV/VBiasManager.h index 69855565b..d697df30d 100644 --- a/software/o_c_REV/VBiasManager.h +++ b/software/o_c_REV/VBiasManager.h @@ -102,27 +102,39 @@ class VBiasManager { return bias_state; } - void SetStateForApp(int app_index) { - /* TODO: something more dynamic - int new_state = VBiasManager::ASYM; - switch (app_index) + // Vbias auto-config helper + // Cross-reference OC_apps.ino for app IDs + void SetStateForApp(OC::App *app) { + int new_state = VBiasManager::ASYM; // default case + + switch (app->id) { - case 0: new_state = VBiasManager::ASYM; break; // CopierMachine (or) ASR - case 1: new_state = VBiasManager::ASYM; break; // Harrington 1200 (or) Triads - case 2: new_state = VBiasManager::ASYM; break; // Automatonnetz (or) Vectors - case 3: new_state = VBiasManager::ASYM; break; // Quantermain (or) 4x Quantizer - case 4: new_state = VBiasManager::ASYM; break; // Meta-Q (or) 2x Quantizer - case 5: new_state = VBiasManager::BI; break; // Quadraturia (or) Quadrature LFO - case 6: new_state = VBiasManager::BI; break; // Low-rents (or) Lorenz - case 7: new_state = VBiasManager::UNI; break; // Piqued (or) 4x EG - case 8: new_state = VBiasManager::ASYM; break; // Sequins (or) 2x Sequencer - case 9: new_state = VBiasManager::UNI; break; // Dialectic Ping Pong (or) Balls - case 10: new_state = VBiasManager::UNI; break; // Viznutcracker sweet (or) Bytebeats - case 11: new_state = VBiasManager::ASYM; break; // Acid Curds (or) Chords - case 12: new_state = VBiasManager::UNI; break; // References (or) Voltages + /* Default cases can be omitted + case TWOCC<'C','8'>::value: // Calibr8or + case TWOCC<'A','S'>::value: // CopierMachine (or) ASR + case TWOCC<'H','A'>::value: // Harrington 1200 (or) Triads + case TWOCC<'A','T'>::value: // Automatonnetz (or) Vectors + case TWOCC<'Q','Q'>::value: // Quantermain (or) 4x Quantizer + case TWOCC<'M','!'>::value: // Meta-Q (or) 2x Quantizer + case TWOCC<'S','Q'>::value: // Sequins (or) 2x Sequencer + case TWOCC<'A','C'>::value: // Acid Curds (or) Chords + new_state = VBiasManager::ASYM; + break; + */ + // Bi-polar +/-5V + case TWOCC<'H','S'>::value: // Hemisphere + case TWOCC<'P','L'>::value: // Quadraturia (or) Quadrature LFO + case TWOCC<'L','R'>::value: // Low-rents (or) Lorenz + new_state = VBiasManager::BI; + break; + // Uni-polar 0-10V + case TWOCC<'E','G'>::value: // Piqued (or) 4x EG + case TWOCC<'B','B'>::value: // Dialectic Ping Pong (or) Balls + case TWOCC<'B','Y'>::value: // Viznutcracker sweet (or) Bytebeats + new_state = VBiasManager::UNI; + break; } instance->ChangeBiasToState(new_state); - */ } /* From 8440e19cef454ea17505f1ffe98969bbf3268555 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 19 Jun 2023 20:37:08 -0400 Subject: [PATCH 250/417] Add OCP hardware schematics from Plum Audio / Shay Shezifi --- hardware/OCP/TH_O_C_MAIN0_1_0.pdf | Bin 0 -> 47303 bytes hardware/OCP/TH_O_C_UI-1_0.pdf | Bin 0 -> 34867 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 hardware/OCP/TH_O_C_MAIN0_1_0.pdf create mode 100644 hardware/OCP/TH_O_C_UI-1_0.pdf diff --git a/hardware/OCP/TH_O_C_MAIN0_1_0.pdf b/hardware/OCP/TH_O_C_MAIN0_1_0.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8c6eff180eca3e43f053c9bcca1f062577755a18 GIT binary patch literal 47303 zcmZU41ymeOw=D!q&=3d^oDkgIJ-EBOySoL?;O_43?he6W2<|qxyS_=j|NHMX}Mx$h!047AMf421NAwgwjP+}!YVO6E>h#)RarZxBilVi39zvfx7q zd`LqGK!`)gK+uER4B&$vf)>IN!VtoY0-jFD!C2qP*5U74!eBX32q_3*2to)I2xABb zu#`E3ErbpDngjd}3;19Jw}rs>>>%91&&?rBAIh*8K@FA@2kSM2pao-)bFejXHZ=YRg#2H1f4ch9CG)=!&24SI z>pOu*_nniGo`HjbfsvV!iG_`xfr_4yjGms10-l!_-q^N)>oZ*jS zD8SQ68ylJH3);F7YW{i7z{<`{$jHRbto^@6|M$&*eMrdG%GN>APT$a&@Q+K$2`SnU z{=pAc^{4qi!2e#G{(D2zLErt4O1l3N_xE*CMSa3Q^9?M@T5jH}$ z{|c$e8dw+`{(S%z`X3Vi4(Ja`O~U_j_J@RjflC?Nm^zsea{Pl;#M}x@G$Eac71)N~ zjSX#$jQ{9zbOO7(HN0E;aff6SGgl>HBVSkU9A!1duyn9^ z8tGs&pmZmDeb@6dl|+h58JS8uLx+z8DrKdD$pN|Kp|QojFIlgzzU?pTZ!aghZ;y9x zpc8`K^{&?!wg&R5%eS@rt~cga2-2k{UoD@fsHV636TY|GtOMH@OeeSPH(j5Aj~NmN zzKQh$MLa^+X;L8D*O#WZ8{M_nWuHK3$g_+xXa|jp^;q%3D$7fPu7g>h7n62*$=T~W zY0)W3=3?fn>{=Nb8S`t(kCr9}Z?92rx6557iEpf)r2;}w& zO-Vv4OlGfuoOqmg91t<3jzBsOo1aKgU4Pl{l(gQSbC1&huomkBZAkv5q7ih`zFt(G zX86iD@a#OOWY}l$ky!bcc^l!cU|)tC+CmkYZ6!H`4^+K4?2?X=aGqOV42D{M6Dv4V zAN*)4zV>HbD4rN?=^~*GY!0-1eP-BQAz+&L(#CH0!}g=NO2EZ8AKBp+KECd9cGD8l zFhS_2Lh(Du8e07MPptQlpvz&P1Pv*B$$0(9HkQdub4<|!G!gX=-rxqj8o%ksNN)AG zwUdF1|!l1O5UPe?Cl4XLE}}o z*7H-7ESlWnyI$OUW}P@WC9^gEBBa9lh$(xQTB6O5{YVNslWLTtm^zma+C7N8l4fS^ zr5W9n#JTwV{f~=9;HC|*3CnW~Er=8=h&j5)c z;L5?KRX(4L6}d!Q!H-hVOMI>(RyfpJ-1C~(qXWx$7QjY!r?pGrWC3Ztnie5XCxhb) zwr$8scH;=JUJ&+EE7oG{;J^&J=Ne4yU~=?q zUE8`2yrbTCysd289qPrFHvqB{3g3~_W$PDu`AI#&_Xy^R_ach8v^{w|H6Cm8h#9|1 z8Zj^psq0kHWT(GoCKfrGK5~8HW~hS@d&PhNt(#>;#-rQ-Zf*$_U&j~}C(I>V8Gd*} z35$r#df^_Ir!ND?KGlViZ96gGAG;f$E}MQXiaD;r>I0Y_o@FXi&q$rr^IV8*V~$g1 z_S-YCrd}oS)uK-k#BPFmTYah9?3LVh z5c-^|E_{^zLk#~u{V6I^)tc%W~Kps5i;g+hwLQ zEfJtSwS48F?b=bmk6^T6J}_lhFkOmK^?Nk_A>UG9+OC1<65xSM31Pa%`#_mfS3GMF zzq{FB$J$b_pfawQB+*i~UvznEb3UB6yRaxVfZayd zw>z+CjyP*g5_?L3(0tbP_I?#LXU~NojG=$X%wM7~JZQk|Eu8onFH?rw2diBB-e}q~ z@Qk&$A(V6G7`h6zbNRifqy`WxpbFJz`+Zb>qo&mNw^|>YhUTDEtOP()Agf>j$<{fDA?qy zWqp{@SM?4;GwzF$e!M(0QM<&BD56e#^1(V$n{_5DK_#(r$IL#nJk#CcJ^tqNb34iC3nRIg-8NK_bh%dnMWF=T`R+d<6a=1O!+EOoQFfiN)s$3LvZlY? zjZye89x1MxkMQH^th{>ING?X^vtR^~HOlz1G!_j@ zup$vl26rCxlPAAK&?3jb4@Yh4IT$PbK7JyTZXD_hij3!MRrf_xg*sH*8{Xn=71;;g zC1+ppIqTSgS5=()+CMjn#`}>Ox}bLmH_}vtoBZo}&%U2i``tiw=^r2x^m?Brk=ppy zuDX>X9}df--g`3;b~tEEyjZyYgsnnPilDRcU5}YF5#(`1iZ;uy!7I%Awb= zqNnrO%UI6+Zpz@vr_rg+M2FF7VYS85IpqFz zJx&FG(#CU&kgdCFjm(*t4mw#pKI1{ig}kpEzfonppih?FOO>bW1vK0` z-8?X5pCmgXb>V*wUb@#7Q^1h9cph(htQlm;_qS%JbDs4i^3Za!K80Wjry~?E&Wi{R zVG2)$za}5#N@X*{7fJ(6u+)OJZIt#PO32e%Hzt zB|`^}<0xOBd5c;tiAA-xA%06&%Wci6NDqoqyETsIB*!!r$!>0+iq$(!QoWC1Hn+h^qb~K+I!SRT8r!#r<@FEntU`ed%yBN^H9S#}S->xfIiUyclui^6RrHWd zgF8-xQ`ED?F$Uj~?QS$k*N58e`=bISo(R3z4L;p13*de1DR zh*;tkdV<(PyuS(gWAkPcqle^59j8=gTLjXDf?kzlt7Z(s1w*0M%c&~HjG@y;$&ITz zB_2FaYND3t&1fUT)C$O&a#FLYs^xxoPdlG3MBw@4N~BULe^-pM(*!d1r6mvT?=aQBw{y7HBu3d3LHo5EjRCFB&Yn2QatRk3Fem+2E+dhZJvhUYBW zM{h>3iQh7x$b7S&PC`o^rMGGh%gVZ z&gb=29StGPh?nUyZ7qpNKA3Q%%E>8C@*aYYb}sp7_;rMvA%Z`RUT4>-*G}(HLOOst z#I-YQM`3C1!fc_Fy7ty4axTu7ZapEj;5jG>U~{^C=YW;N6n@HMb3L_9=z$xI4>!F{ zf2|vAu!WeCEAR-v)Z+{G+~jzw0l9zt1| zq?jZ5%Qr}zc#^+1V&pxWh^qx`>T7`$uqz(pasE6?_zfFlKi3z@RWRXPq#=I#nr?UB zd-4m_UHGTRBnhj>q#5Zq7l{H=={F3GmJo+8If=;9Z+j}nNXeR-H?|?PWzPb3!|sc; z49JSD^J5QdTOU=ESI|g*?0+cpj4E=P1e)3y?jCbI>XmEIn|&G+Q^K%T_wH1we#hkL zFvi}gGx|jCH>oC%JmEzITD6*-6L~w*-dx!688&F?(TG(V#rk6 z>m*}uA<6T}fgv#G-*6LQuGCE&BV&mBDYKMA%Avg0wd2&pLyUuTfu7-g;nY+kn}FD! z2dJv)nqNS{MOpviLS<=dFHf7!{@pXBq-pQ`myd^BKRVp;EhOV8plWmMZg20oCN3;! zt(Cv=)8bECJw&n!-kRJrQSDnM*o&?C@7QU!OWC^7)W6WG0zj&YC%5kn`9SY5%#AT< zpnZTZ-KJg%uibsI1R)O1KE>3QW(4=2L8yj@DY@5e8v1ihlQCH=H$BBoVn-_jYEwhn zb>73k#VcaL+H|C6-X}j@@|S1?cW-!m`8iPvzGzXMpJ8pi$CQq!h6x;D-yH3BhIDwh zTzu{1xcDAFkR}Gk4}N@DQ{X=5xkM?ruw{CHWQ%KYaG5zG5kDxpjMsd^Np@Jf)aF1M zbl^X8)2X0O0#2GqD}5`<=bGe-42!pl*e-#kcDzqymifvjnX-+EusIboMG(#Lk~5a` z6-Vx3S-EHzWhVx-<*z>NEy5ODGS=f%m?@rDjT}=hl)by`Zu<2zZz6~ET#kd=hL{0? zgMQ_Bp^ITmInslCk1ca)~pFD?W;%dy_;|tKe5W9-3DCr>AVTb!jH4@ z?Dwfg6Fq*!Ke*`wWSm;#Ir_>F3{l!8pK`{==b?P1ZdPVz(gx4b7){LzAXM<~Mc$wo z?>_K=+zKF;vRn)3?-IstR=k`wZkt)>T7k}%O-oak*zbD%CeOWxPkwcl#&b+k!F;~> zq6&W|Hs{1cuiGT}+#Ociz?<1G8Lt7nI24Sl&uk1?yMXc$^v?rf;o*+m*FR4yu3ver zK3-J9iqt|n_d}fx`0S^os`VZqL2LwhF4EPh@NJiKt^bBdujFEs#T%U{w9)E%)0d;8 zG}GAN?U7ls61F7fPiU!+GpXk}D}N$kY-%WDP5 zVjJ6%BC9r%Uexk?pSX+sZgYk6;se+qJQ@|anG38>v?*yF6(mNdA6yK`o+{>;Q${^? zt#+iw`DH_18p9>4%?iFJbgNH^wJI z)Ln(hIcKD*12@9ya`xN9hFA(a{T+c!+cKUFZaaEI*j93qaAp6vW09}X0O+uw;w&v66HRo6*ym)dm%7nO zQOpz8OxY>s1HZ4o9I(a=hDrHO4Nf5^#_=zS(3EJNV{g;y5)D*;@EVosO%~ zKL^g)TaSo-KZ+_*$jadPW^R~rb%7*!9aeYR&71g?Fn2uo-puldYd&(!4RhA-r5WW4 z1nUS%=++lg^PKj3gib}ByUKABZ$f%#XDkqNX^d(s2LKu6iLpafzc|*6xfG5umFZ^o z;=amwHoPLW>}tI?yr2g08?Y|lVJbD@A{)ed<+lfkgc0d^c_vxEXa&0qaV7~LjYYg@ z0$<@RVy%)W+%%l7%)b=AJ^x)Aoxv`9qt4+p17>%Y)sdTPM$8Mr48XVl!W8+-^_wLp zso_quHtqQdY?Ix_ifR4h8CINjnz=O(bxe5#(BYKUnzfFnl@&n?iNEzp{n6Yh*loq-{xlXdT0@A~a%59*UbZ8^}Q1{K9ppPNv<%U7_Ly4I7rxTszm zv>dG%<(x!~DOMZKcZ!$uL~`UoWu#ck&7~6&WP07!NvvL{{f$UH^Og31qc6bW6!KSU z#!77LXGC#~6fN01i2dY(%uvcEgq$W>nekm;FP|r2u0c17X^TtgWv;(b!A<~g$-3tvW>qyh%8r5g#Xp5tcO75|jtuTtz7fhzr z)#6n_4fLlq<0gz`)^(=%6=!xfwIWH4inEt61ACVKcO|5mVViV$R6Aj7}5L-uxZu zQrZr1gJJP2H3Dchq0^(PzFad=!C?CDg&cW%0R=7)QW4nl&hMge0`+utMP6e zDUyb(PHpP3S?fOb2QDy#buq=fqX+$VfEFLctLHclMR)yrmVS(y9^1J-)Y)%ak>mkr zito-R}N%!iHksKe&Q7ge*m6lI+7hV~v|Wqs>G zxHaVCPb%jv%BYp9sm97ibCbd{D#DQA8pGG$RYlAA7(p)cCOr;PL_t_ozPPxqQa(kj z2osLZ^y6{mNPHztT(29cS}^Nv@hjg*42 z-1eN`Ldu+R0amiOh@Xnzz^$S+^DKHB>7CyXJEN3Ck6E~antyn`9~J$c9b zBR3)RIX43|TADp;aFYTRyUHXv9+fsud&H7W_C|aIQHpQ1!?J!a0%WJsH?U(ovm~b`2*kqn!NhR9O(PMfTE~=C5 z(V=z*-hNr2u8+`zLaL~qD)%v9jYl=v_8>6D9$R+|XDoWbId^O{W64bBtIKxrM|gJQ zc(mJ)o4cD3(Q`TvQ!rcq(e!C5W`>;xvyix#Jm8?5?U&%J*R zEcXfMHn*x9K!c7x>oPJnfOddJvk`;=yAxLu#cuw z=8C)0mvBNv2LjtYF*l# zK~$6zSr3D&$KU)n1mE|=SWe(}>1`3kY5gq6GwT}`qF zW_(G$g-P=h!4ZsJ7Q@#KfU2LO`bNkA6Q&y`3~>bD%3<>s=(XmddGGl8vg%oWO3No( zcjXyCCwvL|AS1qV92WO1o^%Xx186uO?;sPxNez=@8_4Y}!xKY_?W^ zn|h6c@AmeT*YV?1&hX}ti>y))HN+fu1DlH7CZMCnA3^pw;s;wT8xvpA z^8{0jO<9!*67k;GaGC>h4*6i+eXBld#$s>84 zHqP1CaW1V~e#r;Sc`$?q$Y<$}uGo7CT!Nk-LATp<^;&n5*SN7yEI+jtw|^74N7 zGwMx; z)3VtNq^h!6%|>FRPVg?=LKeE;BqiRx+sKP1q_NHN5`6N6hu%pp)IBDMwAI{YMaT~M zVM6&m+%2;=V9eW~zLUL)!jTDQ3|lFT0r&kr0EGg9HvKL7Qpxe3^R-_jWuh!H}?Sfh!n1FnOKAD8=z!M_kz-MkI-@>}) z=q$4L;m)4J?}_*($FfRZh<8Bdm2_*L7zKwX(au44X(vKu{u*Ws(ziLR9s-OgD5C_A zH%x44)L#2G5Z2Zn$FZ@+&s&Ji+q6`j{o{O|c*Twr97mXTk7FU!XNwxC!mULNqq1SK zZX$WONF#0=LX;>gAD+BNS{B>1G8{E>3c`uFi9u^IEh!HlV&Zwd$98MI3&Od|AKzlB z;QaZX^P?|KJ7Ij`+^*!(P~ zpVM`q#gR%f$Yk3qgO}uwFqZl4>^~?pekJ*|e<{L_jez=4~fP<%b(N z;mw!bezyMtvUP+Q&wj9uEZSQ+@tF8Q5(ZW8gTSfA*mK_9mgHJx4^FY9v}&Gv0V7uk z_)By|{r4CU00i3N?Im^Bs_Sr+W`AX*x0wl zYdu0kLfR1z#vo`}CekHgUGSE-JE5jV<~ofqlU`v}@*TcI{T_nO(3Q^m>(qRwBFbkQ zpw>3;s!)OI`Uw%jtdO5cZQb3}v(=(B5A=Y^jO3&C-h<&Bife>2Q$g^D02_vnqMkOb zqiavg)h7ms!wrG{!>(Ku|Y9?!m%Wz}$@@8EI|6=7-n)_@;$Hb3-g7|LX<2oI2B2DZDiWmIOD_VaBLNHCx z&)fFIULM|{HkIUf)X7 z%=(9C`nEFo{Tv6{QP#n$C5J;>0ypfYy#oNsC$kuNj--kWEE%iKG3!|E0@PII!j_MK zI`C)P6^nFe(AW05c`nGp)9UH$+HE9b!*!+F?5~~K@UOk z=Fu~rq(JYN)^*wse%Iw};FaI$CZu@t&|HQ@uQo4;?oe5_F}>-v`woQwnWR~@>J@jr z(cZ|^BV@CkEu0TAyCrF^voFLaYA!R&+HL49gh1@JjnQCX5J6AYYTFw<(4{KoO`7S6 zi_yU54%Jsg;;Gi{cHSwJyt*~go;$T* z`$SNNPKHL@;Uvm&WP>j&IrZz}vE=M)w`(!Mx%(OC>1oZSNzEH!{Y$z-A!GXeG)?zG z=l1jL8=j%rn$Y?Di4XnYfs;N;)|Pro+Jg{Kc$H_AtaUo`UcFwIu4vb5s?FgMdUAec zjKGdmi9@=nVI`$1Y+}-W%sR;RIN3e3C242!U@Y}%V)Ii~1@M8IqsB0Q)>B&Htemf^ z==r$g@pnccn@5E1wObfNySBtkV3Y6bt?Rp)Bk~8=8T1p^sTa?g>h!U7%UrLjy&!kMlB75cS~d3;_3eZwK+vnHmRuzJa-gFE>1 zvkedHnKS!s&t>1csYQ9eYtM4rx*jDJO$}@#m85(Ng8SK-c$&{~0A+nKlEhR`bs^Z= zbG1E53knjPBkF`iF2XkpNggHRxA=6XT(@H+8F4D#rf05*jaN^Yr89w7+WpyeycZkA zmdI;lIH!VAjw1`2j2@j)`15(#C9;neSGey>99d^sNtPxGWF-J)vfV!|1aP(vo-Uu1 z@39*6rl^-FO9!AGH~RW@QSnbRx;>s-apC3=o(0iz?}k_c3HEULQtM8x7v2XPbZDS+ zK?dZ+7bL9Mvk`QC`?g8F5SM4){#~e)#qJ(;ri{!-?xgaZ7-)*roeI-KwUvHX{}CCE zclMrykOyXVl1F27uNwnIs-H;R zHGzQ}OH_drrO6ClJ6#Zg_M%!aFh#U_i?ZUWWkPW9ZSsN|4F~{*2_HV!?SGuKf_gQu z;o55y6{h0yfw5*lMSgY~k=?yb;u_K$mzI9iu5m`FS1B+@dtd$Dyn&Lxw_q6ZUDE{U zCOWDov1*eu<@2cpENEy?VZj|!Jk7&%rH|+$ps7FXT&?R<*;l*ze#J$+CjhtbMq{yb zFq_9XiY}}ZGkbL6+z7!v^Rj4P+kGe0l;HiL)}83!lLt2AvqlOsr*J7-{m7537JQtH0H{rwrs zh?WBpc>TmBpxfI5c#<4Rcm3x0Q3hAhTXsS`W!tc8dJ$@!_BqDUKuA6hTJ-?9Pmdb+^)+NKjrLz`sU9WaW*|s=ug0y_L z+v`rmL%_&y(J)54x&l?yvL6UqJihe09^;vN2|_GWGgKPv=QpRe_@$3Uh{`{vLU6H& zL$uxjs~J7ID=z);Fa0ncI4fh+H!DLdhJ`+d&v=>5~jpYcMBJ+VnEJz^U)Vfm`ni<?>T=*EP5?sb6zP+pI6psAtVxM`d0m{#^8Tn=L9JRAcb^2J1T8cxchuXN$Vji%{t z+7iTQU!P2`IL5JOoqTnnwtUMRRQ-)&>)P3{N ze~|-!4Dc`)Qv(MXuYMf}z*o%jm(9BkOT~Z>S$SM&R}-srMCa!Rs;# zR`f%?3kOzolxfrW8}cQO#2q5x{6hLYuTZ1gVMt$PAbtxWU)K}| zSAxtQ`ndV~Z`~ts11MP;3!O_bjtYAVuG;}@pc{|XzLtXsI!klw?9a(;aP`lPkkxCk;^a}USW%u;%z^L80y@NDW^elWbG zdmGg*YfHEF`+QlRu9K@+V1^sz?)8UL z!U?>~D@A_CC?y{=N8;IKu9x#Cbf)#})b?S>wL(J#-cA@~@%KMotJjhV(8fJYh(tSU z4I-5Dz3Tc(s%a2DT`5%`$%sYRlK>T5v}^;dCEM6go2s_~xF|h18Vt>L21!#{j0ZSDwJKTQzR^spCV<3CmpIO=w^8vFQgAh|sz=*4x_xGaeQm^AMg6mYu_L?9>V z!}(5bJM9{6@1-QMG527`8uNf<^-nexY5OuotD-3!l&$XHaN2-${VNZCQ907y84)Ck zJADo7kIsV<|Ajx$sYbY4fm3Q#E|d^$3PA2W}@`!C|g*^eNw( z*a=`i@X2f}2um79Q;vUvP!aZjKek1r*GtOrrTVBH9&e=mgN-|v%9MtoXco_fhx{<8 zwmvt(?ppSX>aEuZt!&&xwhb`9ZtbTzqn|S+db2Zcp8TF+_3lx#h)GbIRMiA_UkKDh zJK`HH;ORqB?hNyXr5p2Ff`C)K40srujH;l5qkqFGMMuEV&hc|3&gI3BR284VhE=@c zxACODYf8Pr)NIjXPTMSgn3YR-tD=U%$f7Ip=?iz1r&OKEg5yi(FAYaPBj{+L<+~## za=RlhFH!BC|Aj&xc&LolrwcsF`{e3<_k<}^wVSJ#iFr~DZ|xcaR9dh<@;XVBhturz zfhqw{peDeh2RbYt;PoRh^h7^;@Ki9fFm~j#)bA$acc1MdpUdEmRdfQ_cZ{1sCkrx` z5gk4WBbN=i*Db*$Q_NmU)1DM<`W_L?Eiu285_6d6QPG#N8~Vb>VUxRUc95(ymNqk+ z47n;;T++=J>+7zQ_}Gk=2GIDfpRH>_z8%L2AAmRv`j7TJhr1s2+j9>q8#*%oa+GG) z0jidJZT5rm9sB-73{5-mt}?EmsGuZW0%lBYHyGf;WSb@dvvTF~m!&80Pb)vyJ1|rM zZnZ{`nV)Eeqa`$-vLDzv1dO9}dcohIJQVesF6Wq_KApOAH3BsZLjZ{)ExO?@UKQQ! zgs@o;UJaQU;$9%}<(A{|^`QqyO?2a4r9HIB^=qpO_UREXp!dqPfm@at6}WVhSpK3+ zx^&g0919b9mb=?>p|N3u5FQ7EJa!mUmPKeMtgmjXq^ibB9t}^`BTe!FOd@D2I_Y0F z3XjXlX!HPKqMtjm@{WCe^fi-Le0sm{&4xR|u8Z?v(q@tVw?wolJXU}p((uKd(|rrmEXT!}3^a5oQe!irpR}?M%UiPhVL+ zUL{)d(OyYIp0a`+9|r4kT#N4DL=rcCl^T2fijfTgl7S zsN^ARjBoSTcy|crp`>?@@PmRb+!r{R&e^7zs*VF7xaQr)PWfyT-P|n-*$y(c@z|tG z-5y+f5L-MsqVoY&>=6*!O;Da{f1gvGfMv~+Gc_jV?5)>#-54%fv0wtY{_4Yd@jllNq*7%vv?D-Aak4p1|cBH~3 zWSbSdEo0K!ZL^E?r|G#yZv)hoM<%cWwuyyKHluZgTw5<+^Yx4-HZ5now_@t3iEc+0 z;!kH-Kc@VOJekbONVQ^>_IjNeX}etb_TgQUsW_;g`rw9M@{utBTSFdxg>&5~jOCwf ziesvZCY_><7xMExJjzksz1Rk?SG$8Hat6W*O0rOg1WwvReX1%iWU^4(hw4qleSfqE zT#XdjA4v08EZ}}V=J6dcpuj%x<9_eTCnuw8;ryxfxdE;J))?h z92hIyE-6c7+A>z4MUTCs=P=t%fI8qGRykjw-amQZ_ynr?sm_q?zU;>x{=?_|;vYU$ zcC5jCQgMapHY>=dfmF0a6RO*%5Kfs8c&Nbhlp9`=XDgbHz(iv>|*$F}BL-nK}xP-D?7rEoNFDwg^AIeLGH&h<>eCVnE z6!UEIsv;N8;&{FKC`P_jO`D=Wm3=;yKIF%C5PK=$oi3E>R9W^+i-Fjky23ifTPrT>rJnNf z^!|z~X_hjyW*L*9wz+6I=ADCpmuC;82Vzo7#)|ylo$Y%CA(v)-0mFev_Xq;!{2w?f zJAd4m3dx+)&cylM{I7C5CY;L@*$A(=;AhmhhOilAC-$Z2cQy+)a5}q4e>H!0 zmtTrj1w;Je-Z=LQa9vB(r6u(GzGe(FJkRSI0uD)?O;F2D0K7;cwI5RK3_b$Ku)Gi~ z;;zBv0Sff`XUiVxt1+R&EvfYhI{vP_ytn6@tuQaq(cPi}`8bQZ<0vEjL%9Kd01d~E zzNp6}Fgw)T9UGi-vjtGU!dVmc`3t#RoD&rM&!+n+J^F)Q;XoviQUj`dj{D2O9l0V^9UM|AFUR0cE$lv z5NDQ27&YAcBdb3a1{mo7+Zo&Ma+!@k;aKaywjqv=x=~G(>eC74Q}P>5`>exsICgCx z-jOue!EhUQdMM-MN zZhSPjU0vu=XgONto;(7_EA4~TZ6^5iN9n(&kJYGgJL@y|PV3SUdj0u}Ge5~8HCT#n z`#sX)W~B8Wm$HNddsigb2NwSNz=0bN+PhAx;j^jed4rY<{LoD}dG4%hK5M=eG{dg` z(e+EXNyFneXcCCo23r9`UaAvwYS>O6ZJPy^X!~rq70#?V@~eOMppdRmtI335!meu= z5I(!Elzrn<*`jQUakthIo9u`4fz>(c^N7ZGE8E7K=uv_?TEw5`s&ZSY!7Mn3Geg*R z!_)>jw{8y3d<^!WZ}#V|W#|X*6cpJF7s>5d{)s@G`kVGx|Z9eMa zhw+AhZRg#~*cRu4PBNq_@<-ECn#(g?$8DFN^6E82^lW=;bE zx6K_C95Nn8pvLrwD#~PU@tCa!0o{DNr9+g>6;>LnN6iQ4RZ%mH@VQ|JsYa4KZH4uc z$#_LvHO<*;bf`Lq@RVJ7-xP&9V5_vH^~#B}XUKd?>RfeT4Jdp)j*DxaP>R8RiLa$e zTFn6ldhd>+s>prqAgfDB8s&B5%Ep8yL(*W{UUQplUPBXmt|VJ00MK)ft8rjRWAMs) zl;di&6IIebZfwM>03zO>TqZefyWZ_gHKpx3A970f63)C~x2Gfw8R5q!6WR3Ne7rod zANVz!da=+wyqZIBTd|tm_O*~)#Eh@lzZD{ru)d%pZuwzMUT=6_2J9a zVrqXx=(pSl2_Knt2`8rGuca!#v%mdD)*ey4$)uc~z45SokU(+2PSq%~Lp9b(iUZ;i zkBXaDf0eCl5Jh)~hqefE|8c{s(F4y%Pa#7ewE_->P_~$K2zAjll#a7LlyqMoj!9;E{K$XZirXrad zk?Va4uoTb<^)YvysMidi@J9R)S^N7dTv4Nm=BeZy3p zaZM>E;|=RjGa-rC8+_^4AJM4vBovZ?&r}MmV(XVs(F|Xh1eh4g6lWHi_N63ZxxW)Q z#*%;6j4M`pfoP~&bR(thpGwt$CXFoepAOQ%lZu)ylE`&zI5ab~{#fB|!Nj0$UUE@< z2^U0i5HJ-N(;TO1mCV&LFk7h>YByHgi>Iks{!X4gh4zcvv|c5rZ?tAlLCcCksl;Y$ z;oKlZgz0p{H-i^K9C!*%a~L!sLrYY(Lw=Y%=LXINDs}c>%0mj`BmPsV0M5C=tA}-} zL}rnfCtKf#q@i(QWh*X;?FiC&sd~xy*V&GZfU=HE{2%1K{J`&Mw&&WNJ+6>&>m=zj z(*3}&Bw=cR44T8a_DD&&oH33UGYwvAw$Oeoa!wd$Annn|bQ|aQ+DjCx{TVaTYZSI- z)`;3$58PUx-Ajey9xmks4mUF+$C}i3{g!5SGM5&hvzFEZ;n^|VV_3Fif}t!! zg565fyePf0OS*uNX}XZxIlh8=$0`yy&8#nca=&6MVL1>|9fy`3z&$PuK-23X!A2sX zU>$>sfI|ifh1R0&GNX-S6>$Xv@zA~t`^P0?agxFU1muxN9OUjC6j}|toe;4A3>~l( zs|=0vgaurKH`w`3L*M0PsCU`nBDhjs61cv^wZt*}IrY&PhZ7VVcN$CWY7Sl3KCSr~zV&$(GBqWDXxxqm)uLk^L(X|N zE^bc*&$7!pC@@l&`rqxWNXaFSu@k%QelfHoe?y7OWA$7UU^8Ox;a04*!gwJ56^Qr~ zIWb3Qg+)-+==>{yn{pgLZ6vHd(wuM{Y;~xI`xN8UnQ3x=9w*Y6>@223oT1{!*$n;7 zQw{Dh2tsmNW|VSXf1n3d1nM^f>^+|$NT$dxcRK%Q6U2RR~VDmd&d zzDy!A2vr{yQ97z{CzzUGF{LNit~ymNxL>L$$*Ca$Os^J(=&1s6bvqZ5-{}<837;78 z+H|M}$ZON^&j+0ZVH-{?o61s_dxo}V3|zEbc&UZ2jBJEvV60+p`|i4+DhKmlOG_)E zy6w}wc70|VG>>r23ES0GEFcTaKIu0+LxruaDRX!5XhKELI@EJ7z34F^*FxsuJDgJ^ zJ5Hk75A)axo8bCtGpSd9pPGt|9CA%iT7d5UauzScfOg~}uV&}oxpiJKVwKVPnXF)= z0h!b>UfmKVXQ?_UaQG2@W}WG`w@3mnwD`LW%&y(0FWqmg^^5p&JIjt!_>#m|ERBa8 zc%O#LLm!^%A62o7ER|w1U+b&a1s!Z$V04Qz>@UDVI%y}24Y9Ebq5_%x^TbO)4EZAA zb@%rNFX(5YqVR+mo6l!_+EO(uT^!o1Dy;O5)2&Vq2Dvk-D4F8cn6d)-V*q`n-wGgO zFN+}z$rGjgUo&eYbqjNi_@x~6y;*MoE79sws2 zT-~c>?#*CRV9Y_og;+&96i_GOL!^oB)Y09EmroVtm`w~TU_i&L;REnQ!a;*5Z8$>^ zgIg0N50|o}#sOv!&j@V~gj1{)GCRN~P1}K-?Y~kM{>Q`Gq2f!|NsvOXwt2IS7o5M)W^; z+}|+2i!KQ}Vn$BHHO^i8by8Bi4-5l89!l^t*;x=Avb++}FOB9`=wj9cbT;dBJzn{~ zDHN?yg^XV-?cRJiCwq=%o(8_;_xH1iQm+6QT-_K%&vH(~clYSlLDkQJ5aZh-!W! zzVbR0@ErPv2!tu^l$eqCj5Z!YrVUzg=rg*6Uop?d$vAHcY-=k*56046jumi33cQNh z{x_NxJbs42EAv-|A9k4{xVwny6FVk+0BJi-bqU()qAFs_rxfHlq;?XcK}7a&KV-yO z_JG+)Q1$rJhH#@@mqaeYoej%)SZ%Tskfj2Uqlgf6?!iB5VkR(u1MOsxI93pKqgMEm zf=-S_&pH-!rXGRR`t9-1lzLqm=DKcO?YD2*bx&_AVIDo}PJqJSry5ccTo$PTwR{Bm z7?tC+pkQ2w7wv(^2{$G^=fwF)v_~rk1~PS=`i$S9<>)VnSv$MF$qTRK#sv#&0MUSJ z5wQQ-R=puZo!S!VMn{!7w+2uV(0skpHBGCfNuVsaP3YZz+5}ts3(@cO7fWGycLFt9 z(Q*Z8M)7MDKq)%j@n%4#9;*;nfq&GGHiHdGO^>ogAWTW4j6O@p@J=l;ngkB+0zS~F zx!gUO>MA3@D1!CUd9r;tRN(e>>sYUrW{_!+uD3NmmuDFx5m#{EoK~npF_NDJte z@bIsmq1ovNkN+~BW-)!EM)(pUk-$Y1hgi=K2&|Ys!5Y1mR=Q#igDJy5AN3wr_NP}@ zZ!KoNzRz>@y56T%tYQZ7HkfW&jW|iGERKV5DRp;(HP(?%vyI~-=j&mB)YYLV#K^Qr z1AEJg3v~ta&Vi|fpK;!N9wq$8{@NF4G{qJTlIL4-3`5r}EST1k(V~WPvRf13qFr}W z3HVcD+i?UmMo0eI;OHhPVl@UNGQ903wiF%>4myE08jpFfnCCw2W($xm8Z$I?S;Ib?3Ja^o_ABhLXjx@u zu7GBOjcVYNIfZzoHQFY7Y@dg=a#*;dB&I@grG+mu`_lvG)0qbiWiVwG z4xAU+MdTFFAE;gb!_W_h8dd?l8JN3gZyW2$j+jXy3e<{KR@_tzE|3aS?tI1n)84ki zLJ8x#)&c|tu*Cca(0(gD*TIYIgwG4?8m4gOk_ySYWeh%mpDXEmdXY;GbTG?2si==_e)l3D#lhJV^8w8#tGa2GM~QH&IydLz&E(Gjid#^VQ@6 ztk^QqMr5%Ero6tX`#;{3kuF>fMB7=}=%JM_bC%v%ZKRD7L-W?o$K#uJ##ewHqiP7& zkcs*EBoa{qqxIx7h;KxKO>r5Pj%2JREu1-xYdA^j4Wjz%ih2}Ua8-W>a#_0L#BIV~ z8>4%a_@zE4ihYhtEe$KtEiWBh_%+m+*fu~$vjpl)GTSiyq;KlpU)5@zxTQ_09YGCi z7T$68vZ%DOw?$O2mup;Fhq5W(OJ(v+>)kTfkGgs= z{Fq#0kqo3ex?#@$x(k76|tOy3-FuL+jql3ptmiY<{LefAy*;hVkB~C=JkqZE)_-Max zwG8_GLFDTogxh99-2qgVS#S7MZUVtvi(ksI;!x_HN){grUsq!X`o&b@GQ!l-^={SJ!gs@5}f0;+V|w6?Y~CIRCo4Y(7k`W8c+H zn&PrFA}H5#QaMNuY}Fw`>N_L5aLx)ieMybiRz&32M~fHD3_eWX)Msbc@|b*0Sp3P{ zC_t*P^)&{|#IYiJ)8t*0oa8v+V6}25q14*un(KdQ`07~E7Ox^VOp6n7d^6iVf;0aM z^UTT7ATLw&-_FkAT75I)#>BOJa;-(0VJa8c31Q*iO`uXtTgaqyr{jz_u*l&$Y$&ax zed#sqiE_{U-V+!78c|>JcFdjL6WXJ}BW~_!GMI7_DW~gFwAo2}mdg$56OCTEYrqqz z+bru+q4)5;k-NB>Hjr7|PH&`R7^J0090P1ujxg zndnEDM!IajH7U+^j^Yo!xW)=qyJ;()_HtRmOe_1MVzsPb*($dh!F3FK)>j_tTrl7D zJVj8dajxmMZr)Q#HK~R1apamq-ouEz4hLTQ_7mOtxX!E_9xQ0v|D@St<1=BUEGaj1C!8% z-lax%NVsshRA<~~GI*Ce+^62IU1(GIE9~i|az9F}3g=ontyNA2P8P9pFLLs`Xjl{0 zuT4)P-RTV72?cT8$dsxjtVg&bwc;J+3F08};RZ4|9N93JbgQxeExoXbPrYUWdTax%75gjV(wrSWmx`rg<=$zLTS%iwzymHOt>JA@# zo8aaa;B8!2S7(VlC-Q}g^{m|0IkXk)x=LfQ5Y6uxhR^S(>CUswJZ3tFE-+SRL>1~% zGp;x@;5=XnXKaIM{;+jY9MiQ{>Y4VC^JVIJMV5YvZEoeUZ`0Q?NrjgLzGCSnNKY8| zd-C@PJj9FPUQR8F1`qs|8vaOc3A)qZcKiDd?R*n-d9uZ5tLq3)N=Yx`ACyWcY`-Coh-;%17Y9w#f{cH-ARfIuo^R67bS%+`kJA(KoX@ejjokFTrQki-RC9~^JjyK!%vYP~Nm|Hmf)la@ zzH{|0s-bAJ?gs1PmH7L0TFUk%me?WI(41>tf!;%%QJmFGzlU5~$wMn5Fi={~sL4O` zzP~w$98_84EM#w?alA;}=S`ryMZZ0|!k>aHaZaBB96&AR=xN&T+1w`Th!~7t6YDxn zj~*=Bgcp5SKxUc~#D75C@N*Y#U;bR!cXNwAk}}8-7n-A=%i3Lx*9}S^fxM{glHhW9 zEYI-NCMD>^ctY5S4m8^aYY}z;yH256g@7G5>wrb`h6Kd&#;>ZMxNrN6_w6QVH9L{r zMD)})z4#PQgKbl5&7P;^;0OgmsZ{D+o|m7=FVsd- zN8J)eamszWsMH~FbjFuqjXd?6#JFTF+HsObkRNq!(E7K?r#_7e&IHv8V2E!8v3l;*A?CH@2lOnXYq; z=JUTkd0T$SbNHO`RYw8dVS7rpu5jf(c?PL7;`V)Q1nM^QXDoAf-@&vdLM!RPbO_+Z zdvkq1UTf_@?PUhsdV_b$lZ?w3%LLfVpA@&sjw*O88ShZ<^&XnCZ2}WS69s-v8ph?@SsA~$j$cix**~-paLlMpcW$f4 zefXE1P3YVs!kao&PM&0Sw0e~bcpi3m2>Z8G*l*rE(*(&P8SHYLegiIty6gtURC@kq zqcr+GgWb&nv#VJEt+n6cS;eua8V*+mNpU_)apIVPWB_r5;go25bH^8*rli*KHf|c^ z_YKN3PUh9$pGZp;j|JH+YR}Rez^7th*gt{IOaOjF(S+gIbU3!OXK1|XG!m`5HTGkO zL=K0^5j8nh@Kuxo*me4KFpZdAr>wN_IfA9>NTMybw`25EJo}P383LPE3EEFfB4p1j zGWsux48oXeU}-$`;2z=SmF@wIjpoKOiI~QNORRFe^l3q(xLn70;+o;cSO+YKo9Uz9 zyzwf{Jg{^KG`_5XsV6cRW37R0(l+X$t9x${hqp5HVgG9Ht|`Zixsil=W2x?Sq=4O`gs{%3@ZW=1EI);I1QTrEI9|zJ;GwIke zg)%AstFLw+2T6oM2`j6i<$-{F-%%$}Bl1iGasemuYuYo=)RbeCPckQ1t}z8%MHDvaQXfvA8NVQ!2%9AX>dt;7J3F*AwM&(kDXH5 z!9smtN>E<`X*O{H;lgaRhpRwgZ7EZSED$qw;#AKN<_4-O-Vc$sItJ-k?PyY64dO@b zti*&FQ7R95w-L?@>B#&jGx2v`Q|e38S)gD&OR_>hQwy(1X?-Q+2|Te=KdgR5e1Cu| zSbCLDXf-=}?HoIb5BkWl(6oz|k*`ufHDM6aGAn`l4^im9vp4jf-44C%#5Vn!T0yAf zt(~|r1-l4vIo6KLYaFgX?`XOHU~YiT1{oM|WpP{7arZ7 zd@E?>e2&7woyRw{3`Kb0ZbDerAvt)u<+fGsm7u4bLV-`1urrRemqSc&c0~6|6eqlF z71fMZ?&3<`N>cfjuiR5QlKE;QRu3tyaArKz!h8(YjX?^w(ics?tAh1%XfYwOL}xq@ z6vw1JkIY!c{wIs^j!NhY)n~tvD>ft2qd@}W8;j>hpw;~fY{01+*7nosS|IrS*YzCv zX1#vM%$z`0Pr$R#x8I)o{XNwlRq0<4tT?9ml|oZUR6*K?@bPe6xp`Qr$j~h^`96&y zTx-@%<04~ua_}?N5-Kgvqj)l(kR~gEI9QW3&Q2?w_MM^1jf=>QK$N@W&}2N05CC#}Hc?gJX$2IGNNFo3G)- z83$KbuJg9GnVeKo#~9|{)*t>CE7Wy1lt(PrBJ0V$!_k}=`4o%m2k-@lN!V}EJpG)< zPA=w(B4svjbjRlFe9Yyn1xwn@(50fo3ZXFuBnPr2W?G|OfCLrV!}iJq;Q!90f74&O zBnO6YI6yEFl5~Y*I6E41Wd)M*!@is#^Kr53_h9AG0!~a;kI)#p&=>`h_zv5iXJbV( z;pztCI4n4vhcwaS1=m!bH(io1=0!Di7%oLy8O3cFtfyrOYA(i#5@lHiG>4+VdRrAH zY$VH+qM*Rx7|W3?oY%9A=+hkr$*1hel2EP3^3<0mfM$s*0<|%VahK7x?EB?7KzVfQ zVLBAMFrnXp=FqL7p1sfTAIep#sA4mo>tvcJ)B=W!Y}_QvePF@0wna1AaC*BB3e7TXs-p=F}oeeqey*tJV6`kRhjUo zO!UcsYl!k|O#69$~4zqDbj>H!TSdkE{K5C*y5?-4yDElvM z5k*oZ30DZk?-WI%`Y$(1|XpATcUQ200tPLCw1@tinxf-EMd66=$IS_soleHG(uRT`aaGq61sWDlR z;MRc3jonrq9h=e1D6YeF5urRjr3=lm(mKZeZz}5M=dE<1`c^{IIl}>(X8rgmaBF>O zqSM=bn;`9)qmY@@ga1)0&^&ls|I|A7f7OZv6KZFyJUR@#0hK--x3CG~3P^xwRpHcv zh$JLvw&^{#^)AFDs~GGzlEr~Uu9zsPygF!6$ki0K3yYKxMP_?cYo1tj66qjhh7g+ zN+301H5SM8AJ9KE$8hZo$K>IT4#K~nqL(pC2zvUDpvf}G-aC@o8d%D`f-UNP4&!Hb zEdjmnKMfV*s}<+~maB_&;9|f&A{Ky7n$IVA0@%EzUR*6amNL}3`+?i$Gss-qd~ODE z#{VidXCHw9#DRs1$Ilf!ic7n>miX!vkx>YuAAfHKb1iw0Yk_gwSj9>5IBSjW_;MF9 z?yhIr@E_V#!0kyCFXjJ|?=^fMkuhM$RMF)HYk0%<*xbCf?1$+z()KPj5&ab~0*O>6 zIII0_+(0)1I)@>hc?ukl1MT4|ri(t!-?UqEl+P=;*zZ^KidVi&$NH>w*%hZ7^hU^* zz6F6Dy5pexEh+q}g=+0|CBiBIG>O2eGQY~R*3(pCeU2~vJF{tk^k>$n!#YUiHjhfZ zHMsfc)_G4j0y{8}J%g&|szI*e60d=#f5rv^yWQwBLWGK@eY6;kTn5M?R%~}{cSbcA zdoq3rRV6tn6CJKi0LDyU3;w6j6INGc+Qai)xwb#!kDDn^vR!dBUu5Aa!?Gzy0CI`l zrP5i02s3r*MlBl`Do>kL*t%OA5=Wb8&VerJdVObxC9ZKyUZuVX!1om75*}YP9hA!4 zMn?G&$tn@O2~A*kmguRXKBnk#~(?@XBmvoqp7K1!HWmyzHE?8%Cd zYv9+`pqzs3<&>Nb-g#q(>?hN*Y3vZhg%au6*;&1~(I|g$=Jjkv;0|;c_F(~I4l7j^X4q5E_wl_A~ z&J{go3G;rKDlsAtw~i~TmpZR*8y&eP;`?EE!tNd-5lW*VU*Psez<#1ZQW}`QAiwM% zAkL=28_<-(Nm(ke(Dv#(f|V7h1MUw=FI13{P!i z-fO5(AqCBTWYrsOD@rRj{PSbO=;O#4Ug|jlk!uHZ-KFUTLu@eHEf)Y%s4s_3yf?qa zX>>6AcSOox(Kd!gL{*O1C#5e72X3WzAIW)GN}fL0d6QHGoTjt+CTEy<(1wwZ_%Kj9DCp96Y1u-Lc$i=9obLupq6ZB61@WsUKd?>Yyla?1aTom{=NSzxs zol~c5A<(bLA(-f9^H&0UVp|Grc@1^6>}=0iKnh!{8WM4~?+^lDs5Bno;bK4+zhSD~ zKr~TAv#2kgn9qVPA%rHF#G88}yfB64zbCuNu@OX9o?G6lZUAW95oph|Jv{cw1C#cZ z{;D{OZ`8j7CfZsm?=B{8?T6!1M!Msrdo>l%qiMBZ>Dy-MTGdU3G!vbvv$Bc%5$LIm z$J?%8V*V{`X)o2AZI9?^gu5_kBG=exSCc>e646>urs!a)3dmGC8eCWZyUwr*Z7>x3 zN=WDy3JshQ20n-^@->V|?p&!g_Lg4J_Q@^5Gz@E;{Z*mb4aTl!|HSO5_o1Uz?=n){H< zL-2)~JB48;>Q|81HZ0PdXpw6KmRGLWw;E*#l|q;zQI%*o2@2YuB=fqVcXK&Guqx`* zz=B(SAs%s^-&M8N4V(3+=KBxn?gY9eB~sHl%hsE~XS56$_4zb_mE5Piir9MV=H z`6?cY9|6aMg6YB>!&?25x|&1jps1B5He-FAJ&l$q1rvW9n@&>a$foQVSSXNayBq!& zx{5=I+$J0C*ZR7;-BEC3kQmN^L@2EmTMIF*x-aR9@uJvv6x`BYhV)`!R>TU4zNSi% zsQcCju>%C_&*dy*3tb>jc!o+We<)Ta7ANwnTBZ zpLhm01iyN1oFF*+FDi|^kcOF6k)1H~>r%QEP{?!yc*uQkU9Lk^nH@FGbf zCNpesGqPVw8qj}}CF_=?6KY&MzRzua4$^~%bhZ#Jpbhuq7S@e0TPJ3o&6l|=-cik2WL zvx%J~O`pP=E#yPlL#$8f$8C5FWM<%vf_CRg?k*~AsdTKe%!mxa0qN(V=+?}!w`5jZ zs!T}sE{+!#T{27`SiYHv<*u(iVJs!hk*&D^YrKj6M_?{Ly=Pf8Q7IBhyF8^MC-Ubl zBPhII*k8h_-f0r-cf}Z$g^*|6u_pS5f#Qhtv7{V^m+9Bt_)vqwL1~frp(Sb=XvoZ@YINc(6D}G6awBK=vW)klc6Qv*)k~_ z0(6q1^Y|qXM5&OKbaL0i=y*3Cq%r`ngHybJ8ouH|253xA?C7&;0SMBrMK{Z+~x)kuBdlrZ zr2glIISn;*&L7fN9DeKZfU-n*jmmu4FaddWnmOymNBgCjj0e0=10>>cbvAA}5~5o9 zFDvYybS6MH0v`7sDUMcBGc(E=QZ+0RM>ZETOflerqPtw67RfdO%@?hFP&Zx{l1(kIe z&1RhD`MbQNcG~ztU9Yt$J;%ar(75v+AQRX5FE``!@Z|&JEhCGFsZeVKH5CuOTdiE4S z-sq%#3=U{{MJgR+3(<9B6;aeD=_@ijEZl;Cm{o^pxw8C}vbin}d{Q)J|ei!OnN zr?`v4aHIu4f=X8CL>_WE>*8sjmN?twq7Cx%7{GyFNebGOijtit7kQMHt7>K%QP%X9 z(qJuS^j7rSw2JXOS+(Ai%q33Y0X+?!A5Y-}J*^`?1E53MGbvNSSSVtzfT#)F>>AUG z3{3`QW&X}ZwMg|;r(ioz@*Nu4igDh%+pw^;i94HLvbeB>?;?rn)o(+BsavzQta$Zk ze?xFu{yI?|HfZbTiRpJLjdw9|k@6Apq5iJ>NX0I`40(jhKysXXA(l3JmP}ExTb$O8 z&X1`px=is0)(E+{@!|UQ!-CHdD>3aM?a>>TT+hFDZ_ zSUj@DNGm_@o{jS{k@`acrJZXhYLCZ&q_`Fwn+L4~w^eNUTy5Tzm9V#XAvUim6kVIJ z_hmoiA0Kc(LfaY&545K$Fk=g4f3_7B9_UXcZ0YX*)-{^~d7E%+&I0`pfk*u*>KuaN zg-RzJD|E=FIR4Z3whxa{Ze>ztO#?%~A8*^^QxsDlms( z*@|_C72gtGO!281;P~yR;md&kvEbDON<}BlYZqhtf!R+)g`_@_*WaquF^8-9FlWBq zyS`6dXpQ#QUAY2J=1xIsI42UDDi^TIY-wd+`fNaJbZrgYM_!~gPRMFR#h`mRV(X<4 z;y)c~m-}ku^*MR0cjLSvllg!I3}@z-k$P3SA}9uqtq>=9eVFOYOf`Y$=@tj+=xCf& z?;;Jy#FLJnk~zeG*VuypRT?rgX%m{MgP4t7YYRq%&Ps-=%Bc?HY^>M_hVPf3JGT|w z#HOO%n)qTikUZ8|E>vdL=`I#wjM_ww9!`JfpC;*vJUxFXaucG2MPGy`{m!z(qA8 z(wg`&c&o+2`1unfFYYj&*UP}szlkJhr7D6xdFARf++sTdQg9?_vhVrY?BIbVFU64v zoiFCIpQ7vA6O4RR~mm?#7$NbD<+Hy)C z8H$W{{>FDBZg=pZ-FYPDx9sRrgvq_JNPm1b&X2r{1NB#R`3T$m&)EM0_{(F_+3SvT zHx$h-J2TsVw@1Y4R^U6!@Ncvb{G(bUDrg*d8H6tZCqv8ftwO-VlMPi+c5AK<^A!k>ph_G(R>UEJ4Z2}d>8{X$GbFOj}5UdDp4 z6_IZa(+=)Io2kEd$Ddyuwh6n63Y0#2qJ3Xv&(K)5F+ql#DO<8$R?GD^@I`2$s2Sv)l=A(vXY(`K@Y;ED&oEB#w{?s8fDaF43r_sE zs`O$4wRN(VivhzpnX{vp@a5#!|2G^;^``WAa5hP4^}$J_Gj2$&QF*ZYpdz_s{L)>mG-rLg=g; zH{B#FKi+@3Iv3-4uQ3s4di(s|x_3cHxmOBF`Rz%Gm9YG6zvF|4)4_E+_wKRlftD)a z+e+(`=%=rH-h;FMM{nJSBfG)WoEtr-fWJh~w>svsmAVs(4{e7-_0KKc>nfpjDpZ#l z$EAjn{B7z_PlWrzV7kFCj{bp<8}rt}hA)N6Sj;qWbwtreUm<$#;I}mxY34&Q-t_+* zs1(HS#Gfu=+LGO&Dah~~cFLQ;bbD8Ap4j}s8rR3ZIP5OF5pp~EG~(odmOW^BOmni| zq7kw4XTP0oskP&Ke3U}A-DcWcpdKp58aWwW{0X3BTjCWShO4Rm{{9PlQeIvghwK(@ zs-yGJry?@ZGuJ3t>Uu&zo6@W#|30fXe5L6MzzEW-nb&uqyewG%@a&{ZttnLk< z3koI^zbH>D@MLx{N`TZ;;;=U0v{$FRw?uwt`8~Z_#E{2S0kt#lJUl1NaN4`gsOX_b zwV-ApeIKWPJfd4%$YsUxtdmA;7a(>dsCCh%OleYrep}UhQw`enmECj3>XcAC=wK1H zf6X!1SaikxhamUfwM>q%RzGZzKp0YYw4uuo?6d6S%M`5V#XdwQoFYoh171HYX?ji{ zS2OIa`=1Api}wm`z!h@esw$dyB;yAN-NEiNbVj;hUuP6@ANE}JU)4`1R z$RfV%7XZDV!1$#$G-swGl*;pwyD+S~X|`t#xGv*TAg6B)xIIA^^5xV;0NVM_(Vlrg zgk2SQgSqUmODHdh&yPCrWF_MURBHGo)C~_iBY#^Xe@7#N4XqvEwXQYtwAA*r^!&8c z;Q!d;{|NAZOumAjd<7^f@jpQ;65I7{+mdj`SUC7Gy>m=x7}TKH+`g_-y~s^ae4pC% zy$(Ux=g1gX_*u+ohLS!#OwIg@g`e5 zWNbMt+v#>B_wSN45;vtg3NCeN)=eb{JTzJ%KQOCwUqTvDp5*# zt~UQ*Q7ydFALPl+Rakdbd9Omp39SjoZBa5b%#2MZB*l7IE zE%+-W2#0(sLGMwJdAWSCnLp&$dpG;wTUw0vh=8I$=;y{-zdO z4|g+NF$Y@@$0RY`IuHA{C6*NY2KTi|KM%_|Gh_+Z`=SB#^toi$M!DQW?YpNwT-O@C z>P=sXtzODLYEsJ(^$<9jZNJj@0a0&4-itb}na;46+Uc5?(CdyuHi)+%s-wG;p77a@ z(4bPs&C-i-Zi-7ff<|Onf&Lls{Fi!TP#J|o%^qZVg`h0~$u*pfEWXH$e7i1I$_lh+ z2R%@^o%XX~kd3FOu~xda;PKindr0!BbxXapKg+G2cjwb4dd)KzIzOTcEu9o z&_FUdoFv#ZGv*Q3`?pE3ssHC<%jm1Q?;!6h@Uw;A2awd?+~!E^Fy$b+h3YPl9E}*Y zwtH8W1~;CIk~adoUN^L0zJCYf>oGn8eZ7K*WE-^sYdiDby~0Cg*Qo*HrY-y0>C-2e zgL3XcefGQ8hmYa(?+EN-qE!?jLz}ehs?KX%B{{Bzkn-rxW=*Vu4a2V+vttF(a6)z9 zNt^h$F8spRqEuG;3=vKAc7+=?vdVVn&CBaUuC9ZQi%!V{kL&eH=!W|}CV!3lb&|57 zIrn7`wWb?JJ)B4Jz(!mHgU_!t_6<Ib_;pc(hAEDb_o z4tq`7O79Vc9e|nR1uOkW`sYt93nLe*n6#z9^s_^`>uaJzgAcQgOFjDJ23;~of#Z#- z5(a4311ruo=C?=2S#~-vXQ<%v7Ed+LWXuDRh|N4&|+&5 z#|D5O-Yk^~>G!v?{&5#iohL_Ju+}S?7s((;+=}R?rbe_(-_Nps8oz0ukz|t$=9%=<@gN?g~Tg*SLX>}5yy6c1=6afv7r6As!WfWGuPe;~= z1`{jv>FKRNtyewnH2mys{f1fQ8dr;(A*GW<65nmH=K)|a?Gc0yN^3u2wo znxeEC*xKlFt$`vZ{^<3DIU9&h>GK_Velhlz-;&|eqiEl82?An&&nArtn*99x7#WuMmT|2pz_w8tK zli`F{!(r>#At+IiVRB3SItU?N zF%?=fu(+$2A(aSpwr5UHvlvbVaHfSp;OQ;-eR}2NfQ?Os`v2)`Hr(h`9t{ilqdnZg z{U&e{k{XaV_Sa>`XLz10*>rP)lUt+pv|SFaT##S4+P^mQ0U4MaYCHQ84gp5#HtyFCM^%`onaz4dma4I#tCL zHnTO`Giq2qRtwDOx6+;Eo7}pT$;co&b||9vOb3dtR&TE~@E0zKvG2d&gqLfEIyM?~ z?-=~2GIdq?R$HH@S~)|}{yI_1SbC6klf8oRMi%D=m~SrSKcRnHZunfc;NRygH1`V% zaJ`Yw=+RP34$*szrgDm@^SV%uiDT{Q^Viz7wnPrAH>)!0o8j*1Zykiqwxn~s{-mh8 zR}vT-(;p-Y*2bV_lxsgN7@c+>bY1&p;XBSGNO-AamqOgyG)E)x5Wqq}izF?CVo>pX zEPA$12I0=PAoLbR{4>y}ytE+nk=*bLYHLf6_bMx~B~i^Wjcu{m0S(-IjB=HyGED~X8)wvc=b^c;l>-?j zaV;*IBJ>&_El9DFQ$h88S=qYLeX8XNk^GF`?MAru&`H)%&&-H3DlHQcWvU2#C@b}^gO+kDdAnG3Qv>^x(wNdzc1~3J;xKx&$)A6l z(MeC+&d9G)B@?#XxklFsN(FfBSrEzvvFg{i>*z&D#VuE}x_$8Pj}J(;JKT=PZV3wB zwF;FpIk_Sv7yw$lfz8&1leDP&b*CcZqMZrlzYrBIcXnvmu}7*rV#HUm1h)zc{^8;K z>m2=a)O3EIw5uSNK&2T7ke|ok(eKtem(tj;kJaZ*VTkv{$-TjQD_(U?vlJ)^hW; zc-uJFzDXZ%oO!ypXriFF>w*s@&|;D+om3Wy@`O7P`|D%-&f0X+U#n+kW{>%N2u9C( zvd2qrW?o7Qez!8Ch?%6h0L!ZJmJ_#sL~NH=!1%uX(QiL#_!9P}*fRb+CE7B{^4VKU z;tozUQ0s|{eN%f05Djz_CD9^}+wVV{o$Mi%Sjp-mY?bW%vpK9uEAJSU@Hrqm!}8;T z1b~$s3ErD3AnZNMFC-W@A9(zH8i9ZnL?416e~0J5#v^TyWA!9^G3jL%dYD1(fGkm@ zyF#Nv%rP@@W!sZ|BBb7kpl{JtFVT@p3TwY^gnH1W8U9`#pe5McM=?do7Nd1Hq%G)~ zSgWm$?so80v_cV`nA9FMB(KTn5EUUT-7LeP3o)9rHV8M%hR zixFhlG6Si}BvJdo58zB_2WBOwTBwy*W?%^K3J||!oEBBUNWlN#bWj8^Mn7cbgxz-z z*D^(m8qrTCKBWV<)K3)+P3CiOC{fK#7eScO1S{Ag)DElKaEpFq1u-^Miq8eL;`>8(uY z5=Z8v$$kpDC~Ditb&eHgEQG2xlAJB>Yg6Oh#4a2dy6VR#FHAABl~yn=wC%fHnw;~& z%4?=>k}s=q_l7F<-vY7@wV&v0>_o{HN;8dMMkm>BqlSm_85vE##Hx;!EmSIzrWs$x z$IbtI>@JQ^?WZo?Kxs}KlWI{}h--P+d*dz+jcqmR3pyj1Q8+*(@iOOB=6!VeUj55k znl9KjQ{6#VNYX2O%dCQLttHqq$?%>T<)z8t=lEj4AUfGOCx2b@ZLheINP5R9+e)h_ zokM)+M9Ko}j=1$~`i~5)F0koul7h|zM#@5OA8tx>I2RCU=Smx=u(K-)DQ|RMZxYpl6HgLTh}y$`dB2dGpzbczdiE2*ljV^i_V?<@`CvGXa_I zuO&nXm#@g}4Z0U?+&a9#$p)F*sNnOBiC_KiY#U+URP@1Xg8&lhr+&5%H0v#_={F`K z6CkT{RQ39$vpj*UMSkkp^1-BT-17*xXxs}JXFJ6r!rQ|nk}`cF*rgZMRVqz${I=ra z6?&3ddu3GD76Ddms_xdR@zGXce6-hdGu)Y!2Ku-Gx(%1sFIwJdGV@Ph$>J02vE|dA zGEsi+Iz<_}IYrxeZt|aOdDLu@yfAlRay~h@zvEcNmtC?E;%YWywl2KV;==-V79)!0 zMZJQj>X=1<2Uiv2=%wWGg4>P0P{8YaS-eBv*WkygTea6v!d`&XuVo*(UaDa~)DCD;<9sUJQJ-&g zbM@3%^qK`2aUk4EQz-xWKBzp5&EM7AD~De`T^pwvVsSo(eK4sF@!a!En6>ZS+xJh` z;uzf$s-Ah?NzFNSCoq;QNkijJgivRrH_Ie&4OtHlf9_zg0*PGzog&-4DWHoIT(58&)giQMvx6sYPPxIqmi+x)+m_(UaA$@dhQ})Kjc+~ z$Zm>s^`^e<%X5mXFcDqhks7&H{)ovZE$2kcw?PD=dB4AE`W9*6g4>vv_F#1<2K#AR zW_wIgBB9B;vDc9n{mYS6kx$H(2!#oy`|yRag&3+Wo|ywvvGA&9Z=wRZsq?GV)s1#z z;YIpGcDsZy8D0p=qnPWeLpOZbf%nOicFo<3$6Rr}9y?XTSs_1;Rg;UX&Rn|(OO)4Z zhJxA)jhH&&J#kGvTK0rfq>$-DsvC7lbh|KhMl5q|h2N zPkSVxNf9e-K!fx?00(6DN_^Ww%2Q>8eQ&;97HkF#8rn$M98o5gKYxUEc|5OcMVtjJ zb9XN%l9)muuLKV=J`ei17!Ukr@?XfwdT8t=AZ z*^-;mAIRJ$Cn@osafg>+Br@q$#-4C%CQk&CA;Lb2A%g!au}|-@og59Bk7MM~g*!x< zxI<%?zIuH)I?@#G2KIiL?bvmYQh`OrLKG5<0hu}z-bt*@7HLzQ*Fg=weW%#5qp`ou_XNI5 z&8kw;_dTzRX9((H3*kQTWbgCeY`k5K(HNuJ{;c6CEK!N1jweIlh_QLF*0SFv49!}H z=DU?bUW!arldtTI#5|p!rSZ3Ax}5z`YH;A{Nr)6KD1Y04nVQ(~8>dooF-u0g0m6Ps zw)m~t6FY6eYZb_^ZlL*&zPy|h?i5}qsG*lQs@>QLXGxh59ho34IjmFK$I1^7*1*zs zDm2l!r^t&CEB_Ek7W(^ja{{gwpB^7H1fwY%TIjnb)QyZ*DCuQFe>4|BU4DD|I( zx=7$pj8h<#Y~!a64t`M%+K*E7Nn){ z`o(VzbTIL0*7gMwUQ>1B#FC8o8B&7`T1&1+xs8+$p{bh7T+LWa&W=b~uN~O?324D$ zy%tOP0iUyzgb}xbM;C>~^m|gzjDklEM6vHY_Jl@z$Gpizl}P5VX#DLSV+l693R(~x z05dS-^=l8u0Aa8dpi`ms7QhP){!9@HrlZHK_yGP7Owy#V;dt$@N0)o)94BXko!HR= zv2vF>iVAZ3EQ|O#n+7(-(QiESM#o7QBUIK;cxz>|Egf?Uor*bJY(Gfa+2_9a>irC< zL_71iel+L@dP}V=;uU!@2N`ASbWel}5pfZ6pq_=l>v*w5yX=>G5t#Z?zdK@}FKc7f z+vW``vK?gtGIY=3sgLUubbUJ~IEe(x>5-3x-F;dFK{MoT)dqWxdor)==C%f9^j>m$ zhKA3bg@CyZunTp~72)KjqC4^3>H8S_J|LAV(->DvE_$-WLzl?h3|vK?0te;Tr07hd zsIx;6Oq6?h>9q#188`&E^&4MAyFHCqs}zB)bQ-xTPo1mHF`+N^h})m{R`7YmXLs+9 z`$S5UXn(>{&eFt^CUL>=8LK83LbmN<&jr_SyvfHThjxMSIQyW5!m|!{lsUoyr}V^bAyfBdqiB-+knfniklHaaIKqUEceD&fFKwh`-*PtIK|6 z8F0>q6YkI^a}%ztHBftsuQK$;Qmi|-&YKr^fHI5sGU7mesR~yxiC11LXepnROhdp( zW2$3~Yu}w|Ea&xb0<+^-M8^Uq9>I4=x&9Xu{rN57KNA!=w?^w(ZGXJYEFbKron1Pi zrHa>#A0sNE5ajSklN=O~V z7~$9kj_>)Z!OZ(oL;!((Ki>~e^3A@YTsI7^Uvm?~d1^~OVGlIQ$2?PP}DVUp$gX=FQ8~YzFKR=LF z&BGoFWRM(P1fLVArIKeRc%wPzFg@>JoiyH!D1B2LDAe<0xHXsj(lZ71u<^V&0 zx&T%gXG3cXV-Z_3YbeYi0IP_TG1S)ipF#lEd#CQ1%>}=c+&Oh!+T3AEbVQ#=0Q2_`F0{_J3H{(7y z{QLk`d8nmx>d9G* zT82@}i588ijnu*p@?dyhViQcu;`g*OoyXNzorSlD6r-Q_LGt(j8p?P39Ht}n1 z?1&NPREE5R;4<>pIHT)Y1RUy7OIBCd%Q|S6b|q{i3KN4o?Pz`4223?5DE#SSa~XaJ zSH@PBsbRF!h!BxGyrA+a1f35Ruus-6phf&bSu88$WceiZl8!9X-*dBnC%k(7Mp7H) z1_LeBI>eOgJ4T2^$ih&OVKDGK))!S>mFi9TxH67d47n4&2j-K^F2r(KG!8XTvpuh@ z$Z>%089+09;s^Dw4$>emp*wsEnlzi|jT9&^HrY$a5PgV4agH%!f-Yo5spFqIEk$}? zvCnzBAs27OvMo`D7opl#dQJC4BOKD%Si6T0=+i_hiw6b2lfWfUY~__CDV_*27p11G zpp(z>*Cg;hMq;U}IEu+K4xPNzb<@_K`gB?7bd<$!i=tj*e}E^DzCTkurb_5=xuxpv z`3+nVq53(Q%gL>dZ_Z6z($vYTPPk{>#F|d%kQx+)iBRg8;X(uog|f>eh(}Oy;8q8P zx_MOXW)2rL>b*_e2uHnQoStmibwX*_*#|7g41mAzvp#|AzX(P5X``ot!E+Mg-2Op7 zwI*NXEq!z8J4N&*)J*rvB`os_x(>L8@3FJ9mR8JLyfRdnb`-I3abeC=?_MRQavHgzp&i#Y#2Kt~?5{HDPI z{SmT^#m&U+tk$4OGz~T_<_Sj3n~@4jzeFLt`HJoC%e-~Ao*%Y0BOl`81QU|uQhuep z!*r-d1=HP3+?iTxr{$S#!FNK|o zFel>5dY4_(L=(5bmkM6i1Mog*Ur%SkUyhg0aXV~ht>>tvU@z@kTJ}qS8FT#Tm~INC9Qkg& zY>0FSB6b&^mMRA^+qtv|CyTC{N@JMPr|s6r7N#3~s2B)|R2LF@36110;i=cv{~DPN zp8D1}5*g2Y^Hb7ktT=dVZr17K_*fgjK8pW+Jb!-OX~SM)Ju+j1%eqaB(Kz2=ei61d z&DTyP2oZC?jm6BMS9{C)1UFt}u_51>p==pp*O$5seJ64z4c+crI10CJBg`-jQIHB-JWw;|}B8}^-et%{CW9lcyoY$w-=~4`cQsG2(-A`E?vM43L_WW}E z70J~}CH*5%fMt}?nVjd*qiY3gE5Ds~Ld!Xwt)yFpJ9mzOv}B1^Y1c}Bb-=7w3k4 zhJPF1U?gT>bgYWaqfr`^XLZZTTP~zOeIIBVTX+@psi3ZIwQ!~$Ik9t56LUVxj{S(5 z#Sq=RvlQ<~jc+Ar;tKh&iO=WAQ;dFJie=>-dX|KXj+gxM%W!!x=Z%ux??i|OzQ!XE zthTTRm>-g-Uw2Rl982o(bz5orMc_xdd^ngtJ-eQ3eCKj8{UXKJW#?l_HrvTgLSvN8 z+Ev}{CtdEQ!wbYJ41A^iYh1$fw08VAUqa z@!WPJM|YY0@u4chw9zUe={Jhb91uz{0#T;H3+}RmuFAP zjQ0E4-d>vX8NbI}$$Rd%gz|!kw)PZV4x^JJS^{gNB5qg(_^3K3KVEq+a(4hju<{z< z5TVH%3NJ`O-3f-w@(OOOpQ&`!&d%wrE7~(U@G0gAdE308a`hRgnYE5D?>v3>ruv=# z4rtkvo0v+RD8QiU80`JF-&qq=efp*8>$jkMb)^t4^* zhsi#E$}eo65+Ogb1v-r)Z{1qQn-Hk>W!>nzrcAMz0t$?5Zz%)m8`#1p{s+7)MdJ-o z{sGRo#UAfrGCr59%?hV!5~a2n_cbmGBbrAzc(4y7%7lNQchD7tr#NXN{1o`)6;}Nx zk$L&7p@xR5R^p#%_yfiZ8gMNma?L1-f(3H!j9Ec-42dPkLmlO!6mjU{Nu8zvb{Ypb zc+haFf-kq_ z*0v5x3Rud^Q$6h@@ZqEweR^T`>nu&6(~h^!ktTs&ScLAKG!3;H&N}Pn*d3P7@#REn z#;x=VdC)qk50q()nHOOeDok9IwN*-}pe~>q%eziX6YqAgji|azCbyGO(<$ADZHg$@+hC~XTw$Go3U@n=IJ(#Vi8QsqCb9SD9u66V{n57` zA#=TJKUH*Etn5$J5vzUrBo^f(#bO@jEvS+V*&>WJNeNPi{5 z7geNj5M>recay|KJs-hE`NH9yK`0u;H#4sf1;NY3>)si7^!LVhln*C$c$Y8VOgKTd z-i@)(5<59IN50Q-%B`R%Gq_O{L)N?8Zn|{ePkAOo-u-loEZVU_MRoUdE864zb`#bx zr&0FS%tY>$fvI>fe1ROY)y&2a3WdR;L8=OA$L`LYgyxx_($eOVQkm}XX5+KnIi(UQ z^yvlfUr+d{#buj)-jH;a7=1cNW}9ksC*eb`UE4Gk#IzV6RoxK0C6eSbWEs@EDi#=v zt&1RASh!eWe1++YxAbXEqdfj9VjQHnHY@tmIOyFKVs!eG;H6@@%Z8U!;$&#X<37k- zXdiDf)LNa+ugP8ZSiXt)_j&o94^_Uesw-Vd7jktj(Y{<5-m@9+QaUFu02zpN1>B^6 z{}DeNw{yA*!+QCi3BrF^@?oNJ!W;IN9hcHt9AYwC9)6 zo-Q2Sv-CCNy`kBXN<@OB??PU*$HX`swSAMfBvln>Y`gQP>N1GQ{w`La>?SQ!{UJRu zHF-fh1}8q+Xs$?6!IB)dDaVAVQixnew{fDM!IQI7%}=ak>tt(dREhOGqw!Im%VWFl zZe7AmLFQTtUMUVfmn^<0LN_cQ_IO30V-74-)g-&dAn>IyKfp~c!9Dqv==kH$%YC-z zR6oul&~QkPQ1q(K%%+N;bljzUtp3q9@N|B!qeH4*FQuUxSVrV@UB|NPUSrPdTi0U* z)u|Wj|N1jK2WJx2=m)TZ_cUr!xlh^}%i_Pg)nUyg&{HEuc*o+42bNcsGd zx0?H0vRL08@42osY_d^Pb}Xl;H}CFmyQlA0^EKS6NIzTQCW^n=`K2uJOMHy^tRHQ& z?Wj58Xnd{?8bW8=x;$DxNp0!vz8$wSq`D->#R=KhvF`b??e{!e*$?lRE}oAvp#d`f)h#A2gQ*eHu}0Uo1A!(mzY;8Ti_B-gKvLy%^Mx$Ye@*q}JYl$uGLM z*r~o8n0zQ=iK+Px%dYcn{b+Qg7XMdZJXs>z=oi10n%q?4m=yjwc1|3{ zckwn{Lg8e_#40LP`2x~ruBUICx2&C|2wgi2x&xa!Nw7LE07Gr~x|x!W?-|`O^jw2U z6fklvU+EMo70~LEE3fD}>9z}Rw-Kg%*jly4O&r5c>;@@0OxAE^MK~6G(s$Fu(W2}4 z9Qc8rUJq^X{1?HsX^z{FjKWi2dF|H2EvqmBRy}VQzlGP!v5O84lc)5BYMXDB9V}>A z54V#zt-kQ?zI|@r1HEEi{E*Z~Bm^RHE_qw`-Jgs$qtLc-RPl|?XFpN~$wL{*A8xJd zeI&?!PsS|gX)$oc4nNUmu-8o7Ug6dK{INbwpO&9}_!VNwq+%f|Y`)HMB971aHHEET zSv|Te#ecXqC+F=O1(mC<{upr2lpFs zSY^==;BP;E%*hVq&701kqzTpfl(PSJ49!G*NjdmUbio>y{_5ye(PfhKcrqc`CthyN zjxD{G?#DP%LEy0#zAu9TAu)-AAFy6_OJr#L)GS69(+vREJcv%FsVZB@=KadH}a-%Lqh2v|JTZOntV_c@&`mdIOj zxJ5h(BY_y8=XO}}*0T!LCY0x{$6~EB7gWHp8l&9GNL1oc_+{8D*GVEg)hzy4lxv9V z_EJQG*!0HuZm~?_G8Rb+v_Nyn#Qkyp5Ermq~JSZ2Xv!#q)bzIXiOV6 zO;AWXaa_Lc>MetPnbAv?lCVr70&*M|u)MQ{%*k`>`CTF*-q)(id z+T0+}Djeh|0A(+Z<9RU;H-5s6=G9KtI25_ducoGDI}IS+^3UEguzHrGjSavqj7(bN zRl2TwmQQN66@of-=JykrY0W$W{t#8P%11g*zyWHkX>3gXKrZNg&VaY%2Csn>mW4Yr z6*EBAD!fGe!biyBJnRW`g4olNub|kl7*24Uu6=0^3Cg`Yb*R}>Vi~Omkq2kNE>i0?Cb7L-f zUSYo*vZPo4^VEgutxJQ^Z@GEw{F?qfY3NETuafl0aYnQ^|*Kvy&v&M-vt|Q zPj?P{uQ5Iv&eSe7d0k#XU>{xdq0E}}cg@LZ*$6XjzsT7i=6UauC1x}rpO94@BoVIg ziYQ%}c7{(W(nq=U%zM;nXr53vKIG29^U8|`i(dJ)GU}m&&VKRJUjxfKR0;9CuQ#+A zjxz5+U6NNHIPV^(>EPX(`j&JN5>zdFs+EmM7wkUNtv!5WF7B*VHT zuc#QZr2O$Cmn)Md=ItDA0%|7Rcj-YB1u4E~!BTFY5=+=3hX$W!`4rvpLxNjI*fs+C zzD=9l{sOPOBE=K%`q&uRDYHKyI|NBx*v)N>4WMe-M&i5r!gVowGSB0At7CU+=exsP zyDM%8-%E* z!h?uaM+>O<@bDNPHy#LC@n&UZ8Lv+$CLullwbOY6mpfnHa|-&#Fd~14Iquca`Z@YQ z2iuJPWC`{FvBOHn&AZ5r4|U58_p3IKRB6Q3(MI(i7{-d^U<&hc-)W&S0)#xSO$w%_~hN3WnRqKBEufL7S8d z&O^F*5~xf&Vid$vmGH%!hClm4fF(;8DZVMt0dKj_@Kql-N()>a=_CU!xz}6SvOx_3+1SkIODRJr-{_{h|EtamM*U&8SmtGc}0b} z3uY54#DJVMIJj z`eH>-mWxwN`1x*XiLRs-^2`ql)sg2Np#k7RhQNS0YlQ>e6!>Tu*MPty(*uF&^^pVu z(;MyumNqeAnuAB>sK=5JWOq7~+$H4cM^J-AI4m()elh>%7u0%>8jVqY2sr*AL?)tA zlBH;Sa-5fioPfe7O<;h6tWu3IkQFm-2~W^|VGB=?eKRft|Lis8)(paKfaJHpYfOpI zpRowL!ILBUXonz<*a|>7gaJBarp2t0Up-^}J~>=5yXmXOl1Z($Wy-OG`pOzxY!SM#8)v$l=PSGC6KNe!bK4kplS_6N3Yq+?B01R>(*k59Z%31?DK>sH`NTQ9(Qh*m=(RqsPQTX88{0HFO`UG_ac+>H_=`miV&5dM z$A_7Hjt(ciz=dtl%VIPOOlUyq(7JWe+rDbau)-0za!o)RX$^5b1@`(xV)YBsd zbz|e~N{zOfhL{iPHM4iO8tNvsiNr-In5i=@z1mRM0$F&9FT3U1+XJq;5v3JhH$fX; zl6g_1XOzv@=hlXZtN5!IwUDvC4~k!A$Q#AQ?$3SeHKw<54YLd&8O!`SEHlhB4wZzb z`6{eo60jaL89rd25F-W_T|!gFugvruN5WJnw=__T>~7z5ckgaPJ-Kv-`LF8GS=5Ny~aJN|u;ft^RMVL6b--E(h|i zm4{Bu=P$BW+_XDao*Y)v`k)H6zB!l>OHgf}=+&1^JHMW)xwU{kw?nVKE30QvLrR(P zFGERVMOBt@*Y7-$DNxN+F${j(qGbIkvgcd-5Z81uVA7>cx&@RUd4OZVb@-mPPTY=@ zf1|McBhKM`v(RuN8kJvlEK!EbsNeE8_a$xF#VqDS*Y@;DYSPs}%+VJ#T4k8~FFKO$ z5cY{h&i)62_`QaHzs%0b17iCdg5dbSLJ+Ved>13<--v{mjQBk$aSutFf+R!yJ|2&e<$ z-~d7(>_7|gEt*15L_JMXVKcwj!jeEF>d;rZMCz5IRseH|x^e=ql!-TUkNcK)LGa{u(< zp3lw>`mKN8{$J<4%wPO_-|ppf0lN2?$Y0`5)BeKC&{l7O*Z=cC<5A zg*pRGEo@C3p-y%#j>b@+5v&_+0kB>$u`qW2Q~J#_wlTE--7Wtg{jM&i`oCQcG=ZAJ z48c0~0qOdkF3f**F*8i(KN7!DGMFzg4Ymho?lB-IdqZQWqoJ)C6u=LH9fCl9N!al_ z2>+^Qf6y{A{crIg4}|AI!S!I8>p_p}p#{i;F|I!f58+{d5Mh5{LjLGI z1cd7$65v02e2loqWbFj*hUY=y$IFPRaXJv#^E!Jsj-q?C%Zy zb^bTX3;D-b`wyP`kID3&3HpD`q7t^SbiJP;|DNHPrT@ZSO$=?F@26fTpbp#ptgQk= zZq@A6Z7uFI9LRP*&Q<<1?(ad?zcAGYSCkFSpiTf*ISUgfAV3H3fT+T3h}pTo=3X|y z-@{*rRasmT21rBQVRM3v4NOh+4;B08%;N3{8-ySr8w7AaY90-2cOaaQ=@j>?!)cJa)*x+vVV7=l-|P9Gnmc=wEHXd3@vy3 zjftyTc)~gu0t4#rpRH{79SvYrv9ogq-v5B$9!iHH;D(OQzk7s(i<_GR@cg-ik|f~& E0A7S_&Hw-a literal 0 HcmV?d00001 diff --git a/hardware/OCP/TH_O_C_UI-1_0.pdf b/hardware/OCP/TH_O_C_UI-1_0.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f8d5df06da42882447db7078d931380541a33770 GIT binary patch literal 34867 zcmZU41y~$Qvo?X??!n#NU4pwi3j_}ii@UqKyL*7(79e$vNM5@BiEf zX11rNy6Ww>da9~-fJ#L|iiwSx6M>DCmDIt+8bMGHfkng0#nzmZ_T?2q140JE2tpZr zh=UJJ2w4aw2sUtw6@nSU8Nw9eGaUkpIMCeK#R2$NUJ~LXgfxT#gd_whgcgK31Q0A` z1>pc;55DFDkKqCz?BKRIxX%&76MWwa!V=;$gbTP2Knqp~paZwqz_PsHA11IY>)$I< zuwGZNlmmnrSgSLH4Fn@tP8O`!6oMIyK?Uew=4xvG4+z!2>fTJfndJNrL@Nh-31b)V zyd?P9S=sp5*w{JQIk>pE*cn*aX;@il=n#a35zOt)-dJG&-)wj~nv=4K8@m|WI#_~D z8C#k=lX8Fs)c)51&i^w&(!)hs-34qAfkpizsSD8ct)WQD%FN1+z+!4l%ErtJ=I-M^ zpC4t_NiB?Ro&U-hJKK=5C^^`h{|_Fnf7R=Q&AYgm1MNv!B+T8cOwHA##SH!fn*0A? zc*FXJ7-(+q@-Ji-aR+-B@TD^;@82n~sF^!ExB^YVb7Eutr{|-+y#x3@+uM>*Lts%f zH?uMpbMPS5f4k2HW{#ANo0rqzf6e~y$p5S!aR*xmpt_^6sX6J}Vp0)TcO-qo4_5Wo z{2$eIxfT+5eqASn->v6nHLwrKEUBdHyS;qikYr zZu<8ISm=L9{5zpHl=`Iq8En3=!n za&`ePcRK`+oYQXEcqV~3>bBnA{zdpX*$F5WT2e!aE*P(AQF5qXl7+ajBUMV2Gl;pG z(JxO-iCD6knLhk(7grn-q&rcQ;&P7?YQa|b!>?!guNUdBhyLBqyZNsVuf7igpr=Fs z7h`5>yyq$T*9-rw*H7y3EdKtWOA?mX%iVmxXZ5Go%lf8=($|Zd@Y+#tnb#+oLkz{@ zEgpX4-iJk!*F%x6=lCluI%uk8UM$g-j#CQwqw6}#*URGh^`0fI!z5sbj<&V0EO%-2 zXoK;z=!d0DdCpoNY93pyqt}}|un`jUD-x0ikO&yiXR7;Gy>~Bnx4r(}5B0CIlW`m`uMXI-4%f&uzk<2^0ljG3NyI%_$u90 z#Hl3LsZbdEYNl|1p@$Xln_& zY>><5>+~EhfqYL&f{lks72o-Xc9Wvcr7XqIAcLE9>!eO2o3DO&1nH`?VeVdW8O&5# z`66@WOXm51?8a`>V!sA<_LgJ-{8mP0rBY%`R)(N9+PTNS3@*#FEq8V{Cvpr!A@E&9 zcT42=gS-|qM>KbL1|@8sqq!Px4A?rJ&Th_tG4@xU6m+3hO(Wd=b`6^%Y~2X68QJ0C zSb)>wSsVg!qx5!dg{f=D&>Wsx?a1c2C!*ZENPvu;R7wn@FVXnEd^7d;PRYqSZIvE& zZM_&LtTN_OAKB3mfpZ5x^s)^u1-pFzvW4(;?#Fgh)K{u%G6Z!8Jrd$^%vl?@X5lRP zvwT%U6S-THVA(qXX8tA`wGnd?q!_>RR+>lI5ly2N(TcF=96Ub^J{$3q=Il;kyL0i8 zW|DdNV;f&8ZU;rCNuk~oAGr1~jqZ?{EidvrzCDvxK8Iva@;li*HK&`I0B`cU8(!SU ztQhw};bG_e>jfGhiCCPwdHbnY{`TWFMmCbYT!+!G9(K0s3-Ryg_td}vuJ zTXg;HIFFw+~A+^W#d==4xc(UX0tXV1(xwy$`FO8Ut~WI^IkeaXDc*Hj+{XSy8EuuBd2t@@)O5g=+C(_)*^pG#!|T_u<`Cu zy13SM6+aycNiOg6tIul7&>}?OQ{?Sb*u7br zf3b>U4MaD!eMwk%UL7G3Xpi7}il(zmbMHNK+}RVe%TT7u+`!$ql#CInWce|e4rAhH zpZH+<)X!bckgnm|vl|-fu!>T}^24`WtpP+FVkz((=xb~x(y{jx_e-Rl#nxg?ZUKKc zE=|}x2{v>AO!}z=Vd>O^x%t%L_fFKNOhiFQ=$3Z~G=(A$4QkTDL z$EMpopKl=-*9Tr%-`+6$jC_aA}w?@TSjj zrHyLc;XD5g9fZ-u;Ju%~{p>+c6d-Q!8_l_ZVJR; z{IVk$y~bX9BI?cOhxwrxdq>xay9g5q+6g7_8~eG0O15}F&sM@N^f7G2+&!>-^v7WW zM+;dx2-zt8g&FvPz_ep2gF4%Jul8Z5b*dxPJ<-OA^?LdW*@)tq8R&1Qi!<2Hb2-Xq zsHY7aK<)J*A~_b=_)aqz7T>!sEwMQeK7=lPd$pK)IL+3>lNjC8+m&`E?zI8S$Z*Yo z{)b8A^r_A~)SdBU3-Vs)vilMDaFv!qz<&z01Qc`lbJM3_)ysdBRkd4NVbg=yjZN=o zyxo%-OWE3$di}|;hjzCyTaUTk)5F?KM^KhQY-=a#d3VM>4#&3~@w=i7VD_hBd4H^> z_4As?YFb+T3aY!&oaWX}X3)(`H2Gbh;1sjAwlDcprQIj9WY>K+?GE9HHT_%D+R@)c z*KSXD?%kgoyd?%PY*wE8!}R@rTvyX#IZRN&CKKK=P=yJcYcDv{g&XzQgp^yN^f+83 zo=h2DmjYkme5Wf%A}5TW;a|e)yPxSsYROxd)}X%Es3&ri0&MLtzocsnl=hkuIJi%~ zp69p;i5U0^c$iMM`25^p)GC;R8+bU_US7Yt3q$>(l@nc<-_yD4+6Ha|94ByAST5S~ zpZS_ndnigoR-6Zvr*c^Jt&~z&D%Us=NHS{Me)4}&n;`OMdi-V!{eDlRWJg=#HDJl86w!&*1i%zs>HvOc!CdcNgn{B-P-MT{Lq zhaNFdd87XTOPKlnY>{L4?vwP!H%s(<@jrFjri#CwYMyd`?33Mf)seUM58nt>CwCrR z**a`B@qgU&Jz1`@64E~{UkGwPh`Zx^+=s88=Yt7V7RkohVcJyoffkOwulWrRdapMk zym{U+k9}YBJU-`F8S0#xC&}Ut(%EN!`MEq)d~@FS@GO`CyNKf%CZ8cSBJG_AK$EOih$aDAhds%=bP!OL?&B;qV67m6` z?A}hF6?7Q|_Y=V_=gl6wQ(wc)wYwOFi|wlz1&3YmN&5bpExDCTm}2(OEq7_Iez#&& zcv#_L=RfzuTg4A>+LQg`GKJeWt3k9pU1BF!J~!J*LGZ1eVs{)1neX#^s>!KN@-`3R zE{{6t)!I+8IeWfLrr33VGmQ~`@Pcn1{F6O+80BrVm}rGJv9Uzd;}uKxb=g@2@i>3_ z6byCx6byCx6pkcaA4z^eq17?0B6ct2=xOe2N91h_+{kE$Z~{Vk{0N zoAwcLE9YJWef*@F+%G+)np)pZ#kgh)(Z&nImYf1?SxlRsASB!e8xsVbv$__u=x!YD(x|B(wxUkEBGx|eO!G8A@r%&X7uGn z7=ek^P=V?0xlGiLB=pIDd9r&`UHIEmxV=+#=^2R_9Jo&;SxOO>Ws!N)NRiC5|M1<0 zk))4UdkM1e$z|En6jaw{=hyrjnUqV{W}jIU3%-;;T^GI>S-UK>JhME%C~}pMO=8N6UzY|3j0eajlmO4-Gxxk?^5mU zD^}2$WsljT3CB(6U9Zg^q7i#4NRVB4yhmnv3Utx>(4VegE`l06RfVg4d-YQwRjWzd zTqHhb3iw=ndT8th^gWO-Ys`oJcF2-XLUKDR=!p2~`g}&YaqS#;;&w5fQ46;9bm3#2 zd#{&S@E;j5FPS-0{7hB_BZgJ$>5+1(m$ zrU=vai`|i=zhT}W6N5^yjY^otIOd#k@Z%H0+-I?#`_EXq2ZZ?y^;M;gtuP)R~t}-M~~m6CSiHoZUSR-P#pzNpa}TvTyE7& zkndJ6h;8=(hMGA9%%kZqK7nkwras-fvLJ6%Tp|O-mY0laTzykV)VMXEfpxnAQRhTE z#wlj|;4cqf1MOPeld;(Yt&!Rt0A20L3BOU1?2(B{Bx>Xu|0I+51eAt zG>B+RTi^teXZuKSA7G{?@IfyI<@0X8VW? zRhV_qKoz=rQ}2G5$a{Bb?{0kjxVlQ950A50?T5a2EE`~XR9&s%%iy7smKY&=6~yYyCLe2Yk|8@H*9<@>;?R0#c=*phfue^aNx`GxJ7U+YLsOfYY z=kAmF9WUx)H;%enXDe&U2CsC&PkzgLL?ZaU^N-51D8wZl+aO61DvMg}C)^VeYlYu% z+;=Y{6_l$gpLc@=;?^$WV~A;ld%4)@`M%Pco*-XrJ~gXa2(j;ITC7sv5DUkyGnUcR zup8i^tP#62wPTvz=A&Z*?BR*9XGrw37m%H%$- z4{?{e`Cm5g(+M42&qUI#gS@R0K^UfE2X5~kPp6wGKG?c+H!1UU-Q9ID1YnCcw8_hI zi?rpkA}I^-t-x=n+eI7s3PM?R2BLP)O7Z_;ik+@a3BK-4=QM#&nw`e;^c+XofHAS_f7y z>$Go&zpImCF7s3B-sHQn7s2yw;>&3r^joO$dL0?@-Ko*7n4gYe*7E53o+Ti4&H(ym zT_5qi-6RWe{SY=>CqgyJ!Z1Ypmqj+6qeUI~K zmKRfHFDKy{(G({*!uP^oWqCE_{CX^Uq>w60^-o=+US95aSt^r%FcIt7<&dqW1z2-U zM630ZJ-;Z!v#6@Hd=hU8)i}Se*|9z-wCmGY;zc`OqR;{t%dBP2i>8| zW2G>Ok|bUU;M3`Q1iVQ_mQ76f{&FYy>iPLZ;rt<%2$hy_oz*B%{mh=`;IYSx zUlVv;G~yL&|4=m2uFToqxT5RvXUBmc0s6^6rme7g)3{ zT>g3H_sVZ9m1%cE_Bh+-eu_>Kf#`eZV0ZZ|PG|>&XU@&`jbjzueGyOC5Z`v9IgI*) zmZTv5{#y*jQ-sSD@ns<1KGZcjNrtKziRzfp=ECp)b{xPdD`x>av+qglrP_V!T!e}N z#AYL0scG(2M^q&*_63SZZUg=GzDxF=GG$omS^oQ{`2~LXbG?kBwR9;xxAT5a?{At4 zn2kPh6&lr>RCxzI>Z+u7+prb;_w!G?B!>|T-y?xv@^yI^e%l{koAtpoyWmA*?Y3s*<_Vp?5KPs*|3 z3ust`p~|V~O3A5(ed0qiT7tNoM~&HV)~>#~)1@zzC6i-uQ}_By1L%gL3}?m<{#(pI z7mS0)O8`>l3c97Kbyo+GiFKZxbqtH$%(BH(-HJfZ(bgPx`k7*>f5ht}+7J25EsYIa z3d1ptL!D$SONQj8w4=EM^vR!J_J0V~Am3uBD}hSG<(WV$+4{|8cRuMcN8iLpGl1P~ zXFJO?;;ln2dxfBtrmX7D-JZKWu4xfpT-0W|=+5QS=H7Sk_U1GjhRn;d%(_%Gf?7?x zcD`JN^((e38FeCPj@52AHomSlT2N|-QAh4WseepNdu5oxyHSPB`%{9R*j2nUVK+Ac zdFCC!TvvBa=6ak|EA3x36ZVHDWUV$LZF5`p?%wO21K<*6Bhb!>PPeAc8dt7en(8&7 zm9+M$&ta~7xhm)T;HWI1#+dU+*x>na{hECANFDe6ZA+ z$-6WD8gSS9?P_SST)x%Y6z>pG-)wVT;Rbmv8^>eg7}5Y)@~Xuo+*uXPB(7qqk$Tvz z+04aO2TM-)AY14ZRA(AKC2&h1FXH;4a2Wl3K``FTPye7vhiu^;C6D$mzI6u(Pg8;; z{tq=zj~BDW-qk)E%GF|`af2j-tK+p&kOoQ~H5SAAHMGq&S%(DPo#k^q!T`U4$i?CS z)a#pO6Mi*$eEOM#?wZv@ z{WZqpx;`YK4vfopvBf;ribn3=SZY_9>&PaU%DA|MTPE4KPfk~<>~EXqpMn=})|}gk zo?{g|jg}I6gwU>rZ|Zep_2Wf?S}x0WY|Upl=M+`VLO3{W`JC&mA!`v1x0pe7TaHcR zc^4?oi%sL3o4PE*`1rkt=hE!a^Oi(#=e^}GFo9bvo1|!>%8E+Jz8sy*>fIu@QqC>x z!xTZx1B44-_8GnI*2+7%u-uz)JfH$UIiV>+2f;46TvkK&5u9vqQll*a=6j?zV#p|C zCTxCobptZR;1U>z1y!4NnZ$yaQ1Z&Jcm()C%w*fK%@=p#L0opWKWV;9CH%$|?{Ced zL|Po0Z(^^vPiH%DUuj0&!}0DB{p*=k1>B@!NnSX3?#*+)BLX=1YDB`vqz$MT)JcOG zj?Han+}q`^?HB@98=`cS*W=WmHw^8$q*zxPBq!opk57!%%R0AVKl`8UY_zgg57j+$ zaW-#OqcV!QVUDmVb>$NJ(_ts=XV8F`=V*;do!xH?Q@?=vitBZbmFViLzE+-tz!Tqx z`|HfBCzkq6lYkvPCg5+J-j(*5p~{9h7O!B!E)5&}PR-^V8<>*43_?Mwx0U{|ze|)P4v2di}eSjV*I+y7q3fZ;w@Lu)+k}JzfkbsP+csE6%@A zG-IMm9eV1w{qFo;sy^H?EtmrlB`LISntsr0!Si`wR9m!jD=h*QQ1X&Yxdb){cg3v4 zIX6A0KDZmkJfTp-HdM_^nWS>4*FXZlQ%tOmE%V2}RuNaTDbJmHM1MF`XiU$UbtZJT zi&-^fz6t*Th|@CBMEN+(O9rcKGS7%x9p@zWU)4ZcH;YF9p*Bp@wN_R>!uV-?DwuNi zDZc1DXZj7LxOBb{Gd^8NV=xSBLianH>|R?!?VjMiW%>qs-Ez~MgkYKm>ojNGzskq? zBAqxK;+!g{G;^yj@phF6hkX%~gLTYLU>D|N@;wCj1crG}Qgc#2@PLtKe>h|qYl1cvxcSU(y3BViuh!S209aNk6GnMpA&~}W23{VRZ8Zdmn7BGe|aZaD8nm|`9b3ol=TQ}IF~J7 z^tZGk*K|FDp5>RTr}r%mpDeKoenCC0HjNX9U^&pVKGIMBv%20?t|s|uetLpYe-Ecd zrH7(s7gGXBpn??@(v%!23C&#)hUBa>kk$leOC{wl+p~5lTVjN>)DxJi8c=l$l@H^E zR&}+IAZy>Ou()sL&Q`sAsTEio=7%pRR9>_Q({k~p2l3-r))In(>R47nbO;?K9X{Pg zAONT|?)vYtl8mrv*MM!B(dTM&z})Ex9@I6;^}Jl(DJ#N|qo_z$H#>yJ+56^Cw6=kG1^B|#b2ZD)GLE~c1uYnJ zVv+KXP5)&kIk)t9U~t7x>i!({GH*4t4!p<4vA}tPEkiFTV$g+VA#-Z|`F(ZV={Lr| z@H|^R{8^^L7siF5R;>ch^w$g#KUyVcE^OoT*_ET-%L`?N=@mL6VKs5Gi>A05MoIaM=L;{KW`mz~ReN%zx?{AVS<&LGdMy((B!QxGvdye| zx`JojwV{9z2*o08Yc5ceraC9P#}{(BykNQvJS+ViAOzn*Bshz_d_^-Yn?-2h#EqR^ zw^M#BMTI*#>FNb;USu`(AaA!ieCiooO8-pZQePU|T;x1=#m>d+Tlj%bj=HnTECZh~ z5f8s5$CJkq{#-3M@{%g;l(|mX$$s8?^DM6S8ruXU)30qWV&uGVH|m4iq&V2LTxOlJ z3@MqD6T`krHmj&&mO&nu^rjf(~n0jilhhyH-xEX6=d4$k^_r)4>e!r~@PeRH& zeSd||J|`??4l$jYO;1VUT+<+D@rwS}x zg@jiU;NfuEsJ}lSduz{b7`7n{X`=}A=~as*CFJ7fplHJa36sD9?{CWA5SeAELxRQ< z0=7MlD~4BB-ToQv@g(E>I%kEr#n^g(jRb7m%$q;;sCIO{^FH2$)85w_(Cpny9Z-r@ zZ!^JYxh|yxob6LmvW)_NV1fha0dVOvOV4$E04h=47*lxlWjFFpj88NsFa&2}bu`oJ z4{y(GVUEwhTg)d1tiQT zlT^Chi5ib5syp?%8$%AM#us5Pzru8&Xwm!irym_#e}P(MIVkY>#ps=g`~0!~DcjII zO)+z>Jq3QZH8f=n>pbse3vfEiYJLoV3K;d4;^wPTM_+ctdKpfoCXR9I^?VUK`;T(w z1%lQEbDdQs>3J?F^Y+rJ4Ia~2-b#S}dC=w`*Ym3RR#papZ_yX1aYc-zkPvL@`hj^v zB`~|T9*CyN{VXNLuMxQ=`*Nhdmos~ zdf|MBvq5w+sq$ReyYeU4)%$zD^>r}cXR~ql7rXf(sF^-vM$=(}GY#(%>_4lBS_WF0 z=JwoAVc+qtVZ1tf&w*1WcGZY<=sMg_W8@tMxqOa?H=nQ{CFMALJa~0xJ8IGamTf_$ z@00CsHCOKjuvbz=Ji2bB22$%Qk*hCz^o^cI$I+O+HmU^AiorQaP=6>l3;8$J9F2t5 z&jZwyd~t#5&JBFo*=L|SoeD1pzq|H9AMBecoy;M~fh)xiQSt1A4dgKSSjTay(OAWI1KQu6_0K zw}2@eUBR)FnzIh!!3at)%e$m6LFS84CNGych$Q@*w2?Io_&t=PJ12K~20Ecx4~j^k ziy2XP#imE(cm?pPhuyGxRKob)HU%-Wq+e2det|ffR(Uu`qH2nuyUD5Nww6>Pi_J`a zvMV6RjUa)eB;n-Bx6`EV`1mhT7NExQ&dz=056)CJ=GYS!-BA`!oD2Cu*po^J9{aW9 ze~XqYUZ(JdC=2myw=$p3C%Qa~7I;fjY^Gn>Y=wWx7B35@PBf05Re;EBAc+u0kZj;}<&)hq= z4-vz~3gt{Yxs)A+9~De|3xf1t)?evY>KofGzH|jLn6&h?k}pVU z^)HX|3bhpf4*e&GSYsnY>Q~))Yks51#Ip}9y;J=(Zj)s=P^PV7B8O>7fmuG$a$Sb- zk2AAp$*H%)mY@99_hZFnjEn}_+E3g*vEtA(r>6Q@Nft`yhIoNgeH5~qQY)|9QdGn( z93=+QIFPq~M@H>*zWicge(>nkx7p;ayH2$%wZBQzg0pm~dk>ibcoC#U();Ml&0hTl zF9ckk=GeiNa6sL?9$%7>K=uyc2_RX1tuBUTHL6Ak6ul`(rJj_cUMdhTK=vD9m42fR zZK(_^{nPOobLyHX+%pPJ#CDARP&kBES@`&`B(@K&bdypR*xcBEt5Vf1ip?_r!I_JR zSV(!1G+By{nNCgO6ZrXq@)je}p`2W#_?rxT_F#&)GC9ED#l(*5fe+!?1+Hs;yq+v) z{{F@DMi@BT6}v_5=KYmpvFSUN~V1j!E;v4rB%km&Qd?(=5(8_ zE;`!J;rKhTc1^oOZP~!YURcLiE6{3)&^UDAW1WY3{2InWL{6J&*qt!?SY-3N1%A{S zaDNowQC*Mp=~J|I<FT3g2 z1#9H`n@iA5D(qBS3TF!=+eJ1 z8!rKYPbitK>4>gLh?=8+cX`Q79ctrLWjT*bx!{*L-J8{Xb^sWW&K%0uo+LJZBACNV zDXHz}+tAo1e}hq8k!+R=PZsJ7`Fxk5bie6eRGFVH+&v^?-LaTXf%Xqia$)%OgWFx* zidU_rEv_LCCvq069{y{X#I-a*I%r;&rl1T>1o$btlnPI8zBm@K#(xSa&;EhM=nHMF zrJoj6RP(^zC;haBL*yy<{CJ9ksqqimy@7O(a~(Ae_uI)F)|4!iJ;S)v;4LMt)PE{0 zzv?M{??_+UJBVm%W%ZN_8H#G2;%o;BSMYZ?m!B17(*EXKEmd?Wv%ZtbSO{+sM@6`t^%BBJhkE&jwRvZe3%xHN0eNT?&$h<$Y# zp?2GV*}wDORCR-8Z9S53aDUW0p^c4pv_JUfjs63#UfRWToAsuO(|uBUTqn*DWn49{ zHr!=09lz-!9!5f0)>41z>$2+cg~+4#wPL4ynnxwB(kS`*b<3v73Kd3%nIfT=e$|Pl zCnV}viJ7$L>&vc@{(SFBP0NSQN}gWc)ym5tijrM)^50nKDP83j5&^KyT8%Q^A4`^R zh!YnYL*PoNB_d$W**VB7?z|+*yu3prYSZ{y9-$;`{ad5uhlgLL$?fpa^~cc3 zsgNbStfk(2b}Q()rQE%jIPlVsm8t8eyM;@9Q4NV?{fv z#6#cv_%IU#cic^kZg2tMBERN`FFQde2O0Cyx37~!3RW~HN%A9-(-D%*1c@A-{T-0m z|93!Q<%TW=237x(2bQiKVIsB~2f34k7E|6Y zM7qNWaAoo2)yV(ciW#XgPfQ!J9xyh$A=CviH$I~4(k!cPaG+%#pNT9wk;FRZo|ycUDDSuX;iEu`eqe3;x|@Jt~H$w54Hc3~P0aTwNW7h#4;DsJzYSB&D5NCBKP3 zJZmZ=N{=JOEpU$m-bK0xGUYT#Cw~`@DQHqj-M;XsfqgM7eiRV0i40{m<0<_@UD5yZy*~|nO*UWO45`B(?Vn?5 zu^N9IoK=K9Lm92I`4+|G1EEq;aK+=&sl&cH7JX$a!IPQ^!*;^=NcyPsyk~ZT_KCuw zZU2RyJsu~-I>%2Waso=K%rN!2nKel5w1f?$TRML1dD!liaBz&QG&R*(RRs%XNTk?n zLobzxu2$HIw7`WvDwtXFD68%)sVZ39)AiivcRhDVQwJp@3p0%ja=X?CU`fyq(%#aq zs-g);>MQwB?`c=wZJY4n!r?#4AeUa4XX%$IX^(EZ%7p`ATiHfVLSNB4$9%yVk<(op4GZs zo?e)Nr~5QmSuYjGW6Ls0vmX{5eB?rkNmROwXo2S($Ds!3cX8zJM8hs8#Cks~%SS1B zgN#LV-WPaHcNx!x956sblfn>ADA!5R{&|VapyD*)`R*V=gXpo-G~D(`kP!Hsdt(#6 zr^h>rhE{^qxqG=;Y!>0~ewsibKDWC><{GxhzejsA7q{g$Y@aYYL@!nUVSdekanHJ% zw)^UvEQxL4u57+atorqfK;fHHC!yt>01|jPnnK#_+Nd*t?!G`giJK}f(Qf(&jwu5bujp#< zV3CxB0T{AK<&o=icORZ1pKQcDk1#uJq8)rWcAW~w@Z<*4-A@qO;6GI^oC0yN;5VS!RKu`E}?Q;-&e= z@?Nr8%lu9jZ0-y7Ah61mGnQ5UosnpeL9i!|7CHyh6Ma%wF*nQfBwSZhM1!(}-NDctajXaK$Yc4ll+O3RiYdCqJ<<}FqWlvvS zRRFB3+7pk{8NV3&1=b?EX2GJ>B07f0kh0(|_5}l&V7c=|v6;H$Dow$c!W}k2%}$g* zQqGQ@2B-$A=ZV$}U_%Vpq5$bPA6x$tWok#kC`Vd)lp@PldS2TBZu;>pc*~PQ!ZsGE}Jgyouo+WEnd_j(Q{|XN%;vv<`w|V7q z_@nfVKjXn=iXhgDh~Shx2qx6)jnZ8mmdRLp4kd9as7q;n0#eLl0Tl#Gvc!63YY8A~R5-nFWxX0|-hq)>w z7L1wirntbeB(Q%quat$n&x^;Lk{*1L^IZ6nwEn4j-s<;{ zH|PDU()d}o8%4oadL$ISDa4d)*i5Ux;om8u$I-=ecoAik#Z-OUld7WI%kYrl3iX+n zQ_4Jvu~s9_*YNg8l=}k`HeJhKEQ{(H)8dvyG0e~jkYkbTPfW19_$B|a2&^aFol@O z));olri^*-zq^}b%sL^G5BjPHYzUIM1=pOsXroTSaE)H2G^+Q(*b-`dVw9zUGI9hG z5%UChWTw+v>A6q z$_dLy2nnff0rFi&jssRhfe;sAv66*AnOXvggn0&`$bOO{Qnht-@_8hQs%28K8biv~ zkKngSsxYMN$Lr{Lhkb0YD}%ytu2cx=^~mpFs1tHT$t=Fd!jlsXJAHi@%>`tN!r-yr zf*gg<=nF}*I#NC7C@_ZL=kAKnXK$b%)3c2K=0s>g#uvQH`%`J=H*7U|ZSxTv;dmiV z5(jTu@EN1fm@fjaR;a$Plys&Cwqxy`^p;0lTS89XC80Bo)pIaH$3iNFBcZf`%;Xkt zTxZiY?H%2g2e#*Ysg0~bpS*-1B#q;5BJH0QE6^yt$(%1A2aSA_V}{b8ez%25wIYOX z>3XF+VL1jN^!soJu}9J)DW{{z;!zB5JDDa{8SKdj%I=bA&@SMjS~nz(6#kA+UUafEAcu;y+)h-9pR{}+AcvB)+(1+bBVm@CG~aOoJ17|} z=qx(PPntdu0H7pIHxOmUNEqiPwIoWoG8Uc0NVsAqwS*t>BuG$h!$$5K$`Ce?-?aV1EoMj6>>CB=mY|6f=_^!vR=w(~s-kXrQ0$bJ zj)JgzviPqBkd4mL@0R&cM!?Q-B22IZ`&#{*6Fq4)gITg4H(rDzR)WD+0MaO4&Nr}y zFcA;zgj^AXkx6IKusx)>!vI0~cwjzcoc6m+mf#|VB7_lUVrVm5K2mWY3=J4_$!?o& zKp#BV1oU4M@c%IpNl$te_-TytC?E#~3YZj-F zXWsEmyyH6w$U%hyrUm4<|MeYM+EsM&fBEsyPIS@|danlRkL5+YAYgq$RZ)+lr?8*r zjGfP;D{Op@p%QJfz_9sGwTM5K?t17~!=edOCX{qX=fwGS!L-?kRG#Y2->`b?gJ1QE zu#I*4p}W6!Fz8~~GogE_J)1hB{l5f7 z7#3+dmIcDqo>xi9Kqcy4=gb?3n$3BqXrdKrDk%(8NPL3Fl~bzjRHHN7pEzcI=|d#< ziqz`Ug(3giQH@`SBw;b_tmXbLQT0Mq>tl!(A6srbS|AmGmaq0)t~B8d-F-N1#4jp6 zD%-z98ae-eD|pe_5*6c(?LKV*h~^|+1Z8?@t|XeOp?1-NE1y255vFw?(!NkvrgcXJE|m@v zmh#PexyzK7bH-QUfp&?xsiWShPHc1d-DD1q0^qJNv zE%XICHb})Y5tX9cm5|FN2qCydNx*E)KwDL4v0}oB}C8_~@OaHpf_6p({6C1fO1n zb0$F*i5c7IwoBsrONs4xA=i9v*}M<(<@|djj9bBQ1SWC~nNhL4YIKT8O0#@Cze{bg zy-=&FWjZwCy?25d(y3^CvKkUtrdpPi9P%>O1P_TJG*08a_|pF|DW8DP+Eduia4=Hsx!u! zVd%S}I!@CPYPFrx4e_C-e{ceCj=(^nC781I`1KyCZ%;~`PlBPUScAzf`WWF zFDcc|X^_wTN0z=N1h24wFv=g!6=AGYhi?llMcpYdp?EEdA#f$d)M>Ecy|X)-W=?}D zG=9f@tTlmVMT6=f{-XvrFZ~K<5xpJ##`St#kV+0YNOOlL5nz8OX`3#SPLNU zP0OtFo0iuU7u3A}`x_-jdqgaFLM*5}jV2>Sd%~OILWn>1F1%z{WyXp2==_8TGxjtB)o_h}d3^&r{zEWn1yxkx&$c=XE|g4z0eiBWw;Da zLW&|%{ccN+r-LOsc0jl0mAop$pJF$=tBefvyKy@7kt6|C@Nv0q^)7mLDdi|70OKoiVR8XOMe=AZLrZ9tp zG?x&$5_B{q`m8zkb^)(D9>U1pXPx;8I@(Wg7pWRnvW7Fy?523<9dpg~SPSky7;=qo z>?FRsWvOXup3$)qnE&QsP9ISX9iSJ?x_{s)e@J?!i_9*--yNc9b$sbe%Bj4~Z$uO4(*ddTP?B0Kgk^3k5FL7{xfXVPRk)wP z_GuE5Zd05JiP1}a&UK4Q*Nf@d>!c(o7D6eTV*qpIGHK&}N#lm2vGUJYqrg!m;3$3j z2b%T|sep=PKm|g!=-sT*32cSzoZp}Gmr@$m_xEK;;-bnw2*rm*eCI_32T1f72HLVB z6n32?ljN^y+;Z;UC+%TH(d-%0kjmE6(l%*noj0eVju7>H=NNDsn@^~lC?3vT+-Qc6 zSnFXjJ8Z^-e@XC=z1&Du?=tX{D_8BJ>#Z*Pi9D44;S=@jBBngWahrWzvm^zg!4&!m zYB2zBGkTzPkyvATf56%ir`}~>dQaaXkMzTKN{+~8jSj_;P}cM>l|&h`+V#E6XsLj z9Ic+j-zStTH>M2v$f_OdjBT|eF`ZN1fH54BmlKD42n>mW?-O{NnPv=|>^MH(ivZDU zMdx9kY?L0Kdt>flAC@o&y2AA3M%C?hzj`3guumz^m`|x=vXZHWF^0RVWBmcg$s3;a zqYKP*x#RPe1KJcM%J5@?nz3O9{K}g9;P=u&jt0wgKXr+57l zt_H88h7rE2sqt*XWwx7{G}-MD$oBf|7w)yo*SoIvW8T>CAGK$9s0c1|UFtP#v{yCp z0Cz8CdVNGQ-y`23^O)bfa!($3BKH;`x+P2?Of=|&4m3v@bhF)vP>bCj+0A_wKe#jbndX7!OLM)B5k{!g)Y&b&S7Sn18CuNGNW8-F87mR zn(vH)`O~!D<5tx)63MBK35;iKAu9<{ZX(7s;w|1e#2RQg=u&<8!&WW8j=?_uD+hzu zsKa=zbRir?7qmN@=lD%lEP%whH0P2b1+?o{y}AF}0yu44iV zE-pj8x_iXdQRACe7%ATY=FYPsj6NNMH zx(+RJe}Yy|-HzTvB*G+HDyDLiV^k(g_!F>)9WUC_JR|h*etUFxIl)~9Z|1pGl>wYO zT>Td0O;qUpVPLCtd~LA;jmv|o%}J2xczL46N$|w=*leLWvw`pC^Y`XV6t`n4Am$3h z4wn^|((kEsq5V>)pOSXc@3l^rnL_w(!VZ_2TGH>uU5^uw#vjYvj(2;2G}q(ZmGMV< zzMBiz-Cqdv5Fl=72*zrt!jjy#cc2bh22Bkf6O`bnc)D*$68L z=hD-U#m`$VI0#)MNVBv6DpF#;)BcKoKkCa0CkWNFg5aNB&IJdA#`kREyCs=~ z_IFsM{g&Rotpi`*?EAmyOWjK1+E3fA@CuS|yBE_s$JEnmV-wTLIIoHbNkpomef_g! zO*nR~i-Z;Cd|KmCbW5I>s`-cd>gAGvbjOisDhK>T<~0=b@)CbZtmivy_h@qW(-0*8 zhfB5JXoY^-yYz0e@#*RvI7T(W@Ys~MHi?dlwNiMQM%^VQwyxpk%cqr_AxOe=*s*3K zDJ}|*8WHZf0U$-}fW|AVC?+zbSb?%cH$S z?-V~fm?1W7d#F}pDO-qO))uArZX|X0lW0n5qS4wXQEhH*Q~SCGt(_DbEcK%UHenl0 z=&JtdL2)~9%c6{oK@3?rU}1@mYu<>)j!eKKEVoH$o|EZcY6LHinar9pV;=HUZt(JQ z91IVlAyjz!Ld{0d$0)T*+n!RU6Es_8ue&LX)Fs1qu17FZdRyem@-OZgFDaqAZC#R; zCf^Vum*BuMGMi)+z|Tj-@TgRl-LrxDsY#E1lPn0!t-LC5(UeFK_3O@_Czn+nWaBa^ z_vj+p6*@3%hd~&j)q@9rx6dBsTY$YS36`wEbki}t`Vq)VcLy@amu06C%r$J?Nik+r zrZ07F8__e*089G(-XHLNsA4L3obP7Hxxy&lkC(~TxAfJyIjJ9J<=y+2{nZ2bM<_ruW@{KEwk)9(e`i{0Pn=Z(HUq7L=L8yOJ?9^ujsnCdUx z+_B)ni{53(aHh!}^x$8JX&t}H4c}y^FKyi!-RsxG3oVf%&V7+>0C(@+P&iu2&8NA% zWLV0c^JE#vZo0@-t7{&AY!KDeYWr!kAOgojsR!kECs05e`^`uv`=08trSDSlLsUl- zvZDt$!V2%AFy5x)s|bmC$bI9Hix`*f$6{CoghcZ-!O=5QnoX9>N!nPWsb5v$vUBeR zj67O4l2$c19%YIjG16Up_$|ZsH-r}8t*a|B_11ja;rC8P7cr(Vj%vcON7Ui>ZHPtY0`E=RSCmPLZQJ9VpPTHMe_ziDq2MwPbi-A-Z43NboxU@-|Y5a@BISt0^d z8~pcjEETsv$EVZ4rKvB4*WgYsvuA}m^-tv zV9CT;{9?#394Cuo*wXOd5fY}lrqN4K}~OdN68*EzNK$`T0g?k zl#SNVltq;N{TLDaggWQpK?BmKe(dl8UH+uwY1HYF=09E=salns&SLYsMr-R~WETlH zlm97|zA3L4TQ^qORfZG7b@=EbAyZf2>#ngX{WR+y>5ZD4a-GwnIo0)226t~&((b9n zX3}E$?U{N~K()_y_${Tq^f4a6tXF?P*_T(Uzxr^d5$TFHc%naB>G#JnrzsF_ z&JYp*G9NstKsb8$!3XNK$N<8?D(M{`-uY zf;EzNnFtO!#E`l^RerW%fq^jIFB6sO`UH7PH#nYJSUZtjxZfuFSJ_nVK_9WydY%2v zyt=BCC`EL@XYeH*FNm_kORWP6FoX~3s+{UAtGnOd$*fY0u`Fl;CS{&`k_p;ls zsn>N4X>n`N4~vobIJqxnGJEd3(f^wGeZ9#w?0!FQ6@v@j#b-SY?BV#pv_1~oeuFXP zC`=yfdM~Z@jgVvmtQKAJe0GA-F{?1ghYh63+)s_u~wkQ ze=7NsU-qLvsflz4(n2CnrH3En14rCIm@N7GNAb?r5x!C5870fU9hQov`okCzu z@w3afpJ%MI%teu{Ju_dWgbTWz^oOojc^;|*X`Cg-##Kw&5et^35yxQK8JA9-vK4tWP8Ir7iEyI%e;^d$YRp=THTw61UdR0wjfRzfk=t zHu2zI2zsQ23Lz?rtyT8#!~I+-y?Z4&*p+Nj5fPo-K4s{tEnJ;-zG45@q0xbu>AZ#T9dvb4lhA5-B$lA}%v5IUx&*JCuHexfZ~k*! z@l%LQ!NY}qe5e+pM%41YsQn{AUhoJ|G@ruekG#;LTSkRmho2&Zw+a%bd;HX2cHW|K zMHd+z$TkEJw8~d|6tMtefeYzX*hv0;_y)tFf>QS6);%6c)sv_Lo6QG>m3JY8RLNW; zdIU5BtapR4316Wzut@dg41V%fhbM4!Apq@YotK!ej6Vl-S#R0;jhU-e&j5gn+ztLf=kOr<`yMT{S}`4 zGL8}9-_mu(NBLi0Dj6e+GGV2n#|Q-i$Xr+rhgyC zz;J*3g{iNFb*@I&?0SVZJN1PH!>o*WZmp@@a)yCab@M}xwsMgMg>QN4hwk#weKu|M zPm;vQuQsSbSlfvM5ea|9dx_+GS-do621t?|8Nc~z_3S~KraxN>Ht*;XNxU@WHvNxT zv|R+4K{+xXiZ=o;BIRZvt%fD*@Ydyi>O!k?X4A+#Qck6LTfk;-$1V?KzwSnvP7}FT z`|+SAY#-pO$^hGjmEq!M!@@CM1zEsb=xlR&8l}`yL-EZ4nG0_Vi$NL1QkXO z@w6&Q6(T@8som2rz7JAnN`Xvu^D^Dt+KpN?&CMjKb|j>prG9J|5r>|o>SaQ7d~zc0 z0<0``$yqgMFF*Z972Z%9028KN^N3XrF|YEgFU;2+^x@_BF(ze`Gav z?7}E_?CJ+1Z}?ohrdCBrajXfq1M=@*HNX2P(fn?+>SaTnqp&c#N!&|BSX^oxEX#RB z&CGy3)$IV)OnNW@qf`epj`fu?W9XGIjNzKY?Ce;LLOn~-Jv|pJ~^Zv*2)}LY>zQO7U zJad~L{}S;NC|6gBx>Kk*OQ}V@^A0QSY#6p!Kx>#&H~M{IwY73HXbxVlYP(n$?MEQv zH-KGEl$Liv>8?GnIu(Zj3_h_huQ_zC{UppAl}&b=^Q@z`;7;^-`j)l}oq79G&s$6b zEYgg9>-r_Yml3-QWCTn!##tMa9Ihe28L4Hpz*>lOEuI3>>`xNS1*E9M7MNaU8ad0g z7vBU4Abp12Pg>8uiQY@YZRey92rPOGOM77-=Ifcg;yU8LRvEVAw%n~QbQNhX3BcKo ztEI(oP(Cgh4CL&-dqr!e&d7I=D9O{teROM4;HbXckiHwddOaGy`Sx+r2|nXv_4r{+ zdSP0l*~IsykF}mk)4Xw?N@J_))IB0_xB;es%&%ZqK~`hU}MH9)NS{V9-#5cr(<# zGx^%5QN2|F1uo7-mw^f^V#qSDGX6-#%$KJHIYcCYMrnDwM(05<7H;#w*QX!Y>6+aZoFib?-;r>+Wwi3+iazd+~Z= zvi_%T&B2g7#F1Bw)kQFxM*GNW=_s({HX=PdmPQBLGJsjbx$mWUl@kwB{ zDeh>5%JUv#rl!zO6_KwI>QOPtd2otTY{rn7Qy z#0pJ2vv$jyz}MpAUNvDYfzNF^Qk*iqL+FJf=6)_9H$iv_y|)ur?Au)JbTTVVlc!V3 zflr@$K(oK};sN&Mii)pYkiy4PDt)qftd#^2ae9_&f6F7?O*2!5(-jcHWpRo`595sua|K^B$sy7V}Z9Y>iSdK`nI0+j1BY&ppf`U#cW!#QbF8E#sRShmDZXDA? zNq~ZH*h`(vTxej#OgSB;kEjGQeW_K1`l^a61c*KTOD!Ve$t+qJy&kC zU_Eqqn*L?oZm^1IrSx1}_PjNIq`ML&AIGwE4<}>{L{RoCE>z^Yto0;g(X}u%W`;`H zs9m~`Jzih^awMrp>Cd$b=HCNhs@SZ77 zAOYW1P(nuCnGE|bl;-cX_i_^2jTEN&UF~#}N_(i6IZ<5y8%+`};5NxtU)2AOh(}K8(%mMEBROYHP{e^G^&&>zE?Xac+P0HoQg`9VbCBXk|`z^;LZ$#xsz%#G?c=(#3f9A;?dEdmoz=`1{t%Mbbh zKgQ)Q^a8a8xKH9*As()j5w$C1Lf%Ss6IykyAz@-(EAJLLU%}~CF(!4f8~nsoq1xR4 zy$nA9Mmnog={a5=ojM&o44YR8y| zHoou!c)4CyZOY%EvcEdG^kbhBefz?0{9^-7=eDO48zCq%l+0U`s#~2^f)ZOr+NoKw z$MrnOu4(@vBtm{*U#pUg;Di`*-+u^r;$ur zNEl|Caz*`N+T@6h9@Oyx_5=^tEO6L40ev50sl8E6!wD>Iq1!uT#I`u+H{L)`5h4E4 zW`(0nkOK;wz&*lpNrFOX=eO+EixdJ?o(vOc47XB*2rduG|4g4x5M@9c_I;H%?x+)c zQ`!$QwJVqY^4q^PGt_$~VQo#du+B!-#E9jhfMMe7z0ar|it}lE;Qm98x2-e@(b|Rb zofn61c7k2kUG33mHijIlMxBkzqe68y)|6p*O-Y!3>{$?Ngiok?I)RG&hOo>K)Dgo2 z_7rEWUo3~m9tYn0(sLN%IO(U8<&$}yR(=E|V)-7IyUOn&ZSUe}NFMy)NtVO!=8Ov} zg$DhnzmK9@F1LGje=7Px^{NRJoiJfRb8g0R+ZiCacK3;`%LzlECWCIxen>Hp7TK^3=-P6RZI225F9)ynZt^O?(|c zj*5Tj3}OB~H9)Q(M`{Hai{Io@vqENRm%^d3!&lxpVUdgLsODY;# z7#aD@-<|6NltJqcz}tDQd?>cTfJno;x`f8pMU$%SQez?k>8|R2Ob)14XCh&YeiCl% zxUcdT)+iTDqw{dWU4}6M@%Beh&J_4TDtoyL9!qsGr`$)nF5JRKq{LH%CuS(a#faf? z$xGCv^vJPlLZGd`6mz(Xy}AVBk_|>z`b9S(F5*gOUm?>lbB`jd5%aJJ&$(W@RaP{% z&CLW4XAtT=m{>esu$mHcxdx}1%6xuT_(n$yZ{^+u+!r15s$2Nin08kSo3s0M z1DWql9xT%BRn>cpQA#?;FdWOCM-jx@o-$_wI@698r(>mJnzs6$nmk#R>!V^*k^1m=h`t}| zGgmOQ@>;(C(vjLVjM8rKm+|xh-h6S+7z{kGE{nVIoW??H9LufzDu{OgOyc7g2k(Ey zuwLmD-5a1O#850E&QuT!9}W90#xu+e{s3cW(VSQ)gQzRFvMbwnafSzO9uPUW)$UPO z=wK~=6@g|-J-H(Y)J8@ZrQlkX-&DEkIicn!F~X0f-+gqV6UvBA%Z-9dq4)zh6zjc zie;IvM1Ynm!*XRF43Eho8<@-x{aOoEfa>8)QQl3Q zy|fZlWp+FMB(-q$>VQ3jIiWE6Ted6*7;y^OI+(uqzCDmxhA`q%V}8NtSn)u!urn2V znzPR#UV`J51oTaDK5;u*<&q|IDNNCxs|n`!Rn)|MP6bt3=_rSZWmiri?p@F0>r1pV z@Nh8wLReJ#i29{N_t6+FMj&Kj>UH&T^bRGlF8v~!#cIcsesa;|V-bd@ssQqO5KmTw2GN z8H84c70Rn8+d;9P-WQ=2e|7LrqG#I3cpWSI7*J47I7X}#X@=V5`*BN`%RfR_)hK3{ zkU6T_@>fsDjJ*@XwMf-6b!sf|C4!Nq0+U);``cKbY`8*q3ehEIzn*!8mmJ*Uc6&ri z%=Qd%l{#wV7YDbVFQg_H2|!&|2FxsZh9LAbf(=_|uOX`6zuq_wzOIfb9p*Pl#!dih zqJ(PM)*%i`m-bqVqkPn=pk>iF=||+@fKQm~eXDJw)*69C$KDVI#WE7X!<-Pi+o_rT z8opOzoQ54dh5@8VLv=Ye$tLX#F#bs0v!MEsR2oReit46AnXlsBL}oua>-C7mRH8l* zsnRgHFPYTur8lTt>DEPOHfm^TuW)-1m^$ACdUNES{MN=Nm&mD#$f$NRQuFAy+1Fq2 zboWPZxASEv(uTj;CJvi65>;!*m@3nr;CT&lWDS`Y!E^o>_VE5?>yynwJV-0{Y zWw5#KJ*Rm{j!dnmFN3tCcGCU%ZY$Z%QXFtjB+ZNcTreHsrVAqKUM+t_+p2S?F5|QA z9)DL*6{E1bF7Oko-I~k3I$Ay%p%iq|4y)J#6S)`43UwfSC zDNyV8zfrLQp5O3TiUw0**>i~R&L-5p8^BH6zkiGGA6%_sVX)TYMO0%7Pxk#y=plWmD}1t>i#pYE?a7VJUvyoR6N^ompJU7rX7YF=1d zX)}eNC(Xhz_V#f(Pt)9b%5sO2!NvA%UiA|&2kcdwt#AD{o$9oEIZyXHW{RpX(NEZ8 z6$qjucndceYTZwtU{7^~{Nb}`wDQU!Xi2W?tbd<|c%J2E0sD-Szj z>yC(++qP>}zz{+x5DpjOGz$_A!nq9ElSg3g^$hov%V6~#*dSYfcBIn`34)tXDkKn6 z=wN`k$lQSqLP-B)tDb%;10F&;QcJFe61B~g4FW_QH>3dV=RA&%Y`%78RE1Xw?_?&x z{jkA|3Q>fJ(0)C!&%r+m<38`Gvg?B**uv0q=u+s!Gw1wFZM>nT; z{*DAu)k`-UGwDXBHSNA(a!j5caOojPTHY8??v}AP!`R=y^U5hA`(HNTHW+ zp1>l+o;{`zLj2S!X>bt0ItAJ3j;&F#AMzvtC-@_8R zwDa-c@$7RpvCM0t$gJZfIGX9EAbfX-!dUUemoqgC_OTq$mdkIsw=Ljio?c_k`z3S# zBJEhvSaU>X#kz&Hm;9dHsD+hXT%Y6%P|A=iOFC+By?Lf)bsgRu(4`H);}U|>7gESO zcsNx-_%IyCaJpNVgx1&DHb!W4MFg z7ND(0$RJm+$2;aG`);|9 zWJhDT`0U*ni?zhn|v6(!RN>2VB>KWEMqs-hL~9p!%28XKD2QdLFsN9v~g)~^D>o- z%iNvkn$UbCaWB(~b>mb#>InO=Ef`H`SwLoTLo9a%qbrz8I<@!9SKGoguMdy01XUtkiQ%6nNSWY_TB|_!3yNS&92-oD(2Ae8>OLxM|gw|1Cf+@)(@mM7qsve zJ*h>MHtfvZE)m){gp&<;tXehxl+VkJ~>;%tad;$ zG=7#?*g#?Rj_5pgF>klSGx@|D!gXzMO!6iD0@#1teQRy?g>oAYVJysIaM?tUdi2V0 zQrHY4io>U>y|MGLJ(sx~Wa?;hI#pVyY%FX1cG1EQABF4q-u<(Psz&f>Ma^T2NC*@C z6}w$Q$4-Fn=K$)UIu$mFcBV1|FF9DhiGjYsH^zg3ak2H6aazA^6jN#0Bw{!yKI=ng zM!94F&$UxExvf;K`B@~j8@m$_mXEmjf?!vM8J`JKItI2GM>f9hkFY{T+Y)XYKk*KL z{hAbAQyhIKf+vDyJk|Ly5=Y`qofTcc7d-LfiDdouZmZQPZyRaVn|H;c4?Dj}rph(Z ze5#gliqHmOAx4D14g$Wb{(2|+>&5EHlhKoNBUVl>F4lj%M6vwaw-ty<^^b3>CpSuB zU?*co3wywk*RxwC1w$La>y@&+FmTZcJ6hP-F}wwvxmX)MrCS@CIRRdoo^y))@eRem z!Oq6W!_L75&}U%d;$q}s<>BP!0TBM&z> zD?l7@uH_82Q3r9cF#>+QSh%@3{#Fn0;{;{}s`1CGlYju|Z`=F<{I};P77!;u3_$zq z2#V#&p%&n^>i^9XR6aL?=4XwDn+Mr|xu6%2kY8b7Xl0QNgpUX4y^{@-U+TaP#=AA@AMDUr zZ6zZ5O!Svi-o&6~4uytQ3il4~N^@QNhCB{&xK!-eas3Q>*MWSWn6OwXUrxfrMBdx@ z;=bOcUelAwpTqul`CUvA(o$7# zQd0Uqc$=5|H|@(xUX5mUfZ1*XpjMUC+kHk`oAx148H5&wgDaf6nl{Wa!mU^xwLZ|3 zE)Ve0&#gwKNB;OQ2$sQUsxW%Ilq9||ZN9mpIVHlai` z;D310ImeLouC6Lur|GVE7HKXeO0Y;z+gN;sGpk=bS`X#U4x=86n*wtw6Ql06sy-*7_@{!Ih0&Nd z($ew$KF9K9*@Ce*7pRDg&JyB%y>oNolA_zK9ZmJ5(m@U#UYNADYEyEoD?Y&@yZagmm1@4_( zmBUK6%NLSw-1UwaH)ZdB>}A!Ych6g%qZ57Y+m9<8e}9Q?^m5V)@&R)u!H+IDXM*(d zy&=19QJ2VOQO5@P?;$dBFWO>z8JH%&_|o==cP>k}iK1OzqQb=Z+npQYOGtgSY`iHo zP7*{HTJ;;FE%Fh~KckLW)Vmkwtr9zPf*wEdLE!D{jn~a&)Tv5}deV-{RFF8i#~{e< z?9L@7z&mZyM&POxWdC@8r%sRk;IZ|Y94V>?ORmxZZ)h0M(D@$hkARkb z^%_UL6hss}KJX_}%(6kr=BnR&z5JCI)@3Uja)+V)Q9%hcMgo@5$1k!Z-m;izBL}Xr zraI6U4178MjmQ+E@Iqh;h{I0!KzU|0mQrebeMbnk8%>Na_cEd8eC5sV%4*l@yR(IP zxOW#PK_?$hKn@xz#xjUbXC#Vm^pv|Ad%g;+;|_k|>P*LBom!e#@+hO8psrIRzp`un zb&MIZgBXv@$E5CF0a@o>qwP`)%cxSs=@b z>Lx6fmq9{=SoZKbo=`#7md`(H)d{0pM;NGn@W3y&j8+Mx7J~1Tc&XEJcej@77G!-( z=dD6jwmyNHlJqrTt}Wf&D4~V2zbuN%lx&7OIYsoiyr64D@5kPqswR6U*6QA&yBar5 zS&)L1p`f>w2WFGt#?sOCMV5UBJUcJTQhk=ozK7V+;5+If?_kBeQ2VwUpRVr6j=8T0 zRSu6DXlG)IeHm?u-&bc;+xPawL#){4ZbT2`W(K3)<5xN zWNu!tt#`ZD**Dz7tm?OH#kbYVQZ{9p*nymGCbW_V4Z^Ue!hU9GhdM?<%e|7xN`1NI z$k9?Zuh&y{tj6A{}m?75C`9pUsk09RI!F{0`KY_(g?apy;MwExtbQ^a zjdH)n$}$T&CNJ%uD7Ka9dchc{7xQd_$5>N4!m>Y3ZDr3)y6%m`=o}2Vd6+v1A4Yvb zyLQoUaJ*ail6f|d@gCB~r?U!txD^Ggx$0w^FF!H+>kRmKP=(&t6o%YY$`*B|kY>Wf zP+OwANQo6?)`>nGAE5~<1_`SmX^-C6M!e=ei96HHJbZDCg94tye$|l4rg1YNbF3X3 z$FCe8({5!mOi$VSm^iKPix%E`=$CPW8b13osUBamMgMlIR=j|EO!TE&yIq57D!C09 zN#>34a;@j31GSi#lyva0C>x^$gn?to`sp4L$H>)pe@c>72UZWI&KKrQeZE;tX za{D$cHxmOY4`$1H7T<&kig*&{+iX`g=Mibi4RL7uibZL#a)~aa~EPk%nD93p+W(mG!~lO!7yEFQ?4D`MXK* zR+>6S7zW=j>WB$6DyI`pdQ8jIa~@;3jFt9TGUppj8*p)s+D=;!IB6eeX~&KR&#Le2Wya z8FkDVCmLQo<u$~f`X5D0;U;Y?`lLd3|-JR$|>1UL%LL0R@xGZp= z?_sa`#JGhoKAnLv?4mo&9XW~5MvwqPAMTB$WUcXc=ZScNtGK8k!x+S6np1P!gj4`k9PUORJ+G%Ri4@T z1|DJ(I%TWuBBBI)R+K}x-o2kFz3v?)Wu=hAhGnzV$cv=|^7CwIv=cE0kXq zGPea;Yb=HkQ?6UP*ny{UlhEV4pAZdII%fMDU$&JF4^vT>?(`git693OlaeR5zMqD5 ze&weL~S9#W;M45 zerk-FN%%rb*CsSl7IuyHD!9o?_FE@?ZhPAl9s=d*3wgkd;*u1~>rRWHs3t!h8#U_0 zl7sj)S5Ls3A!Z7Dn7_hmWej)@iV4}&UsTf{R@7=5n-|uPTlP?{#mi1GP@@j?38j^jMKFDy_A)~Oo40_Am`U>S`zRuV7ZF2)E;KMva z^b;%6f<(xL9U6O)n5Z5>RIjP~AUK0Tik};t z0Yy1saN3$h<7toR&Or@QXtY+dUJfp@5Ql{$8wf`Y{m8&+X~7*WExO7Y^T6IZ9nCyy z>44a3yze5D&Z@fp5t4F>`}axdmyMKFPda80*fDtiR%djTx8ryFw4OS%G)J(6Cam^5 ze(g@hGPMxVflLH~_7x{Y@CQ|wkuEO5le7M^(ayH84`v-Y&7N1oR`x%)tDSZ$PDV!( zyA%sYvCAp>WN^E`U(vU77lWNub})+*eteh0N=`2v+;O#sO37;7Zw<0 z&>E_3Q!q>K`JQwNWwrfHqAd+ySO{)^5{4-S?k9~m8tPC3al?CiH4V!T?E(pAOIF6l zwBa~=@rx{?B)EPq%f`njzgnw!c{psy>z5a^2@YN20_UO;&%R9|uf{)$I!N|;w8|-d z;)W%s^{K}I>e%2}5>Arq)VH!a_4ITOQzs|2;$UF|lebD+jEsZM^NV zLsWf?_M_-R(@ie^0L4U}O`*t{gQQ}#&zI9Al#ZsN3Fe3EFb~|>sMZCJas#~M|m1RAG;&@8wNh*#`$(u$P zQ)o|ZX}1*3YbjZ}d|x)s5zp;T`Sk3@;9x}s?F8=6@~+Mu>^}|DZMWFSa2P$m`eYo- zE;jIYu$Xjx%hS0;tzG>UEPzSN+@qqsphrF)L-i7tT(5n9+<&S3+=aC8-gi3Y7K+&Q zRodsc5zisGhwIbbLH%)_OT5oQ3Wgm*2|{wWZ+a7r`6N+ZTZ^Zu1%y@E2obMLxkr4M zH>x$H%XU9@$`NjQIgjQ6vK6?tI^}~RkhIuJ9emM?SIr>yo>sSmguu{K`juN!61su^ zqx4kba0A|wwv4C;8Id*tgPsuY5XHHoQm0rF)R;Zg_YWhZxCKcI!-R&vhTy6_T?JYK zeZn3SE_P~oUe1rU4XM91X6rkEbGPC?KAZs&`c^}r}_#)ynY>_wc^^(L9Li~fwti9D(2{=^$$=L}AR zu8XyoDe)Z-OI88#x0hG%4FV+B9yh

S8e;UEx;vvvXyOk`E*Q(O!L0tc71Dg_Xu zyK;bkxCtqF(re=+Tm;jP!O}7BB4JovFh)P0jd~&^yndevGcp9ZLxFHj8gmGRKrBpI z)$>C@DE6BfxjWWE=X=Vo+@TZhRG6sO`O5@&oTX!6tNJ80)6XM$B@JA4+}3mWi3eQ= zG$r5mlJ=;*`$}pKU4Og{qg^>Rui}$qOl{Nuyi+y1AWJ{jpHC?nPN~lI`Iil!O0R^p zd3A0q?>2S_=58_dQAc(Sj~ZqCh#{-afm|d7?fu3&N9~VN4qBBqEj2SsWrms}LhRD9 z{Tgm16P>n_#SQ!9Q=O%~DxR?hOWG8b&vuoKE!z0@j_E^|rH!BDkQhfC!)RBx9JSJ} z@9+~#Un`f|SXxfaNY8MhRu?Sl-*iM-h7r0t8P|_kRB%kUDy=D z)uAIIo_uc^#T95{BdefyryHepyA%%2JX0voOMCPYrnIEVrc%*x0*$N1Bu!(z--Z_F zpk;g2DR*SIbLHHPN%340_R^FWei^y>-9Hk{rj~QfsBFvjcJ8C9ESecr*zeSgg(ew} z3SqO^FzmcbhFd-INWR{BYJ)`T%r-bZ}1>SQnME0p$T&pE!iXn6coD-r(T%kOzz z1{#sE!y=8`qD0L6ye~{RS9wzw(#5^6MGvQwN^vZZnrevVOC;JAdbgyVfEQ5eO>@mX zWMm)J$t*L1Tjiv7_a8mS*?S*26^xYU_I?XXPW!a78;R*SIPX`wzePTugx@E}%$@f; zq5in*3+cmfI`e<@pnnv3Pr5!1W)|ju=|P{o%0DXr|5FbN=n-9vod2jrMWw}_G^0;C zQdUGJ5kn{NAAj>q|Dz2BvZ;7D0m?^dTT?s08$E~#P?TB#nph7IwXlhu5ts(Vr08e@ zcC@fH15rzx09sB9XAc0V;$m-a4YqmGdNP9q{<^dOw;mSsM^g*vAAtf??Ifke9ADFR?q5Yqx{r}K+YN9Itx7$D_U{in)&^ga4^FLkA z@J}}}06727@kiYb)CIs`d9IAHoe9{<-q0BAXlQE&M&xG(E&&j~1aSRng#Srrea12} z{qO95vHk>qXs`e@n3>tw1VB1K$O(i`fy8ql_MG^f2k{S$$Ie*bO7;rfp$@Nav~f5@pPAoKqp0VQq=jG`yu^Dp7SAoY)J#l+Co`AMod zfpl1&1mau3GNWpzW^3^@@IWk2)9>xSr{j|m&GLyw*Fe{tCWvl7ey z$;ZX{zw`iU_GDc8A9@_zfZgqXU|j6~KXBQAZuqZ!tX$kYz!>;17$7NG{uh@GVEF&E z0V~gc;yODT0@g4`M1Fo?_*i%X9n1w7;+~c*fUzf2*IPR~XV8;@@a>Zjc5*g!bpF#L RZ0zjp+=vtu;))W8{|};h%|`$L literal 0 HcmV?d00001 From d816d35897dec785ba4ac2fec7cae4292e606c5d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 17 Jun 2023 02:49:43 -0400 Subject: [PATCH 251/417] import ResetClock applet from pkyme adapted for 64-bit save data and modal cursor --- software/o_c_REV/HEM_ResetClock.ino | 194 +++++++++++++++++++++++++++ software/o_c_REV/hemisphere_config.h | 1 + 2 files changed, 195 insertions(+) create mode 100644 software/o_c_REV/HEM_ResetClock.ino diff --git a/software/o_c_REV/HEM_ResetClock.ino b/software/o_c_REV/HEM_ResetClock.ino new file mode 100644 index 000000000..97face41c --- /dev/null +++ b/software/o_c_REV/HEM_ResetClock.ino @@ -0,0 +1,194 @@ +// Copyright (c) 2020, Peter Kyme +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#define RC_MIN_SPACING 6 +#define RC_TICKS_PER_MS 17 + +class ResetClock : public HemisphereApplet { +public: + + const char* applet_name() { + return "ResetClk"; + } + + void Start() { + cursor = 0; + spacing = 6; + ticks_since_clock = 0; + pending_clocks = 0; + length = 8; + position = 0; + offset = 0; + } + + void Controller() { + if (Clock(1)) { + pending_clocks += (length - position + offset - 1) % length; + } + + if (Clock(0)) pending_clocks++; + + if (pending_clocks && (ticks_since_clock > spacing * RC_TICKS_PER_MS)) { + ClockOut(0); + if (pending_clocks==1) { + ClockOut(1); + } + ticks_since_clock = 0; + position = (position + 1) % length; + pending_clocks--; + } else { + ticks_since_clock++; + } + } + + void View() { + gfxHeader(applet_name()); + DrawInterface(); + DrawIndicator(); + } + + void OnButtonPress() { + CursorAction(cursor, 3); + } + + void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, 3); + return; + } + + switch (cursor) { + case 0: // length + length = constrain(length + direction, 1, 32); + position %= length; + offset %= length; + break; + case 1: // offset + { + int prevOffset = offset; + offset = constrain(offset + direction, 0, length-1); + pending_clocks += offset - prevOffset; + if (pending_clocks < 0) pending_clocks += length; + break; + } + case 2: // spacing + spacing = constrain(spacing + direction, RC_MIN_SPACING, 100); + break; + case 3: // position + position = ( position + direction + length ) % length; + break; + } + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation {0,5}, length-1); + Pack(data, PackLocation {5,5}, offset); + Pack(data, PackLocation {10,7}, spacing); + return data; + } + + void OnDataReceive(uint64_t data) { + length = Unpack(data, PackLocation {0,5}) + 1; + offset = Unpack(data, PackLocation {5,5}); + spacing = Unpack(data, PackLocation {10,7}); + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "Clock, Reset"; + help[HEMISPHERE_HELP_CVS] = ""; + help[HEMISPHERE_HELP_OUTS] = "Clock, Reset"; + help[HEMISPHERE_HELP_ENCODER] = "Len/Offst/Spac/Pos"; + // "------------------" <-- Size Guide + } + +private: + int cursor; + int ticks_since_clock; + int pending_clocks; + + // Settings + int length; + int position; + int offset; + int spacing; + + void DrawInterface() { + // Length + gfxIcon(1, 14, LOOP_ICON); + gfxPrint(12 + pad(10, length), 15, length); + + // Offset + gfxBitmap(32, 15, 8, ROTATE_R_ICON); + gfxPrint(40 + pad(10, offset), 15, offset); + + // Spacing + gfxBitmap(1, 25, 8, CLOCK_ICON); + gfxPrint(12 + pad(10, spacing), 25, spacing); + gfxPrint("ms"); + + if (cursor == 0) gfxCursor(13, 23, 12); // length + if (cursor == 1) gfxCursor(41, 23, 12); // offset + if (cursor == 2) gfxCursor(13, 33, 12); // spacing + if (cursor == 3) gfxCursor(0, 62, 64); // position + } + + void DrawIndicator() { + gfxLine(0, 45, 63, 45); + if (cursor != 3) gfxLine(0, 62, 63, 62); + for(int i = 0; i < length; i++) + { + int x0 = (i*64)/length; + int width = ((i+1)*64)/length - x0; + if (position == i) + { + gfxRect(x0, 46, width, 16); + } + else + { + gfxLine(x0, 46, x0, 61); + } + } + gfxLine(63, 46, 63, 61); + } + +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to ResetClock, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +ResetClock ResetClock_instance[2]; + +void ResetClock_Start(bool hemisphere) {ResetClock_instance[hemisphere].BaseStart(hemisphere);} +void ResetClock_Controller(bool hemisphere, bool forwarding) {ResetClock_instance[hemisphere].BaseController(forwarding);} +void ResetClock_View(bool hemisphere) {ResetClock_instance[hemisphere].BaseView();} +void ResetClock_OnButtonPress(bool hemisphere) {ResetClock_instance[hemisphere].OnButtonPress();} +void ResetClock_OnEncoderMove(bool hemisphere, int direction) {ResetClock_instance[hemisphere].OnEncoderMove(direction);} +void ResetClock_ToggleHelpScreen(bool hemisphere) {ResetClock_instance[hemisphere].HelpScreen();} +uint64_t ResetClock_OnDataRequest(bool hemisphere) {return ResetClock_instance[hemisphere].OnDataRequest();} +void ResetClock_OnDataReceive(bool hemisphere, uint64_t data) {ResetClock_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index bc08e710c..1ba6fbaa9 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -51,6 +51,7 @@ DECLARE_APPLET( 20, 0x02, Palimpsest), \ 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), \ From 44135488271c5308f3cf6c47d0acc6388acf34ad Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 18 Jun 2023 20:39:45 -0400 Subject: [PATCH 252/417] ResetClock: UI tweaks --- software/o_c_REV/HEM_ResetClock.ino | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HEM_ResetClock.ino b/software/o_c_REV/HEM_ResetClock.ino index 97face41c..906761d5c 100644 --- a/software/o_c_REV/HEM_ResetClock.ino +++ b/software/o_c_REV/HEM_ResetClock.ino @@ -61,7 +61,6 @@ public: void View() { gfxHeader(applet_name()); DrawInterface(); - DrawIndicator(); } void OnButtonPress() { @@ -138,18 +137,22 @@ private: gfxPrint(12 + pad(10, length), 15, length); // Offset - gfxBitmap(32, 15, 8, ROTATE_R_ICON); + gfxIcon(32, 15, ROTATE_R_ICON); gfxPrint(40 + pad(10, offset), 15, offset); // Spacing - gfxBitmap(1, 25, 8, CLOCK_ICON); - gfxPrint(12 + pad(10, spacing), 25, spacing); + gfxIcon(1, 25, CLOCK_ICON); + gfxPrint(12 + pad(100, spacing), 25, spacing); gfxPrint("ms"); + + DrawIndicator(); - if (cursor == 0) gfxCursor(13, 23, 12); // length - if (cursor == 1) gfxCursor(41, 23, 12); // offset - if (cursor == 2) gfxCursor(13, 33, 12); // spacing - if (cursor == 3) gfxCursor(0, 62, 64); // position + switch (cursor) { + case 0: gfxCursor(13, 23, 12); break; // length + case 1: gfxCursor(41, 23, 12); break; // offset + case 2: gfxCursor(13, 33, 18); break; // spacing + case 3: gfxCursor(0, 62, 64, 17); break; // position + } } void DrawIndicator() { From 4f13bdea98145adb88613ff7108692f1bb226423 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 17 Jun 2023 21:21:26 -0400 Subject: [PATCH 253/417] import FPART app (4-part chord sequencer) from jddinneen enabled with build flag ENABLE_APP_FPART, not yet used --- software/o_c_REV/APP_FPART.ino | 926 +++++++++++++++++++++++++++++++++ software/o_c_REV/OC_apps.ino | 3 + 2 files changed, 929 insertions(+) create mode 100644 software/o_c_REV/APP_FPART.ino diff --git a/software/o_c_REV/APP_FPART.ino b/software/o_c_REV/APP_FPART.ino new file mode 100644 index 000000000..8a3098cc2 --- /dev/null +++ b/software/o_c_REV/APP_FPART.ino @@ -0,0 +1,926 @@ +// Copyright (c) 2023 Jesse Dinneen +// +// Author of this app: Jesse Dinneen (jdinneen@gmail.com) +// Authors of O+C firmware and apps: Patrick Dowling (pld@gurkenkiste.com) +// Tim Churches (tim.churches@gmail.com), mxmxmx (https://github.com/mxmxmx) +// With great help from tutorials and examples by Chysn (https://github.com/Chysn) +// and Naomi Seyfer (https://github.com/sixolet) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// FPART: a 4-channel step sequencer with staff-like visualisation. +// To use, add to OC_app.ino app array: +// DECLARE_APP('F','P', "4 Parts", FPART, FPART_isr), +// +// Calling the files/classes "4-Part" (w/ number) causes errors, +// so the admittedly unattractive "fpart" it is (for now)... + +#ifdef ENABLE_APP_FPART + +// refs to other files, data structures, etc +#include "OC_apps.h" +#include "util/util_settings.h" +#include "OC_menus.h" +#include "OC_strings.h" +#include "OC_DAC.h" +#include "OC_digital_inputs.h" +#include "OC_ADC.h" + +/* list each setting that should appear in the settings menu + _SETTING_LAST is required and must come last + to read these in the class: int root = values_[FPART_SETTING_ROOT]; + to write one: apply_value(int index, int value) */ +enum FPART_SETTINGS { + FPART_SETTING_ROOT, + FPART_SETTING_SCALE, + FPART_SETTING_LOOPSTART, + FPART_SETTING_LOOPEND, + FPART_SETTING_A_OCTAVE, + FPART_SETTING_B_OCTAVE, + FPART_SETTING_C_OCTAVE, + FPART_SETTING_D_OCTAVE, + FPART_SETTING_LBUTTON_TOGGLE, + FPART_SETTING_ACTIVECHORD, + FPART_SETTING_CHORD0, + FPART_SETTING_CHORD1, + FPART_SETTING_CHORD2, + FPART_SETTING_CHORD3, + FPART_SETTING_CHORD4, + FPART_SETTING_CHORD5, + FPART_SETTING_CHORD6, + FPART_SETTING_CHORD7, + FPART_SETTING_CHORD8, + FPART_SETTING_CHORD9, + FPART_SETTING_CHORD10, + FPART_SETTING_CHORD11, + FPART_SETTING_CHORD12, + FPART_SETTING_CHORD13, + FPART_SETTING_CHORD14, + FPART_SETTING_CHORD15, + FPART_SETTING_CHORD16, + FPART_SETTING_CHORD17, + FPART_SETTING_CHORD18, + FPART_SETTING_CHORD19, + FPART_SETTING_CHORD20, + FPART_SETTING_CHORD21, + FPART_SETTING_CHORD22, + FPART_SETTING_CHORD23, + FPART_SETTING_CHORD24, + FPART_SETTING_CHORD25, + FPART_SETTING_CHORD26, + FPART_SETTING_CHORD27, + FPART_SETTING_CHORD28, + FPART_SETTING_CHORD29, + FPART_SETTING_CHORD30, + FPART_SETTING_CHORD31, + FPART_SETTING_CHORD32, + FPART_SETTING_CHORD33, + FPART_SETTING_CHORD34, + FPART_SETTING_CHORD35, + FPART_SETTING_CHORD36, + FPART_SETTING_CHORD37, + FPART_SETTING_CHORD38, + FPART_SETTING_CHORD39, + FPART_SETTING_CHORD40, + FPART_SETTING_CHORD41, + FPART_SETTING_CHORD42, + FPART_SETTING_CHORD43, + FPART_SETTING_CHORD44, + FPART_SETTING_CHORD45, + FPART_SETTING_CHORD46, + FPART_SETTING_CHORD47, + FPART_SETTING_CHORD48, + FPART_SETTING_CHORD49, + FPART_SETTING_CHORD50, + FPART_SETTING_CHORD51, + FPART_SETTING_CHORD52, + FPART_SETTING_CHORD53, + FPART_SETTING_CHORD54, + FPART_SETTING_CHORD55, + FPART_SETTING_CHORD56, + FPART_SETTING_CHORD57, + FPART_SETTING_CHORD58, + FPART_SETTING_CHORD59, + FPART_SETTING_CHORD60, + FPART_SETTING_CHORD61, + FPART_SETTING_CHORD62, + FPART_SETTING_CHORD63, + FPART_SETTING_CHORD64, + FPART_SETTING_CHORD65, + FPART_SETTING_CHORD66, + FPART_SETTING_CHORD67, + FPART_SETTING_CHORD68, + FPART_SETTING_CHORD69, + FPART_SETTING_CHORD70, + FPART_SETTING_CHORD71, + FPART_SETTING_CHORD72, + FPART_SETTING_CHORD73, + FPART_SETTING_CHORD74, + FPART_SETTING_CHORD75, + FPART_SETTING_CHORD76, + FPART_SETTING_CHORD77, + FPART_SETTING_CHORD78, + FPART_SETTING_CHORD79, + FPART_SETTING_CHORD80, + FPART_SETTING_CHORD81, + FPART_SETTING_CHORD82, + FPART_SETTING_CHORD83, + FPART_SETTING_CHORD84, + FPART_SETTING_CHORD85, + FPART_SETTING_CHORD86, + FPART_SETTING_CHORD87, + FPART_SETTING_CHORD88, + FPART_SETTING_CHORD89, + FPART_SETTING_CHORD90, + FPART_SETTING_CHORD91, + FPART_SETTING_CHORD92, + FPART_SETTING_CHORD93, + FPART_SETTING_CHORD94, + FPART_SETTING_CHORD95, + FPART_SETTING_CHORD96, + FPART_SETTING_CHORD97, + FPART_SETTING_CHORD98, + FPART_SETTING_LAST +}; + +enum FPART_MENU_PAGES { + FPART_MENU_PARAMETERS, + FPART_MENU_STAFFS, + FPART_MENU_PAGES_LAST +}; + +namespace menu = OC::menu; + +// define roots and scales and their names; root names come from OC_strings +const char * const scale_names[7] = { // name the possible scales, indices match scale_pitches + "IONI", "DORI", "PHRY", "LYDI", "MIXO", "AEOL", "LOCR" +}; +int scale_pitches[7][7] = { // define the pitch values for those scales + { 0, 256, 512, 640, 896, 1152, 1408 }, // Ionian + { 0, 256, 384, 640, 896, 1152, 1280 }, // Dorian + { 0, 128, 384, 640, 896, 1024, 1280 }, // Phrygian + { 0, 256, 512, 768, 896, 1152, 1408 }, // Lydian + { 0, 256, 512, 640, 896, 1152, 1280 }, // Mixolydian + { 0, 256, 384, 640, 896, 1024, 1280 }, // Aeolian + { 0, 128, 384, 640, 768, 1024, 1280 } // Locrian +}; +int root_degree_lookup[12] = { // for each semitone from C, convert it to a degree + 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 +}; +// define values for FPART_SETTING_LBUTTON_TOGGLE +const char * const l_options[2] = { // user will set 0 for C&P or 1 for Reset + "COPY/PASTE", "GO TO 0" +}; + +// define the main app class +class Fpart : public settings::SettingsBase { +public: + + menu::ScreenCursor cursor; // instantiate a cursor + + bool a_or_b = true; // track which channel's note icon is being moved (true = a/c) + bool c_or_d = true; + int last_cv1_index; // for tracking changes to CV1 and CV2 + int last_cv2_index; + bool copy = true; + int copyBuffer = 0; + + // define functions to get values + int get_root() const { + return values_[FPART_SETTING_ROOT]; + } + int get_scale() const { + return values_[FPART_SETTING_SCALE]; + } + int get_a_oct() const { + return values_[FPART_SETTING_A_OCTAVE]; + } + int get_b_oct() const { + return values_[FPART_SETTING_B_OCTAVE]; + } + int get_c_oct() const { + return values_[FPART_SETTING_C_OCTAVE]; + } + int get_d_oct() const { + return values_[FPART_SETTING_D_OCTAVE]; + } + int get_loopend() const { + return values_[FPART_SETTING_LOOPEND]; + } + int get_loopstart() const { + return values_[FPART_SETTING_LOOPSTART]; + } + int get_activechord() const { + return values_[FPART_SETTING_ACTIVECHORD]; + } + int get_chord_int(int chord_number) const { + return values_[chord_number + 10]; // assumes chord_nums 0-98 and that SETTING_CHORD_0 is in slot 10 + } + int get_l_option() const { + return values_[FPART_SETTING_LBUTTON_TOGGLE]; + } + + //define functions to set values + void set_activechord(int chordnum) { + int reasonable_num = constrain(chordnum, 0, 98); + apply_value(FPART_SETTING_ACTIVECHORD, reasonable_num); + } + void set_chord_int(int chord_num, int chord_int) { + int reasonable_chord_num = constrain(chord_num, 0, 98); + int reasonable_chord_int = constrain(chord_int, 0, 32323232); + apply_value(reasonable_chord_num + 10, reasonable_chord_int); // assumes chord_nums 0-98 and that SETTING_CHORD_0 is in slot 10 + } + + // given four 0-22 note values, set to 10-32, put in right place in 8-digit int, return it + int build_chord_int(int a_note, int b_note, int c_note, int d_note) const { + int built_integer = (a_note + 10) * 1000000; // e.g. turns 22 into 32000000 + built_integer += (b_note + 10) * 10000; // e.g. adds 320000, producing 32320000 + built_integer += (c_note + 10) * 100; // adds four digits + built_integer += (d_note + 10); // adds last two + return built_integer; + } + + // given some chord (an 8-digit long integer), + // and an index 0-3 referring to which set of digits are desired, + // get and return those two digits as an int + // TODO: replace with bitshift? + int get_note_from_chord_int(int chord_int, int index) const { + int note = 0; + if (index == 0) { // digits 8 and 7 (counting from right) i.e. channel A + note = (chord_int / 1000000) % 100; + return note; + } else if (index == 1) { //digits 6 and 5, ch. B + note = (chord_int / 10000) % 100; + return note; + } else if (index == 2) { //digits 4 and 3, ch. C + note = (chord_int / 100) % 100; + return note; + } else if (index == 3) { //digits 2 and 1, ch. D + note = chord_int % 100; + return note; + } + return EXIT_SUCCESS; + } + + // define function to calculate a note's pitch in some key + int32_t get_pitch_from_note(int note) const { + // considering the current root (0-11, from note_names) and scale/mode (0-6, scale_pitches), + // calculate the possible pitches of the resulting key. + int root_val = get_root(); + int root_pitch = root_val * 128; + int mode = get_scale(); + int key_pitches[7] = { + root_pitch + scale_pitches[mode][0], + root_pitch + scale_pitches[mode][1], + root_pitch + scale_pitches[mode][2], + root_pitch + scale_pitches[mode][3], + root_pitch + scale_pitches[mode][4], + root_pitch + scale_pitches[mode][5], + root_pitch + scale_pitches[mode][6] + }; + // then given a note's position on the staffs (0-22, see table below), + // and where that note is relative to the root's distance from C (distance of 0-6), + // get that note's output pitch from the generated key_pitches. + // table: + // 22-19 F just below bass clef to bass B + // 18-12 bass C to to B (just below middle c) + // 11-5 middle C to treble B + // 4-0 treble C to G just above clef + int note_val; //for conditionally converting screen positions 0-22 to notes 0-6 (i.e. C-B) plus octaves + int root_shift_amt = root_degree_lookup[root_val]; // convert root from semitone (0-11) to wholetone (0-6) + int32_t note_pitch; // 0C 1D 2E 3F 4G 5A 6B 0C 1D 2E 3F 4G 5A 6B 0C + int note_scale_degree = 0;// for finding which pitch/degree the note is in our new scale + if (note > 18) { // F-B::22-19, add no octaves + note_val = 25 - note; // subtract val (<=22) from (4 + 3*7), because I foolishly assign higher note values the further from the top of the staff they go. + note_scale_degree = note_val - root_shift_amt; //get the distance between the root and note + if (note_scale_degree < 0) { //put it into a usable range if it's too low + note_scale_degree += 7; + note_pitch = key_pitches[note_scale_degree]; // get the pitch from our scale at the index we calculated + note_pitch -= 12 << 7; //tone it back down + } else { + note_pitch = key_pitches[note_scale_degree]; // get the pitch from our scale at the index we calculated + } + } else if (note > 11) { // C-B::18-12, add an octave + note_val = 18 - note; // 4 + 2*7 + note_scale_degree = note_val - root_shift_amt; + if (note_scale_degree < 0) { + note_scale_degree += 7; + note_pitch = key_pitches[note_scale_degree]; + } else { + note_pitch = key_pitches[note_scale_degree]; + note_pitch += 12 << 7; + } + } else if (note > 4) { // C-B::11-5, add two octaves + note_val = 11 - note; // 4 + 1*7 + note_scale_degree = note_val - root_shift_amt; + if (note_scale_degree < 0) { + note_scale_degree += 7; + note_pitch = key_pitches[note_scale_degree]; + note_pitch += 12 << 7; + } else { + note_pitch = key_pitches[note_scale_degree]; + note_pitch += 12 << 7; + note_pitch += 12 << 7; + } + } else { // C-G::4-0, add three octaves + note_val = 4 - note; // 4 + 0*7 + note_scale_degree = note_val - root_shift_amt; + if (note_scale_degree < 0) { + note_scale_degree += 7; + note_pitch = key_pitches[note_scale_degree]; + note_pitch += 12 << 7; + note_pitch += 12 << 7; + } else { + note_pitch = key_pitches[note_scale_degree]; + note_pitch += 12 << 7; + note_pitch += 12 << 7; + note_pitch += 12 << 7; + } + } + return note_pitch; + } + + //define copy and paste actions to be called by long_press_left + void copyChord() { + copyBuffer = get_chord_int(get_activechord()); + } + void pasteChord() { + set_chord_int(get_activechord(), copyBuffer); + } + + //define how to draw each channel symbol + void drawA(int x, int y) { //empty circle + graphics.drawCircle(x+2, y+2, 2.5); //the circle is smaller than the square and its coords refer to its centre so an offset is necessary + } + void drawB(int x, int y) { //empty square + graphics.drawFrame(x, y, 5, 5); + } + void drawC(int x, int y) { //filled circle + graphics.drawCircle(x+2, y+2, 2); //offset circle + graphics.drawRect(x+1, y+1, 3, 3); + } + void drawD(int x, int y) { //filled square + graphics.drawRect(x, y, 5, 5); + } + + //menu-related functions + uint8_t get_menu_page() const { + return menu_page_; + } + void set_menu_page(uint8_t _menu_page) { + menu_page_ = _menu_page; + } + + // initialise variables + void Init() { + InitDefaults(); + menu_page_ = FPART_MENU_STAFFS; + last_cv1_index = 0; + last_cv2_index = 0; + } + + void Loop(); + + void ISR() { + //for each channel, get the note value, get that note's pitch + int chord_int = get_chord_int(get_activechord()); + int a_note = get_note_from_chord_int(chord_int, 0) - 10; //subtract 10 to scale notes back down to 0-22 values expected + int b_note = get_note_from_chord_int(chord_int, 1) - 10; + int c_note = get_note_from_chord_int(chord_int, 2) - 10; + int d_note = get_note_from_chord_int(chord_int, 3) - 10; + int a_oct = get_a_oct(); + int b_oct = get_b_oct(); + int c_oct = get_c_oct(); + int d_oct = get_d_oct(); + int32_t a_pitch = get_pitch_from_note(a_note); + int32_t b_pitch = get_pitch_from_note(b_note); + int32_t c_pitch = get_pitch_from_note(c_note); + int32_t d_pitch = get_pitch_from_note(d_note); + //send pitches to the DAC + //send channel (a-d), pitch (int32), and octave (int32) + OC::DAC::set_pitch(DAC_CHANNEL_A, a_pitch, a_oct); + OC::DAC::set_pitch(DAC_CHANNEL_B, b_pitch, b_oct); + OC::DAC::set_pitch(DAC_CHANNEL_C, c_pitch, c_oct); + OC::DAC::set_pitch(DAC_CHANNEL_D, d_pitch, d_oct); + } + +private: + int8_t menu_page_; +}; + +// declare sources to generate the settings menu (need 4 or more) +SETTINGS_DECLARE(Fpart, FPART_SETTING_LAST) { + { 0, 0, 11, "tonic", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, 0, 6, "mode", scale_names, settings::STORAGE_TYPE_U8 }, + { 0, 0, 97, "loop start", NULL, settings::STORAGE_TYPE_U8 }, + { 3, 1, 98, "loop end", NULL, settings::STORAGE_TYPE_U8 }, + { 0, -3, 6, "chan a oct", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -3, 6, "chan b oct", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -3, 6, "chan c oct", NULL, settings::STORAGE_TYPE_I8 }, + { 0, -3, 6, "chan d oct", NULL, settings::STORAGE_TYPE_I8 }, + { 0, 0, 1, "long L", l_options, settings::STORAGE_TYPE_I8 }, + { 0, 0, 98, "active chord", NULL, settings::STORAGE_TYPE_I8 }, // FPART_SETTING_ACTIVECHORD + { 10101010, 10101010, 32323232, "chord0 int", NULL, settings::STORAGE_TYPE_U32 },// FPART_SETTING_CHORD0 + { 10101010, 10101010, 32323232, "chord1 int", NULL, settings::STORAGE_TYPE_U32 },// FPART_SETTING_CHORD1 + { 10101010, 10101010, 32323232, "chord2 int", NULL, settings::STORAGE_TYPE_U32 },// ...2 + { 10101010, 10101010, 32323232, "chord3 int", NULL, settings::STORAGE_TYPE_U32 },// 3 + { 10101010, 10101010, 32323232, "chord4 int", NULL, settings::STORAGE_TYPE_U32 },// 4 + { 10101010, 10101010, 32323232, "chord5 int", NULL, settings::STORAGE_TYPE_U32 },// 5 + { 10101010, 10101010, 32323232, "chord6 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord7 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord8 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord9 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord10 int", NULL, settings::STORAGE_TYPE_U32 },// 10 + { 10101010, 10101010, 32323232, "chord11 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord12 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord13 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord14 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord15 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord16 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord17 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord18 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord19 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord20 int", NULL, settings::STORAGE_TYPE_U32 },// 20 + { 10101010, 10101010, 32323232, "chord21 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord22 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord23 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord24 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord25 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord26 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord27 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord28 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord29 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord30 int", NULL, settings::STORAGE_TYPE_U32 },// 30 + { 10101010, 10101010, 32323232, "chord31 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord32 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord33 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord34 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord35 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord36 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord37 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord38 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord39 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord40 int", NULL, settings::STORAGE_TYPE_U32 },// 40 + { 10101010, 10101010, 32323232, "chord41 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord42 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord43 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord44 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord45 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord46 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord47 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord48 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord49 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord50 int", NULL, settings::STORAGE_TYPE_U32 },// 50 + { 10101010, 10101010, 32323232, "chord51 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord52 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord53 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord54 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord55 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord56 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord57 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord58 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord59 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord60 int", NULL, settings::STORAGE_TYPE_U32 },// 60 + { 10101010, 10101010, 32323232, "chord61 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord62 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord63 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord64 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord65 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord66 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord67 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord68 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord69 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord70 int", NULL, settings::STORAGE_TYPE_U32 },// 70 + { 10101010, 10101010, 32323232, "chord71 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord72 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord73 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord74 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord75 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord76 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord77 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord78 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord79 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord80 int", NULL, settings::STORAGE_TYPE_U32 },// 80 + { 10101010, 10101010, 32323232, "chord81 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord82 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord83 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord84 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord85 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord86 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord87 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord88 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord89 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord90 int", NULL, settings::STORAGE_TYPE_U32 },// 90 + { 10101010, 10101010, 32323232, "chord91 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord92 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord93 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord94 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord95 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord96 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord97 int", NULL, settings::STORAGE_TYPE_U32 }, + { 10101010, 10101010, 32323232, "chord98 int", NULL, settings::STORAGE_TYPE_U32 } // 98 / last chord slot +}; + +// create an instance of the main app class +Fpart fpart_instance; + +// required global functions +void FPART_init() { + fpart_instance.cursor.Init(FPART_SETTING_ROOT, FPART_SETTING_LAST - 1); + fpart_instance.Init(); +} + +size_t FPART_storageSize() { + return Fpart::storageSize(); +} + +size_t FPART_save(void *storage) { + return fpart_instance.Save(storage); +} + +size_t FPART_restore(const void *storage) { + return fpart_instance.Restore(storage); +} + +void FPART_isr() { + // call class's isr function + fpart_instance.ISR(); + //first the digital inputs (clocks/triggers) + uint32_t triggers = OC::DigitalInputs::clocked(); + int current_chord = fpart_instance.get_activechord(); + if (triggers & (1 << OC::DIGITAL_INPUT_1)) { //input_1 means decrement chord + if (current_chord > fpart_instance.get_loopstart()){ //assuming it's not at loopstart + fpart_instance.set_activechord(current_chord - 1); + } + else { //go to loopend + fpart_instance.set_activechord(fpart_instance.get_loopend()); + } + } + if (triggers & (1 << OC::DIGITAL_INPUT_2)) { //input_2 means increment chord + if (current_chord < fpart_instance.get_loopend()) { //assuming it's not at loopend + fpart_instance.set_activechord(current_chord + 1); + } + else { //go to loopstart + fpart_instance.set_activechord(fpart_instance.get_loopstart()); + } + } + if (triggers & (1 << OC::DIGITAL_INPUT_3)) { //input_3 means go straight to loopstart + fpart_instance.set_activechord(fpart_instance.get_loopstart()); + } + if (triggers & (1 << OC::DIGITAL_INPUT_4)) { //input_4 means go straight to loopend + fpart_instance.set_activechord(fpart_instance.get_loopend()); + } + + // TODO: these work but don't seem to get you to the final possible chords + //now the CV inputs + int32_t adc1_value = OC::ADC::raw_pitch_value(ADC_CHANNEL_1); + if (adc1_value < 90) adc1_value = 0; + int cv1_index = adc1_value / (7800 / 99); // Chysn suggests 7800 is the highest value the ADC method used will return + cv1_index = constrain(cv1_index, 0, 98); + if (cv1_index != fpart_instance.last_cv1_index) { + fpart_instance.last_cv1_index = cv1_index; + fpart_instance.set_activechord(cv1_index); + } + + int32_t adc2_value = OC::ADC::raw_pitch_value(ADC_CHANNEL_2); + if (adc2_value < 90) adc2_value = 0; + int loop_length = fpart_instance.get_loopend() - fpart_instance.get_loopstart(); + loop_length = constrain(loop_length, 0, fpart_instance.get_loopend()); + int cv2_index = adc2_value / (7800 / loop_length); // Chysn suggests 7800 is the highest value the ADC method used will return + cv2_index = constrain(cv2_index, 0, loop_length); + cv2_index += fpart_instance.get_loopstart(); //offset it by loop start amount + if (cv2_index != fpart_instance.last_cv2_index) { + fpart_instance.last_cv2_index = cv2_index; + fpart_instance.set_activechord(cv2_index); + } +} + +// has to be here, but currently unused +void FPART_handleAppEvent(OC::AppEvent event) { + //if (event == OC::APP_EVENT_RESUME) { + //break; +} + +void FPART_loop() { + //FPART_updateChannels(); +} + +void FPART_menu() { + if (fpart_instance.get_menu_page() == FPART_MENU_STAFFS) { + // draw two staffs + for (int s = 0; s < 11; s++) // the screen fits two staffs and middle C line, i.e. 11 lines + { + int y = 7 + (5 * s); // initial offset plus distance between lines + if (s == 5) { // middle C row + //graphics.drawHLineDots(5, y, 64); //could use this to draw the middle C line + continue; + } + graphics.drawLine(8, y, 114, y); + } + //vertical lines to indicate active chord on staffs + graphics.drawVLine(40,0,64); + graphics.drawVLine(52,0,64); + //prepare to draw note symbols + //x coordinates for chord columns. Note: changing active_x entails changing the box/VLines. + int prior_x = 12; + int active_x = 44; + int next_x = 75; + int last_x = 106; + //y coordinates for each of the possible note locations; inconsistent but carefully chosen for clear presentation + int y[23] = {2, 5, 7, 10, 12, 15, 17, 20, 22, 25, 27, 30, 32, 35, 37, 40, 42, 45, 47, 50, 52, 55, 57}; + int chordID = fpart_instance.get_activechord(); + if (chordID > 0) { //if there could be a prior chord, get and display its values + int chord_int = fpart_instance.get_chord_int(fpart_instance.get_activechord() -1); + int a_note = fpart_instance.get_note_from_chord_int(chord_int, 0) - 10; //subtract 10 to scale notes back down to 0-22 values expected + int b_note = fpart_instance.get_note_from_chord_int(chord_int, 1) - 10; + int c_note = fpart_instance.get_note_from_chord_int(chord_int, 2) - 10; + int d_note = fpart_instance.get_note_from_chord_int(chord_int, 3) - 10; + fpart_instance.drawA(prior_x,y[a_note]); + fpart_instance.drawB(prior_x,y[b_note]); + fpart_instance.drawC(prior_x,y[c_note]); + fpart_instance.drawD(prior_x,y[d_note]); + } + // get and display the active chord's values + int chord_int = fpart_instance.get_chord_int(fpart_instance.get_activechord()); + int a_note = fpart_instance.get_note_from_chord_int(chord_int, 0) - 10; + int b_note = fpart_instance.get_note_from_chord_int(chord_int, 1) - 10; + int c_note = fpart_instance.get_note_from_chord_int(chord_int, 2) - 10; + int d_note = fpart_instance.get_note_from_chord_int(chord_int, 3) - 10; + fpart_instance.drawA(active_x,y[a_note]); + fpart_instance.drawB(active_x,y[b_note]); + fpart_instance.drawC(active_x,y[c_note]); + fpart_instance.drawD(active_x,y[d_note]); + if (chordID <= 99 - 2 ) { //if there could be a next chord, get and display its values + int chord_int = fpart_instance.get_chord_int(fpart_instance.get_activechord() + 1); + int a_note = fpart_instance.get_note_from_chord_int(chord_int, 0) - 10; + int b_note = fpart_instance.get_note_from_chord_int(chord_int, 1) - 10; + int c_note = fpart_instance.get_note_from_chord_int(chord_int, 2) - 10; + int d_note = fpart_instance.get_note_from_chord_int(chord_int, 3) - 10; + fpart_instance.drawA(next_x,y[a_note]); + fpart_instance.drawB(next_x,y[b_note]); + fpart_instance.drawC(next_x,y[c_note]); + fpart_instance.drawD(next_x,y[d_note]); + } + if (chordID <= 99 - 3 ) { //if there could be a next-next chord, get and display its values + int chord_int = fpart_instance.get_chord_int(fpart_instance.get_activechord() + 2); + int a_note = fpart_instance.get_note_from_chord_int(chord_int, 0) - 10; + int b_note = fpart_instance.get_note_from_chord_int(chord_int, 1) - 10; + int c_note = fpart_instance.get_note_from_chord_int(chord_int, 2) - 10; + int d_note = fpart_instance.get_note_from_chord_int(chord_int, 3) - 10; + fpart_instance.drawA(last_x,y[a_note]); + fpart_instance.drawB(last_x,y[b_note]); + fpart_instance.drawC(last_x,y[c_note]); + fpart_instance.drawD(last_x,y[d_note]); + } + //draw a number to indicate current step + graphics.setPrintPos(116, 9); // any further right and some 2-digit values go to next line + graphics.print(chordID); + //draw L-button setting + graphics.setPrintPos(116, 19); + graphics.print("L"); + graphics.setPrintPos(122, 19); + if (fpart_instance.get_l_option() == 1) { + graphics.print(0); + } else if (fpart_instance.copy) { + graphics.print("C"); + } else { + graphics.print("P"); + } + //draw output-selection indicators + graphics.setPrintPos(116, 39); + if (fpart_instance.a_or_b) { + graphics.print("1"); + fpart_instance.drawA(123, 40); + } else { + graphics.print("2"); + fpart_instance.drawB(123, 40); + } + graphics.setPrintPos(116, 49);; + if (fpart_instance.c_or_d) { + graphics.print("3"); + fpart_instance.drawC(123, 50); + } else { + graphics.print("4"); + fpart_instance.drawD(123, 50); + } + } else if (fpart_instance.get_menu_page() == FPART_MENU_PARAMETERS) { //draw the settings + menu::DefaultTitleBar::Draw(); + graphics.print("4 Parts"); + menu::SettingsList settings_list(fpart_instance.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) + { + const int current = settings_list.Next(list_item); + const int value = fpart_instance.get_value(current); + if (current < 10) { // draw settings 0-9, i.e. don't show chord ints + list_item.DrawDefault(value, Fpart::value_attr(current)); + } else { + break; + } + } + } +} + +// define what should happen when the screensaver is activated +void FPART_screensaver() { + if (fpart_instance.get_menu_page() == FPART_MENU_PARAMETERS) { //go to staffs + fpart_instance.set_menu_page(FPART_MENU_STAFFS); + } + FPART_menu(); //this effectively blocks the screensaver; good while composing, but bad for screen +} + +// direct button presses to correct functions +void FPART_handleButtonEvent(const UI::Event &event) { + if (UI::EVENT_BUTTON_LONG_PRESS == event.type) { //long presses + switch (event.control) { + case OC::CONTROL_BUTTON_DOWN: + FPART_downButtonLong(); + break; + case OC::CONTROL_BUTTON_L: + FPART_leftButtonLong(); + break; + } + } + if (UI::EVENT_BUTTON_PRESS == event.type) { //short presses + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + FPART_upButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + FPART_downButton(); + break; + case OC::CONTROL_BUTTON_L: + FPART_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + FPART_rightButton(); + break; + } + } +} + +// handle encoder turns +void FPART_handleEncoderEvent(const UI::Event &event) { + if (fpart_instance.get_menu_page() == FPART_MENU_STAFFS) { //staffs page + int chordID = fpart_instance.get_activechord(); + int chord_int = fpart_instance.get_chord_int(chordID); + int a_note = fpart_instance.get_note_from_chord_int(chord_int, 0) - 10; //subtract 10 to scale notes back down to 0-22 values expected + int b_note = fpart_instance.get_note_from_chord_int(chord_int, 1) - 10; + int c_note = fpart_instance.get_note_from_chord_int(chord_int, 2) - 10; + int d_note = fpart_instance.get_note_from_chord_int(chord_int, 3) - 10; + if (OC::CONTROL_ENCODER_L == event.control) { + if (event.value > 0) { // increment A or B value depending on what is selected + if (fpart_instance.a_or_b) { //if channel A + if (a_note < 22) { + ++a_note; + } + } + else { // channel B + if (b_note < 22) { + ++b_note; + } + } + } else if (event.value < 0) { // decrement A or B value + if (fpart_instance.a_or_b) { // channel A + if (a_note > 0) { + --a_note; + } + } + else { // channel B + if (b_note > 0) { + --b_note; + } + } + } + } else if (OC::CONTROL_ENCODER_R == event.control) { // now the same for channels C and D + if (event.value > 0) { //increment C or D + if (fpart_instance.c_or_d) { + if (c_note < 22) { + ++c_note; + } + } + else { + if (d_note < 22) { + ++d_note; + } + } + } else if (event.value < 0) { // decrement C or D + if (fpart_instance.c_or_d) { + if (c_note > 0) { + --c_note; + } + } + else { + if (d_note > 0) { + --d_note; + } + } + } + } + //save the adjustment: build the chord integer, store it in the right place + int new_chord_int = fpart_instance.build_chord_int(a_note, b_note, c_note, d_note); + fpart_instance.set_chord_int(chordID, new_chord_int); + } else if (fpart_instance.get_menu_page() == FPART_MENU_PARAMETERS) { //on menu page, encoder scrolls through setting names/values + if (OC::CONTROL_ENCODER_R == event.control) { + if (fpart_instance.cursor.editing()) { //scroll setting values + fpart_instance.change_value(fpart_instance.cursor.cursor_pos(), event.value); + } else { // TODO: stop user scrolling past last drawn setting + fpart_instance.cursor.Scroll(event.value); //scroll setting names (i.e. rows) + } + } else if (OC::CONTROL_ENCODER_L == event.control){ + //left encoder behaviour in settings menu? + } + } +} + +void FPART_upButton() { + if (fpart_instance.get_menu_page() == FPART_MENU_STAFFS) { //staff page, down a chord + int chordID = fpart_instance.get_activechord(); + if (chordID > 0){ + --chordID; + fpart_instance.set_activechord(chordID); + } + else { + fpart_instance.set_activechord(98); //go to last chord + } + } + else { + //settings menu, scroll cursor up? + } +} + +void FPART_downButton() { + if (fpart_instance.get_menu_page() == FPART_MENU_STAFFS) { //staff page, up a chord + int chordID = fpart_instance.get_activechord(); + if (chordID < 98) { + ++chordID; + fpart_instance.set_activechord(chordID); + } + else { + fpart_instance.set_activechord(0); //go to first chord + } + } else { + //settings menu, scroll cursor down? + } +} + +void FPART_leftButton() { + if (fpart_instance.get_menu_page() == FPART_MENU_STAFFS) { //staff page, toggle channels A/B + fpart_instance.a_or_b = !fpart_instance.a_or_b; + } + else { + //menu stuff? + } +} + +void FPART_rightButton() { + if (fpart_instance.get_menu_page() == FPART_MENU_STAFFS) { + fpart_instance.c_or_d = !fpart_instance.c_or_d; //staff page, toggle channels C/D + } + else if (fpart_instance.get_menu_page() == FPART_MENU_PARAMETERS) { //menu page, toggle editing + fpart_instance.cursor.toggle_editing(); + } +} + +void FPART_leftButtonLong() { + if (fpart_instance.get_menu_page() == FPART_MENU_STAFFS) { //staff page, jump to first chord + if (fpart_instance.get_l_option() == 1) { + fpart_instance.set_activechord(0); + } else if (fpart_instance.copy) { + fpart_instance.copyChord(); + fpart_instance.copy = !fpart_instance.copy; + } else { + fpart_instance.pasteChord(); + fpart_instance.copy = !fpart_instance.copy; + } + } + else { + //do menu stuff? + } +} + +void FPART_downButtonLong() { // toggle between settings/staff pages + uint8_t _menu_page = fpart_instance.get_menu_page(); + if (_menu_page == FPART_MENU_PARAMETERS) { + fpart_instance.set_menu_page(FPART_MENU_STAFFS); + } + else { fpart_instance.set_menu_page(FPART_MENU_PARAMETERS); } +} + +#endif // ENABLE_APP_FPART diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 605ac1072..5d363822a 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -79,6 +79,9 @@ OC::App available_apps[] = { #ifdef ENABLE_APP_CHORDS DECLARE_APP('A','C', "Acid Curds", CHORDS), #endif + #ifdef ENABLE_APP_FPART + DECLARE_APP('F','P', "4 Parts", FPART), + #endif #ifdef ENABLE_APP_MIDI DECLARE_APP('M','I', "Captain MIDI", MIDI), #endif From 6f3c94a823fddc2436805f7bcc9e1ed4a2a0b3d5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 31 May 2023 02:35:54 -0400 Subject: [PATCH 254/417] 4 global shared quantizers for the HS namespace TODO: Global Quantizer Config screen --- software/o_c_REV/APP_CALIBR8OR.ino | 17 +++++++-------- software/o_c_REV/APP_HEMISPHERE.ino | 4 ++++ software/o_c_REV/HEM_ASR.ino | 12 +++++++---- software/o_c_REV/HEM_Chordinator.ino | 20 ++++++++--------- software/o_c_REV/HEM_DualQuant.ino | 12 +++++------ software/o_c_REV/HEM_ScaleDuet.ino | 14 ++++++------ software/o_c_REV/HEM_Shredder.ino | 12 +++++------ software/o_c_REV/HEM_Squanch.ino | 12 +++++------ software/o_c_REV/HEM_TB3PO.ino | 32 ++++++++++++++-------------- software/o_c_REV/HEM_TM2.ino | 16 +++++++------- software/o_c_REV/HemisphereApplet.h | 7 ++++++ software/o_c_REV/braids_quantizer.h | 4 ++-- 12 files changed, 88 insertions(+), 74 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index 858b45716..ab6037c27 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -167,9 +167,9 @@ public: void ClearPreset() { for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { - quantizer[ch].Init(); + HS::quantizer[ch].Init(); channel[ch].scale = OC::Scales::SCALE_SEMI; - quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); + HS::quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); channel[ch].scale_factor = 0; channel[ch].offset = 0; @@ -183,8 +183,8 @@ public: bool success = cal8_presets[index].load_preset(channel); if (success) { for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { - quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); - quantizer[ch].Requantize(); + HS::quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); + HS::quantizer[ch].Requantize(); } preset_modified = 0; } @@ -247,7 +247,7 @@ public: if (c.clocked_mode != SAMPLE_AND_HOLD || clocked) { // CV value int pitch = In(ch); - int quantized = quantizer[ch].Process(pitch, c.root_note * 128, c.transpose_active); + int quantized = HS::quantizer[ch].Process(pitch, c.root_note * 128, c.transpose_active); c.last_note = quantized; } @@ -412,7 +412,7 @@ public: preset_modified = 1; if (scale_edit) { channel[sel_chan].root_note = constrain(channel[sel_chan].root_note + direction, 0, 11); - quantizer[sel_chan].Requantize(); + HS::quantizer[sel_chan].Requantize(); return; } @@ -445,8 +445,8 @@ public: if (s_ < 0) s_ = OC::Scales::NUM_SCALES - 1; channel[sel_chan].scale = s_; - quantizer[sel_chan].Configure(OC::Scales::GetScale(s_), 0xffff); - quantizer[sel_chan].Requantize(); + HS::quantizer[sel_chan].Configure(OC::Scales::GetScale(s_), 0xffff); + HS::quantizer[sel_chan].Requantize(); return; } @@ -474,7 +474,6 @@ private: int trigger_flash[NR_OF_CHANNELS]; SegmentDisplay segment; - braids::Quantizer quantizer[NR_OF_CHANNELS]; Cal8ChannelConfig channel[NR_OF_CHANNELS]; ClockManager *clock_m = clock_m->get(); diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 2bd4c0a8c..ef3d80ec9 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -172,6 +172,10 @@ public: help_hemisphere = -1; clock_setup = 0; + for (int i = 0; i < 4; ++i) { + HS::quantizer[i].Init(); + } + SetApplet(0, get_applet_index_by_id(18)); // DualTM SetApplet(1, get_applet_index_by_id(15)); // EuclidX } diff --git a/software/o_c_REV/HEM_ASR.ino b/software/o_c_REV/HEM_ASR.ino index d8f2d855b..3bdce2304 100644 --- a/software/o_c_REV/HEM_ASR.ino +++ b/software/o_c_REV/HEM_ASR.ino @@ -32,7 +32,10 @@ public: void Start() { scale = OC::Scales::SCALE_SEMI; buffer_m->SetIndex(1); - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone + ForEachChannel(ch) { + quantizer[ch] = GetQuantizer(ch); + quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone + } } void Controller() { @@ -49,7 +52,7 @@ public: ForEachChannel(ch) { int cv = buffer_m->ReadNextValue(ch, hemisphere, index_mod); - int quantized = quantizer.Process(cv, 0, 0); + int quantized = quantizer[ch]->Process(cv, 0, 0); Out(ch, quantized); } buffer_m->Advance(); @@ -80,7 +83,8 @@ public: scale += direction; if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + ForEachChannel(ch) + quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); } } @@ -110,7 +114,7 @@ protected: private: int cursor; RingBufferManager *buffer_m = buffer_m->get(); - braids::Quantizer quantizer; + braids::Quantizer* quantizer[2]; int scale; int index_mod; // Effect of modulation diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index f0e90d557..dfb1a0a23 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -29,8 +29,8 @@ public: void Start() { scale = 5; - root_quantizer.Init(); - chord_quantizer.Init(); + root_quantizer = GetQuantizer(0); + chord_quantizer = GetQuantizer(1); continuous[0] = 1; continuous[1] = 1; set_scale(scale); @@ -48,7 +48,7 @@ public: if (continuous[0] || EndOfADCLag(0)) { chord_root_raw = In(0); int32_t new_root_pitch = - root_quantizer.Process(chord_root_raw, root << 7, 0); + root_quantizer->Process(chord_root_raw, root << 7, 0); if (new_root_pitch != chord_root_pitch) { update_chord_quantizer(); chord_root_pitch = new_root_pitch; @@ -58,7 +58,7 @@ public: if (continuous[1] || EndOfADCLag(1)) { harm_pitch = - chord_quantizer.Process(In(1) + chord_root_pitch, root << 7, 0); + chord_quantizer->Process(In(1) + chord_root_pitch, root << 7, 0); Out(1, harm_pitch); } } @@ -146,8 +146,8 @@ protected: } private: - braids::Quantizer root_quantizer; - braids::Quantizer chord_quantizer; + braids::Quantizer* root_quantizer; + braids::Quantizer* chord_quantizer; int scale; // SEMI int16_t root; @@ -166,11 +166,11 @@ private: void update_chord_quantizer() { size_t num_notes = active_scale.num_notes; - chord_root_pitch = root_quantizer.Process(chord_root_raw, root, 0); + chord_root_pitch = root_quantizer->Process(chord_root_raw, root, 0); size_t chord_root = note_ix(chord_root_pitch); uint16_t mask = rotl32(chord_mask, num_notes, chord_root); - chord_quantizer.Configure(active_scale, mask); - chord_quantizer.Requantize(); + chord_quantizer->Configure(active_scale, mask); + chord_quantizer->Requantize(); } size_t note_ix(int pitch) { @@ -192,7 +192,7 @@ private: else if (value >= OC::Scales::NUM_SCALES) scale = 0; else scale = value; active_scale = OC::Scales::GetScale(scale); - root_quantizer.Configure(active_scale); + root_quantizer->Configure(active_scale); } }; diff --git a/software/o_c_REV/HEM_DualQuant.ino b/software/o_c_REV/HEM_DualQuant.ino index dbc87d90f..98d35f40d 100644 --- a/software/o_c_REV/HEM_DualQuant.ino +++ b/software/o_c_REV/HEM_DualQuant.ino @@ -35,9 +35,9 @@ public: cursor = 0; ForEachChannel(ch) { - quantizer[ch].Init(); scale[ch] = ch + 5; - quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + quantizer[ch] = GetQuantizer(ch); + quantizer[ch]->Configure(OC::Scales::GetScale(scale[ch]), 0xffff); last_note[ch] = 0; continuous[ch] = 1; } @@ -53,7 +53,7 @@ public: if (continuous[ch] || EndOfADCLag(ch)) { int32_t pitch = In(ch); - int32_t quantized = quantizer[ch].Process(pitch, root[ch] << 7, 0); + int32_t quantized = quantizer[ch]->Process(pitch, root[ch] << 7, 0); Out(ch, quantized); last_note[ch] = quantized; } @@ -81,7 +81,7 @@ public: scale[ch] += direction; if (scale[ch] >= OC::Scales::NUM_SCALES) scale[ch] = 0; if (scale[ch] < 0) scale[ch] = OC::Scales::NUM_SCALES - 1; - quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + quantizer[ch]->Configure(OC::Scales::GetScale(scale[ch]), 0xffff); continuous[ch] = 1; // Re-enable continuous mode when scale is changed } else { // Root selection @@ -107,7 +107,7 @@ public: ForEachChannel(ch) { root[0] = constrain(root[0], 0, 11); - quantizer[ch].Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + quantizer[ch]->Configure(OC::Scales::GetScale(scale[ch]), 0xffff); } } @@ -121,7 +121,7 @@ protected: } private: - braids::Quantizer quantizer[2]; + braids::Quantizer* quantizer[2]; int last_note[2]; // Last quantized note bool continuous[2]; // Each channel starts as continuous and becomes clocked when a clock is received int cursor; diff --git a/software/o_c_REV/HEM_ScaleDuet.ino b/software/o_c_REV/HEM_ScaleDuet.ino index 3cab0da9a..d2c2fb49e 100644 --- a/software/o_c_REV/HEM_ScaleDuet.ino +++ b/software/o_c_REV/HEM_ScaleDuet.ino @@ -36,8 +36,8 @@ public: { mask[scale] = 0xffff; } - quantizer.Init(); - quantizer.Configure(OC::Scales::GetScale(5), mask[0]); + quantizer = GetQuantizer(0); + quantizer->Configure(OC::Scales::GetScale(5), mask[0]); last_scale = 0; adc_lag_countdown = 0; } @@ -50,11 +50,11 @@ public: if (EndOfADCLag()) { uint8_t scale = Gate(1); if (scale != last_scale) { - quantizer.Configure(OC::Scales::GetScale(5), mask[scale]); + quantizer->Configure(OC::Scales::GetScale(5), mask[scale]); last_scale = scale; } int32_t pitch = In(0); - int32_t quantized = quantizer.Process(pitch, 0, 0); + int32_t quantized = quantizer->Process(pitch, 0, 0); Out(0, quantized); } } @@ -71,7 +71,7 @@ public: // Toggle the mask bit at the cursor position mask[scale] ^= (0x01 << bit); - if (scale == last_scale) quantizer.Configure(OC::Scales::GetScale(5), mask[scale]); + if (scale == last_scale) quantizer->Configure(OC::Scales::GetScale(5), mask[scale]); } void OnEncoderMove(int direction) { @@ -92,7 +92,7 @@ public: mask[1] = Unpack(data, PackLocation {12,12}); last_scale = 0; - quantizer.Configure(OC::Scales::GetScale(5), mask[last_scale]); + quantizer->Configure(OC::Scales::GetScale(5), mask[last_scale]); } protected: @@ -106,7 +106,7 @@ protected: } private: - braids::Quantizer quantizer; + braids::Quantizer* quantizer; uint16_t mask[2]; uint8_t cursor; // 0-11=Scale 1; 12-23=Scale 2 uint8_t last_scale; // The most-recently-used scale (used to set the mask when necessary) diff --git a/software/o_c_REV/HEM_Shredder.ino b/software/o_c_REV/HEM_Shredder.ino index 211cc16b5..eb4fb13f3 100644 --- a/software/o_c_REV/HEM_Shredder.ino +++ b/software/o_c_REV/HEM_Shredder.ino @@ -39,9 +39,9 @@ public: replay = 0; reset = true; quant_channels = 0; - quantizer.Init(); + quantizer = GetQuantizer(0); scale = OC::Scales::SCALE_NONE; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); ForEachChannel(ch) { Shred(ch); } @@ -154,7 +154,7 @@ public: scale += direction; if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); } } @@ -177,7 +177,7 @@ public: bipolar[1] = Unpack(data, PackLocation {12,1}); quant_channels = Unpack(data, PackLocation {16,8}); scale = Unpack(data, PackLocation {24,8}); - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); ForEachChannel(ch) { Shred(ch); } @@ -209,7 +209,7 @@ private: bool bipolar[2] = {false, false}; int8_t quant_channels; int scale; - braids::Quantizer quantizer; + braids::Quantizer* quantizer; // Variables to handle imprint confirmation animation int confirm_animation_countdown; @@ -309,7 +309,7 @@ private: ForEachChannel(ch) { current[ch] = sequence[ch][step]; int8_t qc = quant_channels - 1; - if (qc < 0 || qc == ch) current[ch] = quantizer.Process(current[ch], 0, 0); + if (qc < 0 || qc == ch) current[ch] = quantizer->Process(current[ch], 0, 0); Out(ch, current[ch]); } } diff --git a/software/o_c_REV/HEM_Squanch.ino b/software/o_c_REV/HEM_Squanch.ino index fc4a3b52e..275c7138a 100644 --- a/software/o_c_REV/HEM_Squanch.ino +++ b/software/o_c_REV/HEM_Squanch.ino @@ -40,8 +40,8 @@ public: void Start() { scale = 5; ForEachChannel(ch) { - quantizer[ch].Init(); - quantizer[ch].Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer[ch] = GetQuantizer(ch); + quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); } } @@ -60,7 +60,7 @@ public: // output, the output is raised by one octave when Digital 2 is gated. int32_t shift_alt = (ch == 1) ? DetentedIn(1) : Gate(1) * (12 << 7); - int32_t quantized = quantizer[ch].Process(pitch + shift_alt, root << 7, shift[ch]); + int32_t quantized = quantizer[ch]->Process(pitch + shift_alt, root << 7, shift[ch]); Out(ch, quantized); last_note[ch] = quantized; } @@ -94,7 +94,7 @@ public: if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; ForEachChannel(ch) - quantizer[ch].Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); continuous = 1; // Re-enable continuous mode when scale is changed break; @@ -120,7 +120,7 @@ public: root = Unpack(data, PackLocation {24,4}); root = constrain(root, 0, 11); ForEachChannel(ch) - quantizer[ch].Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); } protected: @@ -140,7 +140,7 @@ private: // Both channels use the same quantizer settings, but we need two instances // to respect hysteresis independently on each, or else things get slippy - braids::Quantizer quantizer[2]; + braids::Quantizer* quantizer[2]; // Settings int scale; diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 6bd541c9e..e24dfc925 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -55,12 +55,12 @@ class TB_3PO : public HemisphereApplet // Init the quantizer for selecting pitches / CVs from scale = 29; // GUNA scale sounds cool //OC::Scales::SCALE_SEMI; // semi sounds pretty bunk - quantizer.Init(); + quantizer = GetQuantizer(0); set_quantizer_scale(scale); // This quantizer is for displaying a keyboard graphic, mapping the current scale to semitones - display_semi_quantizer.Init(); - display_semi_quantizer.Configure(OC::Scales::GetScale(OC::Scales::SCALE_SEMI), 0xffff); + display_semi_quantizer = GetQuantizer(1); + display_semi_quantizer->Configure(OC::Scales::GetScale(OC::Scales::SCALE_SEMI), 0xffff); density = 12; density_encoder_display = 0; @@ -118,15 +118,15 @@ class TB_3PO : public HemisphereApplet //transpose_note_in = In(0) / 128; // 128 ADC steps per semitone // This will accuarately get notes from an imperfect cv keyboard in semitones - //transpose_cv = display_semi_quantizer.Process(In(0), 0, 0); // Use root == 0 to start at c - //transpose_note_in = display_semi_quantizer.GetLatestNoteNumber() - 64; + //transpose_cv = display_semi_quantizer->Process(In(0), 0, 0); // Use root == 0 to start at c + //transpose_note_in = display_semi_quantizer->GetLatestNoteNumber() - 64; // Quantize the transpose CV to the same scale as the sequence, always based on c. // This allows a CV keyboard or sequencer to work reliably to transpose (e.g. every c is another octave) regardless of scale. // However, the transposition is limited to only in-scale notes so arpeggiations via LFOs, etc are still easily done. // (This CV is summed to the sequence pitch CV directly before output, rather than affecting its note indices.) - transpose_cv = quantizer.Process(In(0), 0, 0); // Use root == 0 to start at c - //transpose_note_in = quantizer.GetLatestNoteNumber() - 64; // For debug readout! + transpose_cv = quantizer->Process(In(0), 0, 0); // Use root == 0 to start at c + //transpose_note_in = quantizer->GetLatestNoteNumber() - 64; // For debug readout! } // Offset density from its encoder-set value with cv2 (Wiggling can build up & break down patterns nicely, especially if seed is locked) @@ -420,8 +420,8 @@ class TB_3PO : public HemisphereApplet private: int cursor = 0; - braids::Quantizer quantizer; // Helper for note index --> pitch cv - braids::Quantizer display_semi_quantizer; // Quantizer to interpret the current note for display on a keyboard + braids::Quantizer* quantizer; // Helper for note index --> pitch cv + braids::Quantizer* display_semi_quantizer; // Quantizer to interpret the current note for display on a keyboard // User settings @@ -512,22 +512,22 @@ class TB_3PO : public HemisphereApplet int out_note = constrain(quant_note, 0, 127); // New: Transpose post-quantize - int pitch_cv = quantizer.Lookup(out_note) + transpose_cv; + int pitch_cv = quantizer->Lookup(out_note) + transpose_cv; return pitch_cv; // Original: Output quantized after transposition added - //return quantizer.Lookup( out_note ); + //return quantizer->Lookup( out_note ); - //return quantizer.Lookup( 64 ); // Test: note 64 is definitely 0v=c4 if output directly, on ALL scales + //return quantizer->Lookup( 64 ); // Test: note 64 is definitely 0v=c4 if output directly, on ALL scales } int get_semitone_for_step(int step_num) { // Don't add in octaves-- use the current quantizer limited to the base octave int quant_note = 64 + notes[step_num] + root;// + transpose_note_in; - int32_t cv_note = quantizer.Lookup( constrain(quant_note, 0, 127)); - display_semi_quantizer.Process(cv_note, 0, 0); // Use root == 0 to start at c - return display_semi_quantizer.GetLatestNoteNumber() % 12; + int32_t cv_note = quantizer->Lookup( constrain(quant_note, 0, 127)); + display_semi_quantizer->Process(cv_note, 0, 0); // Use root == 0 to start at c + return display_semi_quantizer->GetLatestNoteNumber() % 12; } void reseed() @@ -771,7 +771,7 @@ class TB_3PO : public HemisphereApplet void set_quantizer_scale(int new_scale) { const braids::Scale & quant_scale = OC::Scales::GetScale(new_scale); - quantizer.Configure(quant_scale, 0xffff); + quantizer->Configure(quant_scale, 0xffff); scale_size = quant_scale.num_notes; // Track this scale size for octaves and display } diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 208291ca2..fe183728d 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -88,8 +88,8 @@ public: reg[0] = random(0, 65535); reg[1] = ~reg[0]; - quantizer.Init(); - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone + quantizer = GetQuantizer(0); + quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone } void Controller() { @@ -171,14 +171,14 @@ public: int x = constrain(note_trans[2], -range_mod, range_mod); int y = range_mod; int n = (note * (y + x) + note2 * (y - x)) / (2*y); - Output[ch] = slew(Output[ch], quantizer.Lookup(n)); + Output[ch] = slew(Output[ch], quantizer->Lookup(n)); break; } case PITCH1: - Output[ch] = slew(Output[ch], quantizer.Lookup(note + note_trans[0])); + Output[ch] = slew(Output[ch], quantizer->Lookup(note + note_trans[0])); break; case PITCH2: - Output[ch] = slew(Output[ch], quantizer.Lookup(note2 + note_trans[1])); + Output[ch] = slew(Output[ch], quantizer->Lookup(note2 + note_trans[1])); break; case MOD1: // 8-bit bi-polar proportioned CV Output[ch] = slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); @@ -243,7 +243,7 @@ public: scale += direction; if (scale >= TM2_MAX_SCALE) scale = 0; if (scale < 0) scale = TM2_MAX_SCALE - 1; - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); break; case RANGE: range = constrain(range + direction, 1, 32); @@ -292,7 +292,7 @@ public: outmode[0] = (OutputMode) Unpack(data, PackLocation {17,4}); outmode[1] = (OutputMode) Unpack(data, PackLocation {21,4}); scale = Unpack(data, PackLocation {25,8}); - quantizer.Configure(OC::Scales::GetScale(scale), 0xffff); + quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); smoothing = Unpack(data, PackLocation {41,6}) + 1; @@ -312,7 +312,7 @@ protected: private: int cursor; // TM2Cursor - braids::Quantizer quantizer; + braids::Quantizer* quantizer; int scale = OC::Scales::SCALE_SEMI; // Scale used for quantized output int root_note; // TODO diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index c98e3a697..7c92bf4fe 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -89,6 +89,7 @@ typedef int32_t simfloat; } #include "hemisphere_config.h" +#include "braids_quantizer.h" namespace HS { @@ -109,6 +110,8 @@ Applet available_applets[] = HEMISPHERE_APPLETS; Applet clock_setup_applet = DECLARE_APPLET(9999, 0x01, ClockSetup); int octave_max = 5; + +braids::Quantizer quantizer[4]; } // Specifies where data goes in flash storage for each selcted applet, and how big it is @@ -403,6 +406,10 @@ class HemisphereApplet { return OC::ADC::value(channel); } + braids::Quantizer* GetQuantizer(int ch) { + return &HS::quantizer[io_offset + ch]; + } + void Out(int ch, int value, int octave = 0) { DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); OC::DAC::set_pitch(channel, value, octave); diff --git a/software/o_c_REV/braids_quantizer.h b/software/o_c_REV/braids_quantizer.h index 1d810841e..1acfe6650 100644 --- a/software/o_c_REV/braids_quantizer.h +++ b/software/o_c_REV/braids_quantizer.h @@ -94,6 +94,6 @@ class Quantizer { DISALLOW_COPY_AND_ASSIGN(Quantizer); }; -} // namespace stages +} // namespace braids -#endif \ No newline at end of file +#endif From 5ef04c875ef8635447a6d25f9b66b492157d7cf3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 19 Jun 2023 23:58:12 -0400 Subject: [PATCH 255/417] TB3PO: use MIDIQuantizer instead of braids for display --- software/o_c_REV/HEM_TB3PO.ino | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index e24dfc925..57b7bb445 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -58,10 +58,6 @@ class TB_3PO : public HemisphereApplet quantizer = GetQuantizer(0); set_quantizer_scale(scale); - // This quantizer is for displaying a keyboard graphic, mapping the current scale to semitones - display_semi_quantizer = GetQuantizer(1); - display_semi_quantizer->Configure(OC::Scales::GetScale(OC::Scales::SCALE_SEMI), 0xffff); - density = 12; density_encoder_display = 0; @@ -117,10 +113,6 @@ class TB_3PO : public HemisphereApplet // Note: This appears to frequently result in coming up short on some notes when using a cv keyboard (e.g. c# might be c) (prefer interpreting this via a windowed quantizer?) //transpose_note_in = In(0) / 128; // 128 ADC steps per semitone - // This will accuarately get notes from an imperfect cv keyboard in semitones - //transpose_cv = display_semi_quantizer->Process(In(0), 0, 0); // Use root == 0 to start at c - //transpose_note_in = display_semi_quantizer->GetLatestNoteNumber() - 64; - // Quantize the transpose CV to the same scale as the sequence, always based on c. // This allows a CV keyboard or sequencer to work reliably to transpose (e.g. every c is another octave) regardless of scale. // However, the transposition is limited to only in-scale notes so arpeggiations via LFOs, etc are still easily done. @@ -421,11 +413,9 @@ class TB_3PO : public HemisphereApplet int cursor = 0; braids::Quantizer* quantizer; // Helper for note index --> pitch cv - braids::Quantizer* display_semi_quantizer; // Quantizer to interpret the current note for display on a keyboard - - + // User settings - + // Bool int manual_reset_flag = 0; // Manual trigger to reset/regen @@ -526,8 +516,7 @@ class TB_3PO : public HemisphereApplet // Don't add in octaves-- use the current quantizer limited to the base octave int quant_note = 64 + notes[step_num] + root;// + transpose_note_in; int32_t cv_note = quantizer->Lookup( constrain(quant_note, 0, 127)); - display_semi_quantizer->Process(cv_note, 0, 0); // Use root == 0 to start at c - return display_semi_quantizer->GetLatestNoteNumber() % 12; + return MIDIQuantizer::NoteNumber(cv_note) % 12; } void reseed() From 71210dedb6abfe1099d6c9bb5ad4815d86bd673f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 20 Jun 2023 00:59:32 -0400 Subject: [PATCH 256/417] Slight optimization for applet help text --- software/o_c_REV/HemisphereApplet.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 7c92bf4fe..4934a2a53 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -59,10 +59,13 @@ #define HEMISPHERE_CHANGE_THRESHOLD 32 // Codes for help system sections -#define HEMISPHERE_HELP_DIGITALS 0 -#define HEMISPHERE_HELP_CVS 1 -#define HEMISPHERE_HELP_OUTS 2 -#define HEMISPHERE_HELP_ENCODER 3 +enum HEM_HELP_SECTIONS { +HEMISPHERE_HELP_DIGITALS = 0, +HEMISPHERE_HELP_CVS = 1, +HEMISPHERE_HELP_OUTS = 2, +HEMISPHERE_HELP_ENCODER = 3 +}; +const char * HEM_HELP_SECTION_NAMES[4] = {"Dig", "CV", "Out", "Enc"}; // Simulated fixed floats by multiplying and dividing by powers of 2 #ifndef int2simfloat @@ -230,14 +233,12 @@ class HemisphereApplet { void DrawHelpScreen() { gfxHeader(applet_name()); SetHelp(); + for (int section = 0; section < 4; section++) { int y = section * 12 + 16; graphics.setPrintPos(0, y); - if (section == HEMISPHERE_HELP_DIGITALS) graphics.print("Dig"); - if (section == HEMISPHERE_HELP_CVS) graphics.print("CV"); - if (section == HEMISPHERE_HELP_OUTS) graphics.print("Out"); - if (section == HEMISPHERE_HELP_ENCODER) graphics.print("Enc"); + graphics.print( HEM_HELP_SECTION_NAMES[section] ); graphics.invertRect(0, y - 1, 19, 9); graphics.setPrintPos(20, y); From 3e60d97fb1e4b80b1b8ff9021a189721fbf26821 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 20 Jun 2023 01:00:53 -0400 Subject: [PATCH 257/417] Drop Low-rents from +stock1 build --- software/o_c_REV/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index ea27793d7..a591a1d5e 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -75,7 +75,7 @@ build_flags = ; -DENABLE_APP_POLYLFO ; -DENABLE_APP_H1200 -DENABLE_APP_AUTOMATONNETZ - -DENABLE_APP_LORENZ +; -DENABLE_APP_LORENZ ; -DENABLE_APP_BBGEN ; -DENABLE_APP_BYTEBEATGEN -DOC_VERSION_EXTRA="\"+stock1\"" From 5278d108a4aadc42a2574889cc8152b253ed1a77 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 20 Jun 2023 02:15:21 -0400 Subject: [PATCH 258/417] Tuner: small FLIP_180 fix for help text --- software/o_c_REV/HEM_Tuner.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_Tuner.ino b/software/o_c_REV/HEM_Tuner.ino index 61ce7fc65..d7744fc76 100644 --- a/software/o_c_REV/HEM_Tuner.ino +++ b/software/o_c_REV/HEM_Tuner.ino @@ -99,10 +99,11 @@ public: protected: void SetHelp() { - if (hemisphere == 1) { #ifdef FLIP_180 + if (hemisphere == 0) { help[HEMISPHERE_HELP_DIGITALS] = "1=Input"; #else + if (hemisphere == 1) { help[HEMISPHERE_HELP_DIGITALS] = "2=Input"; #endif help[HEMISPHERE_HELP_CVS] = ""; From e17e0e4356deeda9dd9d74e5211c39e1fcaf76ca Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 20 Jun 2023 02:10:16 -0400 Subject: [PATCH 259/417] Handle dual-encoder-press for VOR menu --- software/o_c_REV/OC_ui.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index f4b0ee43c..20f92d6f9 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -126,13 +126,17 @@ UiMode Ui::DispatchEvents(App *app) { break; case UI::EVENT_BUTTON_DOWN: #ifdef VOR - if (OC::CONTROL_BUTTON_M == event.control) { + // dual encoder press + if ( ((OC::CONTROL_BUTTON_L | OC::CONTROL_BUTTON_R) == event.mask) + || (OC::CONTROL_BUTTON_M == event.control) ) + { VBiasManager *vbias_m = vbias_m->get(); vbias_m->AdvanceBias(); - } else app->HandleButtonEvent(event); -#else - app->HandleButtonEvent(event); + SetButtonIgnoreMask(); // ignore release and long-press + } + else #endif + app->HandleButtonEvent(event); break; case UI::EVENT_BUTTON_LONG_PRESS: if (OC::CONTROL_BUTTON_UP == event.control) { From 33ba03e796578f40e5b937717d5f5783bceae48d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 20 Jun 2023 02:10:16 -0400 Subject: [PATCH 260/417] Better dual-press action for Clock Setup --- software/o_c_REV/APP_HEMISPHERE.ino | 47 ++++++++++++++++++----------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index ef3d80ec9..21395ccbe 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -353,30 +353,44 @@ public: return; } + if (clock_setup && !down) { + clock_setup = 0; // Turn off clock setup with any single-click button release + return; + } + // -- button down if (down) { + if (event.mask == (OC::CONTROL_BUTTON_UP | OC::CONTROL_BUTTON_DOWN)) // dual press for Clock Setup + { + clock_setup = 1; + SetHelpScreen(-1); + select_mode = -1; + OC::ui.SetButtonIgnoreMask(); + return; + } + if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME) { - // This is a double-click. Activate corresponding help screen or Clock Setup + // This is a double-click on one button. Activate corresponding help screen and deactivate select mode. if (hemisphere == first_click) SetHelpScreen(hemisphere); - else if (OC::CORE::ticks - click_tick < HEMISPHERE_SIM_CLICK_TIME) // dual press for clock setup uses shorter timing - clock_setup = 1; - // leave Select Mode, and reset the double-click timer - select_mode = -1; + // reset double-click timer either way click_tick = 0; - } else { - // If a help screen is already selected, and the button is for - // the opposite one, go to the other help screen - if (help_hemisphere > -1) { - if (help_hemisphere != hemisphere) SetHelpScreen(hemisphere); - else SetHelpScreen(-1); // Leave help screen if corresponding button is clicked - } + return; + } - // mark this single click - click_tick = OC::CORE::ticks; - first_click = hemisphere; + // -- Single click + // If a help screen is already selected, and the button is for + // the opposite one, go to the other help screen + if (help_hemisphere > -1) { + if (help_hemisphere != hemisphere) SetHelpScreen(hemisphere); + else SetHelpScreen(-1); // Exit help screen if same button is clicked + OC::ui.SetButtonIgnoreMask(); // ignore release } + + // mark this single click + click_tick = OC::CORE::ticks; + first_click = hemisphere; return; } @@ -387,9 +401,6 @@ public: else if (help_hemisphere < 0) // Otherwise, set Select Mode - UNLESS there's a help screen select_mode = hemisphere; } - - if (click_tick) - clock_setup = 0; // Turn off clock setup with any single-click button release } void DelegateEncoderMovement(const UI::Event &event) { From 2224121ee612d46072a7fd6e9fd2daf19c44e2fb Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 18 Jun 2023 18:57:12 -0400 Subject: [PATCH 261/417] Version 1.6.2 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index e348383a2..c86ebff5d 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.1" +"v1.6.2" From dd7a55a7416e91f8b511d21dfce671240630320f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 22 Jun 2023 04:16:55 -0400 Subject: [PATCH 262/417] Revert "Inverted encoder config for flipped build" This is broken on one of my uo_C, and was a poor solution anyway. --- software/o_c_REV/OC_calibration.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/software/o_c_REV/OC_calibration.h b/software/o_c_REV/OC_calibration.h index 263e38408..275de48ac 100644 --- a/software/o_c_REV/OC_calibration.h +++ b/software/o_c_REV/OC_calibration.h @@ -52,11 +52,7 @@ struct CalibrationData { #endif EncoderConfig encoder_config() const { -#ifdef FLIP_180 - return static_cast(~flags & CALIBRATION_FLAG_ENCODER_MASK); -#else - return static_cast(flags & CALIBRATION_FLAG_ENCODER_MASK); -#endif + return static_cast(flags & CALIBRATION_FLAG_ENCODER_MASK); } EncoderConfig next_encoder_config() { From 41bee182d09ee6694403585abff82412a95ca822 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Sat, 12 Aug 2023 02:49:48 -0400 Subject: [PATCH 263/417] Update README.md --- README.md | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 21049380f..fb43baac3 100755 --- a/README.md +++ b/README.md @@ -3,34 +3,25 @@ ## Phazerville Suite - an active o_C firmware fork -Using [Benisphere](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the Hemisphere Suite in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! +Using [**Benisphere**](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the **Hemisphere Suite** in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! -I've also included all of the stock O&C firmware apps, although they don't all fit in one build. I provide 3 different builds with various combinations of apps, listed in the [Release Notes](https://github.com/djphazer/O_C-BenisphereSuite/releases). +I've also included **all of the stock O&C firmware apps**, although they don't all fit in one build. I provide **3 different builds** with various combinations of apps, listed in the [**Release Notes**](https://github.com/djphazer/O_C-BenisphereSuite/releases). Check the [Wiki](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. ### Notable Features in this branch: -* 4 Preset banks for Hemisphere (long-press DOWN button) -* Modal-editing style navigation (push to toggle editing) -* Expanded internal clock - - Note: press both UP+DOWN buttons quickly to access the [**Clock Setup**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Clock-Setup) screen - - Syncs to external clock on TR1, configurable PPQN - - MIDI Clock out via USB - - Independent multipliers for each internal trigger - - Manual triggers (convenient for jogging or resetting a sequencer, testing) +* 4 Presets in the new [**Hemisphere Config**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Hemisphere-Config) +* Modal-editing style cursor navigation +* Expanded internal [**Clock Setup**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Clock-Setup) * A new App called [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) - - quad performance quantizer + pitch CV fine-tuning tool, 4 preset banks -* **[DualTM](https://github.com/djphazer/O_C-BenisphereSuite/wiki/DualTM)** - ShiftReg has been upgraded to two concurrent 32-bit registers governed by the same length/prob/scale/range settings - - outputs assignable to Pitch, Mod, Trig, Gate from either register. Assignable CV inputs. Massive modulation potential! -* **EbbAndLfo** (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - - mini implementation of MI Tides, with v/oct tracking -* **EuclidX** - AnnularFusion got a makeover, now includes padding, configurable CV input modulation - - (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) -* LoFi Tape has been transformed into **LoFi Echo** - a crazy bitcrushing digital delay line - - (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) +* **[DualTM](https://github.com/djphazer/O_C-BenisphereSuite/wiki/DualTM)** - two 32-bit shift registers. Assignable I/O. +* **[EbbAndLfo](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Ebb-&-LFO)** (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking +* **[EuclidX](https://github.com/djphazer/O_C-BenisphereSuite/wiki/EuclidX)** - AnnularFusion got a makeover, now includes padding, configurable CV input modulation - (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) +* LoFi Tape has been transformed into **LoFi Echo** - a crazy bitcrushing digital delay line - (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) * Sequence5 -> **SequenceX** (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) -* lots of other small tweaks + experimental applets + +Plus lots of other small tweaks + experimental applets. ### How do I try it? @@ -45,13 +36,13 @@ python3 get-platformio.py ``` ...or as a [a full-featured IDE](https://platformio.org/install/ide), as well as a plugin for VSCode and other existing IDEs. -The project lives within the `software/o_c_REV` directory. From there, you can Build the desired configuration and Upload via USB to your module: +This project lives within the `software/o_c_REV` directory. From there, you can Build the desired configuration and Upload via USB to your module: ``` pio run -e main -t upload ``` Alternate build environment configurations exist in `platformio.ini` for VOR, Buchla, flipped screen, etc. To build all the defaults consecutively, simply use `pio run` -### Credits +## Credits Many minds before me have made this project possible. Attribution is present in the git commit log and within individual files. Shoutouts: From d9ba7be8534930409fd3873367959e2f105b3681 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 12 Aug 2023 03:37:24 -0400 Subject: [PATCH 264/417] Shoutout to SynthDad! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb43baac3..39abc49bf 100755 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Using [**Benisphere**](https://github.com/benirose/O_C-BenisphereSuite) as a sta I've also included **all of the stock O&C firmware apps**, although they don't all fit in one build. I provide **3 different builds** with various combinations of apps, listed in the [**Release Notes**](https://github.com/djphazer/O_C-BenisphereSuite/releases). -Check the [Wiki](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. +Watch SynthDad's [**video overview**](https://www.youtube.com/watch?v=XRGlAmz3AKM) or check the [**Wiki**](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. ### Notable Features in this branch: From a048d0f84d4c444fab4b94c2b649f07a47cafa9b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 4 Aug 2023 02:20:24 -0400 Subject: [PATCH 265/417] ignore .vscode dir --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8ff86f5db..8831f4658 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ shelved/ Hemisphere\ Suite.cpp builds .pio +.vscode From c1e085c312ae4d4ddc31eff13fa7f0055bbe1008 Mon Sep 17 00:00:00 2001 From: ph1 <6156113+ph1-xyz@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:18:19 +0000 Subject: [PATCH 266/417] Icon updates from ph.xyz Lots of graphical tweaks --- software/o_c_REV/HEM_EuclidX.ino | 8 +++--- software/o_c_REV/HEM_TM2.ino | 28 +++++++++---------- software/o_c_REV/HSicons.h | 48 +++++++++++++++++++++----------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 2d6adf493..9db662139 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -251,11 +251,11 @@ private: const int pad_left = 5; if (cursor < CV_DEST1) { - gfxBitmap(pad_left + 0 * spacing, 15, 8, LOOP_ICON); + gfxBitmap(pad_left + 0 * spacing, 15, 8, LENGTH_ICON); } - gfxBitmap(pad_left + 1 * spacing, 15, 8, X_NOTE_ICON); - gfxBitmap(pad_left + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); - gfxPrint(pad_left + 3 * spacing, 15, "+"); + gfxBitmap(pad_left + 1 * spacing, 15, 8, PULSES_ICON); + gfxBitmap(pad_left + 2 * spacing, 15, 8, ROTATE_ICON); + gfxIcon(pad_left + 3 * spacing, 15, OFFSET_ICON); int y = 15; ForEachChannel (ch) { diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index fe183728d..8619c0e87 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -366,7 +366,7 @@ private: case GATE_SUM: gfxBitmap(24+ch*32, 35, 3, SUB_TWO); case GATE1: case GATE2: - gfxBitmap(15 + ch*32, 35, 8, METER_ICON); + gfxBitmap(15 + ch*32, 35, 8, GATE_ICON); break; default: break; @@ -388,10 +388,10 @@ private: gfxIcon(15 + ch*32, 35, LOOP_ICON); break; case P_MOD: - gfxPrint(15 + ch*32, 35, "p"); + gfxIcon(15 + ch*32, 35, TOSS_ICON); break; case RANGE_MOD: - gfxIcon(15 + ch*32, 35, UP_DOWN_ICON); + gfxIcon(15 + ch*32, 35, RANGE_ICON); break; case TRANSPOSE1: gfxIcon(15 + ch*32, 35, BEND_ICON); @@ -411,18 +411,18 @@ private: void DrawSelector() { gfxBitmap(1, 14, 8, LOOP_ICON); gfxPrint(12 + pad(10, len_mod), 15, len_mod); - gfxPrint(32, 15, "p="); + gfxPrint(pad(100, p_mod), p_mod); if (cursor == PROB || Gate(1)) { // p unlocked - gfxPrint(pad(100, p_mod), p_mod); + gfxBitmap(49, 15, 8, TOSS_ICON); } else { // p is disabled - gfxBitmap(49, 14, 8, LOCK_ICON); + gfxBitmap(49, 15, 8, LOCK_ICON); } gfxBitmap(1, 25, 8, SCALE_ICON); - gfxPrint(12, 25, OC::scale_names_short[scale]); - gfxBitmap(41, 25, 8, UP_DOWN_ICON); + gfxPrint(9, 25, OC::scale_names_short[scale]); + gfxBitmap(40, 25, 8, RANGE_ICON); gfxPrint(49, 25, range_mod); // APD - switch ((TM2Cursor)cursor) { + switch ((TM2Cursor)cursor){ default: ForEachChannel(ch) DrawOutputMode(ch); @@ -433,17 +433,17 @@ private: break; case SLEW: - gfxPrint(1, 35, "Slew:"); - gfxPrint(smooth_mod); + gfxIcon(1, 35, SLEW_ICON); + gfxPrint(15, 35, smooth_mod); - gfxCursor(31, 43, 18); + gfxCursor(15, 43, 10); break; } switch ((TM2Cursor)cursor) { case LENGTH: gfxCursor(13, 23, 12); break; - case PROB: gfxCursor(45, 23, 18); break; - case SCALE: gfxCursor(12, 33, 25); break; + case PROB: gfxCursor(35, 23, 10); break; + case SCALE: gfxCursor(9, 33, 25); break; case RANGE: gfxCursor(49, 33, 14); break; case OUT_A: diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index 1d5898ef1..04f211299 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -1,22 +1,37 @@ // Icons! Made with http://beigemaze.com/bitmap8x8.html +// Retouched with https://bitca--goatama.repl.co/ (Column major MSB Bottom) + #ifndef HS_ICON_SET #define HS_ICON_SET +const uint8_t VOCT_ICON[8] = { 0x07, 0x08, 0x27, 0x10, 0x08, 0xe4, 0xa0, 0xe0 }; +const uint8_t TR_ICON[8] = {0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x00, 0x00, 0x00 }; +const uint8_t GATE_ICON[8] = {0x40, 0x7e, 0x02, 0x02, 0x02, 0x7e, 0x40, 0x40}; // rename to GATE_ICON? +const uint8_t SLEW_ICON[8] = { 0x40, 0x7e, 0x02, 0x02, 0x0c, 0x30, 0x40, 0x40}; +const uint8_t LOOP_ICON[8] = { 0x0a, 0x01, 0x09, 0x22, 0x48, 0x40, 0x28, 0x00 }; +const uint8_t LENGTH_ICON[8] = { 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00 }; +const uint8_t RANGE_ICON[8] = { 0x42, 0x7e, 0x42, 0x00, 0x7e, 0x7e, 0x00, 0x78 }; +const uint8_t PULSES_ICON[8] = {0x08, 0x00, 0x1c, 0x1c, 0x1c, 0x00, 0x08, 0x00}; +const uint8_t OFFSET_ICON[8] = { 0x08, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00 }; +const uint8_t ROTATE_ICON[8] = { 0x08, 0x00, 0x08, 0x00, 0x1c, 0x1c, 0x1c, 0x00}; +const uint8_t TOSS_ICON[8] = { 0x0c, 0x12, 0x52, 0x91, 0x91, 0x09, 0x49, 0x06}; + const uint8_t METER_ICON[8] = {0x00,0xff,0x00,0xfc,0x00,0xff,0x00,0xfc}; -const uint8_t CLOCK_ICON[8] = {0x9c,0xa2,0xc1,0xcf,0xc9,0xa2,0x9c,0x00}; -const uint8_t MOD_ICON[8] = {0x30,0x08,0x04,0x08,0x10,0x20,0x10,0x0c}; -const uint8_t BEND_ICON[8] = {0x20,0x70,0x70,0x3f,0x20,0x14,0x0c,0x1c}; +const uint8_t CLOCK_ICON[8] = { 0x3c, 0x42, 0x81, 0x8d, 0x91, 0x81, 0x42, 0x3c }; +const uint8_t MOD_ICON[8] = { 0x20, 0x18, 0x04, 0x08, 0x08, 0x10, 0x20, 0x20 }; +const uint8_t BEND_ICON[8] = { 0x00, 0x30, 0x3e, 0x82, 0x70, 0x0e, 0x01, 0x00 }; const uint8_t AFTERTOUCH_ICON[8] = {0x00,0x00,0x20,0x42,0xf5,0x48,0x20,0x00}; const uint8_t MIDI_ICON[8] = {0x3c,0x42,0x91,0x45,0x45,0x91,0x42,0x3c}; -const uint8_t CV_ICON[8] = {0x1f,0x11,0x11,0x00,0x07,0x18,0x07,0x00}; -const uint8_t SCALE_ICON[8] = {0x81,0x7f,0x9f,0x81,0x7f,0x9f,0x81,0x7f}; -const uint8_t LOCK_ICON[8] = {0x00,0xf8,0xfe,0xf9,0x89,0xf9,0xfe,0xf8}; +const uint8_t CV_ICON[8] = { 0x00, 0x3e, 0x22, 0x00, 0x1e, 0x20, 0x1e, 0x00 }; +const uint8_t SCALE_ICON[8] = { 0x00, 0x00, 0x0e, 0x8d, 0x61, 0x1e, 0x00, 0x00}; +const uint8_t LOCK_ICON[8] = {0xf8, 0x8e, 0x89, 0xa9, 0x89, 0x8e, 0xf8, 0x00}; +// old lock 0x00,0xf8,0xfe,0xf9,0x89,0xf9,0xfe,0xf8 const uint8_t FAVORITE_ICON[8] = {0x0e,0x11,0x21,0x42,0x42,0x21,0x11,0x0e}; -const uint8_t ROTATE_L_ICON[8] = {0x0c,0x1e,0x3f,0x0c,0x0c,0x08,0x70,0x00}; -const uint8_t ROTATE_R_ICON[8] = {0x00,0x70,0x08,0x0c,0x0c,0x3f,0x1e,0x0c}; +const uint8_t ROTATE_L_ICON[8] = { 0x0c, 0x1e, 0x3f, 0x0c, 0x0c, 0x1c, 0x78, 0x00 }; +const uint8_t ROTATE_R_ICON[8] = { 0x00, 0x78, 0x1c, 0x0c, 0x0c, 0x3f, 0x1e, 0x0c }; const uint8_t MONITOR_ICON[8] = {0x1f,0x51,0x51,0x71,0x71,0x51,0x51,0x1f}; const uint8_t AUDITION_ICON[8] = {0x78,0x68,0x68,0x78,0x48,0x4c,0x4a,0x79}; -const uint8_t LINK_ICON[8] = {0x70,0xd8,0x88,0xda,0x5b,0x11,0x1b,0x0e}; +const uint8_t LINK_ICON[8] = { 0x18, 0x24, 0x24, 0x10, 0x08, 0x24, 0x24, 0x18}; const uint8_t CHECK_OFF_ICON[8] = {0xff,0x81,0x81,0x81,0x81,0x81,0x81,0xff}; const uint8_t CHECK_ON_ICON[8] = {0xcb,0x99,0xb1,0xb1,0x99,0x8c,0x86,0xf3}; const uint8_t CHECK_ICON[8] = {0x08,0x18,0x30,0x30,0x18,0x0c,0x06,0x03}; @@ -40,7 +55,6 @@ const uint8_t BTN_ON_ICON[8] = {0x71,0xaa,0xa8,0xab,0xa8,0xaa,0x71,0x00}; const uint8_t ZAP_ICON[8] = {0x00,0x08,0x9c,0x5e,0x7a,0x39,0x10,0x00}; // Transport -const uint8_t LOOP_ICON[8] = {0x34,0x64,0x4e,0x4e,0xe4,0xe4,0x4c,0x58}; const uint8_t PLAYONCE_ICON[8] = {0x10,0x10,0x10,0x10,0x38,0x38,0x10,0x10}; const uint8_t PLAY_ICON[8] = {0x00,0x7e,0x7e,0x3c,0x3c,0x18,0x18,0x00}; const uint8_t PAUSE_ICON[8] = {0x00,0x7e,0x7e,0x00,0x00,0x7e,0x7e,0x00}; @@ -64,22 +78,22 @@ const uint8_t METRO_L_ICON[8] = {0xf3,0x8c,0x9a,0xa2,0x82,0x8c,0xf0,0x00}; const uint8_t METRO_R_ICON[8] = {0xf0,0x8c,0x82,0xa2,0x9a,0x8c,0xf3,0x00}; // Notes -const uint8_t X_NOTE_ICON[8] = {0x00,0xa0,0x40,0xa0,0x1f,0x02,0x0c,0x00}; -const uint8_t NOTE_ICON[8] = {0xc0,0xe0,0xe0,0xe0,0x7f,0x02,0x14,0x08}; -const uint8_t NOTE2_ICON[8] = {0xc0,0xa0,0xa0,0xa0,0x7f,0x00,0x00,0x00}; -const uint8_t NOTE4_ICON[8] = {0x00,0x00,0x60,0x70,0x70,0x3f,0x00,0x00}; +const uint8_t X_NOTE_ICON[8] = { 0x00, 0x00, 0xa0, 0x40, 0xa0, 0x1f, 0x00, 0x00 }; // ??? +const uint8_t NOTE_ICON[8] = { 0x00, 0xe0, 0xe0, 0x7f, 0x02, 0x1c, 0x00, 0x00 }; +const uint8_t NOTE2_ICON[8] = { 0x00, 0x00, 0xe0, 0xa0, 0xff, 0x00, 0x00, 0x00 }; +const uint8_t NOTE4_ICON[8] = { 0x00, 0x00, 0xe0, 0xe0, 0xff, 0x00, 0x00, 0x00 }; // Waveform const uint8_t UP_DOWN_ICON[8] = {0x00,0x00,0x24,0x66,0xff,0x66,0x24,0x00}; const uint8_t LEFT_RIGHT_ICON[8] = {0x10,0x38,0x7c,0x10,0x10,0x7c,0x38,0x10}; const uint8_t SEGMENT_ICON[8] = {0xc0,0xc0,0x20,0x10,0x08,0x06,0x06,0x00}; -const uint8_t WAVEFORM_ICON[8] = {0x10,0x08,0x04,0x08,0x10,0x20,0x10,0x08}; +const uint8_t WAVEFORM_ICON[8] = {0x10, 0x0c, 0x02, 0x0c, 0x70, 0x80, 0x60, 0x10 }; const uint8_t STAIRS_ICON[8] = {0x00,0x20,0x20,0x38,0x08,0x0e,0x02,0x02}; // Some stairs going up // Superscript and subscript 1 and 2 -const uint8_t SUP_ONE[3] = {0x0a,0x0f,0x08}; -const uint8_t SUB_TWO[3] = {0x90,0xd0,0xa0}; +const uint8_t SUP_ONE[3] = {0x02, 0x0f, 0x00}; +const uint8_t SUB_TWO[3] = {0x00, 0xd0, 0xb0,}; // Units const uint8_t HERTZ_ICON[8] = {0xfe,0x10,0x10,0xfe,0x00,0xc8,0xa8,0x98}; From 00cb6f5a392cf1f80f1ab5d937f935e8565fefb8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 14 Aug 2023 18:23:11 -0400 Subject: [PATCH 267/417] Revert a few icons, DualTM UI tweaks --- software/o_c_REV/HEM_TM2.ino | 14 +++++++------- software/o_c_REV/HSicons.h | 16 +++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 8619c0e87..fefb0c915 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -382,7 +382,7 @@ private: switch (cvmode[ch]) { case SLEW_MOD: - gfxIcon(15 + ch*32, 35, MOD_ICON); + gfxIcon(15 + ch*32, 35, SLEW_ICON); break; case LENGTH_MOD: gfxIcon(15 + ch*32, 35, LOOP_ICON); @@ -391,7 +391,7 @@ private: gfxIcon(15 + ch*32, 35, TOSS_ICON); break; case RANGE_MOD: - gfxIcon(15 + ch*32, 35, RANGE_ICON); + gfxIcon(15 + ch*32, 35, UP_DOWN_ICON); break; case TRANSPOSE1: gfxIcon(15 + ch*32, 35, BEND_ICON); @@ -411,15 +411,15 @@ private: void DrawSelector() { gfxBitmap(1, 14, 8, LOOP_ICON); gfxPrint(12 + pad(10, len_mod), 15, len_mod); - gfxPrint(pad(100, p_mod), p_mod); + gfxPrint(35 + pad(100, p_mod), 15, p_mod); if (cursor == PROB || Gate(1)) { // p unlocked - gfxBitmap(49, 15, 8, TOSS_ICON); + gfxBitmap(55, 15, 8, TOSS_ICON); } else { // p is disabled - gfxBitmap(49, 15, 8, LOCK_ICON); + gfxBitmap(55, 15, 8, LOCK_ICON); } gfxBitmap(1, 25, 8, SCALE_ICON); gfxPrint(9, 25, OC::scale_names_short[scale]); - gfxBitmap(40, 25, 8, RANGE_ICON); + gfxBitmap(40, 25, 8, UP_DOWN_ICON); gfxPrint(49, 25, range_mod); // APD switch ((TM2Cursor)cursor){ @@ -442,7 +442,7 @@ private: switch ((TM2Cursor)cursor) { case LENGTH: gfxCursor(13, 23, 12); break; - case PROB: gfxCursor(35, 23, 10); break; + case PROB: gfxCursor(35, 23, 18); break; case SCALE: gfxCursor(9, 33, 25); break; case RANGE: gfxCursor(49, 33, 14); break; diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index 04f211299..7598caa44 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -6,10 +6,10 @@ const uint8_t VOCT_ICON[8] = { 0x07, 0x08, 0x27, 0x10, 0x08, 0xe4, 0xa0, 0xe0 }; const uint8_t TR_ICON[8] = {0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x00, 0x00, 0x00 }; -const uint8_t GATE_ICON[8] = {0x40, 0x7e, 0x02, 0x02, 0x02, 0x7e, 0x40, 0x40}; // rename to GATE_ICON? +const uint8_t GATE_ICON[8] = {0x40, 0x7e, 0x02, 0x02, 0x02, 0x7e, 0x40, 0x40}; const uint8_t SLEW_ICON[8] = { 0x40, 0x7e, 0x02, 0x02, 0x0c, 0x30, 0x40, 0x40}; -const uint8_t LOOP_ICON[8] = { 0x0a, 0x01, 0x09, 0x22, 0x48, 0x40, 0x28, 0x00 }; -const uint8_t LENGTH_ICON[8] = { 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00 }; +const uint8_t LENGTH_ICON[8] = { 0x0a, 0x01, 0x09, 0x22, 0x48, 0x40, 0x28, 0x00 }; +//const uint8_t LENGTH_ICON[8] = { 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00 }; const uint8_t RANGE_ICON[8] = { 0x42, 0x7e, 0x42, 0x00, 0x7e, 0x7e, 0x00, 0x78 }; const uint8_t PULSES_ICON[8] = {0x08, 0x00, 0x1c, 0x1c, 0x1c, 0x00, 0x08, 0x00}; const uint8_t OFFSET_ICON[8] = { 0x08, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1c, 0x00 }; @@ -18,7 +18,8 @@ const uint8_t TOSS_ICON[8] = { 0x0c, 0x12, 0x52, 0x91, 0x91, 0x09, 0x49, 0 const uint8_t METER_ICON[8] = {0x00,0xff,0x00,0xfc,0x00,0xff,0x00,0xfc}; const uint8_t CLOCK_ICON[8] = { 0x3c, 0x42, 0x81, 0x8d, 0x91, 0x81, 0x42, 0x3c }; -const uint8_t MOD_ICON[8] = { 0x20, 0x18, 0x04, 0x08, 0x08, 0x10, 0x20, 0x20 }; +const uint8_t ENV_ICON[8] = { 0x20, 0x18, 0x04, 0x08, 0x08, 0x10, 0x20, 0x20 }; +const uint8_t MOD_ICON[8] = {0x30,0x08,0x04,0x08,0x10,0x20,0x10,0x0c}; const uint8_t BEND_ICON[8] = { 0x00, 0x30, 0x3e, 0x82, 0x70, 0x0e, 0x01, 0x00 }; const uint8_t AFTERTOUCH_ICON[8] = {0x00,0x00,0x20,0x42,0xf5,0x48,0x20,0x00}; const uint8_t MIDI_ICON[8] = {0x3c,0x42,0x91,0x45,0x45,0x91,0x42,0x3c}; @@ -31,7 +32,7 @@ const uint8_t ROTATE_L_ICON[8] = { 0x0c, 0x1e, 0x3f, 0x0c, 0x0c, 0x1c, 0x78, 0 const uint8_t ROTATE_R_ICON[8] = { 0x00, 0x78, 0x1c, 0x0c, 0x0c, 0x3f, 0x1e, 0x0c }; const uint8_t MONITOR_ICON[8] = {0x1f,0x51,0x51,0x71,0x71,0x51,0x51,0x1f}; const uint8_t AUDITION_ICON[8] = {0x78,0x68,0x68,0x78,0x48,0x4c,0x4a,0x79}; -const uint8_t LINK_ICON[8] = { 0x18, 0x24, 0x24, 0x10, 0x08, 0x24, 0x24, 0x18}; +const uint8_t LINK_ICON[8] = {0x70,0xd8,0x88,0xda,0x5b,0x11,0x1b,0x0e}; const uint8_t CHECK_OFF_ICON[8] = {0xff,0x81,0x81,0x81,0x81,0x81,0x81,0xff}; const uint8_t CHECK_ON_ICON[8] = {0xcb,0x99,0xb1,0xb1,0x99,0x8c,0x86,0xf3}; const uint8_t CHECK_ICON[8] = {0x08,0x18,0x30,0x30,0x18,0x0c,0x06,0x03}; @@ -55,6 +56,7 @@ const uint8_t BTN_ON_ICON[8] = {0x71,0xaa,0xa8,0xab,0xa8,0xaa,0x71,0x00}; const uint8_t ZAP_ICON[8] = {0x00,0x08,0x9c,0x5e,0x7a,0x39,0x10,0x00}; // Transport +const uint8_t LOOP_ICON[8] = {0x34,0x64,0x4e,0x4e,0xe4,0xe4,0x4c,0x58}; const uint8_t PLAYONCE_ICON[8] = {0x10,0x10,0x10,0x10,0x38,0x38,0x10,0x10}; const uint8_t PLAY_ICON[8] = {0x00,0x7e,0x7e,0x3c,0x3c,0x18,0x18,0x00}; const uint8_t PAUSE_ICON[8] = {0x00,0x7e,0x7e,0x00,0x00,0x7e,0x7e,0x00}; @@ -92,8 +94,8 @@ const uint8_t WAVEFORM_ICON[8] = {0x10, 0x0c, 0x02, 0x0c, 0x70, 0x80, 0x60, 0x const uint8_t STAIRS_ICON[8] = {0x00,0x20,0x20,0x38,0x08,0x0e,0x02,0x02}; // Some stairs going up // Superscript and subscript 1 and 2 -const uint8_t SUP_ONE[3] = {0x02, 0x0f, 0x00}; -const uint8_t SUB_TWO[3] = {0x00, 0xd0, 0xb0,}; +const uint8_t SUP_ONE[3] = {0x0a,0x0f,0x08}; +const uint8_t SUB_TWO[3] = {0x90,0xd0,0xa0}; // Units const uint8_t HERTZ_ICON[8] = {0xfe,0x10,0x10,0xfe,0x00,0xc8,0xa8,0x98}; From 1529648516a5732e402830997e1355e1a2b9297d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 16 Jul 2023 23:34:01 -0400 Subject: [PATCH 268/417] Begin TB-3PO cleanup (WIP) reformat via codebeautify.org --- software/o_c_REV/HEM_TB3PO.ino | 1488 ++++++++++++++------------------ 1 file changed, 639 insertions(+), 849 deletions(-) diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 57b7bb445..1fe38c1a6 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -18,7 +18,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. - // TB-3PO Hemisphere Applet // A random generator of TB-303 style acid patterns, closely following 303 gate timings // CV output 1 is pitch, CV output 2 is gates @@ -28,7 +27,6 @@ // Contributions: // Thanks to Github/Muffwiggler user Qiemem for adding reseed(), to break the small cycle of available seed values that was occurring in practice - #include "braids_quantizer.h" #include "braids_quantizer_scales.h" #include "OC_scales.h" @@ -36,944 +34,736 @@ #define ACID_HALF_STEPS 16 #define ACID_MAX_STEPS 32 -class TB_3PO : public HemisphereApplet -{ +class TB_3PO: public HemisphereApplet { public: - const char* applet_name() { // Maximum 10 characters - return "TB-3PO"; + const char * applet_name() { // Maximum 10 characters + return "TB-3PO"; } - void Start() - { - manual_reset_flag = 0; - rand_apply_anim = 0; - curr_step_semitone = 0; - - root = 0; - octave_offset = 0; - - // Init the quantizer for selecting pitches / CVs from - scale = 29; // GUNA scale sounds cool //OC::Scales::SCALE_SEMI; // semi sounds pretty bunk - quantizer = GetQuantizer(0); - set_quantizer_scale(scale); + void Start() { + manual_reset_flag = 0; + rand_apply_anim = 0; + curr_step_semitone = 0; - density = 12; - density_encoder_display = 0; + root = 0; + octave_offset = 0; - num_steps = 16; - - gate_off_clock = 0; - cycle_time = 0; - - curr_gate_cv = 0; - curr_pitch_cv = 0; - - slide_start_cv = 0; - slide_end_cv = 0; - - //transpose_note_in = 0; - - lock_seed = 0; - reseed(); - regenerate_all(); + // Init the quantizer for selecting pitches / CVs from + scale = 29; // GUNA scale sounds cool //OC::Scales::SCALE_SEMI; // semi sounds pretty bunk + quantizer = GetQuantizer(0); + set_quantizer_scale(scale); - } + density = 12; + density_encoder_display = 0; - void Controller() - { - // Track timing to set gate timing at ~32nd notes per recent clocks - int this_tick = OC::CORE::ticks; - - // Regenerate / Reset - if (Clock(1) || manual_reset_flag) - { - manual_reset_flag = 0; - // If the seed is not locked, then randomize it on every reset pulse - // Otherwise, the user has locked it, so leave it as set - if(lock_seed == 0) - { - reseed(); - } + num_steps = 16; - // Apply the seed to regenerate the pattern` - // This is deterministic so if the seed is held, the pattern will not change - regenerate_all(); + gate_off_clock = 0; + cycle_time = 0; - // Reset step - step = 0; - } + curr_gate_cv = 0; + curr_pitch_cv = 0; - // Control transpose from cv1 (Very fun to wiggle) - //transpose_note_in = 99; // Display only: flag no xpose for 0v (would be a pitch like -24, etc) - transpose_cv = 0; - if (DetentedIn(0)) - { - // Original: 1v == 12 scale steps - // Note: This appears to frequently result in coming up short on some notes when using a cv keyboard (e.g. c# might be c) (prefer interpreting this via a windowed quantizer?) - //transpose_note_in = In(0) / 128; // 128 ADC steps per semitone - - // Quantize the transpose CV to the same scale as the sequence, always based on c. - // This allows a CV keyboard or sequencer to work reliably to transpose (e.g. every c is another octave) regardless of scale. - // However, the transposition is limited to only in-scale notes so arpeggiations via LFOs, etc are still easily done. - // (This CV is summed to the sequence pitch CV directly before output, rather than affecting its note indices.) - transpose_cv = quantizer->Process(In(0), 0, 0); // Use root == 0 to start at c - //transpose_note_in = quantizer->GetLatestNoteNumber() - 64; // For debug readout! - } - - // Offset density from its encoder-set value with cv2 (Wiggling can build up & break down patterns nicely, especially if seed is locked) - { - // -2.5v to +5v (HEMISPHERE_MAX_CV), giving about -8 to +15 added to encoder density value - // Note: DetentedIn is used to cut out noise near 0, even though it's being quantized to int below (primarily to make the cv icon work better) - int signal = constrain(DetentedIn(1), -HEMISPHERE_3V_CV, HEMISPHERE_MAX_INPUT_CV); // Allow negative to go about as far as it will reach - density_cv = Proportion(abs(signal), HEMISPHERE_MAX_INPUT_CV, 15); // Apply proportion uniformly to +- voltages as + for symmetry (Avoids rounding differences) - if(signal <0) - { - density_cv *= -1; // Restore negative sign if -v - } - density = static_cast(constrain(density_encoder + density_cv, 0, 14)); - } - - // Wait for the ADC since transpose CV is needed - if (Clock(0)) - { - cycle_time = ClockCycleTicks(0); // Track latest interval of clock 0 for gate timings + slide_start_cv = 0; + slide_end_cv = 0; - // Sneak this in here before clock is 'applied' and the next step is reached, to re-apply density to the pattern if required - regenerate_if_density_or_scale_changed(); // Flag to do the actual update at end of Controller() - - StartADCLag(); - } + //transpose_note_in = 0; - if (EndOfADCLag() && !Gate(1)) // Reset not held - { - int step_pv = step; - - // Advance the step - step = get_next_step(step); - - // Was step before this one set to 'slide'? - // If so, engage a the 'slide circuit' from its pitch to this new step's pitch - if(step_is_slid(step_pv)) - { - // Slide begins from the prior step's pitch (TODO: just use current dac output?) - slide_start_cv = get_pitch_for_step(step_pv); - - // Jump current pitch to prior step's value if not there already - // TODO: Consider just gliding from whereever it is? - curr_pitch_cv = slide_start_cv; - - // Slide target is this step's pitch - slide_end_cv = get_pitch_for_step(step); - } - else - { - // Prior step was not slid, so snap to current pitch - curr_pitch_cv = get_pitch_for_step(step); - slide_start_cv = curr_pitch_cv; - slide_end_cv = curr_pitch_cv; - } - - // Open the gate if this step is gated, or hold it open for at least 1/2 step if the prior step was slid - if(step_is_gated(step) || step_is_slid(step_pv)) - { - // Accented gates get a higher voltage, so it can drive VCA gain in addition to triggering envelope generators - curr_gate_cv = step_is_accent(step) ? HEMISPHERE_MAX_CV : HEMISPHERE_3V_CV; + lock_seed = 0; + reseed(); + regenerate_all(); - // On each clock, schedule the next clock at a multiplied rate - int gate_time = (cycle_time / 2); // multiplier of 2 - gate_off_clock = this_tick + gate_time; - } + } - // When changing steps, compute the nearest semitone at the base octave to show on the keyboard - curr_step_semitone = get_semitone_for_step(step); - - } + void Controller() { + int this_tick = OC::CORE::ticks; - // Update the clock multiplier for gate off timings - if(curr_gate_cv > 0 && gate_off_clock > 0 && this_tick >= gate_off_clock) - { - // Handle turning the gate off, unless sliding - gate_off_clock = 0; - - // Do nothing if the current step should be slid - if(!step_is_slid(step)) - { - curr_gate_cv = 0; - } + if (Clock(1) || manual_reset_flag) { + manual_reset_flag = 0; + if (lock_seed == 0) { + reseed(); } - // Update slide if needed - if(curr_pitch_cv != slide_end_cv) - { - // This gives constant rate linear glide (but we want expo fixed-time): - // curr_pitch_cv += (slide_end_cv - curr_pitch_cv > 0 ? 1 : -1); - - // (This could optionally use peak's lut_env_expo[] for interpolation instead) - // Expo slide (code assist from CBS) - int k = 0x0003; // expo constant: 0 = infinite time to settle, 0xFFFF ~= 1, fastest rate - // Choose this to give 303-like pitch slide timings given the O&C's update rate - // k = 0x3 sounds good here with >>=18 - - int x = slide_end_cv; - x -= curr_pitch_cv; - x >>= 18; - x *= k; - curr_pitch_cv += x; - - // TODO: Check constrain - if(slide_start_cv < slide_end_cv) - { - curr_pitch_cv = constrain(curr_pitch_cv, slide_start_cv, slide_end_cv); + regenerate_all(); - // set a bit if constrain was needed - } - else - { - curr_pitch_cv = constrain(curr_pitch_cv, slide_end_cv, slide_start_cv); + step = 0; + } - // set a bit if constrain was needed - } + transpose_cv = 0; + if (DetentedIn(0)) { + transpose_cv = quantizer->Process(In(0), 0, 0); // Use root == 0 to start at c + } + + { + int signal = constrain(DetentedIn(1), -HEMISPHERE_3V_CV, HEMISPHERE_MAX_INPUT_CV); // Allow negative to go about as far as it will reach + density_cv = Proportion(abs(signal), HEMISPHERE_MAX_INPUT_CV, 15); // Apply proportion uniformly to +- voltages as + for symmetry (Avoids rounding differences) + if (signal < 0) { + density_cv *= -1; // Restore negative sign if -v } + density = static_cast < uint8_t > (constrain(density_encoder + density_cv, 0, 14)); + } - // Pitch out - Out(0, curr_pitch_cv); - - // Gate out (as CV) - Out(1, curr_gate_cv); + if (Clock(0)) { + cycle_time = ClockCycleTicks(0); // Track latest interval of clock 0 for gate timings + regenerate_if_density_or_scale_changed(); // Flag to do the actual update at end of Controller() - // Timesliced generation of new patterns, if triggered - // Do this last to not interfere with the body of the time for this hemisphere's update - // (This is speculation without knowing how to best profile performance on this system) - update_regeneration(); + StartADCLag(); } - void View() { - gfxHeader(applet_name()); - DrawGraphics(); - } + if (EndOfADCLag() && !Gate(1)) // Reset not held + { + int step_pv = step; - void OnButtonPress() { - CursorAction(cursor, 8); - } + step = get_next_step(step); - void OnEncoderMove(int direction) - { - if (!EditMode()) { // move cursor - MoveCursor(cursor, direction, 8); + if (step_is_slid(step_pv)) { + slide_start_cv = get_pitch_for_step(step_pv); - if (!lock_seed && cursor == 1) cursor = 5; // skip from 1 to 5 if not locked - if (!lock_seed && cursor == 4) cursor = 0; // skip from 4 to 0 if not locked + // TODO: Consider just gliding from whereever it is? + curr_pitch_cv = slide_start_cv; - return; + slide_end_cv = get_pitch_for_step(step); + } else { + curr_pitch_cv = get_pitch_for_step(step); + slide_start_cv = curr_pitch_cv; + slide_end_cv = curr_pitch_cv; } - // edit param - switch(cursor) { - case 0: - // Toggle the seed between auto (randomized every reset input pulse) - // or Manual (seed becomes locked, cursor can be moved to edit each digit) - lock_seed += direction; - - // See if the turn would move beyond the random die to the left or the lock to the right - // If so, take this as a manual input just like receiving a reset pulse (handled in Controller()) - // regenerate_all() will honor the random or locked icon shown (seed will be randomized or not) - manual_reset_flag = (lock_seed > 1 || lock_seed < 0) ? 1 : 0; - - // constrain to legal values before regeneration - lock_seed = constrain(lock_seed, 0, 1); - break; - case 1: - case 2: - case 3: - case 4: { // Editing one of the 4 hex digits of the seed - // cursor==1 is at the most significant byte, - // cursor==4 is at least significant byte - int byte_offs = 4-cursor; - int shift_amt = byte_offs*4; - - uint32_t nib = (seed >> shift_amt)& 0xf; // Abduct the nibble - uint8_t c = nib; - c = constrain(c+direction, 0, 0xF); // Edit the nibble - nib = c; - uint32_t mask = 0xf; - seed &= ~(mask << shift_amt); // Clear bits where this nibble lives - seed |= (nib << shift_amt); // Move the nibble to its home - break; - } - case 5: // density - density_encoder = constrain(density_encoder + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice - density_encoder_display = 400; // How long to show the encoder version of density in the number display for - - //density = constrain(density + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice - - // Disabled: Let this occur when detected on the next step - //regenerate_density_if_changed(); - break; - case 6: { // Scale selection - scale += direction; - if (scale >= OC::Scales::NUM_SCALES) scale = 0; - if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - // Apply to the quantizer - set_quantizer_scale(scale); - - // New: Constrain root to scale size (leave oct offset where it is) - int max_root = scale_size > 12 ? 12 : scale_size; - if(max_root > 0) - { - root = constrain(root, 0, max_root-1); - } - break; + if (step_is_gated(step) || step_is_slid(step_pv)) { + curr_gate_cv = step_is_accent(step) ? HEMISPHERE_MAX_CV : HEMISPHERE_3V_CV; + + int gate_time = (cycle_time / 2); // multiplier of 2 + gate_off_clock = this_tick + gate_time; } - case 7: { // Root note selection - - // No oct version - //root = constrain(root + direction, 0, 11); - - // Add in handling for octave settings without affecting root's range - int r = root + direction; - - int max_root = scale_size > 12 ? 12 : scale_size; - - //if(direction > 0 && r > 11 && octave_offset < 3) - if(direction > 0 && r >= max_root && octave_offset < 3) - { - ++octave_offset; // Go up to next octave - r = 0; // Roll around root note - } - else if(direction < 0 && r < 0 && octave_offset > -3) - { - --octave_offset; - r = max_root-1; - //r = 11; // Roll around root note - } + curr_step_semitone = get_semitone_for_step(step); - // Limit root value - //root = constrain(r, 0, 11); - root = constrain(r, 0, max_root-1); + } - break; + if (curr_gate_cv > 0 && gate_off_clock > 0 && this_tick >= gate_off_clock) { + gate_off_clock = 0; + + if (!step_is_slid(step)) { + curr_gate_cv = 0; } - case 8: // pattern length - num_steps = constrain(num_steps + direction, 1, 32); - break; - } //switch - } //OnEncoderMove - - uint64_t OnDataRequest() { - uint64_t data = 0; - - Pack(data, PackLocation {0,8}, scale); - Pack(data, PackLocation {8,4}, root); - Pack(data, PackLocation {12,4}, density_encoder); - Pack(data, PackLocation {16,16}, seed); - Pack(data, PackLocation {32,8}, octave_offset); - Pack(data, PackLocation {40,5}, num_steps - 1); - return data; - } - - void OnDataReceive(uint64_t data) { - - scale = Unpack(data, PackLocation {0,8}); - root = Unpack(data, PackLocation {8,4}); - density_encoder = Unpack(data, PackLocation {12,4}); - seed = Unpack(data, PackLocation {16,16}); - octave_offset = Unpack(data, PackLocation {32,8}); - num_steps = Unpack(data, PackLocation {40,5}) + 1; - - //const braids::Scale & quant_scale = OC::Scales::GetScale(scale); - set_quantizer_scale(scale); - - //scale = constrain(0, OC::Scales::NUM_SCALES-1); - root = constrain(root, 0, 11); - density_encoder = constrain(density_encoder, 0, 14); // Internally just positive - density = density_encoder; - octave_offset = constrain(octave_offset,-3,3); - - // Restore all seed-derived settings! - regenerate_all(); + } - // Reset step position - step = 0; + if (curr_pitch_cv != slide_end_cv) { + int k = 0x0003; // expo constant: 0 = infinite time to settle, 0xFFFF ~= 1, fastest rate + // Choose this to give 303-like pitch slide timings given the O&C's update rate + // k = 0x3 sounds good here with >>=18 + + int x = slide_end_cv; + x -= curr_pitch_cv; + x >>= 18; + x *= k; + curr_pitch_cv += x; + + // TODO: Check constrain, set a bit if constrain was needed + if (slide_start_cv < slide_end_cv) { + curr_pitch_cv = constrain(curr_pitch_cv, slide_start_cv, slide_end_cv); + } else { + curr_pitch_cv = constrain(curr_pitch_cv, slide_end_cv, slide_start_cv); + } } - protected: - void SetHelp() { - // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Regen"; - help[HEMISPHERE_HELP_CVS] = "1=Transp 2=Density"; - help[HEMISPHERE_HELP_OUTS] = "A=CV+glide B=Gate"; - help[HEMISPHERE_HELP_ENCODER] = "seed/dens/qnt/len"; - // "------------------" <-- Size Guide - } - - private: - int cursor = 0; - - braids::Quantizer* quantizer; // Helper for note index --> pitch cv - - // User settings - - // Bool - int manual_reset_flag = 0; // Manual trigger to reset/regen - - // bool - int lock_seed; // If 1, the seed won't randomize (and manual editing is enabled) - - uint16_t seed; // The random seed that deterministically builds the sequence - - int scale; // Active quantization & generation scale - uint8_t root; // Root note - int8_t octave_offset; // Manual octave offset (based on size of current scale, added to root note) - - uint8_t density; // The density parameter controls a couple of things at once. Its 0-14 value is mapped to -7..+7 range - // The larger the magnitude from zero in either direction, the more dense the note patterns are (fewer rests) - // For values mapped < 0 (e.g. left range,) the more negative the value is, the less chance consecutive pitches will - // change from the prior pitch, giving repeating lines (note: octave jumps still apply) - - uint8_t current_pattern_density; // Track what density value was used to generate the current pattern (to detect if regeneration is required) - - // Density controls (Encoder sets center point, CV can apply +-) - int density_encoder; // density value contributed by the encoder (center point) - int density_cv; // density value (+-) contributed by CV - int density_encoder_display; // Countdown of frames to show the encoder's density value (centerpoint) - uint8_t num_steps; // How many steps of the generated pattern to play before looping - - // Playback - uint8_t step = 0; // Current sequencer step - - //int transpose_note_in; // Current transposition from cv in (initially a cv value) TEMP: REMOVE - int32_t transpose_cv; // Quantized transpose in cv - - // Generated sequence data - uint32_t gates = 0; // Bitfield of gates; ((gates >> step) & 1) means gate - uint32_t slides = 0; // Bitfield of slide steps; ((slides >> step) & 1) means slide - uint32_t accents = 0; // Bitfield of accent steps; ((accents >> step) & 1) means accent - uint32_t oct_ups = 0; // Bitfield of octave ups - uint32_t oct_downs = 0; // Bitfield of octave downs - uint8_t notes[ACID_MAX_STEPS]; // Note values - - uint8_t scale_size; // The size of the currently set quantizer scale (for octave detection, etc) - uint8_t current_pattern_scale_size; // Track what size scale was used to render the current pattern (for change detection) - - // For gate timing as ~32nd notes at tempo, detect clock rate like a clock multiplier - //int timing_count; - int gate_off_clock; // Scheduled cycle at which the gate should be turned off (when applicable) - int cycle_time; // Cycle time between the last two clock inputs - - // CV output values - int32_t curr_gate_cv = 0; - int32_t curr_pitch_cv = 0; - - // Pitch slide cv tracking - int32_t slide_start_cv = 0; - int32_t slide_end_cv = 0; - - // Display - int curr_step_semitone = 0; // The pitch converted to nearest semitone, for showing as an index onto the keyboard - - uint8_t rand_apply_anim = 0; // Countdown to animate icons for when regenerate occurs - - uint8_t regenerate_phase = 0; // Split up random generation over multiple frames - - // Get the cv value to use for a given step including root + transpose values - int get_pitch_for_step(int step_num) - { - // Original: Transpose pre-quantize - //int quant_note = 64 + int(notes[step_num]) + int(root) + int(transpose_note_in); + Out(0, curr_pitch_cv); + Out(1, curr_gate_cv); - int quant_note = 64 + int(notes[step_num]) + int(root); + // Timesliced generation of new patterns, if triggered + // Do this last to not interfere with the body of the time for this hemisphere's update + // (This is speculation without knowing how to best profile performance on this system) + update_regeneration(); + } - // Apply the manual octave offset - quant_note += (int(octave_offset) * int(scale_size)); + void View() { + gfxHeader(applet_name()); + DrawGraphics(); + } - // Transpose by one octave up or down if flagged to (note this is one full span of whatever scale is active to give doubling octave behavior) - if(step_is_oct_up(step_num)) - { - quant_note += scale_size; - } - else if(step_is_oct_down(step_num)) - { - quant_note -= scale_size; - } + void OnButtonPress() { + CursorAction(cursor, 8); + } - int out_note = constrain(quant_note, 0, 127); + void OnEncoderMove(int direction) { + if (!EditMode()) { // move cursor + MoveCursor(cursor, direction, 8); - // New: Transpose post-quantize - int pitch_cv = quantizer->Lookup(out_note) + transpose_cv; - return pitch_cv; + if (!lock_seed && cursor == 1) cursor = 5; // skip from 1 to 5 if not locked + if (!lock_seed && cursor == 4) cursor = 0; // skip from 4 to 0 if not locked - // Original: Output quantized after transposition added - //return quantizer->Lookup( out_note ); - - //return quantizer->Lookup( 64 ); // Test: note 64 is definitely 0v=c4 if output directly, on ALL scales + return; } - int get_semitone_for_step(int step_num) - { - // Don't add in octaves-- use the current quantizer limited to the base octave - int quant_note = 64 + notes[step_num] + root;// + transpose_note_in; - int32_t cv_note = quantizer->Lookup( constrain(quant_note, 0, 127)); - return MIDIQuantizer::NoteNumber(cv_note) % 12; - } + // edit param + switch (cursor) { + case 0: + lock_seed += direction; + + manual_reset_flag = (lock_seed > 1 || lock_seed < 0) ? 1 : 0; + + lock_seed = constrain(lock_seed, 0, 1); + break; + case 1: + case 2: + case 3: + case 4: { // Editing one of the 4 hex digits of the seed + int byte_offs = 4 - cursor; + int shift_amt = byte_offs * 4; + + uint32_t nib = (seed >> shift_amt) & 0xf; // Abduct the nibble + uint8_t c = nib; + c = constrain(c + direction, 0, 0xF); // Edit the nibble + nib = c; + uint32_t mask = 0xf; + seed &= ~(mask << shift_amt); // Clear bits where this nibble lives + seed |= (nib << shift_amt); // Move the nibble to its home + break; + } + case 5: // density + density_encoder = constrain(density_encoder + direction, 0, 14); // Treated as a bipolar -7 to 7 in practice + density_encoder_display = 400; // How long to show the encoder version of density in the number display for + + break; + case 6: { // Scale selection + scale += direction; + if (scale >= OC::Scales::NUM_SCALES) scale = 0; + if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; + set_quantizer_scale(scale); - void reseed() - { - randomSeed(micros()); - seed = random(0, 65535); // 16 bits - } - - // Trigger generating the sequence deterministically using the seed (over the next couple of Controller() calls) - void regenerate_all() - { - regenerate_phase = 1; // Set to regenerate on loop - rand_apply_anim = 40; // Show that regenerate started (anim for this many display updates) - } - - void regenerate_if_density_or_scale_changed() - { - // Skip if density has not changed, or if currently regenerating - if(regenerate_phase == 0) - { - if(density != current_pattern_density || scale_size != current_pattern_scale_size) - { - regenerate_phase = 1; // regenerate all since pitches take density into account - } + int max_root = scale_size > 12 ? 12 : scale_size; + if (max_root > 0) { + root = constrain(root, 0, max_root - 1); } + break; } + case 7: { // Root note selection - // Amortize random generation over multiple frames - // Without having profiled this properly, I'm less concerned about overrunning isr times alloted to this app if it's amortized - void update_regeneration() - { - if(regenerate_phase == 0) - { - return; - } - - randomSeed(seed+regenerate_phase); // Ensure random()'s seed at each phase for determinism (note: offset to decouple phase behavior correllations that would result) - - switch(regenerate_phase) - { - // 1st set of 16 steps - case 1: regenerate_pitches(); ++regenerate_phase; break; - //case 2: apply_density(); regenerate_phase = 0; break; - case 2: apply_density(); ++regenerate_phase;break; - // 2nd set of 16 steps - case 3: regenerate_pitches(); ++regenerate_phase; break; - // After doing the 2nd set of bitvectors, swap the low and high 16 bits to align the first 16 steps to the steps they would have had - // when this app only rendered 16 steps - //case 4: apply_density(); restore_legacy_byte_orders(); regenerate_phase = 0; break; - case 4: apply_density(); regenerate_phase = 0; break; - default: break; + int r = root + direction; + int max_root = scale_size > 12 ? 12 : scale_size; + + if (direction > 0 && r >= max_root && octave_offset < 3) { + ++octave_offset; // Go up to next octave + r = 0; // Roll around root note + } else if (direction < 0 && r < 0 && octave_offset > -3) { + --octave_offset; + + r = max_root - 1; } + + root = constrain(r, 0, max_root - 1); + + break; } + case 8: // pattern length + num_steps = constrain(num_steps + direction, 1, 32); + break; + } //switch + } //OnEncoderMove + + uint64_t OnDataRequest() { + uint64_t data = 0; + + Pack(data, PackLocation { 0, 8 }, scale); + Pack(data, PackLocation { 8, 4 }, root); + Pack(data, PackLocation { 12, 4 }, density_encoder); + Pack(data, PackLocation { 16, 16 }, seed); + Pack(data, PackLocation { 32, 8 }, octave_offset); + Pack(data, PackLocation { 40, 5 }, num_steps - 1); + return data; + } - - // Generate the notes sequence based on the seed and modified by density - void regenerate_pitches() - { + void OnDataReceive(uint64_t data) { - // 32 steps are computed across two passes of this function - // Determine if the first 16 or second 16 steps are being handled here - bool bFirstHalf = regenerate_phase < 3; + scale = Unpack(data, PackLocation { 0, 8 }); + root = Unpack(data, PackLocation { 8, 4 }); + density_encoder = Unpack(data, PackLocation { 12, 4 }); + seed = Unpack(data, PackLocation { 16, 16 }); + octave_offset = Unpack(data, PackLocation { 32, 8 }); + num_steps = Unpack(data, PackLocation { 40, 5 }) + 1; - // How much pitch variety to use from the available pitches (one of the factors of the 'density' control when < centerpoint) - int pitch_change_dens = get_pitch_change_density(); - int available_pitches = 0; - if(scale_size > 0) - { - if(pitch_change_dens > 7) - { - available_pitches = scale_size-1; - } - else if(pitch_change_dens < 2) - { - // Give the behavior of just the root note (0) at lowest density, and 0&1 at 2nd lowest (for 303 half-step style) - available_pitches = pitch_change_dens; - } - else // Range 3-7 - { - int range_from_scale = scale_size - 3; - if(range_from_scale < 4) // Ok to saturate at full note count - { - range_from_scale = 4; - } - // Range from 2 pitches to just <= full scale available - available_pitches = 3 + Proportion(pitch_change_dens-3, 4, range_from_scale); - available_pitches = constrain(available_pitches, 1, scale_size -1); - } - } + set_quantizer_scale(scale); - // Set notes and octave up / octave down bitvectors - if(bFirstHalf) - { - // Clear only on the first pass (otherwise, resume with current value) - oct_ups = 0; - oct_downs = 0; + root = constrain(root, 0, 11); + density_encoder = constrain(density_encoder, 0, 14); // Internally just positive + density = density_encoder; + octave_offset = constrain(octave_offset, -3, 3); + + // Restore all seed-derived settings! + regenerate_all(); + + // Reset step position + step = 0; + } + + protected: void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Regen"; + help[HEMISPHERE_HELP_CVS] = "1=Transp 2=Density"; + help[HEMISPHERE_HELP_OUTS] = "A=CV+glide B=Gate"; + help[HEMISPHERE_HELP_ENCODER] = "seed/dens/qnt/len"; + // "------------------" <-- Size Guide + } + + private: int cursor = 0; + + braids::Quantizer * quantizer; // Helper for note index --> pitch cv + + // User settings + + // Bool + int manual_reset_flag = 0; // Manual trigger to reset/regen + + // bool + int lock_seed; // If 1, the seed won't randomize (and manual editing is enabled) + + uint16_t seed; // The random seed that deterministically builds the sequence + + int scale; // Active quantization & generation scale + uint8_t root; // Root note + int8_t octave_offset; // Manual octave offset (based on size of current scale, added to root note) + + uint8_t density; // The density parameter controls a couple of things at once. Its 0-14 value is mapped to -7..+7 range + // The larger the magnitude from zero in either direction, the more dense the note patterns are (fewer rests) + // For values mapped < 0 (e.g. left range,) the more negative the value is, the less chance consecutive pitches will + // change from the prior pitch, giving repeating lines (note: octave jumps still apply) + + uint8_t current_pattern_density; // Track what density value was used to generate the current pattern (to detect if regeneration is required) + + // Density controls (Encoder sets center point, CV can apply +-) + int density_encoder; // density value contributed by the encoder (center point) + int density_cv; // density value (+-) contributed by CV + int density_encoder_display; // Countdown of frames to show the encoder's density value (centerpoint) + uint8_t num_steps; // How many steps of the generated pattern to play before looping + + // Playback + uint8_t step = 0; // Current sequencer step + + //int transpose_note_in; // Current transposition from cv in (initially a cv value) TEMP: REMOVE + int32_t transpose_cv; // Quantized transpose in cv + + // Generated sequence data + uint32_t gates = 0; // Bitfield of gates; ((gates >> step) & 1) means gate + uint32_t slides = 0; // Bitfield of slide steps; ((slides >> step) & 1) means slide + uint32_t accents = 0; // Bitfield of accent steps; ((accents >> step) & 1) means accent + uint32_t oct_ups = 0; // Bitfield of octave ups + uint32_t oct_downs = 0; // Bitfield of octave downs + uint8_t notes[ACID_MAX_STEPS]; // Note values + + uint8_t scale_size; // The size of the currently set quantizer scale (for octave detection, etc) + uint8_t current_pattern_scale_size; // Track what size scale was used to render the current pattern (for change detection) + + // For gate timing as ~32nd notes at tempo, detect clock rate like a clock multiplier + //int timing_count; + int gate_off_clock; // Scheduled cycle at which the gate should be turned off (when applicable) + int cycle_time; // Cycle time between the last two clock inputs + + // CV output values + int32_t curr_gate_cv = 0; + int32_t curr_pitch_cv = 0; + + // Pitch slide cv tracking + int32_t slide_start_cv = 0; + int32_t slide_end_cv = 0; + + // Display + int curr_step_semitone = 0; // The pitch converted to nearest semitone, for showing as an index onto the keyboard + + uint8_t rand_apply_anim = 0; // Countdown to animate icons for when regenerate occurs + + uint8_t regenerate_phase = 0; // Split up random generation over multiple frames + + // Get the cv value to use for a given step including root + transpose values + int get_pitch_for_step(int step_num) { + // Original: Transpose pre-quantize + //int quant_note = 64 + int(notes[step_num]) + int(root) + int(transpose_note_in); + + int quant_note = 64 + int(notes[step_num]) + int(root); + + // Apply the manual octave offset + quant_note += (int(octave_offset) * int(scale_size)); + + // Transpose by one octave up or down if flagged to (note this is one full span of whatever scale is active to give doubling octave behavior) + if (step_is_oct_up(step_num)) { + quant_note += scale_size; + } else if (step_is_oct_down(step_num)) { + quant_note -= scale_size; + } + + int out_note = constrain(quant_note, 0, 127); + + // New: Transpose post-quantize + int pitch_cv = quantizer->Lookup(out_note) + transpose_cv; + return pitch_cv; + //return quantizer->Lookup( 64 ); // Test: note 64 is definitely 0v=c4 if output directly, on ALL scales + } + + int get_semitone_for_step(int step_num) { + // Don't add in octaves-- use the current quantizer limited to the base octave + int quant_note = 64 + notes[step_num] + root; // + transpose_note_in; + int32_t cv_note = quantizer->Lookup(constrain(quant_note, 0, 127)); + return MIDIQuantizer::NoteNumber(cv_note) % 12; + } + + void reseed() { + randomSeed(micros()); + seed = random(0, 65535); // 16 bits + } + + void regenerate_all() { + regenerate_phase = 1; // Set to regenerate on loop + rand_apply_anim = 40; // Show that regenerate started (anim for this many display updates) + } + + void regenerate_if_density_or_scale_changed() { + // Skip if density has not changed, or if currently regenerating + if (regenerate_phase == 0) { + if (density != current_pattern_density || scale_size != current_pattern_scale_size) { + regenerate_phase = 1; // regenerate all since pitches take density into account } + } + } - // Do either the first or second set of steps on this pass - int max_step = (bFirstHalf ? ACID_HALF_STEPS : ACID_MAX_STEPS); - for (int s = (bFirstHalf ? 0 : ACID_HALF_STEPS); s < max_step; s++) + // Amortize random generation over multiple frames + void update_regeneration() { + if (regenerate_phase == 0) { + return; + } + + randomSeed(seed + regenerate_phase); // Ensure random()'s seed at each phase for determinism (note: offset to decouple phase behavior correllations that would result) + + switch (regenerate_phase) { + // 1st set of 16 steps + case 1: + regenerate_pitches(); + ++regenerate_phase; + break; + case 2: + apply_density(); + ++regenerate_phase; + break; + // 2nd set of 16 steps + case 3: + regenerate_pitches(); + ++regenerate_phase; + break; + case 4: + apply_density(); + regenerate_phase = 0; + break; + default: + break; + } + } + + // Generate the notes sequence based on the seed and modified by density + void regenerate_pitches() { + bool bFirstHalf = regenerate_phase < 3; + + // How much pitch variety to use from the available pitches (one of the factors of the 'density' control when < centerpoint) + int pitch_change_dens = get_pitch_change_density(); + int available_pitches = 0; + if (scale_size > 0) { + if (pitch_change_dens > 7) { + available_pitches = scale_size - 1; + } else if (pitch_change_dens < 2) { + // Give the behavior of just the root note (0) at lowest density, and 0&1 at 2nd lowest (for 303 half-step style) + available_pitches = pitch_change_dens; + } else // Range 3-7 { - // Increased chance to repeat the prior note, the smaller the pitch change aspect of 'density' is - // 0-8, least to most likely to change pitch - int force_repeat_note_prob = 50 - (pitch_change_dens * 6); - if(s > 0 && rand_bit(force_repeat_note_prob)) + int range_from_scale = scale_size - 3; + if (range_from_scale < 4) // Ok to saturate at full note count { - notes[s] = notes[s-1]; + range_from_scale = 4; } - else - { - // Grab a random note index from the scale's available pitches - // Since this starts at 0, the root note will always be included, and adjacent scale notes are included as the range grows - notes[s] = random(0,available_pitches+1); // Looking at the source, random(min,max) appears to return the range: min to max-1 - - // Random oct up or down (Treating octave based on the scale's number of notes) - oct_ups <<= 1; - oct_downs <<= 1; - - if(rand_bit(40)) - { - if(rand_bit(50)) - { - oct_ups |= 0x1; - } - else - { - oct_downs |= 0x1; - } - } - } + // Range from 2 pitches to just <= full scale available + available_pitches = 3 + Proportion(pitch_change_dens - 3, 4, range_from_scale); + available_pitches = constrain(available_pitches, 1, scale_size - 1); } + } - // Handle size as semitone scale for display if 'off' - if(scale_size == 0) - { - scale_size = 12; - } + if (bFirstHalf) { + oct_ups = 0; + oct_downs = 0; + } - current_pattern_scale_size = scale_size; - } - - // Change pattern density without affecting pitches - void apply_density() - { - int latest_slide = 0; // Track previous bit for some algos - int latest_accent = 0; // Track previous bit for some algos - - // Get gate probability from the 'density' value - int on_off_dens = get_on_off_density(); - int densProb = 10 + on_off_dens * 14; // Should start >0 and reach 100+ - - // Clear if this is the first 16 steps to generate (otherwise append to these bit vectors for the 2nd set of 16) - bool bFirstHalf = regenerate_phase < 3; - if(bFirstHalf) - { - gates = 0; - slides = 0; - accents = 0; + int max_step = (bFirstHalf ? ACID_HALF_STEPS : ACID_MAX_STEPS); + for (int s = (bFirstHalf ? 0 : ACID_HALF_STEPS); s < max_step; s++) { + int force_repeat_note_prob = 50 - (pitch_change_dens * 6); + if (s > 0 && rand_bit(force_repeat_note_prob)) { + notes[s] = notes[s - 1]; + } else { + notes[s] = random(0, available_pitches + 1); // Looking at the source, random(min,max) appears to return the range: min to max-1 + + oct_ups <<= 1; + oct_downs <<= 1; + + if (rand_bit(40)) { + if (rand_bit(50)) { + oct_ups |= 0x1; + } else { + oct_downs |= 0x1; + } + } } - - // Apply to each step - // Do half of the steps on each pass of this func - for(int i=0; i< ACID_HALF_STEPS; ++i) - { - gates <<= 1; - gates |= rand_bit(densProb); - - // Less probability of consecutive slides - slides <<= 1; - latest_slide = rand_bit((latest_slide ? 10 : 18)); - slides |= latest_slide; - - // Less probability of consecutive accents - accents <<= 1; - latest_accent = rand_bit((latest_accent ? 7 : 16)); - accents |= latest_accent; - } - - // Track the value of density used to render the pattern (to detect changes) - current_pattern_density = density; - - } - - // Get on/off likelihood from the current value of 'density' - int get_on_off_density() - { - // density has a range 0-14 - // Convert density to a bipolar value from -7..+7, with the +-7 extremes in either direction - // as high note density, and the 0 point as lowest possible note density - int note_dens = int(density) - 7; - return abs(note_dens); } - // Get the degree to which pitches should change based on the value of 'density' - // The density slider's center and right half indicate full pitch change range - // The further the slider is to the left of the centerpoint, the less pitches should change - int get_pitch_change_density() - { - // Smaller values indicate fewer pitches should be drawn from - return constrain(density, 0,8); // Note that the right half of the slider is clamped to full range - } - - bool step_is_gated(int step_num) { - return (gates & (0x01 << step_num)); - } - - bool step_is_slid(int step_num) { - return (slides & (0x01 << step_num)); - } - - bool step_is_accent(int step_num) { - return (accents & (0x01 << step_num)); + if (scale_size == 0) { + scale_size = 12; } - bool step_is_oct_up(int step_num){ - return (oct_ups & (0x01 << step_num)); + current_pattern_scale_size = scale_size; + } + + // Change pattern density without affecting pitches + void apply_density() { + int latest_slide = 0; // Track previous bit for some algos + int latest_accent = 0; // Track previous bit for some algos + + // Get gate probability from the 'density' value + int on_off_dens = get_on_off_density(); + int densProb = 10 + on_off_dens * 14; // Should start >0 and reach 100+ + + bool bFirstHalf = regenerate_phase < 3; + if (bFirstHalf) { + gates = 0; + slides = 0; + accents = 0; } - - bool step_is_oct_down(int step_num){ - return (oct_downs & (0x01 << step_num)); + + for (int i = 0; i < ACID_HALF_STEPS; ++i) { + gates <<= 1; + gates |= rand_bit(densProb); + + // Less probability of consecutive slides + slides <<= 1; + latest_slide = rand_bit((latest_slide ? 10 : 18)); + slides |= latest_slide; + + // Less probability of consecutive accents + accents <<= 1; + latest_accent = rand_bit((latest_accent ? 7 : 16)); + accents |= latest_accent; } - int get_next_step(int step_num) - { - // loop at the current loop point - if(++step_num >= num_steps) - { - return 0; - } - return step_num; // Advanced by one - } + current_pattern_density = density; + } - // Pass in a probability 0-100 to get that % chance to return 1 - int rand_bit(int prob) - { - return (random(1, 100) <= prob) ? 1 : 0; - } + int get_on_off_density() { + int note_dens = int(density) - 7; + return abs(note_dens); + } + int get_pitch_change_density() { + return constrain(density, 0, 8); // Note that the right half of the slider is clamped to full range + } - void set_quantizer_scale(int new_scale) - { - const braids::Scale & quant_scale = OC::Scales::GetScale(new_scale); - quantizer->Configure(quant_scale, 0xffff); - scale_size = quant_scale.num_notes; // Track this scale size for octaves and display + bool step_is_gated(int step_num) { + return (gates & (0x01 << step_num)); + } + + bool step_is_slid(int step_num) { + return (slides & (0x01 << step_num)); + } + + bool step_is_accent(int step_num) { + return (accents & (0x01 << step_num)); + } + + bool step_is_oct_up(int step_num) { + return (oct_ups & (0x01 << step_num)); + } + + bool step_is_oct_down(int step_num) { + return (oct_downs & (0x01 << step_num)); + } + + int get_next_step(int step_num) { + if (++step_num >= num_steps) { + return 0; } - - void DrawGraphics() - { - // Wiggle the icon when the sequence regenerates - int heart_y = 15; - int die_y = 15; - if(rand_apply_anim > 0) - { - --rand_apply_anim; - // First the heart jumps, then the die if not locked - if(rand_apply_anim > 20) - { - heart_y = 13; - } - else - { - die_y = 13; - } + return step_num; // Advanced by one + } + + int rand_bit(int prob) { + return (random(1, 100) <= prob) ? 1 : 0; + } + + void set_quantizer_scale(int new_scale) { + const braids::Scale & quant_scale = OC::Scales::GetScale(new_scale); + quantizer->Configure(quant_scale, 0xffff); + scale_size = quant_scale.num_notes; // Track this scale size for octaves and display + } + + void DrawGraphics() { + int heart_y = 15; + int die_y = 15; + if (rand_apply_anim > 0) { + --rand_apply_anim; + + if (rand_apply_anim > 20) { + heart_y = 13; + } else { + die_y = 13; } + } - // Heart represents the seed/favorite - gfxBitmap(4, heart_y, 8, FAVORITE_ICON); - - // Indicate if seed is randomized on reset pulse, or if it's locked for user editing - // (If unlocked, this also wiggles on regenerate because the seed has been randomized) - gfxBitmap(15, (lock_seed ? 15 : die_y), 8, (lock_seed ? LOCK_ICON : RANDOM_ICON)); - - // Show the 16-bit seed as 4 hex digits - int disp_seed = seed; //0xABCD // test display accuracy - char sz[2]; sz[1] = 0; // Null terminated string for easy print - gfxPos(25, 15); - for(int i=3; i>=0; --i) - { - // Grab each nibble in turn, starting with most significant - int nib = (disp_seed >> (i*4))& 0xF; - if(nib<=9) - { - gfxPrint(nib); - } - else - { - sz[0] = 'a' + nib - 10; - gfxPrint(static_cast(sz)); - } + // Heart represents the seed/favorite + gfxBitmap(4, heart_y, 8, FAVORITE_ICON); + + gfxBitmap(15, (lock_seed ? 15 : die_y), 8, (lock_seed ? LOCK_ICON : RANDOM_ICON)); + + // Show the 16-bit seed as 4 hex digits + int disp_seed = seed; //0xABCD // test display accuracy + char sz[2]; + sz[1] = 0; // Null terminated string for easy print + gfxPos(25, 15); + for (int i = 3; i >= 0; --i) { + // Grab each nibble in turn, starting with most significant + int nib = (disp_seed >> (i * 4)) & 0xF; + if (nib <= 9) { + gfxPrint(nib); + } else { + sz[0] = 'a' + nib - 10; + gfxPrint(static_cast(sz)); } - - // Display density - - int gate_dens = get_on_off_density(); - int pitch_dens = get_pitch_change_density(); - - //gfxLine(9,36, 29, 36, true); // dotted line - int xd = 5 + 7-gate_dens; - int yd = (64*pitch_dens)/256; // Multiply for better fidelity - gfxBitmap(12-xd, 27+yd, 8, NOTE4_ICON); - gfxBitmap(12, 27-yd, 8, NOTE4_ICON); - gfxBitmap(12+xd, 27, 8, NOTE4_ICON); - - // Display a number value for density - int dens_display = gate_dens; - bool dens_neg = false; - if(density_encoder_display > 0) - { - // The density encoder value was recently changed, so show it momentarily instead of the cv+encoder value normally shown - --density_encoder_display; - dens_display = abs(density_encoder-7); //Map from 0 to 14 --> -7 to 7 - dens_neg = density_encoder < 7; + } - if(density_cv != 0) // When cv is applied, show that this is the centered value being displayed - { - // Draw a knob to the left to represent the centerpoint being set - gfxCircle(3, 40, 3); - gfxLine(3, 38, 3, 40); - } - - } - else - { - dens_display = gate_dens; - dens_neg = density < 7; - // Indicate if cv is affecting the density - if(density_cv != 0) // Density integer contribution from CV (not raw cv) - { - gfxBitmap(22, 37, 8, CV_ICON); - } - } + // Display density - if(dens_neg) - { - gfxPrint(8, 37, "-"); // Print minus sign this way to right-align the number - } - gfxPrint(14, 37, dens_display); - - /* CV offset test - int test = Proportion(abs(density_cv), HEMISPHERE_3V_CV, 7); - if(density_cv < 0) test *= -1; - gfxPos(0, 27);gfxPrint(test); - gfxPos(0, 37); gfxPrintVoltage(density_cv); - */ - - // Scale and root note select - // xd = (scale < 4) ? 32 : 39; // Slide/crowd to the left a bit if showing the "USER1"-"USER4" scales, which are uniquely five instead of four characters - gfxPrint(39, 26, OC::scale_names_short[scale]); - - gfxPrint((octave_offset == 0 ? 45 : 39), 36, OC::Strings::note_names_unpadded[root]); - if(octave_offset != 0) - { - gfxPrint(51, 36, octave_offset); - } - - //gfxPrint(" (");gfxPrint(density);gfxPrint(")"); // Debug print of actual density value - - // Current / total steps - int display_step = step+1; // Protocol droids know that humans count from 1 - //gfxPrint(1 + pad(100,display_step), 45, display_step); gfxPrint("/");gfxPrint(num_steps); // Pad x enough to hold width steady - gfxPrint(1+pad(10,display_step), 47, display_step); gfxPrint("/");gfxPrint(num_steps); // Pad x enough to hold width steady - - // Show octave icons - if(step_is_oct_down(step)) + int gate_dens = get_on_off_density(); + int pitch_dens = get_pitch_change_density(); + + //gfxLine(9,36, 29, 36, true); // dotted line + int xd = 5 + 7 - gate_dens; + int yd = (64 * pitch_dens) / 256; // Multiply for better fidelity + gfxBitmap(12 - xd, 27 + yd, 8, NOTE4_ICON); + gfxBitmap(12, 27 - yd, 8, NOTE4_ICON); + gfxBitmap(12 + xd, 27, 8, NOTE4_ICON); + + // Display a number value for density + int dens_display = gate_dens; + bool dens_neg = false; + if (density_encoder_display > 0) { + // The density encoder value was recently changed, so show it momentarily instead of the cv+encoder value normally shown + --density_encoder_display; + dens_display = abs(density_encoder - 7); //Map from 0 to 14 --> -7 to 7 + dens_neg = density_encoder < 7; + + if (density_cv != 0) // When cv is applied, show that this is the centered value being displayed { - gfxBitmap(41, 54, 8, DOWN_BTN_ICON); + // Draw a knob to the left to represent the centerpoint being set + gfxCircle(3, 40, 3); + gfxLine(3, 38, 3, 40); } - else if(step_is_oct_up(step)) + + } else { + dens_display = gate_dens; + dens_neg = density < 7; + // Indicate if cv is affecting the density + if (density_cv != 0) // Density integer contribution from CV (not raw cv) { - gfxBitmap(41, 54, 8, UP_BTN_ICON); + gfxBitmap(22, 37, 8, CV_ICON); } + } - int keyboard_pitch = curr_step_semitone -4; // Translate from 0v - if(keyboard_pitch < 0) keyboard_pitch+=12; // Deal with c being at the start, not middle of keyboard + if (dens_neg) { + gfxPrint(8, 37, "-"); // Print minus sign this way to right-align the number + } + gfxPrint(14, 37, dens_display); - gfxPrint(49, 55, keyboard_pitch); + // Scale and root note select + gfxPrint(39, 26, OC::scale_names_short[scale]); - // Debug - //gfxPrint(40, 55, scale_size); - //gfxPrint(40, 55, transpose_note_in); // N.B. if pushed further right, this can crash on hemi2 when it'd print offscreen + gfxPrint((octave_offset == 0 ? 45 : 39), 36, OC::Strings::note_names_unpadded[root]); + if (octave_offset != 0) { + gfxPrint(51, 36, octave_offset); + } - // gfxBitmap(1, 55, 8, CV_ICON); gfxPos(12, 55); gfxPrintVoltage(pitches[step]); + // Current / total steps + int display_step = step + 1; // Protocol droids know that humans count from 1 + gfxPrint(1 + pad(10, display_step), 47, display_step); // Pad x enough to hold width steady + gfxPrint("/"); + gfxPrint(num_steps); + + // Show octave icons + if (step_is_oct_down(step)) { + gfxBitmap(41, 54, 8, DOWN_BTN_ICON); + } else if (step_is_oct_up(step)) { + gfxBitmap(41, 54, 8, UP_BTN_ICON); + } - // Draw a TB-303 style octave of a piano keyboard, indicating the playing pitch - int x = 1; - int y = 61; - int keyPatt = 0x054A; // keys encoded as 0=white 1=black, starting at c, backwards: b 0 0101 0100 1010 - for(int i=0; i<12; ++i) - { - // Black key? - y = ( keyPatt & 0x1 ) ? 56 : 61; - keyPatt >>= 1; - - // Two white keys in a row E and F - if( i == 5 ) x+=3; - - if(keyboard_pitch == i && step_is_gated(step)) // Only render a pitch if gated - { - gfxRect(x-1, y-1, 5, 4); // Larger box - - } - else - { - gfxRect(x, y, 3, 2); // Small filled box - } - x += 3; - } + int keyboard_pitch = curr_step_semitone - 4; // Translate from 0v + if (keyboard_pitch < 0) keyboard_pitch += 12; // Deal with c being at the start, not middle of keyboard - // Indicate if the current step has an accent - if(step_is_accent(step)) - { - gfxPrint(37, 46, "!"); - } + gfxPrint(49, 55, keyboard_pitch); - // Indicate if the current step has a slide - if(step_is_slid(step)) - { - gfxBitmap(42, 46, 8, BEND_ICON); - } - - // Show that the "slide circuit" is actively - // sliding the pitch (one step after the slid step) - if(curr_pitch_cv != slide_end_cv) + // Draw a TB-303 style octave of a piano keyboard, indicating the playing pitch + int x = 1; + int y = 61; + int keyPatt = 0x054A; // keys encoded as 0=white 1=black, starting at c, backwards: b 0 0101 0100 1010 + for (int i = 0; i < 12; ++i) { + // Black key? + y = (keyPatt & 0x1) ? 56 : 61; + keyPatt >>= 1; + + // Two white keys in a row E and F + if (i == 5) x += 3; + + if (keyboard_pitch == i && step_is_gated(step)) // Only render a pitch if gated { - gfxBitmap(52, 46, 8, WAVEFORM_ICON); - } - - // Draw edit cursor - switch (cursor) { - case 0: - // Set length to indicate length - gfxCursor(14, 23, lock_seed ? 11 : 36); // Seed = auto-randomize / locked-manual - break; - case 1: - case 2: - case 3: - case 4: // seed, 4 positions (1-4) - gfxCursor(25 + 6*(cursor-1), 23, 7); - break; - case 5: - gfxCursor(9, 45, 14); // density - break; - case 6: - gfxCursor(39, 34, 25); // scale - break; - case 7: - gfxCursor(39, 44, 24); // root note - break; - case 8: - gfxCursor(20, 54, 12, 8); // step - break; + gfxRect(x - 1, y - 1, 5, 4); // Larger box + + } else { + gfxRect(x, y, 3, 2); // Small filled box } + x += 3; } -}; + if (step_is_accent(step)) { + gfxPrint(37, 46, "!"); + } + if (step_is_slid(step)) { + gfxBitmap(42, 46, 8, BEND_ICON); + } + + // Show that the "slide circuit" is actively + // sliding the pitch (one step after the slid step) + if (curr_pitch_cv != slide_end_cv) { + gfxBitmap(52, 46, 8, WAVEFORM_ICON); + } + + // Draw edit cursor + switch (cursor) { + case 0: + // Set length to indicate length + gfxCursor(14, 23, lock_seed ? 11 : 36); // Seed = auto-randomize / locked-manual + break; + case 1: + case 2: + case 3: + case 4: // seed, 4 positions (1-4) + gfxCursor(25 + 6 * (cursor - 1), 23, 7); + break; + case 5: + gfxCursor(9, 45, 14); // density + break; + case 6: + gfxCursor(39, 34, 25); // scale + break; + case 7: + gfxCursor(39, 44, 24); // root note + break; + case 8: + gfxCursor(20, 54, 12, 8); // step + break; + } + } + +}; //////////////////////////////////////////////////////////////////////////////// //// Hemisphere Applet Functions @@ -986,33 +776,33 @@ class TB_3PO : public HemisphereApplet TB_3PO TB_3PO_instance[2]; void TB_3PO_Start(bool hemisphere) { - TB_3PO_instance[hemisphere].BaseStart(hemisphere); + TB_3PO_instance[hemisphere].BaseStart(hemisphere); } void TB_3PO_Controller(bool hemisphere, bool forwarding) { - TB_3PO_instance[hemisphere].BaseController(forwarding); + TB_3PO_instance[hemisphere].BaseController(forwarding); } void TB_3PO_View(bool hemisphere) { - TB_3PO_instance[hemisphere].BaseView(); + TB_3PO_instance[hemisphere].BaseView(); } void TB_3PO_OnButtonPress(bool hemisphere) { - TB_3PO_instance[hemisphere].OnButtonPress(); + TB_3PO_instance[hemisphere].OnButtonPress(); } void TB_3PO_OnEncoderMove(bool hemisphere, int direction) { - TB_3PO_instance[hemisphere].OnEncoderMove(direction); + TB_3PO_instance[hemisphere].OnEncoderMove(direction); } void TB_3PO_ToggleHelpScreen(bool hemisphere) { - TB_3PO_instance[hemisphere].HelpScreen(); + TB_3PO_instance[hemisphere].HelpScreen(); } uint64_t TB_3PO_OnDataRequest(bool hemisphere) { - return TB_3PO_instance[hemisphere].OnDataRequest(); + return TB_3PO_instance[hemisphere].OnDataRequest(); } void TB_3PO_OnDataReceive(bool hemisphere, uint64_t data) { - TB_3PO_instance[hemisphere].OnDataReceive(data); + TB_3PO_instance[hemisphere].OnDataReceive(data); } From dec775bcb6fc643236c810be780d2011787b5f8e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 17 Jul 2023 03:47:34 -0400 Subject: [PATCH 269/417] TB-3PO: Simplify --- software/o_c_REV/HEM_TB3PO.ino | 52 +++++++++++----------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 1fe38c1a6..46647d463 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -68,8 +68,6 @@ class TB_3PO: public HemisphereApplet { slide_start_cv = 0; slide_end_cv = 0; - //transpose_note_in = 0; - lock_seed = 0; reseed(); regenerate_all(); @@ -77,7 +75,7 @@ class TB_3PO: public HemisphereApplet { } void Controller() { - int this_tick = OC::CORE::ticks; + const int this_tick = OC::CORE::ticks; if (Clock(1) || manual_reset_flag) { manual_reset_flag = 0; @@ -95,14 +93,8 @@ class TB_3PO: public HemisphereApplet { transpose_cv = quantizer->Process(In(0), 0, 0); // Use root == 0 to start at c } - { - int signal = constrain(DetentedIn(1), -HEMISPHERE_3V_CV, HEMISPHERE_MAX_INPUT_CV); // Allow negative to go about as far as it will reach - density_cv = Proportion(abs(signal), HEMISPHERE_MAX_INPUT_CV, 15); // Apply proportion uniformly to +- voltages as + for symmetry (Avoids rounding differences) - if (signal < 0) { - density_cv *= -1; // Restore negative sign if -v - } - density = static_cast < uint8_t > (constrain(density_encoder + density_cv, 0, 14)); - } + density_cv = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, 15); + density = static_cast(constrain(density_encoder + density_cv, 0, 14)); if (Clock(0)) { cycle_time = ClockCycleTicks(0); // Track latest interval of clock 0 for gate timings @@ -202,7 +194,7 @@ class TB_3PO: public HemisphereApplet { case 0: lock_seed += direction; - manual_reset_flag = (lock_seed > 1 || lock_seed < 0) ? 1 : 0; + manual_reset_flag = (lock_seed > 1 || lock_seed < 0); lock_seed = constrain(lock_seed, 0, 1); break; @@ -298,7 +290,8 @@ class TB_3PO: public HemisphereApplet { step = 0; } - protected: void SetHelp() { +protected: + void SetHelp() { // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Regen"; help[HEMISPHERE_HELP_CVS] = "1=Transp 2=Density"; @@ -307,14 +300,14 @@ class TB_3PO: public HemisphereApplet { // "------------------" <-- Size Guide } - private: int cursor = 0; - - braids::Quantizer * quantizer; // Helper for note index --> pitch cv +private: + int cursor = 0; + braids::Quantizer * quantizer; // User settings // Bool - int manual_reset_flag = 0; // Manual trigger to reset/regen + bool manual_reset_flag = 0; // Manual trigger to reset/regen // bool int lock_seed; // If 1, the seed won't randomize (and manual editing is enabled) @@ -341,7 +334,6 @@ class TB_3PO: public HemisphereApplet { // Playback uint8_t step = 0; // Current sequencer step - //int transpose_note_in; // Current transposition from cv in (initially a cv value) TEMP: REMOVE int32_t transpose_cv; // Quantized transpose in cv // Generated sequence data @@ -356,7 +348,6 @@ class TB_3PO: public HemisphereApplet { uint8_t current_pattern_scale_size; // Track what size scale was used to render the current pattern (for change detection) // For gate timing as ~32nd notes at tempo, detect clock rate like a clock multiplier - //int timing_count; int gate_off_clock; // Scheduled cycle at which the gate should be turned off (when applicable) int cycle_time; // Cycle time between the last two clock inputs @@ -370,16 +361,12 @@ class TB_3PO: public HemisphereApplet { // Display int curr_step_semitone = 0; // The pitch converted to nearest semitone, for showing as an index onto the keyboard - uint8_t rand_apply_anim = 0; // Countdown to animate icons for when regenerate occurs uint8_t regenerate_phase = 0; // Split up random generation over multiple frames // Get the cv value to use for a given step including root + transpose values int get_pitch_for_step(int step_num) { - // Original: Transpose pre-quantize - //int quant_note = 64 + int(notes[step_num]) + int(root) + int(transpose_note_in); - int quant_note = 64 + int(notes[step_num]) + int(root); // Apply the manual octave offset @@ -392,11 +379,9 @@ class TB_3PO: public HemisphereApplet { quant_note -= scale_size; } - int out_note = constrain(quant_note, 0, 127); + quant_note = constrain(quant_note, 0, 127); - // New: Transpose post-quantize - int pitch_cv = quantizer->Lookup(out_note) + transpose_cv; - return pitch_cv; + return quantizer->Lookup(quant_note) + transpose_cv; //return quantizer->Lookup( 64 ); // Test: note 64 is definitely 0v=c4 if output directly, on ALL scales } @@ -611,9 +596,8 @@ class TB_3PO: public HemisphereApplet { } // Heart represents the seed/favorite - gfxBitmap(4, heart_y, 8, FAVORITE_ICON); - - gfxBitmap(15, (lock_seed ? 15 : die_y), 8, (lock_seed ? LOCK_ICON : RANDOM_ICON)); + gfxIcon(4, heart_y, FAVORITE_ICON); + gfxIcon(15, (lock_seed ? 15 : die_y), (lock_seed ? LOCK_ICON : RANDOM_ICON)); // Show the 16-bit seed as 4 hex digits int disp_seed = seed; //0xABCD // test display accuracy @@ -636,7 +620,6 @@ class TB_3PO: public HemisphereApplet { int gate_dens = get_on_off_density(); int pitch_dens = get_pitch_change_density(); - //gfxLine(9,36, 29, 36, true); // dotted line int xd = 5 + 7 - gate_dens; int yd = (64 * pitch_dens) / 256; // Multiply for better fidelity gfxBitmap(12 - xd, 27 + yd, 8, NOTE4_ICON); @@ -702,12 +685,11 @@ class TB_3PO: public HemisphereApplet { // Draw a TB-303 style octave of a piano keyboard, indicating the playing pitch int x = 1; - int y = 61; - int keyPatt = 0x054A; // keys encoded as 0=white 1=black, starting at c, backwards: b 0 0101 0100 1010 + int y; + const int keyPatt = 0x054A; // keys encoded as 0=white 1=black, starting at c, backwards: b 0 0101 0100 1010 for (int i = 0; i < 12; ++i) { // Black key? - y = (keyPatt & 0x1) ? 56 : 61; - keyPatt >>= 1; + y = ((keyPatt >> i) & 0x1) ? 56 : 61; // Two white keys in a row E and F if (i == 5) x += 3; From 620440237f3678c7f5b25f8c394525798a9ae896 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 3 Aug 2023 19:51:47 -0400 Subject: [PATCH 270/417] DMA Patch from upstream v1.3.7-alpha Better performance for ADC, seems to reduce CPU usage. Feels more stable, although I'm seeing some minor blips on the right edge of the screen now... --- software/o_c_REV/OC_ADC.cpp | 164 ++++++++++-------- software/o_c_REV/OC_ADC.h | 53 +++--- software/o_c_REV/o_c_REV.ino | 13 +- .../src/drivers/SH1106_128x64_driver.cpp | 39 +++-- software/o_c_REV/src/drivers/display.h | 1 - software/o_c_REV/src/drivers/weegfx.cpp | 2 +- software/o_c_REV/util/util_macros.h | 4 +- software/o_c_REV/util/util_templates.h | 57 ++++++ 8 files changed, 211 insertions(+), 122 deletions(-) create mode 100644 software/o_c_REV/util/util_templates.h diff --git a/software/o_c_REV/OC_ADC.cpp b/software/o_c_REV/OC_ADC.cpp index e6ecf1e6f..79e09804f 100644 --- a/software/o_c_REV/OC_ADC.cpp +++ b/software/o_c_REV/OC_ADC.cpp @@ -1,103 +1,125 @@ #include "OC_ADC.h" #include "OC_gpio.h" - +#include "DMAChannel.h" #include namespace OC { -template struct ChannelDesc { }; -template <> struct ChannelDesc { - static const int PIN = CV1; -}; -template <> struct ChannelDesc { - static const int PIN = CV2; -}; -template <> struct ChannelDesc { - static const int PIN = CV3; -}; -template <> struct ChannelDesc { - static const int PIN = CV4; -}; - /*static*/ ::ADC ADC::adc_; -/*static*/ size_t ADC::scan_channel_; /*static*/ ADC::CalibrationData *ADC::calibration_data_; /*static*/ uint32_t ADC::raw_[ADC_CHANNEL_LAST]; /*static*/ uint32_t ADC::smoothed_[ADC_CHANNEL_LAST]; -#ifdef ENABLE_ADC_DEBUG -/*static*/ volatile uint32_t ADC::busy_waits_; +#ifdef OC_ADC_ENABLE_DMA_INTERRUPT +/*static*/ volatile bool ADC::ready_; #endif -/*static*/ void ADC::Init(CalibrationData *calibration_data) { +constexpr uint16_t ADC::SCA_CHANNEL_ID[DMA_NUM_CH]; // ADCx_SCA register channel numbers +DMAChannel* dma0 = new DMAChannel(false); // dma0 channel, fills adcbuffer_0 +DMAChannel* dma1 = new DMAChannel(false); // dma1 channel, updates ADC0_SC1A which holds the channel/pin IDs +DMAMEM static volatile uint16_t __attribute__((aligned(DMA_BUF_SIZE+0))) adcbuffer_0[DMA_BUF_SIZE]; - // According to Paul Stoffregen: You do NOT want to have the pin in digital mode when using it as analog input. - // https://forum.pjrc.com/threads/34319-Analog-input-impedance-and-pull-up?p=103543&viewfull=1#post103543 - //pinMode(ChannelDesc::PIN, INPUT); - //pinMode(ChannelDesc::PIN, INPUT); - //pinMode(ChannelDesc::PIN, INPUT); - //pinMode(ChannelDesc::PIN, INPUT); +/*static*/ void ADC::Init(CalibrationData *calibration_data) { adc_.setReference(ADC_REF_3V3); adc_.setResolution(kAdcScanResolution); adc_.setConversionSpeed(kAdcConversionSpeed); adc_.setSamplingSpeed(kAdcSamplingSpeed); adc_.setAveraging(kAdcScanAverages); - adc_.disableDMA(); - adc_.disableInterrupts(); - adc_.disableCompare(); - - scan_channel_ = ADC_CHANNEL_1; - adc_.startSingleRead(ChannelDesc::PIN); calibration_data_ = calibration_data; std::fill(raw_, raw_ + ADC_CHANNEL_LAST, 0); std::fill(smoothed_, smoothed_ + ADC_CHANNEL_LAST, 0); -#ifdef ENABLE_ADC_DEBUG - busy_waits_ = 0; -#endif + std::fill(adcbuffer_0, adcbuffer_0 + DMA_BUF_SIZE, 0); + + adc_.enableDMA(); } -// As I understand it, only CV4 can be muxed to ADC1, so it's not possible to -// use ADC::startSynchronizedSingleRead, which would allow reading two channels -// simultaneously +#ifdef OC_ADC_ENABLE_DMA_INTERRUPT +/*static*/ void ADC::DMA_ISR() { + ADC::ready_ = true; + dma0->clearInterrupt(); + /* restart DMA in ADC::Scan_DMA() */ +} +#endif -/*static*/ void FASTRUN ADC::Scan() { +/* + * + * DMA/ADC à la https://forum.pjrc.com/threads/30171-Reconfigure-ADC-via-a-DMA-transfer-to-allow-multiple-Channel-Acquisition + * basically, this sets up two DMA channels and cycles through the four adc mux channels (until the buffer is full), resets, and so on; dma1 advances SCA_CHANNEL_ID + * somewhat like https://www.nxp.com/docs/en/application-note/AN4590.pdf but w/o the PDB. + * +*/ + +void ADC::Init_DMA() { + + dma0->begin(true); // allocate the DMA channel + dma0->TCD->SADDR = &ADC0_RA; + dma0->TCD->SOFF = 0; + dma0->TCD->ATTR = 0x101; + dma0->TCD->NBYTES = 2; + dma0->TCD->SLAST = 0; + dma0->TCD->DADDR = &adcbuffer_0[0]; + dma0->TCD->DOFF = 2; + dma0->TCD->DLASTSGA = -(2 * DMA_BUF_SIZE); + dma0->TCD->BITER = DMA_BUF_SIZE; + dma0->TCD->CITER = DMA_BUF_SIZE; + dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0); + dma0->disableOnCompletion(); +#ifdef OC_ADC_ENABLE_DMA_INTERRUPT + dma0->interruptAtCompletion(); + dma0->attachInterrupt(DMA_ISR); + ready_ = false; +#endif -#ifdef ENABLE_ADC_DEBUG - if (!adc_.isComplete(ADC_0)) { - ++busy_waits_; - while (!adc_.isComplete(ADC_0)); - } + dma1->begin(true); // allocate the DMA channel + dma1->TCD->SADDR = &ADC::SCA_CHANNEL_ID[0]; + dma1->TCD->SOFF = 2; // source increment each transfer (n bytes) + dma1->TCD->ATTR = 0x101; + dma1->TCD->SLAST = - DMA_NUM_CH*2; // num ADC0 samples * 2 + dma1->TCD->BITER = DMA_NUM_CH; + dma1->TCD->CITER = DMA_NUM_CH; + dma1->TCD->DADDR = &ADC0_SC1A; + dma1->TCD->DLASTSGA = 0; + dma1->TCD->NBYTES = 2; + dma1->TCD->DOFF = 0; + dma1->triggerAtTransfersOf(*dma0); + dma1->triggerAtCompletionOf(*dma0); + + dma0->enable(); + dma1->enable(); +} + +/*static*/void FASTRUN ADC::Scan_DMA() { + +#ifdef OC_ADC_ENABLE_DMA_INTERRUPT + if (ADC::ready_) { + ADC::ready_ = false; +#else + if (dma0->complete()) { + dma0->clearComplete(); #endif - const uint16_t value = adc_.readSingle(ADC_0); - - size_t channel = scan_channel_; - switch (channel) { - case ADC_CHANNEL_1: - adc_.startSingleRead(ChannelDesc::PIN, ADC_0); - update(value); - ++channel; - break; - - case ADC_CHANNEL_2: - adc_.startSingleRead(ChannelDesc::PIN, ADC_0); - update(value); - ++channel; - break; - - case ADC_CHANNEL_3: - adc_.startSingleRead(ChannelDesc::PIN, ADC_0); - update(value); - ++channel; - break; - - case ADC_CHANNEL_4: - adc_.startSingleRead(ChannelDesc::PIN, ADC_0); - update(value); - channel = ADC_CHANNEL_1; - break; + dma0->TCD->DADDR = &adcbuffer_0[0]; + + /* + * collect results from adcbuffer_0; as things are, there's DMA_BUF_SIZE = 16 samples in the buffer. + */ + uint32_t value; + + value = (adcbuffer_0[0] + adcbuffer_0[4] + adcbuffer_0[8] + adcbuffer_0[12]) >> 2; // / 4 = DMA_BUF_SIZE / DMA_NUM_CH + update(value); + + value = (adcbuffer_0[1] + adcbuffer_0[5] + adcbuffer_0[9] + adcbuffer_0[13]) >> 2; + update(value); + + value = (adcbuffer_0[2] + adcbuffer_0[6] + adcbuffer_0[10] + adcbuffer_0[14]) >> 2; + update(value); + + value = (adcbuffer_0[3] + adcbuffer_0[7] + adcbuffer_0[11] + adcbuffer_0[15]) >> 2; + update(value); + + /* restart */ + dma0->enable(); } - scan_channel_ = channel; } /*static*/ void ADC::CalibratePitch(int32_t c2, int32_t c4) { diff --git a/software/o_c_REV/OC_ADC.h b/software/o_c_REV/OC_ADC.h index 4d1a7de29..b6795be81 100644 --- a/software/o_c_REV/OC_ADC.h +++ b/software/o_c_REV/OC_ADC.h @@ -1,14 +1,16 @@ #ifndef OC_ADC_H_ #define OC_ADC_H_ -#include #include "src/drivers/ADC/OC_util_ADC.h" #include "OC_config.h" +#include "OC_options.h" #include #include -//#define ENABLE_ADC_DEBUG +// If enabled, use an interrupt to track DMA completion; otherwise use polling +//#define OC_ADC_ENABLE_DMA_INTERRUPT + enum ADC_CHANNEL { ADC_CHANNEL_1, @@ -18,6 +20,9 @@ enum ADC_CHANNEL { ADC_CHANNEL_LAST, }; +#define DMA_BUF_SIZE 16 +#define DMA_NUM_CH ADC_CHANNEL_LAST + namespace OC { class ADC { @@ -31,10 +36,9 @@ class ADC { // These values should be tweaked so startSingleRead/readSingle run in main ISR update time // 16 bit has best-case 13 bits useable, but we only want 12 so we discard 4 anyway static constexpr uint8_t kAdcScanResolution = 16; - static constexpr uint8_t kAdcScanAverages = 16; + static constexpr uint8_t kAdcScanAverages = 4; static constexpr uint8_t kAdcSamplingSpeed = ADC_HIGH_SPEED_16BITS; static constexpr uint8_t kAdcConversionSpeed = ADC_HIGH_SPEED; - static constexpr uint32_t kAdcValueShift = kAdcSmoothBits; @@ -45,13 +49,9 @@ class ADC { }; static void Init(CalibrationData *calibration_data); - - // Read the value of the last conversion and update current channel, then - // start the next conversion. If necessary, some channels could be given - // priority by scanning them more often. Even better might be some kind of - // continuous/DMA sampling to make things even more independent of the main - // ISR timing restrictions. - static void Scan(); + static void Init_DMA(); + static void DMA_ISR(); + static void Scan_DMA(); template static int32_t value() { @@ -79,21 +79,6 @@ class ADC { return (value * calibration_data_->pitch_cv_scale) >> 12; } -#ifdef ENABLE_ADC_DEBUG - // DEBUG - static uint16_t fail_flag0() { - return adc_.adc0->fail_flag; - } - - static uint16_t fail_flag1() { - return adc_.adc1->fail_flag; - } - - static uint32_t busy_waits() { - return busy_waits_; - } -#endif - static void CalibratePitch(int32_t c2, int32_t c4); private: @@ -108,15 +93,25 @@ class ADC { } static ::ADC adc_; +#ifdef OC_ADC_ENABLE_DMA_INTERRUPT + static volatile bool ready_; +#endif static size_t scan_channel_; static CalibrationData *calibration_data_; static uint32_t raw_[ADC_CHANNEL_LAST]; static uint32_t smoothed_[ADC_CHANNEL_LAST]; -#ifdef ENABLE_ADC_DEBUG - static volatile uint32_t busy_waits_; -#endif + /* + * below: channel ids for the ADCx_SCA register: we have 4 inputs + * CV1 (19) = A5 = 0x4C; CV2 (18) = A4 = 0x4D; CV3 (20) = A6 = 0x46; CV4 (17) = A3 = 0x49 + * for some reason the IDs must be in order: CV2, CV3, CV4, CV1 resp. (when flipped) CV3, CV2, CV1, CV4 + */ + #ifdef FLIP_180 + static constexpr uint16_t SCA_CHANNEL_ID[DMA_NUM_CH] = { 0x46, 0x4D, 0x4C, 0x49 }; + #else + static constexpr uint16_t SCA_CHANNEL_ID[DMA_NUM_CH] = { 0x4D, 0x46, 0x49, 0x4C }; + #endif }; }; diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 2527cf066..77d4685c3 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -37,6 +37,7 @@ #include "OC_strings.h" #include "OC_ui.h" #include "OC_options.h" +#include "OC_strings.h" #include "src/drivers/display.h" #include "src/drivers/ADC/OC_util_ADC.h" #include "util/util_debugpins.h" @@ -74,13 +75,8 @@ void FASTRUN CORE_timer_ISR() { OC::DAC::Update(); display::Update(); - // The ADC scan uses async startSingleRead/readSingle and single channel each - // loop, so should be fast enough even at 60us (check ADC::busy_waits() == 0) - // to verify. Effectively, the scan rate is ISR / 4 / ADC::kAdcSmoothing - // 100us: 10kHz / 4 / 4 ~ .6kHz - // 60us: 16.666K / 4 / 4 ~ 1kHz - // kAdcSmoothing == 4 has some (maybe 1-2LSB) jitter but seems "Good Enough". - OC::ADC::Scan(); + // see OC_ADC.h for details; empirically (with current parameters), Scan_DMA() picks up new samples @ 5.55kHz + OC::ADC::Scan_DMA(); // Pin changes are tracked in separate ISRs, so depending on prio it might // need extra precautions. @@ -111,6 +107,7 @@ void setup() { OC::DigitalInputs::Init(); delay(400); OC::ADC::Init(&OC::calibration_data.adc); // Yes, it's using the calibration_data before it's loaded... + OC::ADC::Init_DMA(); OC::DAC::Init(&OC::calibration_data.dac); display::Init(); @@ -212,4 +209,4 @@ void FASTRUN loop() { } } - + diff --git a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp index d5e1842cc..a6ab69d93 100644 --- a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp +++ b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp @@ -28,10 +28,12 @@ #include "../../OC_gpio.h" #include "../../OC_options.h" +// NOTE: Don't disable DMA unless you absolutely know what you're doing. It will hurt you. #define DMA_PAGE_TRANSFER #ifdef DMA_PAGE_TRANSFER #include static DMAChannel page_dma; +static bool page_dma_active = false; #endif #ifndef SPI_SR_RXCTR #define SPI_SR_RXCTR 0XF0 @@ -84,9 +86,9 @@ static uint8_t SH1106_display_on_seq[] = { /*static*/ void SH1106_128x64_Driver::Init() { - pinMode(OLED_CS, OUTPUT); - pinMode(OLED_RST, OUTPUT); - pinMode(OLED_DC, OUTPUT); + OC::pinMode(OLED_CS, OUTPUT); + OC::pinMode(OLED_RST, OUTPUT); + OC::pinMode(OLED_DC, OUTPUT); //SPI_init(); // u8g_teensy::U8G_COM_MSG_INIT @@ -126,13 +128,29 @@ void SH1106_128x64_Driver::Init() { /*static*/ void SH1106_128x64_Driver::Flush() { #ifdef DMA_PAGE_TRANSFER - // Assume DMA transfer has completed, else we're doomed - digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0) - page_dma.clearComplete(); - page_dma.disable(); - // DmaSpi.h::post_finishCurrentTransfer_impl - SPI0_RSER = 0; - SPI0_SR = 0xFF0F0000; + // Famous last words: "Assume DMA transfer has completed, else we're doomed" + // Because it turns out there are conditions(*) where the timing is shifted + // such that it hasn't completed here, which causes weird display glitches + // from which there's no recovery. + // + // (*) If app processing in frame N takes too long, the next frame starts + // late; this leaves less time for frame N+1, and in N+2 the display CS line + // would be pulled high too soon. Why this effect is more pronounced with + // gcc >= 5.4.1 is a different mystery. + + if (page_dma_active) { + while (!page_dma.complete()) { } + while (0 != (SPI0_SR & 0x0000f000)); // SPIx_SR TXCTR + while (!(SPI0_SR & SPI_SR_TCF)); + page_dma_active = false; + + digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0) + page_dma.clearComplete(); + page_dma.disable(); + // DmaSpi.h::post_finishCurrentTransfer_impl + SPI0_RSER = 0; + SPI0_SR = 0xFF0F0000; + } #endif } @@ -173,6 +191,7 @@ void SH1106_128x64_Driver::SendPage(uint_fast8_t index, const uint8_t *data) { page_dma.sourceBuffer(data, kPageSize); page_dma.enable(); // go + page_dma_active = true; #else SPI_send(data, kPageSize); digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0) diff --git a/software/o_c_REV/src/drivers/display.h b/software/o_c_REV/src/drivers/display.h index 99ab83428..750307104 100644 --- a/software/o_c_REV/src/drivers/display.h +++ b/software/o_c_REV/src/drivers/display.h @@ -59,7 +59,6 @@ extern weegfx::Graphics graphics; #define GRAPHICS_BEGIN_FRAME(wait) \ do { \ - DEBUG_PIN_SCOPE(OC_GPIO_DEBUG_PIN1); \ uint8_t *frame = NULL; \ do { \ if (display::frame_buffer.writeable()) \ diff --git a/software/o_c_REV/src/drivers/weegfx.cpp b/software/o_c_REV/src/drivers/weegfx.cpp index d725aa77b..f62fa0952 100644 --- a/software/o_c_REV/src/drivers/weegfx.cpp +++ b/software/o_c_REV/src/drivers/weegfx.cpp @@ -406,7 +406,7 @@ void Graphics::draw_char(char c, coord_t x, coord_t y) { coord_t w = Graphics::kFixedFontW; coord_t h = Graphics::kFixedFontH; font_glyph data = get_char_glyph(c); - if (c + w > kWidth) w = kWidth - x; + if (x + w > kWidth) w = kWidth - x; if (x < 0) { w += x; data += x; diff --git a/software/o_c_REV/util/util_macros.h b/software/o_c_REV/util/util_macros.h index c11d5574b..e4e50ae5f 100644 --- a/software/o_c_REV/util/util_macros.h +++ b/software/o_c_REV/util/util_macros.h @@ -9,8 +9,8 @@ #endif #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete #define CLIP(x) if (x < -32767) x = -32767; if (x > 32767) x = 32767; diff --git a/software/o_c_REV/util/util_templates.h b/software/o_c_REV/util/util_templates.h new file mode 100644 index 000000000..524ba7a44 --- /dev/null +++ b/software/o_c_REV/util/util_templates.h @@ -0,0 +1,57 @@ +// Copyright 2020 Patrick Dowling +// +// Author: Patrick Dowling (pld@gurkenkiste.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// See http://creativecommons.org/licenses/MIT/ for more information. +// +#ifndef UTIL_TEMPLATES_H_ +#define UTIL_TEMPLATES_H_ + +#include + +namespace util { + +// C++11, >= 14 have their own +template +struct index_sequence { + template using append = index_sequence; +}; + +template +struct make_index_sequence { + using type = typename make_index_sequence::type::template append; +}; + +template <> +struct make_index_sequence<0U> { + using type = index_sequence<>; +}; + +template +struct sum : std::integral_constant { }; + +template +struct sum : std::integral_constant::value > { }; + + +} // util + +#endif // UTIL_TEMPLATES_H_ From 21d84d87c83566987a24996208fd9e3f80ae8283 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 4 Aug 2023 02:29:04 -0400 Subject: [PATCH 271/417] Clock: Send MIDI Start when TR1 starts the clock --- software/o_c_REV/APP_HEMISPHERE.ino | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 21395ccbe..d139c7343 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -256,7 +256,10 @@ public: bool reset = OC::DigitalInputs::clocked(); // Paused means wait for clock-sync to start - if (clock_m->IsPaused() && clock_sync) clock_m->Start(); + if (clock_m->IsPaused() && clock_sync) { + clock_m->Start(); + usbMIDI.sendRealTime(usbMIDI.Start); + } // TODO: automatically stop... // Advance internal clock, sync to external clock / reset From d55aa72b50b18a931403aa2e0329edb43a958fe4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 7 Jul 2023 03:19:00 -0400 Subject: [PATCH 272/417] ENVGEN: Output stage tweaks Comply with full range for Plum Audio, 0V offset otherwise --- software/o_c_REV/APP_ENVGEN.ino | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/APP_ENVGEN.ino b/software/o_c_REV/APP_ENVGEN.ino index 9935c5d52..af8fd750b 100644 --- a/software/o_c_REV/APP_ENVGEN.ino +++ b/software/o_c_REV/APP_ENVGEN.ino @@ -569,18 +569,23 @@ public: gate_state |= peaks::CONTROL_GATE_FALLING; gate_raised_ = gate_raised; - // Scale range and offset uint32_t value = env_.ProcessSingleSample(gate_state); // 0 to 32767 - uint32_t max_val = OC::DAC::get_octave_offset(dac_channel, OCTAVES - OC::DAC::kOctaveZero); - uint32_t range = max_val - OC::DAC::get_zero_offset(dac_channel); - value = value * range / 32767; + if (is_inverted()) value = 32767 - value; + const int max_val = OC::DAC::MAX_VALUE; - if (!is_inverted()) - value += OC::DAC::get_zero_offset(dac_channel); - else - value = max_val - value; + // Scale range and offset +#ifdef VOR + // Full range for Plum Audio + const uint32_t offset = OC::DAC::get_octave_offset(dac_channel, -OC::DAC::kOctaveZero); +#else + // Regular O_C settles to 0V + const uint32_t offset = OC::DAC::get_zero_offset(dac_channel); +#endif + + // scale value + value = offset + (value * (max_val - offset) / 32767); - OC::DAC::set(value); + OC::DAC::set(value); } uint16_t RenderPreview(int16_t *values, uint16_t *segment_start_points, uint16_t *loop_points, uint16_t ¤t_phase) const { From 4573310ca816e1cc9e3ae3d2e1e780c9dcd55ff2 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 4 Aug 2023 06:42:47 -0400 Subject: [PATCH 273/417] POLYLFO: Store/Recall Vbias setting plus some refactoring in VBiasManager --- software/o_c_REV/APP_POLYLFO.ino | 34 +++++++++++++++++++++++++++-- software/o_c_REV/OC_calibration.ino | 2 +- software/o_c_REV/VBiasManager.h | 18 ++++++++++----- software/o_c_REV/o_c_REV.ino | 8 +++---- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/APP_POLYLFO.ino b/software/o_c_REV/APP_POLYLFO.ino index 327ce3c59..6f2216bf5 100644 --- a/software/o_c_REV/APP_POLYLFO.ino +++ b/software/o_c_REV/APP_POLYLFO.ino @@ -34,6 +34,8 @@ #include "util/util_settings.h" #include "frames_poly_lfo.h" +#include "VBiasManager.h" + enum POLYLFO_SETTINGS { POLYLFO_SETTING_COARSE, POLYLFO_SETTING_FINE, @@ -56,6 +58,9 @@ enum POLYLFO_SETTINGS { POLYLFO_SETTING_D_AM_BY_C, POLYLFO_SETTING_CV4, POLYLFO_SETTING_TR4_MULT, +#ifdef VOR + POLYLFO_SETTING_VBIAS, +#endif POLYLFO_SETTING_LAST }; @@ -146,6 +151,22 @@ public: return values_[POLYLFO_SETTING_TR4_MULT]; } +#ifdef VOR + void saveVbias() { + VBiasManager *v = v->get(); + values_[POLYLFO_SETTING_VBIAS] = v->GetState(); + } + void restoreVbias() { + if (values_[POLYLFO_SETTING_VBIAS] <= 2) + { + VBiasManager *v = v->get(); + VBiasManager::VState bias_state = (VBiasManager::VState)values_[POLYLFO_SETTING_VBIAS]; + v->SetState( bias_state ); + //v->DrawPopupPerhaps(); + } + } +#endif + void Init(); void freeze() { @@ -232,7 +253,10 @@ SETTINGS_DECLARE(PolyLfo, POLYLFO_SETTING_LAST) { { 0, 0, 127, "C AM by B", NULL, settings::STORAGE_TYPE_U8 }, { 0, 0, 127, "D AM by C", NULL, settings::STORAGE_TYPE_U8 }, { 0, 0, 6, "CV4: DEST", cv4_destinations, settings::STORAGE_TYPE_U8 }, - { 3, 0, 5, "TR4: MULT", tr4_multiplier, settings::STORAGE_TYPE_U8 }, + { 3, 0, 5, "TR4: MULT", tr4_multiplier, settings::STORAGE_TYPE_U4 }, +#ifdef VOR + { 0, 0, 2, "VBias", OC::Strings::VOR_offsets, settings::STORAGE_TYPE_U4 }, +#endif }; PolyLfo poly_lfo; @@ -351,7 +375,7 @@ void FASTRUN POLYLFO_isr() { void POLYLFO_init() { poly_lfo_state.left_edit_mode = POLYLFO_SETTING_COARSE; - poly_lfo_state.cursor.Init(POLYLFO_SETTING_TAP_TEMPO, POLYLFO_SETTING_LAST - 1); + poly_lfo_state.cursor.Init(POLYLFO_SETTING_TAP_TEMPO, POLYLFO_SETTING_TR4_MULT); poly_lfo.Init(); } @@ -424,8 +448,14 @@ void POLYLFO_handleAppEvent(OC::AppEvent event) { switch (event) { case OC::APP_EVENT_RESUME: poly_lfo_state.cursor.set_editing(false); +#ifdef VOR + poly_lfo.restoreVbias(); +#endif break; case OC::APP_EVENT_SUSPEND: +#ifdef VOR + poly_lfo.saveVbias(); +#endif case OC::APP_EVENT_SCREENSAVER_ON: case OC::APP_EVENT_SCREENSAVER_OFF: break; diff --git a/software/o_c_REV/OC_calibration.ino b/software/o_c_REV/OC_calibration.ino index c03d4df52..85bbf9cad 100644 --- a/software/o_c_REV/OC_calibration.ino +++ b/software/o_c_REV/OC_calibration.ino @@ -457,7 +457,7 @@ void OC::Ui::Calibrate() { #ifdef VOR { VBiasManager *vb = vb->get(); - vb->ChangeBiasToState(VBiasManager::UNI); + vb->SetState(VBiasManager::UNI); } #endif diff --git a/software/o_c_REV/VBiasManager.h b/software/o_c_REV/VBiasManager.h index d697df30d..2825a659a 100644 --- a/software/o_c_REV/VBiasManager.h +++ b/software/o_c_REV/VBiasManager.h @@ -49,9 +49,16 @@ class VBiasManager { } public: + enum VState { + BI = 0, + ASYM, + UNI, + }; + /* static const int BI = 0; static const int ASYM = 1; static const int UNI = 2; + */ const int OCTAVE_BIAS[3] = {5, 3, 0}; const int OCTAVE_MAX[3] = {5, 7, 10}; @@ -68,7 +75,7 @@ class VBiasManager { // This is so that the first button press shows the popup without changing anything. if (OC::CORE::ticks - last_advance_tick < BIAS_EDITOR_TIMEOUT) { if (++bias_state > 2) bias_state = 0; - instance->ChangeBiasToState(bias_state); + instance->SetState(VBiasManager::VState(bias_state)); } last_advance_tick = OC::CORE::ticks; } @@ -88,7 +95,7 @@ class VBiasManager { * #endif * */ - void ChangeBiasToState(int new_bias_state) { + void SetState(VState new_bias_state) { int new_bias_value = OC::calibration_data.v_bias & 0xFFFF; // Bipolar = lower 2 bytes if (new_bias_state == VBiasManager::UNI) new_bias_value = OC::DAC::VBiasUnipolar; if (new_bias_state == VBiasManager::ASYM) new_bias_value = (OC::calibration_data.v_bias >> 16); // asym. = upper 2 bytes @@ -105,7 +112,7 @@ class VBiasManager { // Vbias auto-config helper // Cross-reference OC_apps.ino for app IDs void SetStateForApp(OC::App *app) { - int new_state = VBiasManager::ASYM; // default case + VState new_state = VBiasManager::ASYM; // default case switch (app->id) { @@ -123,7 +130,6 @@ class VBiasManager { */ // Bi-polar +/-5V case TWOCC<'H','S'>::value: // Hemisphere - case TWOCC<'P','L'>::value: // Quadraturia (or) Quadrature LFO case TWOCC<'L','R'>::value: // Low-rents (or) Lorenz new_state = VBiasManager::BI; break; @@ -133,8 +139,10 @@ class VBiasManager { case TWOCC<'B','Y'>::value: // Viznutcracker sweet (or) Bytebeats new_state = VBiasManager::UNI; break; + case TWOCC<'P','L'>::value: // Quadraturia (or) Quadrature LFO + return; // cancel, it has its own VBias setting } - instance->ChangeBiasToState(new_state); + instance->SetState(new_state); } /* diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 77d4685c3..398c36c28 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -143,13 +143,13 @@ void setup() { } OC::ui.set_screensaver_timeout(OC::calibration_data.screensaver_timeout); - // initialize apps - OC::apps::Init(reset_settings); - #ifdef VOR VBiasManager *vbias_m = vbias_m->get(); - vbias_m->ChangeBiasToState(VBiasManager::BI); + vbias_m->SetState(VBiasManager::BI); #endif + + // initialize apps + OC::apps::Init(reset_settings); } /* --------- main loop -------- */ From 55611fa8eb84c0c17d47e1aef2d7d0812a33e26e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 14 Aug 2023 06:50:54 -0400 Subject: [PATCH 274/417] PEW PEW PEW! - Phazer's choice build config whitespace cleanup, tidy Setup / About code --- software/o_c_REV/APP_SETTINGS.ino | 49 ++++------- software/o_c_REV/platformio.ini | 135 +++++++++++++++++------------- 2 files changed, 92 insertions(+), 92 deletions(-) diff --git a/software/o_c_REV/APP_SETTINGS.ino b/software/o_c_REV/APP_SETTINGS.ino index 61004393b..16c91e16c 100644 --- a/software/o_c_REV/APP_SETTINGS.ino +++ b/software/o_c_REV/APP_SETTINGS.ino @@ -72,7 +72,13 @@ public: void View() { gfxHeader("Setup / About"); - gfxPrint(0, 15, "Phazerville Suite"); + gfxIcon(0, 15, ZAP_ICON); + gfxIcon(120, 15, ZAP_ICON); + #ifdef PEWPEWPEW + gfxPrint(21, 15, "PEW! PEW! PEW!"); + #else + gfxPrint(12, 15, "Phazerville Suite"); + #endif gfxPrint(0, 25, OC::Strings::VERSION); gfxPrint(0, 35, "github.com/djphazer"); gfxPrint(0, 55, "[CALIBRATE] [RESET]"); @@ -83,32 +89,14 @@ public: ///////////////////////////////////////////////////////////////// // Control handlers ///////////////////////////////////////////////////////////////// - void OnLeftButtonPress() { + void Calibration() { OC::ui.Calibrate(); } - void OnLeftButtonLongPress() { - } - - void OnRightButtonPress() { + void FactoryReset() { OC::apps::Init(1); } - void OnUpButtonPress() { - } - - void OnDownButtonPress() { - } - - void OnDownButtonLongPress() { - } - - void OnLeftEncoderMove(int direction) { - } - - void OnRightEncoderMove(int direction) { - } - private: /* void DrawQRAt(byte x, byte y) { @@ -156,29 +144,20 @@ void Settings_menu() { void Settings_screensaver() {} // Deprecated void Settings_handleButtonEvent(const UI::Event &event) { - // For left encoder, handle press and long press if (event.control == OC::CONTROL_BUTTON_L) { - if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Settings_instance.OnLeftButtonLongPress(); - if (event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.OnLeftButtonPress(); + if (event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.Calibration(); } - // For right encoder, only handle press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.OnRightButtonPress(); - - // For up button, handle only press (long press is reserved) - if (event.control == OC::CONTROL_BUTTON_UP && event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.OnUpButtonPress(); - - // For down button, handle press and long press - if (event.control == OC::CONTROL_BUTTON_DOWN) { - if (event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.OnDownButtonPress(); - if (event.type == UI::EVENT_BUTTON_LONG_PRESS) Settings_instance.OnDownButtonLongPress(); - } + if (event.control == OC::CONTROL_BUTTON_R && event.type == UI::EVENT_BUTTON_PRESS) Settings_instance.FactoryReset(); } void Settings_handleEncoderEvent(const UI::Event &event) { + (void)event; + /* // Left encoder turned if (event.control == OC::CONTROL_ENCODER_L) Settings_instance.OnLeftEncoderMove(event.value); // Right encoder turned if (event.control == OC::CONTROL_ENCODER_R) Settings_instance.OnRightEncoderMove(event.value); + */ } diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index a591a1d5e..f89818c74 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -42,113 +42,134 @@ extra_scripts = pre:resources/progname.py upload_protocol = teensy-gui -[env:cal8] -build_flags = - ${env.build_flags} - -DENABLE_APP_CALIBR8OR - -DOC_VERSION_EXTRA="\" CAL8-0327\"" +[env:pewpewpew] +; phazer's choice build +build_flags = + ${env.build_flags} + -DDRUMMAP_GRIDS2 + -DVOR -DFLIP_180 + -DENABLE_APP_CALIBR8OR + -DENABLE_APP_ENIGMA + -DENABLE_APP_MIDI + -DENABLE_APP_PONG + -DENABLE_APP_PIQUED + -DENABLE_APP_POLYLFO + -DENABLE_APP_H1200 + -DENABLE_APP_BYTEBEATGEN + -DPEWPEWPEW + -DOC_VERSION_EXTRA="\"_phz\"" [env:main] -build_flags = - ${env.build_flags} +build_flags = + ${env.build_flags} -DDRUMMAP_GRIDS2 - -DENABLE_APP_CALIBR8OR - -DENABLE_APP_ENIGMA - -DENABLE_APP_MIDI - -DENABLE_APP_NEURAL_NETWORK - -DENABLE_APP_PONG - -DENABLE_APP_DARKEST_TIMELINE - -DENABLE_APP_PIQUED - -DENABLE_APP_POLYLFO + -DENABLE_APP_CALIBR8OR + -DENABLE_APP_ENIGMA + -DENABLE_APP_MIDI + -DENABLE_APP_NEURAL_NETWORK + -DENABLE_APP_PONG + -DENABLE_APP_DARKEST_TIMELINE + -DENABLE_APP_PIQUED + -DENABLE_APP_POLYLFO -DENABLE_APP_LORENZ +; +; -DENABLE_APP_ASR +; -DENABLE_APP_QUANTERMAIN +; -DENABLE_APP_METAQ +; -DENABLE_APP_CHORDS +; -DENABLE_APP_SEQUINS +; -DENABLE_APP_H1200 +; -DENABLE_APP_AUTOMATONNETZ +; -DENABLE_APP_BBGEN +; -DENABLE_APP_BYTEBEATGEN [env:oc_stock1] -build_flags = - ${env.build_flags} - -DENABLE_APP_PONG +build_flags = + ${env.build_flags} + -DENABLE_APP_PONG -DENABLE_APP_ASR - -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_QUANTERMAIN ; -DENABLE_APP_METAQ - -DENABLE_APP_PIQUED - -DENABLE_APP_CHORDS - -DENABLE_APP_SEQUINS -; -DENABLE_APP_POLYLFO -; -DENABLE_APP_H1200 + -DENABLE_APP_PIQUED + -DENABLE_APP_CHORDS + -DENABLE_APP_SEQUINS +; -DENABLE_APP_POLYLFO +; -DENABLE_APP_H1200 -DENABLE_APP_AUTOMATONNETZ ; -DENABLE_APP_LORENZ ; -DENABLE_APP_BBGEN ; -DENABLE_APP_BYTEBEATGEN - -DOC_VERSION_EXTRA="\"+stock1\"" + -DOC_VERSION_EXTRA="\"+stock1\"" [env:oc_stock2] -build_flags = - ${env.build_flags} - -DENABLE_APP_PONG +build_flags = + ${env.build_flags} + -DENABLE_APP_PONG -DENABLE_APP_ASR - -DENABLE_APP_QUANTERMAIN - -DENABLE_APP_METAQ -; -DENABLE_APP_PIQUED - -DENABLE_APP_CHORDS -; -DENABLE_APP_SEQUINS - -DENABLE_APP_POLYLFO - -DENABLE_APP_H1200 + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_METAQ +; -DENABLE_APP_PIQUED + -DENABLE_APP_CHORDS +; -DENABLE_APP_SEQUINS + -DENABLE_APP_POLYLFO + -DENABLE_APP_H1200 -DENABLE_APP_AUTOMATONNETZ -DENABLE_APP_LORENZ -DENABLE_APP_BBGEN -DENABLE_APP_BYTEBEATGEN - -DOC_VERSION_EXTRA="\"+stock2\"" + -DOC_VERSION_EXTRA="\"+stock2\"" [env:main_flipped] build_flags = - ${env:main.build_flags} + ${env:main.build_flags} -DFLIP_180 [env:oc_stock1_flipped] build_flags = - ${env:oc_stock1.build_flags} + ${env:oc_stock1.build_flags} -DFLIP_180 [env:oc_stock2_flipped] build_flags = - ${env:oc_stock2.build_flags} + ${env:oc_stock2.build_flags} -DFLIP_180 [env:main_vor] -build_flags = - ${env:main.build_flags} - -DVOR +build_flags = + ${env:main.build_flags} + -DVOR [env:main_vor_flipped] build_flags = - ${env:main_flipped.build_flags} - -DVOR + ${env:main_flipped.build_flags} + -DVOR [env:oc_stock1_vor] build_flags = - ${env:oc_stock1.build_flags} - -DVOR + ${env:oc_stock1.build_flags} + -DVOR [env:oc_stock1_vor_flipped] build_flags = - ${env:oc_stock1_flipped.build_flags} - -DVOR + ${env:oc_stock1_flipped.build_flags} + -DVOR [env:oc_stock2_vor] build_flags = - ${env:oc_stock2.build_flags} - -DVOR + ${env:oc_stock2.build_flags} + -DVOR [env:oc_stock2_vor_flipped] build_flags = - ${env:oc_stock2_flipped.build_flags} - -DVOR + ${env:oc_stock2_flipped.build_flags} + -DVOR [env:buchla] -build_flags = - ${env:main.build_flags} - -DBUCHLA_SUPPORT - -DBUCHLA_4U - -DOC_VERSION_EXTRA="\" Buchla\"" +build_flags = + ${env:main.build_flags} + -DBUCHLA_SUPPORT + -DBUCHLA_4U + -DOC_VERSION_EXTRA="\" Buchla\"" [env:oc_dev] build_flags = ${env.build_flags} -DOC_DEV From 0a860c409c11ee1a0daa29ab06d42fdd420f76c6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 14 Aug 2023 19:25:59 -0400 Subject: [PATCH 275/417] LoFi Echo: off-by-one fix --- software/o_c_REV/HEM_LoFiPCM.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index e60e72837..1d7f60693 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -24,8 +24,8 @@ // #define CLIPLIMIT 32512 #define CLIPLIMIT HEMISPHERE_3V_CV -#define PCM_TO_CV(S) Proportion((int)S - 127, 128, CLIPLIMIT) -#define CV_TO_PCM(S) Proportion(constrain(S, -CLIPLIMIT, CLIPLIMIT), CLIPLIMIT, 128) + 127 +#define PCM_TO_CV(S) Proportion((int)S - 127, 127, CLIPLIMIT) +#define CV_TO_PCM(S) Proportion(constrain(S, -CLIPLIMIT, CLIPLIMIT), CLIPLIMIT, 127) + 127 uint8_t lofi_pcm_buffer[HEM_LOFI_PCM_BUFFER_SIZE]; From b8ab3d01705fef61a84be719998213f3eeec2123 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 14 Aug 2023 19:57:57 -0400 Subject: [PATCH 276/417] New Applet API method, Modulate() Generalized way of applying a CV input value to a parameter. Adapted several applets. Reduced code size by about 360 bytes. --- software/o_c_REV/HEM_ADSREG.ino | 18 ++++----- software/o_c_REV/HEM_DrumMap.ino | 60 ++++++++++++----------------- software/o_c_REV/HEM_EbbAndLfo.ino | 10 ++--- software/o_c_REV/HEM_EuclidX.ino | 12 ++---- software/o_c_REV/HEM_LoFiPCM.ino | 4 +- software/o_c_REV/HEM_Shuffle.ino | 15 +++----- software/o_c_REV/HEM_TM2.ino | 8 ++-- software/o_c_REV/HemisphereApplet.h | 7 ++++ 8 files changed, 61 insertions(+), 73 deletions(-) diff --git a/software/o_c_REV/HEM_ADSREG.ino b/software/o_c_REV/HEM_ADSREG.ino index dea6f19fe..56d023ff7 100644 --- a/software/o_c_REV/HEM_ADSREG.ino +++ b/software/o_c_REV/HEM_ADSREG.ino @@ -56,9 +56,11 @@ public: } void Controller() { - // Look for CV modification of attack - attack_mod = get_modification_with_input(0); - release_mod = get_modification_with_input(1); + // CV input modulation + attack_mod = attack; + release_mod = release; + Modulate(attack_mod, 0, 1, HEM_EG_MAX_VALUE); + Modulate(release_mod, 1, 0, HEM_EG_MAX_VALUE - 1); ForEachChannel(ch) { @@ -205,10 +207,9 @@ private: } void AttackAmplitude(int ch) { - int effective_attack = constrain(attack + attack_mod, 1, HEM_EG_MAX_VALUE); - int total_stage_ticks = Proportion(effective_attack, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_AD); + int total_stage_ticks = Proportion(attack_mod, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_AD); int ticks_remaining = total_stage_ticks - stage_ticks[ch]; - if (effective_attack == 1) ticks_remaining = 0; + if (attack_mod == 1) ticks_remaining = 0; if (ticks_remaining <= 0) { // End of attack; move to decay stage[ch] = HEM_EG_DECAY; stage_ticks[ch] = 0; @@ -240,10 +241,9 @@ private: } void ReleaseAmplitude(int ch) { - int effective_release = constrain(release + release_mod, 1, HEM_EG_MAX_VALUE) - 1; - int total_stage_ticks = Proportion(effective_release, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_R); + int total_stage_ticks = Proportion(release_mod, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_R); int ticks_remaining = total_stage_ticks - stage_ticks[ch]; - if (effective_release == 0) ticks_remaining = 0; + if (release_mod == 0) ticks_remaining = 0; if (ticks_remaining <= 0 || amplitude[ch] <= 0) { // End of release; turn off envelope stage[ch] = HEM_EG_NO_STAGE; stage_ticks[ch] = 0; diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index 00de79163..60822c65d 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -43,30 +43,30 @@ public: } void Controller() { - cv1 = Proportion(DetentedIn(0), HEMISPHERE_MAX_INPUT_CV, 255); - cv2 = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, 255); + _fill[0] = fill[0]; + _fill[1] = fill[1]; + _x = x; + _y = y; + _chaos = chaos; - int _fill[2] = {fill[0], fill[1]}; - if (cv_mode == 0) { - _fill[0] = constrain(_fill[0]+cv1, 0, 255); - _fill[1] = constrain(_fill[1]+cv2, 0, 255); - } + switch (cv_mode) { + case 0: + Modulate(_fill[0], 0, 0, 255); + Modulate(_fill[1], 1, 0, 255); + break; - int _x = x; - int _y = y; - if (cv_mode == 1) { - _x = constrain(_x+cv1, 0, 255); - _y = constrain(_y+cv2, 0, 255); - } + case 1: + Modulate(_x, 0, 0, 255); + Modulate(_y, 1, 0, 255); + break; - int _chaos = chaos; - if (cv_mode == 2) { - _fill[0] = constrain(_fill[0]+cv1, 0, 255); - _chaos = constrain(_chaos+cv2, 0, 255); + case 2: + Modulate(_fill[0], 0, 0, 255); + Modulate(_chaos, 1, 0, 255); + break; } - - if (Clock(1)) Reset(); // Reset + if (Clock(1)) Reset(); if (Clock(0)) { // generate randomness for each drum type on first step of the pattern @@ -245,12 +245,14 @@ private: // settings int8_t mode[2] = {0, 1}; int fill[2] = {128, 128}; + int _fill[2] = {128, 128}; int x = 0; + int _x = 0; int y = 0; + int _y = 0; int chaos = 0; + int _chaos = 0; int8_t cv_mode = 0; // 0 = Fill A/B, 1 = X/Y, 2 = Fill A/Chaos - int cv1 = 0; // internal tracking of cv inputs - int cv2 = 0; uint8_t ReadDrumMap(uint8_t step, uint8_t part, uint8_t x, uint8_t y) { uint8_t i = x >> 6; @@ -298,32 +300,20 @@ private: // fill gfxPrint(1,25,"F"); - // add cv1 to fill_a value if cv1 mode is set to Fill A - int fa = fill[0]; - if (cv_mode == 0 || cv_mode == 2) fa = constrain(fa+cv1, 0, 255); - DrawKnobAt(9,25,20,fa,cursor == 2); + DrawKnobAt(9,25,20,_fill[0],cursor == 2); // don't show fill for channel b if it is an accent mode if (mode[1] < 3) { gfxPrint(32,25,"F"); - // add cv1 to fill_a value if cv1 mode is set to Fill A - int fb = fill[1]; - if (cv_mode == 0) fb = constrain(fb+cv2, 0, 255); - DrawKnobAt(40,25,20,fb,cursor == 3); + DrawKnobAt(40,25,20,_fill[1],cursor == 3); } // x & y - int _x = x; - if (cv_mode == 1) _x = constrain(_x+cv1, 0, 255); gfxPrint(1,35,"X"); DrawKnobAt(9,35,20,_x,cursor == 4); - int _y = y; - if (cv_mode == 1) _y = constrain(_y+cv2, 0, 255); gfxPrint(32,35,"Y"); DrawKnobAt(40,35,20,_y,cursor == 5); // chaos - int _chaos = chaos; - if (cv_mode == 2) _chaos = constrain(_chaos+cv2, 0, 255); gfxPrint(1,45,"CHAOS"); DrawKnobAt(32,45,28,_chaos,cursor == 6); diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index f46fda017..f4770ea85 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -30,20 +30,18 @@ public: pitch_mod += In(ch); break; case SLOPE: - slope_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 127); + Modulate(slope_mod, ch, 0, 127); break; case SHAPE: shape_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 127); + while (shape_mod < 0) shape_mod += 128; + while (shape_mod > 127) shape_mod -= 128; break; case FOLD: - fold_mod += Proportion(DetentedIn(ch), HEMISPHERE_MAX_INPUT_CV, 127); + Modulate(fold_mod, ch, 0, 127); break; } } - slope_mod = constrain(slope_mod, 0, 127); - while (shape_mod < 0) shape_mod += 128; - while (shape_mod > 127) shape_mod -= 128; - fold_mod = constrain(fold_mod, 0, 127); uint32_t phase_increment = ComputePhaseIncrement(pitch_mod); phase += phase_increment; diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 9db662139..fe81afb88 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -63,10 +63,6 @@ public: void Controller() { if (Clock(1)) step = 0; // Reset - int cv_data[2]; - cv_data[0] = DetentedIn(0); - cv_data[1] = DetentedIn(1); - // continuously recalculate pattern with CV offsets ForEachChannel(ch) { actual_length[ch] = length[ch]; @@ -78,7 +74,7 @@ public: ForEachChannel(cv_ch) { switch (cv_dest[cv_ch] - ch * LENGTH2) { // this is dumb, but efficient case LENGTH1: - actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, 31), 2, 32); + Modulate(actual_length[ch], ch, 2, 32); if (actual_beats[ch] > actual_length[ch]) actual_beats[ch] = actual_length[ch]; @@ -89,13 +85,13 @@ public: break; case BEATS1: - actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, actual_length[ch]), 0, actual_length[ch]); + Modulate(actual_beats[ch], ch, 0, actual_length[ch]); break; case OFFSET1: - actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); + Modulate(actual_offset[ch], ch, 0, actual_length[ch] + padding[ch]); break; case PADDING1: - actual_padding[ch] = constrain(actual_padding[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_INPUT_CV, 32 - actual_length[ch]), 0, 32 - actual_length[ch]); + Modulate(actual_padding[ch], ch, 0, 32 - actual_length[ch]); if (actual_offset[ch] >= actual_length[ch] + actual_padding[ch]) actual_offset[ch] = actual_length[ch] + actual_padding[ch] - 1; break; diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index 1d7f60693..f8432a808 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -57,7 +57,6 @@ public: } int cv = SmoothedIn(0); - int cv2 = DetentedIn(1); // bitcrush the input cv = cv >> depth; @@ -70,7 +69,8 @@ public: int fbmix = PCM_TO_CV(lofi_pcm_buffer[head]) * fdbk_g / 100 + cv; lofi_pcm_buffer[head_w] = CV_TO_PCM(fbmix); - rate_mod = constrain( rate + Proportion(cv2, HEMISPHERE_MAX_INPUT_CV, 32), 1, 64); + rate_mod = rate; + Modulate(rate_mod, 1, 1, 64); countdown = rate_mod; } diff --git a/software/o_c_REV/HEM_Shuffle.ino b/software/o_c_REV/HEM_Shuffle.ino index 9910dfa11..b45923b42 100644 --- a/software/o_c_REV/HEM_Shuffle.ino +++ b/software/o_c_REV/HEM_Shuffle.ino @@ -65,9 +65,9 @@ public: which = 1 - which; if (last_tick) { tempo = tick - last_tick; - int16_t d = delay[which] + Proportion(DetentedIn(which), HEMISPHERE_MAX_INPUT_CV, 100); - d = constrain(d, 0, 100); - uint32_t delay_ticks = Proportion(d, 100, tempo); + _delay[which] = delay[which]; + Modulate(_delay[which], which, 0, 100); + uint32_t delay_ticks = Proportion(_delay[which], 100, tempo); next_trigger = tick + delay_ticks; } last_tick = tick; @@ -141,13 +141,12 @@ private: // Settings int16_t delay[2]; // Percentage delay for even (0) and odd (1) clock + int16_t _delay[2]; // after CV modulation void DrawSelector() { for (int i = 0; i < 2; i++) { - int16_t d = delay[i] + Proportion(DetentedIn(i), HEMISPHERE_MAX_INPUT_CV, 100); - d = constrain(d, 0, 100); - gfxPrint(32 + pad(10, d), 15 + (i * 10), d); + gfxPrint(32 + pad(10, _delay[i]), 15 + (i * 10), _delay[i]); gfxPrint("%"); if (cursor == i) gfxCursor(32, 23 + (i * 10), 18); } @@ -172,9 +171,7 @@ private: for (int n = 0; n < 2; n++) { - int16_t d = delay[n] + Proportion(DetentedIn(n), HEMISPHERE_MAX_INPUT_CV, 100); - d = constrain(d, 0, 100); - int x = Proportion(d, 100, 20) + (n * 20) + 4; + int x = Proportion(_delay[n], 100, 20) + (n * 20) + 4; gfxBitmap(x, 48 - (which == n ? 3 : 0), 8, which == n ? NOTE_ICON : X_NOTE_ICON); } diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index fefb0c915..8323a1070 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -112,18 +112,18 @@ public: ForEachChannel(ch) { switch (cvmode[ch]) { case SLEW_MOD: - smooth_mod = constrain(smooth_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, 128), 1, 128); + Modulate(smooth_mod, ch, 1, 128); break; case LENGTH_MOD: - len_mod = constrain(len_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, TM2_MAX_LENGTH), TM2_MIN_LENGTH, TM2_MAX_LENGTH); + Modulate(len_mod, ch, TM2_MIN_LENGTH, TM2_MAX_LENGTH); break; case P_MOD: - p_mod = constrain(p_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, 100), 0, 100); + Modulate(p_mod, ch, 0, 100); break; case RANGE_MOD: - range_mod = constrain(range_mod + Proportion(cv_data[ch], HEMISPHERE_MAX_INPUT_CV, 32), 1, 32); + Modulate(range_mod, ch, 1, 32); break; // bi-polar transpose before quantize diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 4934a2a53..eeeb10dd1 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -411,6 +411,13 @@ class HemisphereApplet { return &HS::quantizer[io_offset + ch]; } + // Standard bi-polar CV modulation scenario + void Modulate(auto ¶m, const int ch, const int min = 0, const int max = 255) { + int cv = DetentedIn(ch); + param = constrain(param + Proportion(cv, HEMISPHERE_MAX_INPUT_CV, max), min, max); + //return param_mod; + } + void Out(int ch, int value, int octave = 0) { DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); OC::DAC::set_pitch(channel, value, octave); From 66cd766d968d551c0ec65ae2ef235d385b1c571b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 14 Aug 2023 21:48:00 -0400 Subject: [PATCH 277/417] EbbAndLFO: use +/-5V for bipolar on VOR --- software/o_c_REV/HEM_EbbAndLfo.ino | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index f4770ea85..3cdf9c1ff 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -56,7 +56,11 @@ public: Out(ch, Proportion(sample.unipolar, 65535, HEMISPHERE_MAX_CV)); break; case BIPOLAR: + #ifdef VOR + Out(ch, Proportion(sample.bipolar, 32767, 7680)); // hardcoded at 5V for Plum Audio + #else Out(ch, Proportion(sample.bipolar, 32767, HEMISPHERE_MAX_CV / 2)); + #endif break; case EOA: GateOut(ch, sample.flags & FLAG_EOA); From 303f5d04227db7a107a992d27f4e236b8e82109d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 15 Aug 2023 00:16:57 -0400 Subject: [PATCH 278/417] Calculate: Normalize channel 1 trigger to channel 2 for Rnd --- software/o_c_REV/HEM_Calculate.ino | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/HEM_Calculate.ino b/software/o_c_REV/HEM_Calculate.ino index b5aaea37c..37341a2fc 100644 --- a/software/o_c_REV/HEM_Calculate.ino +++ b/software/o_c_REV/HEM_Calculate.ino @@ -51,14 +51,18 @@ public: if (idx == 5) { // S&H if (Clock(ch)) Out(ch, In(ch)); } else if (idx == 6) { // Rand - // The first time a clock comes in, Rand becomes clocked, freezing a random - // value with each clock pulse. Otherwise, Rand is unclocked, and outputs - // a random value with each tick. - if (Clock(ch)) { - Out(ch, random(0, HEMISPHERE_MAX_CV)); + // The first time a clock comes in, Rand becomes clocked. + // Otherwise, Rand is unclocked, and outputs a random value with each tick. + + bool clk = Clock(ch); + bool recalc = clk || !rand_clocked[ch]; + if (clk) rand_clocked[ch] = 1; - } - else if (!rand_clocked[ch]) Out(ch, random(0, HEMISPHERE_MAX_CV)); + else if (ch == 1 && rand_clocked[0] && !rand_clocked[1]) // normalled clock input from TR1 for channel 2 + recalc = Clock(0); + + if (recalc) + Out(ch, random(0, HEMISPHERE_MAX_CV)); } else if (idx < 5) { int result = calc_fn[idx](In(0), In(1)); Out(ch, result); @@ -122,10 +126,10 @@ private: ForEachChannel(ch) { gfxPrint(31 * ch, 15, op_name[operation[ch]]); - if (ch == selected) gfxCursor(0 + (31 * ch), 23, 30); - // Show the icon if this random calculator is clocked if (operation[ch] == 6 && rand_clocked[ch]) gfxIcon(20 + 31 * ch, 15, CLOCK_ICON); + + if (ch == selected) gfxCursor(0 + (31 * ch), 23, 30); } } }; From 1efa8f3976b29528f1cca3e8623cd68a923c04d9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 17 Jul 2023 04:04:37 -0400 Subject: [PATCH 279/417] Version bump v1.6.3 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index c86ebff5d..63d5699b6 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.2" +"v1.6.3" From 6f0b417c6417a528f1f38c052121e81cdf4b9950 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Tue, 15 Aug 2023 03:08:27 -0400 Subject: [PATCH 280/417] Update README.md --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 39abc49bf..6cd9f7796 100755 --- a/README.md +++ b/README.md @@ -1,18 +1,20 @@ "What's the worst that could happen?" === +Watch SynthDad's [**video overview**](https://www.youtube.com/watch?v=XRGlAmz3AKM) and check the [**Wiki**](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. [Download it here](https://github.com/djphazer/O_C-BenisphereSuite/releases). + ## Phazerville Suite - an active o_C firmware fork Using [**Benisphere**](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the **Hemisphere Suite** in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! -I've also included **all of the stock O&C firmware apps**, although they don't all fit in one build. I provide **3 different builds** with various combinations of apps, listed in the [**Release Notes**](https://github.com/djphazer/O_C-BenisphereSuite/releases). +I've also included **all of the stock O&C firmware apps**, but they don't all fit in one .hex. As a courtesy, I provide **3 different build choices** with various combinations of Apps in my [**Releases**](https://github.com/djphazer/O_C-BenisphereSuite/releases). I think of it like the boxed set of a movie trilogy or whatever. The O&C Saga. 4 different hardware format options. Free and Open Source, baby! -Watch SynthDad's [**video overview**](https://www.youtube.com/watch?v=XRGlAmz3AKM) or check the [**Wiki**](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. +You can also customize the `platformio.ini` file to mix & match for yourself ;-) ### Notable Features in this branch: * 4 Presets in the new [**Hemisphere Config**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Hemisphere-Config) -* Modal-editing style cursor navigation +* Modal-editing style cursor navigation (and other usability tweaks) * Expanded internal [**Clock Setup**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Clock-Setup) * A new App called [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) * **[DualTM](https://github.com/djphazer/O_C-BenisphereSuite/wiki/DualTM)** - two 32-bit shift registers. Assignable I/O. @@ -38,7 +40,7 @@ python3 get-platformio.py This project lives within the `software/o_c_REV` directory. From there, you can Build the desired configuration and Upload via USB to your module: ``` -pio run -e main -t upload +pio run -e oc_stock2_flipped -t upload ``` Alternate build environment configurations exist in `platformio.ini` for VOR, Buchla, flipped screen, etc. To build all the defaults consecutively, simply use `pio run` @@ -46,12 +48,12 @@ Alternate build environment configurations exist in `platformio.ini` for VOR, Bu Many minds before me have made this project possible. Attribution is present in the git commit log and within individual files. Shoutouts: -* Logarhythm1 for the incredible **TB-3PO** sequencer. -* herrkami and Beni Rose for their work on **BugCrack**. -* Ben also gets massive props for **DrumMap** and the **ProbDiv / ProbMelo** applets. -* qiemem (Bryan Head) for the **Ebb&LFO** applet and its _tideslite_ backend +* **[Logarhythm1](https://github.com/Logarhythm1)** for the incredible **TB-3PO** sequencer, as well as **Stairs**. +* **[herrkami](https://github.com/herrkami)** and **Ben Rosenbach** for their work on **BugCrack**. +* **[benirose](https://github.com/benirose)** also gets massive props for **DrumMap** and the **ProbDiv / ProbMelo** applets. +* **[qiemem](https://github.com/qiemem)** (Bryan Head) for the **Ebb&LFO** applet and its _tideslite_ backend, among other things. -And, of course, thank you to Chysn for the clever applet framework from which we've all drawn inspiration. +And, of course, thank you to **[Chysn](https://github.com/Chysn)** for the clever applet framework from which we've all drawn inspiration. This is a fork of [Benisphere Suite](https://github.com/benirose/O_C-BenisphereSuite) which is a fork of [Hemisphere Suite](https://github.com/Chysn/O_C-HemisphereSuite) by Jason Justian (aka chysn). From eb003f949a6de6e13b421d33b3e5f77f1b2c7ed7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 15 Aug 2023 02:10:00 -0400 Subject: [PATCH 281/417] DualTM: UI cursor tweaks --- software/o_c_REV/HEM_TM2.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 8323a1070..c56f0b0c3 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -436,7 +436,7 @@ private: gfxIcon(1, 35, SLEW_ICON); gfxPrint(15, 35, smooth_mod); - gfxCursor(15, 43, 10); + gfxCursor(15, 43, 18); break; } @@ -444,7 +444,7 @@ private: case LENGTH: gfxCursor(13, 23, 12); break; case PROB: gfxCursor(35, 23, 18); break; case SCALE: gfxCursor(9, 33, 25); break; - case RANGE: gfxCursor(49, 33, 14); break; + case RANGE: gfxCursor(49, 33, 13); break; case OUT_A: case CVMODE1: gfxCursor(14, 43, 10); break; From 6900fe0ffa96641003ed4d54d6f5698d8703c478 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 20 Aug 2023 19:45:18 -0400 Subject: [PATCH 282/417] ADSREG: separate envelope settings, via ghostils --- software/o_c_REV/HEM_ADSREG.ino | 183 +++++++++++++++++++++++--------- 1 file changed, 135 insertions(+), 48 deletions(-) diff --git a/software/o_c_REV/HEM_ADSREG.ino b/software/o_c_REV/HEM_ADSREG.ino index 56d023ff7..19cd148d2 100644 --- a/software/o_c_REV/HEM_ADSREG.ino +++ b/software/o_c_REV/HEM_ADSREG.ino @@ -18,6 +18,24 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. + +/* + ghostils: + Envelopes are now independent for control and mod source/destination allowing two individual ADSR's with Release MOD CV input per hemisphere. + * CV mod is now limited to release for each channel + * Output Level indicators have been shrunk to make room for additional on screen indicators for which envelope you are editing. + * Switching between envelopes is currently handled by simply pressing the encoder button until you pass the release stage on each envelope which will toggle the active envelope you are editing + * Envelope is indicated by A or B just above the ADSR segments. + * + * TODO: UI Design: + * Update to allow menu to select CV destinations for CV Input Sources on CH1/CH2 + * This could be assignable to a different destination based on probability potentially as well + * Update to allow internal GATE/Trig count to apply a modulation value to any or each of the envelope segments + +*/ + + + #define HEM_EG_ATTACK 0 #define HEM_EG_DECAY 1 #define HEM_EG_SUSTAIN 2 @@ -28,6 +46,12 @@ #define HEM_SUSTAIN_CONST 35 #define HEM_EG_DISPLAY_HEIGHT 30 +//-ghostils: DEFINE Main menu inactivity timeout ~5secs this will return the user to the main menu: +#define HEM_EG_UI_INACT_TICKS 41666 + +//-ghostils: amount of time to handle Double Encoder Press ~250ms. +#define HEM_EG_UI_DBLPRESS_TICKS 4096 + // About four seconds #define HEM_EG_MAX_TICKS_AD 33333 @@ -43,24 +67,37 @@ public: void Start() { edit_stage = 0; - attack = 20; - decay = 30; - sustain = 120; - release = 25; + //attack = 20; + //decay = 30; + //sustain = 120; + //release = 25; ForEachChannel(ch) { stage_ticks[ch] = 0; gated[ch] = 0; stage[ch] = HEM_EG_NO_STAGE; + + //-ghostils:Initialize ADSR channels independently + attack[ch] = 20; + decay[ch] = 30; + sustain[ch] = 120; + release[ch] = 25; + release_mod[ch] = 0; } + + //-ghostils:Multiple ADSR Envelope Tracking: + curEG = 0; + } void Controller() { - // CV input modulation - attack_mod = attack; - release_mod = release; - Modulate(attack_mod, 0, 1, HEM_EG_MAX_VALUE); - Modulate(release_mod, 1, 0, HEM_EG_MAX_VALUE - 1); + // Look for CV modification + //attack_mod = get_modification_with_input(0); + //release_mod[0] = get_modification_with_input(1); + + //-ghostils: Update CV1/CV2 to support release only but on each ADSR independently: + release_mod[0] = get_modification_with_input(0); + release_mod[1] = get_modification_with_input(1); ForEachChannel(ch) { @@ -90,8 +127,10 @@ public: gated[ch] = 0; } + Out(ch, GetAmplitudeOf(ch)); } + } void View() { @@ -101,53 +140,77 @@ public: } void OnButtonPress() { - if (++edit_stage > HEM_EG_RELEASE) {edit_stage = HEM_EG_ATTACK;} + //if (++edit_stage > HEM_EG_RELEASE) {edit_stage = HEM_EG_ATTACK;} + //-ghostils: flip editing focus between A/B ADSR when we hit the end of the Release stage: + if (++edit_stage > HEM_EG_RELEASE) { + edit_stage = HEM_EG_ATTACK; + curEG ^= 1; + } } void OnEncoderMove(int direction) { - int adsr[4] = {attack, decay, sustain, release}; - adsr[edit_stage] = constrain(adsr[edit_stage] + direction, 1, HEM_EG_MAX_VALUE); - attack = adsr[HEM_EG_ATTACK]; - decay = adsr[HEM_EG_DECAY]; - sustain = adsr[HEM_EG_SUSTAIN]; - release = adsr[HEM_EG_RELEASE]; + //-ghostils:Reference curEG as the indexer to current ADSR when editing stages: + int adsr[4] = {attack[curEG], decay[curEG], sustain[curEG], release[curEG]}; + adsr[edit_stage] = constrain(adsr[edit_stage] += direction, 1, HEM_EG_MAX_VALUE); + attack[curEG] = adsr[HEM_EG_ATTACK]; + decay[curEG] = adsr[HEM_EG_DECAY]; + sustain[curEG] = adsr[HEM_EG_SUSTAIN]; + release[curEG] = adsr[HEM_EG_RELEASE]; } uint64_t OnDataRequest() { + //-ghostils:Update to use an array and snapshot the values using curEG as the index uint64_t data = 0; - Pack(data, PackLocation {0,8}, attack); - Pack(data, PackLocation {8,8}, decay); - Pack(data, PackLocation {16,8}, sustain); - Pack(data, PackLocation {24,8}, release); + Pack(data, PackLocation {0,8}, attack[curEG]); + Pack(data, PackLocation {8,8}, decay[curEG]); + Pack(data, PackLocation {16,8}, sustain[curEG]); + Pack(data, PackLocation {24,8}, release[curEG]); return data; } void OnDataReceive(uint64_t data) { - attack = Unpack(data, PackLocation {0,8}); - decay = Unpack(data, PackLocation {8,8}); - sustain = Unpack(data, PackLocation {16,8}); - release = Unpack(data, PackLocation {24,8}); + //-ghostils:Update to use an array and snapshot the values using curEG as the index + attack[curEG] = Unpack(data, PackLocation {0,8}); + decay[curEG] = Unpack(data, PackLocation {8,8}); + sustain[curEG] = Unpack(data, PackLocation {16,8}); + release[curEG] = Unpack(data, PackLocation {24,8}); - if (attack == 0) Start(); // If empty data, initialize + if (attack[curEG] == 0) Start(); // If empty data, initialize } protected: /* Set help text. Each help section can have up to 18 characters. Be concise! */ void SetHelp() { + /* help[HEMISPHERE_HELP_DIGITALS] = "Gate 1=Ch1 2=Ch2"; help[HEMISPHERE_HELP_CVS] = "Mod 1=Att 2=Rel"; help[HEMISPHERE_HELP_OUTS] = "Amp A=Ch1 B=Ch2"; help[HEMISPHERE_HELP_ENCODER] = "A/D/S/R"; + */ + + //-ghostils:Update onboard help: + help[HEMISPHERE_HELP_DIGITALS] = "Gate 1=Ch1 2=Ch2"; + help[HEMISPHERE_HELP_CVS] = "Mod 1=Rel 2=Rel"; + help[HEMISPHERE_HELP_OUTS] = "Amp A=Ch1 B=Ch2"; + help[HEMISPHERE_HELP_ENCODER] = "A/D/S/R"; } - + private: int edit_stage; - int attack; // Attack rate from 1-255 where 1 is fast - int decay; // Decay rate from 1-255 where 1 is fast - int sustain; // Sustain level from 1-255 where 1 is low - int release; // Release rate from 1-255 where 1 is fast + int attack[2]; // Attack rate from 1-255 where 1 is fast + int decay[2]; // Decay rate from 1-255 where 1 is fast + int sustain[2]; // Sustain level from 1-255 where 1 is low + int release[2]; // Release rate from 1-255 where 1 is fast + + //-ghostils:TODO Modify to adjust independently for each envelope, we won't be able to do both Attack and Release simultaneously so we either have to build a menu or just do Release. + //-Parameterize + int attack_mod; // Modification to attack from CV1 - int release_mod; // Modification to release from CV2 + + int release_mod[2]; // Modification to release from CV2 + + //-ghostils:Additions for tracking multiple ADSR's in each Hemisphere: + int curEG; // Stage management int stage[2]; // The current ASDR stage of the current envelope @@ -163,12 +226,23 @@ private: ForEachChannel(ch) { int w = Proportion(GetAmplitudeOf(ch), HEMISPHERE_MAX_CV, 62); - gfxRect(0, 15 + (ch * 10), w, 6); + //-ghostils:Update to make smaller to allow for additional information on the screen: + //gfxRect(0, 15 + (ch * 10), w, 6); + gfxRect(0, 15 + (ch * 3), w, 2); + } + + //-ghostils:Indicate which ADSR envelope we are selected on: + if(curEG == 0) { + gfxPrint(0,22,"A"); + gfxInvert(0,21,7,9); + }else{ + gfxPrint(0,22,"B"); + gfxInvert(0,21,7,9); } } void DrawADSR() { - int length = attack + decay + release + HEM_SUSTAIN_CONST; // Sustain is constant because it's a level + int length = attack[curEG] + decay[curEG] + release[curEG] + HEM_SUSTAIN_CONST; // Sustain is constant because it's a level int x = 0; x = DrawAttack(x, length); x = DrawDecay(x, length); @@ -177,22 +251,25 @@ private: } int DrawAttack(int x, int length) { - int xA = x + Proportion(attack, length, 62); + //-ghostils:Update to reference curEG: + int xA = x + Proportion(attack[curEG], length, 62); gfxLine(x, BottomAlign(0), xA, BottomAlign(HEM_EG_DISPLAY_HEIGHT), edit_stage != HEM_EG_ATTACK); return xA; } int DrawDecay(int x, int length) { - int xD = x + Proportion(decay, length, 62); + //-ghostils:Update to reference curEG: + int xD = x + Proportion(decay[curEG], length, 62); if (xD < 0) xD = 0; - int yS = Proportion(sustain, HEM_EG_MAX_VALUE, HEM_EG_DISPLAY_HEIGHT); + int yS = Proportion(sustain[curEG], HEM_EG_MAX_VALUE, HEM_EG_DISPLAY_HEIGHT); gfxLine(x, BottomAlign(HEM_EG_DISPLAY_HEIGHT), xD, BottomAlign(yS), edit_stage != HEM_EG_DECAY); return xD; } int DrawSustain(int x, int length) { int xS = x + Proportion(HEM_SUSTAIN_CONST, length, 62); - int yS = Proportion(sustain, HEM_EG_MAX_VALUE, HEM_EG_DISPLAY_HEIGHT); + //-ghostils:Update to reference curEG: + int yS = Proportion(sustain[curEG], HEM_EG_MAX_VALUE, HEM_EG_DISPLAY_HEIGHT); if (yS < 0) yS = 0; if (xS < 0) xS = 0; gfxLine(x, BottomAlign(yS), xS, BottomAlign(yS), edit_stage != HEM_EG_SUSTAIN); @@ -200,16 +277,21 @@ private: } int DrawRelease(int x, int length) { - int xR = x + Proportion(release, length, 62); - int yS = Proportion(sustain, HEM_EG_MAX_VALUE, HEM_EG_DISPLAY_HEIGHT); + //-ghostils:Update to reference curEG: + int xR = x + Proportion(release[curEG], length, 62); + int yS = Proportion(sustain[curEG], HEM_EG_MAX_VALUE, HEM_EG_DISPLAY_HEIGHT); gfxLine(x, BottomAlign(yS), xR, BottomAlign(0), edit_stage != HEM_EG_RELEASE); return xR; } void AttackAmplitude(int ch) { - int total_stage_ticks = Proportion(attack_mod, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_AD); + //-ghostils:Update to reference current channel: + //-Remove attack_mod CV: + //int effective_attack = constrain(attack[ch] + attack_mod, 1, HEM_EG_MAX_VALUE); + int effective_attack = constrain(attack[ch], 1, HEM_EG_MAX_VALUE); + int total_stage_ticks = Proportion(effective_attack, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_AD); int ticks_remaining = total_stage_ticks - stage_ticks[ch]; - if (attack_mod == 1) ticks_remaining = 0; + if (effective_attack == 1) ticks_remaining = 0; if (ticks_remaining <= 0) { // End of attack; move to decay stage[ch] = HEM_EG_DECAY; stage_ticks[ch] = 0; @@ -222,14 +304,15 @@ private: } void DecayAmplitude(int ch) { - int total_stage_ticks = Proportion(decay, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_AD); + //-ghostils:Update to reference current channel: + int total_stage_ticks = Proportion(decay[ch], HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_AD); int ticks_remaining = total_stage_ticks - stage_ticks[ch]; - simfloat amplitude_remaining = amplitude[ch] - int2simfloat(Proportion(sustain, HEM_EG_MAX_VALUE, HEMISPHERE_MAX_CV)); - if (sustain == 1) ticks_remaining = 0; + simfloat amplitude_remaining = amplitude[ch] - int2simfloat(Proportion(sustain[ch], HEM_EG_MAX_VALUE, HEMISPHERE_MAX_CV)); + if (sustain[ch] == 1) ticks_remaining = 0; if (ticks_remaining <= 0) { // End of decay; move to sustain stage[ch] = HEM_EG_SUSTAIN; stage_ticks[ch] = 0; - amplitude[ch] = int2simfloat(Proportion(sustain, HEM_EG_MAX_VALUE, HEMISPHERE_MAX_CV)); + amplitude[ch] = int2simfloat(Proportion(sustain[ch], HEM_EG_MAX_VALUE, HEMISPHERE_MAX_CV)); } else { simfloat decrease = amplitude_remaining / ticks_remaining; amplitude[ch] -= decrease; @@ -237,13 +320,17 @@ private: } void SustainAmplitude(int ch) { - amplitude[ch] = int2simfloat(Proportion(sustain - 1, HEM_EG_MAX_VALUE, HEMISPHERE_MAX_CV)); + //-ghostils:Update to reference current channel: + amplitude[ch] = int2simfloat(Proportion(sustain[ch] - 1, HEM_EG_MAX_VALUE, HEMISPHERE_MAX_CV)); } void ReleaseAmplitude(int ch) { - int total_stage_ticks = Proportion(release_mod, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_R); + //-ghostils:Update to reference current channel: + //-CV1 = ADSR A release MOD, CV2 = ADSR A release MOD + int effective_release = constrain(release[ch] + release_mod[ch], 1, HEM_EG_MAX_VALUE) - 1; + int total_stage_ticks = Proportion(effective_release, HEM_EG_MAX_VALUE, HEM_EG_MAX_TICKS_R); int ticks_remaining = total_stage_ticks - stage_ticks[ch]; - if (release_mod == 0) ticks_remaining = 0; + if (effective_release == 0) ticks_remaining = 0; if (ticks_remaining <= 0 || amplitude[ch] <= 0) { // End of release; turn off envelope stage[ch] = HEM_EG_NO_STAGE; stage_ticks[ch] = 0; From 78081b1779af6f1ca0a960ae7e5085c1875e4cac Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 27 Aug 2023 03:22:30 -0400 Subject: [PATCH 283/417] AttenOff: Allow +/- 200% ; Use TR2 for Mix gate Using TR2 avoids clock pulse collisions. This change breaks existing save data for this applet. --- software/o_c_REV/HEM_AttenuateOffset.ino | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index e51137b23..e4aac44ec 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -19,6 +19,7 @@ // SOFTWARE. #define ATTENOFF_INCREMENTS 128 +#define ATTENOFF_MAX_LEVEL 63 class AttenuateOffset : public HemisphereApplet { public: @@ -28,16 +29,16 @@ public: } void Start() { - ForEachChannel(ch) level[ch] = 63; + ForEachChannel(ch) level[ch] = ATTENOFF_MAX_LEVEL; } void Controller() { - mix_final = mix || Gate(0); + mix_final = mix || Gate(1); int prevSignal = 0; ForEachChannel(ch) { - int signal = Proportion(level[ch], 63, In(ch)) + (offset[ch] * ATTENOFF_INCREMENTS); + int signal = Proportion(level[ch], ATTENOFF_MAX_LEVEL, In(ch)) + (offset[ch] * ATTENOFF_INCREMENTS); if (ch == 1 && mix_final) { signal = signal + prevSignal; } @@ -79,8 +80,8 @@ public: int max = HEMISPHERE_MAX_CV / ATTENOFF_INCREMENTS; offset[ch] = constrain(offset[ch] + direction, min, max); } else { - // Change level percentage - level[ch] = constrain(level[ch] + direction, -63, 63); + // Change level percentage (+/-200%) + level[ch] = constrain(level[ch] + direction, -ATTENOFF_MAX_LEVEL*2, ATTENOFF_MAX_LEVEL*2); } } @@ -88,24 +89,24 @@ public: uint64_t data = 0; Pack(data, PackLocation {0,9}, offset[0] + 256); Pack(data, PackLocation {10,9}, offset[1] + 256); - Pack(data, PackLocation {19,7}, level[0] + 64); - Pack(data, PackLocation {26,7}, level[1] + 64); - Pack(data, PackLocation {34,1}, mix); + Pack(data, PackLocation {19,8}, level[0] + ATTENOFF_MAX_LEVEL*2); + Pack(data, PackLocation {27,8}, level[1] + ATTENOFF_MAX_LEVEL*2); + Pack(data, PackLocation {35,1}, mix); return data; } void OnDataReceive(uint64_t data) { offset[0] = Unpack(data, PackLocation {0,9}) - 256; offset[1] = Unpack(data, PackLocation {10,9}) - 256; - level[0] = Unpack(data, PackLocation {19,7}) - 64; - level[1] = Unpack(data, PackLocation {26,7}) - 64; - mix = Unpack(data, PackLocation {34,1}); + level[0] = Unpack(data, PackLocation {19,8}) - ATTENOFF_MAX_LEVEL*2; + level[1] = Unpack(data, PackLocation {27,8}) - ATTENOFF_MAX_LEVEL*2; + mix = Unpack(data, PackLocation {35,1}); } protected: void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Mix A&B"; + help[HEMISPHERE_HELP_DIGITALS] = "2=Mix A&B"; help[HEMISPHERE_HELP_CVS] = "CV Inputs 1,2"; help[HEMISPHERE_HELP_OUTS] = "Outputs A,B"; help[HEMISPHERE_HELP_ENCODER] = "Offset V / Level %"; From af4f9d293f1e5feeb109a451ec72a488bf4a30e4 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 30 Aug 2023 00:59:09 -0400 Subject: [PATCH 284/417] Switch to Teensy CLI uploader --- software/o_c_REV/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index f89818c74..142cc71ec 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -40,7 +40,7 @@ build_src_filter = extra_scripts = pre:resources/progname.py -upload_protocol = teensy-gui +upload_protocol = teensy-cli [env:pewpewpew] ; phazer's choice build From b794a1324600b836129a8d6d25dee0a06e3c4702 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 24 Mar 2023 22:39:24 -0400 Subject: [PATCH 285/417] Refactoring Applet I/O (CV and MIDI) to a struct MIDI Start/Stop/Clock work intuitively with the internal Clock. MIDI Input overrides CV input when configured. MIDI-In applet serves as the UI for configuring inputs on both sides. Separate MIDI channel settings for all 4 outputs. Modwheel input was replaced with general MIDI CC auto-learn. --- software/o_c_REV/APP_CALIBR8OR.ino | 42 ++-- software/o_c_REV/APP_HEMISPHERE.ino | 101 ++++++---- software/o_c_REV/HEM_ClockSetup.ino | 48 +++-- software/o_c_REV/HEM_hMIDIIn.ino | 291 +++++++++------------------- software/o_c_REV/HSClockManager.h | 8 +- software/o_c_REV/HSIOFrame.h | 234 ++++++++++++++++++++++ software/o_c_REV/HSMIDI.h | 19 ++ software/o_c_REV/HemisphereApplet.h | 260 +++++++++---------------- 8 files changed, 552 insertions(+), 451 deletions(-) create mode 100644 software/o_c_REV/HSIOFrame.h diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index ab6037c27..de3d9a596 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -199,38 +199,26 @@ public: void Resume() { } - void Controller() { - bool clock_sync = OC::DigitalInputs::clocked(); - bool reset = OC::DigitalInputs::clocked(); - bool midi_sync = 0; - - // flush MIDI input and catch incoming Clock + void ProcessMIDI() { + HS::IOFrame &f = HS::frame; while (usbMIDI.read()) { - switch (usbMIDI.getType()) { - case usbMIDI.Clock: - clock_sync = 1; - midi_sync = 1; - break; - case usbMIDI.Start: - clock_m->Start(); - break; - case usbMIDI.Stop: - clock_m->Stop(); - break; + const int message = usbMIDI.getType(); + const int data1 = usbMIDI.getData1(); + const int data2 = usbMIDI.getData2(); + + if (message == usbMIDI.SystemExclusive) { + // TODO: consider implementing SysEx import/export for Calibr8or + continue; } - // TODO: do we need to handle any other MIDI input? - // We will have to in Hemisphere... for the MIDI In applet. - // Might need to delegate other messages or something - } - if (midi_sync) clock_m->SetClockPPQN(24); // rudely snap to MIDI clock sync speed - // Paused means wait for clock-sync to start - if (clock_m->IsPaused() && clock_sync) clock_m->Start(); + f.MIDIState.ProcessMIDIMsg(usbMIDI.getChannel(), message, data1, data2); + } + } - // Advance internal clock, sync to external clock / reset - if (clock_m->IsRunning()) clock_m->SyncTrig( clock_sync, reset ); + void Controller() { + ProcessMIDI(); - // ClockSetup applet handles MIDI Clock Out + // ClockSetup applet handles internal clock duties HS::clock_setup_applet.Controller(0, 0); // -- core processing -- diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index d139c7343..c59aac338 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -167,7 +167,6 @@ class HemisphereManager : public HSApplication { public: void Start() { select_mode = -1; // Not selecting - midi_in_hemisphere = -1; // No MIDI In help_hemisphere = -1; clock_setup = 0; @@ -225,8 +224,6 @@ public: // does not modify the preset, only the manager void SetApplet(int hemisphere, int index) { my_applet[hemisphere] = index; - if (midi_in_hemisphere == hemisphere) midi_in_hemisphere = -1; - if (HS::available_applets[index].id & 0x80) midi_in_hemisphere = hemisphere; HS::available_applets[index].Start(hemisphere); } @@ -239,40 +236,73 @@ public: return select_mode > -1; } - void Controller() { - // TODO: eliminate the need for this with top-level MIDI handling - if (midi_in_hemisphere == -1) { - // Only one ISR can look for MIDI messages at a time, so we need to check - // for another MIDI In applet before looking for sysex. Note that applets - // that use MIDI In should check for sysex themselves; see Midi In for an - // example. - if (usbMIDI.read() && usbMIDI.getType() == usbMIDI.SystemExclusive) { - if (hem_active_preset) - hem_active_preset->OnReceiveSysEx(); + void ProcessMIDI() { + HS::IOFrame &f = HS::frame; + + while (usbMIDI.read()) { + const int message = usbMIDI.getType(); + const int data1 = usbMIDI.getData1(); + const int data2 = usbMIDI.getData2(); + + if (message == usbMIDI.SystemExclusive) { + ReceiveManagerSysEx(); + continue; } + + f.MIDIState.ProcessMIDIMsg(usbMIDI.getChannel(), message, data1, data2); } + } + + void Controller() { + // top-level MIDI-to-CV handling - alters frame outputs + ProcessMIDI(); - bool clock_sync = OC::DigitalInputs::clocked(); - bool reset = OC::DigitalInputs::clocked(); + // Load the IO frame from CV inputs + HS::frame.Load(); - // Paused means wait for clock-sync to start - if (clock_m->IsPaused() && clock_sync) { - clock_m->Start(); - usbMIDI.sendRealTime(usbMIDI.Start); + // XXX: kind of a crutch, should be replaced with general Trigger input mapping + if (clock_m->IsForwarded()) { + HS::frame.clocked[2] = HS::frame.clocked[0]; } - // TODO: automatically stop... - - // Advance internal clock, sync to external clock / reset - if (clock_m->IsRunning()) clock_m->SyncTrig( clock_sync, reset ); - // NJM: always execute ClockSetup controller - it handles MIDI clock out - HS::clock_setup_applet.Controller(LEFT_HEMISPHERE, clock_m->IsForwarded()); + // Clock Setup applet handles internal clock duties + HS::clock_setup_applet.Controller(LEFT_HEMISPHERE, 0); + // execute Applets for (int h = 0; h < 2; h++) { int index = my_applet[h]; - HS::available_applets[index].Controller(h, clock_m->IsForwarded()); + if (HS::available_applets[index].id != 150) // not MIDI In + { + ForEachChannel(ch) { + int chan = h*2 + ch; + // override CV inputs with applicable MIDI signals + switch (HS::frame.MIDIState.function[chan]) { + case HEM_MIDI_CC_OUT: + case HEM_MIDI_NOTE_OUT: + case HEM_MIDI_VEL_OUT: + case HEM_MIDI_AT_OUT: + case HEM_MIDI_PB_OUT: + HS::frame.inputs[chan] = HS::frame.MIDIState.outputs[chan]; + break; + case HEM_MIDI_GATE_OUT: + // XXX: how do we want this one to behave? override digital? or CV? both? + HS::frame.gate_high[chan] = (HS::frame.MIDIState.outputs[chan] > (12 << 7)); + break; + case HEM_MIDI_TRIG_OUT: + case HEM_MIDI_CLOCK_OUT: + case HEM_MIDI_START_OUT: + HS::frame.clocked[chan] = HS::frame.MIDIState.trigout_q[chan]; + HS::frame.MIDIState.trigout_q[chan] = 0; + break; + } + } + } + HS::available_applets[index].Controller(h, 0); } + + // set outputs from IO frame + HS::frame.Send(); } void View() { @@ -426,11 +456,9 @@ public: void ToggleClockRun() { if (clock_m->IsRunning()) { clock_m->Stop(); - usbMIDI.sendRealTime(usbMIDI.Stop); } else { bool p = clock_m->IsPaused(); clock_m->Start( !p ); - if (p) usbMIDI.sendRealTime(usbMIDI.Start); } } @@ -468,7 +496,6 @@ private: int config_cursor = 0; int help_hemisphere; // Which of the hemispheres (if any) is in help mode, or -1 if none - int midi_in_hemisphere; // Which of the hemispheres (if any) is using MIDI In uint32_t click_tick; // Measure time between clicks for double-click int first_click; // The first button pushed of a double-click set, to see if the same one is pressed ClockManager *clock_m = clock_m->get(); @@ -490,7 +517,7 @@ private: } if (config_cursor == TRIG_LENGTH) { - HemisphereApplet::trig_length = (uint32_t) constrain( int(HemisphereApplet::trig_length + dir), 1, 127); + HS::trig_length = (uint32_t) constrain( int(HS::trig_length + dir), 1, 127); } else if (config_cursor == SAVE_PRESET || config_cursor == LOAD_PRESET) { preset_cursor = constrain(preset_cursor + dir, 1, HEM_NR_OF_PRESETS); @@ -521,7 +548,7 @@ private: break; case CURSOR_MODE: - HemisphereApplet::CycleEditMode(); + HS::CycleEditMode(); break; } } @@ -539,11 +566,11 @@ private: gfxPrint(48, 15, "Load / Save"); gfxPrint(1, 35, "Trig Length: "); - gfxPrint(HemisphereApplet::trig_length); + gfxPrint(HS::trig_length); const char * cursor_mode_name[3] = { "legacy", "modal", "modal+wrap" }; gfxPrint(1, 45, "Cursor: "); - gfxPrint(cursor_mode_name[HemisphereApplet::modal_edit_mode]); + gfxPrint(cursor_mode_name[HS::modal_edit_mode]); switch (config_cursor) { case LOAD_PRESET: @@ -591,14 +618,6 @@ private: if (index >= HEMISPHERE_AVAILABLE_APPLETS) index = 0; if (index < 0) index = HEMISPHERE_AVAILABLE_APPLETS - 1; - // If an applet uses MIDI In, it can only be selected in one - // hemisphere, and is designated by bit 7 set in its id. - if (HS::available_applets[index].id & 0x80) { - if (midi_in_hemisphere == (1 - select_mode)) { - return get_next_applet_index(index, dir); - } - } - return index; } }; diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 87b763034..0bf6e21ec 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -45,14 +45,35 @@ public: // The ClockSetup controller handles MIDI Clock and Transport Start/Stop void Controller() { - if (start_q){ - start_q = 0; - usbMIDI.sendRealTime(usbMIDI.Start); + bool clock_sync = OC::DigitalInputs::clocked(); + bool reset = OC::DigitalInputs::clocked(); + + // MIDI Clock is filtered to 2 PPQN + if (frame.MIDIState.clock_q) { + frame.MIDIState.clock_q = 0; + clock_sync = 1; + } + if (frame.MIDIState.start_q) { + frame.MIDIState.start_q = 0; + clock_m->DisableMIDIOut(); + clock_m->Start(); } - if (stop_q){ - stop_q = 0; - usbMIDI.sendRealTime(usbMIDI.Stop); + if (frame.MIDIState.stop_q) { + frame.MIDIState.stop_q = 0; + clock_m->Stop(); + clock_m->EnableMIDIOut(); } + + // Paused means wait for clock-sync to start + if (clock_m->IsPaused() && clock_sync) + clock_m->Start(); + // TODO: automatically stop... + + // Advance internal clock, sync to external clock / reset + if (clock_m->IsRunning()) + clock_m->SyncTrig( clock_sync, reset ); + + // ------------ // if (clock_m->IsRunning() && clock_m->MIDITock()) usbMIDI.sendRealTime(usbMIDI.Clock); // 4 internal clock flashers @@ -154,8 +175,8 @@ public: } // other config settings are kept here as well, it's convenient - Pack(data, PackLocation { 50, 2 }, HemisphereApplet::modal_edit_mode); - Pack(data, PackLocation { 52, 7 }, HemisphereApplet::trig_length); + Pack(data, PackLocation { 50, 2 }, HS::modal_edit_mode); + Pack(data, PackLocation { 52, 7 }, HS::trig_length); return data; } @@ -175,8 +196,8 @@ public: clock_m->SetMultiply(Unpack(data, PackLocation { 16+i*6, 6 })-32, i); } - HemisphereApplet::modal_edit_mode = Unpack(data, PackLocation { 50, 2 }); - HemisphereApplet::trig_length = constrain( Unpack(data, PackLocation { 52, 7 }), 1, 127); + HS::modal_edit_mode = Unpack(data, PackLocation { 50, 2 }); + HS::trig_length = constrain( Unpack(data, PackLocation { 52, 7 }), 1, 127); } protected: @@ -191,8 +212,6 @@ protected: private: int cursor; // ClockSetupCursor - bool start_q; - bool stop_q; int flash_ticker[4]; int button_ticker; ClockManager *clock_m = clock_m->get(); @@ -205,11 +224,10 @@ private: void PlayStop() { if (clock_m->IsRunning()) { - stop_q = 1; clock_m->Stop(); } else { - start_q = clock_m->IsPaused(); - clock_m->Start( !start_q ); // stop->pause->start + bool p = clock_m->IsPaused(); + clock_m->Start( !p ); // stop->pause->start } } diff --git a/software/o_c_REV/HEM_hMIDIIn.ino b/software/o_c_REV/HEM_hMIDIIn.ino index 091e1f6ea..a8d4b6c59 100644 --- a/software/o_c_REV/HEM_hMIDIIn.ino +++ b/software/o_c_REV/HEM_hMIDIIn.ino @@ -20,208 +20,111 @@ // See https://www.pjrc.com/teensy/td_midi.html -#define HEM_MIDI_CLOCK_DIVISOR 12 - -struct MIDILogEntry { - int message; - int data1; - int data2; -}; - class hMIDIIn : public HemisphereApplet { public: - // The functions available for each output - enum hMIDIFunctions { - HEM_MIDI_NOTE_OUT, - HEM_MIDI_TRIG_OUT, - HEM_MIDI_GATE_OUT, - HEM_MIDI_VEL_OUT, - HEM_MIDI_CC_OUT, - HEM_MIDI_AT_OUT, - HEM_MIDI_PB_OUT, - HEM_MIDI_CLOCK_OUT, - HEM_MIDI_START_OUT, - - HEM_MIDI_MAX_FUNCTION = HEM_MIDI_START_OUT + enum hMIDIInCursor { + MIDI_CHANNEL_A, + MIDI_CHANNEL_B, + OUTPUT_MODE_A, + OUTPUT_MODE_B, + LOG_VIEW, + + MIDI_CURSOR_LAST = LOG_VIEW }; - const char* fn_name[HEM_MIDI_MAX_FUNCTION + 1] = {"Note#", "Trig", "Gate", "Veloc", "Mod", "Aft", "Bend", "Clock", "Start"}; const char* applet_name() { return "MIDIIn"; } void Start() { - first_note = -1; - channel = 0; // Default channel 1 - ForEachChannel(ch) { - function[ch] = ch * 2; + int ch_ = ch + io_offset; + frame.MIDIState.channel[ch_] = 0; // Default channel 1 + frame.MIDIState.function[ch_] = HEM_MIDI_NOOP; + frame.MIDIState.outputs[ch_] = 0; Out(ch, 0); } - log_index = 0; - clock_count = 0; + frame.MIDIState.log_index = 0; + frame.MIDIState.clock_count = 0; } void Controller() { - while (usbMIDI.read()) { - int message = usbMIDI.getType(); - int data1 = usbMIDI.getData1(); - int data2 = usbMIDI.getData2(); - - if (message == HEM_MIDI_SYSEX) { - ReceiveManagerSysEx(); - continue; - } - - // Listen for incoming clock - if (HEM_MIDI_CLOCK == message) { - if (++clock_count == 1) { - ForEachChannel(ch) - { - if (function[ch] == HEM_MIDI_CLOCK_OUT) { - ClockOut(ch); - } - } + // MIDI input is processed at a higher level + // here, we just pass the MIDI signals on to physical outputs + ForEachChannel(ch) { + int ch_ = ch + io_offset; + switch (frame.MIDIState.function[ch_]) { + case HEM_MIDI_NOOP: + break; + case HEM_MIDI_CLOCK_OUT: + case HEM_MIDI_START_OUT: + case HEM_MIDI_TRIG_OUT: + if (frame.MIDIState.trigout_q[ch_]) { + frame.MIDIState.trigout_q[ch_] = 0; + ClockOut(ch); } - if (clock_count == HEM_MIDI_CLOCK_DIVISOR) clock_count = 0; - continue; - } - - if (HEM_MIDI_START == message) { - ForEachChannel(ch) - { - if (function[ch] == HEM_MIDI_START_OUT) { - ClockOut(ch); - } - } - - UpdateLog(message, data1, data2); - continue; + break; + default: + Out(ch, frame.MIDIState.outputs[ch_]); + break; } - - // all other messages are filtered by MIDI channel - if (usbMIDI.getChannel() == (channel + 1)) - { - last_tick = OC::CORE::ticks; - bool log_this = false; - - if (message == HEM_MIDI_NOTE_ON) { // Note on - // only track the most recent note - first_note = data1; - - // Should this message go out on any channel? - ForEachChannel(ch) - { - if (function[ch] == HEM_MIDI_NOTE_OUT) - Out(ch, MIDIQuantizer::CV(data1)); - - if (function[ch] == HEM_MIDI_TRIG_OUT) - ClockOut(ch); - - if (function[ch] == HEM_MIDI_GATE_OUT) - GateOut(ch, 1); - - if (function[ch] == HEM_MIDI_VEL_OUT) - Out(ch, Proportion(data2, 127, HEMISPHERE_MAX_CV)); - } - - log_this = 1; // Log all MIDI notes. Other stuff is conditional. - } - - // Note off - only for most recent note - if (message == HEM_MIDI_NOTE_OFF && data1 == first_note) - { - first_note = -1; - - // Should this message go out on any channel? - ForEachChannel(ch) - { - if (function[ch] == HEM_MIDI_GATE_OUT) { - GateOut(ch, 0); - log_this = 1; - } - } - } - - if (message == HEM_MIDI_CC) { // Modulation wheel - ForEachChannel(ch) - { - if (function[ch] == HEM_MIDI_CC_OUT && data1 == 1) { - Out(ch, Proportion(data2, 127, HEMISPHERE_MAX_CV)); - log_this = 1; - } - } - - } - - if (message == HEM_MIDI_AFTERTOUCH) { // Aftertouch - ForEachChannel(ch) - { - if (function[ch] == HEM_MIDI_AT_OUT) { - Out(ch, Proportion(data1, 127, HEMISPHERE_MAX_CV)); - log_this = 1; - } - } - } - - if (message == HEM_MIDI_PITCHBEND) { // Pitch Bend - ForEachChannel(ch) - { - if (function[ch] == HEM_MIDI_PB_OUT) { - int data = (data2 << 7) + data1 - 8192; - Out(ch, Proportion(data, 0x7fff, HEMISPHERE_3V_CV)); - log_this = 1; - } - } - } - - if (log_this) UpdateLog(message, data1, data2); - } - } // while + } } void View() { gfxHeader(applet_name()); DrawMonitor(); - if (cursor == 3) DrawLog(); + if (cursor == LOG_VIEW) DrawLog(); else DrawSelector(); } void OnButtonPress() { - CursorAction(cursor, 3); + CursorAction(cursor, MIDI_CURSOR_LAST); } void OnEncoderMove(int direction) { if (!EditMode()) { - MoveCursor(cursor, direction, 3); + MoveCursor(cursor, direction, MIDI_CURSOR_LAST); return; } - if (cursor == 3) return; - if (cursor == 0) channel = constrain(channel + direction, 0, 15); + // Log view + if (cursor == LOG_VIEW) return; + + if (cursor == MIDI_CHANNEL_A || cursor == MIDI_CHANNEL_B) { + int ch = io_offset + cursor - MIDI_CHANNEL_A; + frame.MIDIState.channel[ch] = constrain(frame.MIDIState.channel[ch] + direction, 0, 15); + } else { - int ch = cursor - 1; - function[ch] = constrain(function[ch] + direction, 0, HEM_MIDI_MAX_FUNCTION); - clock_count = 0; + int ch = io_offset + cursor - OUTPUT_MODE_A; + frame.MIDIState.function[ch] = constrain(frame.MIDIState.function[ch] + direction, 0, HEM_MIDI_MAX_FUNCTION); + frame.MIDIState.function_cc[ch] = -1; // auto-learn MIDI CC + frame.MIDIState.clock_count = 0; } ResetCursor(); } uint64_t OnDataRequest() { uint64_t data = 0; - Pack(data, PackLocation {0,8}, channel); - Pack(data, PackLocation {8,3}, function[0]); - Pack(data, PackLocation {11,3}, function[1]); + Pack(data, PackLocation {0,4}, frame.MIDIState.channel[io_offset + 0]); + Pack(data, PackLocation {4,4}, frame.MIDIState.channel[io_offset + 1]); + Pack(data, PackLocation {8,3}, frame.MIDIState.function[io_offset + 0]); + Pack(data, PackLocation {11,3}, frame.MIDIState.function[io_offset + 1]); + Pack(data, PackLocation {14,7}, frame.MIDIState.function_cc[io_offset + 0] + 1); + Pack(data, PackLocation {21,7}, frame.MIDIState.function_cc[io_offset + 1] + 1); return data; } void OnDataReceive(uint64_t data) { - channel = Unpack(data, PackLocation {0,8}); - function[0] = Unpack(data, PackLocation {8,3}); - function[1] = Unpack(data, PackLocation {11,3}); + frame.MIDIState.channel[io_offset + 0] = Unpack(data, PackLocation {0,4}); + frame.MIDIState.channel[io_offset + 1] = Unpack(data, PackLocation {4,4}); + frame.MIDIState.function[io_offset + 0] = Unpack(data, PackLocation {8,3}); + frame.MIDIState.function[io_offset + 1] = Unpack(data, PackLocation {11,3}); + frame.MIDIState.function_cc[io_offset + 0] = Unpack(data, PackLocation {14,7}) - 1; + frame.MIDIState.function_cc[io_offset + 1] = Unpack(data, PackLocation {21,7}) - 1; } protected: @@ -235,96 +138,80 @@ protected: } private: - // Settings - int channel; // MIDI channel number - int function[2]; // Function for each channel - // Housekeeping int cursor; // 0=MIDI channel, 1=A/C function, 2=B/D function - int last_tick; // Tick of last received message - int first_note; // First note received, for awaiting Note Off - uint8_t clock_count; // MIDI clock counter (24ppqn) - // Logging - MIDILogEntry log[7]; - int log_index; - - void UpdateLog(int message, int data1, int data2) { - log[log_index++] = {message, data1, data2}; - if (log_index == 7) { - for (int i = 0; i < 6; i++) - { - memcpy(&log[i], &log[i+1], sizeof(log[i+1])); - } - log_index--; - } - } - void DrawMonitor() { - if (OC::CORE::ticks - last_tick < 4000) { + if (OC::CORE::ticks - frame.MIDIState.last_msg_tick < 4000) { gfxBitmap(46, 1, 8, MIDI_ICON); } } void DrawSelector() { - // MIDI Channel - gfxPrint(1, 15, "Ch:"); - gfxPrint(24, 15, channel + 1); + // MIDI Channels + gfxPrint(1, 15, hemisphere == 0 ? "ChA:" : "ChC:"); + gfxPrint(24, 15, frame.MIDIState.channel[io_offset + 0] + 1); + gfxPrint(1, 25, hemisphere == 0 ? "ChB:" : "ChD:"); + gfxPrint(24, 25, frame.MIDIState.channel[io_offset + 1] + 1); // Output 1 function - if (hemisphere == 0) gfxPrint(1, 25, "A :"); - else gfxPrint(1, 25, "C :"); - gfxPrint(24, 25, fn_name[function[0]]); + gfxPrint(1, 35, hemisphere == 0 ? "A :" : "C :"); + gfxPrint(24, 35, midi_fn_name[frame.MIDIState.function[io_offset + 0]]); + if (frame.MIDIState.function[io_offset + 0] == HEM_MIDI_CC_OUT) + gfxPrint(frame.MIDIState.function_cc[io_offset + 0]); // Output 2 function - if (hemisphere == 0) gfxPrint(1, 35, "B :"); - else gfxPrint(1, 35, "D :"); - gfxPrint(24, 35, fn_name[function[1]]); + gfxPrint(1, 45, hemisphere == 0 ? "B :" : "D :"); + gfxPrint(24, 45, midi_fn_name[frame.MIDIState.function[io_offset + 1]]); + if (frame.MIDIState.function[io_offset + 1] == HEM_MIDI_CC_OUT) + gfxPrint(frame.MIDIState.function_cc[io_offset + 1]); // Cursor gfxCursor(24, 23 + (cursor * 10), 39); // Last log entry - if (log_index > 0) { - log_entry(56, log_index - 1); + if (frame.MIDIState.log_index > 0) { + PrintLogEntry(56, frame.MIDIState.log_index - 1); } gfxInvert(0, 55, 63, 9); } void DrawLog() { - if (log_index) { - for (int i = 0; i < log_index; i++) + if (frame.MIDIState.log_index) { + for (int i = 0; i < frame.MIDIState.log_index; i++) { - log_entry(15 + (i * 8), i); + PrintLogEntry(15 + (i * 8), i); } } } - void log_entry(int y, int index) { - switch ( log[index].message ) { + void PrintLogEntry(int y, int index) { + MIDILogEntry &log_entry_ = frame.MIDIState.log[index]; + + switch ( log_entry_.message ) { case HEM_MIDI_NOTE_ON: gfxBitmap(1, y, 8, NOTE_ICON); - gfxPrint(10, y, midi_note_numbers[log[index].data1]); - gfxPrint(40, y, log[index].data2); + gfxPrint(10, y, midi_note_numbers[log_entry_.data1]); + gfxPrint(40, y, log_entry_.data2); break; case HEM_MIDI_NOTE_OFF: gfxPrint(1, y, "-"); - gfxPrint(10, y, midi_note_numbers[log[index].data1]); + gfxPrint(10, y, midi_note_numbers[log_entry_.data1]); break; case HEM_MIDI_CC: gfxBitmap(1, y, 8, MOD_ICON); - gfxPrint(10, y, log[index].data2); + gfxPrint(10, y, log_entry_.data2); break; case HEM_MIDI_AFTERTOUCH: gfxBitmap(1, y, 8, AFTERTOUCH_ICON); - gfxPrint(10, y, log[index].data1); + gfxPrint(10, y, log_entry_.data1); break; case HEM_MIDI_PITCHBEND: { - int data = (log[index].data2 << 7) + log[index].data1 - 8192; + int data = (log_entry_.data2 << 7) + log_entry_.data1 - 8192; gfxBitmap(1, y, 8, BEND_ICON); gfxPrint(10, y, data); break; @@ -332,9 +219,9 @@ private: default: gfxPrint(1, y, "?"); - gfxPrint(10, y, log[index].data1); + gfxPrint(10, y, log_entry_.data1); gfxPrint(" "); - gfxPrint(log[index].data2); + gfxPrint(log_entry_.data2); break; } } diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index ae7b40bc3..642837ac2 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -52,6 +52,7 @@ class ClockManager { bool running = 0; // Specifies whether the clock is running for interprocess communication bool paused = 0; // Specifies whethr the clock is paused bool forwarded = 0; // Master clock forwarding is enabled when true + bool midi_out_enabled = 1; uint32_t clock_tick = 0; // tick when a physical clock was received on DIGITAL 1 uint32_t beat_tick = 0; // The tick to count from @@ -74,6 +75,9 @@ class ClockManager { return instance; } + void EnableMIDIOut() { midi_out_enabled = 1; } + void DisableMIDIOut() { midi_out_enabled = 0; } + void SetMultiply(int multiply, int ch = 0) { multiply = constrain(multiply, CLOCK_MIN_MULTIPLE, CLOCK_MAX_MULTIPLE); tocks_per_beat[ch] = multiply; @@ -207,11 +211,13 @@ class ClockManager { Reset(); running = 1; paused = p; + if (!p && midi_out_enabled) usbMIDI.sendRealTime(usbMIDI.Start); } void Stop() { running = 0; paused = 0; + if (midi_out_enabled) usbMIDI.sendRealTime(usbMIDI.Stop); } void Pause() {paused = 1;} @@ -247,7 +253,7 @@ class ClockManager { // Returns true if MIDI Clock should be sent on this tick bool MIDITock() { - return Tock(MIDI_CLOCK); + return midi_out_enabled && Tock(MIDI_CLOCK); } bool EndOfBeat(int ch = 0) {return count[ch] == 1;} diff --git a/software/o_c_REV/HSIOFrame.h b/software/o_c_REV/HSIOFrame.h new file mode 100644 index 000000000..1b61b5f72 --- /dev/null +++ b/software/o_c_REV/HSIOFrame.h @@ -0,0 +1,234 @@ +/* Copyright (c) 2023 Nicholas J. Michalek + * + * IOFrame & friends + * attempts at making applet I/O more flexible and portable + * + * Some processing logic adapted from the MIDI In applet + * + */ + +namespace HS { + +braids::Quantizer quantizer[4]; // global shared quantizers +uint8_t trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS + +typedef struct MIDILogEntry { + int message; + int data1; + int data2; +} MIDILogEntry; + +// shared IO Frame, updated every tick +// this will allow chaining applets together, multiple stages of processing +typedef struct IOFrame { + bool clocked[4]; + bool gate_high[4]; + int inputs[4]; + int outputs[4]; + int outputs_smooth[4]; + int clock_countdown[4]; + int adc_lag_countdown[4]; // Time between a clock event and an ADC read event + uint32_t last_clock[4]; // Tick number of the last clock observed by the child class + uint32_t cycle_ticks[4]; // Number of ticks between last two clocks + bool changed_cv[4]; // Has the input changed by more than 1/8 semitone since the last read? + int last_cv[4]; // For change detection + + /* MIDI message queue/cache */ + struct { + int channel[4]; // MIDI channel number + int function[4]; // Function for each channel + int function_cc[4]; // CC# for each channel + + // Output values and ClockOut triggers, handled by MIDIIn applet + int outputs[4]; + bool trigout_q[4]; + + int8_t notes_on[16]; // attempts to track how many notes are on, per MIDI channel + int last_msg_tick; // Tick of last received message + uint8_t clock_count; // MIDI clock counter (24ppqn) + + // Clock/Start/Stop are handled by ClockSetup applet + bool clock_q; + bool start_q; + bool stop_q; + + // Logging + MIDILogEntry log[7]; + int log_index; + + void UpdateLog(int message, int data1, int data2) { + log[log_index++] = {message, data1, data2}; + if (log_index == 7) { + for (int i = 0; i < 6; i++) + { + memcpy(&log[i], &log[i+1], sizeof(log[i+1])); + } + log_index--; + } + last_msg_tick = OC::CORE::ticks; + } + void ProcessMIDIMsg(const int midi_chan, const int message, const int data1, const int data2) { + switch (message) { + case usbMIDI.Clock: + if (++clock_count == 1) { + clock_q = 1; + ForAllChannels(ch) + { + if (function[ch] == HEM_MIDI_CLOCK_OUT) { + trigout_q[ch] = 1; + } + } + } + if (clock_count == HEM_MIDI_CLOCK_DIVISOR) clock_count = 0; + return; + break; + + case usbMIDI.Continue: // treat Continue like Start + case usbMIDI.Start: + start_q = 1; + ForAllChannels(ch) + { + if (function[ch] == HEM_MIDI_START_OUT) { + trigout_q[ch] = 1; + } + } + + //UpdateLog(message, data1, data2); + return; + break; + + case usbMIDI.SystemReset: + case usbMIDI.Stop: + stop_q = 1; + // a way to reset stuck notes + for (int i = 0; i < 16; ++i) notes_on[i] = 0; + return; + break; + + case usbMIDI.NoteOn: + ++notes_on[midi_chan - 1]; + break; + case usbMIDI.NoteOff: + --notes_on[midi_chan - 1]; + break; + + } + + ForAllChannels(ch) { + if (function[ch] == HEM_MIDI_NOOP) continue; + + // skip unwanted MIDI Channels + if (midi_chan - 1 != channel[ch]) continue; + + bool log_this = false; + + switch (message) { + case usbMIDI.NoteOn: + + // Should this message go out on this channel? + if (function[ch] == HEM_MIDI_NOTE_OUT) + outputs[ch] = MIDIQuantizer::CV(data1); + + if (function[ch] == HEM_MIDI_TRIG_OUT) + trigout_q[ch] = 1; + + if (function[ch] == HEM_MIDI_GATE_OUT) + outputs[ch] = PULSE_VOLTAGE * (12 << 7); + + if (function[ch] == HEM_MIDI_VEL_OUT) + outputs[ch] = Proportion(data2, 127, HEMISPHERE_MAX_CV); + + log_this = 1; // Log all MIDI notes. Other stuff is conditional. + break; + + case usbMIDI.NoteOff: + // turn gate off only when all notes are off + if (notes_on[midi_chan - 1] <= 0) + { + notes_on[midi_chan - 1] = 0; // just in case it becomes negative... + if (function[ch] == HEM_MIDI_GATE_OUT) { + outputs[ch] = 0; + log_this = 1; + } + } + break; + + case usbMIDI.ControlChange: // Modulation wheel or other CC + if (function[ch] == HEM_MIDI_CC_OUT) { + if (function_cc[ch] < 0) function_cc[ch] = data1; + + if (function_cc[ch] == data1) { + outputs[ch] = Proportion(data2, 127, HEMISPHERE_MAX_CV); + log_this = 1; + } + } + break; + + // TODO: consider adding support for AfterTouchPoly + case usbMIDI.AfterTouchChannel: + if (function[ch] == HEM_MIDI_AT_OUT) { + outputs[ch] = Proportion(data1, 127, HEMISPHERE_MAX_CV); + log_this = 1; + } + break; + + case usbMIDI.PitchBend: + if (function[ch] == HEM_MIDI_PB_OUT) { + int data = (data2 << 7) + data1 - 8192; + outputs[ch] = Proportion(data, 0x7fff, HEMISPHERE_3V_CV); + log_this = 1; + } + break; + + } + + if (log_this) UpdateLog(message, data1, data2); + } + } + + } MIDIState; + + // --- Soft IO --- + void Out(DAC_CHANNEL channel, int value) { + outputs[channel] = value; + } + void ClockOut(DAC_CHANNEL ch, const int pulselength = HEMISPHERE_CLOCK_TICKS * HS::trig_length) { + clock_countdown[ch] = pulselength; + outputs[ch] = PULSE_VOLTAGE * (12 << 7); + } + + // TODO: Hardware IO should be extracted + // --- Hard IO --- + void Load() { + clocked[0] = OC::DigitalInputs::clocked(); + clocked[1] = OC::DigitalInputs::clocked(); + clocked[2] = OC::DigitalInputs::clocked(); + clocked[3] = OC::DigitalInputs::clocked(); + gate_high[0] = OC::DigitalInputs::read_immediate(); + gate_high[1] = OC::DigitalInputs::read_immediate(); + gate_high[2] = OC::DigitalInputs::read_immediate(); + gate_high[3] = OC::DigitalInputs::read_immediate(); + + ForAllChannels(i) { + // Set CV inputs + inputs[i] = OC::ADC::raw_pitch_value(ADC_CHANNEL(i)); + if (abs(inputs[i] - last_cv[i]) > HEMISPHERE_CHANGE_THRESHOLD) { + changed_cv[i] = 1; + last_cv[i] = inputs[i]; + } else changed_cv[i] = 0; + + // Handle clock pulse timing + if (clock_countdown[i] > 0) { + if (--clock_countdown[i] == 0) outputs[i] = 0; + } + } + } + void Send() { + ForAllChannels(i) { + OC::DAC::set_pitch(DAC_CHANNEL(i), outputs[i], 0); + } + } + +} IOFrame; + +} // namespace HS diff --git a/software/o_c_REV/HSMIDI.h b/software/o_c_REV/HSMIDI.h index ec42cad39..2077bf8f1 100644 --- a/software/o_c_REV/HSMIDI.h +++ b/software/o_c_REV/HSMIDI.h @@ -44,6 +44,8 @@ #define HEM_MIDI_START usbMIDI.Start #define HEM_MIDI_STOP usbMIDI.Stop +#define HEM_MIDI_CLOCK_DIVISOR 12 + const char* const midi_note_numbers[128] = { "C-1","C#-1","D-1","D#-1","E-1","F-1","F#-1","G-1","G#-1","A-1","A#-1","B-1", "C0","C#0","D0","D#0","E0","F0","F#0","G0","G#0","A0","A#0","B0", @@ -63,6 +65,23 @@ const char* const midi_channels[17] = { " 9", "10", "11", "12", "13", "14", "15", "16" }; +// The functions available for each output +enum MIDIFunctions { + HEM_MIDI_NOOP = 0, + HEM_MIDI_NOTE_OUT, + HEM_MIDI_TRIG_OUT, + HEM_MIDI_GATE_OUT, + HEM_MIDI_VEL_OUT, + HEM_MIDI_CC_OUT, + HEM_MIDI_AT_OUT, + HEM_MIDI_PB_OUT, + HEM_MIDI_CLOCK_OUT, + HEM_MIDI_START_OUT, + + HEM_MIDI_MAX_FUNCTION = HEM_MIDI_START_OUT +}; +const char* const midi_fn_name[HEM_MIDI_MAX_FUNCTION + 1] = {"None", "Note#", "Trig", "Gate", "Veloc", "CC#", "Aft", "Bend", "Clock", "Start"}; + /* Hemisphere Suite Data Packing * diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index eeeb10dd1..4886c8796 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -25,12 +25,12 @@ //// Hemisphere Applet Base Class //////////////////////////////////////////////////////////////////////////////// -#ifndef HEMISPHEREAPPLET_H_ -#define HEMISPHEREAPPLET_H_ +#pragma once #include "HSicons.h" #include "HSClockManager.h" #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" +#include "util/util_math.h" #define LEFT_HEMISPHERE 0 #define RIGHT_HEMISPHERE 1 @@ -76,7 +76,8 @@ typedef int32_t simfloat; // Hemisphere-specific macros #define BottomAlign(h) (62 - h) -#define ForEachChannel(ch) for(int_fast8_t ch = 0; ch < 2; ch++) +#define ForEachChannel(ch) for(int_fast8_t ch = 0; ch < 2; ++ch) +#define ForAllChannels(ch) for(int_fast8_t ch = 0; ch < 4; ++ch) #define gfx_offset (hemisphere * 64) // Graphics offset, based on the side #define io_offset (hemisphere * 2) // Input/Output offset, based on the side @@ -94,6 +95,59 @@ typedef int32_t simfloat; #include "hemisphere_config.h" #include "braids_quantizer.h" +//////////////// Calculation methods +//////////////////////////////////////////////////////////////////////////////// + +/* Proportion method using simfloat, useful for calculating scaled values given + * a fractional value. + * + * Solves this: numerator ??? + * ----------- = ----------- + * denominator max + * + * For example, to convert a parameter with a range of 1 to 100 into value scaled + * to HEMISPHERE_MAX_CV, to be sent to the DAC: + * + * Out(ch, Proportion(value, 100, HEMISPHERE_MAX_CV)); + * + */ +int Proportion(int numerator, int denominator, int max_value) { + simfloat proportion = int2simfloat((int32_t)numerator) / (int32_t)denominator; + int scaled = simfloat2int(proportion * max_value); + return scaled; +} + +/* Proportion CV values into pixels for display purposes. + * + * Solves this: cv_value ??? + * ----------------- = ---------- + * HEMISPHERE_MAX_CV max_pixels + */ +int ProportionCV(int cv_value, int max_pixels) { + int prop = constrain(Proportion(cv_value, HEMISPHERE_MAX_INPUT_CV, max_pixels), 0, max_pixels); + return prop; +} + +// Specifies where data goes in flash storage for each selcted applet, and how big it is +typedef struct PackLocation { + size_t location; + size_t size; +} PackLocation; + +/* Add value to a 64-bit storage unit at the specified location */ +void Pack(uint64_t &data, PackLocation p, uint64_t value) { + data |= (value << p.location); +} + +/* Retrieve value from a 64-bit storage unit at the specified location and of the specified bit size */ +int Unpack(uint64_t data, PackLocation p) { + uint64_t mask = 1; + for (size_t i = 1; i < p.size; i++) mask |= (0x01 << i); + return (data >> p.location) & mask; +} + +#include "HSIOFrame.h" + namespace HS { typedef struct Applet { @@ -112,38 +166,22 @@ typedef struct Applet { Applet available_applets[] = HEMISPHERE_APPLETS; Applet clock_setup_applet = DECLARE_APPLET(9999, 0x01, ClockSetup); +static IOFrame frame; + int octave_max = 5; -braids::Quantizer quantizer[4]; +uint8_t modal_edit_mode = 2; // 0=old behavior, 1=modal editing, 2=modal with wraparound +static void CycleEditMode() { ++modal_edit_mode %= 3; } + } -// Specifies where data goes in flash storage for each selcted applet, and how big it is -typedef struct PackLocation { - size_t location; - size_t size; -} PackLocation; +using namespace HS; class HemisphereApplet { public: - static int inputs[4]; - static int outputs[4]; - static int outputs_smooth[4]; - static int clock_countdown[4]; - static int adc_lag_countdown[4]; // Time between a clock event and an ADC read event - static uint32_t last_clock[4]; // Tick number of the last clock observed by the child class - static uint32_t cycle_ticks[4]; // Number of ticks between last two clocks - static bool changed_cv[4]; // Has the input changed by more than 1/8 semitone since the last read? - static int last_cv[4]; // For change detection + //static HSIOFrame frame; static int cursor_countdown[2]; - static uint8_t trig_length; - static uint8_t modal_edit_mode; - - static void CycleEditMode() { - ++modal_edit_mode %= 3; - } - - virtual const char* applet_name(); // Maximum of 9 characters virtual void Start(); virtual void Controller(); @@ -155,11 +193,11 @@ class HemisphereApplet { // Initialize some things for startup ForEachChannel(ch) { - clock_countdown[io_offset + ch] = 0; - inputs[io_offset + ch] = 0; - outputs[io_offset + ch] = 0; - outputs_smooth[io_offset + ch] = 0; - adc_lag_countdown[io_offset + ch] = 0; + frame.clock_countdown[io_offset + ch] = 0; + frame.inputs[io_offset + ch] = 0; + frame.outputs[io_offset + ch] = 0; + frame.outputs_smooth[io_offset + ch] = 0; + frame.adc_lag_countdown[io_offset + ch] = 0; } help_active = 0; cursor_countdown[hemisphere] = HEMISPHERE_CURSOR_TICKS; @@ -182,23 +220,10 @@ class HemisphereApplet { } } - void BaseController(bool master_clock_on) { - master_clock_bus = (master_clock_on && hemisphere == RIGHT_HEMISPHERE); - ForEachChannel(ch) - { - // Set CV inputs - ADC_CHANNEL channel = (ADC_CHANNEL)(ch + io_offset); - inputs[channel] = OC::ADC::raw_pitch_value(channel); - if (abs(inputs[channel] - last_cv[channel]) > HEMISPHERE_CHANGE_THRESHOLD) { - changed_cv[channel] = 1; - last_cv[channel] = inputs[channel]; - } else changed_cv[channel] = 0; - - // Handle clock timing - if (clock_countdown[channel] > 0) { - if (--clock_countdown[channel] == 0) Out(ch, 0); - } - } + void BaseController(bool master_clock_on = false) { + // I moved the IO-related stuff to the parent HemisphereManager app. + // The IOFrame gets loaded before calling Controllers, and outputs are handled after. + // -NJM // Cursor countdowns. See CursorBlink(), ResetCursor(), gfxCursor() if (--cursor_countdown[hemisphere] < -HEMISPHERE_CURSOR_TICKS) cursor_countdown[hemisphere] = HEMISPHERE_CURSOR_TICKS; @@ -393,7 +418,7 @@ class HemisphereApplet { //////////////// Offset I/O methods //////////////////////////////////////////////////////////////////////////////// int In(int ch) { - return inputs[io_offset + ch]; + return frame.inputs[io_offset + ch]; } // Apply small center detent to input, so it reads zero before a threshold @@ -419,16 +444,13 @@ class HemisphereApplet { } void Out(int ch, int value, int octave = 0) { - DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); - OC::DAC::set_pitch(channel, value, octave); - outputs[channel] = value + (octave * (12 << 7)); + frame.Out( (DAC_CHANNEL)(ch + io_offset), value + (octave * (12 << 7))); } void SmoothedOut(int ch, int value, int kSmoothing) { DAC_CHANNEL channel = (DAC_CHANNEL)(ch + io_offset); - value = (outputs_smooth[channel] * (kSmoothing - 1) + value) / kSmoothing; - OC::DAC::set_pitch(channel, value, 0); - outputs[channel] = outputs_smooth[channel] = value; + value = (frame.outputs_smooth[channel] * (kSmoothing - 1) + value) / kSmoothing; + frame.outputs[channel] = frame.outputs_smooth[channel] = value; } /* @@ -442,60 +464,28 @@ class HemisphereApplet { ClockManager *clock_m = clock_m->get(); bool useTock = (!physical && clock_m->IsRunning()); - if (ch == 0) { // clock triggers - if (hemisphere == LEFT_HEMISPHERE) { - if (useTock && clock_m->GetMultiply(0) != 0) - clocked = clock_m->Tock(0); - else - clocked = OC::DigitalInputs::clocked(); - } else { // right side is special - if (useTock && clock_m->GetMultiply(2) != 0) - clocked = clock_m->Tock(2); - else if (master_clock_bus) // forwarding from left - clocked = OC::DigitalInputs::clocked(); - else - clocked = OC::DigitalInputs::clocked(); - } - } else if (ch == 1) { // TR2 and TR4 - if (hemisphere == LEFT_HEMISPHERE) { - if (useTock && clock_m->GetMultiply(1) != 0) - clocked = clock_m->Tock(1); - else - clocked = OC::DigitalInputs::clocked(); - } - else { - if (useTock && clock_m->GetMultiply(3) != 0) - clocked = clock_m->Tock(3); - else - clocked = OC::DigitalInputs::clocked(); - } - } + // clock triggers + if (useTock && clock_m->GetMultiply(ch + io_offset) != 0) + clocked = clock_m->Tock(ch + io_offset); + else + clocked = frame.clocked[ch + io_offset]; + // Try to eat a boop clocked = clocked || clock_m->Beep(io_offset + ch); if (clocked) { - cycle_ticks[io_offset + ch] = OC::CORE::ticks - last_clock[io_offset + ch]; - last_clock[io_offset + ch] = OC::CORE::ticks; + frame.cycle_ticks[io_offset + ch] = OC::CORE::ticks - frame.last_clock[io_offset + ch]; + frame.last_clock[io_offset + ch] = OC::CORE::ticks; } return clocked; } - void ClockOut(int ch, int ticks = HEMISPHERE_CLOCK_TICKS) { - clock_countdown[io_offset + ch] = ticks * trig_length; - Out(ch, 0, PULSE_VOLTAGE); + void ClockOut(const int ch, const int ticks = HEMISPHERE_CLOCK_TICKS) { + frame.ClockOut( (DAC_CHANNEL)(io_offset + ch), ticks * trig_length); } bool Gate(int ch) { - bool high = 0; - if (hemisphere == 0) { - if (ch == 0) high = OC::DigitalInputs::read_immediate(); - if (ch == 1) high = OC::DigitalInputs::read_immediate(); - } - if (hemisphere == 1) { - if (ch == 0) high = OC::DigitalInputs::read_immediate(); - if (ch == 1) high = OC::DigitalInputs::read_immediate(); - } - return high; + return frame.gate_high[ch + io_offset]; } void GateOut(int ch, bool high) { @@ -503,10 +493,10 @@ class HemisphereApplet { } // Buffered I/O functions - int ViewIn(int ch) {return inputs[io_offset + ch];} - int ViewOut(int ch) {return outputs[io_offset + ch];} - int ClockCycleTicks(int ch) {return cycle_ticks[io_offset + ch];} - bool Changed(int ch) {return changed_cv[io_offset + ch];} + int ViewIn(int ch) {return frame.inputs[io_offset + ch];} + int ViewOut(int ch) {return frame.outputs[io_offset + ch];} + int ClockCycleTicks(int ch) {return frame.cycle_ticks[io_offset + ch];} + bool Changed(int ch) {return frame.changed_cv[io_offset + ch];} protected: bool hemisphere; // Which hemisphere (0, 1) this applet uses @@ -521,50 +511,6 @@ class HemisphereApplet { applet_started = 0; } - //////////////// Calculation methods - //////////////////////////////////////////////////////////////////////////////// - - /* Proportion method using simfloat, useful for calculating scaled values given - * a fractional value. - * - * Solves this: numerator ??? - * ----------- = ----------- - * denominator max - * - * For example, to convert a parameter with a range of 1 to 100 into value scaled - * to HEMISPHERE_MAX_CV, to be sent to the DAC: - * - * Out(ch, Proportion(value, 100, HEMISPHERE_MAX_CV)); - * - */ - int Proportion(int numerator, int denominator, int max_value) { - simfloat proportion = int2simfloat((int32_t)numerator) / (int32_t)denominator; - int scaled = simfloat2int(proportion * max_value); - return scaled; - } - - /* Proportion CV values into pixels for display purposes. - * - * Solves this: cv_value ??? - * ----------------- = ---------- - * HEMISPHERE_MAX_CV max_pixels - */ - int ProportionCV(int cv_value, int max_pixels) { - int prop = constrain(Proportion(cv_value, HEMISPHERE_MAX_INPUT_CV, max_pixels), 0, max_pixels); - return prop; - } - - /* Add value to a 64-bit storage unit at the specified location */ - void Pack(uint64_t &data, PackLocation p, uint64_t value) { - data |= (value << p.location); - } - - /* Retrieve value from a 64-bit storage unit at the specified location and of the specified bit size */ - int Unpack(uint64_t data, PackLocation p) { - uint64_t mask = 1; - for (size_t i = 1; i < p.size; i++) mask |= (0x01 << i); - return (data >> p.location) & mask; - } /* ADC Lag: There is a small delay between when a digital input can be read and when an ADC can be * read. The ADC value lags behind a bit in time. So StartADCLag() and EndADCLag() are used to @@ -578,34 +524,18 @@ class HemisphereApplet { * } */ void StartADCLag(bool ch = 0) { - adc_lag_countdown[io_offset + ch] = HEMISPHERE_ADC_LAG; + frame.adc_lag_countdown[io_offset + ch] = HEMISPHERE_ADC_LAG; } bool EndOfADCLag(bool ch = 0) { - if (adc_lag_countdown[io_offset + ch] < 0) return false; - return (--adc_lag_countdown[io_offset + ch] == 0); + if (frame.adc_lag_countdown[io_offset + ch] < 0) return false; + return (--frame.adc_lag_countdown[io_offset + ch] == 0); } - /* Master Clock Forwarding is activated. This is updated with each ISR cycle by the Hemisphere Manager */ - bool MasterClockForwarded() {return master_clock_bus;} - private: - bool master_clock_bus; // Clock forwarding was on during the last ISR cycle bool applet_started; // Allow the app to maintain state during switching bool help_active; }; -uint8_t HemisphereApplet::modal_edit_mode = 2; // 0=old behavior, 1=modal editing, 2=modal with wraparound -uint8_t HemisphereApplet::trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS -int HemisphereApplet::inputs[4]; -int HemisphereApplet::outputs[4]; -int HemisphereApplet::outputs_smooth[4]; -int HemisphereApplet::clock_countdown[4]; -int HemisphereApplet::adc_lag_countdown[4]; -uint32_t HemisphereApplet::last_clock[4]; -uint32_t HemisphereApplet::cycle_ticks[4]; -bool HemisphereApplet::changed_cv[4]; -int HemisphereApplet::last_cv[4]; int HemisphereApplet::cursor_countdown[2]; -#endif // HEMISPHEREAPPLET_H_ From cbbc5af388663c82674eb214c47a907ec5d8e561 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 31 Aug 2023 01:50:54 -0400 Subject: [PATCH 286/417] MIDI In: Fix Pitch Bend scaling (+/-3V) --- software/o_c_REV/HSIOFrame.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HSIOFrame.h b/software/o_c_REV/HSIOFrame.h index 1b61b5f72..60188b286 100644 --- a/software/o_c_REV/HSIOFrame.h +++ b/software/o_c_REV/HSIOFrame.h @@ -175,7 +175,7 @@ typedef struct IOFrame { case usbMIDI.PitchBend: if (function[ch] == HEM_MIDI_PB_OUT) { int data = (data2 << 7) + data1 - 8192; - outputs[ch] = Proportion(data, 0x7fff, HEMISPHERE_3V_CV); + outputs[ch] = Proportion(data, 8192, HEMISPHERE_3V_CV); log_this = 1; } break; From 6955abed9b5eea7fb701a486b9ebf59ec1c04893 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 22 Aug 2023 17:21:20 -0400 Subject: [PATCH 287/417] ASR applet: fixes and simplification - index mod via CV is now continuous; so are the outputs. - RingBufferManager singleton refactored as simple struct --- software/o_c_REV/HEM_ASR.ino | 52 ++++++++-------- software/o_c_REV/HSRingBufferManager.h | 82 +++++++------------------- 2 files changed, 51 insertions(+), 83 deletions(-) diff --git a/software/o_c_REV/HEM_ASR.ino b/software/o_c_REV/HEM_ASR.ino index 3bdce2304..3e7a995e5 100644 --- a/software/o_c_REV/HEM_ASR.ino +++ b/software/o_c_REV/HEM_ASR.ino @@ -31,7 +31,7 @@ public: void Start() { scale = OC::Scales::SCALE_SEMI; - buffer_m->SetIndex(1); + buffer_m.SetIndex(1); ForEachChannel(ch) { quantizer[ch] = GetQuantizer(ch); quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone @@ -39,23 +39,29 @@ public: } void Controller() { - buffer_m->Register(hemisphere); + buffer_m.Register(hemisphere); + bool secondary = buffer_m.IsLinked() && hemisphere == RIGHT_HEMISPHERE; - if (Clock(0)) StartADCLag(); + if (Clock(0) && !secondary) { + StartADCLag(); + } + + if (EndOfADCLag()) { + // advance the buffer first, then write the new value + buffer_m.Advance(); - if (EndOfADCLag() || buffer_m->Ready(hemisphere)) { - if (!Gate(1) && !buffer_m->IsLinked(hemisphere)) { + if (!Gate(1)) { int cv = In(0); - buffer_m->WriteValueToBuffer(cv, hemisphere); - } - index_mod = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, 32); - ForEachChannel(ch) - { - int cv = buffer_m->ReadNextValue(ch, hemisphere, index_mod); - int quantized = quantizer[ch]->Process(cv, 0, 0); - Out(ch, quantized); + buffer_m.WriteValue(cv); } - buffer_m->Advance(); + } + + index_mod = Proportion(DetentedIn(1), HEMISPHERE_MAX_INPUT_CV, 32); + ForEachChannel(ch) + { + int cv = buffer_m.ReadValue(ch + secondary*2, index_mod); + int quantized = quantizer[ch]->Process(cv, 0, 0); + Out(ch, quantized); } } @@ -76,8 +82,8 @@ public: } if (cursor == 0) { // Index - byte ix = buffer_m->GetIndex(); - buffer_m->SetIndex(ix + direction); + uint8_t ix = buffer_m.GetIndex(); + buffer_m.SetIndex(ix + direction); } if (cursor == 1) { // Scale selection scale += direction; @@ -90,14 +96,14 @@ public: uint64_t OnDataRequest() { uint64_t data = 0; - byte ix = buffer_m->GetIndex(); + uint8_t ix = buffer_m.GetIndex(); Pack(data, PackLocation {0,8}, ix); Pack(data, PackLocation {8,8}, scale); return data; } void OnDataReceive(uint64_t data) { - buffer_m->SetIndex(Unpack(data, PackLocation {0,8})); + buffer_m.SetIndex(Unpack(data, PackLocation {0,8})); scale = Unpack(data, PackLocation {8,8}); } @@ -113,17 +119,17 @@ protected: private: int cursor; - RingBufferManager *buffer_m = buffer_m->get(); + // HS::RingBufferManager *buffer_m = buffer_m->get(); braids::Quantizer* quantizer[2]; int scale; int index_mod; // Effect of modulation void DrawInterface() { // Show Link icon if linked with another ASR - if (buffer_m->IsLinked(hemisphere)) gfxIcon(56, 1, LINK_ICON); + if (buffer_m.IsLinked() && hemisphere == RIGHT_HEMISPHERE) gfxIcon(56, 1, LINK_ICON); // Index (shared between all instances of ASR) - byte ix = buffer_m->GetIndex() + index_mod; + uint8_t ix = buffer_m.GetIndex() + index_mod; gfxPrint(1, 15, "Index: "); gfxPrint(pad(100, ix), ix); if (index_mod != 0) gfxIcon(54, 26, CV_ICON); @@ -138,9 +144,9 @@ private: } void DrawData() { - for (byte x = 0; x < 64; x++) + for (uint8_t x = 0; x < 64; ++x) { - int y = buffer_m->GetYAt(x, hemisphere) + 40; + int y = buffer_m.GetYAt(x, hemisphere) + 40; gfxPixel(x, y); } } diff --git a/software/o_c_REV/HSRingBufferManager.h b/software/o_c_REV/HSRingBufferManager.h index 455f69ad7..7a4527f4a 100644 --- a/software/o_c_REV/HSRingBufferManager.h +++ b/software/o_c_REV/HSRingBufferManager.h @@ -18,86 +18,48 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -class RingBufferManager { - static RingBufferManager *instance; +struct RingBufferManager { int buffer[256]; - byte position = 0; - byte index = 0; - bool ready = 0; // Allows a second instance to know that a first instance has been clocked + uint8_t position = 0; + uint8_t index = 0; uint32_t registered[2]; - uint32_t last_advance_tick = 0; // To prevent double-advancing - - RingBufferManager() { - for (byte i = 0; i < 255; i++) buffer[i] = 0; - registered[LEFT_HEMISPHERE] = 0; - registered[RIGHT_HEMISPHERE] = 0; - } - -public: - static RingBufferManager *get() { - if (!instance) instance = new RingBufferManager; - return instance; - } - void Register(bool hemisphere) { registered[hemisphere] = OC::CORE::ticks; } - bool IsLinked(bool hemisphere) { - uint32_t t = OC::CORE::ticks; - return (hemisphere == RIGHT_HEMISPHERE - && (t - registered[LEFT_HEMISPHERE] < 160) - && (t - registered[RIGHT_HEMISPHERE] < 160)); - } - - // Allows a second instance to know that a first instance has been clocked - bool Ready(bool hemisphere) { - bool r = 0; - if (IsLinked(hemisphere)) { - r = ready; - ready = 0; - } - return r; + bool IsLinked() { + const uint32_t t = OC::CORE::ticks; + return ( (t - registered[LEFT_HEMISPHERE] < 160) + && (t - registered[RIGHT_HEMISPHERE] < 160)); } - void SetIndex(byte ix) {index = ix;} - - byte GetIndex() {return index;} + inline void SetIndex(uint8_t ix) {index = ix;} - byte GetPosition() {return position;} + inline uint8_t GetIndex() {return index;} - void WriteValueToBuffer(int cv, bool hemisphere) { - if (!IsLinked(hemisphere)) { - buffer[position] = cv; - } + inline void WriteValue(int cv) { + buffer[position] = cv; } - int ReadNextValue(byte output, bool hemisphere, int index_mod = 0) { - if (IsLinked(hemisphere)) output += 2; - byte ix = position - (index * output) - index_mod; + int ReadValue(uint8_t output, int index_mod = 0) { + uint8_t ix = position - (index * output) - index_mod; int cv = buffer[ix]; return cv; } - void Advance() { - if (OC::CORE::ticks > last_advance_tick) { - ++position; // No need to check range; 256 positions and an 8-bit counter - last_advance_tick = OC::CORE::ticks; - ready = 1; - } + inline void Advance() { + ++position; // No need to check range; 256 positions and an 8-bit counter } - int GetYAt(byte x, bool hemisphere) { - // If there are two instances, and this is the right hemisphere, show the - // second 128 bytes. Otherwise, just show the first 128. - byte offset = (IsLinked(hemisphere)) ? 64 : 0; - byte ix = (x + offset + position + 64); - int cv = buffer[ix] + HEMISPHERE_MAX_CV; // Force this positive - int y = (((cv << 7) / (HEMISPHERE_MAX_CV * 2)) * 23) >> 7; + int GetYAt(const uint8_t &x, bool hemisphere) { + // If there are two instances, and this is the left hemisphere, start further back + uint8_t offset = (IsLinked() && hemisphere == LEFT_HEMISPHERE) ? -128 : -64; + uint8_t ix = (x + offset + position); + int cv = buffer[ix] + HEMISPHERE_MAX_INPUT_CV; // Force this positive + int y = (((cv << 7) / (HEMISPHERE_MAX_INPUT_CV * 2)) * 23) >> 7; y = constrain(y, 0, 23); return 23 - y; } -}; +} buffer_m; -RingBufferManager *RingBufferManager::instance = 0; From 1a6fb2d994d328c84eb561a983a360197e5679b0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 17 Sep 2023 23:30:48 -0400 Subject: [PATCH 288/417] ClockSetup: remove hardcoded TR4 reset ...because it interferes with the underlying applet. MIDI reset could still be a thing. --- software/o_c_REV/HEM_ClockSetup.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 0bf6e21ec..b2a65fd94 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -46,7 +46,6 @@ public: // The ClockSetup controller handles MIDI Clock and Transport Start/Stop void Controller() { bool clock_sync = OC::DigitalInputs::clocked(); - bool reset = OC::DigitalInputs::clocked(); // MIDI Clock is filtered to 2 PPQN if (frame.MIDIState.clock_q) { @@ -71,7 +70,7 @@ public: // Advance internal clock, sync to external clock / reset if (clock_m->IsRunning()) - clock_m->SyncTrig( clock_sync, reset ); + clock_m->SyncTrig( clock_sync ); // ------------ // if (clock_m->IsRunning() && clock_m->MIDITock()) usbMIDI.sendRealTime(usbMIDI.Clock); From 389790d38ed1bdfa5dc53c45f9cbaae737a432e5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 21 Sep 2023 02:48:09 -0400 Subject: [PATCH 289/417] Revert button dual-press detection method for Clock Setup in HEM Using event.mask turned out to be kinda flaky. Sometimes it cancels when you let go. I suspect the debouncing of two pins at the same time creates a rare edge case that I can't find. --- software/o_c_REV/APP_HEMISPHERE.ino | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index c59aac338..78729de74 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -393,8 +393,10 @@ public: // -- button down if (down) { - if (event.mask == (OC::CONTROL_BUTTON_UP | OC::CONTROL_BUTTON_DOWN)) // dual press for Clock Setup - { + // dual press for Clock Setup... + // a less favorable approach was to check if both buttons are currently pressed. For some reason, this sometimes cancels clock_setup when you let go. + //if (event.mask == (OC::CONTROL_BUTTON_UP | OC::CONTROL_BUTTON_DOWN)) + if (OC::CORE::ticks - click_tick < HEMISPHERE_SIM_CLICK_TIME && hemisphere != first_click) { clock_setup = 1; SetHelpScreen(-1); select_mode = -1; From 516d04f77609c75ff701115281f08dd6d65e9888 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 24 Sep 2023 00:56:32 -0400 Subject: [PATCH 290/417] Remove Calibr8 applet; it was mainly a POC for Calibr8or --- software/o_c_REV/HEM_Calibr8.ino | 190 --------------------------- software/o_c_REV/hemisphere_config.h | 1 - 2 files changed, 191 deletions(-) delete mode 100644 software/o_c_REV/HEM_Calibr8.ino diff --git a/software/o_c_REV/HEM_Calibr8.ino b/software/o_c_REV/HEM_Calibr8.ino deleted file mode 100644 index e09f1aeb0..000000000 --- a/software/o_c_REV/HEM_Calibr8.ino +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) 2023, Nicholas J. Michalek -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -#define CAL8_PRECISION 10000 - -class Calibr8 : public HemisphereApplet { -public: - enum CalCursor { - SCALEFACTOR_A, - TRANS_A, - OFFSET_A, - SCALEFACTOR_B, - TRANS_B, - OFFSET_B, - - MAX_CURSOR = OFFSET_B - }; - - const char* applet_name() { - return "Calibr8"; - } - - void Start() { - clocked_mode = false; - AllowRestart(); - } - - void Controller() { - bool clocked = Clock(0); - if (clocked) clocked_mode = true; - - ForEachChannel(ch) { - uint8_t input_note = MIDIQuantizer::NoteNumber(In(ch), 0); - - // clocked transpose - if (!clocked_mode || clocked) - transpose_active[ch] = transpose[ch]; - - input_note += transpose_active[ch]; - - int output_cv = MIDIQuantizer::CV(input_note) * (CAL8_PRECISION + scale_factor[ch]) / CAL8_PRECISION; - output_cv += offset[ch]; - - Out(ch, output_cv); - } - } - - void View() { - gfxHeader(applet_name()); - DrawInterface(); - } - - void OnButtonPress() { - CursorAction(cursor, MAX_CURSOR); - } - - void OnEncoderMove(int direction) { - if (!EditMode()) { - MoveCursor(cursor, direction, MAX_CURSOR); - return; - } - - bool ch = (cursor > OFFSET_A); - switch (cursor) { - case OFFSET_A: - case OFFSET_B: - offset[ch] = constrain(offset[ch] + direction, -100, 100); - break; - - case SCALEFACTOR_A: - case SCALEFACTOR_B: - scale_factor[ch] = constrain(scale_factor[ch] + direction, -500, 500); - break; - - case TRANS_A: - case TRANS_B: - transpose[ch] = constrain(transpose[ch] + direction, -36, 60); - break; - - default: break; - } - } - - uint64_t OnDataRequest() { - uint64_t data = 0; - Pack(data, PackLocation { 0,10}, scale_factor[0] + 500); - Pack(data, PackLocation {10,10}, scale_factor[1] + 500); - Pack(data, PackLocation {20, 8}, offset[0] + 100); - Pack(data, PackLocation {28, 8}, offset[1] + 100); - Pack(data, PackLocation {36, 7}, transpose[0] + 36); - Pack(data, PackLocation {43, 7}, transpose[1] + 36); - return data; - } - - void OnDataReceive(uint64_t data) { - scale_factor[0] = Unpack(data, PackLocation { 0,10}) - 500; - scale_factor[1] = Unpack(data, PackLocation {10,10}) - 500; - offset[0] = Unpack(data, PackLocation {20, 8}) - 100; - offset[1] = Unpack(data, PackLocation {28, 8}) - 100; - transpose[0] = Unpack(data, PackLocation {36, 7}) - 36; - transpose[1] = Unpack(data, PackLocation {43, 7}) - 36; - } - -protected: - void SetHelp() { - // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Clock"; - help[HEMISPHERE_HELP_CVS] = "1,2=Pitch inputs"; - help[HEMISPHERE_HELP_OUTS] = "A,B=Pitch outputs"; - help[HEMISPHERE_HELP_ENCODER] = "Scale/Offset/Trans"; - // "------------------" <-- Size Guide - } - -private: - int cursor; - bool clocked_mode = false; - int scale_factor[2] = {0,0}; // precision of 0.01% as an offset from 100% - int offset[2] = {0,0}; // fine-tuning offset - int transpose[2] = {0,0}; // in semitones - int transpose_active[2] = {0,0}; // held value while waiting for trigger - - void DrawInterface() { - ForEachChannel(ch) { - int y = 14 + ch*21; - gfxPrint(0, y, ch?"B:":"A:"); - - int whole = (scale_factor[ch] + CAL8_PRECISION) / 100; - int decimal = (scale_factor[ch] + CAL8_PRECISION) % 100; - gfxPrint(12 + pad(100, whole), y, whole); - gfxPrint("."); - if (decimal < 10) gfxPrint("0"); - gfxPrint(decimal); - gfxPrint("%"); - - // second line - y += 10; - gfxIcon(0, y, BEND_ICON); - gfxPrint(8, y, transpose[ch]); - gfxIcon(32, y, UP_DOWN_ICON); - gfxPrint(40, y, offset[ch]); - } - gfxLine(0, 33, 63, 33); // gotta keep em separated - - bool ch = (cursor > OFFSET_A); - int param = (cursor % 3); - if (param == 0) // Scaling - gfxCursor(12, 22 + ch*21, 40); - else // Transpose or Fine Tune - gfxCursor(8 + (param-1)*32, 32 + ch*21, 20); - - gfxSkyline(); - } -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// Hemisphere Applet Functions -/// -/// Once you run the find-and-replace to make these refer to Calibr8, -/// it's usually not necessary to do anything with these functions. You -/// should prefer to handle things in the HemisphereApplet child class -/// above. -//////////////////////////////////////////////////////////////////////////////// -Calibr8 Calibr8_instance[2]; - -void Calibr8_Start(bool hemisphere) {Calibr8_instance[hemisphere].BaseStart(hemisphere);} -void Calibr8_Controller(bool hemisphere, bool forwarding) {Calibr8_instance[hemisphere].BaseController(forwarding);} -void Calibr8_View(bool hemisphere) {Calibr8_instance[hemisphere].BaseView();} -void Calibr8_OnButtonPress(bool hemisphere) {Calibr8_instance[hemisphere].OnButtonPress();} -void Calibr8_OnEncoderMove(bool hemisphere, int direction) {Calibr8_instance[hemisphere].OnEncoderMove(direction);} -void Calibr8_ToggleHelpScreen(bool hemisphere) {Calibr8_instance[hemisphere].HelpScreen();} -uint64_t Calibr8_OnDataRequest(bool hemisphere) {return Calibr8_instance[hemisphere].OnDataRequest();} -void Calibr8_OnDataReceive(bool hemisphere, uint64_t data) {Calibr8_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 1ba6fbaa9..64bcc48cc 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -24,7 +24,6 @@ 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), \ From cc32d683153c78e6c4a02d3236ed958311f403fa Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 20 Sep 2023 02:42:56 -0400 Subject: [PATCH 291/417] New App: Scenes Simple snapshot / macro switcher, with features similar to Traffic by Jasmine & Olive Trees, and Mutable Instruments Frames --- software/o_c_REV/APP_SCENES.ino | 583 +++++++++++++++++++++++++++++++ software/o_c_REV/HSApplication.h | 15 + software/o_c_REV/OC_apps.ino | 4 + software/o_c_REV/platformio.ini | 1 + 4 files changed, 603 insertions(+) create mode 100644 software/o_c_REV/APP_SCENES.ino diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino new file mode 100644 index 000000000..4b502b3bd --- /dev/null +++ b/software/o_c_REV/APP_SCENES.ino @@ -0,0 +1,583 @@ +// Copyright (c) 2023, Nicholas J. Michalek +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +/* + * Based loosely on the Traffic module from Jasmine & Olive Trees + * Also similar to Mutable Instruments Frames + */ + +#ifdef ENABLE_APP_SCENES + +#include "HSApplication.h" +#include "HSMIDI.h" +#include "util/util_settings.h" +#include "src/drivers/FreqMeasure/OC_FreqMeasure.h" +#include "HemisphereApplet.h" + +static const int NR_OF_SCENE_PRESETS = 4; +static const int NR_OF_SCENE_CHANNELS = 4; + +#define SCENE_MAX_VAL HEMISPHERE_MAX_CV +#define SCENE_MIN_VAL HEMISPHERE_MIN_CV + +struct Scene { + int16_t values[4]; + + void Init() { + for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) values[i] = 0; + } +}; + +// Preset storage spec +enum ScenesSettings { + SCENE_FLAGS, // 8 bits + + // 16 bits each + SCENE1_VALUE_A, + SCENE1_VALUE_B, + SCENE1_VALUE_C, + SCENE1_VALUE_D, + + SCENE2_VALUE_A, + SCENE2_VALUE_B, + SCENE2_VALUE_C, + SCENE2_VALUE_D, + + SCENE3_VALUE_A, + SCENE3_VALUE_B, + SCENE3_VALUE_C, + SCENE3_VALUE_D, + + SCENE4_VALUE_A, + SCENE4_VALUE_B, + SCENE4_VALUE_C, + SCENE4_VALUE_D, + + SCENES_SETTING_LAST +}; + +class ScenesAppPreset : public settings::SettingsBase { +public: + + bool is_valid() { + return (values_[SCENE_FLAGS] & 0x01); + } + bool load_preset(Scene *s) { + if (!is_valid()) return false; // don't try to load a blank + + int ix = 1; // skip validity flag + for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { + // somestuff = values_[ix++]; + s[ch].values[0] = values_[ix++]; + s[ch].values[1] = values_[ix++]; + s[ch].values[2] = values_[ix++]; + s[ch].values[3] = values_[ix++]; + } + + return true; + } + void save_preset(Scene *s) { + int ix = 0; + + values_[ix++] = 1; // validity flag + + for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { + values_[ix++] = s[ch].values[0]; + values_[ix++] = s[ch].values[1]; + values_[ix++] = s[ch].values[2]; + values_[ix++] = s[ch].values[3]; + } + } + +}; + +ScenesAppPreset scene_presets[NR_OF_SCENE_PRESETS]; +static const char * scene_preset_id[] = { "A", "B", "C", "D" }; + +class ScenesApp : public HSApplication { +public: + + void Start() { + // make sure to turn this off, just in case + FreqMeasure.end(); + OC::DigitalInputs::reInit(); + } + + void ClearPreset() { + for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { + scene[ch].Init(); + } + //SavePreset(); + } + void LoadPreset() { + bool success = scene_presets[index].load_preset(scene); + if (success) { + for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { + } + preset_modified = 0; + } + else + ClearPreset(); + } + void SavePreset() { + scene_presets[index].save_preset(scene); + preset_modified = 0; + } + + void Resume() { + } + + void Controller() { + const int OCTAVE = (12 << 7); + // -- core processing -- + + // explicit gate/trigger priority right here: + if (Gate(0)) // TR1 takes precedence + trig_chan = 0; + else if (Gate(1)) // TR2 + trig_chan = 1; + else if (Gate(2)) // TR3 + trig_chan = 2; + else if (Gate(3)) // TR4 - TODO: aux trigger modes, random, etc. + trig_chan = 3; + // else, it's unchanged + + bool scene4seq = (In(3) > 2 * OCTAVE); // gate at CV4 + if (scene4seq) { + if (!Sequence.active) Sequence.Generate(); + if (Clock(3)) Sequence.Advance(); + } else { + Sequence.active = 0; + } + + // smooth interpolation offset, starting from last triggered scene + if (DetentedIn(0)) { + int cv = In(0); + int direction = (cv < 0) ? -1 : 1; + int volt = cv / OCTAVE; + int partial = abs(cv % OCTAVE); + + // for display cursor - scaled to pixels, 32px per volt + smooth_offset = cv * 32 / OCTAVE; + + int first = (trig_chan + volt + NR_OF_SCENE_CHANNELS) % NR_OF_SCENE_CHANNELS; + int second = (first + direction + NR_OF_SCENE_CHANNELS) % NR_OF_SCENE_CHANNELS; + + for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { + int16_t v1 = scene[first].values[i]; + int16_t v2 = scene[second].values[i]; + + // the sequence will determine which other value is blended in for Scene 4 + if (scene4seq) { + if (first == 3) + v1 = scene[Sequence.Get(i) / 4].values[Sequence.Get(i) % 4]; + if (second == 3) + v2 = scene[Sequence.Get(i) / 4].values[Sequence.Get(i) % 4]; + } + + // a weighted average of the two chosen scene values + active_scene.values[i] = ( v1 * (OCTAVE - partial) + v2 * partial) / OCTAVE; + } + } else if (scene4seq && trig_chan == 3) { // looped sequence for TR4 + for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { + active_scene.values[i] = scene[ Sequence.Get(i) / 4 ].values[ Sequence.Get(i) % 4 ]; + } + smooth_offset = 0; + } else { // a simple scene copy will suffice + for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { + active_scene.values[i] = scene[trig_chan].values[i]; // copy-on-assign for structs? + } + smooth_offset = 0; + } + + // set outputs + for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { + Out(ch, active_scene.values[ch]); + } + + // encoder deceleration + if (left_accel > 16) --left_accel; + else left_accel = 16; // just in case lol + + if (right_accel > 16) --right_accel; + else right_accel = 16; + } + + void View() { + gfxHeader("Scenes"); + + if (preset_select) { + gfxPrint(70, 1, "- Presets"); + DrawPresetSelector(); + } else { + gfxPos(110, 1); + if (preset_modified) gfxPrint("*"); + if (scene_presets[index].is_valid()) gfxPrint(scene_preset_id[index]); + + DrawInterface(); + } + } + + void Screensaver() { + gfxDottedLine(0, 32, 127, 32); // horizontal baseline + for (int ch = 0; ch < 4; ++ch) + { + if (trigger_flash[ch] > 0) gfxIcon(11 + 32*ch, 0, CLOCK_ICON); + + // input + int height = ProportionCV(ViewIn(ch), 32); + int y = constrain(32 - height, 0, 32); + gfxFrame(3 + (32 * ch), y, 6, abs(height)); + + // output + height = ProportionCV(ViewOut(ch), 32); + y = constrain(32 - height, 0, 32); + gfxInvert(11 + (32 * ch), y, 12, abs(height)); + + gfxLine(32 * ch, 0, 32*ch, 63); // vertical divider, left side + } + gfxLine(127, 0, 127, 63); // vertical line, right side + } + + ///////////////////////////////////////////////////////////////// + // Control handlers + ///////////////////////////////////////////////////////////////// + void OnLeftButtonPress() { + // Toggle between A or B editing + // also doubles as Load or Save for preset select + edit_mode_left = !edit_mode_left; + + // prevent saving to the (clear) slot + if (edit_mode_left && preset_select == 5) preset_select = 4; + } + + void OnLeftButtonLongPress() { + if (preset_select) return; + } + + void OnRightButtonPress() { + if (preset_select) { + // special case to clear values + if (!edit_mode_left && preset_select == NR_OF_SCENE_PRESETS + 1) { + ClearPreset(); + preset_modified = 1; + } + else { + index = preset_select - 1; + if (edit_mode_left) SavePreset(); + else LoadPreset(); + } + + preset_select = 0; + return; + } + + // Toggle between C or D editing + edit_mode_right = !edit_mode_right; + } + + void OnButtonDown(const UI::Event &event) { + // check for clock setup secret combo (dual press) + if ( event.control == OC::CONTROL_BUTTON_DOWN || event.control == OC::CONTROL_BUTTON_UP) + UpOrDownButtonPress(event.control == OC::CONTROL_BUTTON_UP); + } + + void UpOrDownButtonPress(bool up) { + if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME && up != first_click) { + // show clock setup if both buttons pressed quickly + click_tick = 0; + } else { + click_tick = OC::CORE::ticks; + first_click = up; + } + } + + void SwitchChannel(bool up) { + if (!preset_select) { + sel_chan += (up? 1 : -1) + NR_OF_SCENE_CHANNELS; + sel_chan %= NR_OF_SCENE_CHANNELS; + } + + if (click_tick) { + // always cancel preset select on single click + preset_select = 0; + } + } + + void OnDownButtonLongPress() { + // show preset screen, select last loaded + preset_select = 1 + index; + } + + // Left encoder: Edit A or B on current scene + void OnLeftEncoderMove(int direction) { + if (preset_select) { + edit_mode_left = (direction>0); + // prevent saving to the (clear) slot + if (edit_mode_left && preset_select == 5) preset_select = 4; + return; + } + + preset_modified = 1; + + int idx = 0 + edit_mode_left; + scene[sel_chan].values[idx] += direction * left_accel; + scene[sel_chan].values[idx] = constrain( scene[sel_chan].values[idx], SCENE_MIN_VAL, SCENE_MAX_VAL ); + + left_accel <<= 3; + if (left_accel > 256) left_accel = 256; + } + + // Right encoder: Edit C or D on current scene + void OnRightEncoderMove(int direction) { + if (preset_select) { + preset_select = constrain(preset_select + direction, 1, NR_OF_SCENE_PRESETS + (1-edit_mode_left)); + return; + } + + preset_modified = 1; + + int idx = 2 + edit_mode_right; + scene[sel_chan].values[idx] += direction * right_accel; + scene[sel_chan].values[idx] = constrain( scene[sel_chan].values[idx], SCENE_MIN_VAL, SCENE_MAX_VAL ); + + right_accel <<= 4; + if (right_accel > 512) right_accel = 512; + } + +private: + static const int SEQ_LENGTH = 16; + + int index = 0; + + int sel_chan = 0; + int trig_chan = 0; + int preset_select = 0; // both a flag and an index + bool preset_modified = 0; + bool edit_mode_left = 0; + bool edit_mode_right = 0; + + struct { + bool active = 0; + uint64_t sequence[4]; // four 16-step sequences of 4-bit values + uint8_t step; + + void Generate() { + for (int i = 0; i < NR_OF_SCENE_PRESETS; ++i) { + sequence[i] = random() | (uint64_t(random()) << 32); + } + step = 0; + active = 1; + } + void Advance() { + ++step %= SEQ_LENGTH; + } + const uint8_t Get(int i) { + return (uint8_t)( (sequence[i] >> (step * 4)) & 0x0F ); // 4-bit value, 0 to 15 + } + } Sequence; + + uint32_t click_tick = 0; + bool first_click = 0; + + uint16_t left_accel = 16; + uint16_t right_accel = 16; + + int trigger_flash[NR_OF_SCENE_CHANNELS]; + int smooth_offset = 0; // -128 to 128, for display + + Scene scene[NR_OF_SCENE_CHANNELS]; + Scene active_scene; + + void DrawPresetSelector() { + // index is the currently loaded preset (0-3) + // preset_select is current selection (1-4, 5=clear) + int y = 5 + 10*preset_select; + gfxPrint(25, y, edit_mode_left ? "Save" : "Load"); + gfxIcon(50, y, RIGHT_ICON); + + for (int i = 0; i < NR_OF_SCENE_PRESETS; ++i) { + gfxPrint(60, 15 + i*10, scene_preset_id[i]); + if (!scene_presets[i].is_valid()) + gfxPrint(" (empty)"); + else if (i == index) + gfxPrint(" *"); + } + if (!edit_mode_left) + gfxPrint(60, 55, "[CLEAR]"); + } + + void DrawInterface() { + /* + gfxPrint(2, 15, "Scene "); + gfxPrint(sel_chan + 1); + */ + for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { + gfxPrint(i*32 + 13, 14, i+1); + } + // active scene indicator + uint8_t x = (12 + trig_chan*32 + smooth_offset + 128) % 128; + gfxInvert(x, 13, 9, 10); + + // edit pointer + gfxIcon(sel_chan*32 + 13, 25, UP_ICON); + + gfxPrint(8, 35, "A: "); + gfxPrintVoltage(scene[sel_chan].values[0]); + gfxPrint(8, 45, "B: "); + gfxPrintVoltage(scene[sel_chan].values[1]); + + gfxIcon(0, 35 + 10*edit_mode_left, RIGHT_ICON); + + gfxPrint(72, 35, "C: "); + gfxPrintVoltage(scene[sel_chan].values[2]); + gfxPrint(72, 45, "D: "); + gfxPrintVoltage(scene[sel_chan].values[3]); + + gfxIcon(64, 35 + 10*edit_mode_right, RIGHT_ICON); + } +}; + +// TOTAL EEPROM SIZE: ?? +SETTINGS_DECLARE(ScenesAppPreset, SCENES_SETTING_LAST) { + {0, 0, 255, "Flags", NULL, settings::STORAGE_TYPE_U8}, + + {0, 0, 65535, "Scene1ValA", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene1ValB", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene1ValC", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene1ValD", NULL, settings::STORAGE_TYPE_U16}, + + {0, 0, 65535, "Scene2ValA", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene2ValB", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene2ValC", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene2ValD", NULL, settings::STORAGE_TYPE_U16}, + + {0, 0, 65535, "Scene3ValA", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene3ValB", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene3ValC", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene3ValD", NULL, settings::STORAGE_TYPE_U16}, + + {0, 0, 65535, "Scene4ValA", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene4ValB", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene4ValC", NULL, settings::STORAGE_TYPE_U16}, + {0, 0, 65535, "Scene4ValD", NULL, settings::STORAGE_TYPE_U16}, +}; + + +ScenesApp ScenesApp_instance; + +// App stubs +void ScenesApp_init() { ScenesApp_instance.BaseStart(); } + +size_t ScenesApp_storageSize() { + return ScenesAppPreset::storageSize() * NR_OF_SCENE_PRESETS; +} + +size_t ScenesApp_save(void *storage) { + size_t used = 0; + for (int i = 0; i < 4; ++i) { + used += scene_presets[i].Save(static_cast(storage) + used); + } + return used; +} + +size_t ScenesApp_restore(const void *storage) { + size_t used = 0; + for (int i = 0; i < 4; ++i) { + used += scene_presets[i].Restore(static_cast(storage) + used); + } + ScenesApp_instance.LoadPreset(); + return used; +} + +void ScenesApp_isr() { return ScenesApp_instance.BaseController(); } + +void ScenesApp_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + ScenesApp_instance.Resume(); + break; + + // The idea is to auto-save when the screen times out... + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + // TODO: initiate actual EEPROM save + // app_data_save(); + break; + + default: break; + } +} + +void ScenesApp_loop() {} // Deprecated + +void ScenesApp_menu() { ScenesApp_instance.BaseView(); } + +void ScenesApp_screensaver() { + ScenesApp_instance.Screensaver(); +} + +void ScenesApp_handleButtonEvent(const UI::Event &event) { + // For left encoder, handle press and long press + // For right encoder, only handle press (long press is reserved) + // For up button, handle only press (long press is reserved) + // For down button, handle press and long press + switch (event.type) { + case UI::EVENT_BUTTON_DOWN: + ScenesApp_instance.OnButtonDown(event); + + break; + case UI::EVENT_BUTTON_PRESS: { + switch (event.control) { + case OC::CONTROL_BUTTON_L: + ScenesApp_instance.OnLeftButtonPress(); + break; + case OC::CONTROL_BUTTON_R: + ScenesApp_instance.OnRightButtonPress(); + break; + case OC::CONTROL_BUTTON_DOWN: + case OC::CONTROL_BUTTON_UP: + ScenesApp_instance.SwitchChannel(event.control == OC::CONTROL_BUTTON_DOWN); + break; + default: break; + } + } break; + case UI::EVENT_BUTTON_LONG_PRESS: + if (event.control == OC::CONTROL_BUTTON_L) { + ScenesApp_instance.OnLeftButtonLongPress(); + } + if (event.control == OC::CONTROL_BUTTON_DOWN) { + ScenesApp_instance.OnDownButtonLongPress(); + } + break; + + default: break; + } +} + +void ScenesApp_handleEncoderEvent(const UI::Event &event) { + // Left encoder turned + if (event.control == OC::CONTROL_ENCODER_L) ScenesApp_instance.OnLeftEncoderMove(event.value); + + // Right encoder turned + if (event.control == OC::CONTROL_ENCODER_R) ScenesApp_instance.OnRightEncoderMove(event.value); +} + +#endif // ENABLE_APP_SCENES diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index a00f6bd35..671530c4b 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -212,6 +212,21 @@ class HSApplication { graphics.print(num); } + /* Convert CV value to voltage level and print to two decimal places */ + void gfxPrintVoltage(int cv) { + int v = (cv * 100) / (12 << 7); + bool neg = v < 0 ? 1 : 0; + if (v < 0) v = -v; + int wv = v / 100; // whole volts + int dv = v - (wv * 100); // decimal + gfxPrint(neg ? "-" : "+"); + gfxPrint(wv); + gfxPrint("."); + if (dv < 10) gfxPrint("0"); + gfxPrint(dv); + gfxPrint("V"); + } + void gfxPixel(int x, int y) { graphics.setPixel(x, y); } diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index 5d363822a..be01f691e 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -42,6 +42,10 @@ OC::App available_apps[] = { #ifdef ENABLE_APP_CALIBR8OR DECLARE_APP('C','8', "Calibr8or", Calibr8or), #endif + #ifdef ENABLE_APP_SCENES + DECLARE_APP('S','X', "Scenes", ScenesApp), + #endif + DECLARE_APP('H','S', "Hemisphere", HEMISPHERE), #ifdef ENABLE_APP_ASR DECLARE_APP('A','S', "CopierMaschine", ASR), diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 142cc71ec..d91c12b9c 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -49,6 +49,7 @@ build_flags = -DDRUMMAP_GRIDS2 -DVOR -DFLIP_180 -DENABLE_APP_CALIBR8OR + -DENABLE_APP_SCENES -DENABLE_APP_ENIGMA -DENABLE_APP_MIDI -DENABLE_APP_PONG From 787580c586fdc5bdc13a2908e1610128c28e4f16 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 21 Sep 2023 21:28:16 -0400 Subject: [PATCH 292/417] General unified BaseScreensaver() Extracted from Calibr8or. Visualizes hardware I/O state. Used by Hemisphere, Calibr8or, and Scenes apps --- software/o_c_REV/APP_CALIBR8OR.ino | 24 +----------------------- software/o_c_REV/APP_HEMISPHERE.ino | 4 +++- software/o_c_REV/APP_SCENES.ino | 24 +----------------------- software/o_c_REV/HSApplication.h | 28 ++++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 47 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index de3d9a596..da8d5bc74 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -277,28 +277,6 @@ public: } } - void Screensaver() { - gfxDottedLine(0, 32, 127, 32); // horizontal baseline - for (int ch = 0; ch < 4; ++ch) - { - gfxPrint(8 + 32*ch, 55, midi_note_numbers[MIDIQuantizer::NoteNumber(channel[ch].last_note)] ); - if (trigger_flash[ch] > 0) gfxIcon(11 + 32*ch, 0, CLOCK_ICON); - - // input - int height = ProportionCV(ViewIn(ch), 32); - int y = constrain(32 - height, 0, 32); - gfxFrame(3 + (32 * ch), y, 6, abs(height)); - - // output - height = ProportionCV(ViewOut(ch), 32); - y = constrain(32 - height, 0, 32); - gfxInvert(11 + (32 * ch), y, 12, abs(height)); - - gfxLine(32 * ch, 0, 32*ch, 63); // vertical divider, left side - } - gfxLine(127, 0, 127, 63); // vertical line, right side - } - ///////////////////////////////////////////////////////////////// // Control handlers ///////////////////////////////////////////////////////////////// @@ -625,7 +603,7 @@ void Calibr8or_loop() {} // Deprecated void Calibr8or_menu() { Calibr8or_instance.BaseView(); } void Calibr8or_screensaver() { - Calibr8or_instance.Screensaver(); + Calibr8or_instance.BaseScreensaver(true); } void Calibr8or_handleButtonEvent(const UI::Event &event) { diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 78729de74..e9b927b6a 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -695,7 +695,9 @@ void HEMISPHERE_menu() { manager.View(); } -void HEMISPHERE_screensaver() {} // Deprecated in favor of screen blanking +void HEMISPHERE_screensaver() { + manager.BaseScreensaver(true); // show note names +} void HEMISPHERE_handleButtonEvent(const UI::Event &event) { switch (event.type) { diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino index 4b502b3bd..5238f0718 100644 --- a/software/o_c_REV/APP_SCENES.ino +++ b/software/o_c_REV/APP_SCENES.ino @@ -235,27 +235,6 @@ public: } } - void Screensaver() { - gfxDottedLine(0, 32, 127, 32); // horizontal baseline - for (int ch = 0; ch < 4; ++ch) - { - if (trigger_flash[ch] > 0) gfxIcon(11 + 32*ch, 0, CLOCK_ICON); - - // input - int height = ProportionCV(ViewIn(ch), 32); - int y = constrain(32 - height, 0, 32); - gfxFrame(3 + (32 * ch), y, 6, abs(height)); - - // output - height = ProportionCV(ViewOut(ch), 32); - y = constrain(32 - height, 0, 32); - gfxInvert(11 + (32 * ch), y, 12, abs(height)); - - gfxLine(32 * ch, 0, 32*ch, 63); // vertical divider, left side - } - gfxLine(127, 0, 127, 63); // vertical line, right side - } - ///////////////////////////////////////////////////////////////// // Control handlers ///////////////////////////////////////////////////////////////// @@ -400,7 +379,6 @@ private: uint16_t left_accel = 16; uint16_t right_accel = 16; - int trigger_flash[NR_OF_SCENE_CHANNELS]; int smooth_offset = 0; // -128 to 128, for display Scene scene[NR_OF_SCENE_CHANNELS]; @@ -531,7 +509,7 @@ void ScenesApp_loop() {} // Deprecated void ScenesApp_menu() { ScenesApp_instance.BaseView(); } void ScenesApp_screensaver() { - ScenesApp_instance.Screensaver(); + ScenesApp_instance.BaseScreensaver(); } void ScenesApp_handleButtonEvent(const UI::Event &event) { diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 671530c4b..49f44b146 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -93,6 +93,34 @@ class HSApplication { last_view_tick = OC::CORE::ticks; } + // general screensaver view, visualizing inputs and outputs + void BaseScreensaver(bool notenames = 0) { + gfxDottedLine(0, 32, 127, 32); // horizontal baseline + for (int ch = 0; ch < 4; ++ch) + { + if (notenames) { + // approximate notes being output + gfxPrint(8 + 32*ch, 55, midi_note_numbers[MIDIQuantizer::NoteNumber(HS::frame.outputs[ch])] ); + } + + // trigger/gate indicators + if (HS::frame.gate_high[ch]) gfxIcon(11 + 32*ch, 0, CLOCK_ICON); + + // input + int height = ProportionCV(HS::frame.inputs[ch], 32); + int y = constrain(32 - height, 0, 32); + gfxFrame(3 + (32 * ch), y, 6, abs(height)); + + // output + height = ProportionCV(HS::frame.outputs[ch], 32); + y = constrain(32 - height, 0, 32); + gfxInvert(11 + (32 * ch), y, 12, abs(height)); + + gfxLine(32 * ch, 0, 32*ch, 63); // vertical divider, left side + } + gfxLine(127, 0, 127, 63); // vertical line, right side + } + int Proportion(int numerator, int denominator, int max_value) { simfloat proportion = int2simfloat((int32_t)numerator) / (int32_t)denominator; int scaled = simfloat2int(proportion * max_value); From 479646a460f0bd6c3c206d2c05c9fc972157a33b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 24 Sep 2023 01:26:10 -0400 Subject: [PATCH 293/417] Refactoring HSApplication base class to use HSIOFrame --- software/o_c_REV/APP_HEMISPHERE.ino | 6 --- software/o_c_REV/HSApplication.h | 79 ++++++++++------------------- software/o_c_REV/HemisphereApplet.h | 8 --- 3 files changed, 28 insertions(+), 65 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index e9b927b6a..63bc55db3 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -257,9 +257,6 @@ public: // top-level MIDI-to-CV handling - alters frame outputs ProcessMIDI(); - // Load the IO frame from CV inputs - HS::frame.Load(); - // XXX: kind of a crutch, should be replaced with general Trigger input mapping if (clock_m->IsForwarded()) { HS::frame.clocked[2] = HS::frame.clocked[0]; @@ -300,9 +297,6 @@ public: } HS::available_applets[index].Controller(h, 0); } - - // set outputs from IO frame - HS::frame.Send(); } void View() { diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 49f44b146..2eb2b2453 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -25,6 +25,8 @@ * for consistency in development, or ease of porting apps or applets in either direction. */ +#pragma once + #ifndef int2simfloat #define int2simfloat(x) (x << 14) #define simfloat2int(x) (x >> 14) @@ -33,9 +35,7 @@ typedef int32_t simfloat; #include "HSicons.h" #include "HSClockManager.h" - -#ifndef HSAPPLICATION_H_ -#define HSAPPLICATION_H_ +#include "HemisphereApplet.h" #define HSAPPLICATION_CURSOR_TICKS 12000 #define HSAPPLICATION_5V 7680 @@ -48,6 +48,8 @@ typedef int32_t simfloat; #define HSAPP_PULSE_VOLTAGE 5 #endif +using namespace HS; + class HSApplication { public: virtual void Start(); @@ -56,32 +58,27 @@ class HSApplication { virtual void Resume(); void BaseController() { - for (uint8_t ch = 0; ch < 4; ch++) - { - // Set ADC input values - inputs[ch] = OC::ADC::raw_pitch_value((ADC_CHANNEL)ch); - if (abs(inputs[ch] - last_cv[ch]) > HSAPPLICATION_CHANGE_THRESHOLD) { - changed_cv[ch] = 1; - last_cv[ch] = inputs[ch]; - } else changed_cv[ch] = 0; - - if (clock_countdown[ch] > 0) { - if (--clock_countdown[ch] == 0) Out(ch, 0); - } - } + // Load the IO frame from CV inputs + HS::frame.Load(); // Cursor countdowns. See CursorBlink(), ResetCursor(), gfxCursor() if (--cursor_countdown < -HSAPPLICATION_CURSOR_TICKS) cursor_countdown = HSAPPLICATION_CURSOR_TICKS; Controller(); + + // set outputs from IO frame + HS::frame.Send(); } void BaseStart() { // Initialize some things for startup for (uint8_t ch = 0; ch < 4; ch++) { - clock_countdown[ch] = 0; - adc_lag_countdown[ch] = 0; + frame.clock_countdown[ch] = 0; + frame.inputs[ch] = 0; + frame.outputs[ch] = 0; + frame.outputs_smooth[ch] = 0; + frame.adc_lag_countdown[ch] = 0; } cursor_countdown = HSAPPLICATION_CURSOR_TICKS; @@ -130,12 +127,11 @@ class HSApplication { //////////////// Hemisphere-like IO methods //////////////////////////////////////////////////////////////////////////////// void Out(int ch, int value, int octave = 0) { - OC::DAC::set_pitch((DAC_CHANNEL)ch, value, octave); - outputs[ch] = value + (octave * (12 << 7)); + frame.Out( (DAC_CHANNEL)(ch), value + (octave * (12 << 7))); } int In(int ch) { - return inputs[ch]; + return frame.inputs[ch]; } // Apply small center detent to input, so it reads zero before a threshold @@ -144,16 +140,11 @@ class HSApplication { } bool Changed(int ch) { - return changed_cv[ch]; + return frame.changed_cv[ch]; } bool Gate(int ch) { - bool high = 0; - if (ch == 0) high = OC::DigitalInputs::read_immediate(); - if (ch == 1) high = OC::DigitalInputs::read_immediate(); - if (ch == 2) high = OC::DigitalInputs::read_immediate(); - if (ch == 3) high = OC::DigitalInputs::read_immediate(); - return high; + return frame.gate_high[ch]; } void GateOut(int ch, bool high) { @@ -167,31 +158,27 @@ class HSApplication { if (clock_m->IsRunning() && clock_m->GetMultiply(ch) != 0) clocked = clock_m->Tock(ch); else { - if (ch == 0) clocked = OC::DigitalInputs::clocked(); - if (ch == 1) clocked = OC::DigitalInputs::clocked(); - if (ch == 2) clocked = OC::DigitalInputs::clocked(); - if (ch == 3) clocked = OC::DigitalInputs::clocked(); + clocked = frame.clocked[ch]; } // manual triggers clocked = clocked || clock_m->Beep(ch); if (clocked) { - cycle_ticks[ch] = OC::CORE::ticks - last_clock[ch]; - last_clock[ch] = OC::CORE::ticks; + frame.cycle_ticks[ch] = OC::CORE::ticks - frame.last_clock[ch]; + frame.last_clock[ch] = OC::CORE::ticks; } return clocked; } void ClockOut(int ch, int ticks = 100) { - clock_countdown[ch] = ticks; - Out(ch, 0, HSAPP_PULSE_VOLTAGE); + frame.ClockOut( (DAC_CHANNEL)ch, ticks ); } // Buffered I/O functions for use in Views - int ViewIn(int ch) {return inputs[ch];} - int ViewOut(int ch) {return outputs[ch];} - int ClockCycleTicks(int ch) {return cycle_ticks[ch];} + int ViewIn(int ch) {return frame.inputs[ch];} + int ViewOut(int ch) {return frame.outputs[ch];} + int ClockCycleTicks(int ch) {return frame.cycle_ticks[ch];} /* ADC Lag: There is a small delay between when a digital input can be read and when an ADC can be * read. The ADC value lags behind a bit in time. So StartADCLag() and EndADCLag() are used to @@ -204,8 +191,8 @@ class HSApplication { * // etc... * } */ - void StartADCLag(int ch) {adc_lag_countdown[ch] = 96;} - bool EndOfADCLag(int ch) {return (--adc_lag_countdown[ch] == 0);} + void StartADCLag(int ch) {frame.adc_lag_countdown[ch] = 96;} + bool EndOfADCLag(int ch) {return (--frame.adc_lag_countdown[ch] == 0);} //////////////// Hemisphere-like graphics methods for easy porting //////////////////////////////////////////////////////////////////////////////// @@ -330,16 +317,6 @@ class HSApplication { } private: - int clock_countdown[4]; // For clock output timing - int adc_lag_countdown[4]; // Lag countdown for each input channel int cursor_countdown; // Timer for cursor blinkin' uint32_t last_view_tick; // Time since the last view, for activating screen blanking - int inputs[4]; // Last ADC values - int outputs[4]; // Last DAC values; inputs[] and outputs[] are used to allow access to values in Views - bool changed_cv[4]; // Has the input changed by more than 1/8 semitone since the last read? - int last_cv[4]; // For change detection - uint32_t last_clock[4]; // Tick number of the last clock observed by the child class - uint32_t cycle_ticks[4]; // Number of ticks between last two clocks }; - -#endif /* HSAPPLICATION_H_ */ diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 4886c8796..c13c47f8c 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -191,14 +191,6 @@ class HemisphereApplet { hemisphere = hemisphere_; // Initialize some things for startup - ForEachChannel(ch) - { - frame.clock_countdown[io_offset + ch] = 0; - frame.inputs[io_offset + ch] = 0; - frame.outputs[io_offset + ch] = 0; - frame.outputs_smooth[io_offset + ch] = 0; - frame.adc_lag_countdown[io_offset + ch] = 0; - } help_active = 0; cursor_countdown[hemisphere] = HEMISPHERE_CURSOR_TICKS; From 6d9f04d5be791c81c84dcbad0f4e2bcff1008219 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 24 Sep 2023 04:21:18 -0400 Subject: [PATCH 294/417] Fix shared quantizer bug in Calibr8or TODO: Quantizer scale settings are still not properly restored when switching back to Hemisphere. --- software/o_c_REV/APP_CALIBR8OR.ino | 14 ++++++++------ software/o_c_REV/APP_HEMISPHERE.ino | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index da8d5bc74..4b83341fa 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -182,10 +182,7 @@ public: void LoadPreset() { bool success = cal8_presets[index].load_preset(channel); if (success) { - for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { - HS::quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); - HS::quantizer[ch].Requantize(); - } + Resume(); preset_modified = 0; } else @@ -196,8 +193,13 @@ public: preset_modified = 0; } - void Resume() { - } + void Resume() { + // restore quantizer settings + for (int ch = 0; ch < NR_OF_CHANNELS; ++ch) { + HS::quantizer[ch].Configure(OC::Scales::GetScale(channel[ch].scale), 0xffff); + HS::quantizer[ch].Requantize(); + } + } void ProcessMIDI() { HS::IOFrame &f = HS::frame; diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 63bc55db3..5bc7bcb89 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -182,6 +182,7 @@ public: void Resume() { if (!hem_active_preset) LoadFromPreset(0); + // TODO: restore quantizer settings... } void Suspend() { if (hem_active_preset) { From a02c68ea7876b1813f9ecd9f67655fbc5c6c1b02 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 24 Sep 2023 06:58:29 -0400 Subject: [PATCH 295/417] Refactoring apps to use global quantizer instances --- software/o_c_REV/APP_SCALEEDITOR.ino | 27 +++++++++++---------- software/o_c_REV/APP_SCENES.ino | 7 +++--- software/o_c_REV/APP_THEDARKESTTIMELINE.ino | 11 ++++----- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/software/o_c_REV/APP_SCALEEDITOR.ino b/software/o_c_REV/APP_SCALEEDITOR.ino index e0d1cbdf7..097191654 100644 --- a/software/o_c_REV/APP_SCALEEDITOR.ino +++ b/software/o_c_REV/APP_SCALEEDITOR.ino @@ -30,17 +30,16 @@ public: void Start() { current_scale = 0; current_note = 0; - quantizer.Configure(OC::Scales::GetScale(current_scale), 0xffff); current_import_scale = 5; undo_value = OC::user_scales[current_scale].notes[0]; octave = 1; - QuantizeCurrent(); segment.Init(SegmentSize::BIG_SEGMENTS); tinynumbers.Init(SegmentSize::TINY_SEGMENTS); } void Resume() { - + HS::quantizer[0].Configure(OC::Scales::GetScale(current_scale), 0xffff); + QuantizeCurrent(); } void Controller() { @@ -48,7 +47,7 @@ public: // Scale monitor int32_t pitch = In(0); - int32_t quantized = quantizer.Process(pitch, 0, 0); + int32_t quantized = HS::quantizer[0].Process(pitch, 0, 0); Out(0, quantized); // Current note monitor @@ -116,7 +115,7 @@ public: current_note = 0; undo_value = OC::user_scales[current_scale].notes[current_note]; // Configure and force requantize for real-time monitoring purposes - quantizer.Configure(OC::Scales::GetScale(current_scale), 0xffff); + HS::quantizer[0].Configure(OC::Scales::GetScale(current_scale), 0xffff); QuantizeCurrent(); } } @@ -170,7 +169,6 @@ private: int undo_value; bool length_set_mode; bool import_mode; - braids::Quantizer quantizer; int current_quantized; int octave; SegmentDisplay segment; @@ -235,7 +233,7 @@ private: current_scale = constrain(current_scale + direction, 0, OC::Scales::SCALE_USER_LAST - 1); // Configure and force requantize for real-time monitoring purposes - quantizer.Configure(OC::Scales::GetScale(current_scale), 0xffff); + HS::quantizer[0].Configure(OC::Scales::GetScale(current_scale), 0xffff); QuantizeCurrent(); uint8_t length = static_cast(OC::user_scales[current_scale].num_notes); @@ -293,7 +291,7 @@ private: } // Configure and force requantize for real-time monitoring purposes - quantizer.Configure(OC::Scales::GetScale(current_scale), 0xffff); + HS::quantizer[0].Configure(OC::Scales::GetScale(current_scale), 0xffff); QuantizeCurrent(); ResetCursor(); @@ -315,14 +313,14 @@ private: OC::user_scales[current_scale].notes[current_note] = new_value; // Configure and force requantize for real-time monitoring purposes - quantizer.Configure(OC::Scales::GetScale(current_scale), 0xffff); + HS::quantizer[0].Configure(OC::Scales::GetScale(current_scale), 0xffff); QuantizeCurrent(); } void ImportScale() { OC::Scale source = OC::Scales::GetScale(current_import_scale); memcpy(&OC::user_scales[current_scale], &source, sizeof(source)); - quantizer.Configure(OC::Scales::GetScale(current_scale), 0xffff); + HS::quantizer[0].Configure(OC::Scales::GetScale(current_scale), 0xffff); QuantizeCurrent(); import_mode = 0; undo_value = OC::user_scales[current_scale].notes[current_note]; @@ -335,9 +333,9 @@ private: void QuantizeCurrent() { int transpose = OC::user_scales[current_scale].span * octave; - quantizer.Requantize(); - current_quantized = quantizer.Process(OC::user_scales[current_scale].notes[current_note] + transpose, 0, 0); - quantizer.Requantize(); // This is for the next one in the Controller + HS::quantizer[0].Requantize(); + current_quantized = HS::quantizer[0].Process(OC::user_scales[current_scale].notes[current_note] + transpose, 0, 0); + HS::quantizer[0].Requantize(); // This is for the next one in the Controller } }; @@ -361,6 +359,9 @@ void SCALEEDITOR_handleAppEvent(OC::AppEvent event) { if (event == OC::APP_EVENT_SUSPEND) { scale_editor_instance.OnSendSysEx(); } + if (event == OC::APP_EVENT_RESUME) { + scale_editor_instance.Resume(); + } } void SCALEEDITOR_loop() {} diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino index 5238f0718..af1b57980 100644 --- a/software/o_c_REV/APP_SCENES.ino +++ b/software/o_c_REV/APP_SCENES.ino @@ -488,20 +488,19 @@ size_t ScenesApp_restore(const void *storage) { void ScenesApp_isr() { return ScenesApp_instance.BaseController(); } void ScenesApp_handleAppEvent(OC::AppEvent event) { + /* switch (event) { case OC::APP_EVENT_RESUME: - ScenesApp_instance.Resume(); + //ScenesApp_instance.Resume(); break; - // The idea is to auto-save when the screen times out... case OC::APP_EVENT_SUSPEND: case OC::APP_EVENT_SCREENSAVER_ON: - // TODO: initiate actual EEPROM save - // app_data_save(); break; default: break; } + */ } void ScenesApp_loop() {} // Deprecated diff --git a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino index 554ea3fd6..6af368ada 100644 --- a/software/o_c_REV/APP_THEDARKESTTIMELINE.ino +++ b/software/o_c_REV/APP_THEDARKESTTIMELINE.ino @@ -52,8 +52,6 @@ class TheDarkestTimeline : public HSApplication, public SystemExclusiveHandler, public settings::SettingsBase { public: void Start() { - quantizer.Init(); - quantizer.Configure(OC::Scales::GetScale(5), 0xffff); Resume(); } @@ -67,6 +65,8 @@ public: values_[DT_MIDI_CHANNEL] = 11; values_[DT_MIDI_CHANNEL_ALT] = 12; } + HS::quantizer[0].Init(); + HS::quantizer[0].Configure(OC::Scales::GetScale(scale()), 0xffff); } void Controller() { @@ -156,13 +156,13 @@ public: if (tl == 0) { // This is a CV Timeline, so output the normal universe note - int32_t pitch = quantizer.Process(cv, root() << 7, transpose); + int32_t pitch = HS::quantizer[0].Process(cv, root() << 7, transpose); Out(0, pitch); // and then output the alternate universe note uint8_t alt_idx = (idx + length()) % 32; int alt_cv = get_data_at(alt_idx, tl); - pitch = quantizer.Process(alt_cv, root() << 7, transpose); + pitch = HS::quantizer[0].Process(alt_cv, root() << 7, transpose); Out(1, pitch); } else if (clocked) { // This is the Probability Timeline, and it's only calculated when @@ -352,7 +352,7 @@ public: if (setup_screen == 0) change_value(DT_LENGTH, -direction); else change_value(setup_screen + 1, direction); - quantizer.Configure(OC::Scales::GetScale(scale()), 0xffff); + HS::quantizer[0].Configure(OC::Scales::GetScale(scale()), 0xffff); if (setup_screen > 0) setup_screen_timeout_countdown = DT_SETUP_SCREEN_TIMEOUT; } @@ -370,7 +370,6 @@ private: int8_t cursor; // The play/record point within the sequence bool record[2]; // 0 = CV Timeline, 1 = Proability Timeline bool index_edit_enabled; // The index is being edited via the panel - braids::Quantizer quantizer; uint8_t setup_screen; // Setup screen state int setup_screen_timeout_countdown; bool clocked; // Sequencer has been clocked, and a probability trigger needs to be determined From 6442f4839d08cbe904d6b8059449ce13741fabf7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 24 Sep 2023 04:23:13 -0400 Subject: [PATCH 296/417] Extract utility functions to HSUtils.h --- software/o_c_REV/HSApplication.h | 111 +------------------- software/o_c_REV/HSUtils.h | 153 ++++++++++++++++++++++++++++ software/o_c_REV/HemisphereApplet.h | 99 +++--------------- 3 files changed, 166 insertions(+), 197 deletions(-) create mode 100644 software/o_c_REV/HSUtils.h diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 2eb2b2453..50a8b8e66 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -36,6 +36,7 @@ typedef int32_t simfloat; #include "HSicons.h" #include "HSClockManager.h" #include "HemisphereApplet.h" +#include "HSUtils.h" #define HSAPPLICATION_CURSOR_TICKS 12000 #define HSAPPLICATION_5V 7680 @@ -118,12 +119,6 @@ class HSApplication { gfxLine(127, 0, 127, 63); // vertical line, right side } - int Proportion(int numerator, int denominator, int max_value) { - simfloat proportion = int2simfloat((int32_t)numerator) / (int32_t)denominator; - int scaled = simfloat2int(proportion * max_value); - return scaled; - } - //////////////// Hemisphere-like IO methods //////////////////////////////////////////////////////////////////////////////// void Out(int ch, int value, int octave = 0) { @@ -194,124 +189,20 @@ class HSApplication { void StartADCLag(int ch) {frame.adc_lag_countdown[ch] = 96;} bool EndOfADCLag(int ch) {return (--frame.adc_lag_countdown[ch] == 0);} - //////////////// Hemisphere-like graphics methods for easy porting - //////////////////////////////////////////////////////////////////////////////// void gfxCursor(int x, int y, int w) { if (CursorBlink()) gfxLine(x, y, x + w - 1, y); } - - void gfxPos(int x, int y) { - graphics.setPrintPos(x, y); - } - - void gfxPrint(int x, int y, const char *str) { - graphics.setPrintPos(x, y); - graphics.print(str); - } - - void gfxPrint(int x, int y, int num) { - graphics.setPrintPos(x, y); - graphics.print(num); - } - - void gfxPrint(int x_adv, int num) { // Print number with character padding - for (int c = 0; c < (x_adv / 6); c++) gfxPrint(" "); - gfxPrint(num); - } - - void gfxPrint(const char *str) { - graphics.print(str); - } - - void gfxPrint(int num) { - graphics.print(num); - } - - /* Convert CV value to voltage level and print to two decimal places */ - void gfxPrintVoltage(int cv) { - int v = (cv * 100) / (12 << 7); - bool neg = v < 0 ? 1 : 0; - if (v < 0) v = -v; - int wv = v / 100; // whole volts - int dv = v - (wv * 100); // decimal - gfxPrint(neg ? "-" : "+"); - gfxPrint(wv); - gfxPrint("."); - if (dv < 10) gfxPrint("0"); - gfxPrint(dv); - gfxPrint("V"); - } - - void gfxPixel(int x, int y) { - graphics.setPixel(x, y); - } - - void gfxFrame(int x, int y, int w, int h) { - graphics.drawFrame(x, y, w, h); - } - - void gfxRect(int x, int y, int w, int h) { - graphics.drawRect(x, y, w, h); - } - - void gfxInvert(int x, int y, int w, int h) { - graphics.invertRect(x, y, w, h); - } - - void gfxLine(int x, int y, int x2, int y2) { - graphics.drawLine(x, y, x2, y2); - } - - void gfxDottedLine(int x, int y, int x2, int y2, uint8_t p = 2) { -#ifdef HS_GFX_MOD - graphics.drawLine(x, y, x2, y2, p); -#else - graphics.drawLine(x, y, x2, y2); -#endif - } - - void gfxCircle(int x, int y, int r) { - graphics.drawCircle(x, y, r); - } - - void gfxBitmap(int x, int y, int w, const uint8_t *data) { - graphics.drawBitmap8(x, y, w, data); - } - - // Like gfxBitmap, but always 8x8 - void gfxIcon(int x, int y, const uint8_t *data) { - gfxBitmap(x, y, 8, data); - } - - - uint8_t pad(int range, int number) { - uint8_t padding = 0; - while (range > 1) - { - if (abs(number) < range) padding += 6; - range = range / 10; - } - if (number < 0 && padding > 0) padding -= 6; // Compensate for minus sign - return padding; - } - void gfxHeader(const char *str) { gfxPrint(1, 2, str); gfxLine(0, 10, 127, 10); gfxLine(0, 12, 127, 12); } - int ProportionCV(int cv_value, int max_pixels) { - int prop = constrain(Proportion(cv_value, HSAPPLICATION_5V, max_pixels), -max_pixels, max_pixels); - return prop; - } - protected: // Check cursor blink cycle bool CursorBlink() { return (cursor_countdown > 0); } - void ResetCursor() { cursor_countdown = HSAPPLICATION_CURSOR_TICKS; } diff --git a/software/o_c_REV/HSUtils.h b/software/o_c_REV/HSUtils.h new file mode 100644 index 000000000..58ab2b5f7 --- /dev/null +++ b/software/o_c_REV/HSUtils.h @@ -0,0 +1,153 @@ +#pragma once + +// misc. utility functions extracted from Hemisphere +// -NJM + +//////////////// Calculation methods +//////////////////////////////////////////////////////////////////////////////// + +/* Proportion method using simfloat, useful for calculating scaled values given + * a fractional value. + * + * Solves this: numerator ??? + * ----------- = ----------- + * denominator max + * + * For example, to convert a parameter with a range of 1 to 100 into value scaled + * to HEMISPHERE_MAX_CV, to be sent to the DAC: + * + * Out(ch, Proportion(value, 100, HEMISPHERE_MAX_CV)); + * + */ +int Proportion(int numerator, int denominator, int max_value) { + simfloat proportion = int2simfloat((int32_t)numerator) / (int32_t)denominator; + int scaled = simfloat2int(proportion * max_value); + return scaled; +} + +/* Proportion CV values into pixels for display purposes. + * + * Solves this: cv_value ??? + * ----------------- = ---------- + * HEMISPHERE_MAX_CV max_pixels + */ +int ProportionCV(int cv_value, int max_pixels) { + // TODO: variable scaling for VOR? + int prop = constrain(Proportion(cv_value, HEMISPHERE_MAX_INPUT_CV, max_pixels), -max_pixels, max_pixels); + return prop; +} + +// Specifies where data goes in flash storage for each selcted applet, and how big it is +typedef struct PackLocation { + size_t location; + size_t size; +} PackLocation; + +/* Add value to a 64-bit storage unit at the specified location */ +void Pack(uint64_t &data, PackLocation p, uint64_t value) { + data |= (value << p.location); +} + +/* Retrieve value from a 64-bit storage unit at the specified location and of the specified bit size */ +int Unpack(uint64_t data, PackLocation p) { + uint64_t mask = 1; + for (size_t i = 1; i < p.size; i++) mask |= (0x01 << i); + return (data >> p.location) & mask; +} + +//////////////// Hemisphere-like graphics methods for easy porting +//////////////////////////////////////////////////////////////////////////////// +void gfxPos(int x, int y) { + graphics.setPrintPos(x, y); +} + +void gfxPrint(int x, int y, const char *str) { + graphics.setPrintPos(x, y); + graphics.print(str); +} + +void gfxPrint(int x, int y, int num) { + graphics.setPrintPos(x, y); + graphics.print(num); +} + +void gfxPrint(const char *str) { + graphics.print(str); +} + +void gfxPrint(int num) { + graphics.print(num); +} + +void gfxPrint(int x_adv, int num) { // Print number with character padding + for (int c = 0; c < (x_adv / 6); c++) gfxPrint(" "); + gfxPrint(num); +} + +/* Convert CV value to voltage level and print to two decimal places */ +void gfxPrintVoltage(int cv) { + int v = (cv * 100) / (12 << 7); + bool neg = v < 0 ? 1 : 0; + if (v < 0) v = -v; + int wv = v / 100; // whole volts + int dv = v - (wv * 100); // decimal + gfxPrint(neg ? "-" : "+"); + gfxPrint(wv); + gfxPrint("."); + if (dv < 10) gfxPrint("0"); + gfxPrint(dv); + gfxPrint("V"); +} + +void gfxPixel(int x, int y) { + graphics.setPixel(x, y); +} + +void gfxFrame(int x, int y, int w, int h) { + graphics.drawFrame(x, y, w, h); +} + +void gfxRect(int x, int y, int w, int h) { + graphics.drawRect(x, y, w, h); +} + +void gfxInvert(int x, int y, int w, int h) { + graphics.invertRect(x, y, w, h); +} + +void gfxLine(int x, int y, int x2, int y2) { + graphics.drawLine(x, y, x2, y2); +} + +void gfxDottedLine(int x, int y, int x2, int y2, uint8_t p = 2) { +#ifdef HS_GFX_MOD + graphics.drawLine(x, y, x2, y2, p); +#else + graphics.drawLine(x, y, x2, y2); +#endif +} + +void gfxCircle(int x, int y, int r) { + graphics.drawCircle(x, y, r); +} + +void gfxBitmap(int x, int y, int w, const uint8_t *data) { + graphics.drawBitmap8(x, y, w, data); +} + +// Like gfxBitmap, but always 8x8 +void gfxIcon(int x, int y, const uint8_t *data) { + gfxBitmap(x, y, 8, data); +} + +uint8_t pad(int range, int number) { + uint8_t padding = 0; + while (range > 1) + { + if (abs(number) < range) padding += 6; + range = range / 10; + } + if (number < 0 && padding > 0) padding -= 6; // Compensate for minus sign + return padding; +} + diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index c13c47f8c..23d8b6a19 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -94,58 +94,7 @@ typedef int32_t simfloat; #include "hemisphere_config.h" #include "braids_quantizer.h" - -//////////////// Calculation methods -//////////////////////////////////////////////////////////////////////////////// - -/* Proportion method using simfloat, useful for calculating scaled values given - * a fractional value. - * - * Solves this: numerator ??? - * ----------- = ----------- - * denominator max - * - * For example, to convert a parameter with a range of 1 to 100 into value scaled - * to HEMISPHERE_MAX_CV, to be sent to the DAC: - * - * Out(ch, Proportion(value, 100, HEMISPHERE_MAX_CV)); - * - */ -int Proportion(int numerator, int denominator, int max_value) { - simfloat proportion = int2simfloat((int32_t)numerator) / (int32_t)denominator; - int scaled = simfloat2int(proportion * max_value); - return scaled; -} - -/* Proportion CV values into pixels for display purposes. - * - * Solves this: cv_value ??? - * ----------------- = ---------- - * HEMISPHERE_MAX_CV max_pixels - */ -int ProportionCV(int cv_value, int max_pixels) { - int prop = constrain(Proportion(cv_value, HEMISPHERE_MAX_INPUT_CV, max_pixels), 0, max_pixels); - return prop; -} - -// Specifies where data goes in flash storage for each selcted applet, and how big it is -typedef struct PackLocation { - size_t location; - size_t size; -} PackLocation; - -/* Add value to a 64-bit storage unit at the specified location */ -void Pack(uint64_t &data, PackLocation p, uint64_t value) { - data |= (value << p.location); -} - -/* Retrieve value from a 64-bit storage unit at the specified location and of the specified bit size */ -int Unpack(uint64_t data, PackLocation p) { - uint64_t mask = 1; - for (size_t i = 1; i < p.size; i++) mask |= (0x01 << i); - return (data >> p.location) & mask; -} - +#include "HSUtils.h" #include "HSIOFrame.h" namespace HS { @@ -179,7 +128,6 @@ using namespace HS; class HemisphereApplet { public: - //static HSIOFrame frame; static int cursor_countdown[2]; virtual const char* applet_name(); // Maximum of 9 characters @@ -287,6 +235,13 @@ class HemisphereApplet { return (isEditing || !modal_edit_mode); } + // Override HSUtils function to only return positive values + // Not ideal, but too many applets rely on this. + int ProportionCV(int cv_value, int max_pixels) { + int prop = constrain(Proportion(cv_value, HEMISPHERE_MAX_INPUT_CV, max_pixels), 0, max_pixels); + return prop; + } + //////////////// Offset graphics methods //////////////////////////////////////////////////////////////////////////////// void gfxCursor(int x, int y, int w, int h = 9) { // assumes standard text height for highlighting @@ -302,40 +257,22 @@ class HemisphereApplet { graphics.setPrintPos(x + gfx_offset, y); graphics.print(str); } - void gfxPrint(int x, int y, int num) { graphics.setPrintPos(x + gfx_offset, y); graphics.print(num); } - - void gfxPrint(int x_adv, int num) { // Print number with character padding - for (int c = 0; c < (x_adv / 6); c++) gfxPrint(" "); - gfxPrint(num); - } - void gfxPrint(const char *str) { graphics.print(str); } - void gfxPrint(int num) { graphics.print(num); } - - /* Convert CV value to voltage level and print to two decimal places */ - void gfxPrintVoltage(int cv) { - int v = (cv * 100) / (12 << 7); - bool neg = v < 0 ? 1 : 0; - if (v < 0) v = -v; - int wv = v / 100; // whole volts - int dv = v - (wv * 100); // decimal - gfxPrint(neg ? "-" : "+"); - gfxPrint(wv); - gfxPrint("."); - if (dv < 10) gfxPrint("0"); - gfxPrint(dv); - gfxPrint("V"); + void gfxPrint(int x_adv, int num) { // Print number with character padding + for (int c = 0; c < (x_adv / 6); c++) gfxPrint(" "); + gfxPrint(num); } + /* Convert CV value to voltage level and print to two decimal places */ void gfxPixel(int x, int y) { graphics.setPixel(x + gfx_offset, y); } @@ -371,21 +308,10 @@ class HemisphereApplet { void gfxBitmap(int x, int y, int w, const uint8_t *data) { graphics.drawBitmap8(x + gfx_offset, y, w, data); } - void gfxIcon(int x, int y, const uint8_t *data) { gfxBitmap(x, y, 8, data); } - uint8_t pad(int range, int number) { - uint8_t padding = 0; - while (range > 1) - { - if (abs(number) < range) padding += 6; - range = range / 10; - } - return padding; - } - //////////////// Hemisphere-specific graphics methods //////////////////////////////////////////////////////////////////////////////// @@ -432,7 +358,6 @@ class HemisphereApplet { void Modulate(auto ¶m, const int ch, const int min = 0, const int max = 255) { int cv = DetentedIn(ch); param = constrain(param + Proportion(cv, HEMISPHERE_MAX_INPUT_CV, max), min, max); - //return param_mod; } void Out(int ch, int value, int octave = 0) { From 2593f70c5a2f242f34a2c2eca695fe03c7907342 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 24 Sep 2023 07:25:16 -0400 Subject: [PATCH 297/417] VectorLFO: frequency modulation fix; tweaks --- software/o_c_REV/HEM_VectorLFO.ino | 15 ++++++--------- software/o_c_REV/vector_osc/HSVectorOscillator.h | 10 ++++++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/HEM_VectorLFO.ino b/software/o_c_REV/HEM_VectorLFO.ino index 2e8541ca3..2e0ec42c8 100644 --- a/software/o_c_REV/HEM_VectorLFO.ino +++ b/software/o_c_REV/HEM_VectorLFO.ino @@ -28,7 +28,7 @@ public: return "VectorLFO"; } - static constexpr int min_freq = 3; + static constexpr int min_freq = 1; static constexpr int max_freq = 100000; void Start() { @@ -43,11 +43,9 @@ public: void Controller() { // Input 1 is frequency modulation for channel 1 - if (Changed(0)) { - int mod = Proportion(DetentedIn(0), HEMISPHERE_3V_CV, 3000); - mod = constrain(mod, -3000, 3000); - if (mod + freq[0] > 10) osc[0].SetFrequency(freq[0] + mod); - } + int freq_mod = Proportion(DetentedIn(0), HEMISPHERE_3V_CV, 3000); + freq_mod = constrain(freq[0] + freq_mod, min_freq, max_freq); + osc[0].SetFrequency(freq_mod); // Input 2 determines signal 1's level on the B/D output mix int mix_level = DetentedIn(1); @@ -59,9 +57,8 @@ public: if (Clock(ch)) { uint32_t ticks = ClockCycleTicks(ch); int new_freq = 1666666 / ticks; - new_freq = constrain(new_freq, min_freq, max_freq); - osc[ch].SetFrequency(new_freq); - freq[ch] = new_freq; + freq[ch] = constrain(new_freq, min_freq, max_freq); + osc[ch].SetFrequency(freq[ch]); osc[ch].Reset(); } diff --git a/software/o_c_REV/vector_osc/HSVectorOscillator.h b/software/o_c_REV/vector_osc/HSVectorOscillator.h index 0e2943720..532d82f20 100644 --- a/software/o_c_REV/vector_osc/HSVectorOscillator.h +++ b/software/o_c_REV/vector_osc/HSVectorOscillator.h @@ -111,8 +111,10 @@ class VectorOscillator { /* frequency is centihertz (e.g., 440 Hz is 44000) */ void SetFrequency(uint32_t frequency_) { - frequency = frequency_; - rise = calculate_rise(segment_index); + if (frequency_ != frequency) { + frequency = frequency_; + rise = calculate_rise(segment_index); + } } bool GetEOC() {return eoc;} @@ -145,8 +147,8 @@ class VectorOscillator { if (validate()) { if (rise) { signal += rise; - if (rise >= 0 && signal >= target) advance_segment(); - if (rise < 0 && signal <= target) advance_segment(); + if (rise > 0 && signal >= target) advance_segment(); + else if (rise < 0 && signal <= target) advance_segment(); } else { if (countdown) { --countdown; From 1852f03aa113b8fb134cbea250b88f97b9a91198 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 21 Sep 2023 22:00:03 -0400 Subject: [PATCH 298/417] Build config updates - Add SCENES app to "main" - Add "+main" suffix to be more clear - restore Low-rents for "stock1" - Extra "pewpewpew" and "pewpewvor" configs for Phazer's choice build --- software/o_c_REV/platformio.ini | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index d91c12b9c..c77e79080 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -47,7 +47,6 @@ upload_protocol = teensy-cli build_flags = ${env.build_flags} -DDRUMMAP_GRIDS2 - -DVOR -DFLIP_180 -DENABLE_APP_CALIBR8OR -DENABLE_APP_SCENES -DENABLE_APP_ENIGMA @@ -60,11 +59,17 @@ build_flags = -DPEWPEWPEW -DOC_VERSION_EXTRA="\"_phz\"" +[env:pewpewvor] +build_flags = + ${env:pewpewpew.build_flags} + -DVOR -DFLIP_180 + [env:main] build_flags = ${env.build_flags} -DDRUMMAP_GRIDS2 -DENABLE_APP_CALIBR8OR + -DENABLE_APP_SCENES -DENABLE_APP_ENIGMA -DENABLE_APP_MIDI -DENABLE_APP_NEURAL_NETWORK @@ -73,6 +78,7 @@ build_flags = -DENABLE_APP_PIQUED -DENABLE_APP_POLYLFO -DENABLE_APP_LORENZ + -DOC_VERSION_EXTRA="\"+main\"" ; ; -DENABLE_APP_ASR ; -DENABLE_APP_QUANTERMAIN @@ -97,7 +103,7 @@ build_flags = ; -DENABLE_APP_POLYLFO ; -DENABLE_APP_H1200 -DENABLE_APP_AUTOMATONNETZ -; -DENABLE_APP_LORENZ + -DENABLE_APP_LORENZ ; -DENABLE_APP_BBGEN ; -DENABLE_APP_BYTEBEATGEN -DOC_VERSION_EXTRA="\"+stock1\"" From 2cc3ca06619fa580367aff9a19f6fb6412c2969d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 30 Aug 2023 01:58:48 -0400 Subject: [PATCH 299/417] Version bump v1.6.4 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 63d5699b6..642fdadeb 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.3" +"v1.6.4" From 416ee88fb39d582a49744c210af8c9617cdd7953 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Tue, 26 Sep 2023 03:40:25 -0400 Subject: [PATCH 300/417] Fix calibration I/O bug (#26) --- software/o_c_REV/APP_SETTINGS.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_SETTINGS.ino b/software/o_c_REV/APP_SETTINGS.ino index 16c91e16c..5b4de5e0c 100644 --- a/software/o_c_REV/APP_SETTINGS.ino +++ b/software/o_c_REV/APP_SETTINGS.ino @@ -126,7 +126,8 @@ size_t Settings_save(void *storage) {return 0;} size_t Settings_restore(const void *storage) {return 0;} void Settings_isr() { - return Settings_instance.BaseController(); +// skip the Controller to avoid I/O conflict with Calibration + return; } void Settings_handleAppEvent(OC::AppEvent event) { From 4d212cf8d68c105cc848afb0c89b4a6e99e80caa Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 26 Sep 2023 03:36:49 -0400 Subject: [PATCH 301/417] Fix dual-press button action more properly --- software/o_c_REV/APP_HEMISPHERE.ino | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 5bc7bcb89..c9834646d 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -388,14 +388,12 @@ public: // -- button down if (down) { - // dual press for Clock Setup... - // a less favorable approach was to check if both buttons are currently pressed. For some reason, this sometimes cancels clock_setup when you let go. - //if (event.mask == (OC::CONTROL_BUTTON_UP | OC::CONTROL_BUTTON_DOWN)) - if (OC::CORE::ticks - click_tick < HEMISPHERE_SIM_CLICK_TIME && hemisphere != first_click) { + // dual press for Clock Setup... check first_click, so we only process the 2nd button event + if (event.mask == (OC::CONTROL_BUTTON_UP | OC::CONTROL_BUTTON_DOWN) && hemisphere != first_click) { clock_setup = 1; SetHelpScreen(-1); select_mode = -1; - OC::ui.SetButtonIgnoreMask(); + OC::ui.SetButtonIgnoreMask(); // ignore button release return; } From a55c6f0fd18cff7e8e87588ff67abd145b02f3e0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 26 Sep 2023 06:01:21 -0400 Subject: [PATCH 302/417] VectorLFO: lower limit of 0.08Hz Anything lower results in weirdness from rounding errors or integer overflows. A careful audit/rewrite of HSVectorOscillator.h would be necessary for slower waveforms... perhaps store frequency as ticks instead of centihertz. --- software/o_c_REV/HEM_VectorLFO.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_VectorLFO.ino b/software/o_c_REV/HEM_VectorLFO.ino index 2e0ec42c8..b455d83ad 100644 --- a/software/o_c_REV/HEM_VectorLFO.ino +++ b/software/o_c_REV/HEM_VectorLFO.ino @@ -28,7 +28,7 @@ public: return "VectorLFO"; } - static constexpr int min_freq = 1; + static constexpr int min_freq = 8; static constexpr int max_freq = 100000; void Start() { From b6e71f1619df50d5a07b2ba48180d90db46447a0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 26 Sep 2023 07:14:43 -0400 Subject: [PATCH 303/417] SCENES: CV2 bipolar offset; fix encoder accel; cleanup --- software/o_c_REV/APP_SCENES.ino | 74 +++++++++++++-------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino index af1b57980..a8b1cd764 100644 --- a/software/o_c_REV/APP_SCENES.ino +++ b/software/o_c_REV/APP_SCENES.ino @@ -36,6 +36,8 @@ static const int NR_OF_SCENE_CHANNELS = 4; #define SCENE_MAX_VAL HEMISPHERE_MAX_CV #define SCENE_MIN_VAL HEMISPHERE_MIN_CV +#define SCENE_ACCEL_MIN 16 +#define SCENE_ACCEL_MAX 256 struct Scene { int16_t values[4]; @@ -167,7 +169,7 @@ public: Sequence.active = 0; } - // smooth interpolation offset, starting from last triggered scene + // CV1: smooth interpolation offset, starting from last triggered scene if (DetentedIn(0)) { int cv = In(0); int direction = (cv < 0) ? -1 : 1; @@ -207,17 +209,27 @@ public: smooth_offset = 0; } + // CV2: bipolar offset added to all values + if (DetentedIn(1)) { + int cv_offset = In(1); + for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { + active_scene.values[i] = constrain(active_scene.values[i] + cv_offset, SCENE_MIN_VAL, SCENE_MAX_VAL); + } + } + + // CV3: TODO + // set outputs for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { Out(ch, active_scene.values[ch]); } // encoder deceleration - if (left_accel > 16) --left_accel; - else left_accel = 16; // just in case lol + if (left_accel > SCENE_ACCEL_MIN) --left_accel; + else left_accel = SCENE_ACCEL_MIN; // just in case lol - if (right_accel > 16) --right_accel; - else right_accel = 16; + if (right_accel > SCENE_ACCEL_MIN) --right_accel; + else right_accel = SCENE_ACCEL_MIN; } void View() { @@ -249,6 +261,7 @@ public: void OnLeftButtonLongPress() { if (preset_select) return; + // TODO: toggle something cool here } void OnRightButtonPress() { @@ -272,29 +285,11 @@ public: edit_mode_right = !edit_mode_right; } - void OnButtonDown(const UI::Event &event) { - // check for clock setup secret combo (dual press) - if ( event.control == OC::CONTROL_BUTTON_DOWN || event.control == OC::CONTROL_BUTTON_UP) - UpOrDownButtonPress(event.control == OC::CONTROL_BUTTON_UP); - } - - void UpOrDownButtonPress(bool up) { - if (OC::CORE::ticks - click_tick < HEMISPHERE_DOUBLE_CLICK_TIME && up != first_click) { - // show clock setup if both buttons pressed quickly - click_tick = 0; - } else { - click_tick = OC::CORE::ticks; - first_click = up; - } - } - void SwitchChannel(bool up) { if (!preset_select) { sel_chan += (up? 1 : -1) + NR_OF_SCENE_CHANNELS; sel_chan %= NR_OF_SCENE_CHANNELS; - } - - if (click_tick) { + } else { // always cancel preset select on single click preset_select = 0; } @@ -321,7 +316,7 @@ public: scene[sel_chan].values[idx] = constrain( scene[sel_chan].values[idx], SCENE_MIN_VAL, SCENE_MAX_VAL ); left_accel <<= 3; - if (left_accel > 256) left_accel = 256; + if (left_accel > SCENE_ACCEL_MAX) left_accel = SCENE_ACCEL_MAX; } // Right encoder: Edit C or D on current scene @@ -337,8 +332,8 @@ public: scene[sel_chan].values[idx] += direction * right_accel; scene[sel_chan].values[idx] = constrain( scene[sel_chan].values[idx], SCENE_MIN_VAL, SCENE_MAX_VAL ); - right_accel <<= 4; - if (right_accel > 512) right_accel = 512; + right_accel <<= 3; + if (right_accel > SCENE_ACCEL_MAX) right_accel = SCENE_ACCEL_MAX; } private: @@ -373,11 +368,8 @@ private: } } Sequence; - uint32_t click_tick = 0; - bool first_click = 0; - - uint16_t left_accel = 16; - uint16_t right_accel = 16; + uint16_t left_accel = SCENE_ACCEL_MIN; + uint16_t right_accel = SCENE_ACCEL_MIN; int smooth_offset = 0; // -128 to 128, for display @@ -403,10 +395,6 @@ private: } void DrawInterface() { - /* - gfxPrint(2, 15, "Scene "); - gfxPrint(sel_chan + 1); - */ for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { gfxPrint(i*32 + 13, 14, i+1); } @@ -417,16 +405,16 @@ private: // edit pointer gfxIcon(sel_chan*32 + 13, 25, UP_ICON); - gfxPrint(8, 35, "A: "); + gfxPrint(8, 35, "A:"); gfxPrintVoltage(scene[sel_chan].values[0]); - gfxPrint(8, 45, "B: "); + gfxPrint(8, 45, "B:"); gfxPrintVoltage(scene[sel_chan].values[1]); gfxIcon(0, 35 + 10*edit_mode_left, RIGHT_ICON); - gfxPrint(72, 35, "C: "); + gfxPrint(72, 35, "C:"); gfxPrintVoltage(scene[sel_chan].values[2]); - gfxPrint(72, 45, "D: "); + gfxPrint(72, 45, "D:"); gfxPrintVoltage(scene[sel_chan].values[3]); gfxIcon(64, 35 + 10*edit_mode_right, RIGHT_ICON); @@ -488,7 +476,6 @@ size_t ScenesApp_restore(const void *storage) { void ScenesApp_isr() { return ScenesApp_instance.BaseController(); } void ScenesApp_handleAppEvent(OC::AppEvent event) { - /* switch (event) { case OC::APP_EVENT_RESUME: //ScenesApp_instance.Resume(); @@ -500,7 +487,6 @@ void ScenesApp_handleAppEvent(OC::AppEvent event) { default: break; } - */ } void ScenesApp_loop() {} // Deprecated @@ -518,8 +504,6 @@ void ScenesApp_handleButtonEvent(const UI::Event &event) { // For down button, handle press and long press switch (event.type) { case UI::EVENT_BUTTON_DOWN: - ScenesApp_instance.OnButtonDown(event); - break; case UI::EVENT_BUTTON_PRESS: { switch (event.control) { @@ -538,7 +522,7 @@ void ScenesApp_handleButtonEvent(const UI::Event &event) { } break; case UI::EVENT_BUTTON_LONG_PRESS: if (event.control == OC::CONTROL_BUTTON_L) { - ScenesApp_instance.OnLeftButtonLongPress(); + //ScenesApp_instance.OnLeftButtonLongPress(); } if (event.control == OC::CONTROL_BUTTON_DOWN) { ScenesApp_instance.OnDownButtonLongPress(); From 96f5bf4e55b86c59025b8656e230eff7105d1e30 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 26 Sep 2023 07:31:50 -0400 Subject: [PATCH 304/417] Version v1.6.5 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 642fdadeb..89a41b3bf 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.4" +"v1.6.5" From fec01019fe3a648d5f0db799d2ef681a6494be2c Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Sun, 8 Oct 2023 03:12:19 -0700 Subject: [PATCH 305/417] C++17 compatibility --- software/o_c_REV/HemisphereApplet.h | 5 +++++ software/o_c_REV/util/util_math.h | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 23d8b6a19..b6a1a2e80 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -355,7 +355,12 @@ class HemisphereApplet { } // Standard bi-polar CV modulation scenario +#if __cplusplus == 201703L + template + void Modulate(T ¶m, const int ch, const int min = 0, const int max = 255) { +#else void Modulate(auto ¶m, const int ch, const int min = 0, const int max = 255) { +#endif int cv = DetentedIn(ch); param = constrain(param + Proportion(cv, HEMISPHERE_MAX_INPUT_CV, max), min, max); } diff --git a/software/o_c_REV/util/util_math.h b/software/o_c_REV/util/util_math.h index dd86f7191..0fb056712 100644 --- a/software/o_c_REV/util/util_math.h +++ b/software/o_c_REV/util/util_math.h @@ -55,7 +55,7 @@ inline uint32_t USAT16(int32_t value) { static inline uint32_t multiply_u32xu32_rshift24(uint32_t a, uint32_t b) __attribute__((always_inline)); static inline uint32_t multiply_u32xu32_rshift24(uint32_t a, uint32_t b) { - register uint32_t lo, hi; + /*register*/ uint32_t lo, hi; asm volatile("umull %0, %1, %2, %3" : "=r" (lo), "=r" (hi) : "r" (a), "r" (b)); return (lo >> 24) | (hi << 8); } @@ -63,7 +63,7 @@ static inline uint32_t multiply_u32xu32_rshift24(uint32_t a, uint32_t b) static inline uint32_t multiply_u32xu32_rshift(uint32_t a, uint32_t b, uint32_t shift) __attribute__((always_inline)); static inline uint32_t multiply_u32xu32_rshift(uint32_t a, uint32_t b, uint32_t shift) { - register uint32_t lo, hi; + /*register*/ uint32_t lo, hi; asm volatile("umull %0, %1, %2, %3" : "=r" (lo), "=r" (hi) : "r" (a), "r" (b)); return (lo >> shift) | (hi << (32 - shift)); } From 10977a78ed7df2996bf8cd801c17f7ee9afeb1c8 Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Sun, 8 Oct 2023 03:13:47 -0700 Subject: [PATCH 306/417] Support DSP instructions on Cortex M4 & M7 --- software/o_c_REV/extern/dspinst.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/extern/dspinst.h b/software/o_c_REV/extern/dspinst.h index c5cd6355b..4c302fa53 100644 --- a/software/o_c_REV/extern/dspinst.h +++ b/software/o_c_REV/extern/dspinst.h @@ -33,7 +33,7 @@ static inline int32_t signed_saturate_rshift(int32_t val, int bits, int rshift) __attribute__((always_inline, unused)); static inline int32_t signed_saturate_rshift(int32_t val, int bits, int rshift) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("ssat %0, %1, %2, asr %3" : "=r" (out) : "I" (bits), "r" (val), "I" (rshift)); return out; @@ -54,7 +54,7 @@ static inline int32_t signed_saturate_rshift(int32_t val, int bits, int rshift) static inline int16_t saturate16(int32_t val) __attribute__((always_inline, unused)); static inline int16_t saturate16(int32_t val) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int16_t out; int32_t tmp; asm volatile("ssat %0, %1, %2" : "=r" (tmp) : "I" (16), "r" (val) ); @@ -69,7 +69,7 @@ static inline int16_t saturate16(int32_t val) static inline int32_t signed_multiply_32x16b(int32_t a, uint32_t b) __attribute__((always_inline, unused)); static inline int32_t signed_multiply_32x16b(int32_t a, uint32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("smulwb %0, %1, %2" : "=r" (out) : "r" (a), "r" (b)); return out; @@ -82,7 +82,7 @@ static inline int32_t signed_multiply_32x16b(int32_t a, uint32_t b) static inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b) __attribute__((always_inline, unused)); static inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("smulwt %0, %1, %2" : "=r" (out) : "r" (a), "r" (b)); return out; @@ -95,7 +95,7 @@ static inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b) static inline int32_t multiply_32x32_rshift32(int32_t a, int32_t b) __attribute__((always_inline, unused)); static inline int32_t multiply_32x32_rshift32(int32_t a, int32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("smmul %0, %1, %2" : "=r" (out) : "r" (a), "r" (b)); return out; @@ -108,7 +108,7 @@ static inline int32_t multiply_32x32_rshift32(int32_t a, int32_t b) static inline uint32_t multiply_u32xu32_rshift32(uint32_t a, uint32_t b) __attribute__((always_inline)); static inline uint32_t multiply_u32xu32_rshift32(uint32_t a, uint32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) uint32_t out, tmp; asm volatile("umull %0, %1, %2, %3" : "=r" (tmp), "=r" (out) : "r" (a), "r" (b)); return out; @@ -121,7 +121,7 @@ static inline uint32_t multiply_u32xu32_rshift32(uint32_t a, uint32_t b) static inline int32_t multiply_32x32_rshift32_rounded(int32_t a, int32_t b) __attribute__((always_inline, unused)); static inline int32_t multiply_32x32_rshift32_rounded(int32_t a, int32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("smmulr %0, %1, %2" : "=r" (out) : "r" (a), "r" (b)); return out; @@ -134,7 +134,7 @@ static inline int32_t multiply_32x32_rshift32_rounded(int32_t a, int32_t b) static inline int32_t multiply_accumulate_32x32_rshift32_rounded(int32_t sum, int32_t a, int32_t b) __attribute__((always_inline, unused)); static inline int32_t multiply_accumulate_32x32_rshift32_rounded(int32_t sum, int32_t a, int32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("smmlar %0, %2, %3, %1" : "=r" (out) : "r" (sum), "r" (a), "r" (b)); return out; @@ -147,7 +147,7 @@ static inline int32_t multiply_accumulate_32x32_rshift32_rounded(int32_t sum, in static inline int32_t multiply_subtract_32x32_rshift32_rounded(int32_t sum, int32_t a, int32_t b) __attribute__((always_inline, unused)); static inline int32_t multiply_subtract_32x32_rshift32_rounded(int32_t sum, int32_t a, int32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("smmlsr %0, %2, %3, %1" : "=r" (out) : "r" (sum), "r" (a), "r" (b)); return out; @@ -161,7 +161,7 @@ static inline int32_t multiply_subtract_32x32_rshift32_rounded(int32_t sum, int3 static inline uint32_t pack_16t_16t(int32_t a, int32_t b) __attribute__((always_inline, unused)); static inline uint32_t pack_16t_16t(int32_t a, int32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("pkhtb %0, %1, %2, asr #16" : "=r" (out) : "r" (a), "r" (b)); return out; @@ -174,7 +174,7 @@ static inline uint32_t pack_16t_16t(int32_t a, int32_t b) static inline uint32_t pack_16t_16b(int32_t a, int32_t b) __attribute__((always_inline, unused)); static inline uint32_t pack_16t_16b(int32_t a, int32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("pkhtb %0, %1, %2" : "=r" (out) : "r" (a), "r" (b)); return out; @@ -187,7 +187,7 @@ static inline uint32_t pack_16t_16b(int32_t a, int32_t b) static inline uint32_t pack_16b_16b(int32_t a, int32_t b) __attribute__((always_inline, unused)); static inline uint32_t pack_16b_16b(int32_t a, int32_t b) { -#if defined(KINETISK) +#if defined(__ARM_ARCH_7EM__) int32_t out; asm volatile("pkhbt %0, %1, %2, lsl #16" : "=r" (out) : "r" (b), "r" (a)); return out; From 734dad8cda1dbbfb83a6295f60ece5987c5c1813 Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Sun, 8 Oct 2023 03:15:03 -0700 Subject: [PATCH 307/417] ifdef drivers for Teensy 3.2 vs 4.1 (placeholders only) --- software/o_c_REV/OC_ADC.cpp | 23 ++++++++++ software/o_c_REV/OC_DAC.cpp | 43 +++++++++++++++++++ software/o_c_REV/OC_config.h | 4 +- software/o_c_REV/OC_gpio.h | 4 ++ software/o_c_REV/o_c_REV.ino | 2 + .../o_c_REV/src/drivers/ADC/ADC_Module.cpp | 3 +- software/o_c_REV/src/drivers/ADC/ADC_Module.h | 9 +++- .../o_c_REV/src/drivers/ADC/OC_util_ADC.cpp | 3 +- .../o_c_REV/src/drivers/ADC/RingBuffer.cpp | 2 + .../o_c_REV/src/drivers/ADC/RingBufferDMA.cpp | 2 + .../drivers/FreqMeasure/OC_FreqMeasure.cpp | 33 ++++++++++++++ .../FreqMeasure/OC_FreqMeasureCapture.h | 3 ++ .../src/drivers/SH1106_128x64_driver.cpp | 32 ++++++++++++-- 13 files changed, 154 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/OC_ADC.cpp b/software/o_c_REV/OC_ADC.cpp index 79e09804f..93a2d7055 100644 --- a/software/o_c_REV/OC_ADC.cpp +++ b/software/o_c_REV/OC_ADC.cpp @@ -5,7 +5,9 @@ namespace OC { +#if defined(__MK20DX256__) /*static*/ ::ADC ADC::adc_; +#endif /*static*/ ADC::CalibrationData *ADC::calibration_data_; /*static*/ uint32_t ADC::raw_[ADC_CHANNEL_LAST]; /*static*/ uint32_t ADC::smoothed_[ADC_CHANNEL_LAST]; @@ -18,6 +20,7 @@ DMAChannel* dma0 = new DMAChannel(false); // dma0 channel, fills adcbuffer_0 DMAChannel* dma1 = new DMAChannel(false); // dma1 channel, updates ADC0_SC1A which holds the channel/pin IDs DMAMEM static volatile uint16_t __attribute__((aligned(DMA_BUF_SIZE+0))) adcbuffer_0[DMA_BUF_SIZE]; +#if defined(__MK20DX256__) /*static*/ void ADC::Init(CalibrationData *calibration_data) { adc_.setReference(ADC_REF_3V3); @@ -34,6 +37,17 @@ DMAMEM static volatile uint16_t __attribute__((aligned(DMA_BUF_SIZE+0))) adcbuff adc_.enableDMA(); } +#elif defined(__IMXRT1062__) +/*static*/ void ADC::Init(CalibrationData *calibration_data) { + calibration_data_ = calibration_data; + std::fill(raw_, raw_ + ADC_CHANNEL_LAST, 0); + std::fill(smoothed_, smoothed_ + ADC_CHANNEL_LAST, 0); + std::fill(adcbuffer_0, adcbuffer_0 + DMA_BUF_SIZE, 0); + // TODO Teensy 4.1 +} + +#endif // __IMXRT1062__ + #ifdef OC_ADC_ENABLE_DMA_INTERRUPT /*static*/ void ADC::DMA_ISR() { ADC::ready_ = true; @@ -50,6 +64,7 @@ DMAMEM static volatile uint16_t __attribute__((aligned(DMA_BUF_SIZE+0))) adcbuff * */ +#if defined(__MK20DX256__) void ADC::Init_DMA() { dma0->begin(true); // allocate the DMA channel @@ -89,6 +104,14 @@ void ADC::Init_DMA() { dma1->enable(); } +#elif defined(__IMXRT1062__) +void ADC::Init_DMA() { + // TODO Teensy 4.1 +} + +#endif // __IMXRT1062__ + + /*static*/void FASTRUN ADC::Scan_DMA() { #ifdef OC_ADC_ENABLE_DMA_INTERRUPT diff --git a/software/o_c_REV/OC_DAC.cpp b/software/o_c_REV/OC_DAC.cpp index 5e8620634..2d96f3fd8 100644 --- a/software/o_c_REV/OC_DAC.cpp +++ b/software/o_c_REV/OC_DAC.cpp @@ -77,9 +77,15 @@ void DAC::Init(CalibrationData *calibration_data) { history_tail_ = 0; memset(history_, 0, sizeof(uint16_t) * kHistoryDepth * DAC_CHANNEL_LAST); +#if defined(__MK20DX256__) if (F_BUS == 60000000 || F_BUS == 48000000) SPIFIFO.begin(DAC_CS, SPICLOCK_30MHz, SPI_MODE0); +#elif defined(__IMXRT1062__) + // TODO Teensy 4.1 + +#endif + set_all(0xffff); Update(); } @@ -206,6 +212,7 @@ uint32_t DAC::store_scaling() { return _scaling; } +#if defined(__MK20DX256__) /*static*/ void DAC::init_Vbias() { /* using MK20 DAC0 for Vbias*/ @@ -218,6 +225,16 @@ void DAC::set_Vbias(uint32_t data) { *(volatile int16_t *)&(DAC0_DAT0L) = data; } +#elif defined(__IMXRT1062__) +void DAC::init_Vbias() { + // TODO Teensy 4.1 +} +void DAC::set_Vbias(uint32_t data) { + // TODO Teensy 4.1 +} + +#endif + /*static*/ DAC::CalibrationData *DAC::calibration_data_ = nullptr; /*static*/ @@ -230,6 +247,7 @@ volatile size_t DAC::history_tail_; uint8_t DAC::DAC_scaling[DAC_CHANNEL_LAST]; }; // namespace OC +#if defined(__MK20DX256__) void set8565_CHA(uint32_t data) { #ifdef BUCHLA_cOC uint32_t _data = data; @@ -294,8 +312,25 @@ void set8565_CHD(uint32_t data) { SPIFIFO.read(); } +#elif defined(__IMXRT1062__) +void set8565_CHA(uint32_t data) { + // TODO Teensy 4.1 +} +void set8565_CHB(uint32_t data) { + // TODO Teensy 4.1 +} +void set8565_CHC(uint32_t data) { + // TODO Teensy 4.1 +} +void set8565_CHD(uint32_t data) { + // TODO Teensy 4.1 +} + +#endif // __IMXRT1062__ + // adapted from https://github.com/xxxajk/spi4teensy3 (MISO disabled) : +#if defined(__MK20DX256__) void SPI_init() { uint32_t ctar0, ctar1; @@ -328,4 +363,12 @@ void SPI_init() { SPI0_MCR = mcr; } } + +#elif defined(__IMXRT1062__) +void SPI_init() { + // TODO Teensy 4.1 +} + +#endif // __IMXRT1062__ + // OC_DAC diff --git a/software/o_c_REV/OC_config.h b/software/o_c_REV/OC_config.h index 4b2ce8ee0..9639a5621 100644 --- a/software/o_c_REV/OC_config.h +++ b/software/o_c_REV/OC_config.h @@ -1,8 +1,8 @@ #ifndef OC_CONFIG_H_ #define OC_CONFIG_H_ -#if F_CPU != 120000000 -#error "Please compile O&C firmware with CPU speed 120MHz" +#if defined(__MK20DX256__) && F_CPU != 120000000 +#error "Please compile O&C firmware for Teensy 3.2 with CPU speed 120MHz" #endif // 60us = 16.666...kHz : Works, SPI transfer ends 2uS before next ISR diff --git a/software/o_c_REV/OC_gpio.h b/software/o_c_REV/OC_gpio.h index c9e14446b..37d3bbafd 100644 --- a/software/o_c_REV/OC_gpio.h +++ b/software/o_c_REV/OC_gpio.h @@ -80,6 +80,7 @@ namespace OC { void inline pinMode(uint8_t pin, uint8_t mode) { +#if defined(__MK20DX256__) volatile uint32_t *config; if (pin >= CORE_NUM_DIGITAL) return; @@ -114,6 +115,9 @@ void inline pinMode(uint8_t pin, uint8_t mode) { *config = 0; } } +#else + ::pinMode(pin, mode); // for Teensy 4.x, just use normal pinMode +#endif } } diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 398c36c28..074868611 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -98,7 +98,9 @@ void FASTRUN CORE_timer_ISR() { void setup() { delay(50); +#if defined(__MK20DX256__) NVIC_SET_PRIORITY(IRQ_PORTB, 0); // TR1 = 0 = PTB16 +#endif SPI_init(); SERIAL_PRINTLN("* O&C BOOTING..."); SERIAL_PRINTLN("* %s", OC_VERSION); diff --git a/software/o_c_REV/src/drivers/ADC/ADC_Module.cpp b/software/o_c_REV/src/drivers/ADC/ADC_Module.cpp index c9246cb51..a09597594 100644 --- a/software/o_c_REV/src/drivers/ADC/ADC_Module.cpp +++ b/software/o_c_REV/src/drivers/ADC/ADC_Module.cpp @@ -27,7 +27,7 @@ * */ - +#if defined(__MK20DX256__) #include "ADC_Module.h" @@ -1304,3 +1304,4 @@ void ADC_Module::stopPDB() { } #endif +#endif // __MK20DX256__ diff --git a/software/o_c_REV/src/drivers/ADC/ADC_Module.h b/software/o_c_REV/src/drivers/ADC/ADC_Module.h index 104473906..2140d6918 100644 --- a/software/o_c_REV/src/drivers/ADC/ADC_Module.h +++ b/software/o_c_REV/src/drivers/ADC/ADC_Module.h @@ -44,6 +44,9 @@ #define ADC_TEENSY_3_4 #elif defined(__MK66FX1M0__) // Teensy 3.5 #define ADC_TEENSY_3_5 +#elif defined(__IMXRT1062__) // Teensy 4.x (not really supported, but don't error) +#define ADC_NUM_ADCS 1 +#define ADC_DIFF_PAIRS 0 #else #error "Board not supported!" #endif @@ -298,6 +301,8 @@ cycles. ADHSC should be used when the ADCLK exceeds the limit for ADHSC = 0. #define ADC_CFG1_HI_SPEED (ADC_CFG1_2MHZ) #define ADC_CFG1_VERY_HIGH_SPEED ADC_CFG1_HI_SPEED +#elif defined(__IMXRT1062__) // Teensy 4.0 or 4.1 +// don't give a compile error #else #error "F_BUS must be 108, 60, 56, 54, 48, 40, 36, 24, 4 or 2 MHz" #endif @@ -592,7 +597,7 @@ class ADC_Module { //! Disable PGA void disablePGA(); - +#if defined(__MK20DX256__) //! Set continuous conversion mode void continuousMode() __attribute__((always_inline)) { setBit(ADC_SC3, ADC_SC3_ADCO_BIT); @@ -651,7 +656,7 @@ class ADC_Module { volatile bool isPGAEnabled() __attribute__((always_inline)) { return getBit(ADC_PGA, ADC_PGA_PGAEN_BIT); } - +#endif //////////////// INFORMATION ABOUT VALID PINS ////////////////// diff --git a/software/o_c_REV/src/drivers/ADC/OC_util_ADC.cpp b/software/o_c_REV/src/drivers/ADC/OC_util_ADC.cpp index 1c5d2a9a2..bec21261f 100644 --- a/software/o_c_REV/src/drivers/ADC/OC_util_ADC.cpp +++ b/software/o_c_REV/src/drivers/ADC/OC_util_ADC.cpp @@ -26,7 +26,7 @@ /* ADC.cpp: Implements the control of one or more ADC modules of Teensy 3.x, LC * */ - +#if defined(__MK20DX256__) #include "OC_util_ADC.h" @@ -1305,4 +1305,5 @@ ADC::Sync_result ADC::readSynchronizedContinuous() {ADC::Sync_result res={0}; re void ADC::stopSynchronizedContinuous() {} #endif +#endif // __MK20DX256__ diff --git a/software/o_c_REV/src/drivers/ADC/RingBuffer.cpp b/software/o_c_REV/src/drivers/ADC/RingBuffer.cpp index 470b1b1f4..6a9fe8e39 100644 --- a/software/o_c_REV/src/drivers/ADC/RingBuffer.cpp +++ b/software/o_c_REV/src/drivers/ADC/RingBuffer.cpp @@ -23,6 +23,7 @@ * SOFTWARE. */ +#if defined(__MK20DX256__) #include "RingBuffer.h" @@ -62,3 +63,4 @@ int RingBuffer::read() { int RingBuffer::increase(int p) { return (p + 1)&(2*b_size-1); } +#endif // __MK20DX256__ diff --git a/software/o_c_REV/src/drivers/ADC/RingBufferDMA.cpp b/software/o_c_REV/src/drivers/ADC/RingBufferDMA.cpp index 367b43165..4b39d0ee9 100644 --- a/software/o_c_REV/src/drivers/ADC/RingBufferDMA.cpp +++ b/software/o_c_REV/src/drivers/ADC/RingBufferDMA.cpp @@ -23,6 +23,7 @@ * SOFTWARE. */ +#if defined(__MK20DX256__) #include "RingBufferDMA.h" // Point static_ringbuffer_dma to an RingBufferDMA object @@ -171,3 +172,4 @@ int16_t RingBufferDMA::read() { uint16_t RingBufferDMA::increase(uint16_t p) { return (p + 1)&(2*b_size-1); } +#endif // __MK20DX256__ diff --git a/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasure.cpp b/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasure.cpp index 7f50a5650..f7f9ee171 100755 --- a/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasure.cpp +++ b/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasure.cpp @@ -26,6 +26,8 @@ #include "OC_FreqMeasure.h" #include "OC_FreqMeasureCapture.h" +#if defined(__MK20DX256__) + #define FREQMEASURE_BUFFER_LEN 12 static volatile uint32_t buffer_value[FREQMEASURE_BUFFER_LEN]; static volatile uint8_t buffer_head; @@ -119,5 +121,36 @@ void FTM_ISR_NAME (void) } } +#elif defined(__IMXRT1062__) +void FreqMeasureClass::begin(void) +{ + // TODO Teensy 4.1 +} + +uint8_t FreqMeasureClass::available(void) +{ + // TODO Teensy 4.1 + return 0; +} + +uint32_t FreqMeasureClass::read(void) +{ + // TODO Teensy 4.1 + return 0; +} + +float FreqMeasureClass::countToFrequency(uint32_t count) +{ + // TODO Teensy 4.1 + return 0.0f; +} + +void FreqMeasureClass::end(void) +{ + // TODO Teensy 4.1 +} + +#endif // __IMXRT1062__ + FreqMeasureClass FreqMeasure; diff --git a/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasureCapture.h b/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasureCapture.h index ef3f233d8..40079031a 100755 --- a/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasureCapture.h +++ b/software/o_c_REV/src/drivers/FreqMeasure/OC_FreqMeasureCapture.h @@ -61,6 +61,9 @@ #elif defined(__AVR_AT90USB162__) #define CAPTURE_USE_TIMER1 // ICP1 is pin 16 +// Teensy 4.0 & 4.1 +#elif defined(__IMXRT1062__) + // Sanguino #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) #define CAPTURE_USE_TIMER1 // ICP1 is pin 14 diff --git a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp index a6ab69d93..79e27f85b 100644 --- a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp +++ b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp @@ -114,13 +114,19 @@ void SH1106_128x64_Driver::Init() { digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0), /* disable chip */ #ifdef DMA_PAGE_TRANSFER +#if defined(__MK20DX256__) page_dma.destination((volatile uint8_t&)SPI0_PUSHR); page_dma.transferSize(1); page_dma.transferCount(kPageSize); page_dma.disableOnCompletion(); page_dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX); page_dma.disable(); -#endif + +#elif defined(__IMXRT1062__) + // TODO Teensy 4.1 + +#endif // __IMXRT1062__ +#endif // DMA_PAGE_TRANSFER Clear(); } @@ -138,6 +144,7 @@ void SH1106_128x64_Driver::Flush() { // would be pulled high too soon. Why this effect is more pronounced with // gcc >= 5.4.1 is a different mystery. +#if defined(__MK20DX256__) if (page_dma_active) { while (!page_dma.complete()) { } while (0 != (SPI0_SR & 0x0000f000)); // SPIx_SR TXCTR @@ -151,7 +158,12 @@ void SH1106_128x64_Driver::Flush() { SPI0_RSER = 0; SPI0_SR = 0xFF0F0000; } -#endif + +#elif defined(__IMXRT1062__) + // TODO Teensy 4.1 + +#endif // __IMXRT1062__ +#endif // DMA_PAGE_TRANSFER } static uint8_t empty_page[SH1106_128x64_Driver::kPageSize]; @@ -186,18 +198,26 @@ void SH1106_128x64_Driver::SendPage(uint_fast8_t index, const uint8_t *data) { #ifdef DMA_PAGE_TRANSFER // DmaSpi.h::pre_cs_impl() +#if defined(__MK20DX256__) SPI0_SR = 0xFF0F0000; SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; page_dma.sourceBuffer(data, kPageSize); page_dma.enable(); // go page_dma_active = true; -#else + +#elif defined(__IMXRT1062__) + // TODO Teensy 4.1 + page_dma_active = true; + +#endif // __IMXRT1062__ +#else // not DMA_PAGE_TRANSFER SPI_send(data, kPageSize); digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0) #endif } +#if defined(__MK20DX256__) void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { // adapted from https://github.com/xxxajk/spi4teensy3 @@ -241,6 +261,12 @@ void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { } } +#elif defined(__IMXRT1062__) +void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { + // TODO Teensy 4.1 +} + +#endif // __IMXRT1062__ /*static*/ void SH1106_128x64_Driver::AdjustOffset(uint8_t offset) { SH1106_data_start_seq[1] = offset; // lower 4 bits of col adr From 2590059ca6b17a8f46292badc27db33047413b9a Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Tue, 10 Oct 2023 07:40:50 -0700 Subject: [PATCH 308/417] DAC and display driver for Teensy 4.x - work in progress --- software/o_c_REV/OC_ADC.cpp | 2 + software/o_c_REV/OC_DAC.cpp | 61 ++++++++- .../src/drivers/SH1106_128x64_driver.cpp | 126 +++++++++++++++--- 3 files changed, 164 insertions(+), 25 deletions(-) diff --git a/software/o_c_REV/OC_ADC.cpp b/software/o_c_REV/OC_ADC.cpp index 93a2d7055..a8fe6d284 100644 --- a/software/o_c_REV/OC_ADC.cpp +++ b/software/o_c_REV/OC_ADC.cpp @@ -107,6 +107,8 @@ void ADC::Init_DMA() { #elif defined(__IMXRT1062__) void ADC::Init_DMA() { // TODO Teensy 4.1 + dma0->begin(true); // allocate the DMA channel + dma1->begin(true); // allocate the DMA channel } #endif // __IMXRT1062__ diff --git a/software/o_c_REV/OC_DAC.cpp b/software/o_c_REV/OC_DAC.cpp index 2d96f3fd8..26a53db6a 100644 --- a/software/o_c_REV/OC_DAC.cpp +++ b/software/o_c_REV/OC_DAC.cpp @@ -40,6 +40,9 @@ #include "OC_calibration.h" #include "OC_autotune_presets.h" #include "OC_autotune.h" +#if defined(__IMXRT1062__) +#include +#endif #define SPICLOCK_30MHz (SPI_CTAR_PBR(0) | SPI_CTAR_BR(0) | SPI_CTAR_DBR) //(60 / 2) * ((1+1)/2) = 30 MHz (= 24MHz, when F_BUS == 48000000) @@ -82,8 +85,8 @@ void DAC::Init(CalibrationData *calibration_data) { SPIFIFO.begin(DAC_CS, SPICLOCK_30MHz, SPI_MODE0); #elif defined(__IMXRT1062__) - // TODO Teensy 4.1 - + SPI.begin(); + IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 3; // DAC CS pin controlled by SPI #endif set_all(0xffff); @@ -312,18 +315,62 @@ void set8565_CHD(uint32_t data) { SPIFIFO.read(); } -#elif defined(__IMXRT1062__) +#elif defined(__IMXRT1062__) // Teensy 4.1 void set8565_CHA(uint32_t data) { - // TODO Teensy 4.1 + LPSPI4_TCR = (LPSPI4_TCR & 0xF8000000) | LPSPI_TCR_FRAMESZ(23) + | LPSPI_TCR_PCS(0) | LPSPI_TCR_RXMSK; + #ifdef BUCHLA_cOC + uint32_t _data = data; + #else + uint32_t _data = OC::DAC::MAX_VALUE - data; + #endif + #ifdef FLIP_180 + _data = (0b00010110 << 16) | (data & 0xFFFF); + #else + _data = (0b00010000 << 16) | (data & 0xFFFF); + #endif + LPSPI4_TDR = _data; } + void set8565_CHB(uint32_t data) { - // TODO Teensy 4.1 + #ifdef BUCHLA_cOC + uint32_t _data = data; + #else + uint32_t _data = OC::DAC::MAX_VALUE - data; + #endif + #ifdef FLIP_180 + _data = (0b00010100 << 16) | (data & 0xFFFF); + #else + _data = (0b00010010 << 16) | (data & 0xFFFF); + #endif + LPSPI4_TDR = _data; } void set8565_CHC(uint32_t data) { - // TODO Teensy 4.1 + #ifdef BUCHLA_cOC + uint32_t _data = data; + #else + uint32_t _data = OC::DAC::MAX_VALUE - data; + #endif + #ifdef FLIP_180 + _data = (0b00010010 << 16) | (data & 0xFFFF); + #else + _data = (0b00010100 << 16) | (data & 0xFFFF); + #endif + LPSPI4_TDR = _data; } void set8565_CHD(uint32_t data) { - // TODO Teensy 4.1 + #ifdef BUCHLA_cOC + uint32_t _data = data; + #else + uint32_t _data = OC::DAC::MAX_VALUE - data; + #endif + #ifdef FLIP_180 + _data = (0b00010000 << 16) | (data & 0xFFFF); + #else + _data = (0b00010110 << 16) | (data & 0xFFFF); + #endif + LPSPI4_SR = LPSPI_SR_TCF; // clear transmit complete flag before last write to FIFO + LPSPI4_TDR = _data; } #endif // __IMXRT1062__ diff --git a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp index 79e27f85b..30211ec5c 100644 --- a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp +++ b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp @@ -27,8 +27,13 @@ #include "SH1106_128x64_driver.h" #include "../../OC_gpio.h" #include "../../OC_options.h" +#include "../../util/util_debugpins.h" +#if defined(__IMXRT1062__) +#include +#endif // NOTE: Don't disable DMA unless you absolutely know what you're doing. It will hurt you. +#if defined(__MK20DX256__) #define DMA_PAGE_TRANSFER #ifdef DMA_PAGE_TRANSFER #include @@ -39,6 +44,14 @@ static bool page_dma_active = false; #define SPI_SR_RXCTR 0XF0 #endif +// Teensy 4.1 has large SPI FIFO, so FIFO and interrupt is used rather than DMA +#elif defined(__IMXRT1062__) +static void spi_sendpage_isr(); +static int sendpage_state; +static int sendpage_count; +static const uint32_t *sendpage_src; +#endif + static uint8_t SH1106_data_start_seq[] = { // u8g_dev_ssd1306_128x64_data_start 0x10, /* set upper 4 bit of the col adr to 0 */ @@ -109,24 +122,30 @@ void SH1106_128x64_Driver::Init() { digitalWriteFast(OLED_CS, OLED_CS_ACTIVE); // U8G_ESC_CS(1), /* enable chip */ + // assumes OC::DAC:Init already initialized SPI SPI_send(SH1106_init_seq, sizeof(SH1106_init_seq)); digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0), /* disable chip */ -#ifdef DMA_PAGE_TRANSFER #if defined(__MK20DX256__) +#ifdef DMA_PAGE_TRANSFER page_dma.destination((volatile uint8_t&)SPI0_PUSHR); page_dma.transferSize(1); page_dma.transferCount(kPageSize); page_dma.disableOnCompletion(); page_dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX); page_dma.disable(); +#endif // DMA_PAGE_TRANSFER #elif defined(__IMXRT1062__) - // TODO Teensy 4.1 - + LPSPI4_IER = 0; + LPSPI4_SR = 0x3F00; // clear any prior pending interrupt flags + LPSPI4_FCR = LPSPI_FCR_RXWATER(0) | LPSPI_FCR_TXWATER(3); + attachInterruptVector(IRQ_LPSPI4, spi_sendpage_isr); + NVIC_CLEAR_PENDING(IRQ_LPSPI4); + NVIC_SET_PRIORITY(IRQ_LPSPI4, 48); + NVIC_ENABLE_IRQ(IRQ_LPSPI4); #endif // __IMXRT1062__ -#endif // DMA_PAGE_TRANSFER Clear(); } @@ -158,11 +177,7 @@ void SH1106_128x64_Driver::Flush() { SPI0_RSER = 0; SPI0_SR = 0xFF0F0000; } - -#elif defined(__IMXRT1062__) - // TODO Teensy 4.1 - -#endif // __IMXRT1062__ +#endif // __MK20DX256__ #endif // DMA_PAGE_TRANSFER } @@ -187,6 +202,7 @@ void SH1106_128x64_Driver::Clear() { digitalWriteFast(OLED_DC, HIGH); } +#if defined(__MK20DX256__) /*static*/ void SH1106_128x64_Driver::SendPage(uint_fast8_t index, const uint8_t *data) { SH1106_data_start_seq[2] = 0xb0 | index; @@ -198,25 +214,86 @@ void SH1106_128x64_Driver::SendPage(uint_fast8_t index, const uint8_t *data) { #ifdef DMA_PAGE_TRANSFER // DmaSpi.h::pre_cs_impl() -#if defined(__MK20DX256__) SPI0_SR = 0xFF0F0000; SPI0_RSER = SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS | SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; page_dma.sourceBuffer(data, kPageSize); page_dma.enable(); // go page_dma_active = true; - -#elif defined(__IMXRT1062__) - // TODO Teensy 4.1 - page_dma_active = true; - -#endif // __IMXRT1062__ #else // not DMA_PAGE_TRANSFER SPI_send(data, kPageSize); digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0) #endif } +#elif defined(__IMXRT1062__) +/*static*/ +void SH1106_128x64_Driver::SendPage(uint_fast8_t index, const uint8_t *data) { + SH1106_data_start_seq[2] = 0xb0 | index; + sendpage_state = 0; + sendpage_src = (const uint32_t *)data; // frame buffer is 32 bit aligned + sendpage_count = kPageSize >> 2; // number of 32 bit words to write into FIFO + // don't clear SPI status flags, already cleared before DAC data was loaded into FIFO + LPSPI4_IER = LPSPI_IER_TCIE; // run spi_sendpage_isr() when DAC data complete +} + +static void spi_sendpage_isr() { + DEBUG_PIN_SCOPE(OC_GPIO_DEBUG_PIN2); + uint32_t status = LPSPI4_SR; + LPSPI4_SR = status; // clear interrupt status flags + if (sendpage_state == 0) { + // begin command phase + digitalWriteFast(OLED_DC, LOW); + digitalWriteFast(OLED_CS, OLED_CS_ACTIVE); + LPSPI4_TCR = (LPSPI4_TCR & 0xF8000000) | LPSPI_TCR_FRAMESZ(23) + | LPSPI_TCR_PCS(3) | LPSPI_TCR_RXMSK; + LPSPI4_TDR = (SH1106_data_start_seq[2] << 16) | (SH1106_data_start_seq[1] << 8) + | SH1106_data_start_seq[0]; + sendpage_state = 1; + LPSPI4_IER = LPSPI_IER_TCIE; // run spi_sendpage_isr() when command complete + return; // FIFO loaded with 3 byte command + } + if (sendpage_state == 1) { + // begin data phase + digitalWriteFast(OLED_DC, HIGH); + LPSPI4_CR |= LPSPI_CR_RRF | LPSPI_CR_RTF; // clear FIFO + LPSPI4_IER = LPSPI_IER_TDIE; // run spi_sendpage_isr() when FIFO wants data + const size_t nbits = SH1106_128x64_Driver::kPageSize * 8; + LPSPI4_TCR = (LPSPI4_TCR & 0xF8000000) | LPSPI_TCR_FRAMESZ(nbits-1) + | LPSPI_TCR_PCS(3) | LPSPI_TCR_RXMSK | LPSPI_TCR_BYSW; + sendpage_state = 2; + } + if (sendpage_state == 2) { + // feed display data into the FIFO + if (!(status & LPSPI_SR_TDF)) return; + const int fifo_space = 16 - (LPSPI4_FSR & 0x1F); + if (fifo_space < sendpage_count) { + // we have more data than the FIFO can hold + LPSPI4_IER = LPSPI_IER_TDIE; // run spi_sendpage_isr() when FIFO wants more data + for (int i=0; i < fifo_space; i++) { + LPSPI4_TDR = *sendpage_src++; + asm volatile ("dsb":::"memory"); + } + sendpage_count -= fifo_space; + } else { + // remaining data fits in FIFO + LPSPI4_IER = LPSPI_IER_TCIE; // run spi_sendpage_isr() when all display data finished + for (int i=0; i < sendpage_count; i++) { + LPSPI4_TDR = *sendpage_src++; + asm volatile ("dsb":::"memory"); + } + sendpage_count = 0; + sendpage_state = 3; + } + return; + } else { + // finished + digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); + LPSPI4_IER = 0; + } +} +#endif // __IMXRT1062__ + #if defined(__MK20DX256__) void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { @@ -263,10 +340,23 @@ void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { #elif defined(__IMXRT1062__) void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { - // TODO Teensy 4.1 + delayMicroseconds(10); + SPI.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0)); + // TODO: check if we're also pulling DAC CS low? + //LPSPI4_TCR |= LPSPI_TCR_PCS(3); // do not interfere with DAC's CS pin +#if 0 + const uint8_t *p = bufr; + for (size_t i=0; i < n; i++) { + SPI.transfer(*p++); + } +#else + SPI.transfer(bufr, NULL, n); +#endif + SPI.endTransaction(); + delayMicroseconds(10); } - #endif // __IMXRT1062__ + /*static*/ void SH1106_128x64_Driver::AdjustOffset(uint8_t offset) { SH1106_data_start_seq[1] = offset; // lower 4 bits of col adr From e8aa84dc53607a5c309438fdb777f8ce1f131347 Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Wed, 11 Oct 2023 06:44:53 -0700 Subject: [PATCH 309/417] Don't init display too quickly with Teensy 4.1 --- .../src/drivers/SH1106_128x64_driver.cpp | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp index 30211ec5c..a24508f5d 100644 --- a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp +++ b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp @@ -126,6 +126,7 @@ void SH1106_128x64_Driver::Init() { SPI_send(SH1106_init_seq, sizeof(SH1106_init_seq)); digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0), /* disable chip */ + delayMicroseconds(1); #if defined(__MK20DX256__) #ifdef DMA_PAGE_TRANSFER @@ -195,11 +196,13 @@ void SH1106_128x64_Driver::Clear() { for (size_t p = 0; p < kNumPages; ++p) SPI_send(empty_page, kPageSize); digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); // U8G_ESC_CS(0) + delayMicroseconds(1); digitalWriteFast(OLED_DC, LOW); digitalWriteFast(OLED_CS, OLED_CS_ACTIVE); SPI_send(SH1106_display_on_seq, sizeof(SH1106_display_on_seq)); digitalWriteFast(OLED_DC, HIGH); + digitalWriteFast(OLED_CS, OLED_CS_INACTIVE); } #if defined(__MK20DX256__) @@ -340,20 +343,10 @@ void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { #elif defined(__IMXRT1062__) void SH1106_128x64_Driver::SPI_send(void *bufr, size_t n) { - delayMicroseconds(10); - SPI.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0)); - // TODO: check if we're also pulling DAC CS low? - //LPSPI4_TCR |= LPSPI_TCR_PCS(3); // do not interfere with DAC's CS pin -#if 0 - const uint8_t *p = bufr; - for (size_t i=0; i < n; i++) { - SPI.transfer(*p++); - } -#else + SPI.beginTransaction(SPISettings(24000000, MSBFIRST, SPI_MODE0)); + LPSPI4_TCR |= LPSPI_TCR_PCS(3); // do not interfere with DAC's CS pin SPI.transfer(bufr, NULL, n); -#endif SPI.endTransaction(); - delayMicroseconds(10); } #endif // __IMXRT1062__ From f281fd7dc9240fd7a10a50f4b82fc41901da8b96 Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Wed, 11 Oct 2023 08:54:28 -0700 Subject: [PATCH 310/417] Fix SH1106_data_start_seq byte order on Teensy 4.1 --- software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp index a24508f5d..f866322a1 100644 --- a/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp +++ b/software/o_c_REV/src/drivers/SH1106_128x64_driver.cpp @@ -250,8 +250,8 @@ static void spi_sendpage_isr() { digitalWriteFast(OLED_CS, OLED_CS_ACTIVE); LPSPI4_TCR = (LPSPI4_TCR & 0xF8000000) | LPSPI_TCR_FRAMESZ(23) | LPSPI_TCR_PCS(3) | LPSPI_TCR_RXMSK; - LPSPI4_TDR = (SH1106_data_start_seq[2] << 16) | (SH1106_data_start_seq[1] << 8) - | SH1106_data_start_seq[0]; + LPSPI4_TDR = (SH1106_data_start_seq[0] << 16) | (SH1106_data_start_seq[1] << 8) + | SH1106_data_start_seq[2]; sendpage_state = 1; LPSPI4_IER = LPSPI_IER_TCIE; // run spi_sendpage_isr() when command complete return; // FIFO loaded with 3 byte command From 8a1b13eef0458c82b6a16866a3de33f256c128c3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 11 Oct 2023 18:45:05 -0400 Subject: [PATCH 311/417] build config for Teensy 4.0 --- software/o_c_REV/platformio.ini | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index c77e79080..d555660ca 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -42,6 +42,35 @@ extra_scripts = pre:resources/progname.py upload_protocol = teensy-cli +[env:T40] +board = teensy40 +board_build.f_cpu = 600000000 +build_flags = + -DTEENSY_OPT_SMALLEST_CODE + -DUSB_MIDI + -Iextern + -DPEWPEWPEW + -DDRUMMAP_GRIDS2 + -DENABLE_APP_CALIBR8OR + -DENABLE_APP_SCENES + -DENABLE_APP_ENIGMA + -DENABLE_APP_MIDI + -DENABLE_APP_PONG + -DENABLE_APP_PIQUED + -DENABLE_APP_POLYLFO + -DENABLE_APP_H1200 + -DENABLE_APP_BYTEBEATGEN + -DENABLE_APP_NEURAL_NETWORK + -DENABLE_APP_DARKEST_TIMELINE + -DENABLE_APP_LORENZ + -DENABLE_APP_ASR + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_METAQ + -DENABLE_APP_CHORDS + -DENABLE_APP_SEQUINS + -DENABLE_APP_AUTOMATONNETZ + -DENABLE_APP_BBGEN + [env:pewpewpew] ; phazer's choice build build_flags = From a3f644f177a645e505ee25e9ca47664c832bc8c7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 11 Oct 2023 21:35:41 -0400 Subject: [PATCH 312/417] Fix inverted DAC on T4.x --- software/o_c_REV/OC_DAC.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/software/o_c_REV/OC_DAC.cpp b/software/o_c_REV/OC_DAC.cpp index 26a53db6a..58695f487 100644 --- a/software/o_c_REV/OC_DAC.cpp +++ b/software/o_c_REV/OC_DAC.cpp @@ -325,9 +325,9 @@ void set8565_CHA(uint32_t data) { uint32_t _data = OC::DAC::MAX_VALUE - data; #endif #ifdef FLIP_180 - _data = (0b00010110 << 16) | (data & 0xFFFF); + _data = (0b00010110 << 16) | (_data & 0xFFFF); #else - _data = (0b00010000 << 16) | (data & 0xFFFF); + _data = (0b00010000 << 16) | (_data & 0xFFFF); #endif LPSPI4_TDR = _data; } @@ -339,9 +339,9 @@ void set8565_CHB(uint32_t data) { uint32_t _data = OC::DAC::MAX_VALUE - data; #endif #ifdef FLIP_180 - _data = (0b00010100 << 16) | (data & 0xFFFF); + _data = (0b00010100 << 16) | (_data & 0xFFFF); #else - _data = (0b00010010 << 16) | (data & 0xFFFF); + _data = (0b00010010 << 16) | (_data & 0xFFFF); #endif LPSPI4_TDR = _data; } @@ -352,9 +352,9 @@ void set8565_CHC(uint32_t data) { uint32_t _data = OC::DAC::MAX_VALUE - data; #endif #ifdef FLIP_180 - _data = (0b00010010 << 16) | (data & 0xFFFF); + _data = (0b00010010 << 16) | (_data & 0xFFFF); #else - _data = (0b00010100 << 16) | (data & 0xFFFF); + _data = (0b00010100 << 16) | (_data & 0xFFFF); #endif LPSPI4_TDR = _data; } @@ -365,9 +365,9 @@ void set8565_CHD(uint32_t data) { uint32_t _data = OC::DAC::MAX_VALUE - data; #endif #ifdef FLIP_180 - _data = (0b00010000 << 16) | (data & 0xFFFF); + _data = (0b00010000 << 16) | (_data & 0xFFFF); #else - _data = (0b00010110 << 16) | (data & 0xFFFF); + _data = (0b00010110 << 16) | (_data & 0xFFFF); #endif LPSPI4_SR = LPSPI_SR_TCF; // clear transmit complete flag before last write to FIFO LPSPI4_TDR = _data; From f117bd1bbf09723af9fba6976015a5f43120c8c7 Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Sun, 15 Oct 2023 08:53:14 -0700 Subject: [PATCH 313/417] Add ADC driver for Teensy 4.x (via Paul Stoffregen) Scan_DMA function fixed up by djphazer --- software/o_c_REV/OC_ADC.cpp | 196 +++++++++++++++++++++++++++++++++-- software/o_c_REV/o_c_REV.ino | 7 ++ 2 files changed, 197 insertions(+), 6 deletions(-) diff --git a/software/o_c_REV/OC_ADC.cpp b/software/o_c_REV/OC_ADC.cpp index a8fe6d284..9bbaf2d04 100644 --- a/software/o_c_REV/OC_ADC.cpp +++ b/software/o_c_REV/OC_ADC.cpp @@ -15,11 +15,71 @@ namespace OC { /*static*/ volatile bool ADC::ready_; #endif +#if defined(__MK20DX256__) constexpr uint16_t ADC::SCA_CHANNEL_ID[DMA_NUM_CH]; // ADCx_SCA register channel numbers DMAChannel* dma0 = new DMAChannel(false); // dma0 channel, fills adcbuffer_0 DMAChannel* dma1 = new DMAChannel(false); // dma1 channel, updates ADC0_SC1A which holds the channel/pin IDs DMAMEM static volatile uint16_t __attribute__((aligned(DMA_BUF_SIZE+0))) adcbuffer_0[DMA_BUF_SIZE]; +#elif defined(__IMXRT1062__) +#define ADC_SAMPLE_RATE 66000.0f +extern "C" void xbar_connect(unsigned int input, unsigned int output); +DMAChannel dma0(false); +typedef struct { + uint16_t adc[4]; +} adcframe_t; +// sizeof(adc_buffer) must be multiple of 32 byte cache row size +static const int adc_buffer_len = 32; +static DMAMEM __attribute__((aligned(32))) adcframe_t adc_buffer[adc_buffer_len]; +static PROGMEM const uint8_t adc2_pin_to_channel[] = { + 7, // 0/A0 AD_B1_02 + 8, // 1/A1 AD_B1_03 + 12, // 2/A2 AD_B1_07 + 11, // 3/A3 AD_B1_06 + 6, // 4/A4 AD_B1_01 + 5, // 5/A5 AD_B1_00 + 15, // 6/A6 AD_B1_10 + 0, // 7/A7 AD_B1_11 + 13, // 8/A8 AD_B1_08 + 14, // 9/A9 AD_B1_09 + 255, // 10/A10 AD_B0_12 - can't use this pin! + 255, // 11/A11 AD_B0_13 - can't use this pin! + 3, // 12/A12 AD_B1_14 + 4, // 13/A13 AD_B1_15 + 7, // 14/A0 AD_B1_02 + 8, // 15/A1 AD_B1_03 + 12, // 16/A2 AD_B1_07 + 11, // 17/A3 AD_B1_06 + 6, // 18/A4 AD_B1_01 + 5, // 19/A5 AD_B1_00 + 15, // 20/A6 AD_B1_10 + 0, // 21/A7 AD_B1_11 + 13, // 22/A8 AD_B1_08 + 14, // 23/A9 AD_B1_09 + 255, // 24/A10 AD_B0_12 - can't use this pin! + 255, // 25/A11 AD_B0_13 - can't use this pin! + 3, // 26/A12 AD_B1_14 + 4, // 27/A13 AD_B1_15 +#ifdef ARDUINO_TEENSY41 + 255, // 28 + 255, // 29 + 255, // 30 + 255, // 31 + 255, // 32 + 255, // 33 + 255, // 34 + 255, // 35 + 255, // 36 + 255, // 37 + 1, // 38/A14 AD_B1_12 + 2, // 39/A15 AD_B1_13 + 9, // 40/A16 AD_B1_04 + 10, // 41/A17 AD_B1_05 +#endif +}; +#endif // __IMXRT1062__ + + #if defined(__MK20DX256__) /*static*/ void ADC::Init(CalibrationData *calibration_data) { @@ -42,8 +102,6 @@ DMAMEM static volatile uint16_t __attribute__((aligned(DMA_BUF_SIZE+0))) adcbuff calibration_data_ = calibration_data; std::fill(raw_, raw_ + ADC_CHANNEL_LAST, 0); std::fill(smoothed_, smoothed_ + ADC_CHANNEL_LAST, 0); - std::fill(adcbuffer_0, adcbuffer_0 + DMA_BUF_SIZE, 0); - // TODO Teensy 4.1 } #endif // __IMXRT1062__ @@ -106,14 +164,102 @@ void ADC::Init_DMA() { #elif defined(__IMXRT1062__) void ADC::Init_DMA() { - // TODO Teensy 4.1 - dma0->begin(true); // allocate the DMA channel - dma1->begin(true); // allocate the DMA channel -} + // Ornament & Crime CV inputs are 19/A5 18/A4 20/A6 17/A3 +#ifdef FLIP_180 + const int pin4 = A5; + const int pin3 = A4; + const int pin2 = A6; + const int pin1 = A3; +#else + const int pin1 = A5; + const int pin2 = A4; + const int pin3 = A6; + const int pin4 = A3; +#endif + pinMode(pin1, INPUT_DISABLE); + pinMode(pin2, INPUT_DISABLE); + pinMode(pin3, INPUT_DISABLE); + pinMode(pin4, INPUT_DISABLE); + + // configure a timer to trigger ADC + const int comp1 = ((float)F_BUS_ACTUAL) / (ADC_SAMPLE_RATE) / 2.0f + 0.5f; + TMR4_ENBL &= ~(1<<3); + TMR4_SCTRL3 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE; + TMR4_CSCTRL3 = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_TCF1EN; + TMR4_CNTR3 = 0; + TMR4_LOAD3 = 0; + TMR4_COMP13 = comp1; + TMR4_CMPLD13 = comp1; + TMR4_CTRL3 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(3); + TMR4_DMA3 = TMR_DMA_CMPLD1DE; + TMR4_CNTR3 = 0; + TMR4_ENBL |= (1<<3); + + // connect the timer output the ADC_ETC input + const int trigger = 4; // 0-3 for ADC1, 4-7 for ADC2 + CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); + xbar_connect(XBARA1_IN_QTIMER4_TIMER3, XBARA1_OUT_ADC_ETC_TRIG00 + trigger); + + // turn on ADC_ETC and configure to receive trigger + if (ADC_ETC_CTRL & (ADC_ETC_CTRL_SOFTRST | ADC_ETC_CTRL_TSC_BYPASS)) { + ADC_ETC_CTRL = 0; // clears SOFTRST only + ADC_ETC_CTRL = 0; // clears TSC_BYPASS + } + ADC_ETC_CTRL |= ADC_ETC_CTRL_TRIG_ENABLE(1 << trigger) | ADC_ETC_CTRL_DMA_MODE_SEL; + ADC_ETC_DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(trigger); + + // configure ADC_ETC trigger4 to make four ADC2 measurements + const int len = 4; + IMXRT_ADC_ETC.TRIG[trigger].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(len - 1) | + ADC_ETC_TRIG_CTRL_TRIG_PRIORITY(7); + IMXRT_ADC_ETC.TRIG[trigger].CHAIN_1_0 = + ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_HWTS1(1) | + ADC_ETC_TRIG_CHAIN_CSEL0(adc2_pin_to_channel[pin1]) | ADC_ETC_TRIG_CHAIN_B2B0 | + ADC_ETC_TRIG_CHAIN_CSEL1(adc2_pin_to_channel[pin2]) | ADC_ETC_TRIG_CHAIN_B2B1; + IMXRT_ADC_ETC.TRIG[trigger].CHAIN_3_2 = + ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_HWTS1(1) | + ADC_ETC_TRIG_CHAIN_CSEL0(adc2_pin_to_channel[pin3]) | ADC_ETC_TRIG_CHAIN_B2B0 | + ADC_ETC_TRIG_CHAIN_CSEL1(adc2_pin_to_channel[pin4]) | ADC_ETC_TRIG_CHAIN_B2B1; + + // set up ADC2 for 12 bit mode, hardware trigger + // ADLPC=0, ADHSC=1, 12 bit mode, 40 MHz max ADC clock + // ADLPC=0, ADHSC=0, 12 bit mode, 30 MHz max ADC clock + // ADLPC=1, ADHSC=0, 12 bit mode, 20 MHz max ADC clock + uint32_t cfg = ADC_CFG_ADTRG; + cfg |= ADC_CFG_MODE(2); // 2 = 12 bits + cfg |= ADC_CFG_AVGS(0); // # samples to average + cfg |= ADC_CFG_ADSTS(2); // sampling time, 0-3 + //cfg |= ADC_CFG_ADLSMP; // long sample time + cfg |= ADC_CFG_ADHSC; // high speed conversion + //cfg |= ADC_CFG_ADLPC; // low power + cfg |= ADC_CFG_ADICLK(0);// 0:ipg, 1=ipg/2, 3=adack (10 or 20 MHz) + cfg |= ADC_CFG_ADIV(2); // 0:div1, 1=div2, 2=div4, 3=div8 + ADC2_CFG = cfg; + //ADC2_GC &= ~ADC_GC_AVGE; // single sample, no averaging + ADC2_GC |= ADC_GC_AVGE; // use averaging + ADC2_HC0 = ADC_HC_ADCH(16); // 16 = controlled by ADC_ETC + // use a DMA channel to capture ADC_ETC output + dma0.begin(); + dma0.TCD->SADDR = &(IMXRT_ADC_ETC.TRIG[4].RESULT_1_0); + dma0.TCD->SOFF = 4; + dma0.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma0.TCD->NBYTES_MLNO = DMA_TCD_NBYTES_MLOFFYES_NBYTES(8) | // sizeof(adcframe_t) = 8 + DMA_TCD_NBYTES_SMLOE | DMA_TCD_NBYTES_MLOFFYES_MLOFF(-8); + dma0.TCD->SLAST = -8; + dma0.TCD->DADDR = adc_buffer; + dma0.TCD->DOFF = 4; + dma0.TCD->CITER_ELINKNO = sizeof(adc_buffer) / 8; + dma0.TCD->DLASTSGA = -sizeof(adc_buffer); + dma0.TCD->BITER_ELINKNO = sizeof(adc_buffer) / 8; + dma0.TCD->CSR = 0; + dma0.triggerAtHardwareEvent(DMAMUX_SOURCE_ADC_ETC); + dma0.enable(); +} #endif // __IMXRT1062__ +#if defined(__MK20DX256__) /*static*/void FASTRUN ADC::Scan_DMA() { #ifdef OC_ADC_ENABLE_DMA_INTERRUPT @@ -121,6 +267,7 @@ void ADC::Init_DMA() { ADC::ready_ = false; #else if (dma0->complete()) { + // On Teensy 3.2, this runs every 180us (every 3rd call from 60us timer) dma0->clearComplete(); #endif dma0->TCD->DADDR = &adcbuffer_0[0]; @@ -147,6 +294,43 @@ void ADC::Init_DMA() { } } +#elif defined(__IMXRT1062__) +static void sum_adc(uint32_t *sum, const adcframe_t *n) { + sum[0] += n->adc[0]; + sum[1] += n->adc[1]; + sum[2] += n->adc[2]; + sum[3] += n->adc[3]; +} + +/*static*/void FASTRUN ADC::Scan_DMA() { + static int old_idx = 0; + + // find the most recently DMA-stored ADC data frame + const adcframe_t *p = (adcframe_t *)dma0.TCD->DADDR; + arm_dcache_delete(adc_buffer, sizeof(adc_buffer)); + //asm("dsb"); + + uint32_t sum[4] = {0, 0, 0, 0}; + int idx = p - adc_buffer; + int count = idx - old_idx; + if (count < 0) count += adc_buffer_len; + if (count) { + for (int i=0; i < count ; i++) { + sum_adc(sum, &adc_buffer[(idx + i) % adc_buffer_len]); + } + + const int mult = 16; + update(sum[0] * mult / count); + update(sum[1] * mult / count); + update(sum[2] * mult / count); + update(sum[3] * mult / count); + + old_idx = idx; + } +} +#endif // __IMXRT1062__ + + /*static*/ void ADC::CalibratePitch(int32_t c2, int32_t c4) { // This is the method used by the Mutable Instruments calibration and // extrapolates from two octaves. I guess an alternative would be to get the diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 074868611..e692d51fd 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -98,6 +98,13 @@ void FASTRUN CORE_timer_ISR() { void setup() { delay(50); +#if defined(__IMXRT1062__) + if (CrashReport) { + while (!Serial && millis() < 3000) ; // wait + Serial.println(CrashReport); + delay(1500); + } +#endif #if defined(__MK20DX256__) NVIC_SET_PRIORITY(IRQ_PORTB, 0); // TR1 = 0 = PTB16 #endif From a6bbf5243a0f34e63044fe3a23b45f61857749c7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 16 Oct 2023 09:51:40 -0400 Subject: [PATCH 314/417] Custom eeprom.h for Teensy 4.0 (2048 bytes) --- software/o_c_REV/extern/avr/eeprom.h | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 software/o_c_REV/extern/avr/eeprom.h diff --git a/software/o_c_REV/extern/avr/eeprom.h b/software/o_c_REV/extern/avr/eeprom.h new file mode 100644 index 000000000..f7959c82c --- /dev/null +++ b/software/o_c_REV/extern/avr/eeprom.h @@ -0,0 +1,53 @@ +/* Simple compatibility headers for AVR code used with ARM chips + * Copyright (c) 2015 Paul Stoffregen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Guidelines for editing this file: +// https://forum.pjrc.com/threads/34537-Teensy-LC-Increase-EEPROM-Size/page2 + +// modified version for Phazerville Suite +// This is a combo of the T3 and T4 versions of this file, with the 4.0 value bumped up to 2047 + +#ifndef _AVR_EEPROM_H_ +#define _AVR_EEPROM_H_ 1 + +#include +#include + +#include "avr_functions.h" + +#if defined(ARDUINO_TEENSY40) + #define E2END 0x7FF // 0x437 +#elif defined(ARDUINO_TEENSY41) + #define E2END 0x10BB +#elif defined(ARDUINO_TEENSY_MICROMOD) + #define E2END 0x10BB +#elif defined(__MK20DX128__) || defined(__MK20DX256__) + #define E2END 0x7FF +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) + #define E2END 0xFFF +#elif defined(__MKL26Z64__) + #define E2END 0x7F +#else + #define E2END 0 +#endif + +#endif From 3703cbf1b3c83bb952b45b4da318ddd8adea7936 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 15 Oct 2023 13:56:25 -0400 Subject: [PATCH 315/417] build configs for Teensy 4.x --- software/o_c_REV/platformio.ini | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index d555660ca..fe539b190 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -42,9 +42,7 @@ extra_scripts = pre:resources/progname.py upload_protocol = teensy-cli -[env:T40] -board = teensy40 -board_build.f_cpu = 600000000 +[env:T4] build_flags = -DTEENSY_OPT_SMALLEST_CODE -DUSB_MIDI @@ -71,6 +69,18 @@ build_flags = -DENABLE_APP_AUTOMATONNETZ -DENABLE_APP_BBGEN +[env:T40] +board = teensy40 +board_build.f_cpu = 600000000 +build_flags = ${env:T4.build_flags} + -DOC_VERSION_EXTRA="\"_T40\"" + +[env:T41] +board = teensy41 +board_build.f_cpu = 600000000 +build_flags = ${env:T4.build_flags} + -DOC_VERSION_EXTRA="\"_T41\"" + [env:pewpewpew] ; phazer's choice build build_flags = From cf779437aadd063062f79a48497c59781d1b0c54 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 16 Oct 2023 10:08:40 -0400 Subject: [PATCH 316/417] Show Teensy version on Setup / About screen PEW PEW PEW! nothing to see here (: --- software/o_c_REV/APP_SETTINGS.ino | 82 ++++++++++--------------------- software/o_c_REV/platformio.ini | 2 +- 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/software/o_c_REV/APP_SETTINGS.ino b/software/o_c_REV/APP_SETTINGS.ino index 5b4de5e0c..993dd79b1 100644 --- a/software/o_c_REV/APP_SETTINGS.ino +++ b/software/o_c_REV/APP_SETTINGS.ino @@ -21,44 +21,6 @@ #include "HSApplication.h" #include "OC_strings.h" -// Bitmap representation of QR code for access to http://www.beigemaze.com/hs, which -// redirects to Hemisphere Suite documentation. -// -// And no, the QR code doesn't seem to work. I'm sure that the pixels are too small, -// and that the dark spots are too illuminated by adjacent pixels, or whatever. -// But I'm leaving this in because with the right phone and the right display, who -// knows? -// Ah, the heck with it. Commenting it out. -/* -const uint32_t QR[25] = { - 0x1fdeb7f, - 0x1042d41, - 0x174455d, - 0x174f75d, - 0x174ad5d, - 0x105c441, - 0x1fd557f, - 0x8500, - 0x1f6536a, - 0x9cb1b8, - 0x9356cb, - 0x13b29a0, - 0x131cb6d, - 0x1757138, - 0x1d94d5c, - 0x92d5a6, - 0x9f6ef7, - 0x314f00, - 0xb5147f, - 0x1f1ff41, - 0x1bf545d, - 0x19ee55d, - 0x177105d, - 0x12c7741, - 0x1e4dc7f -}; -*/ - class Settings : public HSApplication { public: void Start() { @@ -68,10 +30,29 @@ public: } void Controller() { + #ifdef PEWPEWPEW + HS::frame.Load();PewPewTime.PEWPEW(Clock(3)<<1|Clock(0));} + struct{bool go=0;int idx=0;struct{uint8_t x,y;int x_v,y_v;}pewpews[8]; + void PEWPEW(uint8_t mask){uint32_t t=OC::CORE::ticks;for(int i=0;i<8;++i){auto &p=pewpews[i]; + if(mask>>i&0x01){auto &pp=pewpews[idx++];pp.x=0+120*i;pp.y=55;pp.x_v=(6+random(3))*(i?-1:1);pp.y_v=-9;idx%=8;} + if(t%500==0){p.x+=p.x_v;p.y+=p.y_v;if(p.y>=55&&p.y_v>0)p.y_v=-p.y_v;else ++p.y_v;} + if(t%10000==0){p.x_v=p.x_v*100/101;p.y_v=p.y_v*10/11;}}}}PewPewTime; + void PEWPEW(){for(int i=0;i<8;++i){auto &p=PewPewTime.pewpews[i];gfxIcon(p.x%128,p.y%64,ZAP_ICON);} + #endif } void View() { gfxHeader("Setup / About"); + + #if defined(ARDUINO_TEENSY40) + gfxPrint(100, 0, "T4.0"); + //gfxPrint(0, 45, "E2END="); gfxPrint(E2END); + #elif defined(ARDUINO_TEENSY41) + gfxPrint(100, 0, "T4.1"); + #else + gfxPrint(100, 0, "T3.2"); + #endif + gfxIcon(0, 15, ZAP_ICON); gfxIcon(120, 15, ZAP_ICON); #ifdef PEWPEWPEW @@ -82,8 +63,6 @@ public: gfxPrint(0, 25, OC::Strings::VERSION); gfxPrint(0, 35, "github.com/djphazer"); gfxPrint(0, 55, "[CALIBRATE] [RESET]"); - - //DrawQRAt(103, 15); } ///////////////////////////////////////////////////////////////// @@ -97,20 +76,6 @@ public: OC::apps::Init(1); } -private: -/* - void DrawQRAt(byte x, byte y) { - for (byte c = 0; c < 25; c++) // Column - { - uint32_t col = QR[c]; - for (byte b = 0; b < 25; b++) // Bit - { - if (col & (1 << b)) gfxPixel(x + c, y + b); - } - } - } -*/ - }; Settings Settings_instance; @@ -126,6 +91,9 @@ size_t Settings_save(void *storage) {return 0;} size_t Settings_restore(const void *storage) {return 0;} void Settings_isr() { +#ifdef PEWPEWPEW + Settings_instance.Controller(); +#endif // skip the Controller to avoid I/O conflict with Calibration return; } @@ -142,7 +110,11 @@ void Settings_menu() { Settings_instance.BaseView(); } -void Settings_screensaver() {} // Deprecated +void Settings_screensaver() { +#ifdef PEWPEWPEW + Settings_instance.PEWPEW(); +#endif +} void Settings_handleButtonEvent(const UI::Event &event) { if (event.control == OC::CONTROL_BUTTON_L) { diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index fe539b190..b42bcbab8 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -101,7 +101,7 @@ build_flags = [env:pewpewvor] build_flags = ${env:pewpewpew.build_flags} - -DVOR -DFLIP_180 + -DVOR ; -DFLIP_180 [env:main] build_flags = From 138e1fdec940583cbad8b08f910852907ff72cf5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 16 Oct 2023 09:43:55 -0400 Subject: [PATCH 317/417] ProbMeloD: use standard CV modulation of params --- software/o_c_REV/HEM_ProbabilityMelody.ino | 42 +++++++++++----------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index bef921221..328efbb88 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -46,19 +46,17 @@ public: void Controller() { loop_linker->RegisterMelo(hemisphere); - int downCv = DetentedIn(0); - int oldDown = down; - if (downCv < 0) down = 1; - if (downCv > 0) { - down = constrain(ProportionCV(downCv, HEM_PROB_MEL_MAX_RANGE + 1), 1, up); - } - - int upCv = DetentedIn(1); - int oldUp = up; - if (upCv < 0) up = 1; - if (upCv > 0) { - up = constrain(ProportionCV(upCv, HEM_PROB_MEL_MAX_RANGE + 1), down, HEM_PROB_MEL_MAX_RANGE); - } + // stash these to check for regen + int oldDown = down_mod; + int oldUp = up_mod; + + // CV modulation + down_mod = down; + up_mod = up; + // down scales to the up setting + Modulate(down_mod, 0, 1, up); + // up scales full range, with down value as a floor + Modulate(up_mod, 1, down_mod, HEM_PROB_MEL_MAX_RANGE); // regen when looping was enabled from ProbDiv bool regen = loop_linker->IsLooping() && !isLooping; @@ -68,7 +66,7 @@ public: regen = regen || loop_linker->ShouldReseed(); // reseed loop if range has changed due to CV - regen = regen || (isLooping && (oldDown != down || oldUp != up)); + regen = regen || (isLooping && (down_mod != oldDown || up_mod != oldUp)); if (regen) { GenerateLoop(); @@ -178,8 +176,8 @@ protected: private: int cursor; // ProbMeloCursor int weights[12] = {10,0,0,2,0,0,0,2,0,0,4,0}; - int up; - int down; + int up, up_mod; + int down, down_mod; int pitch; bool isLooping = false; int loop[HEM_PROB_MEL_MAX_LOOP_LENGTH]; @@ -195,12 +193,12 @@ private: int GetNextWeightedPitch() { int total_weights = 0; - for(int i = down-1; i < up; i++) { + for(int i = down_mod-1; i < up_mod; i++) { total_weights += weights[i % 12]; } int rnd = random(0, total_weights + 1); - for(int i = down-1; i < up; i++) { + for(int i = down_mod-1; i < up_mod; i++) { int weight = weights[i % 12]; if (rnd <= weight && weight > 0) { return i; @@ -279,14 +277,14 @@ private: // scaling params gfxIcon(0, 13, DOWN_BTN_ICON); - gfxPrint(8, 15, ((down - 1) / 12) + 1); + gfxPrint(8, 15, ((down_mod - 1) / 12) + 1); gfxPrint(13, 15, "."); - gfxPrint(17, 15, ((down - 1) % 12) + 1); + gfxPrint(17, 15, ((down_mod - 1) % 12) + 1); gfxIcon(30, 16, UP_BTN_ICON); - gfxPrint(38, 15, ((up - 1) / 12) + 1); + gfxPrint(38, 15, ((up_mod - 1) / 12) + 1); gfxPrint(43, 15, "."); - gfxPrint(47, 15, ((up - 1) % 12) + 1); + gfxPrint(47, 15, ((up_mod - 1) % 12) + 1); if (cursor == LOWER) gfxCursor(9, 23, 21); if (cursor == UPPER) gfxCursor(39, 23, 21); From aae7bbf7d2a33d5debf4569612e9ae4c880ba2d9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 16 Oct 2023 20:19:20 -0400 Subject: [PATCH 318/417] ProbDiv: use standard CV modulation of loop length --- software/o_c_REV/HEM_ProbabilityDivider.ino | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/software/o_c_REV/HEM_ProbabilityDivider.ino b/software/o_c_REV/HEM_ProbabilityDivider.ino index 5a5acc663..424e7b890 100644 --- a/software/o_c_REV/HEM_ProbabilityDivider.ino +++ b/software/o_c_REV/HEM_ProbabilityDivider.ino @@ -54,13 +54,13 @@ public: loop_linker->RegisterDiv(hemisphere); // CV 1 control over loop length - int lengthCv = DetentedIn(0); - if (lengthCv < 0) loop_length = 0; - if (lengthCv > 0) { - loop_length = constrain(ProportionCV(lengthCv, HEM_PROB_DIV_MAX_LOOP_LENGTH + 1), 0, HEM_PROB_DIV_MAX_LOOP_LENGTH); + loop_length_mod = loop_length; + if (DetentedIn(0)) { + Modulate(loop_length_mod, 0, 0, HEM_PROB_DIV_MAX_LOOP_LENGTH); + // TODO: regenerate if changing from 0? } - loop_linker->SetLooping(loop_length > 0); + loop_linker->SetLooping(loop_length_mod > 0); // reset if (Clock(1)) { @@ -85,7 +85,7 @@ public: } // reset loop - if (loop_length > 0 && loop_step >= loop_length) { + if (loop_length_mod > 0 && loop_step >= loop_length_mod) { loop_step = 0; loop_index = 0; skip_steps = 0; @@ -96,7 +96,7 @@ public: // continue with active division if (--skip_steps > 0) { - if (loop_length > 0) { + if (loop_length_mod > 0) { loop_step++; } ClockOut(1); @@ -104,7 +104,7 @@ public: } // get next weighted div or next div from loop - if (loop_length > 0) { + if (loop_length_mod > 0) { skip_steps = GetNextLoopDiv(); } else { skip_steps = GetNextWeightedDiv(); @@ -208,7 +208,7 @@ private: int weight_2; int weight_4; int weight_8; - int loop_length; + int loop_length, loop_length_mod; int loop[HEM_PROB_DIV_MAX_LOOP_LENGTH]; int loop_index; int loop_step; @@ -245,10 +245,10 @@ private: if (reseed_animation > 0) { gfxInvert(4, 55, 12, 8); } - if (loop_length == 0) { + if (loop_length_mod == 0) { gfxPrint(19, 55, "off"); } else { - gfxPrint(19, 55, loop_length); + gfxPrint(19, 55, loop_length_mod); } if (cursor == LOOP_LENGTH) gfxCursor(19, 63, 18); From 97a27670010f00e94a91a3a4ea2e3ce9d78625de Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 29 Sep 2023 00:05:11 -0400 Subject: [PATCH 319/417] Combine MIDI inputs with CV rather than override --- software/o_c_REV/APP_HEMISPHERE.ino | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index c9834646d..ae04a2c03 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -274,23 +274,22 @@ public: { ForEachChannel(ch) { int chan = h*2 + ch; - // override CV inputs with applicable MIDI signals + // mix CV inputs with applicable MIDI signals switch (HS::frame.MIDIState.function[chan]) { case HEM_MIDI_CC_OUT: case HEM_MIDI_NOTE_OUT: case HEM_MIDI_VEL_OUT: case HEM_MIDI_AT_OUT: case HEM_MIDI_PB_OUT: - HS::frame.inputs[chan] = HS::frame.MIDIState.outputs[chan]; + HS::frame.inputs[chan] += HS::frame.MIDIState.outputs[chan]; break; case HEM_MIDI_GATE_OUT: - // XXX: how do we want this one to behave? override digital? or CV? both? - HS::frame.gate_high[chan] = (HS::frame.MIDIState.outputs[chan] > (12 << 7)); + HS::frame.gate_high[chan] |= (HS::frame.MIDIState.outputs[chan] > (12 << 7)); break; case HEM_MIDI_TRIG_OUT: case HEM_MIDI_CLOCK_OUT: case HEM_MIDI_START_OUT: - HS::frame.clocked[chan] = HS::frame.MIDIState.trigout_q[chan]; + HS::frame.clocked[chan] |= HS::frame.MIDIState.trigout_q[chan]; HS::frame.MIDIState.trigout_q[chan] = 0; break; } From b69cf80e564c62212927086347f58095538d6d77 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 9 Oct 2023 09:35:08 -0400 Subject: [PATCH 320/417] SCENES: keep MIDI Clock out ticking --- software/o_c_REV/APP_SCENES.ino | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino index a8b1cd764..794ae7f92 100644 --- a/software/o_c_REV/APP_SCENES.ino +++ b/software/o_c_REV/APP_SCENES.ino @@ -147,6 +147,16 @@ public: } void Controller() { + // Keep the MIDI clock ticking + HS::IOFrame &f = HS::frame; + while (usbMIDI.read()) { + const int message = usbMIDI.getType(); + const int data1 = usbMIDI.getData1(); + const int data2 = usbMIDI.getData2(); + f.MIDIState.ProcessMIDIMsg(usbMIDI.getChannel(), message, data1, data2); + } + HS::clock_setup_applet.Controller(0, 0); + const int OCTAVE = (12 << 7); // -- core processing -- From 95335d6a7b3dab3c961ac4503e1b84482653ce2a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 18 Oct 2023 22:14:05 -0400 Subject: [PATCH 321/417] SCENES: CV3 modulates Slew/Smoothing amount --- software/o_c_REV/APP_SCENES.ino | 47 +++++++++++++++++++++++--------- software/o_c_REV/HSApplication.h | 7 +++++ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino index 794ae7f92..ab058d77f 100644 --- a/software/o_c_REV/APP_SCENES.ino +++ b/software/o_c_REV/APP_SCENES.ino @@ -179,6 +179,16 @@ public: Sequence.active = 0; } + // CV2: bipolar offset added to all values + int cv_offset = DetentedIn(1); + + // CV3: Slew/Smoothing + smoothing_mod = smoothing; + if (DetentedIn(2) > 0) { + Modulate(smoothing_mod, 2, 0, 127); + } + + // -- update active scene values, with smoothing // CV1: smooth interpolation offset, starting from last triggered scene if (DetentedIn(0)) { int cv = In(0); @@ -205,30 +215,26 @@ public: } // a weighted average of the two chosen scene values - active_scene.values[i] = ( v1 * (OCTAVE - partial) + v2 * partial) / OCTAVE; + int target = ( v1 * (OCTAVE - partial) + v2 * partial ) / OCTAVE; + target = constrain(target + cv_offset, SCENE_MIN_VAL, SCENE_MAX_VAL); + + slew(active_scene.values[i], target); } } else if (scene4seq && trig_chan == 3) { // looped sequence for TR4 for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { - active_scene.values[i] = scene[ Sequence.Get(i) / 4 ].values[ Sequence.Get(i) % 4 ]; + int target = scene[ Sequence.Get(i) / 4 ].values[ Sequence.Get(i) % 4 ]; + target = constrain(target + cv_offset, SCENE_MIN_VAL, SCENE_MAX_VAL); + slew(active_scene.values[i], target); } smooth_offset = 0; } else { // a simple scene copy will suffice for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { - active_scene.values[i] = scene[trig_chan].values[i]; // copy-on-assign for structs? + int target = constrain(scene[trig_chan].values[i] + cv_offset, SCENE_MIN_VAL, SCENE_MAX_VAL); + slew(active_scene.values[i], target); } smooth_offset = 0; } - // CV2: bipolar offset added to all values - if (DetentedIn(1)) { - int cv_offset = In(1); - for (int i = 0; i < NR_OF_SCENE_CHANNELS; ++i) { - active_scene.values[i] = constrain(active_scene.values[i] + cv_offset, SCENE_MIN_VAL, SCENE_MAX_VAL); - } - } - - // CV3: TODO - // set outputs for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { Out(ch, active_scene.values[ch]); @@ -386,6 +392,17 @@ private: Scene scene[NR_OF_SCENE_CHANNELS]; Scene active_scene; + int smoothing, smoothing_mod; + + template + void slew(T &old_val, const int new_val = 0) { + const int s = 1 + smoothing_mod; + // more smoothing causes more ticks to be skipped + if (OC::CORE::ticks % s) return; + + old_val = (old_val * (s - 1) + new_val) / s; + } + void DrawPresetSelector() { // index is the currently loaded preset (0-3) // preset_select is current selection (1-4, 5=clear) @@ -428,6 +445,10 @@ private: gfxPrintVoltage(scene[sel_chan].values[3]); gfxIcon(64, 35 + 10*edit_mode_right, RIGHT_ICON); + + // slew amount (view only) + gfxIcon(0, 55, SLEW_ICON); + gfxPrint(10, 55, smoothing_mod); } }; diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 50a8b8e66..7a480e964 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -134,6 +134,13 @@ class HSApplication { return (In(ch) > 64 || In(ch) < -64) ? In(ch) : 0; } + // Standard bi-polar CV modulation scenario + template + void Modulate(T ¶m, const int ch, const int min = 0, const int max = 255) { + int cv = DetentedIn(ch); + param = constrain(param + Proportion(cv, HEMISPHERE_MAX_INPUT_CV, max), min, max); + } + bool Changed(int ch) { return frame.changed_cv[ch]; } From 9618dd9ee000fd7baf1df1dc6f0747e4624f01e5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 21 Oct 2023 16:44:29 -0400 Subject: [PATCH 322/417] SCENES: TrigSum mode on output D, long-press left enc to toggle + extra UI indicators --- software/o_c_REV/APP_SCENES.ino | 43 ++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino index ab058d77f..b0c355e84 100644 --- a/software/o_c_REV/APP_SCENES.ino +++ b/software/o_c_REV/APP_SCENES.ino @@ -171,7 +171,7 @@ public: trig_chan = 3; // else, it's unchanged - bool scene4seq = (In(3) > 2 * OCTAVE); // gate at CV4 + scene4seq = (In(3) > 2 * OCTAVE); // gate at CV4 if (scene4seq) { if (!Sequence.active) Sequence.Generate(); if (Clock(3)) Sequence.Advance(); @@ -180,7 +180,7 @@ public: } // CV2: bipolar offset added to all values - int cv_offset = DetentedIn(1); + cv_offset = DetentedIn(1); // CV3: Slew/Smoothing smoothing_mod = smoothing; @@ -237,6 +237,11 @@ public: // set outputs for (int ch = 0; ch < NR_OF_SCENE_CHANNELS; ++ch) { + if (trigsum_mode && ch == 3) { // TrigSum output overrides D + if (Clock(0) || Clock(1) || Clock(2) || Clock(3)) + ClockOut(3); + continue; + } Out(ch, active_scene.values[ch]); } @@ -276,8 +281,8 @@ public: } void OnLeftButtonLongPress() { - if (preset_select) return; - // TODO: toggle something cool here + //if (preset_select) return; + trigsum_mode = !trigsum_mode; } void OnRightButtonPress() { @@ -356,6 +361,7 @@ private: static const int SEQ_LENGTH = 16; int index = 0; + int cv_offset = 0; int sel_chan = 0; int trig_chan = 0; @@ -363,6 +369,9 @@ private: bool preset_modified = 0; bool edit_mode_left = 0; bool edit_mode_right = 0; + bool trigsum_mode = 0; + bool scene4seq = 0; + // oh jeez, why do we have so many bools?! struct { bool active = 0; @@ -442,13 +451,29 @@ private: gfxPrint(72, 35, "C:"); gfxPrintVoltage(scene[sel_chan].values[2]); gfxPrint(72, 45, "D:"); - gfxPrintVoltage(scene[sel_chan].values[3]); + if (trigsum_mode) + gfxPrint("(trig)"); + else + gfxPrintVoltage(scene[sel_chan].values[3]); gfxIcon(64, 35 + 10*edit_mode_right, RIGHT_ICON); - // slew amount (view only) - gfxIcon(0, 55, SLEW_ICON); - gfxPrint(10, 55, smoothing_mod); + // ------------------- // + gfxLine(0, 54, 127, 54); + + // -- Input indicators + // bias (CV2) + if (cv_offset) { + gfxIcon(0, 56, UP_DOWN_ICON); + gfxPos(10, 56); + gfxPrintVoltage(cv_offset); + } + // slew amount (CV3) + gfxIcon(64, 56, SLEW_ICON); + gfxPrint(74, 56, smoothing_mod); + + // sequencer (CV4) + if (scene4seq) gfxIcon( 108, 56, LOOP_ICON ); } }; @@ -553,7 +578,7 @@ void ScenesApp_handleButtonEvent(const UI::Event &event) { } break; case UI::EVENT_BUTTON_LONG_PRESS: if (event.control == OC::CONTROL_BUTTON_L) { - //ScenesApp_instance.OnLeftButtonLongPress(); + ScenesApp_instance.OnLeftButtonLongPress(); } if (event.control == OC::CONTROL_BUTTON_DOWN) { ScenesApp_instance.OnDownButtonLongPress(); From f3fbbe2117e26c7a5cc140cab453e1bbe20bfd66 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 9 Oct 2023 13:10:09 -0400 Subject: [PATCH 323/417] DualTM: default Slew to 0; tweaks & refactoring --- software/o_c_REV/HEM_TM2.ino | 96 ++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index c56f0b0c3..b5d768886 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -112,7 +112,7 @@ public: ForEachChannel(ch) { switch (cvmode[ch]) { case SLEW_MOD: - Modulate(smooth_mod, ch, 1, 128); + Modulate(smooth_mod, ch, 0, 127); break; case LENGTH_MOD: Modulate(len_mod, ch, TM2_MIN_LENGTH, TM2_MAX_LENGTH); @@ -171,20 +171,20 @@ public: int x = constrain(note_trans[2], -range_mod, range_mod); int y = range_mod; int n = (note * (y + x) + note2 * (y - x)) / (2*y); - Output[ch] = slew(Output[ch], quantizer->Lookup(n)); + slew(Output[ch], quantizer->Lookup(n)); break; } case PITCH1: - Output[ch] = slew(Output[ch], quantizer->Lookup(note + note_trans[0])); + slew(Output[ch], quantizer->Lookup(note + note_trans[0])); break; case PITCH2: - Output[ch] = slew(Output[ch], quantizer->Lookup(note2 + note_trans[1])); + slew(Output[ch], quantizer->Lookup(note2 + note_trans[1])); break; case MOD1: // 8-bit bi-polar proportioned CV - Output[ch] = slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); + slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case MOD2: - Output[ch] = slew(Output[ch], Proportion( int(reg[1] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); + slew(Output[ch], Proportion( int(reg[1] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); break; case TRIG1: case TRIG2: @@ -197,16 +197,16 @@ public: { // hold until it's time to pull it down if (--trigpulse[ch] < 0) - Output[ch] = slew(Output[ch]); + slew(Output[ch]); } break; case GATE1: case GATE2: - Output[ch] = slew(Output[ch], (reg[outmode[ch] - GATE1] & 0x01)*HEMISPHERE_MAX_CV ); + slew(Output[ch], (reg[outmode[ch] - GATE1] & 0x01)*HEMISPHERE_MAX_CV ); break; case GATE_SUM: - Output[ch] = slew(Output[ch], ((reg[0] & 0x01)+(reg[1] & 0x01))*HEMISPHERE_MAX_CV/2 ); + slew(Output[ch], ((reg[0] & 0x01)+(reg[1] & 0x01))*HEMISPHERE_MAX_CV/2 ); break; default: break; @@ -261,7 +261,7 @@ public: cvmode[1] = (InputMode) constrain(cvmode[1] + direction, 0, INMODE_LAST-1); break; case SLEW: - smoothing = constrain(smoothing + direction, 1, 128); + smoothing = constrain(smoothing + direction, 0, 127); break; default: break; @@ -278,7 +278,7 @@ public: Pack(data, PackLocation {25,8}, constrain(scale, 0, 255)); Pack(data, PackLocation {33,4}, cvmode[0]); Pack(data, PackLocation {37,4}, cvmode[1]); - Pack(data, PackLocation {41,6}, smoothing - 1); + Pack(data, PackLocation {41,6}, smoothing); // TODO: utilize enigma's global turing machine storage for the registers @@ -295,8 +295,8 @@ public: quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); - smoothing = Unpack(data, PackLocation {41,6}) + 1; - smoothing = constrain(smoothing, 1, 128); + smoothing = Unpack(data, PackLocation {41,6}); + smoothing = constrain(smoothing, 0, 127); } protected: @@ -330,19 +330,19 @@ private: int p_mod; int range = 24; int range_mod; - int smoothing = 4; + int smoothing = 0; int smooth_mod; int note_trans[3] = {0, 0, 0}; // transpose from CV input OutputMode outmode[2] = {PITCH1, TRIG2}; InputMode cvmode[2] = {LENGTH_MOD, RANGE_MOD}; - int slew(int old_val, const int new_val = 0) { + void slew(int &old_val, const int new_val = 0) { + const int s = 1 + smooth_mod; // more smoothing causes more ticks to be skipped - if (OC::CORE::ticks % smooth_mod) return old_val; + if (OC::CORE::ticks % s) return; - old_val = (old_val * (smooth_mod - 1) + new_val) / smooth_mod; - return old_val; + old_val = (old_val * (s - 1) + new_val) / s; } void DrawOutputMode(int ch) { @@ -376,36 +376,40 @@ private: gfxBitmap(24+ch*32, 35, 3, (outmode[ch] % 2) ? SUP_ONE : SUB_TWO ); } - void DrawCVMode(int ch) { - gfxIcon(1 + 31*ch, 35, CV_ICON); - gfxBitmap(9 + 31*ch, 35, 3, ch ? SUB_TWO : SUP_ONE); + void DrawCVMode(int ch, bool cur) { + const int y = 25; + + gfxIcon(1 + 31*ch, y, CV_ICON); + gfxBitmap(9 + 31*ch, y, 3, ch ? SUB_TWO : SUP_ONE); switch (cvmode[ch]) { case SLEW_MOD: - gfxIcon(15 + ch*32, 35, SLEW_ICON); + gfxIcon(15 + ch*32, y, SLEW_ICON); break; case LENGTH_MOD: - gfxIcon(15 + ch*32, 35, LOOP_ICON); + gfxIcon(15 + ch*32, y, LOOP_ICON); break; case P_MOD: - gfxIcon(15 + ch*32, 35, TOSS_ICON); + gfxIcon(15 + ch*32, y, TOSS_ICON); break; case RANGE_MOD: - gfxIcon(15 + ch*32, 35, UP_DOWN_ICON); + gfxIcon(15 + ch*32, y, UP_DOWN_ICON); break; case TRANSPOSE1: - gfxIcon(15 + ch*32, 35, BEND_ICON); - gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + gfxIcon(15 + ch*32, y, BEND_ICON); + gfxBitmap(24+ch*32, y, 3, SUP_ONE); break; case BLEND_XFADE: - gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + gfxBitmap(24+ch*32, y, 3, SUP_ONE); case TRANSPOSE2: - gfxIcon(15 + ch*32, 35, BEND_ICON); - gfxBitmap(24+ch*32, 35, 3, SUB_TWO); + gfxIcon(15 + ch*32, y, BEND_ICON); + gfxBitmap(24+ch*32, y, 3, SUB_TWO); break; default: break; } + + if (cur) gfxCursor(14 + 32*ch, y + 8, 10); } void DrawSelector() { @@ -417,40 +421,38 @@ private: } else { // p is disabled gfxBitmap(55, 15, 8, LOCK_ICON); } - gfxBitmap(1, 25, 8, SCALE_ICON); - gfxPrint(9, 25, OC::scale_names_short[scale]); - gfxBitmap(40, 25, 8, UP_DOWN_ICON); - gfxPrint(49, 25, range_mod); // APD + // two separate pages of params switch ((TM2Cursor)cursor){ default: + gfxBitmap(1, 25, 8, SCALE_ICON); + gfxPrint(9, 25, OC::scale_names_short[scale]); + gfxBitmap(40, 25, 8, UP_DOWN_ICON); + gfxPrint(49, 25, range_mod); ForEachChannel(ch) DrawOutputMode(ch); break; case CVMODE1: case CVMODE2: - ForEachChannel(ch) DrawCVMode(ch); - - break; case SLEW: + ForEachChannel(ch) DrawCVMode(ch, cursor == CVMODE1 + ch); + gfxIcon(1, 35, SLEW_ICON); - gfxPrint(15, 35, smooth_mod); + gfxPrint(12, 35, smooth_mod); - gfxCursor(15, 43, 18); break; } + // TODO: generalize this as a cursor LUT for all applets switch ((TM2Cursor)cursor) { case LENGTH: gfxCursor(13, 23, 12); break; - case PROB: gfxCursor(35, 23, 18); break; - case SCALE: gfxCursor(9, 33, 25); break; - case RANGE: gfxCursor(49, 33, 13); break; - - case OUT_A: - case CVMODE1: gfxCursor(14, 43, 10); break; + case PROB: gfxCursor(35, 23, 18); break; + case SCALE: gfxCursor( 9, 33, 25); break; + case RANGE: gfxCursor(49, 33, 13); break; - case OUT_B: - case CVMODE2: gfxCursor(46, 43, 10); break; + case OUT_A: gfxCursor(14, 43, 10); break; + case OUT_B: gfxCursor(46, 43, 10); break; + case SLEW: gfxCursor(12, 43, 18); break; default: break; } From b24b2d2e10b0a121bcb9d47eee4d3b0616d5a50e Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 23 Oct 2023 21:17:22 -0400 Subject: [PATCH 324/417] DualTM: add Root Note; rearrange UI --- software/o_c_REV/HEM_TM2.ino | 105 +++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index b5d768886..03bc38484 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -46,13 +46,14 @@ public: LENGTH, PROB, SCALE, + ROOT_NOTE, RANGE, - OUT_A, - OUT_B, + SLEW, CVMODE1, CVMODE2, - SLEW, - LAST_SETTING = SLEW + OUT_A, + OUT_B, + LAST_SETTING = OUT_B }; enum OutputMode { @@ -171,14 +172,14 @@ public: int x = constrain(note_trans[2], -range_mod, range_mod); int y = range_mod; int n = (note * (y + x) + note2 * (y - x)) / (2*y); - slew(Output[ch], quantizer->Lookup(n)); + slew(Output[ch], quantizer->Lookup(n) + (root_note << 7)); break; } case PITCH1: - slew(Output[ch], quantizer->Lookup(note + note_trans[0])); + slew(Output[ch], quantizer->Lookup(note + note_trans[0]) + (root_note << 7)); break; case PITCH2: - slew(Output[ch], quantizer->Lookup(note2 + note_trans[1])); + slew(Output[ch], quantizer->Lookup(note2 + note_trans[1]) + (root_note << 7)); break; case MOD1: // 8-bit bi-polar proportioned CV slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); @@ -245,6 +246,9 @@ public: if (scale < 0) scale = TM2_MAX_SCALE - 1; quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); break; + case ROOT_NOTE: + root_note = constrain(root_note + direction, 0, 11); + break; case RANGE: range = constrain(range + direction, 1, 32); break; @@ -279,6 +283,7 @@ public: Pack(data, PackLocation {33,4}, cvmode[0]); Pack(data, PackLocation {37,4}, cvmode[1]); Pack(data, PackLocation {41,6}, smoothing); + Pack(data, PackLocation {47,4}, root_note); // TODO: utilize enigma's global turing machine storage for the registers @@ -297,6 +302,7 @@ public: cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); smoothing = Unpack(data, PackLocation {41,6}); smoothing = constrain(smoothing, 0, 127); + root_note = Unpack(data, PackLocation {47,4}); } protected: @@ -314,7 +320,7 @@ private: braids::Quantizer* quantizer; int scale = OC::Scales::SCALE_SEMI; // Scale used for quantized output - int root_note; // TODO + int root_note = 0; // TODO: consider using the TuringMachine class or whatev uint32_t reg[2]; // 32-bit sequence registers @@ -346,70 +352,73 @@ private: } void DrawOutputMode(int ch) { - gfxPrint(1 + 31*ch, 36, ch ? (hemisphere ? "D" : "B") : (hemisphere ? "C" : "A") ); + const int y = 35; + const int x = 34*ch; + + gfxPrint(x+1, y+1, ch ? (hemisphere ? "D" : "B") : (hemisphere ? "C" : "A") ); gfxPrint(":"); switch (outmode[ch]) { - case PITCH_BLEND: gfxBitmap(24+ch*32, 35, 3, SUP_ONE); + case PITCH_BLEND: gfxBitmap(24+x, y, 3, SUP_ONE); case PITCH1: case PITCH2: - gfxBitmap(15 + ch*32, 35, 8, NOTE_ICON); + gfxBitmap(15 + x, y, 8, NOTE_ICON); break; case MOD1: case MOD2: - gfxBitmap(15 + ch*32, 35, 8, WAVEFORM_ICON); + gfxBitmap(15 + x, y, 8, WAVEFORM_ICON); break; case TRIG1: case TRIG2: - gfxBitmap(15 + ch*32, 35, 8, CLOCK_ICON); + gfxBitmap(15 + x, y, 8, CLOCK_ICON); break; - case GATE_SUM: gfxBitmap(24+ch*32, 35, 3, SUB_TWO); + case GATE_SUM: gfxBitmap(24+x, y, 3, SUB_TWO); case GATE1: case GATE2: - gfxBitmap(15 + ch*32, 35, 8, GATE_ICON); + gfxBitmap(15 + x, y, 8, GATE_ICON); break; default: break; } // indicator for reg1 or reg2 - gfxBitmap(24+ch*32, 35, 3, (outmode[ch] % 2) ? SUP_ONE : SUB_TWO ); + gfxBitmap(24+x, y, 3, (outmode[ch] % 2) ? SUP_ONE : SUB_TWO ); } - void DrawCVMode(int ch, bool cur) { + void DrawCVMode(int ch) { const int y = 25; + const int x = 34*ch; - gfxIcon(1 + 31*ch, y, CV_ICON); - gfxBitmap(9 + 31*ch, y, 3, ch ? SUB_TWO : SUP_ONE); + gfxIcon(1 + x, y, CV_ICON); + gfxBitmap(9 + x, y, 3, ch ? SUB_TWO : SUP_ONE); switch (cvmode[ch]) { case SLEW_MOD: - gfxIcon(15 + ch*32, y, SLEW_ICON); + gfxIcon(15 + x, y, SLEW_ICON); break; case LENGTH_MOD: - gfxIcon(15 + ch*32, y, LOOP_ICON); + gfxIcon(15 + x, y, LOOP_ICON); break; case P_MOD: - gfxIcon(15 + ch*32, y, TOSS_ICON); + gfxIcon(15 + x, y, TOSS_ICON); break; case RANGE_MOD: - gfxIcon(15 + ch*32, y, UP_DOWN_ICON); + gfxIcon(15 + x, y, UP_DOWN_ICON); break; case TRANSPOSE1: - gfxIcon(15 + ch*32, y, BEND_ICON); - gfxBitmap(24+ch*32, y, 3, SUP_ONE); + gfxIcon(15 + x, y, BEND_ICON); + gfxBitmap(24+x, y, 3, SUP_ONE); break; case BLEND_XFADE: - gfxBitmap(24+ch*32, y, 3, SUP_ONE); + gfxBitmap(24+x, y, 3, SUP_ONE); case TRANSPOSE2: - gfxIcon(15 + ch*32, y, BEND_ICON); - gfxBitmap(24+ch*32, y, 3, SUB_TWO); + gfxIcon(15 + x, y, BEND_ICON); + gfxBitmap(24+x, y, 3, SUB_TWO); break; default: break; } - if (cur) gfxCursor(14 + 32*ch, y + 8, 10); } void DrawSelector() { @@ -425,34 +434,46 @@ private: // two separate pages of params switch ((TM2Cursor)cursor){ default: + case SLEW: gfxBitmap(1, 25, 8, SCALE_ICON); gfxPrint(9, 25, OC::scale_names_short[scale]); - gfxBitmap(40, 25, 8, UP_DOWN_ICON); - gfxPrint(49, 25, range_mod); - ForEachChannel(ch) DrawOutputMode(ch); + gfxPrint(39, 25, OC::Strings::note_names_unpadded[root_note]); + + gfxBitmap(1, 35, 8, UP_DOWN_ICON); + gfxPrint(10, 35, range_mod); + + gfxIcon(35, 35, SLEW_ICON); + gfxPrint(44, 35, smooth_mod); break; case CVMODE1: case CVMODE2: - case SLEW: - ForEachChannel(ch) DrawCVMode(ch, cursor == CVMODE1 + ch); - - gfxIcon(1, 35, SLEW_ICON); - gfxPrint(12, 35, smooth_mod); + case OUT_A: + case OUT_B: + ForEachChannel(ch) { + DrawCVMode(ch); + DrawOutputMode(ch); + } break; } // TODO: generalize this as a cursor LUT for all applets switch ((TM2Cursor)cursor) { - case LENGTH: gfxCursor(13, 23, 12); break; - case PROB: gfxCursor(35, 23, 18); break; + case LENGTH: gfxCursor(12, 23, 13); break; + case PROB: gfxCursor(35, 23, 19); break; case SCALE: gfxCursor( 9, 33, 25); break; - case RANGE: gfxCursor(49, 33, 13); break; + case ROOT_NOTE: gfxCursor( 39, 33, 13); break; + case RANGE: gfxCursor(10, 43, 13); break; + case SLEW: gfxCursor(44, 43, 19); break; + + case CVMODE1: + case CVMODE2: + gfxCursor(14 + 34*(cursor-CVMODE1), 33, 10); + break; case OUT_A: gfxCursor(14, 43, 10); break; - case OUT_B: gfxCursor(46, 43, 10); break; - case SLEW: gfxCursor(12, 43, 18); break; + case OUT_B: gfxCursor(48, 43, 10); break; default: break; } From 6ef5c02d1d090c20f06d9782f286d2e234cf0f49 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 18 Oct 2023 23:27:32 -0400 Subject: [PATCH 325/417] TB-3PO: fixes for Root note & Transpose Root note acts as a semitone offset after quantizing, since the Lookup function always returns CV based on a root note of C. Transpose is now in scale degrees, applied before quantization. Visual keyboard display was also fixed. --- software/o_c_REV/HEM_TB3PO.ino | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index 46647d463..ce2d7aaeb 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -224,17 +224,12 @@ class TB_3PO: public HemisphereApplet { if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; set_quantizer_scale(scale); - - int max_root = scale_size > 12 ? 12 : scale_size; - if (max_root > 0) { - root = constrain(root, 0, max_root - 1); - } break; } case 7: { // Root note selection int r = root + direction; - int max_root = scale_size > 12 ? 12 : scale_size; + const int max_root = 12; if (direction > 0 && r >= max_root && octave_offset < 3) { ++octave_offset; // Go up to next octave @@ -367,7 +362,10 @@ private: // Get the cv value to use for a given step including root + transpose values int get_pitch_for_step(int step_num) { - int quant_note = 64 + int(notes[step_num]) + int(root); + int quant_note = 64 + int(notes[step_num]); + + // transpose in scale degrees, proportioned from semitones + quant_note += (MIDIQuantizer::NoteNumber(transpose_cv) - 60) * scale_size / 12; // Apply the manual octave offset quant_note += (int(octave_offset) * int(scale_size)); @@ -381,15 +379,16 @@ private: quant_note = constrain(quant_note, 0, 127); - return quantizer->Lookup(quant_note) + transpose_cv; + // root note is the semitone offset after quantization + return quantizer->Lookup(quant_note) + (root << 7); //return quantizer->Lookup( 64 ); // Test: note 64 is definitely 0v=c4 if output directly, on ALL scales } int get_semitone_for_step(int step_num) { // Don't add in octaves-- use the current quantizer limited to the base octave - int quant_note = 64 + notes[step_num] + root; // + transpose_note_in; + int quant_note = 64 + notes[step_num]; // + transpose_note_in; int32_t cv_note = quantizer->Lookup(constrain(quant_note, 0, 127)); - return MIDIQuantizer::NoteNumber(cv_note) % 12; + return (MIDIQuantizer::NoteNumber(cv_note) + root) % 12; } void reseed() { @@ -678,10 +677,7 @@ private: gfxBitmap(41, 54, 8, UP_BTN_ICON); } - int keyboard_pitch = curr_step_semitone - 4; // Translate from 0v - if (keyboard_pitch < 0) keyboard_pitch += 12; // Deal with c being at the start, not middle of keyboard - - gfxPrint(49, 55, keyboard_pitch); + gfxPrint(49, 55, curr_step_semitone); // Draw a TB-303 style octave of a piano keyboard, indicating the playing pitch int x = 1; @@ -694,7 +690,7 @@ private: // Two white keys in a row E and F if (i == 5) x += 3; - if (keyboard_pitch == i && step_is_gated(step)) // Only render a pitch if gated + if (curr_step_semitone == i && step_is_gated(step)) // Only render a pitch if gated { gfxRect(x - 1, y - 1, 5, 4); // Larger box From 57c85cfc5d8cea68c0820c7ed61d1fe4ad098e66 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 21 Oct 2023 16:42:36 -0400 Subject: [PATCH 326/417] LoFiPCM: invert waveform display --- software/o_c_REV/HEM_LoFiPCM.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index f8432a808..d948e5ad3 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -31,7 +31,6 @@ uint8_t lofi_pcm_buffer[HEM_LOFI_PCM_BUFFER_SIZE]; class LoFiPCM : public HemisphereApplet { public: - // TODO: consider making a singleton class to manage/share buffers const int length = HEM_LOFI_PCM_BUFFER_SIZE; const char* applet_name() { // Maximum 10 characters @@ -157,7 +156,7 @@ private: if (pos < 0) pos += length; for (int i = 0; i < 64; i++) { - int height = Proportion((int)lofi_pcm_buffer[pos]-127, 128, 16); + int height = Proportion(127 - (int)lofi_pcm_buffer[pos], 128, 16); gfxLine(i, 46, i, 46+height); pos += inc; From b89e2ea0519689763232f41e1057a290729d4224 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 22 Oct 2023 22:33:07 -0400 Subject: [PATCH 327/417] smol cursor tweak --- software/o_c_REV/HemisphereApplet.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index b6a1a2e80..95257d91a 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -246,7 +246,11 @@ class HemisphereApplet { //////////////////////////////////////////////////////////////////////////////// void gfxCursor(int x, int y, int w, int h = 9) { // assumes standard text height for highlighting if (isEditing) gfxInvert(x, y - h, w, h); - else if (CursorBlink()) gfxLine(x, y, x + w - 1, y); + else if (CursorBlink()) { + gfxLine(x, y, x + w - 1, y); + gfxPixel(x, y-1); + gfxPixel(x + w - 1, y-1); + } } void gfxPos(int x, int y) { From 625d8ab42bf3e8b64265d4969ecd6aa8dd43a6e3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 23 Oct 2023 18:48:07 -0400 Subject: [PATCH 328/417] Calibr8or: auto-save to EEPROM when storing Preset; UI tweaks Swap encoders for Scale/Root Note editing Swap UP/DOWN buttons for channel switching --- software/o_c_REV/APP_CALIBR8OR.ino | 36 ++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index 4b83341fa..72f217297 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -35,6 +35,10 @@ #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" #include "HemisphereApplet.h" +namespace OC { + void save_app_data(); +} + #define CAL8_MAX_TRANSPOSE 60 const int CAL8OR_PRECISION = 10000; @@ -191,6 +195,14 @@ public: void SavePreset() { cal8_presets[index].save_preset(channel); preset_modified = 0; + + // initiate actual EEPROM save + OC::CORE::app_isr_enabled = false; + delay(1); + OC::save_app_data(); + delay(1); + // TODO: display message during save? + OC::CORE::app_isr_enabled = true; } void Resume() { @@ -348,7 +360,7 @@ public: // fires on button release void SwitchChannel(bool up) { if (!clock_setup && !preset_select) { - sel_chan += (up? 1 : -1) + NR_OF_CHANNELS; + sel_chan += (up? -1 : 1) + NR_OF_CHANNELS; sel_chan %= NR_OF_CHANNELS; } @@ -364,7 +376,7 @@ public: preset_select = 1 + index; } - // Left encoder: Octave or VScaling + Root Note + // Left encoder: Octave or VScaling + Scale Select void OnLeftEncoderMove(int direction) { if (clock_setup) { HS::clock_setup_applet.OnEncoderMove(0, direction); @@ -379,7 +391,13 @@ public: preset_modified = 1; if (scale_edit) { - channel[sel_chan].root_note = constrain(channel[sel_chan].root_note + direction, 0, 11); + // Scale Select + int s_ = channel[sel_chan].scale + direction; + if (s_ >= OC::Scales::NUM_SCALES) s_ = 0; + if (s_ < 0) s_ = OC::Scales::NUM_SCALES - 1; + + channel[sel_chan].scale = s_; + HS::quantizer[sel_chan].Configure(OC::Scales::GetScale(s_), 0xffff); HS::quantizer[sel_chan].Requantize(); return; } @@ -395,7 +413,7 @@ public: } } - // Right encoder: Semitones or Bias Offset + Scale Select + // Right encoder: Semitones or Bias Offset + Root Note void OnRightEncoderMove(int direction) { if (clock_setup) { HS::clock_setup_applet.OnEncoderMove(0, direction); @@ -408,12 +426,8 @@ public: preset_modified = 1; if (scale_edit) { - int s_ = channel[sel_chan].scale + direction; - if (s_ >= OC::Scales::NUM_SCALES) s_ = 0; - if (s_ < 0) s_ = OC::Scales::NUM_SCALES - 1; - - channel[sel_chan].scale = s_; - HS::quantizer[sel_chan].Configure(OC::Scales::GetScale(s_), 0xffff); + // Root Note + channel[sel_chan].root_note = constrain(channel[sel_chan].root_note + direction, 0, 11); HS::quantizer[sel_chan].Requantize(); return; } @@ -592,8 +606,6 @@ void Calibr8or_handleAppEvent(OC::AppEvent event) { // The idea is to auto-save when the screen times out... case OC::APP_EVENT_SUSPEND: case OC::APP_EVENT_SCREENSAVER_ON: - // TODO: initiate actual EEPROM save - // app_data_save(); break; default: break; From 39636a26bd228644fd5054fdffa72853fc103d64 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 23 Oct 2023 23:07:05 -0400 Subject: [PATCH 329/417] SequenceX: Trigger at CV2 randomizes all steps --- software/o_c_REV/HEM_SequenceX.ino | 31 +++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/software/o_c_REV/HEM_SequenceX.ino b/software/o_c_REV/HEM_SequenceX.ino index ce0ab186a..e28369ae1 100644 --- a/software/o_c_REV/HEM_SequenceX.ino +++ b/software/o_c_REV/HEM_SequenceX.ino @@ -35,15 +35,30 @@ public: } void Start() { + Randomize(); + } + + void Randomize() { for (int s = 0; s < SEQX_STEPS; s++) note[s] = random(0, SEQX_MAX_VALUE); } void Controller() { + if (DetentedIn(1) > (24 << 7) ) // 24 semitones == 2V + { + // new random sequence if CV2 goes high + if (!cv2_gate) { + cv2_gate = 1; + Randomize(); + } + } + else cv2_gate = 0; + if (Clock(1)) { // reset step = 0; reset = true; } if (Clock(0)) { // clock + if (!reset) Advance(step); reset = false; // send trigger on first step @@ -105,9 +120,9 @@ protected: void SetHelp() { // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Reset"; - help[HEMISPHERE_HELP_CVS] = "1=Transpose"; + help[HEMISPHERE_HELP_CVS] = "1=Trans 2=RndSeq"; help[HEMISPHERE_HELP_OUTS] = "A=CV B=Clk Step 1"; - help[HEMISPHERE_HELP_ENCODER] = "Note"; + help[HEMISPHERE_HELP_ENCODER] = "Edit Step"; // "------------------" <-- Size Guide } @@ -117,6 +132,7 @@ private: int note[SEQX_STEPS]; // Sequence value (0 - 30) int step = 0; // Current sequencer step bool reset = true; + bool cv2_gate = false; void Advance(int starting_point) { if (++step == SEQX_STEPS) step = 0; @@ -132,7 +148,7 @@ private: int x = 6 + (7 * s); // APD: narrower to fit more if (!step_is_muted(s)) { - gfxLine(x, 25, x, 63, (s != cursor) ); // dotted line for unselected steps + gfxLine(x, 27, x, 63, (s != cursor) ); // dotted line for unselected steps // When cursor, there's a solid slider if (s == cursor) { @@ -141,15 +157,16 @@ private: gfxFrame(x - 2, BottomAlign(note[s]), 5, 3); // APD } - // When on this step, there's an indicator circle + // Arrow indicator for current step if (s == step) { - gfxCircle(x, 20, 3); + gfxIcon(x - 3, 17, DOWN_ICON); + //gfxCircle(x, 20, 3); } } else if (s == cursor) { - gfxLine(x, 25, x, 63); + gfxLine(x, 27, x, 63); } - if (s == cursor && EditMode()) gfxInvert(x - 2, 25, 5, 39); + if (s == cursor && EditMode()) gfxInvert(x - 2, 27, 5, 37); } } From 417f59335ff4c680ad8aadf9f6396221044be5a3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 23 Oct 2023 23:25:38 -0400 Subject: [PATCH 330/417] ResetClk: CV1 modulates Offset (for re-sequencing steps!) --- software/o_c_REV/HEM_ResetClock.ino | 32 ++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/HEM_ResetClock.ino b/software/o_c_REV/HEM_ResetClock.ino index 906761d5c..d8c88dae2 100644 --- a/software/o_c_REV/HEM_ResetClock.ino +++ b/software/o_c_REV/HEM_ResetClock.ino @@ -39,8 +39,15 @@ public: } void Controller() { + // CV1 modulates offset + int cv1_mod = offset; + Modulate(cv1_mod, 0, 0, length-1); + + if (cv1_mod != offset_mod) // changed + UpdateOffset(offset_mod, cv1_mod); + if (Clock(1)) { - pending_clocks += (length - position + offset - 1) % length; + pending_clocks += (length - position + offset_mod - 1) % length; } if (Clock(0)) pending_clocks++; @@ -80,13 +87,8 @@ public: offset %= length; break; case 1: // offset - { - int prevOffset = offset; - offset = constrain(offset + direction, 0, length-1); - pending_clocks += offset - prevOffset; - if (pending_clocks < 0) pending_clocks += length; - break; - } + UpdateOffset(offset, offset + direction); + break; case 2: // spacing spacing = constrain(spacing + direction, RC_MIN_SPACING, 100); break; @@ -95,7 +97,13 @@ public: break; } } - + void UpdateOffset(int &o, int new_offset) { + int prev_offset = o; + o = constrain(new_offset, 0, length-1); + pending_clocks += o - prev_offset; + if (pending_clocks < 0) pending_clocks += length; + } + uint64_t OnDataRequest() { uint64_t data = 0; Pack(data, PackLocation {0,5}, length-1); @@ -114,7 +122,7 @@ protected: void SetHelp() { // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "Clock, Reset"; - help[HEMISPHERE_HELP_CVS] = ""; + help[HEMISPHERE_HELP_CVS] = "1=Offset"; help[HEMISPHERE_HELP_OUTS] = "Clock, Reset"; help[HEMISPHERE_HELP_ENCODER] = "Len/Offst/Spac/Pos"; // "------------------" <-- Size Guide @@ -128,7 +136,7 @@ private: // Settings int length; int position; - int offset; + int offset, offset_mod; int spacing; void DrawInterface() { @@ -138,7 +146,7 @@ private: // Offset gfxIcon(32, 15, ROTATE_R_ICON); - gfxPrint(40 + pad(10, offset), 15, offset); + gfxPrint(40 + pad(10, offset_mod), 15, offset_mod); // Spacing gfxIcon(1, 25, CLOCK_ICON); From b708bbe1421adeb7134babb796b45845f90e3b33 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 21 Oct 2023 23:41:17 -0400 Subject: [PATCH 331/417] Version bump v1.6.6; disable LORENZ in +main build --- software/o_c_REV/OC_version.h | 2 +- software/o_c_REV/platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 89a41b3bf..081f39423 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.5" +"v1.6.6" diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index b42bcbab8..00c099782 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -116,7 +116,7 @@ build_flags = -DENABLE_APP_DARKEST_TIMELINE -DENABLE_APP_PIQUED -DENABLE_APP_POLYLFO - -DENABLE_APP_LORENZ +; -DENABLE_APP_LORENZ -DOC_VERSION_EXTRA="\"+main\"" ; ; -DENABLE_APP_ASR From 64297f821195422114764e773a4e71d11c2614fe Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 19 Oct 2023 17:42:33 -0400 Subject: [PATCH 332/417] HemisphereApplet: more Quantizer API functions --- software/o_c_REV/HemisphereApplet.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 95257d91a..db0be96c3 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -357,6 +357,16 @@ class HemisphereApplet { braids::Quantizer* GetQuantizer(int ch) { return &HS::quantizer[io_offset + ch]; } + int Quantize(int ch, int cv, int root, int transpose) { + return HS::quantizer[io_offset + ch].Process(cv, root, transpose); + } + int QuantizerLookup(int ch, int note) { + return HS::quantizer[io_offset + ch].Lookup(note); + } + void QuantizerConfigure(int ch, int scale, uint16_t mask = 0xffff) { + const braids::Scale &quant_scale = OC::Scales::GetScale(scale); + HS::quantizer[io_offset + ch].Configure(quant_scale, mask); + } // Standard bi-polar CV modulation scenario #if __cplusplus == 201703L From 1b563c0b8703f6762d0b3d6b690a20d8a0e72e45 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 22 Oct 2023 22:01:53 -0400 Subject: [PATCH 333/417] Utilize new Quantizer API in applets --- software/o_c_REV/HEM_ASR.ino | 9 +++------ software/o_c_REV/HEM_Chordinator.ino | 16 +++++----------- software/o_c_REV/HEM_DualQuant.ino | 10 ++++------ software/o_c_REV/HEM_ScaleDuet.ino | 13 ++++++------- software/o_c_REV/HEM_Shredder.ino | 10 ++++------ software/o_c_REV/HEM_Squanch.ino | 13 ++++--------- software/o_c_REV/HEM_TM2.ino | 14 ++++++-------- 7 files changed, 32 insertions(+), 53 deletions(-) diff --git a/software/o_c_REV/HEM_ASR.ino b/software/o_c_REV/HEM_ASR.ino index 3e7a995e5..dc234db53 100644 --- a/software/o_c_REV/HEM_ASR.ino +++ b/software/o_c_REV/HEM_ASR.ino @@ -33,8 +33,7 @@ public: scale = OC::Scales::SCALE_SEMI; buffer_m.SetIndex(1); ForEachChannel(ch) { - quantizer[ch] = GetQuantizer(ch); - quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone + QuantizerConfigure(ch, scale); } } @@ -60,7 +59,7 @@ public: ForEachChannel(ch) { int cv = buffer_m.ReadValue(ch + secondary*2, index_mod); - int quantized = quantizer[ch]->Process(cv, 0, 0); + int quantized = Quantize(ch, cv, 0, 0); Out(ch, quantized); } } @@ -90,7 +89,7 @@ public: if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; ForEachChannel(ch) - quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(ch, scale); } } @@ -119,8 +118,6 @@ protected: private: int cursor; - // HS::RingBufferManager *buffer_m = buffer_m->get(); - braids::Quantizer* quantizer[2]; int scale; int index_mod; // Effect of modulation diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index dfb1a0a23..b2b945290 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -29,8 +29,6 @@ public: void Start() { scale = 5; - root_quantizer = GetQuantizer(0); - chord_quantizer = GetQuantizer(1); continuous[0] = 1; continuous[1] = 1; set_scale(scale); @@ -48,7 +46,7 @@ public: if (continuous[0] || EndOfADCLag(0)) { chord_root_raw = In(0); int32_t new_root_pitch = - root_quantizer->Process(chord_root_raw, root << 7, 0); + Quantize(0, chord_root_raw, root << 7, 0); if (new_root_pitch != chord_root_pitch) { update_chord_quantizer(); chord_root_pitch = new_root_pitch; @@ -58,7 +56,7 @@ public: if (continuous[1] || EndOfADCLag(1)) { harm_pitch = - chord_quantizer->Process(In(1) + chord_root_pitch, root << 7, 0); + Quantize(1, In(1) + chord_root_pitch, root << 7, 0); Out(1, harm_pitch); } } @@ -146,9 +144,6 @@ protected: } private: - braids::Quantizer* root_quantizer; - braids::Quantizer* chord_quantizer; - int scale; // SEMI int16_t root; bool continuous[2]; @@ -166,11 +161,10 @@ private: void update_chord_quantizer() { size_t num_notes = active_scale.num_notes; - chord_root_pitch = root_quantizer->Process(chord_root_raw, root, 0); + chord_root_pitch = Quantize(0, chord_root_raw, root, 0); size_t chord_root = note_ix(chord_root_pitch); uint16_t mask = rotl32(chord_mask, num_notes, chord_root); - chord_quantizer->Configure(active_scale, mask); - chord_quantizer->Requantize(); + QuantizerConfigure(1, scale, mask); } size_t note_ix(int pitch) { @@ -192,7 +186,7 @@ private: else if (value >= OC::Scales::NUM_SCALES) scale = 0; else scale = value; active_scale = OC::Scales::GetScale(scale); - root_quantizer->Configure(active_scale); + QuantizerConfigure(0, scale); } }; diff --git a/software/o_c_REV/HEM_DualQuant.ino b/software/o_c_REV/HEM_DualQuant.ino index 98d35f40d..210c3c428 100644 --- a/software/o_c_REV/HEM_DualQuant.ino +++ b/software/o_c_REV/HEM_DualQuant.ino @@ -36,8 +36,7 @@ public: ForEachChannel(ch) { scale[ch] = ch + 5; - quantizer[ch] = GetQuantizer(ch); - quantizer[ch]->Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + QuantizerConfigure(ch, scale[ch]); last_note[ch] = 0; continuous[ch] = 1; } @@ -53,7 +52,7 @@ public: if (continuous[ch] || EndOfADCLag(ch)) { int32_t pitch = In(ch); - int32_t quantized = quantizer[ch]->Process(pitch, root[ch] << 7, 0); + int32_t quantized = Quantize(ch, pitch, root[ch] << 7, 0); Out(ch, quantized); last_note[ch] = quantized; } @@ -81,7 +80,7 @@ public: scale[ch] += direction; if (scale[ch] >= OC::Scales::NUM_SCALES) scale[ch] = 0; if (scale[ch] < 0) scale[ch] = OC::Scales::NUM_SCALES - 1; - quantizer[ch]->Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + QuantizerConfigure(ch, scale[ch]); continuous[ch] = 1; // Re-enable continuous mode when scale is changed } else { // Root selection @@ -107,7 +106,7 @@ public: ForEachChannel(ch) { root[0] = constrain(root[0], 0, 11); - quantizer[ch]->Configure(OC::Scales::GetScale(scale[ch]), 0xffff); + QuantizerConfigure(ch, scale[ch]); } } @@ -121,7 +120,6 @@ protected: } private: - braids::Quantizer* quantizer[2]; int last_note[2]; // Last quantized note bool continuous[2]; // Each channel starts as continuous and becomes clocked when a clock is received int cursor; diff --git a/software/o_c_REV/HEM_ScaleDuet.ino b/software/o_c_REV/HEM_ScaleDuet.ino index d2c2fb49e..a414ecbe5 100644 --- a/software/o_c_REV/HEM_ScaleDuet.ino +++ b/software/o_c_REV/HEM_ScaleDuet.ino @@ -36,8 +36,7 @@ public: { mask[scale] = 0xffff; } - quantizer = GetQuantizer(0); - quantizer->Configure(OC::Scales::GetScale(5), mask[0]); + QuantizerConfigure(0, OC::Scales::SCALE_SEMI, mask[0]); last_scale = 0; adc_lag_countdown = 0; } @@ -50,11 +49,11 @@ public: if (EndOfADCLag()) { uint8_t scale = Gate(1); if (scale != last_scale) { - quantizer->Configure(OC::Scales::GetScale(5), mask[scale]); + QuantizerConfigure(0, OC::Scales::SCALE_SEMI, mask[scale]); last_scale = scale; } int32_t pitch = In(0); - int32_t quantized = quantizer->Process(pitch, 0, 0); + int32_t quantized = Quantize(0, pitch, 0, 0); Out(0, quantized); } } @@ -71,7 +70,8 @@ public: // Toggle the mask bit at the cursor position mask[scale] ^= (0x01 << bit); - if (scale == last_scale) quantizer->Configure(OC::Scales::GetScale(5), mask[scale]); + if (scale == last_scale) + QuantizerConfigure(0, OC::Scales::SCALE_SEMI, mask[scale]); } void OnEncoderMove(int direction) { @@ -92,7 +92,7 @@ public: mask[1] = Unpack(data, PackLocation {12,12}); last_scale = 0; - quantizer->Configure(OC::Scales::GetScale(5), mask[last_scale]); + QuantizerConfigure(0, OC::Scales::SCALE_SEMI, mask[last_scale]); } protected: @@ -106,7 +106,6 @@ protected: } private: - braids::Quantizer* quantizer; uint16_t mask[2]; uint8_t cursor; // 0-11=Scale 1; 12-23=Scale 2 uint8_t last_scale; // The most-recently-used scale (used to set the mask when necessary) diff --git a/software/o_c_REV/HEM_Shredder.ino b/software/o_c_REV/HEM_Shredder.ino index eb4fb13f3..3d8f05fbf 100644 --- a/software/o_c_REV/HEM_Shredder.ino +++ b/software/o_c_REV/HEM_Shredder.ino @@ -39,9 +39,8 @@ public: replay = 0; reset = true; quant_channels = 0; - quantizer = GetQuantizer(0); scale = OC::Scales::SCALE_NONE; - quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(0, scale); ForEachChannel(ch) { Shred(ch); } @@ -154,7 +153,7 @@ public: scale += direction; if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; - quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(0, scale); } } @@ -177,7 +176,7 @@ public: bipolar[1] = Unpack(data, PackLocation {12,1}); quant_channels = Unpack(data, PackLocation {16,8}); scale = Unpack(data, PackLocation {24,8}); - quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(0, scale); ForEachChannel(ch) { Shred(ch); } @@ -209,7 +208,6 @@ private: bool bipolar[2] = {false, false}; int8_t quant_channels; int scale; - braids::Quantizer* quantizer; // Variables to handle imprint confirmation animation int confirm_animation_countdown; @@ -309,7 +307,7 @@ private: ForEachChannel(ch) { current[ch] = sequence[ch][step]; int8_t qc = quant_channels - 1; - if (qc < 0 || qc == ch) current[ch] = quantizer->Process(current[ch], 0, 0); + if (qc < 0 || qc == ch) current[ch] = Quantize(0, current[ch], 0, 0); Out(ch, current[ch]); } } diff --git a/software/o_c_REV/HEM_Squanch.ino b/software/o_c_REV/HEM_Squanch.ino index 275c7138a..86039754a 100644 --- a/software/o_c_REV/HEM_Squanch.ino +++ b/software/o_c_REV/HEM_Squanch.ino @@ -40,8 +40,7 @@ public: void Start() { scale = 5; ForEachChannel(ch) { - quantizer[ch] = GetQuantizer(ch); - quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(ch, scale); } } @@ -60,7 +59,7 @@ public: // output, the output is raised by one octave when Digital 2 is gated. int32_t shift_alt = (ch == 1) ? DetentedIn(1) : Gate(1) * (12 << 7); - int32_t quantized = quantizer[ch]->Process(pitch + shift_alt, root << 7, shift[ch]); + int32_t quantized = Quantize(ch, pitch + shift_alt, root << 7, shift[ch]); Out(ch, quantized); last_note[ch] = quantized; } @@ -94,7 +93,7 @@ public: if (scale >= OC::Scales::NUM_SCALES) scale = 0; if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; ForEachChannel(ch) - quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(ch, scale); continuous = 1; // Re-enable continuous mode when scale is changed break; @@ -120,7 +119,7 @@ public: root = Unpack(data, PackLocation {24,4}); root = constrain(root, 0, 11); ForEachChannel(ch) - quantizer[ch]->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(ch, scale); } protected: @@ -138,10 +137,6 @@ private: bool continuous = 1; int last_note[2]; // Last quantized note - // Both channels use the same quantizer settings, but we need two instances - // to respect hysteresis independently on each, or else things get slippy - braids::Quantizer* quantizer[2]; - // Settings int scale; uint8_t root; diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 03bc38484..5adaa2c8a 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -89,8 +89,7 @@ public: reg[0] = random(0, 65535); reg[1] = ~reg[0]; - quantizer = GetQuantizer(0); - quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); // Semi-tone + QuantizerConfigure(0, scale); } void Controller() { @@ -172,14 +171,14 @@ public: int x = constrain(note_trans[2], -range_mod, range_mod); int y = range_mod; int n = (note * (y + x) + note2 * (y - x)) / (2*y); - slew(Output[ch], quantizer->Lookup(n) + (root_note << 7)); + slew(Output[ch], QuantizerLookup(0, n) + (root_note << 7)); break; } case PITCH1: - slew(Output[ch], quantizer->Lookup(note + note_trans[0]) + (root_note << 7)); + slew(Output[ch], QuantizerLookup(0, note + note_trans[0]) + (root_note << 7)); break; case PITCH2: - slew(Output[ch], quantizer->Lookup(note2 + note_trans[1]) + (root_note << 7)); + slew(Output[ch], QuantizerLookup(0, note2 + note_trans[1]) + (root_note << 7)); break; case MOD1: // 8-bit bi-polar proportioned CV slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); @@ -244,7 +243,7 @@ public: scale += direction; if (scale >= TM2_MAX_SCALE) scale = 0; if (scale < 0) scale = TM2_MAX_SCALE - 1; - quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(0, scale); break; case ROOT_NOTE: root_note = constrain(root_note + direction, 0, 11); @@ -297,7 +296,7 @@ public: outmode[0] = (OutputMode) Unpack(data, PackLocation {17,4}); outmode[1] = (OutputMode) Unpack(data, PackLocation {21,4}); scale = Unpack(data, PackLocation {25,8}); - quantizer->Configure(OC::Scales::GetScale(scale), 0xffff); + QuantizerConfigure(0, scale); cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); smoothing = Unpack(data, PackLocation {41,6}); @@ -318,7 +317,6 @@ protected: private: int cursor; // TM2Cursor - braids::Quantizer* quantizer; int scale = OC::Scales::SCALE_SEMI; // Scale used for quantized output int root_note = 0; From 07860721df09af6d4976e96f5dc02d765a4bb349 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 19 Oct 2023 17:42:33 -0400 Subject: [PATCH 334/417] New Applet: Pigeons Based on the "Pigeonhole Principle" using mathematical patterns like the Fibonacci sequence - each iteration calculates the sum of the previous two iterations, modulo a set value. The result is mapped to degrees of a scale to produce a Pitch CV sequence. --- software/o_c_REV/HEM_Pigeons.ino | 213 +++++++++++++++++++++++++++ software/o_c_REV/HSicons.h | 8 +- software/o_c_REV/hemisphere_config.h | 1 + 3 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 software/o_c_REV/HEM_Pigeons.ino diff --git a/software/o_c_REV/HEM_Pigeons.ino b/software/o_c_REV/HEM_Pigeons.ino new file mode 100644 index 000000000..6c68d1c53 --- /dev/null +++ b/software/o_c_REV/HEM_Pigeons.ino @@ -0,0 +1,213 @@ +// Copyright (c) 2023, Nicholas J. Michalek +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +class Pigeons : public HemisphereApplet { +public: + enum PigeonCursor { + CHAN1_V1, CHAN1_V2, + CHAN1_MOD, + CHAN2_V1, CHAN2_V2, + CHAN2_MOD, + SCALE, ROOT_NOTE, + + CURSOR_LAST = ROOT_NOTE + }; + + const char* applet_name() { + return "Pigeons"; + } + + void Start() { + ForEachChannel(ch) { + pigeons[ch].mod = 7 + ch*3; + QuantizerConfigure(ch, scale); + } + } + + void Controller() { + ForEachChannel(ch) + { + // CV modulation of modulo value + pigeons[ch].mod_v = pigeons[ch].mod; + Modulate(pigeons[ch].mod_v, ch, 1, 64); + + if (Clock(ch)) { + int signal = QuantizerLookup(ch, pigeons[ch].Bump() + 64) + (root_note << 7); + Out(ch, signal); + + pigeons[ch].pulse = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; + } + if (pigeons[ch].pulse) --pigeons[ch].pulse; + } + } + + void View() { + gfxHeader(applet_name()); + DrawInterface(); + } + + void OnButtonPress() { + CursorAction(cursor, CURSOR_LAST); + } + + void OnEncoderMove(int direction) { + if (!EditMode()) { + MoveCursor(cursor, direction, CURSOR_LAST); + return; + } + + // param LUT + const struct { uint8_t &p; int min, max; } params[] = { + { pigeons[0].val[0], 0, 63 }, // CHAN1_V1 + { pigeons[0].val[1], 0, 63 }, // CHAN1_V2 + { pigeons[0].mod, 1, 64 }, // CHAN1_MOD + { pigeons[1].val[0], 0, 63 }, // CHAN2_V1 + { pigeons[1].val[1], 0, 63 }, // CHAN2_V2 + { pigeons[1].mod, 1, 64 }, // CHAN2_MOD + { root_note, 0, 11 }, // DUMMY? + { root_note, 0, 11 }, // ROOT NOTE + }; + + if (cursor == SCALE) { + scale += direction; + if (scale >= OC::Scales::NUM_SCALES) scale = 0; + if (scale < 0) scale = OC::Scales::NUM_SCALES - 1; + QuantizerConfigure(0, scale); + QuantizerConfigure(1, scale); + + return; + } + + params[cursor].p = constrain(params[cursor].p + direction, params[cursor].min, params[cursor].max); + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation {0,6}, pigeons[0].val[0]); + Pack(data, PackLocation {6,6}, pigeons[0].val[1]); + Pack(data, PackLocation {12,6}, pigeons[0].mod - 1); + Pack(data, PackLocation {18,6}, pigeons[1].val[0]); + Pack(data, PackLocation {24,6}, pigeons[1].val[1]); + Pack(data, PackLocation {30,6}, pigeons[1].mod - 1); + Pack(data, PackLocation {36,8}, scale); + Pack(data, PackLocation {44,4}, root_note); + return data; + } + + void OnDataReceive(uint64_t data) { + pigeons[0].val[0] = Unpack(data, PackLocation {0,6}); + pigeons[0].val[1] = Unpack(data, PackLocation {6,6}); + pigeons[0].mod = Unpack(data, PackLocation {12,6}) + 1; + pigeons[1].val[0] = Unpack(data, PackLocation {18,6}); + pigeons[1].val[1] = Unpack(data, PackLocation {24,6}); + pigeons[1].mod = Unpack(data, PackLocation {30,6}) + 1; + scale = Unpack(data, PackLocation {36,8}); + root_note = Unpack(data, PackLocation {44,4}); + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "Clock 1,2"; + help[HEMISPHERE_HELP_CVS] = "Modulo 1,2"; + help[HEMISPHERE_HELP_OUTS] = "Pitch 1,2"; + help[HEMISPHERE_HELP_ENCODER] = "Params"; + // "------------------" <-- Size Guide + } + +private: + int cursor; + struct Pigeon { + bool index; + uint8_t val[2] = {1, 2}; + uint8_t mod = 10; + uint8_t mod_v = 10; + uint32_t pulse; + + uint8_t Get() { return val[index]; } + uint8_t Bump() { + val[index] = (val[0] + val[1]) % mod_v; + index = !index; + return val[index]; + } + } pigeons[2]; + + int scale = 16; // Pentatonic minor + uint8_t root_note = 4; // key of E + + void DrawInterface() { + // cursor LUT + const struct { uint8_t x, y, w; } cur[CURSOR_LAST+1] = { + { 1, 22, 13 }, // val1 + { 25, 22, 13 }, // val2 + { 49, 22, 13 }, // mod + + { 1, 43, 13 }, // val1 + { 25, 43, 13 }, // val2 + { 49, 43, 13 }, // mod + + { 10, 63, 25 }, // scale + { 40, 63, 13 }, // root note + }; + + ForEachChannel(ch) { + int y = 14+ch*21; + gfxPrint(1 + pad(10, pigeons[ch].val[0]), y, pigeons[ch].val[0]); + gfxPrint(16, y, "+"); + gfxPrint(25 + pad(10, pigeons[ch].val[1]), y, pigeons[ch].val[1]); + gfxPrint(40, y, "%"); + gfxPrint(49, y, pigeons[ch].mod_v); + + y += 10; + gfxIcon( 5+(pigeons[ch].index ? 24 : 0), y, NOTE_ICON); + gfxIcon( 16, y, pigeons[ch].pulse ? SINGING_PIGEON_ICON : SILENT_PIGEON_ICON ); + + y += 8; + gfxLine(4, y, 54, y); // ---------------------------------- // + } + + gfxPrint(10, 55, OC::scale_names_short[scale]); + gfxPrint(40, 55, OC::Strings::note_names_unpadded[root_note]); + + // i'm proud of this one: + gfxCursor( cur[cursor].x, cur[cursor].y, cur[cursor].w ); + } + +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to Pigeons, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +Pigeons Pigeons_instance[2]; + +void Pigeons_Start(bool hemisphere) {Pigeons_instance[hemisphere].BaseStart(hemisphere);} +void Pigeons_Controller(bool hemisphere, bool forwarding) {Pigeons_instance[hemisphere].BaseController(forwarding);} +void Pigeons_View(bool hemisphere) {Pigeons_instance[hemisphere].BaseView();} +void Pigeons_OnButtonPress(bool hemisphere) {Pigeons_instance[hemisphere].OnButtonPress();} +void Pigeons_OnEncoderMove(bool hemisphere, int direction) {Pigeons_instance[hemisphere].OnEncoderMove(direction);} +void Pigeons_ToggleHelpScreen(bool hemisphere) {Pigeons_instance[hemisphere].HelpScreen();} +uint64_t Pigeons_OnDataRequest(bool hemisphere) {return Pigeons_instance[hemisphere].OnDataRequest();} +void Pigeons_OnDataReceive(bool hemisphere, uint64_t data) {Pigeons_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index 7598caa44..ef5d7db06 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -5,7 +5,6 @@ #define HS_ICON_SET const uint8_t VOCT_ICON[8] = { 0x07, 0x08, 0x27, 0x10, 0x08, 0xe4, 0xa0, 0xe0 }; -const uint8_t TR_ICON[8] = {0x00, 0x00, 0x1c, 0x1c, 0x1c, 0x00, 0x00, 0x00 }; const uint8_t GATE_ICON[8] = {0x40, 0x7e, 0x02, 0x02, 0x02, 0x7e, 0x40, 0x40}; const uint8_t SLEW_ICON[8] = { 0x40, 0x7e, 0x02, 0x02, 0x0c, 0x30, 0x40, 0x40}; const uint8_t LENGTH_ICON[8] = { 0x0a, 0x01, 0x09, 0x22, 0x48, 0x40, 0x28, 0x00 }; @@ -24,6 +23,7 @@ const uint8_t BEND_ICON[8] = { 0x00, 0x30, 0x3e, 0x82, 0x70, 0x0e, 0x01, 0 const uint8_t AFTERTOUCH_ICON[8] = {0x00,0x00,0x20,0x42,0xf5,0x48,0x20,0x00}; const uint8_t MIDI_ICON[8] = {0x3c,0x42,0x91,0x45,0x45,0x91,0x42,0x3c}; const uint8_t CV_ICON[8] = { 0x00, 0x3e, 0x22, 0x00, 0x1e, 0x20, 0x1e, 0x00 }; +const uint8_t TR_ICON[8] = { 0x00, 0x02, 0x3e, 0x02, 0x00, 0x3e, 0x0a, 0x36 }; const uint8_t SCALE_ICON[8] = { 0x00, 0x00, 0x0e, 0x8d, 0x61, 0x1e, 0x00, 0x00}; const uint8_t LOCK_ICON[8] = {0xf8, 0x8e, 0x89, 0xa9, 0x89, 0x8e, 0xf8, 0x00}; // old lock 0x00,0xf8,0xfe,0xf9,0x89,0xf9,0xfe,0xf8 @@ -80,11 +80,15 @@ const uint8_t METRO_L_ICON[8] = {0xf3,0x8c,0x9a,0xa2,0x82,0x8c,0xf0,0x00}; const uint8_t METRO_R_ICON[8] = {0xf0,0x8c,0x82,0xa2,0x9a,0x8c,0xf3,0x00}; // Notes -const uint8_t X_NOTE_ICON[8] = { 0x00, 0x00, 0xa0, 0x40, 0xa0, 0x1f, 0x00, 0x00 }; // ??? +const uint8_t X_NOTE_ICON[8] = { 0x00, 0x00, 0xa0, 0x40, 0xa0, 0x1f, 0x00, 0x00 }; const uint8_t NOTE_ICON[8] = { 0x00, 0xe0, 0xe0, 0x7f, 0x02, 0x1c, 0x00, 0x00 }; const uint8_t NOTE2_ICON[8] = { 0x00, 0x00, 0xe0, 0xa0, 0xff, 0x00, 0x00, 0x00 }; const uint8_t NOTE4_ICON[8] = { 0x00, 0x00, 0xe0, 0xe0, 0xff, 0x00, 0x00, 0x00 }; +// Pigeons +const uint8_t SILENT_PIGEON_ICON[8] = { 0x3c, 0xc2, 0x01, 0x0d, 0x01, 0x22, 0x54, 0x88 }; +const uint8_t SINGING_PIGEON_ICON[8] = { 0x3c, 0xc2, 0x01, 0x0d, 0x01, 0x02, 0x48, 0x94 }; + // Waveform const uint8_t UP_DOWN_ICON[8] = {0x00,0x00,0x24,0x66,0xff,0x66,0x24,0x00}; const uint8_t LEFT_RIGHT_ICON[8] = {0x10,0x38,0x7c,0x10,0x10,0x7c,0x38,0x10}; diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 64bcc48cc..24e955902 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -48,6 +48,7 @@ DECLARE_APPLET( 27, 0x20, hMIDIOut), \ DECLARE_APPLET( 33, 0x10, MixerBal), \ DECLARE_APPLET( 20, 0x02, Palimpsest), \ + DECLARE_APPLET( 71, 0x02, Pigeons), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ DECLARE_APPLET( 70, 0x14, ResetClock), \ From 1829dd4ea9c32b7cfeba8343a96a46faf60a0999 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Tue, 24 Oct 2023 00:35:00 -0400 Subject: [PATCH 335/417] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cd9f7796..85ad60001 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ You can also customize the `platformio.ini` file to mix & match for yourself ;-) * 4 Presets in the new [**Hemisphere Config**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Hemisphere-Config) * Modal-editing style cursor navigation (and other usability tweaks) * Expanded internal [**Clock Setup**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Clock-Setup) -* A new App called [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) +* New Apps: [**Scenes**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Scenes) and [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) * **[DualTM](https://github.com/djphazer/O_C-BenisphereSuite/wiki/DualTM)** - two 32-bit shift registers. Assignable I/O. * **[EbbAndLfo](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Ebb-&-LFO)** (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking * **[EuclidX](https://github.com/djphazer/O_C-BenisphereSuite/wiki/EuclidX)** - AnnularFusion got a makeover, now includes padding, configurable CV input modulation - (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) From 71036dcc7cf2929570af90563d561a031b36cdd9 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 24 Oct 2023 03:37:23 -0400 Subject: [PATCH 336/417] bump T4.0 eeprom up to 3071 bytes Scenes EEPROM size comment - 264 bytes --- software/o_c_REV/APP_SCENES.ino | 2 +- software/o_c_REV/extern/avr/eeprom.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/APP_SCENES.ino b/software/o_c_REV/APP_SCENES.ino index b0c355e84..20b85821f 100644 --- a/software/o_c_REV/APP_SCENES.ino +++ b/software/o_c_REV/APP_SCENES.ino @@ -477,7 +477,7 @@ private: } }; -// TOTAL EEPROM SIZE: ?? +// TOTAL EEPROM SIZE: 264 bytes SETTINGS_DECLARE(ScenesAppPreset, SCENES_SETTING_LAST) { {0, 0, 255, "Flags", NULL, settings::STORAGE_TYPE_U8}, diff --git a/software/o_c_REV/extern/avr/eeprom.h b/software/o_c_REV/extern/avr/eeprom.h index f7959c82c..6e087c27d 100644 --- a/software/o_c_REV/extern/avr/eeprom.h +++ b/software/o_c_REV/extern/avr/eeprom.h @@ -24,7 +24,7 @@ // https://forum.pjrc.com/threads/34537-Teensy-LC-Increase-EEPROM-Size/page2 // modified version for Phazerville Suite -// This is a combo of the T3 and T4 versions of this file, with the 4.0 value bumped up to 2047 +// This is a combo of the T3 and T4 versions of this file, with the 4.0 value bumped up to 3071 #ifndef _AVR_EEPROM_H_ #define _AVR_EEPROM_H_ 1 @@ -35,7 +35,7 @@ #include "avr_functions.h" #if defined(ARDUINO_TEENSY40) - #define E2END 0x7FF // 0x437 + #define E2END 0xBFF // 0x437 #elif defined(ARDUINO_TEENSY41) #define E2END 0x10BB #elif defined(ARDUINO_TEENSY_MICROMOD) From b226595d2b41bf797f4de4eb0fc957f3f850f85b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 16 Oct 2023 21:14:23 -0400 Subject: [PATCH 337/417] Version bump v1.6.666 / New build defaults --- software/o_c_REV/OC_version.h | 2 +- software/o_c_REV/platformio.ini | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index 081f39423..d6dc77778 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.6" +"v1.6.666" diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 00c099782..82cb4f568 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -11,18 +11,12 @@ [platformio] src_dir = . default_envs = - main - main_flipped - main_vor - main_vor_flipped - oc_stock1 - oc_stock1_flipped - oc_stock1_vor - oc_stock1_vor_flipped - oc_stock2 - oc_stock2_flipped - oc_stock2_vor - oc_stock2_vor_flipped + pewpewpew + pewpewvor + wepwepwep + wepwepvor + T40 + T41 [env] platform = teensy@4.17.0 @@ -97,11 +91,20 @@ build_flags = -DENABLE_APP_BYTEBEATGEN -DPEWPEWPEW -DOC_VERSION_EXTRA="\"_phz\"" +[env:wepwepwep] +build_flags = + ${env:pewpewpew.build_flags} + -DFLIP_180 [env:pewpewvor] build_flags = ${env:pewpewpew.build_flags} - -DVOR ; -DFLIP_180 + -DVOR +[env:wepwepvor] +build_flags = + ${env:pewpewpew.build_flags} + -DVOR + -DFLIP_180 [env:main] build_flags = From 21e4104a2fc757c6d8da05d93e731ab1926e2fdd Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 4 Nov 2023 19:17:49 -0400 Subject: [PATCH 338/417] Update TODO.md --- TODO.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/TODO.md b/TODO.md index d31df15d9..3a9706ec9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,13 +1,19 @@ TODO === -* better MIDI input message delegation (event listeners?) -* global output quantizer setting for Hemispheres -* Add Root Note to DualTM -* import alternative grids_resources patterns for DrumMap2 -* Subharmonicon applets +* Fixup & Add auto-tuner to Calibr8or +* Move calibration routines to a proper App +* Runtime filtering/hiding of Applets +* Flexible input remapping for Hemisphere + - generalize applet params for assignment +* global quantizer settings in Hemisphere Config +* applet with modal interchange (CV modulation of scale) +* ProbMeloD - alternate melody on 2nd output +* Subharmonicon app/applets * Update Boilerplates -* Automatic stop for internal Clock +* Automatic stop for internal Clock? +* Polyphonic MIDI input tracking +* MIDI output for all apps? [APP IDEAS] * QUADRANTS @@ -15,9 +21,12 @@ TODO * Snake Game [DONE] -* - Fix FLIP_180 calibration -* - Add Clock Setup to Calibr8or -* - Calibr8or screensaver +* Fix FLIP_180 calibration +* Add Clock Setup to Calibr8or +* Calibr8or screensaver * Pull in Automatonnetz * Sync-Start for internal Clock * General Config screen (long-press right button) +* better MIDI input message delegation (event listeners?) +* import alternative grids_resources patterns for DrumMap2 +* Add Root Note to DualTM From 14d90c1e40c94165af7f84e0a0cd09d8b2a23fb3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 24 Oct 2023 19:51:13 -0400 Subject: [PATCH 339/417] Quantizer API; DualTM refactoring --- software/o_c_REV/HEM_TM2.ino | 25 ++++++++++++------------- software/o_c_REV/HSIOFrame.h | 3 +++ software/o_c_REV/HemisphereApplet.h | 28 +++++++++++++++++++++------- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 5adaa2c8a..1e42f5d7b 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -88,8 +88,6 @@ public: void Start() { reg[0] = random(0, 65535); reg[1] = ~reg[0]; - - QuantizerConfigure(0, scale); } void Controller() { @@ -171,14 +169,14 @@ public: int x = constrain(note_trans[2], -range_mod, range_mod); int y = range_mod; int n = (note * (y + x) + note2 * (y - x)) / (2*y); - slew(Output[ch], QuantizerLookup(0, n) + (root_note << 7)); + slew(Output[ch], QuantizerLookup(0, n)); break; } case PITCH1: - slew(Output[ch], QuantizerLookup(0, note + note_trans[0]) + (root_note << 7)); + slew(Output[ch], QuantizerLookup(0, note + note_trans[0])); break; case PITCH2: - slew(Output[ch], QuantizerLookup(0, note2 + note_trans[1]) + (root_note << 7)); + slew(Output[ch], QuantizerLookup(0, note2 + note_trans[1])); break; case MOD1: // 8-bit bi-polar proportioned CV slew(Output[ch], Proportion( int(reg[0] & 0xff)-0x7f, 0x80, HEMISPHERE_MAX_CV) ); @@ -240,13 +238,12 @@ public: p = constrain(p + direction, 0, 100); break; case SCALE: - scale += direction; - if (scale >= TM2_MAX_SCALE) scale = 0; - if (scale < 0) scale = TM2_MAX_SCALE - 1; - QuantizerConfigure(0, scale); + NudgeScale(0, direction); + NudgeScale(1, direction); break; case ROOT_NOTE: - root_note = constrain(root_note + direction, 0, 11); + root_note = GetRootNote(0); + root_note = SetRootNote(0, constrain(root_note + direction, 0, 11)); break; case RANGE: range = constrain(range + direction, 1, 32); @@ -296,12 +293,14 @@ public: outmode[0] = (OutputMode) Unpack(data, PackLocation {17,4}); outmode[1] = (OutputMode) Unpack(data, PackLocation {21,4}); scale = Unpack(data, PackLocation {25,8}); - QuantizerConfigure(0, scale); cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); smoothing = Unpack(data, PackLocation {41,6}); smoothing = constrain(smoothing, 0, 127); root_note = Unpack(data, PackLocation {47,4}); + + QuantizerConfigure(0, scale); + SetRootNote(0, root_note); } protected: @@ -434,8 +433,8 @@ private: default: case SLEW: gfxBitmap(1, 25, 8, SCALE_ICON); - gfxPrint(9, 25, OC::scale_names_short[scale]); - gfxPrint(39, 25, OC::Strings::note_names_unpadded[root_note]); + gfxPrint(9, 25, OC::scale_names_short[GetScale(0)]); + gfxPrint(39, 25, OC::Strings::note_names_unpadded[GetRootNote(0)]); gfxBitmap(1, 35, 8, UP_DOWN_ICON); gfxPrint(10, 35, range_mod); diff --git a/software/o_c_REV/HSIOFrame.h b/software/o_c_REV/HSIOFrame.h index 60188b286..51cd25895 100644 --- a/software/o_c_REV/HSIOFrame.h +++ b/software/o_c_REV/HSIOFrame.h @@ -10,6 +10,9 @@ namespace HS { braids::Quantizer quantizer[4]; // global shared quantizers +int quant_scale[4]; +int root_note[4]; + uint8_t trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS typedef struct MIDILogEntry { diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index db0be96c3..62c3c21bb 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -361,20 +361,34 @@ class HemisphereApplet { return HS::quantizer[io_offset + ch].Process(cv, root, transpose); } int QuantizerLookup(int ch, int note) { - return HS::quantizer[io_offset + ch].Lookup(note); + return HS::quantizer[io_offset + ch].Lookup(note) + (HS::root_note[io_offset+ch] << 7); } void QuantizerConfigure(int ch, int scale, uint16_t mask = 0xffff) { - const braids::Scale &quant_scale = OC::Scales::GetScale(scale); - HS::quantizer[io_offset + ch].Configure(quant_scale, mask); + HS::quant_scale[io_offset + ch] = scale; + HS::quantizer[io_offset + ch].Configure(OC::Scales::GetScale(scale), mask); + } + int GetScale(int ch) { + return HS::quant_scale[io_offset + ch]; + } + int GetRootNote(int ch) { + return HS::root_note[io_offset + ch]; + } + int SetRootNote(int ch, int root) { + return (HS::root_note[io_offset + ch] = root); + } + void NudgeScale(int ch, int dir) { + const int max = OC::Scales::NUM_SCALES; + int &s = HS::quant_scale[io_offset + ch]; + + s+= dir; + if (s >= max) s = 0; + if (s < 0) s = max - 1; + QuantizerConfigure(ch, s); } // Standard bi-polar CV modulation scenario -#if __cplusplus == 201703L template void Modulate(T ¶m, const int ch, const int min = 0, const int max = 255) { -#else - void Modulate(auto ¶m, const int ch, const int min = 0, const int max = 255) { -#endif int cv = DetentedIn(ch); param = constrain(param + Proportion(cv, HEMISPHERE_MAX_INPUT_CV, max), min, max); } From a45c1465242f3a414a92bccf1531240c41da9a29 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 8 Nov 2023 16:48:50 -0500 Subject: [PATCH 340/417] Fix EEPROM app data allocation for Teensy 4.x --- software/o_c_REV/OC_config.h | 4 +++- software/o_c_REV/util/EEPROMStorage.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/OC_config.h b/software/o_c_REV/OC_config.h index 9639a5621..de9902966 100644 --- a/software/o_c_REV/OC_config.h +++ b/software/o_c_REV/OC_config.h @@ -45,7 +45,9 @@ static constexpr unsigned long SETTINGS_SAVE_TIMEOUT_MS = 1000; #define EEPROM_APPDATA_END EEPROMStorage::LENGTH // This is the available space for all apps' settings (\sa OC_apps.ino) -#define EEPROM_APPDATA_BINARY_SIZE (1000 - 4) +#define EEPROM_APPDATA_BINARY_SIZE (EEPROM_APPDATA_END - EEPROM_APPDATA_START - 92) +// (I'm not entirely sure where 92 comes from... it was originally (1000 - 4) which leaves 92 bytes free +// -NJM #define OC_UI_DEBUG #define OC_UI_SEPARATE_ISR diff --git a/software/o_c_REV/util/EEPROMStorage.h b/software/o_c_REV/util/EEPROMStorage.h index 89ffa2e0e..5dc1d02dc 100644 --- a/software/o_c_REV/util/EEPROMStorage.h +++ b/software/o_c_REV/util/EEPROMStorage.h @@ -25,7 +25,7 @@ /* Define a storage implemenation using teensy EEPROM */ struct EEPROMStorage { - static const size_t LENGTH = 2048; + static const size_t LENGTH = E2END + 1; static void update(size_t addr, const void *data, size_t length) { EEPtr e = addr; From 1fdebcd388537a77cc3034175d3cc11f8ab93de6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 29 Oct 2023 17:18:55 -0400 Subject: [PATCH 341/417] Restore References app --- software/o_c_REV/APP_REFS.ino | 1006 +++++++++++++++++++++++++++++++ software/o_c_REV/OC_apps.ino | 3 + software/o_c_REV/platformio.ini | 3 +- 3 files changed, 1011 insertions(+), 1 deletion(-) create mode 100644 software/o_c_REV/APP_REFS.ino diff --git a/software/o_c_REV/APP_REFS.ino b/software/o_c_REV/APP_REFS.ino new file mode 100644 index 000000000..e405fa469 --- /dev/null +++ b/software/o_c_REV/APP_REFS.ino @@ -0,0 +1,1006 @@ +// Copyright (c) 2016 Patrick Dowling, 2017 Max Stadler & Tim Churches +// +// Author: Patrick Dowling (pld@gurkenkiste.com) +// Enhancements: Max Stadler and Tim Churches +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Very simple "reference" voltage app (not so simple any more...) + +#ifdef ENABLE_APP_REFERENCES + +#include "OC_apps.h" +#include "OC_menus.h" +#include "OC_strings.h" +#include "util/util_settings.h" +#include "OC_autotuner.h" +#include "src/drivers/FreqMeasure/OC_FreqMeasure.h" + +// autotune constants: +#define FREQ_MEASURE_TIMEOUT 512 +#define ERROR_TIMEOUT (FREQ_MEASURE_TIMEOUT << 0x4) +#define MAX_NUM_PASSES 1500 +#define CONVERGE_PASSES 5 +// +static constexpr double kAaboveMidCtoC0 = 0.03716272234383494188492; + +// +#ifdef FLIP_180 +const uint8_t DAC_CHANNEL_FTM = DAC_CHANNEL_A; +#else +const uint8_t DAC_CHANNEL_FTM = DAC_CHANNEL_D; +#endif + +// +#ifdef BUCHLA_4U + const float target_multipliers[OCTAVES] = { 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f }; +#else + const float target_multipliers[OCTAVES] = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f }; +#endif + +#ifdef BUCHLA_SUPPORT + const float target_multipliers_1V2[OCTAVES] = { + 0.1767766952966368931843f, + 0.3149802624737182976666f, + 0.5612310241546865086093f, + 1.0f, + 1.7817974362806785482150f, + 3.1748021039363991668836f, + 5.6568542494923805818985f, + 10.0793683991589855253324f, + 17.9593927729499718282113f, + 32.0f + }; + + const float target_multipliers_2V0[OCTAVES] = { + 0.3535533905932737863687f, + 0.5f, + 0.7071067811865475727373f, + 1.0f, + 1.4142135623730951454746f, + 2.0f, + 2.8284271247461902909492f, + 4.0f, + 5.6568542494923805818985f, + 8.0f + }; +#endif + +const uint8_t NUM_REF_CHANNELS = DAC_CHANNEL_LAST; + +enum ReferenceSetting { + REF_SETTING_OCTAVE, + REF_SETTING_SEMI, + REF_SETTING_RANGE, + REF_SETTING_RATE, + REF_SETTING_NOTES_OR_BPM, + REF_SETTING_A_ABOVE_MID_C_INTEGER, + REF_SETTING_A_ABOVE_MID_C_MANTISSA, + REF_SETTING_PPQN, + REF_SETTING_AUTOTUNE, + REF_SETTING_DUMMY, + REF_SETTING_LAST +}; + +enum ChannelPpqn { + CHANNEL_PPQN_1, + CHANNEL_PPQN_2, + CHANNEL_PPQN_4, + CHANNEL_PPQN_8, + CHANNEL_PPQN_16, + CHANNEL_PPQN_24, + CHANNEL_PPQN_32, + CHANNEL_PPQN_48, + CHANNEL_PPQN_64, + CHANNEL_PPQN_96, + CHANNEL_PPQN_LAST +}; + +class ReferenceChannel : public settings::SettingsBase { +public: + + static constexpr size_t kHistoryDepth = 10; + + void Init(DAC_CHANNEL dac_channel) { + InitDefaults(); + + rate_phase_ = 0; + mod_offset_ = 0; + last_pitch_ = 0; + autotuner_ = false; + autotuner_step_ = OC::DAC_VOLT_0_ARM; + dac_channel_ = dac_channel; + auto_DAC_offset_error_ = 0; + auto_frequency_ = 0; + auto_last_frequency_ = 0; + auto_freq_sum_ = 0; + auto_freq_count_ = 0; + auto_ready_ = 0; + ticks_since_last_freq_ = 0; + auto_next_step_ = false; + autotune_completed_ = false; + F_correction_factor_ = 0xFF; + correction_direction_ = false; + correction_cnt_positive_ = 0x0; + correction_cnt_negative_ = 0x0; + reset_calibration_data(); + update_enabled_settings(); + history_[0].Init(0x0); + } + + int get_octave() const { + return values_[REF_SETTING_OCTAVE]; + } + + int get_channel() const { + return dac_channel_; + } + + int32_t get_semitone() const { + return values_[REF_SETTING_SEMI]; + } + + int get_range() const { + return values_[REF_SETTING_RANGE]; + } + + uint32_t get_rate() const { + return values_[REF_SETTING_RATE]; + } + + uint8_t get_notes_or_bpm() const { + return values_[REF_SETTING_NOTES_OR_BPM]; + } + + double get_a_above_mid_c() const { + double mantissa_divisor = 100.0; + return static_cast(values_[REF_SETTING_A_ABOVE_MID_C_INTEGER]) + (static_cast(values_[REF_SETTING_A_ABOVE_MID_C_MANTISSA])/mantissa_divisor) ; + } + + uint8_t get_a_above_mid_c_mantissa() const { + return values_[REF_SETTING_A_ABOVE_MID_C_MANTISSA]; + } + + ChannelPpqn get_channel_ppqn() const { + return static_cast(values_[REF_SETTING_PPQN]); + } + + uint8_t autotuner_active() { + return (autotuner_ && autotuner_step_) ? (dac_channel_ + 0x1) : 0x0; + } + + bool autotuner_completed() { + return autotune_completed_; + } + + void autotuner_reset_completed() { + autotune_completed_ = false; + } + + bool autotuner_error() { + return auto_error_; + } + + uint8_t get_octave_cnt() { + return octaves_cnt_ + 0x1; + } + + uint8_t auto_tune_step() { + return autotuner_step_; + } + + void autotuner_arm(uint8_t _status) { + reset_autotuner(); + autotuner_ = _status ? true : false; + } + + void autotuner_run() { + autotuner_step_ = autotuner_ ? OC::DAC_VOLT_0_BASELINE : OC::DAC_VOLT_0_ARM; + if (autotuner_step_ == OC::DAC_VOLT_0_BASELINE) + // we start, so reset data to defaults: + OC::DAC::set_default_channel_calibration_data(dac_channel_); + } + + void auto_reset_step() { + auto_num_passes_ = 0x0; + auto_DAC_offset_error_ = 0x0; + correction_direction_ = false; + correction_cnt_positive_ = correction_cnt_negative_ = 0x0; + F_correction_factor_ = 0xFF; + auto_ready_ = false; + } + + void reset_autotuner() { + ticks_since_last_freq_ = 0x0; + auto_frequency_ = 0x0; + auto_last_frequency_ = 0x0; + auto_error_ = 0x0; + auto_ready_ = 0x0; + autotuner_ = 0x0; + autotuner_step_ = 0x0; + F_correction_factor_ = 0xFF; + correction_direction_ = false; + correction_cnt_positive_ = 0x0; + correction_cnt_negative_ = 0x0; + octaves_cnt_ = 0x0; + auto_num_passes_ = 0x0; + auto_DAC_offset_error_ = 0x0; + autotune_completed_ = 0x0; + reset_calibration_data(); + } + + float get_auto_frequency() { + return auto_frequency_; + } + + uint8_t _ready() { + return auto_ready_; + } + + void reset_calibration_data() { + + for (int i = 0; i <= OCTAVES; i++) { + auto_calibration_data_[i] = 0; + auto_target_frequencies_[i] = 0.0f; + } + } + + uint8_t data_available() { + return OC::DAC::calibration_data_used(dac_channel_); + } + + void use_default() { + OC::DAC::set_default_channel_calibration_data(dac_channel_); + } + + void use_auto_calibration() { + OC::DAC::set_auto_channel_calibration_data(dac_channel_); + } + + bool auto_frequency() { + + bool _f_result = false; + + if (ticks_since_last_freq_ > ERROR_TIMEOUT) { + auto_error_ = true; + } + + if (FreqMeasure.available()) { + + auto_freq_sum_ = auto_freq_sum_ + FreqMeasure.read(); + auto_freq_count_ = auto_freq_count_ + 1; + + // take more time as we're converging toward the target frequency + uint32_t _wait = (F_correction_factor_ == 0x1) ? (FREQ_MEASURE_TIMEOUT << 2) : (FREQ_MEASURE_TIMEOUT >> 2); + + if (ticks_since_last_freq_ > _wait) { + + // store frequency, reset, and poke ui to preempt screensaver: + auto_frequency_ = FreqMeasure.countToFrequency(auto_freq_sum_ / auto_freq_count_); + history_[0].Push(auto_frequency_); + auto_freq_sum_ = 0; + auto_ready_ = true; + auto_freq_count_ = 0; + _f_result = true; + ticks_since_last_freq_ = 0x0; + OC::ui._Poke(); + for (auto &sh : history_) + sh.Update(); + } + } + return _f_result; + } + + void measure_frequency_and_calc_error() { + + switch(autotuner_step_) { + + case OC::DAC_VOLT_0_ARM: + // do nothing + break; + case OC::DAC_VOLT_0_BASELINE: + // 0V baseline / calibration point: in this case, we don't correct. + { + bool _update = auto_frequency(); + if (_update && auto_num_passes_ > kHistoryDepth) { + + auto_last_frequency_ = auto_frequency_; + float history[kHistoryDepth]; + float average = 0.0f; + // average + history_->Read(history); + for (uint8_t i = 0; i < kHistoryDepth; i++) + average += history[i]; + // ... and derive target frequency at 0V + auto_frequency_ = (uint32_t) (0.5f + ((auto_frequency_ + average) / (float)(kHistoryDepth + 1))); // 0V + // reset step, and proceed: + auto_reset_step(); + autotuner_step_++; + } + else if (_update) + auto_num_passes_++; + } + break; + case OC::DAC_VOLT_TARGET_FREQUENCIES: + { + #ifdef BUCHLA_SUPPORT + + switch(OC::DAC::get_voltage_scaling(dac_channel_)) { + + case VOLTAGE_SCALING_1_2V_PER_OCT: // 1.2V/octave + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers_1V2[octaves_cnt_]; + break; + case VOLTAGE_SCALING_2V_PER_OCT: // 2V/octave + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers_2V0[octaves_cnt_]; + break; + default: // 1V/octave + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_]; + break; + } + #else + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_]; + #endif + octaves_cnt_++; + // go to next step, if done: + if (octaves_cnt_ >= OCTAVES) { + octaves_cnt_ = 0x0; + autotuner_step_++; + } + } + break; + case OC::DAC_VOLT_3m: + case OC::DAC_VOLT_2m: + case OC::DAC_VOLT_1m: + case OC::DAC_VOLT_0: + case OC::DAC_VOLT_1: + case OC::DAC_VOLT_2: + case OC::DAC_VOLT_3: + case OC::DAC_VOLT_4: + case OC::DAC_VOLT_5: + case OC::DAC_VOLT_6: + { + bool _update = auto_frequency(); + + if (_update && (auto_num_passes_ > MAX_NUM_PASSES)) { + /* target frequency reached */ + + if ((autotuner_step_ > OC::DAC_VOLT_2m) && (auto_last_frequency_ * 1.25f > auto_frequency_)) + auto_error_ = true; // throw error, if things don't seem to double ... + // average: + float history[kHistoryDepth]; + float average = 0.0f; + history_->Read(history); + for (uint8_t i = 0; i < kHistoryDepth; i++) + average += history[i]; + // store last frequency: + auto_last_frequency_ = ((auto_frequency_ + average) / (float)(kHistoryDepth + 1)); + // and DAC correction value: + auto_calibration_data_[autotuner_step_ - OC::DAC_VOLT_3m] = auto_DAC_offset_error_; + // and reset step: + auto_reset_step(); + autotuner_step_++; + } + // + else if (_update) { + + // count passes + auto_num_passes_++; + // and correct frequency + if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] > auto_frequency_) { + // update correction factor? + if (!correction_direction_) + F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; + correction_direction_ = true; + + auto_DAC_offset_error_ += F_correction_factor_; + // we're converging -- count passes, so we can stop after x attempts: + if (F_correction_factor_ == 0x1) + correction_cnt_positive_++; + } + else if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] < auto_frequency_) { + // update correction factor? + if (correction_direction_) + F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; + correction_direction_ = false; + + auto_DAC_offset_error_ -= F_correction_factor_; + // we're converging -- count passes, so we can stop after x attempts: + if (F_correction_factor_ == 0x1) + correction_cnt_negative_++; + } + + // approaching target? if so, go to next step. + if (correction_cnt_positive_ > CONVERGE_PASSES && correction_cnt_negative_ > CONVERGE_PASSES) + auto_num_passes_ = MAX_NUM_PASSES << 1; + } + } + break; + case OC::AUTO_CALIBRATION_STEP_LAST: + // step through the octaves: + if (ticks_since_last_freq_ > 2000) { + int32_t new_auto_calibration_point = OC::calibration_data.dac.calibrated_octaves[dac_channel_][octaves_cnt_] + auto_calibration_data_[octaves_cnt_]; + // write to DAC and update data + if (new_auto_calibration_point >= 65536 || new_auto_calibration_point < 0) { + auto_error_ = true; + autotuner_step_++; + } + else { + OC::DAC::set(dac_channel_, new_auto_calibration_point); + OC::DAC::update_auto_channel_calibration_data(dac_channel_, octaves_cnt_, new_auto_calibration_point); + ticks_since_last_freq_ = 0x0; + octaves_cnt_++; + } + } + // then stop ... + if (octaves_cnt_ > OCTAVES) { + autotune_completed_ = true; + // and point to auto data ... + OC::DAC::set_auto_channel_calibration_data(dac_channel_); + autotuner_step_++; + } + break; + default: + autotuner_step_ = OC::DAC_VOLT_0_ARM; + autotuner_ = 0x0; + break; + } + } + + void autotune_updateDAC() { + + switch(autotuner_step_) { + + case OC::DAC_VOLT_0_ARM: + { + F_correction_factor_ = 0x1; // don't go so fast + auto_frequency(); + OC::DAC::set(dac_channel_, OC::calibration_data.dac.calibrated_octaves[dac_channel_][OC::DAC::kOctaveZero]); + } + break; + case OC::DAC_VOLT_0_BASELINE: + // set DAC to 0.000V, default calibration: + OC::DAC::set(dac_channel_, OC::calibration_data.dac.calibrated_octaves[dac_channel_][OC::DAC::kOctaveZero]); + break; + case OC::DAC_VOLT_TARGET_FREQUENCIES: + case OC::AUTO_CALIBRATION_STEP_LAST: + // do nothing + break; + default: + // set DAC to calibration point + error + { + int32_t _default_calibration_point = OC::calibration_data.dac.calibrated_octaves[dac_channel_][autotuner_step_ - OC::DAC_VOLT_3m]; + OC::DAC::set(dac_channel_, _default_calibration_point + auto_DAC_offset_error_); + } + break; + } + } + + void Update() { + + if (autotuner_) { + autotune_updateDAC(); + ticks_since_last_freq_++; + return; + } + + int octave = get_octave(); + int range = get_range(); + if (range) { + rate_phase_ += OC_CORE_TIMER_RATE; + if (rate_phase_ >= get_rate() * 1000000UL) { + rate_phase_ = 0; + mod_offset_ = 1 - mod_offset_; + } + octave += mod_offset_ * range; + } else { + rate_phase_ = 0; + mod_offset_ = 0; + } + + int32_t semitone = get_semitone(); + OC::DAC::set(dac_channel_, OC::DAC::semitone_to_scaled_voltage_dac(dac_channel_, semitone, octave, OC::DAC::get_voltage_scaling(dac_channel_))); + last_pitch_ = (semitone + octave * 12) << 7; + } + + int num_enabled_settings() const { + return num_enabled_settings_; + } + + ReferenceSetting enabled_setting_at(int index) const { + return enabled_settings_[index]; + } + + void update_enabled_settings() { + ReferenceSetting *settings = enabled_settings_; + *settings++ = REF_SETTING_OCTAVE; + *settings++ = REF_SETTING_SEMI; + *settings++ = REF_SETTING_RANGE; + *settings++ = REF_SETTING_RATE; + *settings++ = REF_SETTING_AUTOTUNE; + //*settings++ = REF_SETTING_AUTOTUNE_ERROR; + + if (DAC_CHANNEL_FTM == dac_channel_) { + *settings++ = REF_SETTING_NOTES_OR_BPM; + *settings++ = REF_SETTING_A_ABOVE_MID_C_INTEGER; + *settings++ = REF_SETTING_A_ABOVE_MID_C_MANTISSA; + *settings++ = REF_SETTING_PPQN; + } + else { + *settings++ = REF_SETTING_DUMMY; + *settings++ = REF_SETTING_DUMMY; + } + + num_enabled_settings_ = settings - enabled_settings_; + } + + void RenderScreensaver(weegfx::coord_t start_x, uint8_t chan) const; + +private: + uint32_t rate_phase_; + int mod_offset_; + int32_t last_pitch_; + bool autotuner_; + uint8_t autotuner_step_; + int32_t auto_DAC_offset_error_; + float auto_frequency_; + float auto_target_frequencies_[OCTAVES + 1]; + int16_t auto_calibration_data_[OCTAVES + 1]; + float auto_last_frequency_; + bool auto_next_step_; + bool auto_error_; + bool auto_ready_; + bool autotune_completed_; + uint32_t auto_freq_sum_; + uint32_t auto_freq_count_; + uint32_t ticks_since_last_freq_; + uint32_t auto_num_passes_; + uint16_t F_correction_factor_; + bool correction_direction_; + int16_t correction_cnt_positive_; + int16_t correction_cnt_negative_; + int16_t octaves_cnt_; + DAC_CHANNEL dac_channel_; + + OC::vfx::ScrollingHistory history_[0x1]; + + int num_enabled_settings_; + ReferenceSetting enabled_settings_[REF_SETTING_LAST]; +}; + +const char* const notes_or_bpm[2] = { + "notes", "bpm", +}; + +const char* const ppqn_labels[10] = { + " 1", " 2", " 4", " 8", "16", "24", "32", "48", "64", "96", +}; + +const char* const error[] = { + "0.050", "0.125", "0.250", "0.500", "1.000", "2.000", "4.000" +}; + +SETTINGS_DECLARE(ReferenceChannel, REF_SETTING_LAST) { + #ifdef BUCHLA_4U + { 0, 0, 9, "Octave", nullptr, settings::STORAGE_TYPE_I8 }, + #elif defined(VOR) + {0, 0, 10, "Octave", nullptr, settings::STORAGE_TYPE_I8 }, + #else + { 0, -3, 6, "Octave", nullptr, settings::STORAGE_TYPE_I8 }, + #endif + { 0, 0, 11, "Semitone", OC::Strings::note_names_unpadded, settings::STORAGE_TYPE_U8 }, + { 0, -3, 3, "Mod range oct", nullptr, settings::STORAGE_TYPE_U8 }, + { 0, 0, 30, "Mod rate (s)", nullptr, settings::STORAGE_TYPE_U8 }, + { 0, 0, 1, "Notes/BPM :", notes_or_bpm, settings::STORAGE_TYPE_U8 }, + { 440, 400, 480, "A above mid C", nullptr, settings::STORAGE_TYPE_U16 }, + { 0, 0, 99, " > mantissa", nullptr, settings::STORAGE_TYPE_U8 }, + { CHANNEL_PPQN_4, CHANNEL_PPQN_1, CHANNEL_PPQN_LAST - 1, "> ppqn", ppqn_labels, settings::STORAGE_TYPE_U8 }, + { 0, 0, 0, "--> autotune", NULL, settings::STORAGE_TYPE_U8 }, + { 0, 0, 0, "-", NULL, settings::STORAGE_TYPE_U8 } // dummy +}; + +class ReferencesApp { +public: + ReferencesApp() { } + + OC::Autotuner autotuner; + + void Init() { + int dac_channel = DAC_CHANNEL_A; + for (auto &channel : channels_) + channel.Init(static_cast(dac_channel++)); + + ui.selected_channel = DAC_CHANNEL_FTM; + ui.cursor.Init(0, channels_[DAC_CHANNEL_FTM].num_enabled_settings() - 1); + + freq_sum_ = 0; + freq_count_ = 0; + frequency_ = 0; + autotuner.Init(); + } + + void ISR() { + + for (auto &channel : channels_) + channel.Update(); + + uint8_t _autotuner_active_channel = 0x0; + for (auto &channel : channels_) + _autotuner_active_channel += channel.autotuner_active(); + + if (_autotuner_active_channel) { + channels_[_autotuner_active_channel - 0x1].measure_frequency_and_calc_error(); + return; + } + else if (FreqMeasure.available()) { + // average several readings together + freq_sum_ = freq_sum_ + FreqMeasure.read(); + freq_count_ = freq_count_ + 1; + + if (milliseconds_since_last_freq_ > 750) { + frequency_ = FreqMeasure.countToFrequency(freq_sum_ / freq_count_); + freq_sum_ = 0; + freq_count_ = 0; + milliseconds_since_last_freq_ = 0; + } + } else if (milliseconds_since_last_freq_ > 100000) { + frequency_ = 0.0f; + } + } + + ReferenceChannel &selected_channel() { + return channels_[ui.selected_channel]; + } + + struct { + int selected_channel; + menu::ScreenCursor cursor; + } ui; + + ReferenceChannel channels_[DAC_CHANNEL_LAST]; + + float get_frequency( ) { + return(frequency_) ; + } + + float get_ppqn() { + float ppqn_ = 4.0 ; + switch(channels_[DAC_CHANNEL_FTM].get_channel_ppqn()){ + case CHANNEL_PPQN_1: + ppqn_ = 1.0; + break; + case CHANNEL_PPQN_2: + ppqn_ = 2.0; + break; + case CHANNEL_PPQN_4: + ppqn_ = 4.0; + break; + case CHANNEL_PPQN_8: + ppqn_ = 8.0; + break; + case CHANNEL_PPQN_16: + ppqn_ = 16.0; + break; + case CHANNEL_PPQN_24: + ppqn_ = 24.0; + break; + case CHANNEL_PPQN_32: + ppqn_ = 32.0; + break; + case CHANNEL_PPQN_48: + ppqn_ = 48.0; + break; + case CHANNEL_PPQN_64: + ppqn_ = 64.0; + break; + case CHANNEL_PPQN_96: + ppqn_ = 96.0; + break; + default: + ppqn_ = 8.0 ; + break; + } + return(ppqn_); + } + + float get_bpm( ) { + return((60.0 * frequency_)/get_ppqn()) ; + } + + bool get_notes_or_bpm( ) { + return(static_cast(channels_[DAC_CHANNEL_FTM].get_notes_or_bpm())) ; + } + + float get_C0_freq() { + return(static_cast(channels_[DAC_CHANNEL_FTM].get_a_above_mid_c() * kAaboveMidCtoC0)); + } + +private: + double freq_sum_; + uint32_t freq_count_; + float frequency_ ; + elapsedMillis milliseconds_since_last_freq_; +}; + +ReferencesApp references_app; + +// App stubs +void REFS_init() { + references_app.Init(); +} + +size_t REFS_storageSize() { + return NUM_REF_CHANNELS * ReferenceChannel::storageSize(); +} + +size_t REFS_save(void *storage) { + size_t used = 0; + for (size_t i = 0; i < NUM_REF_CHANNELS; ++i) { + used += references_app.channels_[i].Save(static_cast(storage) + used); + } + return used; +} + +size_t REFS_restore(const void *storage) { + size_t used = 0; + for (size_t i = 0; i < NUM_REF_CHANNELS; ++i) { + used += references_app.channels_[i].Restore(static_cast(storage) + used); + references_app.channels_[i].update_enabled_settings(); + } + references_app.ui.cursor.AdjustEnd(references_app.channels_[0].num_enabled_settings() - 1); + return used; +} + +void REFS_isr() { + return references_app.ISR(); +} + +void REFS_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + references_app.ui.cursor.set_editing(false); + FreqMeasure.begin(); + references_app.autotuner.Close(); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + for (size_t i = 0; i < NUM_REF_CHANNELS; ++i) + references_app.channels_[i].reset_autotuner(); + break; + } +} + +void REFS_loop() { +} + +void REFS_menu() { + menu::QuadTitleBar::Draw(); + for (uint_fast8_t i = 0; i < NUM_REF_CHANNELS; ++i) { + menu::QuadTitleBar::SetColumn(i); + graphics.print((char)('A' + i)); + } + menu::QuadTitleBar::Selected(references_app.ui.selected_channel); + + const auto &channel = references_app.selected_channel(); + menu::SettingsList settings_list(references_app.ui.cursor); + menu::SettingsListItem list_item; + + while (settings_list.available()) { + const int setting = + channel.enabled_setting_at(settings_list.Next(list_item)); + const int value = channel.get_value(setting); + const settings::value_attr &attr = ReferenceChannel::value_attr(setting); + + switch (setting) { + case REF_SETTING_AUTOTUNE: + case REF_SETTING_DUMMY: + list_item.DrawNoValue(value, attr); + break; + default: + list_item.DrawDefault(value, attr); + break; + } + } + // autotuner ... + if (references_app.autotuner.active()) + references_app.autotuner.Draw(); +} + +void print_voltage(int octave, int fraction) { + graphics.printf("%01d", octave); + graphics.movePrintPos(-1, 0); graphics.print('.'); + graphics.movePrintPos(-2, 0); graphics.printf("%03d", fraction); +} + +void ReferenceChannel::RenderScreensaver(weegfx::coord_t start_x, uint8_t chan) const { + + // Mostly borrowed from QQ + + weegfx::coord_t x = start_x + 26; + weegfx::coord_t y = 34 ; // was 60 + // for (int i = 0; i < 5 ; ++i, y -= 4) // was i < 12 + graphics.setPixel(x, y); + + int32_t pitch = last_pitch_ ; + int32_t unscaled_pitch = last_pitch_ ; + + #ifdef BUCHLA_SUPPORT + switch (OC::DAC::get_voltage_scaling(chan)) { + + case VOLTAGE_SCALING_1_2V_PER_OCT: // 1.2V/oct + pitch = (pitch * 19661) >> 14 ; + break; + case VOLTAGE_SCALING_2V_PER_OCT: // 2V/oct + pitch = pitch << 1 ; + break; + default: // 1V/oct + break; + } + #endif + + pitch += (OC::DAC::kOctaveZero * 12) << 7; + unscaled_pitch += (OC::DAC::kOctaveZero * 12) << 7; + + + CONSTRAIN(pitch, 0, 120 << 7); + + int32_t octave = pitch / (12 << 7); + int32_t unscaled_octave = unscaled_pitch / (12 << 7); + pitch -= (octave * 12 << 7); + unscaled_pitch -= (unscaled_octave * 12 << 7); + int semitone = pitch >> 7; + int unscaled_semitone = unscaled_pitch >> 7; + + y = 34 - unscaled_semitone * 2; // was 60, multiplier was 4 + if (unscaled_semitone < 6) + graphics.setPrintPos(start_x + menu::kIndentDx, y - 7); + else + graphics.setPrintPos(start_x + menu::kIndentDx, y); + graphics.print(OC::Strings::note_names_unpadded[unscaled_semitone]); + + graphics.drawHLine(start_x + 16, y, 8); + graphics.drawBitmap8(start_x + 28, 34 - unscaled_octave * 2 - 1, OC::kBitmapLoopMarkerW, OC::bitmap_loop_markers_8 + OC::kBitmapLoopMarkerW); // was 60 + + #ifdef BUCHLA_SUPPORT + // Try and round to 3 digits + switch (OC::DAC::get_voltage_scaling(chan)) { + + case VOLTAGE_SCALING_1_2V_PER_OCT: // 1.2V/oct + semitone = (semitone * 10000 + 40) / 100; + break; + case VOLTAGE_SCALING_2V_PER_OCT: // 2V/oct + default: // 1V/oct + semitone = (semitone * 10000 + 50) / 120; + break; + } + #else + semitone = (semitone * 10000 + 50) / 120; + #endif + + semitone %= 1000; + octave -= OC::DAC::kOctaveZero; + + + // We want [sign]d.ddd = 6 chars in 32px space; with the current font width + // of 6px that's too tight, so squeeze in the mini minus... + y = menu::kTextDy; + graphics.setPrintPos(start_x + menu::kIndentDx, y); + if (octave >= 0) { + print_voltage(octave, semitone); + } else { + graphics.drawHLine(start_x, y + 3, 2); + if (semitone) + print_voltage(-octave - 1, 1000 - semitone); + else + print_voltage(-octave, 0); + } +} + +void REFS_screensaver() { + references_app.channels_[0].RenderScreensaver( 0, 0); + references_app.channels_[1].RenderScreensaver(32, 1); + references_app.channels_[2].RenderScreensaver(64, 2); + references_app.channels_[3].RenderScreensaver(96, 3); + graphics.setPrintPos(2, 44); + + float frequency_ = references_app.get_frequency() ; + float c0_freq_ = references_app.get_C0_freq() ; + float bpm_ = (60.0 * frequency_)/references_app.get_ppqn() ; + + int32_t freq_decicents_deviation_ = round(12000.0 * log2f(frequency_ / c0_freq_)) + 500; + int8_t freq_octave_ = -2 + ((freq_decicents_deviation_)/ 12000) ; + int8_t freq_note_ = (freq_decicents_deviation_ - ((freq_octave_ + 2) * 12000)) / 1000; + int32_t freq_decicents_residual_ = ((freq_decicents_deviation_ - ((freq_octave_ - 1) * 12000)) % 1000) - 500; + + if (frequency_ > 0.0) { + #ifdef FLIP_180 + graphics.printf("TR1 %7.3f Hz", frequency_); + #else + graphics.printf("TR4 %7.3f Hz", frequency_); + #endif + graphics.setPrintPos(2, 56); + if (references_app.get_notes_or_bpm()) { + graphics.printf("%7.2f bpm %2.0fppqn", bpm_, references_app.get_ppqn()); + } else if(frequency_ >= c0_freq_) { + graphics.printf("%+i %s %+7.1fc", freq_octave_, OC::Strings::note_names[freq_note_], freq_decicents_residual_ / 10.0) ; + } + } else { + graphics.print("TR4 no input") ; + } +} + +void REFS_handleButtonEvent(const UI::Event &event) { + + if (references_app.autotuner.active()) { + references_app.autotuner.HandleButtonEvent(event); + return; + } + + if (OC::CONTROL_BUTTON_R == event.control) { + + auto &selected_channel = references_app.selected_channel(); + switch (selected_channel.enabled_setting_at(references_app.ui.cursor.cursor_pos())) { + case REF_SETTING_AUTOTUNE: + references_app.autotuner.Open(&selected_channel); + break; + case REF_SETTING_DUMMY: + break; + default: + references_app.ui.cursor.toggle_editing(); + break; + } + } +} + +void REFS_handleEncoderEvent(const UI::Event &event) { + + if (references_app.autotuner.active()) { + references_app.autotuner.HandleEncoderEvent(event); + return; + } + + if (OC::CONTROL_ENCODER_L == event.control) { + + int previous = references_app.selected_channel().num_enabled_settings(); + int selected = references_app.ui.selected_channel + event.value; + CONSTRAIN(selected, 0, NUM_REF_CHANNELS - 0x1); + references_app.ui.selected_channel = selected; + + // hack -- deal w/ menu items / channels + if ((references_app.ui.cursor.cursor_pos() > 4) && (previous > references_app.selected_channel().num_enabled_settings())) { + references_app.ui.cursor.Init(0, 0); + references_app.ui.cursor.AdjustEnd(references_app.selected_channel().num_enabled_settings() - 1); + } + else + references_app.ui.cursor.AdjustEnd(references_app.selected_channel().num_enabled_settings() - 1); + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (references_app.ui.cursor.editing()) { + auto &selected_channel = references_app.selected_channel(); + ReferenceSetting setting = selected_channel.enabled_setting_at(references_app.ui.cursor.cursor_pos()); + if (setting == REF_SETTING_DUMMY) + references_app.ui.cursor.set_editing(false); + selected_channel.change_value(setting, event.value); + selected_channel.update_enabled_settings(); + } else { + references_app.ui.cursor.Scroll(event.value); + } + } +} + +#endif // ENABLE_APP_REFERENCES diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index be01f691e..f764a6d12 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -103,6 +103,9 @@ OC::App available_apps[] = { #ifdef ENABLE_APP_PONG DECLARE_APP('P','O', "Pong", PONGGAME), #endif + #ifdef ENABLE_APP_REFERENCES + DECLARE_APP('R','F', "References", REFS), + #endif DECLARE_APP('B','R', "Backup / Restore", Backup), DECLARE_APP('S','E', "Setup / About", Settings), }; diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 82cb4f568..d9a779f5d 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -85,10 +85,11 @@ build_flags = -DENABLE_APP_ENIGMA -DENABLE_APP_MIDI -DENABLE_APP_PONG - -DENABLE_APP_PIQUED +; -DENABLE_APP_PIQUED -DENABLE_APP_POLYLFO -DENABLE_APP_H1200 -DENABLE_APP_BYTEBEATGEN + -DENABLE_APP_REFERENCES -DPEWPEWPEW -DOC_VERSION_EXTRA="\"_phz\"" [env:wepwepwep] From ff1b0f94c5408c3f254814af20abd02e1a89ef56 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 29 Oct 2023 17:28:21 -0400 Subject: [PATCH 342/417] REFS: fix button handler; fix printf for float --- software/o_c_REV/APP_REFS.ino | 30 +++++++++++++++++++++++++----- software/o_c_REV/OC_autotuner.h | 16 ++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/APP_REFS.ino b/software/o_c_REV/APP_REFS.ino index e405fa469..5f28aaf72 100644 --- a/software/o_c_REV/APP_REFS.ino +++ b/software/o_c_REV/APP_REFS.ino @@ -912,6 +912,15 @@ void ReferenceChannel::RenderScreensaver(weegfx::coord_t start_x, uint8_t chan) } } +/* +void printFloat(float f) { + const int f_ = int(floor(f * 1000)); + const int value = f_ / 1000; + const int cents = f_ % 1000; + graphics.printf("%6u.%03u", value, cents); +} +*/ + void REFS_screensaver() { references_app.channels_[0].RenderScreensaver( 0, 0); references_app.channels_[1].RenderScreensaver(32, 1); @@ -929,16 +938,27 @@ void REFS_screensaver() { int32_t freq_decicents_residual_ = ((freq_decicents_deviation_ - ((freq_octave_ - 1) * 12000)) % 1000) - 500; if (frequency_ > 0.0) { + { + const int f = int(floor(frequency_ * 1000)); + const int value = f / 1000; + const int cents = f % 1000; #ifdef FLIP_180 - graphics.printf("TR1 %7.3f Hz", frequency_); + graphics.printf("TR1 %7u.%03u Hz", value, cents); #else - graphics.printf("TR4 %7.3f Hz", frequency_); + graphics.printf("TR4 %7u.%03u Hz", value, cents); #endif + } graphics.setPrintPos(2, 56); if (references_app.get_notes_or_bpm()) { - graphics.printf("%7.2f bpm %2.0fppqn", bpm_, references_app.get_ppqn()); + const int f = int(floor(bpm_ * 100)); + const int value = f / 100; + const int cents = f % 100; + graphics.printf("%5u.%02u bpm %2.0fppqn", value, cents, references_app.get_ppqn()); } else if(frequency_ >= c0_freq_) { - graphics.printf("%+i %s %+7.1fc", freq_octave_, OC::Strings::note_names[freq_note_], freq_decicents_residual_ / 10.0) ; + const int f = int(floor(freq_decicents_residual_)); + const int value = f / 10; + const int cents = f % 10; + graphics.printf("%+i %s %+5u.%01uc", freq_octave_, OC::Strings::note_names[freq_note_], value, cents) ; } } else { graphics.print("TR4 no input") ; @@ -952,7 +972,7 @@ void REFS_handleButtonEvent(const UI::Event &event) { return; } - if (OC::CONTROL_BUTTON_R == event.control) { + if (OC::CONTROL_BUTTON_R == event.control && event.type == UI::EVENT_BUTTON_PRESS) { auto &selected_channel = references_app.selected_channel(); switch (selected_channel.enabled_setting_at(references_app.ui.cursor.cursor_pos())) { diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index 3558a933a..4f05aba44 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -160,8 +160,12 @@ class Autotuner { if (_freq == 0.0f) graphics.printf("wait ..."); else - // TODO: printf doesn't handle floats with TEENSY_OPT_SMALLEST_CODE - graphics.printf("%7.3f", _freq); + { + const int f = int(floor(_freq * 1000)); + const int value = f / 1000; + const int cents = f % 1000; + graphics.printf("%5u.%03u", value, cents); + } } break; case AT_RUN: @@ -178,8 +182,12 @@ class Autotuner { if (!owner_->_ready()) graphics.print(" "); else - // TODO: printf doesn't handle floats with TEENSY_OPT_SMALLEST_CODE - graphics.printf(" > %7.3f", owner_->get_auto_frequency()); + { + const int f = int(floor(owner_->get_auto_frequency() * 1000)); + const int value = f / 1000; + const int cents = f % 1000; + graphics.printf(" > %5u.%03u", value, cents); + } } } break; From 9efdb8eb8eecbafdc0d6222ec011c3a21f975a27 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 3 Nov 2023 21:00:19 -0400 Subject: [PATCH 343/417] REFS/Autotuner fixes printf float fixes; debug stuff 16 total octave points on VOR (-5 to +10) --- software/o_c_REV/APP_REFS.ino | 107 ++++++++++++++++++++------------ software/o_c_REV/OC_autotuner.h | 18 +++--- software/o_c_REV/OC_ui.cpp | 1 + software/o_c_REV/VBiasManager.h | 1 + software/o_c_REV/o_c_REV.ino | 3 +- software/o_c_REV/platformio.ini | 3 + 6 files changed, 84 insertions(+), 49 deletions(-) diff --git a/software/o_c_REV/APP_REFS.ino b/software/o_c_REV/APP_REFS.ino index 5f28aaf72..11e6fb155 100644 --- a/software/o_c_REV/APP_REFS.ino +++ b/software/o_c_REV/APP_REFS.ino @@ -33,6 +33,12 @@ #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" // autotune constants: +#ifdef VOR +static constexpr int ACTIVE_OCTAVES = OCTAVES; +#else +static constexpr int ACTIVE_OCTAVES = OCTAVES - 1; +#endif + #define FREQ_MEASURE_TIMEOUT 512 #define ERROR_TIMEOUT (FREQ_MEASURE_TIMEOUT << 0x4) #define MAX_NUM_PASSES 1500 @@ -49,13 +55,14 @@ const uint8_t DAC_CHANNEL_FTM = DAC_CHANNEL_D; // #ifdef BUCHLA_4U - const float target_multipliers[OCTAVES] = { 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f }; + constexpr float target_multipliers[OCTAVES] = { 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f }; #else - const float target_multipliers[OCTAVES] = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f }; + constexpr float target_multipliers[OCTAVES + 6] = { 0.03125f, 0.0625f, 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f, 1024.0f }; +// constexpr float target_multipliers[OCTAVES] = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f }; #endif #ifdef BUCHLA_SUPPORT - const float target_multipliers_1V2[OCTAVES] = { + constexpr float target_multipliers_1V2[OCTAVES] = { 0.1767766952966368931843f, 0.3149802624737182976666f, 0.5612310241546865086093f, @@ -68,7 +75,7 @@ const uint8_t DAC_CHANNEL_FTM = DAC_CHANNEL_D; 32.0f }; - const float target_multipliers_2V0[OCTAVES] = { + constexpr float target_multipliers_2V0[OCTAVES] = { 0.3535533905932737863687f, 0.5f, 0.7071067811865475727373f, @@ -211,6 +218,7 @@ public: } void autotuner_run() { + SERIAL_PRINTLN("Starting autotuner..."); autotuner_step_ = autotuner_ ? OC::DAC_VOLT_0_BASELINE : OC::DAC_VOLT_0_ARM; if (autotuner_step_ == OC::DAC_VOLT_0_BASELINE) // we start, so reset data to defaults: @@ -218,6 +226,7 @@ public: } void auto_reset_step() { + SERIAL_PRINTLN("Autotuner reset step..."); auto_num_passes_ = 0x0; auto_DAC_offset_error_ = 0x0; correction_direction_ = false; @@ -245,7 +254,7 @@ public: reset_calibration_data(); } - float get_auto_frequency() { + const uint32_t get_auto_frequency() { return auto_frequency_; } @@ -255,7 +264,7 @@ public: void reset_calibration_data() { - for (int i = 0; i <= OCTAVES; i++) { + for (int i = 0; i < OCTAVES + 1; i++) { auto_calibration_data_[i] = 0; auto_target_frequencies_[i] = 0.0f; } @@ -292,7 +301,7 @@ public: if (ticks_since_last_freq_ > _wait) { // store frequency, reset, and poke ui to preempt screensaver: - auto_frequency_ = FreqMeasure.countToFrequency(auto_freq_sum_ / auto_freq_count_); + auto_frequency_ = uint32_t(FreqMeasure.countToFrequency(auto_freq_sum_ / auto_freq_count_) * 1000); history_[0].Push(auto_frequency_); auto_freq_sum_ = 0; auto_ready_ = true; @@ -321,14 +330,15 @@ public: if (_update && auto_num_passes_ > kHistoryDepth) { auto_last_frequency_ = auto_frequency_; - float history[kHistoryDepth]; - float average = 0.0f; + uint32_t history[kHistoryDepth]; + uint32_t average = 0; // average history_->Read(history); for (uint8_t i = 0; i < kHistoryDepth; i++) average += history[i]; // ... and derive target frequency at 0V - auto_frequency_ = (uint32_t) (0.5f + ((auto_frequency_ + average) / (float)(kHistoryDepth + 1))); // 0V + auto_frequency_ = ((auto_frequency_ + average) / (kHistoryDepth + 1)); // 0V + SERIAL_PRINTLN("Baseline auto_frequency_ = %4.d", auto_frequency_); // reset step, and proceed: auto_reset_step(); autotuner_step_++; @@ -354,11 +364,11 @@ public: break; } #else - auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_]; + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_ + (5 - OC::DAC::kOctaveZero)]; #endif octaves_cnt_++; // go to next step, if done: - if (octaves_cnt_ >= OCTAVES) { + if (octaves_cnt_ > ACTIVE_OCTAVES) { octaves_cnt_ = 0x0; autotuner_step_++; } @@ -374,57 +384,73 @@ public: case OC::DAC_VOLT_4: case OC::DAC_VOLT_5: case OC::DAC_VOLT_6: + #ifdef VOR + case OC::DAC_VOLT_7: + #endif { bool _update = auto_frequency(); if (_update && (auto_num_passes_ > MAX_NUM_PASSES)) { /* target frequency reached */ + SERIAL_PRINTLN("* Target Frequency Reached *"); if ((autotuner_step_ > OC::DAC_VOLT_2m) && (auto_last_frequency_ * 1.25f > auto_frequency_)) auto_error_ = true; // throw error, if things don't seem to double ... + // average: - float history[kHistoryDepth]; - float average = 0.0f; + uint32_t history[kHistoryDepth]; + uint32_t average = 0; history_->Read(history); for (uint8_t i = 0; i < kHistoryDepth; i++) average += history[i]; + // store last frequency: - auto_last_frequency_ = ((auto_frequency_ + average) / (float)(kHistoryDepth + 1)); + auto_last_frequency_ = (auto_frequency_ + average) / (kHistoryDepth + 1); // and DAC correction value: auto_calibration_data_[autotuner_step_ - OC::DAC_VOLT_3m] = auto_DAC_offset_error_; - // and reset step: + + // reset and step forward auto_reset_step(); autotuner_step_++; } - // - else if (_update) { + else if (_update) + { + auto_num_passes_++; // count passes + + SERIAL_PRINTLN("auto_target_frequencies[%3d]_ = %3d", autotuner_step_ - OC::DAC_VOLT_3m, + auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] ); + SERIAL_PRINTLN("auto_frequency_ = %3d", auto_frequency_); - // count passes - auto_num_passes_++; // and correct frequency - if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] > auto_frequency_) { + if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] > auto_frequency_) + { // update correction factor? if (!correction_direction_) F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; + correction_direction_ = true; - + auto_DAC_offset_error_ += F_correction_factor_; + // we're converging -- count passes, so we can stop after x attempts: - if (F_correction_factor_ == 0x1) - correction_cnt_positive_++; + if (F_correction_factor_ == 0x1) correction_cnt_positive_++; } - else if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] < auto_frequency_) { + else if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] < auto_frequency_) + { // update correction factor? if (correction_direction_) F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; + correction_direction_ = false; auto_DAC_offset_error_ -= F_correction_factor_; + // we're converging -- count passes, so we can stop after x attempts: - if (F_correction_factor_ == 0x1) - correction_cnt_negative_++; + if (F_correction_factor_ == 0x1) correction_cnt_negative_++; } + SERIAL_PRINTLN("auto_DAC_offset_error_ = %3d", auto_DAC_offset_error_); + // approaching target? if so, go to next step. if (correction_cnt_positive_ > CONVERGE_PASSES && correction_cnt_negative_ > CONVERGE_PASSES) auto_num_passes_ = MAX_NUM_PASSES << 1; @@ -558,10 +584,10 @@ private: bool autotuner_; uint8_t autotuner_step_; int32_t auto_DAC_offset_error_; - float auto_frequency_; - float auto_target_frequencies_[OCTAVES + 1]; + uint32_t auto_frequency_; + uint32_t auto_target_frequencies_[OCTAVES + 1]; int16_t auto_calibration_data_[OCTAVES + 1]; - float auto_last_frequency_; + uint32_t auto_last_frequency_; bool auto_next_step_; bool auto_error_; bool auto_ready_; @@ -577,7 +603,7 @@ private: int16_t octaves_cnt_; DAC_CHANNEL dac_channel_; - OC::vfx::ScrollingHistory history_[0x1]; + OC::vfx::ScrollingHistory history_[0x1]; int num_enabled_settings_; ReferenceSetting enabled_settings_[REF_SETTING_LAST]; @@ -595,11 +621,12 @@ const char* const error[] = { "0.050", "0.125", "0.250", "0.500", "1.000", "2.000", "4.000" }; +// EEPROM size: 11 bytes * 4 channels == 44 bytes SETTINGS_DECLARE(ReferenceChannel, REF_SETTING_LAST) { #ifdef BUCHLA_4U { 0, 0, 9, "Octave", nullptr, settings::STORAGE_TYPE_I8 }, #elif defined(VOR) - {0, 0, 10, "Octave", nullptr, settings::STORAGE_TYPE_I8 }, + {0, -5, 10, "Octave", nullptr, settings::STORAGE_TYPE_I8 }, #else { 0, -3, 6, "Octave", nullptr, settings::STORAGE_TYPE_I8 }, #endif @@ -928,9 +955,9 @@ void REFS_screensaver() { references_app.channels_[3].RenderScreensaver(96, 3); graphics.setPrintPos(2, 44); - float frequency_ = references_app.get_frequency() ; - float c0_freq_ = references_app.get_C0_freq() ; - float bpm_ = (60.0 * frequency_)/references_app.get_ppqn() ; + const float frequency_ = references_app.get_frequency() ; + const float c0_freq_ = references_app.get_C0_freq() ; + const float bpm_ = (60.0 * frequency_)/references_app.get_ppqn() ; int32_t freq_decicents_deviation_ = round(12000.0 * log2f(frequency_ / c0_freq_)) + 500; int8_t freq_octave_ = -2 + ((freq_decicents_deviation_)/ 12000) ; @@ -943,9 +970,9 @@ void REFS_screensaver() { const int value = f / 1000; const int cents = f % 1000; #ifdef FLIP_180 - graphics.printf("TR1 %7u.%03u Hz", value, cents); + graphics.printf("TR1 %7d.%03d Hz", value, cents); #else - graphics.printf("TR4 %7u.%03u Hz", value, cents); + graphics.printf("TR4 %7d.%03d Hz", value, cents); #endif } graphics.setPrintPos(2, 56); @@ -953,12 +980,12 @@ void REFS_screensaver() { const int f = int(floor(bpm_ * 100)); const int value = f / 100; const int cents = f % 100; - graphics.printf("%5u.%02u bpm %2.0fppqn", value, cents, references_app.get_ppqn()); + graphics.printf("%5d.%02d bpm %2.0fppqn", value, cents, references_app.get_ppqn()); } else if(frequency_ >= c0_freq_) { const int f = int(floor(freq_decicents_residual_)); const int value = f / 10; - const int cents = f % 10; - graphics.printf("%+i %s %+5u.%01uc", freq_octave_, OC::Strings::note_names[freq_note_], value, cents) ; + const int cents = abs(f) % 10; + graphics.printf("%+i %s %+5d.%01dc", freq_octave_, OC::Strings::note_names[freq_note_], value, cents) ; } } else { graphics.print("TR4 no input") ; diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index 4f05aba44..cce14bdc4 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -54,6 +54,9 @@ enum AUTO_CALIBRATION_STEP { DAC_VOLT_4, DAC_VOLT_5, DAC_VOLT_6, +#ifdef VOR + DAC_VOLT_7, +#endif AUTO_CALIBRATION_STEP_LAST }; @@ -156,14 +159,13 @@ class Autotuner { break; case AT_READY: { graphics.print("arm > "); - float _freq = owner_->get_auto_frequency(); - if (_freq == 0.0f) + const uint32_t _freq = owner_->get_auto_frequency(); + if (_freq == 0) graphics.printf("wait ..."); else { - const int f = int(floor(_freq * 1000)); - const int value = f / 1000; - const int cents = f % 1000; + const uint32_t value = _freq / 1000; + const uint32_t cents = _freq % 1000; graphics.printf("%5u.%03u", value, cents); } } @@ -183,9 +185,9 @@ class Autotuner { graphics.print(" "); else { - const int f = int(floor(owner_->get_auto_frequency() * 1000)); - const int value = f / 1000; - const int cents = f % 1000; + const uint32_t f = owner_->get_auto_frequency(); + const uint32_t value = f / 1000; + const uint32_t cents = f % 1000; graphics.printf(" > %5u.%03u", value, cents); } } diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index 20f92d6f9..7a7d5d88c 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -1,6 +1,7 @@ #include #include +#include "OC_strings.h" #include "OC_apps.h" #include "OC_bitmaps.h" #include "OC_calibration.h" diff --git a/software/o_c_REV/VBiasManager.h b/software/o_c_REV/VBiasManager.h index 2825a659a..860c59ab9 100644 --- a/software/o_c_REV/VBiasManager.h +++ b/software/o_c_REV/VBiasManager.h @@ -137,6 +137,7 @@ class VBiasManager { case TWOCC<'E','G'>::value: // Piqued (or) 4x EG case TWOCC<'B','B'>::value: // Dialectic Ping Pong (or) Balls case TWOCC<'B','Y'>::value: // Viznutcracker sweet (or) Bytebeats + case TWOCC<'R','F'>::value: // References new_state = VBiasManager::UNI; break; case TWOCC<'P','L'>::value: // Quadraturia (or) Quadrature LFO diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index e692d51fd..1ef772928 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -98,6 +98,7 @@ void FASTRUN CORE_timer_ISR() { void setup() { delay(50); + Serial.begin(9600); #if defined(__IMXRT1062__) if (CrashReport) { while (!Serial && millis() < 3000) ; // wait @@ -110,7 +111,7 @@ void setup() { #endif SPI_init(); SERIAL_PRINTLN("* O&C BOOTING..."); - SERIAL_PRINTLN("* %s", OC_VERSION); + SERIAL_PRINTLN("* %s", OC::Strings::VERSION); OC::DEBUG::Init(); OC::DigitalInputs::Init(); diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index d9a779f5d..2c4cfb29c 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -27,6 +27,9 @@ lib_deps = EEPROM build_flags = -DTEENSY_OPT_SMALLEST_CODE -DUSB_MIDI +; -DUSB_MIDI_SERIAL +; -DOC_DEV +; -DPRINT_DEBUG -Iextern build_src_filter = +<*> From 00d4d58f66872f421e9e55d2522bda1bdc5f2ae7 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 4 Nov 2023 15:29:54 -0400 Subject: [PATCH 344/417] Calibr8or: Show channel auto-calibration status --- software/o_c_REV/APP_CALIBR8OR.ino | 3 +++ 1 file changed, 3 insertions(+) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index 72f217297..f3c870421 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -533,6 +533,9 @@ private: if (channel[sel_chan].offset >= 0) gfxPrint("+"); gfxPrint(channel[sel_chan].offset); + if ( OC::DAC::calibration_data_used( DAC_CHANNEL(sel_chan) ) == 0x01 ) + gfxPrint(" (auto)"); + // mode indicator if (!scale_edit) gfxIcon(0, 32 + edit_mode*22, RIGHT_ICON); From f59f9017f8f96a031f19127f7182a494e1cfb6cd Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 4 Nov 2023 23:52:05 -0400 Subject: [PATCH 345/417] Middle button cycles Clock in Hemisphere With the Vbias switcher accessible via dual-encoder press, I'm trying out other uses of the middle "VOR" button. Apps can now handle button_down, button_press and button_long_press events for it. --- software/o_c_REV/APP_HEMISPHERE.ino | 7 +++++++ software/o_c_REV/OC_ui.cpp | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index ae04a2c03..a50853d47 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -694,6 +694,13 @@ void HEMISPHERE_screensaver() { void HEMISPHERE_handleButtonEvent(const UI::Event &event) { switch (event.type) { case UI::EVENT_BUTTON_DOWN: + #ifdef VOR + if (event.control == OC::CONTROL_BUTTON_M) { + manager.ToggleClockRun(); + OC::ui.SetButtonIgnoreMask(); // ignore release and long-press + break; + } + #endif case UI::EVENT_BUTTON_PRESS: if (event.control == OC::CONTROL_BUTTON_UP || event.control == OC::CONTROL_BUTTON_DOWN) { manager.DelegateSelectButtonPush(event); diff --git a/software/o_c_REV/OC_ui.cpp b/software/o_c_REV/OC_ui.cpp index 7a7d5d88c..fe742d95a 100644 --- a/software/o_c_REV/OC_ui.cpp +++ b/software/o_c_REV/OC_ui.cpp @@ -128,8 +128,7 @@ UiMode Ui::DispatchEvents(App *app) { case UI::EVENT_BUTTON_DOWN: #ifdef VOR // dual encoder press - if ( ((OC::CONTROL_BUTTON_L | OC::CONTROL_BUTTON_R) == event.mask) - || (OC::CONTROL_BUTTON_M == event.control) ) + if ( ((OC::CONTROL_BUTTON_L | OC::CONTROL_BUTTON_R) == event.mask) ) { VBiasManager *vbias_m = vbias_m->get(); vbias_m->AdvanceBias(); From db881955cda502523bda9c32d9039b33aa3fbe0b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 6 Nov 2023 18:30:23 -0500 Subject: [PATCH 346/417] Applet header extracted to base class --- software/o_c_REV/APP_HEMISPHERE.ino | 5 +++-- software/o_c_REV/HEM_ADEG.ino | 1 - software/o_c_REV/HEM_ADSREG.ino | 1 - software/o_c_REV/HEM_ASR.ino | 1 - software/o_c_REV/HEM_AttenuateOffset.ino | 1 - software/o_c_REV/HEM_Binary.ino | 1 - software/o_c_REV/HEM_BootsNCat.ino | 1 - software/o_c_REV/HEM_Brancher.ino | 1 - software/o_c_REV/HEM_BugCrack.ino | 1 - software/o_c_REV/HEM_Burst.ino | 1 - software/o_c_REV/HEM_Button.ino | 1 - software/o_c_REV/HEM_CVRecV2.ino | 1 - software/o_c_REV/HEM_Calculate.ino | 1 - software/o_c_REV/HEM_Carpeggio.ino | 1 - software/o_c_REV/HEM_Chordinator.ino | 2 -- software/o_c_REV/HEM_ClockDivider.ino | 1 - software/o_c_REV/HEM_ClockSkip.ino | 1 - software/o_c_REV/HEM_Compare.ino | 1 - software/o_c_REV/HEM_DrCrusher.ino.disabled | 1 - software/o_c_REV/HEM_DrumMap.ino | 1 - software/o_c_REV/HEM_DualQuant.ino | 1 - software/o_c_REV/HEM_EbbAndLfo.ino | 2 -- software/o_c_REV/HEM_EnigmaJr.ino | 1 - software/o_c_REV/HEM_EnvFollow.ino | 1 - software/o_c_REV/HEM_EuclidX.ino | 1 - software/o_c_REV/HEM_GameOfLife.ino | 1 - software/o_c_REV/HEM_GateDelay.ino | 1 - software/o_c_REV/HEM_GatedVCA.ino | 1 - software/o_c_REV/HEM_ICONS.ino.disabled | 1 - software/o_c_REV/HEM_LoFiPCM.ino | 1 - software/o_c_REV/HEM_Logic.ino | 1 - software/o_c_REV/HEM_LowerRenz.ino | 1 - software/o_c_REV/HEM_Metronome.ino | 1 - software/o_c_REV/HEM_MixerBal.ino | 1 - software/o_c_REV/HEM_Palimpsest.ino | 1 - software/o_c_REV/HEM_Pigeons.ino | 1 - software/o_c_REV/HEM_ProbabilityDivider.ino | 1 - software/o_c_REV/HEM_ProbabilityMelody.ino | 1 - software/o_c_REV/HEM_ResetClock.ino | 1 - software/o_c_REV/HEM_RndWalk.ino | 1 - software/o_c_REV/HEM_RunglBook.ino | 1 - software/o_c_REV/HEM_ScaleDuet.ino | 1 - software/o_c_REV/HEM_Schmitt.ino | 1 - software/o_c_REV/HEM_Scope.ino | 2 -- software/o_c_REV/HEM_SequenceX.ino | 1 - software/o_c_REV/HEM_ShiftGate.ino | 1 - software/o_c_REV/HEM_Shredder.ino | 1 - software/o_c_REV/HEM_Shuffle.ino | 1 - software/o_c_REV/HEM_Slew.ino | 1 - software/o_c_REV/HEM_Squanch.ino | 1 - software/o_c_REV/HEM_Stairs.ino | 1 - software/o_c_REV/HEM_Switch.ino | 1 - software/o_c_REV/HEM_TB3PO.ino | 1 - software/o_c_REV/HEM_TLNeuron.ino | 1 - software/o_c_REV/HEM_TM.ino.disabled | 1 - software/o_c_REV/HEM_TM2.ino | 1 - software/o_c_REV/HEM_Trending.ino | 1 - software/o_c_REV/HEM_TrigSeq.ino | 1 - software/o_c_REV/HEM_TrigSeq16.ino | 1 - software/o_c_REV/HEM_Tuner.ino | 1 - software/o_c_REV/HEM_VectorEG.ino | 1 - software/o_c_REV/HEM_VectorLFO.ino | 1 - software/o_c_REV/HEM_VectorMod.ino | 1 - software/o_c_REV/HEM_VectorMorph.ino | 1 - software/o_c_REV/HEM_Voltage.ino | 1 - software/o_c_REV/HEM_hMIDIIn.ino | 1 - software/o_c_REV/HEM_hMIDIOut.ino | 1 - software/o_c_REV/HemisphereApplet.h | 4 +++- 68 files changed, 6 insertions(+), 72 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index a50853d47..2a3e937b0 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -163,10 +163,12 @@ HemispherePreset *hem_active_preset; //// Hemisphere Manager //////////////////////////////////////////////////////////////////////////////// +using namespace HS; + class HemisphereManager : public HSApplication { public: void Start() { - select_mode = -1; // Not selecting + //select_mode = -1; // Not selecting help_hemisphere = -1; clock_setup = 0; @@ -483,7 +485,6 @@ private: int preset_id = 0; int preset_cursor = 0; int my_applet[2]; // Indexes to available_applets - int select_mode; bool clock_setup; bool config_menu; bool isEditing = false; diff --git a/software/o_c_REV/HEM_ADEG.ino b/software/o_c_REV/HEM_ADEG.ino index 78a2db070..f3ff67742 100644 --- a/software/o_c_REV/HEM_ADEG.ino +++ b/software/o_c_REV/HEM_ADEG.ino @@ -88,7 +88,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_ADSREG.ino b/software/o_c_REV/HEM_ADSREG.ino index 19cd148d2..d5988346d 100644 --- a/software/o_c_REV/HEM_ADSREG.ino +++ b/software/o_c_REV/HEM_ADSREG.ino @@ -134,7 +134,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawIndicator(); DrawADSR(); } diff --git a/software/o_c_REV/HEM_ASR.ino b/software/o_c_REV/HEM_ASR.ino index dc234db53..587cc0e08 100644 --- a/software/o_c_REV/HEM_ASR.ino +++ b/software/o_c_REV/HEM_ASR.ino @@ -65,7 +65,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); DrawData(); } diff --git a/software/o_c_REV/HEM_AttenuateOffset.ino b/software/o_c_REV/HEM_AttenuateOffset.ino index e4aac44ec..115c18324 100644 --- a/software/o_c_REV/HEM_AttenuateOffset.ino +++ b/software/o_c_REV/HEM_AttenuateOffset.ino @@ -52,7 +52,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Binary.ino b/software/o_c_REV/HEM_Binary.ino index 9f85b1f1f..3de38f919 100644 --- a/software/o_c_REV/HEM_Binary.ino +++ b/software/o_c_REV/HEM_Binary.ino @@ -51,7 +51,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawDisplay(); } diff --git a/software/o_c_REV/HEM_BootsNCat.ino b/software/o_c_REV/HEM_BootsNCat.ino index 983224384..d75284473 100644 --- a/software/o_c_REV/HEM_BootsNCat.ino +++ b/software/o_c_REV/HEM_BootsNCat.ino @@ -95,7 +95,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Brancher.ino b/software/o_c_REV/HEM_Brancher.ino index 0759b7576..f4b17b477 100644 --- a/software/o_c_REV/HEM_Brancher.ino +++ b/software/o_c_REV/HEM_Brancher.ino @@ -50,7 +50,6 @@ public: } void View() { - gfxHeader("Brancher"); DrawInterface(); } diff --git a/software/o_c_REV/HEM_BugCrack.ino b/software/o_c_REV/HEM_BugCrack.ino index 3995fbd80..9f739bb73 100644 --- a/software/o_c_REV/HEM_BugCrack.ino +++ b/software/o_c_REV/HEM_BugCrack.ino @@ -219,7 +219,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Burst.ino b/software/o_c_REV/HEM_Burst.ino index 5144c495d..df425f54f 100644 --- a/software/o_c_REV/HEM_Burst.ino +++ b/software/o_c_REV/HEM_Burst.ino @@ -119,7 +119,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_Button.ino b/software/o_c_REV/HEM_Button.ino index fc93bc322..9e4d4784f 100644 --- a/software/o_c_REV/HEM_Button.ino +++ b/software/o_c_REV/HEM_Button.ino @@ -51,7 +51,6 @@ public: /* Draw the screen */ void View() { - gfxHeader(applet_name()); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_CVRecV2.ino b/software/o_c_REV/HEM_CVRecV2.ino index 65bd75e08..21e70f40f 100644 --- a/software/o_c_REV/HEM_CVRecV2.ino +++ b/software/o_c_REV/HEM_CVRecV2.ino @@ -85,7 +85,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Calculate.ino b/software/o_c_REV/HEM_Calculate.ino index 37341a2fc..2d491b409 100644 --- a/software/o_c_REV/HEM_Calculate.ino +++ b/software/o_c_REV/HEM_Calculate.ino @@ -71,7 +71,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); gfxSkyline(); } diff --git a/software/o_c_REV/HEM_Carpeggio.ino b/software/o_c_REV/HEM_Carpeggio.ino index 5bbd16a35..affe41960 100644 --- a/software/o_c_REV/HEM_Carpeggio.ino +++ b/software/o_c_REV/HEM_Carpeggio.ino @@ -92,7 +92,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawGrid(); } diff --git a/software/o_c_REV/HEM_Chordinator.ino b/software/o_c_REV/HEM_Chordinator.ino index b2b945290..2cd556570 100644 --- a/software/o_c_REV/HEM_Chordinator.ino +++ b/software/o_c_REV/HEM_Chordinator.ino @@ -62,8 +62,6 @@ public: } void View() { - gfxHeader(applet_name()); - gfxPrint(0, 15, OC::scale_names_short[scale]); if (cursor == 0) gfxCursor(0, 23, 30); diff --git a/software/o_c_REV/HEM_ClockDivider.ino b/software/o_c_REV/HEM_ClockDivider.ino index e3a5b2a62..caef21453 100644 --- a/software/o_c_REV/HEM_ClockDivider.ino +++ b/software/o_c_REV/HEM_ClockDivider.ino @@ -80,7 +80,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); } diff --git a/software/o_c_REV/HEM_ClockSkip.ino b/software/o_c_REV/HEM_ClockSkip.ino index ef93be042..cc6454e5d 100644 --- a/software/o_c_REV/HEM_ClockSkip.ino +++ b/software/o_c_REV/HEM_ClockSkip.ino @@ -49,7 +49,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_Compare.ino b/software/o_c_REV/HEM_Compare.ino index ae79a1fd0..4a33ce86e 100644 --- a/software/o_c_REV/HEM_Compare.ino +++ b/software/o_c_REV/HEM_Compare.ino @@ -49,7 +49,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_DrCrusher.ino.disabled b/software/o_c_REV/HEM_DrCrusher.ino.disabled index 6fafb8882..4f76c8666 100644 --- a/software/o_c_REV/HEM_DrCrusher.ino.disabled +++ b/software/o_c_REV/HEM_DrCrusher.ino.disabled @@ -50,7 +50,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index 60822c65d..118a5ef9d 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -127,7 +127,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_DualQuant.ino b/software/o_c_REV/HEM_DualQuant.ino index 210c3c428..46a776964 100644 --- a/software/o_c_REV/HEM_DualQuant.ino +++ b/software/o_c_REV/HEM_DualQuant.ino @@ -60,7 +60,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); } diff --git a/software/o_c_REV/HEM_EbbAndLfo.ino b/software/o_c_REV/HEM_EbbAndLfo.ino index 3cdf9c1ff..2c5a32e8e 100644 --- a/software/o_c_REV/HEM_EbbAndLfo.ino +++ b/software/o_c_REV/HEM_EbbAndLfo.ino @@ -76,8 +76,6 @@ public: } void View() { - gfxHeader(applet_name()); - ForEachChannel(ch) { int h = 17; int bottom = 32 + (h + 1) * ch; diff --git a/software/o_c_REV/HEM_EnigmaJr.ino b/software/o_c_REV/HEM_EnigmaJr.ino index 208e1dbc5..849b67c09 100644 --- a/software/o_c_REV/HEM_EnigmaJr.ino +++ b/software/o_c_REV/HEM_EnigmaJr.ino @@ -69,7 +69,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_EnvFollow.ino b/software/o_c_REV/HEM_EnvFollow.ino index aa5c5148f..893ea9b8e 100644 --- a/software/o_c_REV/HEM_EnvFollow.ino +++ b/software/o_c_REV/HEM_EnvFollow.ino @@ -74,7 +74,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); gfxSkyline(); } diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index fe81afb88..dbe16a954 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -120,7 +120,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSteps(); DrawEditor(); } diff --git a/software/o_c_REV/HEM_GameOfLife.ino b/software/o_c_REV/HEM_GameOfLife.ino index 185385219..d2da8a8bd 100644 --- a/software/o_c_REV/HEM_GameOfLife.ino +++ b/software/o_c_REV/HEM_GameOfLife.ino @@ -38,7 +38,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawBoard(); DrawIndicator(); DrawCrosshairs(); diff --git a/software/o_c_REV/HEM_GateDelay.ino b/software/o_c_REV/HEM_GateDelay.ino index 32be83ce7..1bde3d03f 100644 --- a/software/o_c_REV/HEM_GateDelay.ino +++ b/software/o_c_REV/HEM_GateDelay.ino @@ -56,7 +56,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_GatedVCA.ino b/software/o_c_REV/HEM_GatedVCA.ino index 98319cc2d..60822f9be 100644 --- a/software/o_c_REV/HEM_GatedVCA.ino +++ b/software/o_c_REV/HEM_GatedVCA.ino @@ -45,7 +45,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_ICONS.ino.disabled b/software/o_c_REV/HEM_ICONS.ino.disabled index 98c692297..75bce04a5 100644 --- a/software/o_c_REV/HEM_ICONS.ino.disabled +++ b/software/o_c_REV/HEM_ICONS.ino.disabled @@ -43,7 +43,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_LoFiPCM.ino b/software/o_c_REV/HEM_LoFiPCM.ino index d948e5ad3..f2b33c677 100644 --- a/software/o_c_REV/HEM_LoFiPCM.ino +++ b/software/o_c_REV/HEM_LoFiPCM.ino @@ -80,7 +80,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); if (play) DrawWaveform(); } diff --git a/software/o_c_REV/HEM_Logic.ino b/software/o_c_REV/HEM_Logic.ino index acdd8ec28..dea6cc3e4 100644 --- a/software/o_c_REV/HEM_Logic.ino +++ b/software/o_c_REV/HEM_Logic.ino @@ -71,7 +71,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_LowerRenz.ino b/software/o_c_REV/HEM_LowerRenz.ino index b4f087f7b..3e9af5ac9 100644 --- a/software/o_c_REV/HEM_LowerRenz.ino +++ b/software/o_c_REV/HEM_LowerRenz.ino @@ -62,7 +62,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawEditor(); DrawOutput(); } diff --git a/software/o_c_REV/HEM_Metronome.ino b/software/o_c_REV/HEM_Metronome.ino index c2fac1a80..1c5c2fc06 100644 --- a/software/o_c_REV/HEM_Metronome.ino +++ b/software/o_c_REV/HEM_Metronome.ino @@ -42,7 +42,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_MixerBal.ino b/software/o_c_REV/HEM_MixerBal.ino index 71f6698cd..c6335ea0e 100644 --- a/software/o_c_REV/HEM_MixerBal.ino +++ b/software/o_c_REV/HEM_MixerBal.ino @@ -46,7 +46,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawBalanceIndicator(); gfxSkyline(); } diff --git a/software/o_c_REV/HEM_Palimpsest.ino b/software/o_c_REV/HEM_Palimpsest.ino index a327dd13c..dbcb65cd9 100644 --- a/software/o_c_REV/HEM_Palimpsest.ino +++ b/software/o_c_REV/HEM_Palimpsest.ino @@ -80,7 +80,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawControls(); DrawSequence(); } diff --git a/software/o_c_REV/HEM_Pigeons.ino b/software/o_c_REV/HEM_Pigeons.ino index 6c68d1c53..ef6a89373 100644 --- a/software/o_c_REV/HEM_Pigeons.ino +++ b/software/o_c_REV/HEM_Pigeons.ino @@ -59,7 +59,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_ProbabilityDivider.ino b/software/o_c_REV/HEM_ProbabilityDivider.ino index 424e7b890..f0c50686d 100644 --- a/software/o_c_REV/HEM_ProbabilityDivider.ino +++ b/software/o_c_REV/HEM_ProbabilityDivider.ino @@ -132,7 +132,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index 328efbb88..08693fbb9 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -97,7 +97,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawParams(); DrawKeyboard(); } diff --git a/software/o_c_REV/HEM_ResetClock.ino b/software/o_c_REV/HEM_ResetClock.ino index d8c88dae2..796f186ef 100644 --- a/software/o_c_REV/HEM_ResetClock.ino +++ b/software/o_c_REV/HEM_ResetClock.ino @@ -66,7 +66,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_RndWalk.ino b/software/o_c_REV/HEM_RndWalk.ino index fb62e73bf..7dd941202 100644 --- a/software/o_c_REV/HEM_RndWalk.ino +++ b/software/o_c_REV/HEM_RndWalk.ino @@ -93,7 +93,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawDisplay(); } diff --git a/software/o_c_REV/HEM_RunglBook.ino b/software/o_c_REV/HEM_RunglBook.ino index 2925cc427..ebcd087db 100644 --- a/software/o_c_REV/HEM_RunglBook.ino +++ b/software/o_c_REV/HEM_RunglBook.ino @@ -48,7 +48,6 @@ public: } void View() { - gfxHeader(applet_name()); gfxPrint(1, 15, "Thr:"); gfxPrintVoltage(threshold); gfxSkyline(); diff --git a/software/o_c_REV/HEM_ScaleDuet.ino b/software/o_c_REV/HEM_ScaleDuet.ino index a414ecbe5..65e3bda18 100644 --- a/software/o_c_REV/HEM_ScaleDuet.ino +++ b/software/o_c_REV/HEM_ScaleDuet.ino @@ -59,7 +59,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawKeyboard(); DrawMaskIndicators(); } diff --git a/software/o_c_REV/HEM_Schmitt.ino b/software/o_c_REV/HEM_Schmitt.ino index 769fa32fa..8e217b8d4 100644 --- a/software/o_c_REV/HEM_Schmitt.ino +++ b/software/o_c_REV/HEM_Schmitt.ino @@ -46,7 +46,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Scope.ino b/software/o_c_REV/HEM_Scope.ino index b4692dd8d..b32651310 100644 --- a/software/o_c_REV/HEM_Scope.ino +++ b/software/o_c_REV/HEM_Scope.ino @@ -74,8 +74,6 @@ public: } void View() { - gfxHeader(applet_name()); - if(current_display == 4) { DrawInput(-1); } else { diff --git a/software/o_c_REV/HEM_SequenceX.ino b/software/o_c_REV/HEM_SequenceX.ino index e28369ae1..528d01bad 100644 --- a/software/o_c_REV/HEM_SequenceX.ino +++ b/software/o_c_REV/HEM_SequenceX.ino @@ -75,7 +75,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawPanel(); } diff --git a/software/o_c_REV/HEM_ShiftGate.ino b/software/o_c_REV/HEM_ShiftGate.ino index 2cfd9199a..243de9867 100644 --- a/software/o_c_REV/HEM_ShiftGate.ino +++ b/software/o_c_REV/HEM_ShiftGate.ino @@ -42,7 +42,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Shredder.ino b/software/o_c_REV/HEM_Shredder.ino index 3d8f05fbf..e0fd89027 100644 --- a/software/o_c_REV/HEM_Shredder.ino +++ b/software/o_c_REV/HEM_Shredder.ino @@ -96,7 +96,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawParams(); DrawMeters(); DrawGrid(); diff --git a/software/o_c_REV/HEM_Shuffle.ino b/software/o_c_REV/HEM_Shuffle.ino index b45923b42..5331dc7f7 100644 --- a/software/o_c_REV/HEM_Shuffle.ino +++ b/software/o_c_REV/HEM_Shuffle.ino @@ -87,7 +87,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_Slew.ino b/software/o_c_REV/HEM_Slew.ino index 0f5afaf56..1bdabe8b5 100644 --- a/software/o_c_REV/HEM_Slew.ino +++ b/software/o_c_REV/HEM_Slew.ino @@ -65,7 +65,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_Squanch.ino b/software/o_c_REV/HEM_Squanch.ino index 86039754a..08cb279e8 100644 --- a/software/o_c_REV/HEM_Squanch.ino +++ b/software/o_c_REV/HEM_Squanch.ino @@ -68,7 +68,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Stairs.ino b/software/o_c_REV/HEM_Stairs.ino index 24f0d43d1..d9f5ebe95 100644 --- a/software/o_c_REV/HEM_Stairs.ino +++ b/software/o_c_REV/HEM_Stairs.ino @@ -198,7 +198,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawDisplay(); } diff --git a/software/o_c_REV/HEM_Switch.ino b/software/o_c_REV/HEM_Switch.ino index 2d772a4ed..488cf6fb9 100644 --- a/software/o_c_REV/HEM_Switch.ino +++ b/software/o_c_REV/HEM_Switch.ino @@ -49,7 +49,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawCaptions(); DrawIndicator(); gfxSkyline(); diff --git a/software/o_c_REV/HEM_TB3PO.ino b/software/o_c_REV/HEM_TB3PO.ino index ce2d7aaeb..68e4cfe07 100644 --- a/software/o_c_REV/HEM_TB3PO.ino +++ b/software/o_c_REV/HEM_TB3PO.ino @@ -171,7 +171,6 @@ class TB_3PO: public HemisphereApplet { } void View() { - gfxHeader(applet_name()); DrawGraphics(); } diff --git a/software/o_c_REV/HEM_TLNeuron.ino b/software/o_c_REV/HEM_TLNeuron.ino index 22f0f96b7..826165da8 100644 --- a/software/o_c_REV/HEM_TLNeuron.ino +++ b/software/o_c_REV/HEM_TLNeuron.ino @@ -67,7 +67,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawDendrites(); DrawAxon(); DrawStates(); diff --git a/software/o_c_REV/HEM_TM.ino.disabled b/software/o_c_REV/HEM_TM.ino.disabled index 2dc7ee50b..175c14c3c 100644 --- a/software/o_c_REV/HEM_TM.ino.disabled +++ b/software/o_c_REV/HEM_TM.ino.disabled @@ -105,7 +105,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 1e42f5d7b..26143ce62 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -215,7 +215,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_Trending.ino b/software/o_c_REV/HEM_Trending.ino index e26d6b780..c0f04b3ea 100644 --- a/software/o_c_REV/HEM_Trending.ino +++ b/software/o_c_REV/HEM_Trending.ino @@ -86,7 +86,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawSelector(); DrawIndicator(); } diff --git a/software/o_c_REV/HEM_TrigSeq.ino b/software/o_c_REV/HEM_TrigSeq.ino index 29557b6da..b40265ff9 100644 --- a/software/o_c_REV/HEM_TrigSeq.ino +++ b/software/o_c_REV/HEM_TrigSeq.ino @@ -54,7 +54,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawDisplay(); } diff --git a/software/o_c_REV/HEM_TrigSeq16.ino b/software/o_c_REV/HEM_TrigSeq16.ino index be7923538..1b02e113b 100644 --- a/software/o_c_REV/HEM_TrigSeq16.ino +++ b/software/o_c_REV/HEM_TrigSeq16.ino @@ -54,7 +54,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawDisplay(); } diff --git a/software/o_c_REV/HEM_Tuner.ino b/software/o_c_REV/HEM_Tuner.ino index d7744fc76..4c8d56904 100644 --- a/software/o_c_REV/HEM_Tuner.ino +++ b/software/o_c_REV/HEM_Tuner.ino @@ -70,7 +70,6 @@ public: } void View() { - gfxHeader(applet_name()); #ifdef FLIP_180 if (hemisphere == 0) DrawTuner(); #else diff --git a/software/o_c_REV/HEM_VectorEG.ino b/software/o_c_REV/HEM_VectorEG.ino index 21b97a7b3..6f87562d8 100644 --- a/software/o_c_REV/HEM_VectorEG.ino +++ b/software/o_c_REV/HEM_VectorEG.ino @@ -57,7 +57,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_VectorLFO.ino b/software/o_c_REV/HEM_VectorLFO.ino index b455d83ad..1653b1d82 100644 --- a/software/o_c_REV/HEM_VectorLFO.ino +++ b/software/o_c_REV/HEM_VectorLFO.ino @@ -81,7 +81,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_VectorMod.ino b/software/o_c_REV/HEM_VectorMod.ino index 6ed3d082e..2fed21854 100644 --- a/software/o_c_REV/HEM_VectorMod.ino +++ b/software/o_c_REV/HEM_VectorMod.ino @@ -50,7 +50,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_VectorMorph.ino b/software/o_c_REV/HEM_VectorMorph.ino index 1485e033b..638ae310f 100644 --- a/software/o_c_REV/HEM_VectorMorph.ino +++ b/software/o_c_REV/HEM_VectorMorph.ino @@ -54,7 +54,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_Voltage.ino b/software/o_c_REV/HEM_Voltage.ino index 69e4389fe..1d56116b4 100644 --- a/software/o_c_REV/HEM_Voltage.ino +++ b/software/o_c_REV/HEM_Voltage.ino @@ -51,7 +51,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawInterface(); } diff --git a/software/o_c_REV/HEM_hMIDIIn.ino b/software/o_c_REV/HEM_hMIDIIn.ino index a8d4b6c59..5ce472b80 100644 --- a/software/o_c_REV/HEM_hMIDIIn.ino +++ b/software/o_c_REV/HEM_hMIDIIn.ino @@ -75,7 +75,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawMonitor(); if (cursor == LOG_VIEW) DrawLog(); else DrawSelector(); diff --git a/software/o_c_REV/HEM_hMIDIOut.ino b/software/o_c_REV/HEM_hMIDIOut.ino index 87ec58d48..a10e76e47 100644 --- a/software/o_c_REV/HEM_hMIDIOut.ino +++ b/software/o_c_REV/HEM_hMIDIOut.ino @@ -128,7 +128,6 @@ public: } void View() { - gfxHeader(applet_name()); DrawMonitor(); if (cursor == 4) DrawLog(); else DrawSelector(); diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 62c3c21bb..05642ffbc 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -119,6 +119,7 @@ static IOFrame frame; int octave_max = 5; +int select_mode = -1; uint8_t modal_edit_mode = 2; // 0=old behavior, 1=modal editing, 2=modal with wraparound static void CycleEditMode() { ++modal_edit_mode %= 3; } @@ -172,6 +173,8 @@ class HemisphereApplet { } void BaseView() { + //if (HS::select_mode == hemisphere) + gfxHeader(applet_name()); // If help is active, draw the help screen instead of the application screen if (help_active) DrawHelpScreen(); else View(); @@ -196,7 +199,6 @@ class HemisphereApplet { } void DrawHelpScreen() { - gfxHeader(applet_name()); SetHelp(); for (int section = 0; section < 4; section++) From fa7a4284ec5869e57930c714a4ecfe704dad9caa Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 7 Nov 2023 18:26:27 -0500 Subject: [PATCH 347/417] Trigger input remapping (replaces Clock Forwarding) Configured and stored in Clock Setup modal_edit_mode setting is saved in a different place now.... --- software/o_c_REV/APP_HEMISPHERE.ino | 10 --- software/o_c_REV/HEM_ClockSetup.ino | 135 ++++++++++++++++------------ software/o_c_REV/HSClockManager.h | 9 -- software/o_c_REV/HSIOFrame.h | 1 + software/o_c_REV/HemisphereApplet.h | 4 +- 5 files changed, 82 insertions(+), 77 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 2a3e937b0..f635d7268 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -260,11 +260,6 @@ public: // top-level MIDI-to-CV handling - alters frame outputs ProcessMIDI(); - // XXX: kind of a crutch, should be replaced with general Trigger input mapping - if (clock_m->IsForwarded()) { - HS::frame.clocked[2] = HS::frame.clocked[0]; - } - // Clock Setup applet handles internal clock duties HS::clock_setup_applet.Controller(LEFT_HEMISPHERE, 0); @@ -329,11 +324,6 @@ public: gfxIcon(56, 1, PAUSE_ICON); } - if (clock_m->IsForwarded()) { - // CV Forwarding Icon - gfxIcon(120, 1, CLOCK_ICON); - } - if (select_mode == LEFT_HEMISPHERE) graphics.drawFrame(0, 0, 64, 64); if (select_mode == RIGHT_HEMISPHERE) graphics.drawFrame(64, 0, 64, 64); } diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index b2a65fd94..c12d5e1bc 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -23,9 +23,8 @@ public: enum ClockSetupCursor { PLAY_STOP, - FORWARDING, - EXT_PPQN, TEMPO, + EXT_PPQN, MULT1, MULT2, MULT3, @@ -34,7 +33,11 @@ public: TRIG2, TRIG3, TRIG4, - LAST_SETTING = TRIG4 + BOOP1, + BOOP2, + BOOP3, + BOOP4, + LAST_SETTING = BOOP4 }; const char* applet_name() { @@ -93,9 +96,8 @@ public: void OnButtonPress() { if (!EditMode()) { // special cases for toggle buttons if (cursor == PLAY_STOP) PlayStop(); - else if (cursor == FORWARDING) clock_m->ToggleForwarding(); - else if (cursor >= TRIG1) { - clock_m->Boop(cursor-TRIG1); + else if (cursor >= BOOP1) { + clock_m->Boop(cursor-BOOP1); button_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; } else CursorAction(cursor, LAST_SETTING); @@ -133,15 +135,18 @@ public: PlayStop(); break; - case FORWARDING: - clock_m->ToggleForwarding(); - break; - case TRIG1: case TRIG2: case TRIG3: case TRIG4: - clock_m->Boop(cursor-TRIG1); + HS::trigger_mapping[cursor-TRIG1] = constrain( HS::trigger_mapping[cursor-TRIG1] + direction, 0, 4); + break; + + case BOOP1: + case BOOP2: + case BOOP3: + case BOOP4: + clock_m->Boop(cursor-BOOP1); button_ticker = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; break; @@ -165,37 +170,43 @@ public: uint64_t OnDataRequest() { uint64_t data = 0; - Pack(data, PackLocation { 0, 1 }, clock_m->IsRunning() || clock_m->IsPaused()); - Pack(data, PackLocation { 1, 1 }, clock_m->IsForwarded()); + // first 2 bits are reserved Pack(data, PackLocation { 2, 9 }, clock_m->GetTempo()); Pack(data, PackLocation { 11, 5 }, clock_m->GetClockPPQN()); for (size_t i = 0; i < 4; ++i) { Pack(data, PackLocation { 16+i*6, 6 }, clock_m->GetMultiply(i)+32); + Pack(data, PackLocation { 40+i*3, 3 }, HS::trigger_mapping[i] + 1); } - // other config settings are kept here as well, it's convenient - Pack(data, PackLocation { 50, 2 }, HS::modal_edit_mode); Pack(data, PackLocation { 52, 7 }, HS::trig_length); + // --- PackLocation { 59, 1 } --- + Pack(data, PackLocation { 60, 2 }, HS::modal_edit_mode); + // --- PackLocation { 62, 2 } --- return data; } void OnDataReceive(uint64_t data) { - /* leave Play state unchanged - Start/Stop will always be manual - if (Unpack(data, PackLocation { 0, 1 })) { - clock_m->Start(1); // start paused - } else { - clock_m->Stop(); - } - */ - clock_m->SetForwarding(Unpack(data, PackLocation { 1, 1 })); + // bit 0 - reserved + // bit 1 - backward compatibility with Clock Forwarding + if (Unpack(data, PackLocation { 1, 1 })) HS::trigger_mapping[2] = 1; + clock_m->SetTempoBPM(Unpack(data, PackLocation { 2, 9 })); clock_m->SetClockPPQN(Unpack(data, PackLocation { 11, 5 })); for (size_t i = 0; i < 4; ++i) { clock_m->SetMultiply(Unpack(data, PackLocation { 16+i*6, 6 })-32, i); } - HS::modal_edit_mode = Unpack(data, PackLocation { 50, 2 }); + for (size_t i = 0; i < 4; ++i) { + uint8_t t = Unpack(data, PackLocation { 40+i*3, 3 }); + if (t) HS::trigger_mapping[i] = t - 1; + else { + // backward compatibility + HS::modal_edit_mode = Unpack(data, PackLocation { 50, 2 }); + break; + } + } + HS::trig_length = constrain( Unpack(data, PackLocation { 52, 7 }), 1, 127); } @@ -234,73 +245,84 @@ private: // Header: This is sort of a faux applet, so its header // needs to extend across the screen graphics.setPrintPos(1, 2); - graphics.print("Clock Setup"); + graphics.print("Clocks/Triggers"); //gfxLine(0, 10, 62, 10); //gfxLine(0, 12, 62, 12); graphics.drawLine(0, 10, 127, 10); - graphics.drawLine(0, 12, 127, 12); + //graphics.drawLine(0, 12, 127, 12); - // Clock Source - gfxIcon(1, 15, CLOCK_ICON); + int y = 14; + // Clock State + gfxIcon(1, y, CLOCK_ICON); if (clock_m->IsRunning()) { - gfxIcon(12, 15, PLAY_ICON); + gfxIcon(12, y, PLAY_ICON); } else if (clock_m->IsPaused()) { - gfxIcon(12, 15, PAUSE_ICON); + gfxIcon(12, y, PAUSE_ICON); } else { - gfxIcon(12, 15, STOP_ICON); + gfxIcon(12, y, STOP_ICON); } - gfxPrint(26, 15, "Fwd "); - gfxIcon(50, 15, clock_m->IsForwarded() ? CHECK_ON_ICON : CHECK_OFF_ICON); - - // Input PPQN - gfxPrint(64, 15, "PPQN x"); - gfxPrint(clock_m->GetClockPPQN()); // Tempo - gfxIcon(1, 26, NOTE4_ICON); - gfxPrint(9, 26, "= "); - gfxPrint(pad(100, clock_m->GetTempo()), clock_m->GetTempo()); + gfxPrint(22 + pad(100, clock_m->GetTempo()), y, clock_m->GetTempo()); gfxPrint(" BPM"); + // Input PPQN + gfxPrint(73, y, "Sync=x"); + gfxPrint(clock_m->GetClockPPQN()); + + y += 10; for (int ch=0; ch<4; ++ch) { - int mult = clock_m->GetMultiply(ch); - int x = ch * 32; + const int x = ch * 32; // Multipliers - gfxPrint(1 + x, 37, (mult >= 0) ? "x" : "/"); - gfxPrint( (mult >= 0) ? mult : 1 - mult ); + int mult = clock_m->GetMultiply(ch); + if (0 != mult || cursor == MULT1 + ch) { // hide if 0 + gfxPrint(1 + x, y, (mult >= 0) ? "x" : "/"); + gfxPrint( (mult >= 0) ? mult : 1 - mult ); + } + + // Physical trigger input mappings + gfxPrint(1 + x, y + 11, OC::Strings::trigger_input_names_none[ HS::trigger_mapping[ch] ] ); + + // Manual trigger buttons + gfxIcon(4 + x, 47, (button_ticker && ch == cursor-BOOP1)?BTN_ON_ICON:BTN_OFF_ICON); - // Manual triggers - gfxIcon(4 + x, 47, (button_ticker && ch == cursor-TRIG1)?BTN_ON_ICON:BTN_OFF_ICON); + // Trigger indicators gfxIcon(4 + x, 54, DOWN_BTN_ICON); if (flash_ticker[ch]) gfxInvert(3 + x, 56, 9, 8); } switch ((ClockSetupCursor)cursor) { - case PLAY_STOP: gfxFrame(11, 14, 10, 10); break; - case FORWARDING: gfxFrame(49, 14, 10, 10); break; - - case EXT_PPQN: - gfxCursor(100,23, 12); + case PLAY_STOP: + gfxFrame(11, 13, 10, 10); break; - case TEMPO: - gfxCursor(22, 34, 18); + gfxCursor(22, 22, 19); + break; + case EXT_PPQN: + gfxCursor(109,22, 13); break; case MULT1: case MULT2: case MULT3: case MULT4: - gfxCursor(8 + 32*(cursor-MULT1), 45, 12); + gfxCursor(8 + 32*(cursor-MULT1), 32, 12); break; case TRIG1: case TRIG2: case TRIG3: case TRIG4: + gfxCursor(1 + 32*(cursor-TRIG1), 43, 19); + break; + + case BOOP1: + case BOOP2: + case BOOP3: + case BOOP4: if (0 == button_ticker) - gfxIcon(12 + 32*(cursor-TRIG1), 49, LEFT_ICON); + gfxIcon(12 + 32*(cursor-BOOP1), 49, LEFT_ICON); break; default: break; @@ -322,7 +344,8 @@ ClockSetup ClockSetup_instance[1]; void ClockSetup_Start(bool hemisphere) {ClockSetup_instance[hemisphere].BaseStart(hemisphere);} // NJM: don't call BaseController for ClockSetup void ClockSetup_Controller(bool hemisphere, bool forwarding) {ClockSetup_instance[hemisphere].Controller();} -void ClockSetup_View(bool hemisphere) {ClockSetup_instance[hemisphere].BaseView();} +// don't call BaseView either... this isn't a real applet, is it? +void ClockSetup_View(bool hemisphere) {ClockSetup_instance[hemisphere].View();} void ClockSetup_OnButtonPress(bool hemisphere) {ClockSetup_instance[hemisphere].OnButtonPress();} void ClockSetup_OnEncoderMove(bool hemisphere, int direction) {ClockSetup_instance[hemisphere].OnEncoderMove(direction);} void ClockSetup_ToggleHelpScreen(bool hemisphere) {ClockSetup_instance[hemisphere].HelpScreen();} diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 642837ac2..ea3b5b49d 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -51,7 +51,6 @@ class ClockManager { uint32_t ticks_per_beat; // Based on the selected tempo in BPM bool running = 0; // Specifies whether the clock is running for interprocess communication bool paused = 0; // Specifies whethr the clock is paused - bool forwarded = 0; // Master clock forwarding is enabled when true bool midi_out_enabled = 1; uint32_t clock_tick = 0; // tick when a physical clock was received on DIGITAL 1 @@ -222,18 +221,10 @@ class ClockManager { void Pause() {paused = 1;} - void ToggleForwarding() { - forwarded = 1 - forwarded; - } - - void SetForwarding(bool f) {forwarded = f;} - bool IsRunning() {return (running && !paused);} bool IsPaused() {return paused;} - bool IsForwarded() {return forwarded;} - // beep boop void Boop(int ch = 0) { boop[ch] = true; diff --git a/software/o_c_REV/HSIOFrame.h b/software/o_c_REV/HSIOFrame.h index 51cd25895..c62466158 100644 --- a/software/o_c_REV/HSIOFrame.h +++ b/software/o_c_REV/HSIOFrame.h @@ -14,6 +14,7 @@ int quant_scale[4]; int root_note[4]; uint8_t trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS +int trigger_mapping[] = { 1, 2, 3, 4 }; typedef struct MIDILogEntry { int message; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 05642ffbc..f387c2f7a 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -419,8 +419,8 @@ class HemisphereApplet { // clock triggers if (useTock && clock_m->GetMultiply(ch + io_offset) != 0) clocked = clock_m->Tock(ch + io_offset); - else - clocked = frame.clocked[ch + io_offset]; + else if (trigger_mapping[ch + io_offset]) + clocked = frame.clocked[ trigger_mapping[ch + io_offset] - 1 ]; // Try to eat a boop clocked = clocked || clock_m->Beep(io_offset + ch); From 5d9a08a0e1ac98e0c9eaf4fd74cc548685074e81 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 11 Nov 2023 03:47:21 -0500 Subject: [PATCH 348/417] Gate input follows Trigger input mapping --- software/o_c_REV/HemisphereApplet.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index f387c2f7a..f66f48518 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -437,7 +437,8 @@ class HemisphereApplet { } bool Gate(int ch) { - return frame.gate_high[ch + io_offset]; + const int t = trigger_mapping[ch + io_offset]; + return t ? frame.gate_high[t - 1] : false; } void GateOut(int ch, bool high) { From 0ed221ea09da690764ab8d85f375b87214fa08ac Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 8 Nov 2023 20:55:48 -0500 Subject: [PATCH 349/417] better quantizer initialization in Hemisphere --- software/o_c_REV/APP_HEMISPHERE.ino | 4 +++- software/o_c_REV/HEM_TM2.ino | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index f635d7268..02572826c 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -174,7 +174,9 @@ public: clock_setup = 0; for (int i = 0; i < 4; ++i) { - HS::quantizer[i].Init(); + quant_scale[i] = OC::Scales::SCALE_SEMI; + quantizer[i].Init(); + quantizer[i].Configure(OC::Scales::GetScale(quant_scale[i]), 0xffff); } SetApplet(0, get_applet_index_by_id(18)); // DualTM diff --git a/software/o_c_REV/HEM_TM2.ino b/software/o_c_REV/HEM_TM2.ino index 26143ce62..d6834dc31 100644 --- a/software/o_c_REV/HEM_TM2.ino +++ b/software/o_c_REV/HEM_TM2.ino @@ -274,7 +274,7 @@ public: Pack(data, PackLocation {12,5}, range - 1); Pack(data, PackLocation {17,4}, outmode[0]); Pack(data, PackLocation {21,4}, outmode[1]); - Pack(data, PackLocation {25,8}, constrain(scale, 0, 255)); + Pack(data, PackLocation {25,8}, constrain(GetScale(0), 0, 255)); Pack(data, PackLocation {33,4}, cvmode[0]); Pack(data, PackLocation {37,4}, cvmode[1]); Pack(data, PackLocation {41,6}, smoothing); @@ -291,7 +291,7 @@ public: range = Unpack(data, PackLocation{12,5}) + 1; outmode[0] = (OutputMode) Unpack(data, PackLocation {17,4}); outmode[1] = (OutputMode) Unpack(data, PackLocation {21,4}); - scale = Unpack(data, PackLocation {25,8}); + int scale = Unpack(data, PackLocation {25,8}); cvmode[0] = (InputMode) Unpack(data, PackLocation {33,4}); cvmode[1] = (InputMode) Unpack(data, PackLocation {37,4}); smoothing = Unpack(data, PackLocation {41,6}); @@ -315,7 +315,6 @@ protected: private: int cursor; // TM2Cursor - int scale = OC::Scales::SCALE_SEMI; // Scale used for quantized output int root_note = 0; // TODO: consider using the TuringMachine class or whatev From 69a96dd15b312214405bb84100fdfe7818a331a6 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 8 Nov 2023 22:38:00 -0500 Subject: [PATCH 350/417] Decouple Autotuner from REFS; add to Calibr8or --- software/o_c_REV/APP_CALIBR8OR.ino | 39 ++- software/o_c_REV/APP_REFS.ino | 452 +-------------------------- software/o_c_REV/OC_autotuner.h | 472 +++++++++++++++++++++++++++-- 3 files changed, 502 insertions(+), 461 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index f3c870421..bcf7f50ea 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -31,6 +31,7 @@ #include "braids_quantizer.h" #include "braids_quantizer_scales.h" #include "OC_scales.h" +#include "OC_autotuner.h" #include "SegmentDisplay.h" #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" #include "HemisphereApplet.h" @@ -53,6 +54,9 @@ struct Cal8ChannelConfig { int16_t scale_factor; // precision of 0.01% as an offset from 100% int8_t transpose; // in semitones int8_t transpose_active; // held value while waiting for trigger + + DAC_CHANNEL chan_; + DAC_CHANNEL get_channel() { return chan_; } }; // Preset storage spec @@ -158,6 +162,14 @@ Calibr8orPreset cal8_presets[NR_OF_PRESETS]; class Calibr8or : public HSApplication { public: + Calibr8or() { + for (int i = DAC_CHANNEL_A; i < DAC_CHANNEL_LAST; ++i) { + channel[i].chan_ = DAC_CHANNEL(i); + } + } + + OC::Autotuner autotuner; + void Start() { segment.Init(SegmentSize::BIG_SEGMENTS); @@ -167,6 +179,8 @@ public: OC::DigitalInputs::reInit(); ClearPreset(); + + autotuner.Init(); } void ClearPreset() { @@ -230,6 +244,10 @@ public: } void Controller() { + if (autotuner.active()) { + autotuner.ISR(); + return; + } ProcessMIDI(); // ClockSetup applet handles internal clock duties @@ -270,6 +288,11 @@ public: return; } + if (autotuner.active()) { + autotuner.Draw(); + return; + } + gfxHeader("Calibr8or"); // Metronome icon @@ -309,6 +332,11 @@ public: void OnLeftButtonLongPress() { if (preset_select) return; + if (edit_mode) { + autotuner.Open(&channel[sel_chan]); + return; + } + // Toggle triggered transpose mode ++channel[sel_chan].clocked_mode %= NR_OF_CLOCKMODES; preset_modified = 1; @@ -440,7 +468,6 @@ public: } } -private: int index = 0; int sel_chan = 0; @@ -624,6 +651,11 @@ void Calibr8or_screensaver() { } void Calibr8or_handleButtonEvent(const UI::Event &event) { + if (Calibr8or_instance.autotuner.active()) { + Calibr8or_instance.autotuner.HandleButtonEvent(event); + return; + } + // For left encoder, handle press and long press // For right encoder, only handle press (long press is reserved) // For up button, handle only press (long press is reserved) @@ -662,6 +694,11 @@ void Calibr8or_handleButtonEvent(const UI::Event &event) { } void Calibr8or_handleEncoderEvent(const UI::Event &event) { + if (Calibr8or_instance.autotuner.active()) { + Calibr8or_instance.autotuner.HandleButtonEvent(event); + return; + } + // Left encoder turned if (event.control == OC::CONTROL_ENCODER_L) Calibr8or_instance.OnLeftEncoderMove(event.value); diff --git a/software/o_c_REV/APP_REFS.ino b/software/o_c_REV/APP_REFS.ino index 11e6fb155..4b64beb6e 100644 --- a/software/o_c_REV/APP_REFS.ino +++ b/software/o_c_REV/APP_REFS.ino @@ -32,17 +32,6 @@ #include "OC_autotuner.h" #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" -// autotune constants: -#ifdef VOR -static constexpr int ACTIVE_OCTAVES = OCTAVES; -#else -static constexpr int ACTIVE_OCTAVES = OCTAVES - 1; -#endif - -#define FREQ_MEASURE_TIMEOUT 512 -#define ERROR_TIMEOUT (FREQ_MEASURE_TIMEOUT << 0x4) -#define MAX_NUM_PASSES 1500 -#define CONVERGE_PASSES 5 // static constexpr double kAaboveMidCtoC0 = 0.03716272234383494188492; @@ -53,42 +42,6 @@ const uint8_t DAC_CHANNEL_FTM = DAC_CHANNEL_A; const uint8_t DAC_CHANNEL_FTM = DAC_CHANNEL_D; #endif -// -#ifdef BUCHLA_4U - constexpr float target_multipliers[OCTAVES] = { 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f }; -#else - constexpr float target_multipliers[OCTAVES + 6] = { 0.03125f, 0.0625f, 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f, 1024.0f }; -// constexpr float target_multipliers[OCTAVES] = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f }; -#endif - -#ifdef BUCHLA_SUPPORT - constexpr float target_multipliers_1V2[OCTAVES] = { - 0.1767766952966368931843f, - 0.3149802624737182976666f, - 0.5612310241546865086093f, - 1.0f, - 1.7817974362806785482150f, - 3.1748021039363991668836f, - 5.6568542494923805818985f, - 10.0793683991589855253324f, - 17.9593927729499718282113f, - 32.0f - }; - - constexpr float target_multipliers_2V0[OCTAVES] = { - 0.3535533905932737863687f, - 0.5f, - 0.7071067811865475727373f, - 1.0f, - 1.4142135623730951454746f, - 2.0f, - 2.8284271247461902909492f, - 4.0f, - 5.6568542494923805818985f, - 8.0f - }; -#endif - const uint8_t NUM_REF_CHANNELS = DAC_CHANNEL_LAST; enum ReferenceSetting { @@ -122,40 +75,21 @@ enum ChannelPpqn { class ReferenceChannel : public settings::SettingsBase { public: - static constexpr size_t kHistoryDepth = 10; - void Init(DAC_CHANNEL dac_channel) { InitDefaults(); rate_phase_ = 0; mod_offset_ = 0; last_pitch_ = 0; - autotuner_ = false; - autotuner_step_ = OC::DAC_VOLT_0_ARM; dac_channel_ = dac_channel; - auto_DAC_offset_error_ = 0; - auto_frequency_ = 0; - auto_last_frequency_ = 0; - auto_freq_sum_ = 0; - auto_freq_count_ = 0; - auto_ready_ = 0; - ticks_since_last_freq_ = 0; - auto_next_step_ = false; - autotune_completed_ = false; - F_correction_factor_ = 0xFF; - correction_direction_ = false; - correction_cnt_positive_ = 0x0; - correction_cnt_negative_ = 0x0; - reset_calibration_data(); update_enabled_settings(); - history_[0].Init(0x0); } int get_octave() const { return values_[REF_SETTING_OCTAVE]; } - int get_channel() const { + DAC_CHANNEL get_channel() const { return dac_channel_; } @@ -188,343 +122,8 @@ public: return static_cast(values_[REF_SETTING_PPQN]); } - uint8_t autotuner_active() { - return (autotuner_ && autotuner_step_) ? (dac_channel_ + 0x1) : 0x0; - } - - bool autotuner_completed() { - return autotune_completed_; - } - - void autotuner_reset_completed() { - autotune_completed_ = false; - } - - bool autotuner_error() { - return auto_error_; - } - - uint8_t get_octave_cnt() { - return octaves_cnt_ + 0x1; - } - - uint8_t auto_tune_step() { - return autotuner_step_; - } - - void autotuner_arm(uint8_t _status) { - reset_autotuner(); - autotuner_ = _status ? true : false; - } - - void autotuner_run() { - SERIAL_PRINTLN("Starting autotuner..."); - autotuner_step_ = autotuner_ ? OC::DAC_VOLT_0_BASELINE : OC::DAC_VOLT_0_ARM; - if (autotuner_step_ == OC::DAC_VOLT_0_BASELINE) - // we start, so reset data to defaults: - OC::DAC::set_default_channel_calibration_data(dac_channel_); - } - - void auto_reset_step() { - SERIAL_PRINTLN("Autotuner reset step..."); - auto_num_passes_ = 0x0; - auto_DAC_offset_error_ = 0x0; - correction_direction_ = false; - correction_cnt_positive_ = correction_cnt_negative_ = 0x0; - F_correction_factor_ = 0xFF; - auto_ready_ = false; - } - - void reset_autotuner() { - ticks_since_last_freq_ = 0x0; - auto_frequency_ = 0x0; - auto_last_frequency_ = 0x0; - auto_error_ = 0x0; - auto_ready_ = 0x0; - autotuner_ = 0x0; - autotuner_step_ = 0x0; - F_correction_factor_ = 0xFF; - correction_direction_ = false; - correction_cnt_positive_ = 0x0; - correction_cnt_negative_ = 0x0; - octaves_cnt_ = 0x0; - auto_num_passes_ = 0x0; - auto_DAC_offset_error_ = 0x0; - autotune_completed_ = 0x0; - reset_calibration_data(); - } - - const uint32_t get_auto_frequency() { - return auto_frequency_; - } - - uint8_t _ready() { - return auto_ready_; - } - - void reset_calibration_data() { - - for (int i = 0; i < OCTAVES + 1; i++) { - auto_calibration_data_[i] = 0; - auto_target_frequencies_[i] = 0.0f; - } - } - - uint8_t data_available() { - return OC::DAC::calibration_data_used(dac_channel_); - } - - void use_default() { - OC::DAC::set_default_channel_calibration_data(dac_channel_); - } - - void use_auto_calibration() { - OC::DAC::set_auto_channel_calibration_data(dac_channel_); - } - - bool auto_frequency() { - - bool _f_result = false; - - if (ticks_since_last_freq_ > ERROR_TIMEOUT) { - auto_error_ = true; - } - - if (FreqMeasure.available()) { - - auto_freq_sum_ = auto_freq_sum_ + FreqMeasure.read(); - auto_freq_count_ = auto_freq_count_ + 1; - - // take more time as we're converging toward the target frequency - uint32_t _wait = (F_correction_factor_ == 0x1) ? (FREQ_MEASURE_TIMEOUT << 2) : (FREQ_MEASURE_TIMEOUT >> 2); - - if (ticks_since_last_freq_ > _wait) { - - // store frequency, reset, and poke ui to preempt screensaver: - auto_frequency_ = uint32_t(FreqMeasure.countToFrequency(auto_freq_sum_ / auto_freq_count_) * 1000); - history_[0].Push(auto_frequency_); - auto_freq_sum_ = 0; - auto_ready_ = true; - auto_freq_count_ = 0; - _f_result = true; - ticks_since_last_freq_ = 0x0; - OC::ui._Poke(); - for (auto &sh : history_) - sh.Update(); - } - } - return _f_result; - } - - void measure_frequency_and_calc_error() { - - switch(autotuner_step_) { - - case OC::DAC_VOLT_0_ARM: - // do nothing - break; - case OC::DAC_VOLT_0_BASELINE: - // 0V baseline / calibration point: in this case, we don't correct. - { - bool _update = auto_frequency(); - if (_update && auto_num_passes_ > kHistoryDepth) { - - auto_last_frequency_ = auto_frequency_; - uint32_t history[kHistoryDepth]; - uint32_t average = 0; - // average - history_->Read(history); - for (uint8_t i = 0; i < kHistoryDepth; i++) - average += history[i]; - // ... and derive target frequency at 0V - auto_frequency_ = ((auto_frequency_ + average) / (kHistoryDepth + 1)); // 0V - SERIAL_PRINTLN("Baseline auto_frequency_ = %4.d", auto_frequency_); - // reset step, and proceed: - auto_reset_step(); - autotuner_step_++; - } - else if (_update) - auto_num_passes_++; - } - break; - case OC::DAC_VOLT_TARGET_FREQUENCIES: - { - #ifdef BUCHLA_SUPPORT - - switch(OC::DAC::get_voltage_scaling(dac_channel_)) { - - case VOLTAGE_SCALING_1_2V_PER_OCT: // 1.2V/octave - auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers_1V2[octaves_cnt_]; - break; - case VOLTAGE_SCALING_2V_PER_OCT: // 2V/octave - auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers_2V0[octaves_cnt_]; - break; - default: // 1V/octave - auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_]; - break; - } - #else - auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_ + (5 - OC::DAC::kOctaveZero)]; - #endif - octaves_cnt_++; - // go to next step, if done: - if (octaves_cnt_ > ACTIVE_OCTAVES) { - octaves_cnt_ = 0x0; - autotuner_step_++; - } - } - break; - case OC::DAC_VOLT_3m: - case OC::DAC_VOLT_2m: - case OC::DAC_VOLT_1m: - case OC::DAC_VOLT_0: - case OC::DAC_VOLT_1: - case OC::DAC_VOLT_2: - case OC::DAC_VOLT_3: - case OC::DAC_VOLT_4: - case OC::DAC_VOLT_5: - case OC::DAC_VOLT_6: - #ifdef VOR - case OC::DAC_VOLT_7: - #endif - { - bool _update = auto_frequency(); - - if (_update && (auto_num_passes_ > MAX_NUM_PASSES)) { - /* target frequency reached */ - SERIAL_PRINTLN("* Target Frequency Reached *"); - - if ((autotuner_step_ > OC::DAC_VOLT_2m) && (auto_last_frequency_ * 1.25f > auto_frequency_)) - auto_error_ = true; // throw error, if things don't seem to double ... - - // average: - uint32_t history[kHistoryDepth]; - uint32_t average = 0; - history_->Read(history); - for (uint8_t i = 0; i < kHistoryDepth; i++) - average += history[i]; - - // store last frequency: - auto_last_frequency_ = (auto_frequency_ + average) / (kHistoryDepth + 1); - // and DAC correction value: - auto_calibration_data_[autotuner_step_ - OC::DAC_VOLT_3m] = auto_DAC_offset_error_; - - // reset and step forward - auto_reset_step(); - autotuner_step_++; - } - else if (_update) - { - auto_num_passes_++; // count passes - - SERIAL_PRINTLN("auto_target_frequencies[%3d]_ = %3d", autotuner_step_ - OC::DAC_VOLT_3m, - auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] ); - SERIAL_PRINTLN("auto_frequency_ = %3d", auto_frequency_); - - // and correct frequency - if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] > auto_frequency_) - { - // update correction factor? - if (!correction_direction_) - F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; - - correction_direction_ = true; - - auto_DAC_offset_error_ += F_correction_factor_; - - // we're converging -- count passes, so we can stop after x attempts: - if (F_correction_factor_ == 0x1) correction_cnt_positive_++; - } - else if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] < auto_frequency_) - { - // update correction factor? - if (correction_direction_) - F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; - - correction_direction_ = false; - - auto_DAC_offset_error_ -= F_correction_factor_; - - // we're converging -- count passes, so we can stop after x attempts: - if (F_correction_factor_ == 0x1) correction_cnt_negative_++; - } - - SERIAL_PRINTLN("auto_DAC_offset_error_ = %3d", auto_DAC_offset_error_); - - // approaching target? if so, go to next step. - if (correction_cnt_positive_ > CONVERGE_PASSES && correction_cnt_negative_ > CONVERGE_PASSES) - auto_num_passes_ = MAX_NUM_PASSES << 1; - } - } - break; - case OC::AUTO_CALIBRATION_STEP_LAST: - // step through the octaves: - if (ticks_since_last_freq_ > 2000) { - int32_t new_auto_calibration_point = OC::calibration_data.dac.calibrated_octaves[dac_channel_][octaves_cnt_] + auto_calibration_data_[octaves_cnt_]; - // write to DAC and update data - if (new_auto_calibration_point >= 65536 || new_auto_calibration_point < 0) { - auto_error_ = true; - autotuner_step_++; - } - else { - OC::DAC::set(dac_channel_, new_auto_calibration_point); - OC::DAC::update_auto_channel_calibration_data(dac_channel_, octaves_cnt_, new_auto_calibration_point); - ticks_since_last_freq_ = 0x0; - octaves_cnt_++; - } - } - // then stop ... - if (octaves_cnt_ > OCTAVES) { - autotune_completed_ = true; - // and point to auto data ... - OC::DAC::set_auto_channel_calibration_data(dac_channel_); - autotuner_step_++; - } - break; - default: - autotuner_step_ = OC::DAC_VOLT_0_ARM; - autotuner_ = 0x0; - break; - } - } - - void autotune_updateDAC() { - - switch(autotuner_step_) { - - case OC::DAC_VOLT_0_ARM: - { - F_correction_factor_ = 0x1; // don't go so fast - auto_frequency(); - OC::DAC::set(dac_channel_, OC::calibration_data.dac.calibrated_octaves[dac_channel_][OC::DAC::kOctaveZero]); - } - break; - case OC::DAC_VOLT_0_BASELINE: - // set DAC to 0.000V, default calibration: - OC::DAC::set(dac_channel_, OC::calibration_data.dac.calibrated_octaves[dac_channel_][OC::DAC::kOctaveZero]); - break; - case OC::DAC_VOLT_TARGET_FREQUENCIES: - case OC::AUTO_CALIBRATION_STEP_LAST: - // do nothing - break; - default: - // set DAC to calibration point + error - { - int32_t _default_calibration_point = OC::calibration_data.dac.calibrated_octaves[dac_channel_][autotuner_step_ - OC::DAC_VOLT_3m]; - OC::DAC::set(dac_channel_, _default_calibration_point + auto_DAC_offset_error_); - } - break; - } - } - void Update() { - if (autotuner_) { - autotune_updateDAC(); - ticks_since_last_freq_++; - return; - } - int octave = get_octave(); int range = get_range(); if (range) { @@ -581,30 +180,8 @@ private: uint32_t rate_phase_; int mod_offset_; int32_t last_pitch_; - bool autotuner_; - uint8_t autotuner_step_; - int32_t auto_DAC_offset_error_; - uint32_t auto_frequency_; - uint32_t auto_target_frequencies_[OCTAVES + 1]; - int16_t auto_calibration_data_[OCTAVES + 1]; - uint32_t auto_last_frequency_; - bool auto_next_step_; - bool auto_error_; - bool auto_ready_; - bool autotune_completed_; - uint32_t auto_freq_sum_; - uint32_t auto_freq_count_; - uint32_t ticks_since_last_freq_; - uint32_t auto_num_passes_; - uint16_t F_correction_factor_; - bool correction_direction_; - int16_t correction_cnt_positive_; - int16_t correction_cnt_negative_; - int16_t octaves_cnt_; DAC_CHANNEL dac_channel_; - OC::vfx::ScrollingHistory history_[0x1]; - int num_enabled_settings_; ReferenceSetting enabled_settings_[REF_SETTING_LAST]; }; @@ -663,18 +240,15 @@ public: void ISR() { - for (auto &channel : channels_) - channel.Update(); + if (autotuner.active()) { + autotuner.ISR(); + return; + } - uint8_t _autotuner_active_channel = 0x0; for (auto &channel : channels_) - _autotuner_active_channel += channel.autotuner_active(); + channel.Update(); - if (_autotuner_active_channel) { - channels_[_autotuner_active_channel - 0x1].measure_frequency_and_calc_error(); - return; - } - else if (FreqMeasure.available()) { + if (FreqMeasure.available()) { // average several readings together freq_sum_ = freq_sum_ + FreqMeasure.read(); freq_count_ = freq_count_ + 1; @@ -807,8 +381,7 @@ void REFS_handleAppEvent(OC::AppEvent event) { case OC::APP_EVENT_SUSPEND: case OC::APP_EVENT_SCREENSAVER_ON: case OC::APP_EVENT_SCREENSAVER_OFF: - for (size_t i = 0; i < NUM_REF_CHANNELS; ++i) - references_app.channels_[i].reset_autotuner(); + references_app.autotuner.Reset(); break; } } @@ -817,6 +390,12 @@ void REFS_loop() { } void REFS_menu() { + // autotuner ... + if (references_app.autotuner.active()) { + references_app.autotuner.Draw(); + return; + } + menu::QuadTitleBar::Draw(); for (uint_fast8_t i = 0; i < NUM_REF_CHANNELS; ++i) { menu::QuadTitleBar::SetColumn(i); @@ -844,9 +423,6 @@ void REFS_menu() { break; } } - // autotuner ... - if (references_app.autotuner.active()) - references_app.autotuner.Draw(); } void print_voltage(int octave, int fraction) { diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index cce14bdc4..40bbac12f 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -3,6 +3,21 @@ #include "OC_autotune.h" #include "OC_options.h" +#include "OC_visualfx.h" + +// autotune constants: +#ifdef VOR +static constexpr int ACTIVE_OCTAVES = OCTAVES; +#else +static constexpr int ACTIVE_OCTAVES = OCTAVES - 1; +#endif + +static constexpr size_t kHistoryDepth = 10; + +#define FREQ_MEASURE_TIMEOUT 512 +#define ERROR_TIMEOUT (FREQ_MEASURE_TIMEOUT << 0x4) +#define MAX_NUM_PASSES 1500 +#define CONVERGE_PASSES 5 #if defined(BUCHLA_4U) && !defined(IO_10V) const char* const AT_steps[] = { @@ -22,6 +37,42 @@ const char* const AT_steps[] = { }; #endif +// +#ifdef BUCHLA_4U + constexpr float target_multipliers[OCTAVES] = { 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f }; +#else + constexpr float target_multipliers[OCTAVES + 6] = { 0.03125f, 0.0625f, 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f, 128.0f, 256.0f, 512.0f, 1024.0f }; +// constexpr float target_multipliers[OCTAVES] = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f, 32.0f, 64.0f }; +#endif + +#ifdef BUCHLA_SUPPORT + constexpr float target_multipliers_1V2[OCTAVES] = { + 0.1767766952966368931843f, + 0.3149802624737182976666f, + 0.5612310241546865086093f, + 1.0f, + 1.7817974362806785482150f, + 3.1748021039363991668836f, + 5.6568542494923805818985f, + 10.0793683991589855253324f, + 17.9593927729499718282113f, + 32.0f + }; + + constexpr float target_multipliers_2V0[OCTAVES] = { + 0.3535533905932737863687f, + 0.5f, + 0.7071067811865475727373f, + 1.0f, + 1.4142135623730951454746f, + 2.0f, + 2.8284271247461902909492f, + 4.0f, + 5.6568542494923805818985f, + 8.0f + }; +#endif + namespace OC { enum AUTO_MENU_ITEMS { @@ -69,9 +120,29 @@ class Autotuner { owner_ = nullptr; cursor_pos_ = 0; data_select_ = 0; - channel_ = 0; + channel_ = DAC_CHANNEL_A; calibration_data_ = 0; auto_tune_running_status_ = 0; + + // moved from REFS + armed_ = false; + autotuner_step_ = OC::DAC_VOLT_0_ARM; + auto_DAC_offset_error_ = 0; + auto_frequency_ = 0; + auto_last_frequency_ = 0; + auto_freq_sum_ = 0; + auto_freq_count_ = 0; + auto_ready_ = 0; + ticks_since_last_freq_ = 0; + auto_next_step_ = false; + autotune_completed_ = false; + F_correction_factor_ = 0xFF; + correction_direction_ = false; + correction_cnt_positive_ = 0x0; + correction_cnt_negative_ = 0x0; + reset_calibration_data(); + + history_[0].Init(0x0); } bool active() const { @@ -84,6 +155,11 @@ class Autotuner { Begin(); } + void Reset() { + reset_autotuner(); + } + + void ISR(); void Close(); void Draw(); void HandleButtonEvent(const UI::Event &event); @@ -94,18 +170,370 @@ class Autotuner { Owner *owner_; size_t cursor_pos_; size_t data_select_; - int8_t channel_; + DAC_CHANNEL channel_; uint8_t calibration_data_; uint8_t auto_tune_running_status_; + // moved from References + OC::vfx::ScrollingHistory history_[0x1]; + + bool armed_; + uint8_t autotuner_step_; + int32_t auto_DAC_offset_error_; + uint32_t auto_frequency_; + uint32_t auto_target_frequencies_[OCTAVES + 1]; + int16_t auto_calibration_data_[OCTAVES + 1]; + uint32_t auto_last_frequency_; + bool auto_next_step_; + bool auto_error_; + bool auto_ready_; + bool autotune_completed_; + uint32_t auto_freq_sum_; + uint32_t auto_freq_count_; + uint32_t ticks_since_last_freq_; + uint32_t auto_num_passes_; + uint16_t F_correction_factor_; + bool correction_direction_; + int16_t correction_cnt_positive_; + int16_t correction_cnt_negative_; + int16_t octaves_cnt_; + // ----- + void Begin(); void move_cursor(int offset); void change_value(int offset); void handleButtonLeft(const UI::Event &event); void handleButtonUp(const UI::Event &event); void handleButtonDown(const UI::Event &event); + + + void autotuner_reset_completed() { + autotune_completed_ = false; + } + + inline uint8_t get_octave_cnt() { + return octaves_cnt_ + 0x1; + } + + inline uint8_t auto_tune_step() { + return autotuner_step_; + } + + void autotuner_arm(uint8_t _status) { + reset_autotuner(); + armed_ = _status ? true : false; + } + + void autotuner_run() { + SERIAL_PRINTLN("Starting autotuner..."); + autotuner_step_ = armed_ ? OC::DAC_VOLT_0_BASELINE : OC::DAC_VOLT_0_ARM; + if (autotuner_step_ == OC::DAC_VOLT_0_BASELINE) + // we start, so reset data to defaults: + OC::DAC::set_default_channel_calibration_data(channel_); + } + + void auto_reset_step() { + SERIAL_PRINTLN("Autotuner reset step..."); + auto_num_passes_ = 0x0; + auto_DAC_offset_error_ = 0x0; + correction_direction_ = false; + correction_cnt_positive_ = correction_cnt_negative_ = 0x0; + F_correction_factor_ = 0xFF; + auto_ready_ = false; + } + + void reset_autotuner() { + ticks_since_last_freq_ = 0x0; + auto_frequency_ = 0x0; + auto_last_frequency_ = 0x0; + auto_error_ = 0x0; + auto_ready_ = 0x0; + armed_ = 0x0; + autotuner_step_ = 0x0; + F_correction_factor_ = 0xFF; + correction_direction_ = false; + correction_cnt_positive_ = 0x0; + correction_cnt_negative_ = 0x0; + octaves_cnt_ = 0x0; + auto_num_passes_ = 0x0; + auto_DAC_offset_error_ = 0x0; + autotune_completed_ = 0x0; + reset_calibration_data(); + } + + const uint32_t get_auto_frequency() { + return auto_frequency_; + } + + uint8_t _ready() { + return auto_ready_; + } + + void reset_calibration_data() { + + for (int i = 0; i < OCTAVES + 1; i++) { + auto_calibration_data_[i] = 0; + auto_target_frequencies_[i] = 0.0f; + } + } + + uint8_t data_available() { + return OC::DAC::calibration_data_used(channel_); + } + + void use_default() { + OC::DAC::set_default_channel_calibration_data(channel_); + } + + void use_auto_calibration() { + OC::DAC::set_auto_channel_calibration_data(channel_); + } + + bool auto_frequency() { + + bool _f_result = false; + + if (ticks_since_last_freq_ > ERROR_TIMEOUT) { + auto_error_ = true; + } + + if (FreqMeasure.available()) { + + auto_freq_sum_ = auto_freq_sum_ + FreqMeasure.read(); + auto_freq_count_ = auto_freq_count_ + 1; + + // take more time as we're converging toward the target frequency + uint32_t _wait = (F_correction_factor_ == 0x1) ? (FREQ_MEASURE_TIMEOUT << 2) : (FREQ_MEASURE_TIMEOUT >> 2); + + if (ticks_since_last_freq_ > _wait) { + + // store frequency, reset, and poke ui to preempt screensaver: + auto_frequency_ = uint32_t(FreqMeasure.countToFrequency(auto_freq_sum_ / auto_freq_count_) * 1000); + history_[0].Push(auto_frequency_); + auto_freq_sum_ = 0; + auto_ready_ = true; + auto_freq_count_ = 0; + _f_result = true; + ticks_since_last_freq_ = 0x0; + OC::ui._Poke(); + for (auto &sh : history_) + sh.Update(); + } + } + return _f_result; + } + + void measure_frequency_and_calc_error() { + + switch(autotuner_step_) { + + case OC::DAC_VOLT_0_ARM: + // do nothing + break; + case OC::DAC_VOLT_0_BASELINE: + // 0V baseline / calibration point: in this case, we don't correct. + { + bool _update = auto_frequency(); + if (_update && auto_num_passes_ > kHistoryDepth) { + + auto_last_frequency_ = auto_frequency_; + uint32_t history[kHistoryDepth]; + uint32_t average = 0; + // average + history_->Read(history); + for (uint8_t i = 0; i < kHistoryDepth; i++) + average += history[i]; + // ... and derive target frequency at 0V + auto_frequency_ = ((auto_frequency_ + average) / (kHistoryDepth + 1)); // 0V + SERIAL_PRINTLN("Baseline auto_frequency_ = %4.d", auto_frequency_); + // reset step, and proceed: + auto_reset_step(); + autotuner_step_++; + } + else if (_update) + auto_num_passes_++; + } + break; + case OC::DAC_VOLT_TARGET_FREQUENCIES: + { + #ifdef BUCHLA_SUPPORT + + switch(OC::DAC::get_voltage_scaling(channel_)) { + + case VOLTAGE_SCALING_1_2V_PER_OCT: // 1.2V/octave + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers_1V2[octaves_cnt_]; + break; + case VOLTAGE_SCALING_2V_PER_OCT: // 2V/octave + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers_2V0[octaves_cnt_]; + break; + default: // 1V/octave + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_]; + break; + } + #else + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_ + (5 - OC::DAC::kOctaveZero)]; + #endif + octaves_cnt_++; + // go to next step, if done: + if (octaves_cnt_ > ACTIVE_OCTAVES) { + octaves_cnt_ = 0x0; + autotuner_step_++; + } + } + break; + case OC::DAC_VOLT_3m: + case OC::DAC_VOLT_2m: + case OC::DAC_VOLT_1m: + case OC::DAC_VOLT_0: + case OC::DAC_VOLT_1: + case OC::DAC_VOLT_2: + case OC::DAC_VOLT_3: + case OC::DAC_VOLT_4: + case OC::DAC_VOLT_5: + case OC::DAC_VOLT_6: + #ifdef VOR + case OC::DAC_VOLT_7: + #endif + { + bool _update = auto_frequency(); + + if (_update && (auto_num_passes_ > MAX_NUM_PASSES)) { + /* target frequency reached */ + SERIAL_PRINTLN("* Target Frequency Reached *"); + + if ((autotuner_step_ > OC::DAC_VOLT_2m) && (auto_last_frequency_ * 1.25f > auto_frequency_)) + auto_error_ = true; // throw error, if things don't seem to double ... + + // average: + uint32_t history[kHistoryDepth]; + uint32_t average = 0; + history_->Read(history); + for (uint8_t i = 0; i < kHistoryDepth; i++) + average += history[i]; + + // store last frequency: + auto_last_frequency_ = (auto_frequency_ + average) / (kHistoryDepth + 1); + // and DAC correction value: + auto_calibration_data_[autotuner_step_ - OC::DAC_VOLT_3m] = auto_DAC_offset_error_; + + // reset and step forward + auto_reset_step(); + autotuner_step_++; + } + else if (_update) + { + auto_num_passes_++; // count passes + + SERIAL_PRINTLN("auto_target_frequencies[%3d]_ = %3d", autotuner_step_ - OC::DAC_VOLT_3m, + auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] ); + SERIAL_PRINTLN("auto_frequency_ = %3d", auto_frequency_); + + // and correct frequency + if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] > auto_frequency_) + { + // update correction factor? + if (!correction_direction_) + F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; + + correction_direction_ = true; + + auto_DAC_offset_error_ += F_correction_factor_; + + // we're converging -- count passes, so we can stop after x attempts: + if (F_correction_factor_ == 0x1) correction_cnt_positive_++; + } + else if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] < auto_frequency_) + { + // update correction factor? + if (correction_direction_) + F_correction_factor_ = (F_correction_factor_ >> 1) | 1u; + + correction_direction_ = false; + + auto_DAC_offset_error_ -= F_correction_factor_; + + // we're converging -- count passes, so we can stop after x attempts: + if (F_correction_factor_ == 0x1) correction_cnt_negative_++; + } + + SERIAL_PRINTLN("auto_DAC_offset_error_ = %3d", auto_DAC_offset_error_); + + // approaching target? if so, go to next step. + if (correction_cnt_positive_ > CONVERGE_PASSES && correction_cnt_negative_ > CONVERGE_PASSES) + auto_num_passes_ = MAX_NUM_PASSES << 1; + } + } + break; + case OC::AUTO_CALIBRATION_STEP_LAST: + // step through the octaves: + if (ticks_since_last_freq_ > 2000) { + int32_t new_auto_calibration_point = OC::calibration_data.dac.calibrated_octaves[channel_][octaves_cnt_] + auto_calibration_data_[octaves_cnt_]; + // write to DAC and update data + if (new_auto_calibration_point >= 65536 || new_auto_calibration_point < 0) { + auto_error_ = true; + autotuner_step_++; + } + else { + OC::DAC::set(channel_, new_auto_calibration_point); + OC::DAC::update_auto_channel_calibration_data(channel_, octaves_cnt_, new_auto_calibration_point); + ticks_since_last_freq_ = 0x0; + octaves_cnt_++; + } + } + // then stop ... + if (octaves_cnt_ > OCTAVES) { + autotune_completed_ = true; + // and point to auto data ... + OC::DAC::set_auto_channel_calibration_data(channel_); + autotuner_step_++; + } + break; + default: + autotuner_step_ = OC::DAC_VOLT_0_ARM; + armed_ = 0x0; + break; + } + } + + void autotune_updateDAC() { + + switch(autotuner_step_) { + + case OC::DAC_VOLT_0_ARM: + { + F_correction_factor_ = 0x1; // don't go so fast + auto_frequency(); + OC::DAC::set(channel_, OC::calibration_data.dac.calibrated_octaves[channel_][OC::DAC::kOctaveZero]); + } + break; + case OC::DAC_VOLT_0_BASELINE: + // set DAC to 0.000V, default calibration: + OC::DAC::set(channel_, OC::calibration_data.dac.calibrated_octaves[channel_][OC::DAC::kOctaveZero]); + break; + case OC::DAC_VOLT_TARGET_FREQUENCIES: + case OC::AUTO_CALIBRATION_STEP_LAST: + // do nothing + break; + default: + // set DAC to calibration point + error + { + int32_t _default_calibration_point = OC::calibration_data.dac.calibrated_octaves[channel_][autotuner_step_ - OC::DAC_VOLT_3m]; + OC::DAC::set(channel_, _default_calibration_point + auto_DAC_offset_error_); + } + break; + } + } }; + template + void Autotuner::ISR() { + measure_frequency_and_calc_error(); + + // moved from ReferenceChannel::Update() + autotune_updateDAC(); + ticks_since_last_freq_++; + } + template void Autotuner::Draw() { @@ -121,13 +549,13 @@ class Autotuner { x = 16; y = 15; - if (owner_->autotuner_error()) { + if (auto_error_) { auto_tune_running_status_ = AT_ERROR; - owner_->reset_autotuner(); + reset_autotuner(); } - else if (owner_->autotuner_completed()) { + else if (autotune_completed_) { auto_tune_running_status_ = AT_DONE; - owner_->autotuner_reset_completed(); + autotuner_reset_completed(); } for (size_t i = 0; i < (AUTO_MENU_ITEMS_LAST - 0x1); ++i, y += 20) { @@ -159,7 +587,7 @@ class Autotuner { break; case AT_READY: { graphics.print("arm > "); - const uint32_t _freq = owner_->get_auto_frequency(); + const uint32_t _freq = get_auto_frequency(); if (_freq == 0) graphics.printf("wait ..."); else @@ -172,20 +600,20 @@ class Autotuner { break; case AT_RUN: { - int _octave = owner_->get_octave_cnt(); + int _octave = get_octave_cnt(); if (_octave > 1 && _octave < OCTAVES) { for (int i = 0; i <= _octave; i++, x += 6) graphics.drawBitmap8(x + 18, y + 4, 4, OC::bitmap_indicator_4x8); } - else if (owner_->auto_tune_step() == DAC_VOLT_0_BASELINE || owner_->auto_tune_step() == DAC_VOLT_TARGET_FREQUENCIES) // this goes too quick, so ... + else if (auto_tune_step() == DAC_VOLT_0_BASELINE || auto_tune_step() == DAC_VOLT_TARGET_FREQUENCIES) // this goes too quick, so ... graphics.print(" 0.0V baseline"); else { - graphics.print(AT_steps[owner_->auto_tune_step() - DAC_VOLT_3m]); - if (!owner_->_ready()) + graphics.print(AT_steps[auto_tune_step() - DAC_VOLT_3m]); + if (!_ready()) graphics.print(" "); else { - const uint32_t f = owner_->get_auto_frequency(); + const uint32_t f = get_auto_frequency(); const uint32_t value = f / 1000; const uint32_t cents = f % 1000; graphics.printf(" > %5u.%03u", value, cents); @@ -200,7 +628,7 @@ class Autotuner { case AT_DONE: graphics.print(OC::Strings::channel_id[channel_]); graphics.print(" --> a-ok!"); - calibration_data_ = owner_->data_available(); + calibration_data_ = data_available(); break; default: break; @@ -233,7 +661,7 @@ class Autotuner { handleButtonLeft(event); break; case OC::CONTROL_BUTTON_R: - owner_->reset_autotuner(); + reset_autotuner(); Close(); break; default: @@ -292,7 +720,7 @@ class Autotuner { switch (cursor_pos_) { case DATA_SELECT: { - uint8_t data = owner_->data_available(); + uint8_t data = data_available(); if (!data) { // no data -- calibration_data_ = 0x0; data_select_ = 0x0; @@ -303,11 +731,11 @@ class Autotuner { data_select_ = _data_sel; if (_data_sel == 0x0) { calibration_data_ = 0xFF; - owner_->use_default(); + use_default(); } else { calibration_data_ = 0x01; - owner_->use_auto_calibration(); + use_auto_calibration(); } } } @@ -318,7 +746,7 @@ class Autotuner { int _status = auto_tune_running_status_ + offset; CONSTRAIN(_status, 0, AT_READY); auto_tune_running_status_ = _status; - owner_->autotuner_arm(_status); + autotuner_arm(_status); } else if (auto_tune_running_status_ == AT_ERROR || auto_tune_running_status_ == AT_DONE) auto_tune_running_status_ = 0x0; @@ -335,10 +763,10 @@ class Autotuner { if (cursor_pos_ == AUTOTUNE && auto_tune_running_status_ == AT_OFF) { // arm the tuner auto_tune_running_status_ = AT_READY; - owner_->autotuner_arm(auto_tune_running_status_); + autotuner_arm(auto_tune_running_status_); } else { - owner_->reset_autotuner(); + reset_autotuner(); auto_tune_running_status_ = AT_OFF; } } @@ -347,11 +775,11 @@ class Autotuner { void Autotuner::handleButtonDown(const UI::Event &event) { if (cursor_pos_ == AUTOTUNE && auto_tune_running_status_ == AT_READY) { - owner_->autotuner_run(); + autotuner_run(); auto_tune_running_status_ = AT_RUN; } else if (auto_tune_running_status_ == AT_ERROR) { - owner_->reset_autotuner(); + reset_autotuner(); auto_tune_running_status_ = AT_OFF; } } From 10677a774dfdc71d9bb7524d07ad5b94fcc70c7b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 8 Nov 2023 23:22:32 -0500 Subject: [PATCH 351/417] Calibr8or/Autotuner fixes --- software/o_c_REV/APP_CALIBR8OR.ino | 18 ++++++++++------ software/o_c_REV/APP_REFS.ino | 2 ++ software/o_c_REV/OC_autotuner.h | 34 +++++++++++++++--------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index bcf7f50ea..34b852e28 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -57,6 +57,9 @@ struct Cal8ChannelConfig { DAC_CHANNEL chan_; DAC_CHANNEL get_channel() { return chan_; } + void ExitAutotune() { + FreqMeasure.end(); + } }; // Preset storage spec @@ -244,10 +247,6 @@ public: } void Controller() { - if (autotuner.active()) { - autotuner.ISR(); - return; - } ProcessMIDI(); // ClockSetup applet handles internal clock duties @@ -333,6 +332,7 @@ public: if (preset_select) return; if (edit_mode) { + FreqMeasure.begin(); autotuner.Open(&channel[sel_chan]); return; } @@ -625,7 +625,13 @@ size_t Calibr8or_restore(const void *storage) { return used; } -void Calibr8or_isr() { return Calibr8or_instance.BaseController(); } +void Calibr8or_isr() { + if (Calibr8or_instance.autotuner.active()) { + Calibr8or_instance.autotuner.ISR(); + return; + } + Calibr8or_instance.BaseController(); +} void Calibr8or_handleAppEvent(OC::AppEvent event) { switch (event) { @@ -695,7 +701,7 @@ void Calibr8or_handleButtonEvent(const UI::Event &event) { void Calibr8or_handleEncoderEvent(const UI::Event &event) { if (Calibr8or_instance.autotuner.active()) { - Calibr8or_instance.autotuner.HandleButtonEvent(event); + Calibr8or_instance.autotuner.HandleEncoderEvent(event); return; } diff --git a/software/o_c_REV/APP_REFS.ino b/software/o_c_REV/APP_REFS.ino index 4b64beb6e..81f63bc09 100644 --- a/software/o_c_REV/APP_REFS.ino +++ b/software/o_c_REV/APP_REFS.ino @@ -93,6 +93,8 @@ public: return dac_channel_; } + void ExitAutotune() { } + int32_t get_semitone() const { return values_[REF_SETTING_SEMI]; } diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index 40bbac12f..82f674f1a 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -122,7 +122,7 @@ class Autotuner { data_select_ = 0; channel_ = DAC_CHANNEL_A; calibration_data_ = 0; - auto_tune_running_status_ = 0; + auto_tune_running_status_ = AT_OFF; // moved from REFS armed_ = false; @@ -142,7 +142,7 @@ class Autotuner { correction_cnt_negative_ = 0x0; reset_calibration_data(); - history_[0].Init(0x0); + history_.Init(0x0); } bool active() const { @@ -172,10 +172,10 @@ class Autotuner { size_t data_select_; DAC_CHANNEL channel_; uint8_t calibration_data_; - uint8_t auto_tune_running_status_; + AT_STATUS auto_tune_running_status_; // moved from References - OC::vfx::ScrollingHistory history_[0x1]; + OC::vfx::ScrollingHistory history_; bool armed_; uint8_t autotuner_step_; @@ -309,15 +309,14 @@ class Autotuner { // store frequency, reset, and poke ui to preempt screensaver: auto_frequency_ = uint32_t(FreqMeasure.countToFrequency(auto_freq_sum_ / auto_freq_count_) * 1000); - history_[0].Push(auto_frequency_); + history_.Push(auto_frequency_); auto_freq_sum_ = 0; auto_ready_ = true; auto_freq_count_ = 0; _f_result = true; ticks_since_last_freq_ = 0x0; OC::ui._Poke(); - for (auto &sh : history_) - sh.Update(); + history_.Update(); } } return _f_result; @@ -340,7 +339,7 @@ class Autotuner { uint32_t history[kHistoryDepth]; uint32_t average = 0; // average - history_->Read(history); + history_.Read(history); for (uint8_t i = 0; i < kHistoryDepth; i++) average += history[i]; // ... and derive target frequency at 0V @@ -407,7 +406,7 @@ class Autotuner { // average: uint32_t history[kHistoryDepth]; uint32_t average = 0; - history_->Read(history); + history_.Read(history); for (uint8_t i = 0; i < kHistoryDepth; i++) average += history[i]; @@ -527,11 +526,11 @@ class Autotuner { template void Autotuner::ISR() { - measure_frequency_and_calc_error(); - - // moved from ReferenceChannel::Update() - autotune_updateDAC(); - ticks_since_last_freq_++; + if (armed_) { + autotune_updateDAC(); + measure_frequency_and_calc_error(); + ticks_since_last_freq_++; + } } template @@ -745,11 +744,11 @@ class Autotuner { if (auto_tune_running_status_ < AT_RUN) { int _status = auto_tune_running_status_ + offset; CONSTRAIN(_status, 0, AT_READY); - auto_tune_running_status_ = _status; + auto_tune_running_status_ = AT_STATUS(_status); autotuner_arm(_status); } else if (auto_tune_running_status_ == AT_ERROR || auto_tune_running_status_ == AT_DONE) - auto_tune_running_status_ = 0x0; + auto_tune_running_status_ = AT_OFF; } break; default: @@ -799,12 +798,13 @@ class Autotuner { else data_select_ = 0x00; cursor_pos_ = 0x0; - auto_tune_running_status_ = 0x0; + auto_tune_running_status_ = AT_OFF; } template void Autotuner::Close() { ui.SetButtonIgnoreMask(); + owner_->ExitAutotune(); owner_ = nullptr; } }; // namespace OC From 6f1b027288fc50dff8de0c9dcfd302047ceefe2b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 9 Nov 2023 20:46:40 -0500 Subject: [PATCH 352/417] Autotuner: wait for user after Baseline 0V step --- software/o_c_REV/OC_autotuner.h | 48 ++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index 82f674f1a..532b64407 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -85,6 +85,7 @@ enum AUTO_MENU_ITEMS { enum AT_STATUS { AT_OFF, AT_READY, + AT_WAIT, AT_RUN, AT_ERROR, AT_DONE, @@ -95,6 +96,7 @@ enum AUTO_CALIBRATION_STEP { DAC_VOLT_0_ARM, DAC_VOLT_0_BASELINE, DAC_VOLT_TARGET_FREQUENCIES, + DAC_VOLT_WAIT, DAC_VOLT_3m, DAC_VOLT_2m, DAC_VOLT_1m, @@ -327,6 +329,7 @@ class Autotuner { switch(autotuner_step_) { case OC::DAC_VOLT_0_ARM: + case OC::DAC_VOLT_WAIT: // do nothing break; case OC::DAC_VOLT_0_BASELINE: @@ -348,6 +351,8 @@ class Autotuner { // reset step, and proceed: auto_reset_step(); autotuner_step_++; + // wait for user to patch output to oscillator V/Oct + auto_tune_running_status_ = AT_WAIT; } else if (_update) auto_num_passes_++; @@ -510,6 +515,7 @@ class Autotuner { OC::DAC::set(channel_, OC::calibration_data.dac.calibrated_octaves[channel_][OC::DAC::kOctaveZero]); break; case OC::DAC_VOLT_TARGET_FREQUENCIES: + case OC::DAC_VOLT_WAIT: case OC::AUTO_CALIBRATION_STEP_LAST: // do nothing break; @@ -531,6 +537,16 @@ class Autotuner { measure_frequency_and_calc_error(); ticks_since_last_freq_++; } + + if (auto_error_) { + auto_tune_running_status_ = AT_ERROR; + reset_autotuner(); + } + else if (autotune_completed_) { + auto_tune_running_status_ = AT_DONE; + autotuner_reset_completed(); + } + } template @@ -548,15 +564,6 @@ class Autotuner { x = 16; y = 15; - if (auto_error_) { - auto_tune_running_status_ = AT_ERROR; - reset_autotuner(); - } - else if (autotune_completed_) { - auto_tune_running_status_ = AT_DONE; - autotuner_reset_completed(); - } - for (size_t i = 0; i < (AUTO_MENU_ITEMS_LAST - 0x1); ++i, y += 20) { // graphics.setPrintPos(x + 2, y + 4); @@ -597,6 +604,10 @@ class Autotuner { } } break; + case AT_WAIT: + graphics.print("Patch V/Oct"); + graphics.print(" now!"); + break; case AT_RUN: { int _octave = get_octave_cnt(); @@ -772,15 +783,22 @@ class Autotuner { template void Autotuner::handleButtonDown(const UI::Event &event) { - - if (cursor_pos_ == AUTOTUNE && auto_tune_running_status_ == AT_READY) { - autotuner_run(); - auto_tune_running_status_ = AT_RUN; - } - else if (auto_tune_running_status_ == AT_ERROR) { + if (auto_tune_running_status_ == AT_ERROR) { reset_autotuner(); auto_tune_running_status_ = AT_OFF; + return; } + + if (cursor_pos_ == AUTOTUNE) { + if (auto_tune_running_status_ == AT_READY) { + autotuner_run(); + auto_tune_running_status_ = AT_RUN; + } + else if (auto_tune_running_status_ == AT_WAIT) { + auto_tune_running_status_ = AT_RUN; + autotuner_step_++; + } + } } template From c1cb32da5bc046d7649590882de20b323993be6f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 9 Nov 2023 22:51:47 -0500 Subject: [PATCH 353/417] Autotuner: fixes/refactoring; succeed when ceiling reached --- software/o_c_REV/OC_autotuner.h | 200 ++++++++++++++------------------ 1 file changed, 88 insertions(+), 112 deletions(-) diff --git a/software/o_c_REV/OC_autotuner.h b/software/o_c_REV/OC_autotuner.h index 532b64407..abe2d87d4 100644 --- a/software/o_c_REV/OC_autotuner.h +++ b/software/o_c_REV/OC_autotuner.h @@ -13,7 +13,9 @@ static constexpr int ACTIVE_OCTAVES = OCTAVES - 1; #endif static constexpr size_t kHistoryDepth = 10; - + +#define ZERO_OFFSET (5 - OC::DAC::kOctaveZero) + #define FREQ_MEASURE_TIMEOUT 512 #define ERROR_TIMEOUT (FREQ_MEASURE_TIMEOUT << 0x4) #define MAX_NUM_PASSES 1500 @@ -21,19 +23,12 @@ static constexpr size_t kHistoryDepth = 10; #if defined(BUCHLA_4U) && !defined(IO_10V) const char* const AT_steps[] = { + " ", " ", " ", " ", " ", // 5 blanks "0.0V", "1.2V", "2.4V", "3.6V", "4.8V", "6.0V", "7.2V", "8.4V", "9.6V", "10.8V", " " }; -#elif defined(IO_10V) -const char* const AT_steps[] = { - "0.0V", "1.0V", "2.0V", "3.0V", "4.0V", "5.0V", "6.0V", "7.0V", "8.0V", "9.0V", " " -}; -#elif defined(VOR) -const char* const AT_steps[] = { - "0.0V", "1.0V", "2.0V", "3.0V", "4.0V", "5.0V", "6.0V", "7.0V", "8.0V", "9.0V", "10.0V", " " -}; #else const char* const AT_steps[] = { - "-3V", "-2V", "-1V", " 0V", "+1V", "+2V", "+3V", "+4V", "+5V", "+6V", " " + "-5V", "-4V", "-3V", "-2V", "-1V", " 0V", "+1V", "+2V", "+3V", "+4V", "+5V", "+6V", "+7V", "+8V", "+9V", "+10V", " " }; #endif @@ -124,20 +119,19 @@ class Autotuner { data_select_ = 0; channel_ = DAC_CHANNEL_A; calibration_data_ = 0; - auto_tune_running_status_ = AT_OFF; + run_status_ = AT_OFF; // moved from REFS armed_ = false; - autotuner_step_ = OC::DAC_VOLT_0_ARM; + step_ = OC::DAC_VOLT_0_ARM; auto_DAC_offset_error_ = 0; auto_frequency_ = 0; auto_last_frequency_ = 0; auto_freq_sum_ = 0; auto_freq_count_ = 0; - auto_ready_ = 0; + ready_ = 0; ticks_since_last_freq_ = 0; - auto_next_step_ = false; - autotune_completed_ = false; + completed_ = false; F_correction_factor_ = 0xFF; correction_direction_ = false; correction_cnt_positive_ = 0x0; @@ -174,22 +168,21 @@ class Autotuner { size_t data_select_; DAC_CHANNEL channel_; uint8_t calibration_data_; - AT_STATUS auto_tune_running_status_; + AT_STATUS run_status_; // moved from References OC::vfx::ScrollingHistory history_; bool armed_; - uint8_t autotuner_step_; + uint8_t step_; int32_t auto_DAC_offset_error_; uint32_t auto_frequency_; uint32_t auto_target_frequencies_[OCTAVES + 1]; int16_t auto_calibration_data_[OCTAVES + 1]; uint32_t auto_last_frequency_; - bool auto_next_step_; - bool auto_error_; - bool auto_ready_; - bool autotune_completed_; + bool error_; + bool ready_; + bool completed_; uint32_t auto_freq_sum_; uint32_t auto_freq_count_; uint32_t ticks_since_last_freq_; @@ -209,18 +202,6 @@ class Autotuner { void handleButtonDown(const UI::Event &event); - void autotuner_reset_completed() { - autotune_completed_ = false; - } - - inline uint8_t get_octave_cnt() { - return octaves_cnt_ + 0x1; - } - - inline uint8_t auto_tune_step() { - return autotuner_step_; - } - void autotuner_arm(uint8_t _status) { reset_autotuner(); armed_ = _status ? true : false; @@ -228,30 +209,32 @@ class Autotuner { void autotuner_run() { SERIAL_PRINTLN("Starting autotuner..."); - autotuner_step_ = armed_ ? OC::DAC_VOLT_0_BASELINE : OC::DAC_VOLT_0_ARM; - if (autotuner_step_ == OC::DAC_VOLT_0_BASELINE) + step_ = armed_ ? OC::DAC_VOLT_0_BASELINE : OC::DAC_VOLT_0_ARM; + if (step_ == OC::DAC_VOLT_0_BASELINE) // we start, so reset data to defaults: OC::DAC::set_default_channel_calibration_data(channel_); } - void auto_reset_step() { + void auto_next_step() { SERIAL_PRINTLN("Autotuner reset step..."); + ticks_since_last_freq_ = 0x0; auto_num_passes_ = 0x0; auto_DAC_offset_error_ = 0x0; correction_direction_ = false; correction_cnt_positive_ = correction_cnt_negative_ = 0x0; F_correction_factor_ = 0xFF; - auto_ready_ = false; + ready_ = false; + step_++; } void reset_autotuner() { ticks_since_last_freq_ = 0x0; auto_frequency_ = 0x0; auto_last_frequency_ = 0x0; - auto_error_ = 0x0; - auto_ready_ = 0x0; + error_ = 0x0; + ready_ = 0x0; armed_ = 0x0; - autotuner_step_ = 0x0; + step_ = 0x0; F_correction_factor_ = 0xFF; correction_direction_ = false; correction_cnt_positive_ = 0x0; @@ -259,18 +242,10 @@ class Autotuner { octaves_cnt_ = 0x0; auto_num_passes_ = 0x0; auto_DAC_offset_error_ = 0x0; - autotune_completed_ = 0x0; + completed_ = 0x0; reset_calibration_data(); } - const uint32_t get_auto_frequency() { - return auto_frequency_; - } - - uint8_t _ready() { - return auto_ready_; - } - void reset_calibration_data() { for (int i = 0; i < OCTAVES + 1; i++) { @@ -296,7 +271,7 @@ class Autotuner { bool _f_result = false; if (ticks_since_last_freq_ > ERROR_TIMEOUT) { - auto_error_ = true; + error_ = true; } if (FreqMeasure.available()) { @@ -313,7 +288,7 @@ class Autotuner { auto_frequency_ = uint32_t(FreqMeasure.countToFrequency(auto_freq_sum_ / auto_freq_count_) * 1000); history_.Push(auto_frequency_); auto_freq_sum_ = 0; - auto_ready_ = true; + ready_ = true; auto_freq_count_ = 0; _f_result = true; ticks_since_last_freq_ = 0x0; @@ -326,7 +301,7 @@ class Autotuner { void measure_frequency_and_calc_error() { - switch(autotuner_step_) { + switch(step_) { case OC::DAC_VOLT_0_ARM: case OC::DAC_VOLT_WAIT: @@ -349,10 +324,9 @@ class Autotuner { auto_frequency_ = ((auto_frequency_ + average) / (kHistoryDepth + 1)); // 0V SERIAL_PRINTLN("Baseline auto_frequency_ = %4.d", auto_frequency_); // reset step, and proceed: - auto_reset_step(); - autotuner_step_++; + auto_next_step(); // wait for user to patch output to oscillator V/Oct - auto_tune_running_status_ = AT_WAIT; + run_status_ = AT_WAIT; } else if (_update) auto_num_passes_++; @@ -375,13 +349,13 @@ class Autotuner { break; } #else - auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_ + (5 - OC::DAC::kOctaveZero)]; + auto_target_frequencies_[octaves_cnt_] = auto_frequency_ * target_multipliers[octaves_cnt_ + ZERO_OFFSET]; #endif octaves_cnt_++; // go to next step, if done: if (octaves_cnt_ > ACTIVE_OCTAVES) { octaves_cnt_ = 0x0; - autotuner_step_++; + step_++; } } break; @@ -405,8 +379,11 @@ class Autotuner { /* target frequency reached */ SERIAL_PRINTLN("* Target Frequency Reached *"); - if ((autotuner_step_ > OC::DAC_VOLT_2m) && (auto_last_frequency_ * 1.25f > auto_frequency_)) - auto_error_ = true; // throw error, if things don't seem to double ... + int step_idx = step_ - OC::DAC_VOLT_3m; + + // if things don't seem to double ... we've hit the ceiling. + if ((step_ > OC::DAC_VOLT_2m) && (auto_last_frequency_ * 1.25f > auto_frequency_)) + step_ = OC::AUTO_CALIBRATION_STEP_LAST - 1; // average: uint32_t history[kHistoryDepth]; @@ -418,22 +395,21 @@ class Autotuner { // store last frequency: auto_last_frequency_ = (auto_frequency_ + average) / (kHistoryDepth + 1); // and DAC correction value: - auto_calibration_data_[autotuner_step_ - OC::DAC_VOLT_3m] = auto_DAC_offset_error_; + auto_calibration_data_[step_idx] = auto_DAC_offset_error_; // reset and step forward - auto_reset_step(); - autotuner_step_++; + auto_next_step(); } else if (_update) { auto_num_passes_++; // count passes - SERIAL_PRINTLN("auto_target_frequencies[%3d]_ = %3d", autotuner_step_ - OC::DAC_VOLT_3m, - auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] ); + SERIAL_PRINTLN("auto_target_frequencies[%3d]_ = %3d", step_ - OC::DAC_VOLT_3m, + auto_target_frequencies_[step_ - OC::DAC_VOLT_3m] ); SERIAL_PRINTLN("auto_frequency_ = %3d", auto_frequency_); // and correct frequency - if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] > auto_frequency_) + if (auto_target_frequencies_[step_ - OC::DAC_VOLT_3m] > auto_frequency_) { // update correction factor? if (!correction_direction_) @@ -446,7 +422,7 @@ class Autotuner { // we're converging -- count passes, so we can stop after x attempts: if (F_correction_factor_ == 0x1) correction_cnt_positive_++; } - else if (auto_target_frequencies_[autotuner_step_ - OC::DAC_VOLT_3m] < auto_frequency_) + else if (auto_target_frequencies_[step_ - OC::DAC_VOLT_3m] < auto_frequency_) { // update correction factor? if (correction_direction_) @@ -474,8 +450,8 @@ class Autotuner { int32_t new_auto_calibration_point = OC::calibration_data.dac.calibrated_octaves[channel_][octaves_cnt_] + auto_calibration_data_[octaves_cnt_]; // write to DAC and update data if (new_auto_calibration_point >= 65536 || new_auto_calibration_point < 0) { - auto_error_ = true; - autotuner_step_++; + error_ = true; + step_++; } else { OC::DAC::set(channel_, new_auto_calibration_point); @@ -486,22 +462,22 @@ class Autotuner { } // then stop ... if (octaves_cnt_ > OCTAVES) { - autotune_completed_ = true; + completed_ = true; // and point to auto data ... OC::DAC::set_auto_channel_calibration_data(channel_); - autotuner_step_++; + step_++; } break; default: - autotuner_step_ = OC::DAC_VOLT_0_ARM; + step_ = OC::DAC_VOLT_0_ARM; armed_ = 0x0; break; } } - void autotune_updateDAC() { + void updateDAC() { - switch(autotuner_step_) { + switch(step_) { case OC::DAC_VOLT_0_ARM: { @@ -522,7 +498,7 @@ class Autotuner { default: // set DAC to calibration point + error { - int32_t _default_calibration_point = OC::calibration_data.dac.calibrated_octaves[channel_][autotuner_step_ - OC::DAC_VOLT_3m]; + int32_t _default_calibration_point = OC::calibration_data.dac.calibrated_octaves[channel_][step_ - OC::DAC_VOLT_3m]; OC::DAC::set(channel_, _default_calibration_point + auto_DAC_offset_error_); } break; @@ -533,18 +509,18 @@ class Autotuner { template void Autotuner::ISR() { if (armed_) { - autotune_updateDAC(); + updateDAC(); measure_frequency_and_calc_error(); ticks_since_last_freq_++; } - if (auto_error_) { - auto_tune_running_status_ = AT_ERROR; + if (error_) { + run_status_ = AT_ERROR; reset_autotuner(); } - else if (autotune_completed_) { - auto_tune_running_status_ = AT_DONE; - autotuner_reset_completed(); + else if (completed_) { + run_status_ = AT_DONE; + completed_ = false; } } @@ -557,7 +533,7 @@ class Autotuner { weegfx::coord_t y = 0; weegfx::coord_t h = 64; - graphics.clearRect(x, y, w, h); + //graphics.clearRect(x, y, w, h); graphics.drawFrame(x, y, w, h); graphics.setPrintPos(x + 2, y + 3); graphics.print(OC::Strings::channel_id[channel_]); @@ -585,7 +561,7 @@ class Autotuner { } else if (i == AUTOTUNE) { - switch (auto_tune_running_status_) { + switch (run_status_) { //to display progress, if running case AT_OFF: graphics.print("run --> "); @@ -593,14 +569,14 @@ class Autotuner { break; case AT_READY: { graphics.print("arm > "); - const uint32_t _freq = get_auto_frequency(); + const uint32_t _freq = auto_frequency_; if (_freq == 0) - graphics.printf("wait ..."); + graphics.print("wait ..."); else { const uint32_t value = _freq / 1000; const uint32_t cents = _freq % 1000; - graphics.printf("%5u.%03u", value, cents); + graphics.printf("%4u.%03u", value, cents); } } break; @@ -610,23 +586,23 @@ class Autotuner { break; case AT_RUN: { - int _octave = get_octave_cnt(); + int _octave = octaves_cnt_ + 0x1; if (_octave > 1 && _octave < OCTAVES) { for (int i = 0; i <= _octave; i++, x += 6) graphics.drawBitmap8(x + 18, y + 4, 4, OC::bitmap_indicator_4x8); } - else if (auto_tune_step() == DAC_VOLT_0_BASELINE || auto_tune_step() == DAC_VOLT_TARGET_FREQUENCIES) // this goes too quick, so ... + else if (step_ == DAC_VOLT_0_BASELINE || step_ == DAC_VOLT_TARGET_FREQUENCIES) // this goes too quick, so ... graphics.print(" 0.0V baseline"); else { - graphics.print(AT_steps[auto_tune_step() - DAC_VOLT_3m]); - if (!_ready()) + graphics.print(AT_steps[step_ - DAC_VOLT_3m + ZERO_OFFSET]); + if (!ready_) graphics.print(" "); else { - const uint32_t f = get_auto_frequency(); + const uint32_t f = auto_frequency_; const uint32_t value = f / 1000; const uint32_t cents = f % 1000; - graphics.printf(" > %5u.%03u", value, cents); + graphics.printf(" > %4u.%03u", value, cents); } } } @@ -712,16 +688,16 @@ class Autotuner { template void Autotuner::move_cursor(int offset) { - if (auto_tune_running_status_ < AT_RUN) { + if (run_status_ < AT_RUN) { int cursor_pos = cursor_pos_ + offset; CONSTRAIN(cursor_pos, 0, AUTO_MENU_ITEMS_LAST - 0x2); cursor_pos_ = cursor_pos; // if (cursor_pos_ == DATA_SELECT) - auto_tune_running_status_ = AT_OFF; + run_status_ = AT_OFF; } - else if (auto_tune_running_status_ == AT_ERROR || auto_tune_running_status_ == AT_DONE) - auto_tune_running_status_ = AT_OFF; + else if (run_status_ == AT_ERROR || run_status_ == AT_DONE) + run_status_ = AT_OFF; } template @@ -752,14 +728,14 @@ class Autotuner { break; case AUTOTUNE: { - if (auto_tune_running_status_ < AT_RUN) { - int _status = auto_tune_running_status_ + offset; + if (run_status_ < AT_RUN) { + int _status = run_status_ + offset; CONSTRAIN(_status, 0, AT_READY); - auto_tune_running_status_ = AT_STATUS(_status); + run_status_ = AT_STATUS(_status); autotuner_arm(_status); } - else if (auto_tune_running_status_ == AT_ERROR || auto_tune_running_status_ == AT_DONE) - auto_tune_running_status_ = AT_OFF; + else if (run_status_ == AT_ERROR || run_status_ == AT_DONE) + run_status_ = AT_OFF; } break; default: @@ -770,33 +746,33 @@ class Autotuner { template void Autotuner::handleButtonUp(const UI::Event &event) { - if (cursor_pos_ == AUTOTUNE && auto_tune_running_status_ == AT_OFF) { + if (cursor_pos_ == AUTOTUNE && run_status_ == AT_OFF) { // arm the tuner - auto_tune_running_status_ = AT_READY; - autotuner_arm(auto_tune_running_status_); + run_status_ = AT_READY; + autotuner_arm(run_status_); } else { reset_autotuner(); - auto_tune_running_status_ = AT_OFF; + run_status_ = AT_OFF; } } template void Autotuner::handleButtonDown(const UI::Event &event) { - if (auto_tune_running_status_ == AT_ERROR) { + if (run_status_ == AT_ERROR) { reset_autotuner(); - auto_tune_running_status_ = AT_OFF; + run_status_ = AT_OFF; return; } if (cursor_pos_ == AUTOTUNE) { - if (auto_tune_running_status_ == AT_READY) { + if (run_status_ == AT_READY) { autotuner_run(); - auto_tune_running_status_ = AT_RUN; + run_status_ = AT_RUN; } - else if (auto_tune_running_status_ == AT_WAIT) { - auto_tune_running_status_ = AT_RUN; - autotuner_step_++; + else if (run_status_ == AT_WAIT) { + auto_next_step(); + run_status_ = AT_RUN; } } } @@ -816,7 +792,7 @@ class Autotuner { else data_select_ = 0x00; cursor_pos_ = 0x0; - auto_tune_running_status_ = AT_OFF; + run_status_ = AT_OFF; } template From b0f4ce055404109c1716bc0ece85b5f21e5c7708 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 3 Nov 2023 19:27:38 -0400 Subject: [PATCH 354/417] Shuffle: continuously update values --- software/o_c_REV/HEM_Shuffle.ino | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/software/o_c_REV/HEM_Shuffle.ino b/software/o_c_REV/HEM_Shuffle.ino index 5331dc7f7..64c98dd6c 100644 --- a/software/o_c_REV/HEM_Shuffle.ino +++ b/software/o_c_REV/HEM_Shuffle.ino @@ -41,14 +41,22 @@ public: void Controller() { uint32_t tick = OC::CORE::ticks; - if (Clock(1)) { + if (Clock(1)) + { which = 0; // Reset (next trigger will be even clock) last_tick = tick; triplet_which = 0; // Triplets reset to down beat } - if (Clock(0)) { - + // continuously update CV modulated delay values, for display + ForEachChannel(ch) + { + _delay[ch] = delay[ch]; + Modulate(_delay[ch], ch, 0, 100); + } + + if (Clock(0)) + { // Triplets: Track what triplet timing should be to span 4 normal clocks triplet_time = (ClockCycleTicks(0) * 4) / 3; if(triplet_which == 0) @@ -65,8 +73,6 @@ public: which = 1 - which; if (last_tick) { tempo = tick - last_tick; - _delay[which] = delay[which]; - Modulate(_delay[which], which, 0, 100); uint32_t delay_ticks = Proportion(_delay[which], 100, tempo); next_trigger = tick + delay_ticks; } @@ -143,7 +149,7 @@ private: int16_t _delay[2]; // after CV modulation void DrawSelector() { - for (int i = 0; i < 2; i++) + ForEachChannel(i) { gfxPrint(32 + pad(10, _delay[i]), 15 + (i * 10), _delay[i]); gfxPrint("%"); @@ -168,7 +174,7 @@ private: gfxCircle(53, 47, 1); gfxCircle(53, 55, 1); - for (int n = 0; n < 2; n++) + ForEachChannel(n) { int x = Proportion(_delay[n], 100, 20) + (n * 20) + 4; gfxBitmap(x, 48 - (which == n ? 3 : 0), 8, which == n ? NOTE_ICON : X_NOTE_ICON); From a648989d8b7856b52145526fa6dbe3d1156899c5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 8 Nov 2023 19:36:33 -0500 Subject: [PATCH 355/417] ProbMeloD: alternate melody on second output --- software/o_c_REV/HEM_ProbabilityMelody.ino | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index 08693fbb9..289d99431 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -72,17 +72,19 @@ public: GenerateLoop(); } - if (Clock(0) || loop_linker->Ready()) { - if (isLooping) { - pitch = loop[loop_linker->GetLoopStep()] + 60; - } else { - pitch = GetNextWeightedPitch() + 60; - } - if (pitch != -1) { - Out(0, MIDIQuantizer::CV(pitch)); - pulse_animation = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; - } else { - Out(0, 0); + ForEachChannel(ch) { + if (Clock(ch) || loop_linker->Ready()) { + if (isLooping) { + pitch = seqloop[ch][loop_linker->GetLoopStep()] + 60; + } else { + pitch = GetNextWeightedPitch() + 60; + } + if (pitch != -1) { + Out(ch, MIDIQuantizer::CV(pitch)); + pulse_animation = HEMISPHERE_PULSE_ANIMATION_TIME_LONG; + } else { + Out(ch, 0); + } } } @@ -165,9 +167,9 @@ public: protected: void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Clock"; + help[HEMISPHERE_HELP_DIGITALS] = "1=ClockA 2=ClockB"; help[HEMISPHERE_HELP_CVS] = "1=LowRng 2=HighRng"; - help[HEMISPHERE_HELP_OUTS] = "A=Out"; + help[HEMISPHERE_HELP_OUTS] = "A,B=Pitch out"; help[HEMISPHERE_HELP_ENCODER] = "Push to edit value"; // "------------------" <-- Size Guide } @@ -179,7 +181,7 @@ private: int down, down_mod; int pitch; bool isLooping = false; - int loop[HEM_PROB_MEL_MAX_LOOP_LENGTH]; + int seqloop[2][HEM_PROB_MEL_MAX_LOOP_LENGTH]; ProbLoopLinker *loop_linker = loop_linker->get(); @@ -210,7 +212,8 @@ private: void GenerateLoop() { // always fill the whole loop to make things easier for (int i = 0; i < HEM_PROB_MEL_MAX_LOOP_LENGTH; i++) { - loop[i] = GetNextWeightedPitch(); + seqloop[0][i] = GetNextWeightedPitch(); + seqloop[1][i] = GetNextWeightedPitch(); } } From fbe9744462dd98a97887f88a728ecdfde26aba1d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 9 Nov 2023 11:38:39 -0500 Subject: [PATCH 356/417] ProbDiv/ProbMeloD tweaks; separate triggers when linked --- software/o_c_REV/HEM_ProbabilityDivider.ino | 3 ++- software/o_c_REV/HEM_ProbabilityMelody.ino | 2 +- software/o_c_REV/HSProbLoopLinker.h | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_ProbabilityDivider.ino b/software/o_c_REV/HEM_ProbabilityDivider.ino index f0c50686d..b1bbba27c 100644 --- a/software/o_c_REV/HEM_ProbabilityDivider.ino +++ b/software/o_c_REV/HEM_ProbabilityDivider.ino @@ -100,6 +100,7 @@ public: loop_step++; } ClockOut(1); + loop_linker->Trigger(1); return; } @@ -116,7 +117,7 @@ public: } ClockOut(0); - loop_linker->Trigger(); + loop_linker->Trigger(0); pulse_animation = HEMISPHERE_PULSE_ANIMATION_TIME; } diff --git a/software/o_c_REV/HEM_ProbabilityMelody.ino b/software/o_c_REV/HEM_ProbabilityMelody.ino index 289d99431..6e93de16b 100644 --- a/software/o_c_REV/HEM_ProbabilityMelody.ino +++ b/software/o_c_REV/HEM_ProbabilityMelody.ino @@ -73,7 +73,7 @@ public: } ForEachChannel(ch) { - if (Clock(ch) || loop_linker->Ready()) { + if (loop_linker->TrigPop(ch) || Clock(ch)) { if (isLooping) { pitch = seqloop[ch][loop_linker->GetLoopStep()] + 60; } else { diff --git a/software/o_c_REV/HSProbLoopLinker.h b/software/o_c_REV/HSProbLoopLinker.h index 30a6605a4..6c577735c 100644 --- a/software/o_c_REV/HSProbLoopLinker.h +++ b/software/o_c_REV/HSProbLoopLinker.h @@ -23,7 +23,7 @@ class ProbLoopLinker { static ProbLoopLinker *instance; - bool ready = false; + bool trig_q[2] = {0, 0}; uint8_t hemDiv; uint8_t hemMelo; uint32_t registered[2]; @@ -65,13 +65,13 @@ class ProbLoopLinker { && (t - registered[RIGHT_HEMISPHERE] < 160)); } - void Trigger() { - ready = true; + void Trigger(int ch) { + trig_q[ch] = true; } - bool Ready() { - if (IsLinked() && ready) { - ready = false; + bool TrigPop(int ch) { + if (IsLinked() && trig_q[ch]) { + trig_q[ch] = false; return true; } return false; @@ -97,7 +97,7 @@ class ProbLoopLinker { reseed = true; } - int ShouldReseed() { + bool ShouldReseed() { if (reseed) { reseed = false; return true; @@ -109,4 +109,4 @@ class ProbLoopLinker { ProbLoopLinker *ProbLoopLinker::instance = 0; -#endif \ No newline at end of file +#endif From 30fe60b8e7e220ea01fa26458a6465c1dae78831 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 9 Nov 2023 23:58:00 -0500 Subject: [PATCH 357/417] Pigeons: Link with ProbDiv for triggers --- software/o_c_REV/HEM_Pigeons.ino | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_Pigeons.ino b/software/o_c_REV/HEM_Pigeons.ino index ef6a89373..1bf559ff9 100644 --- a/software/o_c_REV/HEM_Pigeons.ino +++ b/software/o_c_REV/HEM_Pigeons.ino @@ -18,8 +18,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +// Hijacking ProbDiv triggers, if available, by pretending to be ProbMeloD ;) +#include "HSProbLoopLinker.h" + class Pigeons : public HemisphereApplet { public: + ProbLoopLinker *loop_linker = loop_linker->get(); + enum PigeonCursor { CHAN1_V1, CHAN1_V2, CHAN1_MOD, @@ -42,13 +47,15 @@ public: } void Controller() { + loop_linker->RegisterMelo(hemisphere); + ForEachChannel(ch) { // CV modulation of modulo value pigeons[ch].mod_v = pigeons[ch].mod; Modulate(pigeons[ch].mod_v, ch, 1, 64); - if (Clock(ch)) { + if (loop_linker->TrigPop(ch) || Clock(ch)) { int signal = QuantizerLookup(ch, pigeons[ch].Bump() + 64) + (root_note << 7); Out(ch, signal); From a8282e461bf2b493111c2bb166d0f8d7f25d9b2f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 6 Nov 2023 16:43:41 -0500 Subject: [PATCH 358/417] Update API-Notes / TODO --- API-notes.md | 20 ++++++++++++++++++-- TODO.md | 6 ++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/API-notes.md b/API-notes.md index f0a97c147..6a7f6ac20 100644 --- a/API-notes.md +++ b/API-notes.md @@ -19,13 +19,23 @@ Anyway, we have top-level *Apps* and then there are *Applets* as implemented in the _Hemisphere_ *App*. An *Applet* inherits the base class _HemisphereApplet_ and gains the superpowers necessary to live on half of your module's screen. +### Base Classes + +The two primary interfaces in the Hemisphere API are _HSApplication_ and _HemisphereApplet_. +They both have many similarly named methods for I/O and graphics, with +_HemisphereApplet_ taking extra considerations for offsetting both in the right side. + +All of the hardware details are neatly abstracted under these two interfaces. If you +simply want to make an applet that does some stuff, these APIs are your starting point. + ### Applets There are a few different things an Applet must do: * Controller - the main logic computed every tick (every time the ISR fires) * View - draw the pixels on the screen (there are many helpful _gfx*_ functions) -* OnButtonPress - what to do when the encoder button is pressed -* OnEncoderMove - what to do when the encoder rotated +* UI Event Handling: + * OnButtonPress - what to do when the encoder button is pressed + * OnEncoderMove - what to do when the encoder rotated * OnDataRequest / OnDataReceive - how to save / load state There is also a `Start()` function for initializing things at runtime, @@ -42,14 +52,20 @@ Function? or Method? Either way, this is how you do the things. The main argument of each is the channel to operate on - each half of the screen gets 2 channels. So _n_ is typically either 0 or 1. +**Input**: * Clock(n) - has the digital input received a new clock pulse? * Gate(n) - is the digital input held high? * In(n) - Raw value of the CV input * DetentedIn(n) - this one reads 0 until it's past a threshold ~ a quartertone + +**Output**: * ClockOut(n) - hold the output high for a pulse length * GateOut(n, on_off) - set the output high or low * Out(n, raw) - set the output to an explicit value +I've added a standard case function for modulating a parameter with a certain input. +* Modulate(param, n, min, max) - automatically scales the input and modifies param + #### gfx Functions There are many strategies for drawing things on the screen, and therefore, many graphics related functions. You can see them for yourself in `HemisphereApplet.h` diff --git a/TODO.md b/TODO.md index 3a9706ec9..3a4dd57d8 100644 --- a/TODO.md +++ b/TODO.md @@ -1,16 +1,16 @@ TODO === -* Fixup & Add auto-tuner to Calibr8or +* Auto-tuner floor/ceiling detection (fail gracefully) * Move calibration routines to a proper App * Runtime filtering/hiding of Applets * Flexible input remapping for Hemisphere - generalize applet params for assignment * global quantizer settings in Hemisphere Config * applet with modal interchange (CV modulation of scale) -* ProbMeloD - alternate melody on 2nd output * Subharmonicon app/applets * Update Boilerplates +* add swing/shuffle to internal clock * Automatic stop for internal Clock? * Polyphonic MIDI input tracking * MIDI output for all apps? @@ -21,6 +21,8 @@ TODO * Snake Game [DONE] +* Add auto-tuner to Calibr8or +* ProbMeloD - alternate melody on 2nd output * Fix FLIP_180 calibration * Add Clock Setup to Calibr8or * Calibr8or screensaver From eee2e84f9c19efe35458edb0ff0d5268dd48efc1 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 9 Nov 2023 19:07:17 -0500 Subject: [PATCH 359/417] Version bump v1.6.777 / new build defaults --- software/o_c_REV/OC_version.h | 2 +- software/o_c_REV/platformio.ini | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index d6dc77778..a67dabf9f 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.666" +"v1.6.777" diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 2c4cfb29c..7853d50e0 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -86,13 +86,13 @@ build_flags = -DENABLE_APP_CALIBR8OR -DENABLE_APP_SCENES -DENABLE_APP_ENIGMA - -DENABLE_APP_MIDI +; -DENABLE_APP_MIDI -DENABLE_APP_PONG -; -DENABLE_APP_PIQUED + -DENABLE_APP_PIQUED -DENABLE_APP_POLYLFO -DENABLE_APP_H1200 -DENABLE_APP_BYTEBEATGEN - -DENABLE_APP_REFERENCES +; -DENABLE_APP_REFERENCES -DPEWPEWPEW -DOC_VERSION_EXTRA="\"_phz\"" [env:wepwepwep] From 62e3dd970d5220568852f9800aa0275d4c089633 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 15 Nov 2023 03:54:17 -0500 Subject: [PATCH 360/417] UI tweaks: screensaver; applet headers; clock screen --- software/o_c_REV/HEM_ClockSetup.ino | 12 ++++++------ software/o_c_REV/HSApplication.h | 6 +++--- software/o_c_REV/HemisphereApplet.h | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index c12d5e1bc..18b242661 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -246,10 +246,7 @@ private: // needs to extend across the screen graphics.setPrintPos(1, 2); graphics.print("Clocks/Triggers"); - //gfxLine(0, 10, 62, 10); - //gfxLine(0, 12, 62, 12); - graphics.drawLine(0, 10, 127, 10); - //graphics.drawLine(0, 12, 127, 12); + gfxLine(0, 10, 127, 10); int y = 14; // Clock State @@ -282,7 +279,7 @@ private: } // Physical trigger input mappings - gfxPrint(1 + x, y + 11, OC::Strings::trigger_input_names_none[ HS::trigger_mapping[ch] ] ); + gfxPrint(1 + x, y + 13, OC::Strings::trigger_input_names_none[ HS::trigger_mapping[ch] ] ); // Manual trigger buttons gfxIcon(4 + x, 47, (button_ticker && ch == cursor-BOOP1)?BTN_ON_ICON:BTN_OFF_ICON); @@ -292,6 +289,9 @@ private: if (flash_ticker[ch]) gfxInvert(3 + x, 56, 9, 8); } + y += 10; + gfxDottedLine(0, y, 127, y, 3); + switch ((ClockSetupCursor)cursor) { case PLAY_STOP: gfxFrame(11, 13, 10, 10); @@ -314,7 +314,7 @@ private: case TRIG2: case TRIG3: case TRIG4: - gfxCursor(1 + 32*(cursor-TRIG1), 43, 19); + gfxCursor(1 + 32*(cursor-TRIG1), 45, 19); break; case BOOP1: diff --git a/software/o_c_REV/HSApplication.h b/software/o_c_REV/HSApplication.h index 7a480e964..73631804c 100644 --- a/software/o_c_REV/HSApplication.h +++ b/software/o_c_REV/HSApplication.h @@ -93,7 +93,7 @@ class HSApplication { // general screensaver view, visualizing inputs and outputs void BaseScreensaver(bool notenames = 0) { - gfxDottedLine(0, 32, 127, 32); // horizontal baseline + gfxDottedLine(0, 32, 127, 32, 3); // horizontal baseline for (int ch = 0; ch < 4; ++ch) { if (notenames) { @@ -114,9 +114,9 @@ class HSApplication { y = constrain(32 - height, 0, 32); gfxInvert(11 + (32 * ch), y, 12, abs(height)); - gfxLine(32 * ch, 0, 32*ch, 63); // vertical divider, left side + gfxDottedLine(32 * ch, 0, 32*ch, 63, 3); // vertical divider, left side } - gfxLine(127, 0, 127, 63); // vertical line, right side + gfxDottedLine(127, 0, 127, 63, 3); // vertical line, right side } //////////////// Hemisphere-like IO methods diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index f66f48518..956202411 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -335,8 +335,8 @@ class HemisphereApplet { void gfxHeader(const char *str) { gfxPrint(1, 2, str); - gfxLine(0, 10, 62, 10); - gfxLine(0, 11, 62, 11); + gfxDottedLine(0, 10, 62, 10); + //gfxLine(0, 11, 62, 11); } //////////////// Offset I/O methods From 0e30ba24b8413df8fb4c64d53977970dc82c712f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 17 Nov 2023 00:45:10 -0500 Subject: [PATCH 361/417] ResetClk: fix bug when changing offset via encoder --- software/o_c_REV/HEM_ResetClock.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_ResetClock.ino b/software/o_c_REV/HEM_ResetClock.ino index 796f186ef..306258a79 100644 --- a/software/o_c_REV/HEM_ResetClock.ino +++ b/software/o_c_REV/HEM_ResetClock.ino @@ -86,7 +86,7 @@ public: offset %= length; break; case 1: // offset - UpdateOffset(offset, offset + direction); + offset = constrain(offset + direction, 0, length - 1); break; case 2: // spacing spacing = constrain(spacing + direction, RC_MIN_SPACING, 100); From 56bd24fe728b955660760d57a9ebb355c3a10849 Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Sun, 19 Nov 2023 15:31:35 -0800 Subject: [PATCH 362/417] Configurable pins on Teensy 4 --- software/o_c_REV/OC_digital_inputs.cpp | 66 ++++++++++++++++++++++++-- software/o_c_REV/OC_digital_inputs.h | 61 ++++++++++++++++++++++-- software/o_c_REV/OC_gpio.cpp | 54 +++++++++++++++++++++ software/o_c_REV/OC_gpio.h | 31 ++++++++++-- software/o_c_REV/UI/ui_encoder.h | 4 ++ software/o_c_REV/o_c_REV.ino | 1 + 6 files changed, 206 insertions(+), 11 deletions(-) create mode 100644 software/o_c_REV/OC_gpio.cpp diff --git a/software/o_c_REV/OC_digital_inputs.cpp b/software/o_c_REV/OC_digital_inputs.cpp index c48cbc2b3..9f2f7659b 100644 --- a/software/o_c_REV/OC_digital_inputs.cpp +++ b/software/o_c_REV/OC_digital_inputs.cpp @@ -4,25 +4,27 @@ #include "OC_gpio.h" #include "OC_options.h" +#if defined(__MK20DX256__) // Teensy 3.2 + /*static*/ uint32_t OC::DigitalInputs::clocked_mask_; /*static*/ volatile uint32_t OC::DigitalInputs::clocked_[DIGITAL_INPUT_LAST]; -void FASTRUN tr1_ISR() { +void FASTRUN OC::tr1_ISR() { OC::DigitalInputs::clock(); } // main clock -void FASTRUN tr2_ISR() { +void FASTRUN OC::tr2_ISR() { OC::DigitalInputs::clock(); } -void FASTRUN tr3_ISR() { +void FASTRUN OC::tr3_ISR() { OC::DigitalInputs::clock(); } -void FASTRUN tr4_ISR() { +void FASTRUN OC::tr4_ISR() { OC::DigitalInputs::clock(); } @@ -84,3 +86,59 @@ void OC::DigitalInputs::Scan() { ScanInput() | ScanInput(); } + +#endif // Teensy 3.2 + + +#if defined(__IMXRT1062__) // Teensy 4.0 or 4.1 + +uint8_t OC::DigitalInputs::clocked_mask_; +IMXRT_GPIO_t * OC::DigitalInputs::port[DIGITAL_INPUT_LAST]; +uint32_t OC::DigitalInputs::bitmask[DIGITAL_INPUT_LAST]; + +FLASHMEM +void OC::DigitalInputs::Init() { + pinMode(TR1, INPUT_PULLUP); + pinMode(TR2, INPUT_PULLUP); + pinMode(TR3, INPUT_PULLUP); + pinMode(TR4, INPUT_PULLUP); + port[0] = (IMXRT_GPIO_t *)digitalPinToPortReg(TR1); + port[1] = (IMXRT_GPIO_t *)digitalPinToPortReg(TR2); + port[2] = (IMXRT_GPIO_t *)digitalPinToPortReg(TR3); + port[3] = (IMXRT_GPIO_t *)digitalPinToPortReg(TR4); + bitmask[0] = digitalPinToBitMask(TR1); + bitmask[1] = digitalPinToBitMask(TR2); + bitmask[2] = digitalPinToBitMask(TR3); + bitmask[3] = digitalPinToBitMask(TR4); + for (unsigned int i=0; i < 4; i++) { + unsigned int bitnum = __builtin_ctz(bitmask[i]); + if (bitnum < 16) { + port[i]->ICR1 |= (0x03 << (bitnum * 2)); // falling edge detect, bits 0-15 + } else { + port[i]->ICR2 |= (0x03 << ((bitnum - 16) * 2)); // falling edge detect, bits 16-31 + } + port[i]->ISR = bitmask[i]; // clear any prior detected edge + } +} + +void OC::DigitalInputs::Scan() { + uint32_t mask[4]; + noInterrupts(); + mask[0] = port[0]->ISR & bitmask[0]; + port[0]->ISR = mask[0]; + mask[1] = port[1]->ISR & bitmask[1]; + port[1]->ISR = mask[1]; + mask[2] = port[2]->ISR & bitmask[2]; + port[2]->ISR = mask[0]; + mask[3] = port[3]->ISR & bitmask[3]; + port[3]->ISR = mask[3]; + interrupts(); + uint8_t new_clocked_mask = 0; + if (mask[0]) new_clocked_mask |= 0x01; + if (mask[1]) new_clocked_mask |= 0x02; + if (mask[2]) new_clocked_mask |= 0x04; + if (mask[3]) new_clocked_mask |= 0x08; + clocked_mask_ = new_clocked_mask; +} + +#endif // Teensy 4.0 or 4.1 diff --git a/software/o_c_REV/OC_digital_inputs.h b/software/o_c_REV/OC_digital_inputs.h index ce8cedbfb..539481a59 100644 --- a/software/o_c_REV/OC_digital_inputs.h +++ b/software/o_c_REV/OC_digital_inputs.h @@ -23,12 +23,19 @@ static constexpr uint32_t DIGITAL_INPUT_2_MASK = DIGITAL_INPUT_MASK(DIGITAL_INPU static constexpr uint32_t DIGITAL_INPUT_3_MASK = DIGITAL_INPUT_MASK(DIGITAL_INPUT_3); static constexpr uint32_t DIGITAL_INPUT_4_MASK = DIGITAL_INPUT_MASK(DIGITAL_INPUT_4); +#if defined(__MK20DX256__) // Teensy 3.2 + template struct InputPinDesc { }; template <> struct InputPinDesc { static constexpr int PIN = TR1; }; template <> struct InputPinDesc { static constexpr int PIN = TR2; }; template <> struct InputPinDesc { static constexpr int PIN = TR3; }; template <> struct InputPinDesc { static constexpr int PIN = TR4; }; +void tr1_ISR(); +void tr2_ISR(); +void tr3_ISR(); +void tr4_ISR(); + class DigitalInputs { public: @@ -38,17 +45,17 @@ class DigitalInputs { static void Scan(); - // @return mask of all pins cloked since last call, reset state + // @return mask of all pins cloked since last call (does not reset state) static inline uint32_t clocked() { return clocked_mask_; } - // @return mask if pin clocked since last call and reset state + // @return mask if pin clocked since last call (does not reset state) template static inline uint32_t clocked() { return clocked_mask_ & (0x1 << input); } - // @return mask if pin clocked since last call, reset state + // @return mask if pin clocked since last call (does not reset state) static inline uint32_t clocked(DigitalInput input) { return clocked_mask_ & (0x1 << input); } @@ -61,6 +68,12 @@ class DigitalInputs { return !digitalReadFast(InputPinMap(input)); } +private: + // clock() only called from interrupt functions + friend void tr1_ISR(); + friend void tr2_ISR(); + friend void tr3_ISR(); + friend void tr4_ISR(); template static inline void clock() { clocked_[input] = 1; } @@ -92,6 +105,48 @@ class DigitalInputs { } }; + +#elif defined(__IMXRT1062__) // Teensy 4.0 or 4.1 + +class DigitalInputs { +public: + static void Init(); + static void reInit() { Init(); } + static void Scan(); + + // @return mask of all pins clocked since last Scan() + static inline uint32_t clocked() { + return clocked_mask_; + } + // @return mask if pin clocked since last Scan() + template static inline uint32_t clocked() { + return clocked(input); + } + static inline uint32_t clocked(DigitalInput input) { + return clocked_mask_ & (0x1 << input); + } + template static inline bool read_immediate() { + return read_immediate(input); + } + static inline bool read_immediate(DigitalInput input) { + switch (input) { + case DIGITAL_INPUT_1: return (digitalRead(TR1) == LOW) ? true : false; + case DIGITAL_INPUT_2: return (digitalRead(TR2) == LOW) ? true : false; + case DIGITAL_INPUT_3: return (digitalRead(TR3) == LOW) ? true : false; + case DIGITAL_INPUT_4: return (digitalRead(TR4) == LOW) ? true : false; + case DIGITAL_INPUT_LAST: break; + } + return false; + } +private: + static uint8_t clocked_mask_; + static IMXRT_GPIO_t *port[DIGITAL_INPUT_LAST]; + static uint32_t bitmask[DIGITAL_INPUT_LAST]; +}; + +#endif + + // Helper class for visualizing digital inputs with decay // Uses 4 bits for decay class DigitalInputDisplay { diff --git a/software/o_c_REV/OC_gpio.cpp b/software/o_c_REV/OC_gpio.cpp new file mode 100644 index 000000000..c305d5d1d --- /dev/null +++ b/software/o_c_REV/OC_gpio.cpp @@ -0,0 +1,54 @@ +#include +#include +#include "OC_digital_inputs.h" +#include "OC_gpio.h" +#include "OC_options.h" + +#if defined(__IMXRT1062__) + +// default settings for traditional O_C hardware wired for Teensy 3.2 +uint8_t CV1=19, CV2=18, CV3=20, CV4=17; +uint8_t TR1=0, TR2=1, TR3=2, TR4=3; +uint8_t OLED_DC=6, OLED_RST=7, OLED_CS=8; +uint8_t DAC_CS=10, DAC_RST=9; +uint8_t encL1=22, encL2=21, butL=23, encR1=16, encR2=15, butR=14; +uint8_t but_top=4, but_bot=5, but_mid=255, but_top2=255, but_bot2=255; +uint8_t OC_GPIO_DEBUG_PIN1=24, OC_GPIO_DEBUG_PIN2=25; + +FLASHMEM +void OC::Pinout_Detect() { +#if defined(ARDUINO_TEENSY41) + // TODO, read A17 voltage and configure pins +/* + CV1 = 19; + CV2 = 18; + CV3 = 20; + CV4 = 17; + TR1 = 0; + TR2 = 1; + TR3 = 2; + TR4 = 3; + but_top = 5; + but_bot = 4; + OLED_DC = 6; + OLED_RST = 7; + OLED_CS = 8; + DAC_CS = 10; + DAC_RST = 9; + encR1 = 16; + encR2 = 15; + butR = 14; + encL1 = 22; + encL2 = 21; + butL = 23; + OC_GPIO_DEBUG_PIN1 = 24; + OC_GPIO_DEBUG_PIN2 = 25; +*/ + + + + +#endif // ARDUINO_TEENSY41 +} + +#endif // __IMXRT1062__ diff --git a/software/o_c_REV/OC_gpio.h b/software/o_c_REV/OC_gpio.h index 37d3bbafd..982f3bd1a 100644 --- a/software/o_c_REV/OC_gpio.h +++ b/software/o_c_REV/OC_gpio.h @@ -3,6 +3,10 @@ #include "OC_options.h" +// Teensy 3.2 or Teensy 4.0 have fixed pinout +// +#if defined(__MK20DX256__) || (defined(__IMXRT1062__) && defined(ARDUINO_TEENSY40)) + #ifdef FLIP_180 #define CV4 19 #define CV3 18 @@ -35,10 +39,6 @@ #define OLED_RST 7 #define OLED_CS 8 -// OLED CS is active low -#define OLED_CS_ACTIVE LOW -#define OLED_CS_INACTIVE HIGH - #define DAC_CS 10 #ifdef VOR @@ -70,6 +70,26 @@ #define OC_GPIO_DEBUG_PIN1 24 #define OC_GPIO_DEBUG_PIN2 25 +#endif // Teensy 3.2 or Teensy 4.0 + +// Teensy 4.1 has different pinouts depending on voltage at pin 41/A17 +// +#if defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) + +extern uint8_t CV1, CV2, CV3, CV4; +extern uint8_t TR1, TR2, TR3, TR4; +extern uint8_t OLED_DC, OLED_RST, OLED_CS; +extern uint8_t DAC_CS, DAC_RST; +extern uint8_t encL1, encL2, butL, encR1, encR2, butR; +extern uint8_t but_top, but_bot, but_mid, but_top2, but_bot2; +extern uint8_t OC_GPIO_DEBUG_PIN1, OC_GPIO_DEBUG_PIN2; + +#endif + +// OLED CS is active low +#define OLED_CS_ACTIVE LOW +#define OLED_CS_INACTIVE HIGH + #define OC_GPIO_BUTTON_PINMODE INPUT_PULLUP #define OC_GPIO_TRx_PINMODE INPUT_PULLUP #define OC_GPIO_ENC_PINMODE INPUT_PULLUP @@ -119,6 +139,9 @@ void inline pinMode(uint8_t pin, uint8_t mode) { ::pinMode(pin, mode); // for Teensy 4.x, just use normal pinMode #endif } + +void Pinout_Detect(); + } #endif // OC_GPIO_H_ diff --git a/software/o_c_REV/UI/ui_encoder.h b/software/o_c_REV/UI/ui_encoder.h index 0e930aa8e..9e3bc4823 100644 --- a/software/o_c_REV/UI/ui_encoder.h +++ b/software/o_c_REV/UI/ui_encoder.h @@ -27,7 +27,11 @@ namespace UI { +#if defined(__MK20DX256__) // Teensy 3.2 template +#elif defined(__IMXRT1062__) // Teensy 4.0 or 4.1 +template +#endif class Encoder { public: diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 1ef772928..299bf00a3 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -105,6 +105,7 @@ void setup() { Serial.println(CrashReport); delay(1500); } + OC::Pinout_Detect(); #endif #if defined(__MK20DX256__) NVIC_SET_PRIORITY(IRQ_PORTB, 0); // TR1 = 0 = PTB16 From e60cfadb480299bdb5e17e87542d2cbbd219f33d Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Mon, 20 Nov 2023 05:07:23 -0800 Subject: [PATCH 363/417] Opps, fix TR3 input on Teensy 4.x --- software/o_c_REV/OC_digital_inputs.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_digital_inputs.cpp b/software/o_c_REV/OC_digital_inputs.cpp index 9f2f7659b..01c7dbd10 100644 --- a/software/o_c_REV/OC_digital_inputs.cpp +++ b/software/o_c_REV/OC_digital_inputs.cpp @@ -129,7 +129,7 @@ void OC::DigitalInputs::Scan() { mask[1] = port[1]->ISR & bitmask[1]; port[1]->ISR = mask[1]; mask[2] = port[2]->ISR & bitmask[2]; - port[2]->ISR = mask[0]; + port[2]->ISR = mask[2]; mask[3] = port[3]->ISR & bitmask[3]; port[3]->ISR = mask[3]; interrupts(); @@ -139,6 +139,13 @@ void OC::DigitalInputs::Scan() { if (mask[2]) new_clocked_mask |= 0x04; if (mask[3]) new_clocked_mask |= 0x08; clocked_mask_ = new_clocked_mask; + #if 0 + if (clocked_mask_) { + static elapsedMicros usec; + Serial.printf("%u %u\n", clocked_mask_, (int)usec); + usec = 0; + } + #endif } #endif // Teensy 4.0 or 4.1 From 37b3759d190f5826076ac17a710fce838fc85940 Mon Sep 17 00:00:00 2001 From: PaulStoffregen Date: Mon, 20 Nov 2023 14:56:41 -0800 Subject: [PATCH 364/417] Read PCB ID voltage (pin A17) on Teensy 4.1 --- software/o_c_REV/OC_ADC.cpp | 28 ++++++++++++++ software/o_c_REV/OC_ADC.h | 2 + software/o_c_REV/OC_gpio.cpp | 75 ++++++++++++++++++++++-------------- 3 files changed, 76 insertions(+), 29 deletions(-) diff --git a/software/o_c_REV/OC_ADC.cpp b/software/o_c_REV/OC_ADC.cpp index 9bbaf2d04..f37a35573 100644 --- a/software/o_c_REV/OC_ADC.cpp +++ b/software/o_c_REV/OC_ADC.cpp @@ -328,6 +328,34 @@ static void sum_adc(uint32_t *sum, const adcframe_t *n) { old_idx = idx; } } + +#if defined(ARDUINO_TEENSY41) // Teensy 4.1 - A17 pin identifies PCB hardware +FLASHMEM static +float read_id_voltage() { + const unsigned int count = 50; + unsigned int sum=0; + delayMicroseconds(10); + for (unsigned int i=0; i < count; i++) { + sum += analogRead(A17); + } + return (float)sum * (3.3f / 1023.0f / (float)count); +} + +FLASHMEM +float ADC::Read_ID_Voltage() { + pinMode(A17, INPUT_PULLUP); + float volts_pullup = read_id_voltage(); + pinMode(A17, INPUT_PULLDOWN); + float volts_pulldown = read_id_voltage(); + pinMode(A17, INPUT_DISABLE); + if (volts_pullup - volts_pulldown > 2.5f) return 0; // pin not connected + return read_id_voltage(); +} +#else // Teensy 4.0 +FLASHMEM float ADC::Read_ID_Voltage() { return 0; } +#endif + + #endif // __IMXRT1062__ diff --git a/software/o_c_REV/OC_ADC.h b/software/o_c_REV/OC_ADC.h index b6795be81..9562c48c2 100644 --- a/software/o_c_REV/OC_ADC.h +++ b/software/o_c_REV/OC_ADC.h @@ -81,6 +81,8 @@ class ADC { static void CalibratePitch(int32_t c2, int32_t c4); + static float Read_ID_Voltage(); + private: template diff --git a/software/o_c_REV/OC_gpio.cpp b/software/o_c_REV/OC_gpio.cpp index c305d5d1d..58b4b95cf 100644 --- a/software/o_c_REV/OC_gpio.cpp +++ b/software/o_c_REV/OC_gpio.cpp @@ -2,7 +2,8 @@ #include #include "OC_digital_inputs.h" #include "OC_gpio.h" -#include "OC_options.h" +#include "OC_ADC.h" +//#include "OC_options.h" #if defined(__IMXRT1062__) @@ -14,39 +15,55 @@ uint8_t DAC_CS=10, DAC_RST=9; uint8_t encL1=22, encL2=21, butL=23, encR1=16, encR2=15, butR=14; uint8_t but_top=4, but_bot=5, but_mid=255, but_top2=255, but_bot2=255; uint8_t OC_GPIO_DEBUG_PIN1=24, OC_GPIO_DEBUG_PIN2=25; +bool ADC33131D_Uses_FlexIO=false; +bool OLED_Uses_SPI1=false; +bool DAC8558_Uses_SPI=false; +bool I2S2_Audio_ADC=false; +bool I2S2_Audio_DAC=false; +bool I2C_Expansion=false; +bool MIDI_Uses_Serial8=false; FLASHMEM void OC::Pinout_Detect() { #if defined(ARDUINO_TEENSY41) - // TODO, read A17 voltage and configure pins -/* - CV1 = 19; - CV2 = 18; - CV3 = 20; - CV4 = 17; - TR1 = 0; - TR2 = 1; - TR3 = 2; - TR4 = 3; - but_top = 5; - but_bot = 4; - OLED_DC = 6; - OLED_RST = 7; - OLED_CS = 8; - DAC_CS = 10; - DAC_RST = 9; - encR1 = 16; - encR2 = 15; - butR = 14; - encL1 = 22; - encL2 = 21; - butL = 23; - OC_GPIO_DEBUG_PIN1 = 24; - OC_GPIO_DEBUG_PIN2 = 25; -*/ - - + float id_voltage = OC::ADC::Read_ID_Voltage(); + Serial.printf("ID voltage (pin A17) = %.3f\n", id_voltage); + if (id_voltage >= 0.05f && id_voltage < 0.15f) { + CV1 = 255; + CV2 = 255; // CV inputs with ADC33131D + CV3 = 255; + CV4 = 255; + TR1 = 0; + TR2 = 1; + TR3 = 23; + TR4 = 22; + but_top = 14; + but_top2 = 15; + but_bot = 29; + but_bot2 = 28; + but_mid = 20; + OLED_Uses_SPI1 = true; // pins 26=MOSI, 27=SCK + OLED_DC = 39; + OLED_RST = 38; + OLED_CS = 40; + DAC8558_Uses_SPI = true; // pins 10=CS, 11=MOSI, 13=SCK + DAC_CS = 255; // pin 10 controlled by SPI hardware + DAC_RST = 255; + encR1 = 36; + encR2 = 37; + butR = 25; + encL1 = 30; + encL2 = 31; + butL = 24; + OC_GPIO_DEBUG_PIN1 = 16; + OC_GPIO_DEBUG_PIN2 = 17; + ADC33131D_Uses_FlexIO = true; // pins 6=A0, 7=A1, 8=SCK, 9=CS, 12=DATA, 32=A2 + I2S2_Audio_ADC = true; // pins 3=LRCLK, 4=BCLK, 5=DATA, 33=MCLK + I2S2_Audio_DAC = true; // pins 2=DATA, 3=LRCLK, 4=BCLK, 33=MCLK + I2C_Expansion = true; // pins 18=SDA, 19=SCL + MIDI_Uses_Serial8 = true; // pins 34=IN, 35=OUT + } #endif // ARDUINO_TEENSY41 } From ba88103ef8630e832ad53bdd9c6735111d0589c5 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 24 Nov 2023 17:55:35 -0500 Subject: [PATCH 365/417] Fixes for Teensy 4.0; custom pin config only for 4.1 --- software/o_c_REV/OC_gpio.cpp | 7 +++---- software/o_c_REV/UI/ui_encoder.h | 6 +++--- software/o_c_REV/o_c_REV.ino | 2 ++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/software/o_c_REV/OC_gpio.cpp b/software/o_c_REV/OC_gpio.cpp index 58b4b95cf..01e74d6af 100644 --- a/software/o_c_REV/OC_gpio.cpp +++ b/software/o_c_REV/OC_gpio.cpp @@ -5,7 +5,8 @@ #include "OC_ADC.h" //#include "OC_options.h" -#if defined(__IMXRT1062__) +// custom pins only for Teensy 4.1 +#if defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) // default settings for traditional O_C hardware wired for Teensy 3.2 uint8_t CV1=19, CV2=18, CV3=20, CV4=17; @@ -25,7 +26,6 @@ bool MIDI_Uses_Serial8=false; FLASHMEM void OC::Pinout_Detect() { -#if defined(ARDUINO_TEENSY41) float id_voltage = OC::ADC::Read_ID_Voltage(); Serial.printf("ID voltage (pin A17) = %.3f\n", id_voltage); @@ -65,7 +65,6 @@ void OC::Pinout_Detect() { MIDI_Uses_Serial8 = true; // pins 34=IN, 35=OUT } -#endif // ARDUINO_TEENSY41 } -#endif // __IMXRT1062__ +#endif // __IMXRT1062__ && ARDUINO_TEENSY41 diff --git a/software/o_c_REV/UI/ui_encoder.h b/software/o_c_REV/UI/ui_encoder.h index 9e3bc4823..675ac665f 100644 --- a/software/o_c_REV/UI/ui_encoder.h +++ b/software/o_c_REV/UI/ui_encoder.h @@ -27,10 +27,10 @@ namespace UI { -#if defined(__MK20DX256__) // Teensy 3.2 -template -#elif defined(__IMXRT1062__) // Teensy 4.0 or 4.1 +#if defined(__IMXRT1062__) && defined(ARDUINO_TEENSY41) // Teensy 4.1 has dynamic pins template +#else // default are macros (Teensy 3.2 or 4.0) +template #endif class Encoder { public: diff --git a/software/o_c_REV/o_c_REV.ino b/software/o_c_REV/o_c_REV.ino index 299bf00a3..c1a75cc7c 100644 --- a/software/o_c_REV/o_c_REV.ino +++ b/software/o_c_REV/o_c_REV.ino @@ -105,7 +105,9 @@ void setup() { Serial.println(CrashReport); delay(1500); } + #if defined(ARDUINO_TEENSY41) OC::Pinout_Detect(); + #endif #endif #if defined(__MK20DX256__) NVIC_SET_PRIORITY(IRQ_PORTB, 0); // TR1 = 0 = PTB16 From fa7584ca8d56f1a110bbbaecf77b249bf7f48820 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 14 Dec 2023 03:26:10 -0500 Subject: [PATCH 366/417] Github action for PlatformIO CI (via pld) Cache version bumped to v3 --- .github/workflows/firmware.yml | 45 ++++++++++++++++++++++++++++++ .github/workflows/requirements.txt | 1 + 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/firmware.yml create mode 100644 .github/workflows/requirements.txt diff --git a/.github/workflows/firmware.yml b/.github/workflows/firmware.yml new file mode 100644 index 000000000..88877175e --- /dev/null +++ b/.github/workflows/firmware.yml @@ -0,0 +1,45 @@ +name: PlatformIO CI + +on: + push: + branches: + - 'dev/**' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup env + run: | + echo "OC_ARTIFACT_TAG=${GITHUB_REF_NAME}-$(software/o_c_REV/resources/oc_build_tag.sh)" | tr '/' '_' >> $GITHUB_ENV + + - name: Cache PlatformIO + uses: actions/cache@v3 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + cache: 'pip' + cache-dependency-path: '**/requirements.txt' + + - run: | + pip3 install --upgrade pip + pip3 install -r .github/workflows/requirements.txt + + - name: Build firmware + working-directory: software/o_c_REV/ + run: | + pio run + + - name: Copy artifact + uses: actions/upload-artifact@v3 + with: + name: o_C-${{env.OC_ARTIFACT_TAG}} + path: software/o_c_REV/.pio/build/*/*.hex diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt new file mode 100644 index 000000000..7c715125f --- /dev/null +++ b/.github/workflows/requirements.txt @@ -0,0 +1 @@ +platformio From a133fe6fc7ae837f9aebe05fab061b9f863762b9 Mon Sep 17 00:00:00 2001 From: Nicholas Michalek Date: Sat, 16 Dec 2023 00:18:05 -0500 Subject: [PATCH 367/417] Update README.md; Add workflow status badge Emphasizing automated server-side builds --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 85ad60001..82e58581c 100755 --- a/README.md +++ b/README.md @@ -1,17 +1,20 @@ -"What's the worst that could happen?" +[![PlatformIO CI](https://github.com/djphazer/O_C-BenisphereSuite/actions/workflows/firmware.yml/badge.svg)](https://github.com/djphazer/O_C-BenisphereSuite/actions/workflows/firmware.yml) + +Phazerville Suite - an active o_C firmware fork === +[![SynthDad's video overview](http://img.youtube.com/vi/XRGlAmz3AKM/0.jpg)](http://www.youtube.com/watch?v=XRGlAmz3AKM "Phazerville; newest firmware for Ornament and Crime. Tutorial and patch ideas") -Watch SynthDad's [**video overview**](https://www.youtube.com/watch?v=XRGlAmz3AKM) and check the [**Wiki**](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. [Download it here](https://github.com/djphazer/O_C-BenisphereSuite/releases). +Watch SynthDad's **video overview** (above) or check the [**Wiki**](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. [Download it here](https://github.com/djphazer/O_C-BenisphereSuite/releases). -## Phazerville Suite - an active o_C firmware fork +## Stolen Ornaments -Using [**Benisphere**](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the **Hemisphere Suite** in new directions, with several new applets and enhancements to existing ones. I've merged bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! +Using [**Benisphere**](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the **Hemisphere Suite** in new directions, with several new applets and enhancements to existing ones. I wanted to collect all the bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! I've also included **all of the stock O&C firmware apps**, but they don't all fit in one .hex. As a courtesy, I provide **3 different build choices** with various combinations of Apps in my [**Releases**](https://github.com/djphazer/O_C-BenisphereSuite/releases). I think of it like the boxed set of a movie trilogy or whatever. The O&C Saga. 4 different hardware format options. Free and Open Source, baby! You can also customize the `platformio.ini` file to mix & match for yourself ;-) -### Notable Features in this branch: +### New Crimes I've Committed * 4 Presets in the new [**Hemisphere Config**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Hemisphere-Config) * Modal-editing style cursor navigation (and other usability tweaks) @@ -25,24 +28,21 @@ You can also customize the `platformio.ini` file to mix & match for yourself ;-) Plus lots of other small tweaks + experimental applets. -### How do I try it? +### How To Get It -Check the [Releases](https://github.com/djphazer/O_C-BenisphereSuite/releases) section for a .hex file, or clone the repository and build it yourself! I think the beauty of this module is the fact that it's relatively easy to modify and build the source code to reprogram it. You are free to customize the firmware, similar to how you've no doubt already selected a custom set of physical modules. +Check the [Releases](https://github.com/djphazer/O_C-BenisphereSuite/releases) section for a .hex file (to be used with the Teensy loader app), or clone the repository and build it yourself! I think the beauty of this module is the fact that it's relatively easy to modify and build the source code to reprogram it. You are free to customize the firmware, similar to how you've no doubt already selected a custom set of physical modules. -### How do I build it? +### How To Change It -I've abandoned the old Arduino IDE in favor of Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html): -``` -wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -O get-platformio.py -python3 get-platformio.py -``` -...or as a [a full-featured IDE](https://platformio.org/install/ide), as well as a plugin for VSCode and other existing IDEs. +This firmware fork is built using Platform IO, a Python-based build toolchain, available as either a [standalone CLI](https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html) or a [full-featured IDE](https://platformio.org/install/ide), as well as a plugin for VSCode and other existing IDEs. -This project lives within the `software/o_c_REV` directory. From there, you can Build the desired configuration and Upload via USB to your module: +The project lives within the `software/o_c_REV` directory. From there, you can Build the desired configuration and Upload via USB to your module: ``` pio run -e oc_stock2_flipped -t upload ``` -Alternate build environment configurations exist in `platformio.ini` for VOR, Buchla, flipped screen, etc. To build all the defaults consecutively, simply use `pio run` +Have a look inside `platformio.ini` for alternative build environment configurations - VOR, Buchla, flipped screen, etc. To build all the defaults consecutively, simply use `pio run` + +_**Pro-tip**_: If you decide to fork the project, and enable GitHub Actions on your own repo, GitHub will build the files for you... ;) ## Credits From 0f2fa44d6fc2bb8a4947ef47fb5d85ce08133c42 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 17 Dec 2023 03:43:12 -0500 Subject: [PATCH 368/417] Custom Build workflow / GitHub Action Initiated by a comment containing "/buildthis" on any Discussion --- .github/workflows/custom.yml | 72 +++++++++++++++++++ software/o_c_REV/platformio.ini | 6 ++ .../o_c_REV/resources/parse_build_request.py | 54 ++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 .github/workflows/custom.yml create mode 100644 software/o_c_REV/resources/parse_build_request.py diff --git a/.github/workflows/custom.yml b/.github/workflows/custom.yml new file mode 100644 index 000000000..1cd2230db --- /dev/null +++ b/.github/workflows/custom.yml @@ -0,0 +1,72 @@ +name: Custom Build + +on: + discussion_comment: + types: [created] + +jobs: + build: + name: Build Custom Firmware + if: contains(github.event.comment.body, '/buildthis') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + ref: phazerville + + - name: Setup env + env: + GH_COMMENT: ${{ github.event.comment.body }} + run: | + echo "OC_ARTIFACT_TAG=custom_${GITHUB_ACTOR}-$(software/o_c_REV/resources/oc_build_tag.sh)" | tr '/' '_' >> $GITHUB_ENV + echo "CUSTOM_FLAGS=$(python software/o_c_REV/resources/parse_build_request.py)" >> $GITHUB_ENV + env | grep ^GITHUB + + - name: Cache PlatformIO + uses: actions/cache@v3 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + cache: 'pip' + cache-dependency-path: '**/requirements.txt' + + - run: | + pip3 install --upgrade pip + pip3 install -r .github/workflows/requirements.txt + + - name: Build firmware + env: + CUSTOM_BUILD_FLAGS: ${{env.CUSTOM_FLAGS}} + working-directory: software/o_c_REV/ + run: | + pio run -e custom + + - name: Copy artifact + uses: actions/upload-artifact@v3 + with: + name: o_C-${{env.OC_ARTIFACT_TAG}} + path: software/o_c_REV/.pio/build/*/*.hex + +# TODO: this will only work with Issues or Pull Requests... +# - name: Add follow-up comment to discussion +# uses: actions/github-script@v6 +# if: always() +# with: +# script: | +# const name = '${{ github.workflow }}'; +# const url = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'; +# const success = '${{ job.status }}' === 'success'; +# const body = `${name}: ${success ? 'succeeded ✅' : 'failed ❌'}\n${url}`; +# +# await github.rest.issues.createComment({ +# issue_number: context.issue.number, +# owner: context.repo.owner, +# repo: context.repo.repo, +# body: body +# }) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 7853d50e0..49e717b62 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -39,6 +39,12 @@ extra_scripts = pre:resources/progname.py upload_protocol = teensy-cli +[env:custom] +build_flags = + ${env.build_flags} + ${sysenv.CUSTOM_BUILD_FLAGS} + -DOC_VERSION_EXTRA="\"_custom\"" + [env:T4] build_flags = -DTEENSY_OPT_SMALLEST_CODE diff --git a/software/o_c_REV/resources/parse_build_request.py b/software/o_c_REV/resources/parse_build_request.py new file mode 100644 index 000000000..21e656114 --- /dev/null +++ b/software/o_c_REV/resources/parse_build_request.py @@ -0,0 +1,54 @@ +import os + +comment_text = os.environ['GH_COMMENT'] + +flags = comment_text.replace(',', ' ').replace(';', ' ').split() +custom_defines = "-DCUSTOM_BUILD" + +for item in flags: + f = item.strip().upper() + if f.startswith('CALIBR8'): + custom_defines += " -DENABLE_APP_CALIBR8OR" + if f.startswith('SCENE'): + custom_defines += " -DENABLE_APP_SCENES" + if f.startswith('ENIGMA'): + custom_defines += " -DENABLE_APP_ENIGMA" + if f.startswith('CAPTAIN') or f.startswith('MIDI'): + custom_defines += " -DENABLE_APP_MIDI" + if f.startswith('PIQUED'): + custom_defines += " -DENABLE_APP_PIQUED" + if f.startswith('QUADRAT'): + custom_defines += " -DENABLE_APP_POLYLFO" + if f.startswith('HARRING'): + custom_defines += " -DENABLE_APP_H1200" + if f.startswith('BYTE'): + custom_defines += " -DENABLE_APP_BYTEBEATGEN" + if f.startswith('NEURAL'): + custom_defines += " -DENABLE_APP_NEURAL_NETWORK" + if f.startswith('DARKEST'): + custom_defines += " -DENABLE_APP_DARKEST_TIMELINE" + if f.startswith('LOW-RENT'): + custom_defines += " -DENABLE_APP_LORENZ" + if f.startswith('COPIER'): + custom_defines += " -DENABLE_APP_ASR" + if f.startswith('QUANTER'): + custom_defines += " -DENABLE_APP_QUANTERMAIN" + if f.startswith('META'): + custom_defines += " -DENABLE_APP_METAQ" + if f.startswith('ACID'): + custom_defines += " -DENABLE_APP_CHORDS" + if f.startswith('PASSEN'): + custom_defines += " -DENABLE_APP_PASSENCORE" + if f.startswith('SEQUIN'): + custom_defines += " -DENABLE_APP_SEQUINS" + if f.startswith('AUTOMATON'): + custom_defines += " -DENABLE_APP_AUTOMATONNETZ" + if f.startswith('DIALECT') or f.startswith('BBGEN'): + custom_defines += " -DENABLE_APP_BBGEN" + if f.startswith('GRIDS2'): + custom_defines += " -DDRUMMAP_GRIDS2" + + #TODO proper lookup table + +print(custom_defines) +os.environ["CUSTOM_BUILD_FLAGS"] = custom_defines From 5044baf01d79f9b2630f51f06c2d93ef72a1275a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 17 Dec 2023 15:30:48 -0500 Subject: [PATCH 369/417] How could I forget Pong?! --- software/o_c_REV/resources/parse_build_request.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/software/o_c_REV/resources/parse_build_request.py b/software/o_c_REV/resources/parse_build_request.py index 21e656114..4d19d4f67 100644 --- a/software/o_c_REV/resources/parse_build_request.py +++ b/software/o_c_REV/resources/parse_build_request.py @@ -11,6 +11,8 @@ custom_defines += " -DENABLE_APP_CALIBR8OR" if f.startswith('SCENE'): custom_defines += " -DENABLE_APP_SCENES" + if f.startswith('PONG'): + custom_defines += " -DENABLE_APP_PONG" if f.startswith('ENIGMA'): custom_defines += " -DENABLE_APP_ENIGMA" if f.startswith('CAPTAIN') or f.startswith('MIDI'): From 370a40154ced73ee6d2f6696fb6e6909a68ba3d0 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 17 Dec 2023 15:46:11 -0500 Subject: [PATCH 370/417] Custom Build: handle VOR and FLIP_180 --- software/o_c_REV/resources/parse_build_request.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/software/o_c_REV/resources/parse_build_request.py b/software/o_c_REV/resources/parse_build_request.py index 4d19d4f67..1966c3649 100644 --- a/software/o_c_REV/resources/parse_build_request.py +++ b/software/o_c_REV/resources/parse_build_request.py @@ -7,6 +7,10 @@ for item in flags: f = item.strip().upper() + if f.startswith('FLIP'): + custom_defines += " -DFLIP_180" + if f.startswith('VOR'): + custom_defines += " -DVOR" if f.startswith('CALIBR8'): custom_defines += " -DENABLE_APP_CALIBR8OR" if f.startswith('SCENE'): From 136f49b0a4f53b5cb80ae0515148770a34db1f3b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Nov 2023 17:28:43 -0500 Subject: [PATCH 371/417] ResetClk: use dynamic clock pulse lengths --- software/o_c_REV/HEM_ResetClock.ino | 4 ++-- software/o_c_REV/HemisphereApplet.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/HEM_ResetClock.ino b/software/o_c_REV/HEM_ResetClock.ino index 306258a79..0443744f9 100644 --- a/software/o_c_REV/HEM_ResetClock.ino +++ b/software/o_c_REV/HEM_ResetClock.ino @@ -53,9 +53,9 @@ public: if (Clock(0)) pending_clocks++; if (pending_clocks && (ticks_since_clock > spacing * RC_TICKS_PER_MS)) { - ClockOut(0); + ClockOut(0, (spacing * RC_TICKS_PER_MS)/2); if (pending_clocks==1) { - ClockOut(1); + ClockOut(1, (spacing * RC_TICKS_PER_MS)/2); } ticks_since_clock = 0; position = (position + 1) % length; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 956202411..85a47b343 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -432,8 +432,8 @@ class HemisphereApplet { return clocked; } - void ClockOut(const int ch, const int ticks = HEMISPHERE_CLOCK_TICKS) { - frame.ClockOut( (DAC_CHANNEL)(io_offset + ch), ticks * trig_length); + void ClockOut(const int ch, const int ticks = HEMISPHERE_CLOCK_TICKS * trig_length) { + frame.ClockOut( (DAC_CHANNEL)(io_offset + ch), ticks); } bool Gate(int ch) { From 0e7c7359bb92e6216bf44394bb2343576f2e3dd8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 18 Nov 2023 17:35:58 -0500 Subject: [PATCH 372/417] Hemisphere Trig Length in milliseconds (approx) Previously was a multiple of approx 3ms, so this will make it even shorter... Anything less than 3 might not trigger all modules. --- software/o_c_REV/APP_HEMISPHERE.ino | 1 + software/o_c_REV/HemisphereApplet.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 02572826c..0cd7c9b16 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -554,6 +554,7 @@ private: gfxPrint(1, 35, "Trig Length: "); gfxPrint(HS::trig_length); + gfxPrint("ms"); const char * cursor_mode_name[3] = { "legacy", "modal", "modal+wrap" }; gfxPrint(1, 45, "Cursor: "); diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index 85a47b343..a8851652f 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -53,7 +53,7 @@ #define HEMISPHERE_3V_CV 4608 #define HEMISPHERE_MAX_INPUT_CV 9216 // 6V #define HEMISPHERE_CENTER_DETENT 80 -#define HEMISPHERE_CLOCK_TICKS 50 +#define HEMISPHERE_CLOCK_TICKS 17 // one millisecond #define HEMISPHERE_CURSOR_TICKS 12000 #define HEMISPHERE_ADC_LAG 33 #define HEMISPHERE_CHANGE_THRESHOLD 32 From 58d7743a8d5ebf25da74a2819a5c60f1746a3926 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 23 Nov 2023 23:18:56 -0500 Subject: [PATCH 373/417] New Screensavers in Hem Config: blank, Meters, Zaps, Stars saved with Clock Data in Preset --- software/o_c_REV/APP_HEMISPHERE.ino | 91 +++++++++++++++++++++++++++-- software/o_c_REV/HEM_ClockSetup.ino | 3 +- software/o_c_REV/HSIOFrame.h | 1 + 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 0cd7c9b16..8f505f144 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -490,6 +490,7 @@ private: enum HEMConfigCursor { LOAD_PRESET, SAVE_PRESET, TRIG_LENGTH, + SCREENSAVER_MODE, CURSOR_MODE, MAX_CURSOR = CURSOR_MODE @@ -503,11 +504,17 @@ private: return; } - if (config_cursor == TRIG_LENGTH) { + switch (config_cursor) { + case TRIG_LENGTH: HS::trig_length = (uint32_t) constrain( int(HS::trig_length + dir), 1, 127); - } - else if (config_cursor == SAVE_PRESET || config_cursor == LOAD_PRESET) { + break; + //case SCREENSAVER_MODE: + // TODO? + //break; + case SAVE_PRESET: + case LOAD_PRESET: preset_cursor = constrain(preset_cursor + dir, 1, HEM_NR_OF_PRESETS); + break; } } void ConfigButtonPush(int h) { @@ -534,6 +541,10 @@ private: isEditing = !isEditing; break; + case SCREENSAVER_MODE: + ++HS::screensaver_mode %= 4; + break; + case CURSOR_MODE: HS::CycleEditMode(); break; @@ -556,8 +567,12 @@ private: gfxPrint(HS::trig_length); gfxPrint("ms"); + const char * ssmodes[4] = { "[blank]", "Meters", "Zaps", "Stars" }; + gfxPrint(1, 45, "Screensaver: "); + gfxPrint( ssmodes[HS::screensaver_mode] ); + const char * cursor_mode_name[3] = { "legacy", "modal", "modal+wrap" }; - gfxPrint(1, 45, "Cursor: "); + gfxPrint(1, 55, "Cursor: "); gfxPrint(cursor_mode_name[HS::modal_edit_mode]); switch (config_cursor) { @@ -570,8 +585,11 @@ private: if (isEditing) gfxInvert(79, 34, 25, 9); else gfxCursor(80, 43, 24); break; + case SCREENSAVER_MODE: + gfxIcon(73, 45, RIGHT_ICON); + break; case CURSOR_MODE: - gfxIcon(43, 45, RIGHT_ICON); + gfxIcon(43, 55, RIGHT_ICON); break; } } @@ -681,8 +699,69 @@ void HEMISPHERE_menu() { manager.View(); } +typedef struct { + int x = 0; + int y = 0; + int x_v = 6; + int y_v = 3; + + void Move(bool stars) { + x += x_v; + y += y_v; + if (x > 12700 || x < 0 || y > 6300 || y < 0) { + if (stars) { + x = 6300; + y = 3100; + } else { + x = random(12700); + y = random(6300); + } + x_v = random(30) - 15; + y_v = random(30) - 15; + if (x_v == 0) ++x_v; + if (y_v == 0) ++y_v; + } + } +} Zap; +static constexpr int HOW_MANY_ZAPS = 25; +static Zap zaps[HOW_MANY_ZAPS]; +static void ZapScreensaver(const bool stars = false) { + static int frame_delay = 0; + for (int i = 0; i < (stars ? HOW_MANY_ZAPS : 5); i++) { + if (frame_delay & 0x1) + zaps[i].Move(stars); + + if (stars && frame_delay == 0) { + // accel + zaps[i].x_v *= 2; + zaps[i].y_v *= 2; + /* + zaps[i].x_v += zaps[i].x_v > 0 ? 1 : -1; + zaps[i].y_v += zaps[i].y_v > 0 ? 1 : -1; + */ + } + + if (stars) + gfxPixel(zaps[i].x/100, zaps[i].y/100); + else + gfxIcon(zaps[i].x/100, zaps[i].y/100, ZAP_ICON); + } + if (--frame_delay < 0) frame_delay = 55; +} + void HEMISPHERE_screensaver() { - manager.BaseScreensaver(true); // show note names + switch (HS::screensaver_mode) { + case 0x3: // Stars + ZapScreensaver(true); + break; + case 0x2: // Zaps + ZapScreensaver(); + break; + case 0x1: // Meters + manager.BaseScreensaver(true); // show note names + break; + default: break; // blank screen + } } void HEMISPHERE_handleButtonEvent(const UI::Event &event) { diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 18b242661..ba4a10633 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -181,7 +181,7 @@ public: Pack(data, PackLocation { 52, 7 }, HS::trig_length); // --- PackLocation { 59, 1 } --- Pack(data, PackLocation { 60, 2 }, HS::modal_edit_mode); - // --- PackLocation { 62, 2 } --- + Pack(data, PackLocation { 62, 2 }, HS::screensaver_mode); return data; } @@ -208,6 +208,7 @@ public: } HS::trig_length = constrain( Unpack(data, PackLocation { 52, 7 }), 1, 127); + HS::screensaver_mode = Unpack(data, PackLocation { 62, 2 }); } protected: diff --git a/software/o_c_REV/HSIOFrame.h b/software/o_c_REV/HSIOFrame.h index c62466158..3e95f76b6 100644 --- a/software/o_c_REV/HSIOFrame.h +++ b/software/o_c_REV/HSIOFrame.h @@ -15,6 +15,7 @@ int root_note[4]; uint8_t trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS int trigger_mapping[] = { 1, 2, 3, 4 }; +uint8_t screensaver_mode = 2; // 0 = blank, 1 = Meters, 2 = Zaps typedef struct MIDILogEntry { int message; From 4e216ff6de844362810ef3dd4dc8badf1bccf61f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 28 Nov 2023 02:56:20 -0500 Subject: [PATCH 374/417] Alt screensavers: Zips (T3) vs Stars (T4) Zips uses the "stars" more like fireflies --- software/o_c_REV/APP_HEMISPHERE.ino | 44 ++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 8f505f144..b0230e0e0 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -567,7 +567,13 @@ private: gfxPrint(HS::trig_length); gfxPrint("ms"); - const char * ssmodes[4] = { "[blank]", "Meters", "Zaps", "Stars" }; + const char * ssmodes[4] = { "[blank]", "Meters", "Zaps", + #if defined(__IMXRT1062__) + "Stars" + #else + "Zips" + #endif + }; gfxPrint(1, 45, "Screensaver: "); gfxPrint( ssmodes[HS::screensaver_mode] ); @@ -706,16 +712,23 @@ typedef struct { int y_v = 3; void Move(bool stars) { + if (stars) Move(6100, 2900); + else Move(); + } + void Move(int target_x = -1, int target_y = -1) { x += x_v; y += y_v; if (x > 12700 || x < 0 || y > 6300 || y < 0) { - if (stars) { - x = 6300; - y = 3100; - } else { + if (target_x < 0 || target_y < 0) { x = random(12700); y = random(6300); + } else { + x = target_x + random(400); + y = target_y + random(400); + CONSTRAIN(x, 0, 12700); + CONSTRAIN(y, 0, 6300); } + x_v = random(30) - 15; y_v = random(30) - 15; if (x_v == 0) ++x_v; @@ -723,22 +736,25 @@ typedef struct { } } } Zap; -static constexpr int HOW_MANY_ZAPS = 25; +static constexpr int HOW_MANY_ZAPS = 30; static Zap zaps[HOW_MANY_ZAPS]; static void ZapScreensaver(const bool stars = false) { static int frame_delay = 0; for (int i = 0; i < (stars ? HOW_MANY_ZAPS : 5); i++) { - if (frame_delay & 0x1) - zaps[i].Move(stars); + if (frame_delay & 0x1) { + #if defined(__IMXRT1062__) + zaps[i].Move(stars); // centered starfield + #else + // Zips respawn from their previous sibling + if (0 == i) zaps[0].Move(); + else zaps[i].Move(zaps[i-1].x, zaps[i-1].y); + #endif + } if (stars && frame_delay == 0) { // accel zaps[i].x_v *= 2; zaps[i].y_v *= 2; - /* - zaps[i].x_v += zaps[i].x_v > 0 ? 1 : -1; - zaps[i].y_v += zaps[i].y_v > 0 ? 1 : -1; - */ } if (stars) @@ -746,12 +762,12 @@ static void ZapScreensaver(const bool stars = false) { else gfxIcon(zaps[i].x/100, zaps[i].y/100, ZAP_ICON); } - if (--frame_delay < 0) frame_delay = 55; + if (--frame_delay < 0) frame_delay = 100; } void HEMISPHERE_screensaver() { switch (HS::screensaver_mode) { - case 0x3: // Stars + case 0x3: // Zips or Stars ZapScreensaver(true); break; case 0x2: // Zaps From b89f4c1cd841309d899d274649d82363c6b28a5f Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Fri, 24 Nov 2023 02:03:06 -0500 Subject: [PATCH 375/417] properly restore modal_edit_mode from Clock Data --- software/o_c_REV/HEM_ClockSetup.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index ba4a10633..bcae01d2c 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -197,6 +197,7 @@ public: clock_m->SetMultiply(Unpack(data, PackLocation { 16+i*6, 6 })-32, i); } + HS::modal_edit_mode = Unpack(data, PackLocation { 60, 2 }); for (size_t i = 0; i < 4; ++i) { uint8_t t = Unpack(data, PackLocation { 40+i*3, 3 }); if (t) HS::trigger_mapping[i] = t - 1; From 191f72d7f4243074c475c84ddc6ca823b28e0c4b Mon Sep 17 00:00:00 2001 From: zerbian Date: Sun, 5 Mar 2023 00:48:24 +0100 Subject: [PATCH 376/417] [MultiScale] add new Quantizer with multiple CV-selecable scales, default 4 scales --- software/o_c_REV/HEM_MultiScale.ino | 204 +++++++++++++++++++++++++++ software/o_c_REV/HSicons.h | 1 + software/o_c_REV/hemisphere_config.h | 1 + 3 files changed, 206 insertions(+) create mode 100644 software/o_c_REV/HEM_MultiScale.ino diff --git a/software/o_c_REV/HEM_MultiScale.ino b/software/o_c_REV/HEM_MultiScale.ino new file mode 100644 index 000000000..77573fadb --- /dev/null +++ b/software/o_c_REV/HEM_MultiScale.ino @@ -0,0 +1,204 @@ +// Copyright (c) 2023, Jakob Zerbian +// +// Based on Braids Quantizer, Copyright 2015 Émilie Gillet. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_scales.h" + +#define MS_QUANT_SCALES_COUNT 4 + +class MultiScale : public HemisphereApplet { +public: + + const char* applet_name() { + return "MultiScale"; + } + + void Start() { + // init all scales no all notes + for (uint8_t i = 0; i < MS_QUANT_SCALES_COUNT; i++) { + scale_mask[i] = 0x0001; + } + quantizer.Init(); + quantizer.Configure(OC::Scales::GetScale(5), scale_mask[0]); + } + + void Controller() { + + // user selected scale from input 2 + int scale = DetentedIn(1); + if (scale < 0) scale = 0; + if (scale > 0) { + scale = constrain(ProportionCV(scale, MS_QUANT_SCALES_COUNT + 1), 0, MS_QUANT_SCALES_COUNT); + } + if (scale != current_scale) { + current_scale = scale; + quantizer.Configure(OC::Scales::GetScale(5), scale_mask[current_scale]); + ClockOut(1); // send clock at second output + } + + // quantize notes from input 1, can be clocked + if (Clock(0)) { + continuous = false; + StartADCLag(0); + } + + if (continuous || EndOfADCLag(0)) { + + int32_t pitch = In(0); + int32_t quantized = quantizer.Process(pitch, 0, 0); + Out(0, quantized); + } + } + + void View() { + gfxHeader(applet_name()); + DrawKeyboard(); + DrawIndicators(); + } + + void OnButtonPress() { + if (cursor == 0) { // scale page selection mode + change_page = !change_page; + } else { // scale note edit mode + uint8_t bit = cursor - 1; + scale_mask[scale_page] ^= (0x01 << bit); // togle bit at position + if (scale_page == current_scale) { + quantizer.Configure(OC::Scales::GetScale(5), scale_mask[current_scale]); + } + } + + } + + void OnEncoderMove(int direction) { + if (change_page) { + // select page + scale_page = constrain(scale_page + direction, 0, MS_QUANT_SCALES_COUNT - 1); + } else { + // move cursor along the "keyboard" + cursor += direction; + if (cursor > 12) cursor = 0; + if (cursor < 0) cursor = 12; + } + ResetCursor(); + } + + uint32_t OnDataRequest() { + uint32_t data = 0; + Pack(data, PackLocation {0,12}, scale_mask[0]); + Pack(data, PackLocation {12,12}, scale_mask[1]); + Pack(data, PackLocation {24,8}, scale_mask[2]); + return data; + } + + void OnDataReceive(uint32_t data) { + scale_mask[0] = Unpack(data, PackLocation {0,12}); + scale_mask[1] = Unpack(data, PackLocation {12,12}); + scale_mask[2] = Unpack(data, PackLocation {24, 8}); + quantizer.Configure(OC::Scales::GetScale(5), scale_mask[0]); + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock"; + help[HEMISPHERE_HELP_CVS] = "1=CV 2=Scale"; + help[HEMISPHERE_HELP_OUTS] = "A=Pitch B=Gate"; + help[HEMISPHERE_HELP_ENCODER] = "Edit Scales"; + // "------------------" <-- Size Guide + } + +private: + braids::Quantizer quantizer; + uint16_t scale_mask[MS_QUANT_SCALES_COUNT]; + + bool continuous = true; + + int8_t current_scale = 0; + int8_t cursor = 0; + int8_t scale_page = 0; + bool change_page = 0; + + #define X_ 4 + + void DrawKeyboard() { + gfxFrame(4, 27, 56, 32); + + // white keys + for (uint8_t x = 0; x < 7; x++) { + gfxLine(x * 8 + 4, 27, x*8 + 4, 58); + } + + // black keys + for (uint8_t i = 0; i < 6; i++) { + if (i != 2) { + uint8_t x = (i * 8) + 10; + gfxRect(x, 27, 5, 16); + } + } + + gfxBitmap(1, 14, 8, PLAY_ICON); + gfxPrint(12, 15, current_scale); + + gfxBitmap(32, 14, 8, EDIT_ICON); + gfxPrint(43, 15, scale_page); + + } + + void DrawIndicators() { + if (cursor == 0) { + // draw cursor at scale page text + if (change_page) { + gfxInvert(31, 13, 20, 10); + } else { + gfxCursor(31, 23, 20); + } + } + uint8_t x[12] = {2, 7, 10, 15, 18, 26, 31, 34, 39, 42, 47, 50}; + uint8_t p[12] = {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0}; + for (uint8_t i = 0; i < 12; i++) { + if ((scale_mask[scale_page] >> i) & 0x01) gfxInvert(x[i] + 4, (p[i] ? 37 : 51), 4 - p[i], 4 - p[i]); + + if (i == (cursor - 1)) gfxCursor(x[i] + 3, p[i] ? 25 : 60, 6); + } + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to MultiScale, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +MultiScale MultiScale_instance[2]; + +void MultiScale_Start(bool hemisphere) {MultiScale_instance[hemisphere].BaseStart(hemisphere);} +void MultiScale_Controller(bool hemisphere, bool forwarding) {MultiScale_instance[hemisphere].BaseController(forwarding);} +void MultiScale_View(bool hemisphere) {MultiScale_instance[hemisphere].BaseView();} +void MultiScale_OnButtonPress(bool hemisphere) {MultiScale_instance[hemisphere].OnButtonPress();} +void MultiScale_OnEncoderMove(bool hemisphere, int direction) {MultiScale_instance[hemisphere].OnEncoderMove(direction);} +void MultiScale_ToggleHelpScreen(bool hemisphere) {MultiScale_instance[hemisphere].HelpScreen();} +uint32_t MultiScale_OnDataRequest(bool hemisphere) {return MultiScale_instance[hemisphere].OnDataRequest();} +void MultiScale_OnDataReceive(bool hemisphere, uint32_t data) {MultiScale_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/HSicons.h b/software/o_c_REV/HSicons.h index ef5d7db06..40cab47d1 100644 --- a/software/o_c_REV/HSicons.h +++ b/software/o_c_REV/HSicons.h @@ -63,6 +63,7 @@ const uint8_t PAUSE_ICON[8] = {0x00,0x7e,0x7e,0x00,0x00,0x7e,0x7e,0x00}; const uint8_t RESET_ICON[8] = {0x00,0x7e,0x00,0x00,0x18,0x3c,0x7e,0x00}; const uint8_t RECORD_ICON[8] = {0x00,0x3c,0x7e,0x7e,0x7e,0x7e,0x3c,0x00}; const uint8_t STOP_ICON[8] = {0x00,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x00}; +const uint8_t EDIT_ICON[8] = {0xc0,0xb0,0x48,0x44,0x22,0x15,0x0a,0x04}; // Direction Buttons const uint8_t UP_BTN_ICON[8] = {0x00,0x08,0x0c,0x0e,0x0e,0x0c,0x08,0x00}; diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 24e955902..d0684d23c 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -47,6 +47,7 @@ DECLARE_APPLET(150, 0x20, hMIDIIn), \ DECLARE_APPLET( 27, 0x20, hMIDIOut), \ DECLARE_APPLET( 33, 0x10, MixerBal), \ + DECLARE_APPLET( 72, 0x08, MultiScale), \ DECLARE_APPLET( 20, 0x02, Palimpsest), \ DECLARE_APPLET( 71, 0x02, Pigeons), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ From 03576a84f065b2d2742d423f56ce343b9aa4bf92 Mon Sep 17 00:00:00 2001 From: zerbian Date: Mon, 1 May 2023 12:45:48 +0200 Subject: [PATCH 377/417] [MultiScale] fix scale page selection --- software/o_c_REV/HEM_MultiScale.ino | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/software/o_c_REV/HEM_MultiScale.ino b/software/o_c_REV/HEM_MultiScale.ino index 77573fadb..976388481 100644 --- a/software/o_c_REV/HEM_MultiScale.ino +++ b/software/o_c_REV/HEM_MultiScale.ino @@ -48,7 +48,7 @@ public: int scale = DetentedIn(1); if (scale < 0) scale = 0; if (scale > 0) { - scale = constrain(ProportionCV(scale, MS_QUANT_SCALES_COUNT + 1), 0, MS_QUANT_SCALES_COUNT); + scale = constrain(ProportionCV(scale, MS_QUANT_SCALES_COUNT + 1), 0, MS_QUANT_SCALES_COUNT - 1); } if (scale != current_scale) { current_scale = scale; @@ -102,18 +102,20 @@ public: ResetCursor(); } - uint32_t OnDataRequest() { - uint32_t data = 0; - Pack(data, PackLocation {0,12}, scale_mask[0]); - Pack(data, PackLocation {12,12}, scale_mask[1]); - Pack(data, PackLocation {24,8}, scale_mask[2]); + uint64_t OnDataRequest() { + uint64_t data = 0; + Pack(data, PackLocation { 0, 12}, scale_mask[0]); + Pack(data, PackLocation {12, 12}, scale_mask[1]); + Pack(data, PackLocation {24, 12}, scale_mask[2]); + Pack(data, PackLocation {36, 12}, scale_mask[3]); return data; } - void OnDataReceive(uint32_t data) { - scale_mask[0] = Unpack(data, PackLocation {0,12}); - scale_mask[1] = Unpack(data, PackLocation {12,12}); - scale_mask[2] = Unpack(data, PackLocation {24, 8}); + void OnDataReceive(uint64_t data) { + scale_mask[0] = Unpack(data, PackLocation { 0, 12}); + scale_mask[1] = Unpack(data, PackLocation {12, 12}); + scale_mask[2] = Unpack(data, PackLocation {24, 12}); + scale_mask[3] = Unpack(data, PackLocation {36, 12}); quantizer.Configure(OC::Scales::GetScale(5), scale_mask[0]); } From a1e6b921970096f63725fb351126f516a4ea3e4c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 26 Nov 2023 16:44:56 -0500 Subject: [PATCH 378/417] MultiScale: tweaks, cleanup; adapt to new Quantizer API unclock when TR2 is gated TODO: merge with ScaleDuet; legacy cursor is broken --- software/o_c_REV/HEM_MultiScale.ino | 71 +++++++++++++--------------- software/o_c_REV/hemisphere_config.h | 2 +- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/software/o_c_REV/HEM_MultiScale.ino b/software/o_c_REV/HEM_MultiScale.ino index 976388481..bd5a44361 100644 --- a/software/o_c_REV/HEM_MultiScale.ino +++ b/software/o_c_REV/HEM_MultiScale.ino @@ -38,8 +38,7 @@ public: for (uint8_t i = 0; i < MS_QUANT_SCALES_COUNT; i++) { scale_mask[i] = 0x0001; } - quantizer.Init(); - quantizer.Configure(OC::Scales::GetScale(5), scale_mask[0]); + QuantizerConfigure(0, 5, scale_mask[0]); } void Controller() { @@ -52,7 +51,7 @@ public: } if (scale != current_scale) { current_scale = scale; - quantizer.Configure(OC::Scales::GetScale(5), scale_mask[current_scale]); + QuantizerConfigure(0, 5, scale_mask[current_scale]); ClockOut(1); // send clock at second output } @@ -62,44 +61,47 @@ public: StartADCLag(0); } - if (continuous || EndOfADCLag(0)) { + // Unclock + if (Gate(1)) { + continuous = true; + } - int32_t pitch = In(0); - int32_t quantized = quantizer.Process(pitch, 0, 0); + if (continuous || EndOfADCLag(0)) { + int32_t quantized = Quantize(0, In(0), 0, 0); Out(0, quantized); } } void View() { - gfxHeader(applet_name()); DrawKeyboard(); DrawIndicators(); } + void ToggleBit(const uint8_t bit) { + scale_mask[scale_page] ^= (0x01 << bit); // togle bit at position + if (scale_page == current_scale) { + QuantizerConfigure(0, 5, scale_mask[current_scale]); + } + } void OnButtonPress() { if (cursor == 0) { // scale page selection mode - change_page = !change_page; + CursorAction(cursor, 12); } else { // scale note edit mode - uint8_t bit = cursor - 1; - scale_mask[scale_page] ^= (0x01 << bit); // togle bit at position - if (scale_page == current_scale) { - quantizer.Configure(OC::Scales::GetScale(5), scale_mask[current_scale]); - } + const uint8_t bit = cursor - 1; + ToggleBit(bit); } } void OnEncoderMove(int direction) { - if (change_page) { - // select page - scale_page = constrain(scale_page + direction, 0, MS_QUANT_SCALES_COUNT - 1); - } else { + if (!EditMode()) { // move cursor along the "keyboard" - cursor += direction; - if (cursor > 12) cursor = 0; - if (cursor < 0) cursor = 12; + MoveCursor(cursor, direction, 12); + return; } - ResetCursor(); + + // select page + scale_page = constrain(scale_page + direction, 0, MS_QUANT_SCALES_COUNT - 1); } uint64_t OnDataRequest() { @@ -116,13 +118,13 @@ public: scale_mask[1] = Unpack(data, PackLocation {12, 12}); scale_mask[2] = Unpack(data, PackLocation {24, 12}); scale_mask[3] = Unpack(data, PackLocation {36, 12}); - quantizer.Configure(OC::Scales::GetScale(5), scale_mask[0]); + QuantizerConfigure(0, 5, scale_mask[0]); } protected: void SetHelp() { // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Clock"; + help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Unclock"; help[HEMISPHERE_HELP_CVS] = "1=CV 2=Scale"; help[HEMISPHERE_HELP_OUTS] = "A=Pitch B=Gate"; help[HEMISPHERE_HELP_ENCODER] = "Edit Scales"; @@ -130,18 +132,13 @@ protected: } private: - braids::Quantizer quantizer; - uint16_t scale_mask[MS_QUANT_SCALES_COUNT]; - + int cursor = 0; bool continuous = true; + uint16_t scale_mask[MS_QUANT_SCALES_COUNT]; int8_t current_scale = 0; - int8_t cursor = 0; int8_t scale_page = 0; - bool change_page = 0; - #define X_ 4 - void DrawKeyboard() { gfxFrame(4, 27, 56, 32); @@ -159,21 +156,17 @@ private: } gfxBitmap(1, 14, 8, PLAY_ICON); - gfxPrint(12, 15, current_scale); + gfxPrint(12, 15, current_scale + 1); gfxBitmap(32, 14, 8, EDIT_ICON); - gfxPrint(43, 15, scale_page); + gfxPrint(43, 15, scale_page + 1); } void DrawIndicators() { if (cursor == 0) { // draw cursor at scale page text - if (change_page) { - gfxInvert(31, 13, 20, 10); - } else { - gfxCursor(31, 23, 20); - } + gfxCursor(31, 23, 20); } uint8_t x[12] = {2, 7, 10, 15, 18, 26, 31, 34, 39, 42, 47, 50}; uint8_t p[12] = {0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0}; @@ -202,5 +195,5 @@ void MultiScale_View(bool hemisphere) {MultiScale_instance[hemisphere].BaseView( void MultiScale_OnButtonPress(bool hemisphere) {MultiScale_instance[hemisphere].OnButtonPress();} void MultiScale_OnEncoderMove(bool hemisphere, int direction) {MultiScale_instance[hemisphere].OnEncoderMove(direction);} void MultiScale_ToggleHelpScreen(bool hemisphere) {MultiScale_instance[hemisphere].HelpScreen();} -uint32_t MultiScale_OnDataRequest(bool hemisphere) {return MultiScale_instance[hemisphere].OnDataRequest();} -void MultiScale_OnDataReceive(bool hemisphere, uint32_t data) {MultiScale_instance[hemisphere].OnDataReceive(data);} +uint64_t MultiScale_OnDataRequest(bool hemisphere) {return MultiScale_instance[hemisphere].OnDataRequest();} +void MultiScale_OnDataReceive(bool hemisphere, uint64_t data) {MultiScale_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index d0684d23c..9b1b14e83 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -47,7 +47,7 @@ DECLARE_APPLET(150, 0x20, hMIDIIn), \ DECLARE_APPLET( 27, 0x20, hMIDIOut), \ DECLARE_APPLET( 33, 0x10, MixerBal), \ - DECLARE_APPLET( 72, 0x08, MultiScale), \ + DECLARE_APPLET( 73, 0x08, MultiScale), \ DECLARE_APPLET( 20, 0x02, Palimpsest), \ DECLARE_APPLET( 71, 0x02, Pigeons), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ From a734052e39a623ad41cb28e9d79178b1af379527 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sun, 30 May 2021 20:43:19 -0700 Subject: [PATCH 379/417] Passencore, a functional harmony-based chord sequencer. For now the best documentation is at https://llllllll.co/t/passencore-chord-ornament-music-theory-crime/45925 --- software/o_c_REV/APP_PASSENCORE.ino | 1089 ++++++++++++++++++++ software/o_c_REV/OC_apps.ino | 5 + software/o_c_REV/OC_scales.cpp | 14 + software/o_c_REV/OC_strings.cpp | 6 + software/o_c_REV/OC_strings.h | 4 + software/o_c_REV/braids_quantizer_scales.h | 14 + 6 files changed, 1132 insertions(+) create mode 100644 software/o_c_REV/APP_PASSENCORE.ino diff --git a/software/o_c_REV/APP_PASSENCORE.ino b/software/o_c_REV/APP_PASSENCORE.ino new file mode 100644 index 000000000..e4071fe9e --- /dev/null +++ b/software/o_c_REV/APP_PASSENCORE.ino @@ -0,0 +1,1089 @@ +#include + +#include "OC_apps.h" +#include "util/util_settings.h" +#include "util/util_trigger_delay.h" +#include "braids_quantizer.h" +#include "braids_quantizer_scales.h" +#include "OC_menus.h" +#include "OC_scales.h" +#include "OC_scale_edit.h" +#include "OC_strings.h" +#include "OC_chords.h" +#include "OC_chords_edit.h" +#include "OC_input_map.h" +#include "OC_input_maps.h" + +#define POSSIBLE_LEN 32 + +enum PASSENCORE_SETTINGS { + PASSENCORE_SETTING_SCALE, + PASSENCORE_SETTING_MASK, + PASSENCORE_SETTING_SAMPLE_TRIGGER, + PASSENCORE_SETTING_TARGET_TRIGGER, + PASSENCORE_SETTING_PASSING_TRIGGER, + PASSENCORE_SETTING_RESET_TRIGGER, + PASSENCORE_SETTING_A_OCTAVE, + PASSENCORE_SETTING_A_MIDRANGE, + PASSENCORE_SETTING_B_OCTAVE, + PASSENCORE_SETTING_B_MIDRANGE, + PASSENCORE_SETTING_C_OCTAVE, + PASSENCORE_SETTING_C_MIDRANGE, + PASSENCORE_SETTING_D_OCTAVE, + PASSENCORE_SETTING_D_MIDRANGE, + PASSENCORE_SETTING_BORROW_CHORDS, + PASSENCORE_SETTING_BASE_COLOR, + PASSENCORE_SETTING_CV3_ROLE, + PASSENCORE_SETTING_CV4_ROLE, + PASSENCORE_SETTING_LAST +}; + +enum PASSENCORE_FUNCTIONS { + PASSENCORE_FUNCTIONS_TONIC, + PASSENCORE_FUNCTIONS_TONIC_PREDOMINANT, + PASSENCORE_FUNCTIONS_PREDOMINANT, + PASSENCORE_FUNCTIONS_PREDOMINANT_DOMINANT, + PASSENCORE_FUNCTIONS_DOMINANT, + PASSENCORE_FUNCTIONS_MEDIANT, + PASSENCORE_FUNCTIONS_LAST, +}; + +enum PASSENCORE_COLORS { + PASSENCORE_COLORS_POWER, + PASSENCORE_COLORS_CLASSIC, + PASSENCORE_COLORS_EXTENDED, + PASSENCORE_COLORS_SUBSTITUTED, + PASSENCORE_COLORS_JAZZ, + PASSENCORE_COLORS_LAST, +}; + +const char* const passencore_color_names[] = {"power", "classic", "interesting", "ext/subst", "iono jazz?"}; + +enum PASSENCORE_CV_ROLES { + PASSENCORE_CV_ROLE_NONE, + PASSENCORE_CV_ROLE_INCLUDE, + PASSENCORE_CV_ROLE_ROOT, + PASSENCORE_CV_ROLE_BASS, + PASSENCORE_CV_ROLE_LAST, + // Future. + PASSENCORE_CV_ROLE_MELODY, + PASSENCORE_CV_ROLE_CHROMATIC_TRANSPOSE, +}; + +const char* const passencore_cv_role_names[] = {"-", "include", "root", "bass"}; + +enum PASSENCORE_MELODY_QUANT { + PASSENCORE_MELODY_QUANT_ACTIVE_SCALE, + PASSENCORE_MELODY_QUANT_TONIC_PENTATONIC, + PASSENCORE_MELODY_QUANT_CHORD_PENTATONIC, + PASSENCORE_MELODY_QUANT_MODAL_TRANSPOSE, + PASSENCORE_MELODY_QUANT_LAST, +}; + +PASSENCORE_FUNCTIONS PASSENCORE_FUNCTION_TABLE[] = { + PASSENCORE_FUNCTIONS_TONIC, + PASSENCORE_FUNCTIONS_PREDOMINANT_DOMINANT, + PASSENCORE_FUNCTIONS_MEDIANT, + PASSENCORE_FUNCTIONS_PREDOMINANT, + PASSENCORE_FUNCTIONS_DOMINANT, + PASSENCORE_FUNCTIONS_TONIC_PREDOMINANT, + PASSENCORE_FUNCTIONS_DOMINANT, +}; + +enum PASSENCORE_CHORD_TYPES { + CHORD_TYPES_NONE, + CHORD_TYPES_MAJOR, + CHORD_TYPES_MINOR, + CHORD_TYPES_DIMINISHED, + CHORD_TYPES_AUGMENTED, + CHORD_TYPES_LAST, +}; + + +struct PassenChord { + // 1-indexed scale degree + int8_t root; + // In order of importance to the chord. 1-indexed from the root, ex [0, 2, 4, 7] is a triad w/8ve + int8_t intervals[4]; + + int8_t accidental; + + // Attributes to score by; mutable. + PASSENCORE_FUNCTIONS function; + PASSENCORE_COLORS color; + + // Fitness; mutable; we sort by this. + int32_t score; + + int32_t samples[4]; + + PASSENCORE_CHORD_TYPES triad_type; + PASSENCORE_CHORD_TYPES seventh_type; + PASSENCORE_CHORD_TYPES ninth_type; + + void print() { + int acc = accidental; + CONSTRAIN(acc, -2, 2); + const char* accidental_part = OC::Strings::accidentals[2 + acc]; + int r = root; + CONSTRAIN(r, 0, 7); + const char* degree_part = (triad_type == CHORD_TYPES_MINOR || triad_type == CHORD_TYPES_DIMINISHED) ? OC::Strings::scale_degrees_min[root - 1] : OC::Strings::scale_degrees_maj[root - 1]; + char aug_dim = ' '; + if (triad_type == CHORD_TYPES_DIMINISHED) { + aug_dim = 0xb0; + } else if (triad_type == CHORD_TYPES_AUGMENTED) { + aug_dim = '+'; + } + graphics.printf("%s%s%c", accidental_part, degree_part, aug_dim); + if (triad_type == CHORD_TYPES_NONE) { + graphics.print("sus"); + } + switch (seventh_type) { + case CHORD_TYPES_NONE: + break; + case CHORD_TYPES_MINOR: + graphics.print("7"); + break; + case CHORD_TYPES_MAJOR: + graphics.print("maj7"); + break; + case CHORD_TYPES_DIMINISHED: + graphics.print("dim7"); + break; + case CHORD_TYPES_AUGMENTED: + graphics.print("aug7"); + break; + case CHORD_TYPES_LAST: + break; + } + switch (ninth_type) { + case CHORD_TYPES_NONE: + break; + case CHORD_TYPES_MINOR: + graphics.print("b9"); + break; + case CHORD_TYPES_MAJOR: + graphics.printf("add9"); + break; + case CHORD_TYPES_DIMINISHED: + graphics.printf("dim9"); + break; + case CHORD_TYPES_AUGMENTED: + graphics.printf("#9"); + break; + case CHORD_TYPES_LAST: + break; + } + } + + void print_notes() { + for (int i = 0; i < 4; i++) { + int tone = (samples[i]>>7)%12; + if (tone < 0) tone += 12; + int octave = (samples[i]>>7)/12; + graphics.print(OC::Strings::note_names_unpadded[tone]); + graphics.pretty_print(octave); + } + } +}; + + +class PASSENCORE : public settings::SettingsBase { + public: + int get_scale(uint8_t selected_scale_slot_) const { + return values_[PASSENCORE_SETTING_SCALE]; + } + uint16_t get_mask() const { + return values_[PASSENCORE_SETTING_MASK]; + } + + // Wrappers for ScaleEdit + void scale_changed() { + force_update_ = true; + } + + int get_root(uint8_t DUMMY) const { + return 0x0; + } + // dummy + int get_scale_select() const { + return 0; + } + + // dummy + void set_scale_at_slot(int scale, uint16_t mask, int root, int transpose, uint8_t scale_slot) { + + } + + // dummy + int get_transpose(uint8_t DUMMY) const { + return 0; + } + uint16_t get_scale_mask(uint8_t scale_select) const { + return get_mask(); + } + + void update_scale_mask(uint16_t mask, uint8_t scale_select) { + apply_value(CHORDS_SETTING_MASK, mask); + last_mask_ = mask; + force_update_ = true; + } + + void set_scale(int scale) { + + if (scale != get_scale(DUMMY)) { + const OC::Scale &scale_def = OC::Scales::GetScale(scale); + uint16_t mask = get_mask(); + if (0 == (mask & ~(0xffff << scale_def.num_notes))) + mask = 0xffff; + apply_value(PASSENCORE_SETTING_MASK, mask); + apply_value(PASSENCORE_SETTING_SCALE, scale); + } + } + + void Init() { + last_mask_ = 0; + last_scale_ = -1; + set_scale((int)OC::Scales::SCALE_SEMI + 1); + quantizer_.Init(); + update_scale(true, false); + // start the playing chord on somethig we can start leading pleasantly from + add_chord(5, 1, 3, 5, 8, PASSENCORE_FUNCTIONS_DOMINANT, PASSENCORE_COLORS_CLASSIC); + voice_basic(possibilities[0]); + soundingChord = targetChord = possibilities[0]; + } + + void ISR(); + + void Loop(); + + void Draw(); + + private: + braids::Quantizer quantizer_; + PassenChord possibilities[POSSIBLE_LEN]; + int8_t p_len; + PassenChord soundingChord; + PassenChord targetChord; + int8_t function; + int8_t color; + int last_scale_; + uint16_t last_mask_; + bool force_update_; + + bool calc_new_chord_; + bool play_target_; + bool play_passing_chord_; + bool reset_; + + int32_t sample_a, sample_b, sample_c, sample_d; + + void reset_possibilities() { + p_len = 0; + } + + void find_new_chord(); + void add_variants(); + void add_basic_triads(); + void take_top_four(); + void score_by_function(); + void score_by_root(); + void score_by_include(); + void score_by_bass(); + void score_by_color(); + void add_voicings(); + void score_voicings(); + void add_passing_chords(); + void score_passing_voicing(); + void find_passing_chord(); + int score_voicing(const PassenChord& from, const PassenChord& to); + void voice(PassenChord& target, const PassenChord& previous, uint8_t leading_mask, bool force_bass_movement, bool constant); + void voice_basic(PassenChord& target); + + void add_chord(int8_t root, int8_t i1, int8_t i2, int8_t i3, int8_t i4, PASSENCORE_FUNCTIONS function, PASSENCORE_COLORS color); + void play_chord(PassenChord& chord) { + for (int i = 0; i < 4; i++) { + OC::DAC::set_pitch((DAC_CHANNEL)i, chord.samples[i], 0); + } + soundingChord = chord; + } + + bool update_scale(bool force, int32_t mask_rotate) { + + force_update_ = false; + const int scale = get_scale(DUMMY); + uint16_t mask = get_mask(); + + if (mask_rotate) + mask = OC::ScaleEditor::RotateMask(mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); + + if (force || (last_scale_ != scale || last_mask_ != mask)) { + last_scale_ = scale; + last_mask_ = mask; + quantizer_.Configure(OC::Scales::GetScale(scale), mask); + return true; + } else { + return false; + } + } +}; + +// Returns the version of `sample` less than an octave above `comparison` +int32_t nearest_above(int32_t sample, int32_t comparison) { + int octaves_above = (sample - comparison) / (12 << 7); + return sample - octaves_above * (12 << 7); +} + +int32_t nearest_below(int32_t sample, int32_t comparison) { + int32_t above = nearest_above(sample, comparison); + if (above == sample) return above; + return above - (12 << 7); +} + +int32_t nearest_note(int32_t sample, int32_t comparison) { + int32_t above = nearest_above(sample, comparison); + int32_t below = nearest_below(sample, comparison); + return (abs(comparison - above) < abs(comparison - below)) ? above : below; +} + +void PASSENCORE::voice_basic(PassenChord& target) { + for (int i = 0; i < 4; i++) { + target.samples[i] = nearest_note( + target.samples[i], + (values_[PASSENCORE_SETTING_A_OCTAVE + 2 * i] + values_[PASSENCORE_SETTING_A_MIDRANGE + 2 * i]) << 7 + ); + } +} + +// Voice the target with reference to previous, using the leading mask to determine whether +// the voices should go up or down from previous - 0 is down, 1 is up, lsb is bass. +void PASSENCORE::voice(PassenChord& target, const PassenChord& previous, uint8_t leading_mask, bool force_bass_movement, bool constant) { + for (int v = 0; v < 4; v++) { + bool up = (leading_mask >> v) & 0x1; + // Look at voices we have not assigned yet, find the one that's nearest in the given direction to the same voice in the prev. chord. + int32_t best_score = 12 << 7; + int best_p = 0; + int32_t note, best_note = 0; + for (int p = v; constant ? (p == v) : (p < 4); p++) { + if (constant) { + note = nearest_note(target.samples[p], previous.samples[v]); + } else if (up) { + note = nearest_above(target.samples[p], previous.samples[v]); + } else { + note = nearest_below(target.samples[p], previous.samples[v]); + } + int score = abs(note - previous.samples[v]); + if (v == 0 && force_bass_movement && score == 0) score = (12 << 7); + if (score < best_score) { + best_score = score; + best_p = p; + best_note = note; + } + } + // Now swap the best note into place. + target.samples[best_p] = target.samples[v]; + target.samples[v] = best_note; + std::swap(target.intervals[best_p], target.intervals[v]); + } +} + +uint8_t PASSENCORE_VOICINGS[] = {0b0011, 0b1100, 0b0101, 0b1010, 0b1001, 0b0110}; + +void PASSENCORE::add_voicings() { + int len = p_len; + for (int i = 0; i < len; i++) { + for (int j = 0; j < 5; j++) { + if (p_len >= POSSIBLE_LEN) break; + possibilities[p_len] = possibilities[i]; + voice(possibilities[p_len], soundingChord, PASSENCORE_VOICINGS[j], j > 2, false); + p_len++; + } + // voice the orig. + voice(possibilities[i], soundingChord, PASSENCORE_VOICINGS[5], true, true); + } +} + +int PASSENCORE::score_voicing(const PassenChord& from, const PassenChord& to) { + int score = 8; + int held = 0; + bool prefer_root = false; + int32_t last_sample = -4 * (12 << 7); + for (int v = 0; v < 4; v++) { + if (to.intervals[v] > 8) { + prefer_root = true; + } + int movement = (abs(to.samples[v] - from.samples[v])) >> 7; + if (movement < 4) { + score += 2; + } + // We like staying constant and half-steps + if (movement == 0) { + score += 1; + held += 1; + } + if (movement == 1) { + score += 2; + } + if (v == 1 || v == 2) { + if (movement > 4) score -= 1; + if (movement > 8) score -= (movement - 8); + } else { + // Soprano and Bass can move more + if (movement > 7) score -= 1; + if (movement > 12) score -= (movement - 12); + } + // Gently keep our voices in range. + int32_t octave = values_[PASSENCORE_SETTING_A_OCTAVE + 2 * v]; + int32_t midrange = values_[PASSENCORE_SETTING_A_MIDRANGE + 2 * v]; + int32_t center = (12 * octave + midrange); + int32_t distance = abs((to.samples[v] >> 7) - center); + + if (distance > 8) score -= (distance - 8); + if (distance > 12) score -= (distance - 12); + + // penalize neighboring half-steps in the same chord. This should eliminate "false" sus chords. + if ((abs(last_sample - to.samples[v]) >> 7) == 1) { + score -= 10; + } + last_sample = to.samples[v]; + } + // penalize holding the same chord by the voicing score it got for holding those notes. + if (held == 4) { + score -= 12; + } + if (to.intervals[0] > 5) { + // avoid third inversions. + score -= 3; + } + if (to.intervals[0] == 1) { + // prefer root position, but not strongly + // score += 1; + } else if (prefer_root) { + score -= 3; + } + return score; +} + +void PASSENCORE::score_voicings() { + for (int c = 0; c < p_len; c++) { + int score = score_voicing(soundingChord, possibilities[c]); + possibilities[c].score *= score; + } +} + +void PASSENCORE::Draw() { + graphics.setPrintPos(0, 0); + for (int i = 0; i <= function; i++) { + graphics.print("**"); + } + graphics.setPrintPos(20, 10); + soundingChord.print(); + graphics.setPrintPos(20, 20); + soundingChord.print_notes(); + graphics.setPrintPos(50, 30); + graphics.print("to"); + graphics.setPrintPos(20, 40); + targetChord.print(); + graphics.setPrintPos(50, 50); + graphics.print(passencore_color_names[color]); + /* + graphics.setPrintPos(0, 0); + graphics.print("r"); + graphics.pretty_print(possibilities[0].root); + graphics.print("f"); + graphics.pretty_print(function); + graphics.print("c"); + graphics.pretty_print(color); + graphics.pretty_print(p_len); + graphics.setPrintPos(0, 10); + graphics.print("r"); + graphics.pretty_print(soundingChord.root); + //graphics.print("f"); + //graphics.pretty_print(sounding_chord.function); + graphics.print("c"); + graphics.pretty_print(soundingChord.color); + graphics.pretty_print(soundingChord.intervals[0]); + graphics.pretty_print(soundingChord.intervals[1]); + graphics.pretty_print(soundingChord.intervals[2]); + graphics.pretty_print(soundingChord.intervals[3]); + graphics.print("s"); + graphics.pretty_print(soundingChord.score); + for (int i = 1; i < 5; i++) { + graphics.setPrintPos(0, (i + 1) * 10); + graphics.print("r"); + graphics.pretty_print(possibilities[i].root); + //graphics.print("f"); + //graphics.pretty_print(possibilities[i].function); + graphics.print("c"); + graphics.pretty_print(possibilities[i].color); + graphics.pretty_print(possibilities[i].intervals[0]); + graphics.pretty_print(possibilities[i].intervals[1]); + graphics.pretty_print(possibilities[i].intervals[2]); + graphics.pretty_print(possibilities[i].intervals[3]); + graphics.print("s"); + graphics.pretty_print(possibilities[i].score); + + } + */ +} + +void PASSENCORE::Loop() { +} + +void PASSENCORE::find_passing_chord() { + add_passing_chords(); + for (int i = 0; i < p_len; i++) { + possibilities[i].score = 1; + } + score_by_color(); + take_top_four(); + add_voicings(); + score_passing_voicing(); + take_top_four(); +} + +void PASSENCORE::score_by_root() { + bool root = false; + int root_sample = 0; + + if (values_[PASSENCORE_SETTING_CV3_ROLE] == PASSENCORE_CV_ROLE_ROOT) { + root = true; + root_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_3), 0, 0)); + } else if (values_[PASSENCORE_SETTING_CV4_ROLE] == PASSENCORE_CV_ROLE_ROOT) { + root = true; + root_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_4), 0, 0)); + } + + for (int i = 0; i < p_len; i++) { + if (root) { + if (chromatic_tone(possibilities[i].samples[0]) == root_sample) { + possibilities[i].score += 1; + } else { + possibilities[i].score = 0; + } + } + } +} + + +int chromatic_tone(int32_t sample) { + int ret = (sample>>7)%12; + if (ret < 0) { + return ret + 12; + } + return ret; +} + +void PASSENCORE::score_by_bass() { + bool bass = false; + int bass_sample = 0; + + if (values_[PASSENCORE_SETTING_CV3_ROLE] == PASSENCORE_CV_ROLE_BASS) { + bass = true; + bass_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_3), 0, 0)); + } else if (values_[PASSENCORE_SETTING_CV4_ROLE] == PASSENCORE_CV_ROLE_BASS) { + bass = true; + bass_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_4), 0, 0)); + } + + for (int i = 0; i < p_len; i++) { + if (bass) { + if (chromatic_tone(possibilities[i].samples[0]) == bass_sample) { + possibilities[i].score += 1; + } else { + possibilities[i].score = 0; + } + } + } +} + +void PASSENCORE::score_by_include() { + bool include = false; + int include_sample = 0; + if (values_[PASSENCORE_SETTING_CV3_ROLE] == PASSENCORE_CV_ROLE_INCLUDE) { + include = true; + include_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_3), 0, 0)); + } else if (values_[PASSENCORE_SETTING_CV4_ROLE] == PASSENCORE_CV_ROLE_INCLUDE) { + include = true; + include_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_4), 0, 0)); + } else if (values_[PASSENCORE_SETTING_CV3_ROLE] == PASSENCORE_CV_ROLE_BASS) { + include = true; + include_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_3), 0, 0)); + } else if (values_[PASSENCORE_SETTING_CV4_ROLE] == PASSENCORE_CV_ROLE_BASS) { + include = true; + include_sample = chromatic_tone(quantizer_.Process(OC::ADC::raw_pitch_value(ADC_CHANNEL_4), 0, 0)); + } + + for (int i = 0; i < p_len; i++) { + bool found = false; + if (include) { + for (int j = 0; j < 4; j++) { + if (chromatic_tone(possibilities[i].samples[j]) == include_sample) { + possibilities[i].score += 1; + found = true; + } + } + if (!found) { + possibilities[i].score = 0; + } + } + } +} + + +void PASSENCORE::find_new_chord() { + function = (int)((OC::ADC::value((ADC_CHANNEL)0) + 128) >> 9); + CONSTRAIN(function, 0, PASSENCORE_FUNCTIONS_MEDIANT - 1); + color = values_[PASSENCORE_SETTING_BASE_COLOR] + (int)((OC::ADC::value((ADC_CHANNEL)1) + 255) >> 9); + CONSTRAIN(color, 0, PASSENCORE_COLORS_LAST - 1); + + reset_possibilities(); + add_basic_triads(); + score_by_function(); + score_by_root(); + take_top_four(); + add_variants(); + score_by_function(); + score_by_root(); + score_by_color(); + score_by_include(); + if (reset_) { + score_by_bass(); + take_top_four(); + voice_basic(possibilities[0]); + reset_ = false; + } else { + take_top_four(); + add_voicings(); + score_voicings(); + score_by_bass(); + take_top_four(); + } + targetChord = possibilities[0]; +} + +void PASSENCORE::add_basic_triads() { + for (int degree = 1; degree < 8; degree++) { + add_chord(degree, 1, 3, 5, 8, PASSENCORE_FUNCTION_TABLE[degree - 1], PASSENCORE_COLORS_CLASSIC); + } +} + +void PASSENCORE::score_passing_voicing() { + + for (int i = 0; i < p_len; i++) { + int to_passing = score_voicing(soundingChord, possibilities[i]); + if (to_passing < 1) { + to_passing = 1; + } + int from_passing = score_voicing(possibilities[i], targetChord); + // Slightly nudge away from the same root as the target chord, since otherwise we'll only play sus chords as passing chords, they lead so well. + if (possibilities[i].root == targetChord.root) { + from_passing -= 2; + } + if (from_passing < 1) { + from_passing = 1; + } + possibilities[i].score *= (to_passing * from_passing); + } +} + +void PASSENCORE::add_passing_chords() { + p_len = 1; + // sus variants of target, but at lower color + add_chord(targetChord.root, 1, 2, 5, 8, targetChord.function, PASSENCORE_COLORS_CLASSIC); + add_chord(targetChord.root, 1, 4, 5, 8, targetChord.function, PASSENCORE_COLORS_CLASSIC); + add_chord(targetChord.root, 1, 3, 7, 9, targetChord.function, PASSENCORE_COLORS_EXTENDED); + add_chord(targetChord.root, 1, 4, 7, 5, targetChord.function, PASSENCORE_COLORS_EXTENDED); + // variants of the secondary dominant + int secondary_dominant = (targetChord.root + 3) % 7 + 1; + add_chord(secondary_dominant, 1, 3, 5, 8, PASSENCORE_FUNCTIONS_DOMINANT, PASSENCORE_COLORS_CLASSIC); + add_chord(secondary_dominant, 1, 3, 5, 7, PASSENCORE_FUNCTIONS_DOMINANT, PASSENCORE_COLORS_EXTENDED); + add_chord(secondary_dominant, 1, 4, 9, 7, PASSENCORE_FUNCTIONS_DOMINANT, PASSENCORE_COLORS_SUBSTITUTED); + // Stepwise passing chord + if (abs(soundingChord.root - targetChord.root) == 2) { + add_chord((soundingChord.root + targetChord.root) / 2, 1, 3, 7, 5, PASSENCORE_FUNCTIONS_PREDOMINANT, PASSENCORE_COLORS_EXTENDED); + add_chord((soundingChord.root + targetChord.root) / 2, 1, 3, 5, 8, PASSENCORE_FUNCTIONS_PREDOMINANT, PASSENCORE_COLORS_CLASSIC); + } else if (values_[PASSENCORE_SETTING_BORROW_CHORDS] && abs(soundingChord.samples[0] - targetChord.samples[0]) == (2 << 7)) { + int increment = (targetChord.samples[0] - soundingChord.samples[0]) / 2; + possibilities[p_len] = soundingChord; + possibilities[p_len].color = PASSENCORE_COLORS_JAZZ; + possibilities[p_len].accidental = (increment >> 7); + for (int i = 0; i < 4; i++) { + possibilities[p_len].samples[i] += increment; + } + p_len++; + } + // Neighbor of the secondary dominant + int neighbor_dominant = (secondary_dominant + 4) % 7 + 1; + add_chord(neighbor_dominant, 1, 3, 5, 8, PASSENCORE_FUNCTIONS_PREDOMINANT_DOMINANT, PASSENCORE_COLORS_CLASSIC); + add_chord(neighbor_dominant, 1, 3, 7, 5, PASSENCORE_FUNCTIONS_PREDOMINANT_DOMINANT, PASSENCORE_COLORS_SUBSTITUTED); +} + +void PASSENCORE::add_variants() { + int len = p_len; + for (int i = 0; i < len; i++) { + // Power chord + add_chord(possibilities[i].root, 1, 5, 8, 12, possibilities[i].function, PASSENCORE_COLORS_POWER); + // Don't resolve to a sus chord here; tonic sus chords can go as passing chords though + if (possibilities[i].root != 1) { + //Sus 2 + add_chord(possibilities[i].root, 1, 2, 5, 8, possibilities[i].function, PASSENCORE_COLORS_SUBSTITUTED); + // Sus 4 + add_chord(possibilities[i].root, 1, 4, 5, 8, possibilities[i].function, PASSENCORE_COLORS_SUBSTITUTED); + } + // 7th + add_chord(possibilities[i].root, 1, 3, 7, 5, possibilities[i].function, PASSENCORE_COLORS_EXTENDED); + // 7th+9 + add_chord(possibilities[i].root, 1, 3, 7, 9, possibilities[i].function, PASSENCORE_COLORS_JAZZ); + // 7th Sus 4 + add_chord(possibilities[i].root, 1, 4, 7, 5, possibilities[i].function, PASSENCORE_COLORS_JAZZ); + + if (possibilities[i].function == PASSENCORE_FUNCTIONS_DOMINANT) { + // Dominant 9 sus 4 + add_chord(possibilities[i].root, 1, 4, 9, 7, possibilities[i].function, PASSENCORE_COLORS_JAZZ); + } + } +} + + +void PASSENCORE::take_top_four() { + std::sort(possibilities, possibilities + p_len, [](PassenChord lhs, PassenChord rhs) { + return lhs.score > rhs.score; + }); + p_len = 4; +} + +void PASSENCORE::score_by_function() { + for (int c = 0; c < p_len; c++) { + int function_score = 2 - abs(function - possibilities[c].function); + // Major chords lead to the tonic better in both major (V) and minor (VII). + if (function == PASSENCORE_FUNCTIONS_DOMINANT && possibilities[c].triad_type == CHORD_TYPES_MAJOR) { + function_score++; + // Extra bonus function score for dominant 7ths + if (possibilities[c].seventh_type == CHORD_TYPES_MINOR) { + function_score++; + } + } + if (function_score < 0) { + function_score = 0; + } else if (function_score > 0) { + // Increase it a little so it won't have such a huge effect when we multiply by it later. + function_score += 2; + } + possibilities[c].score = function_score; + } +} + +void PASSENCORE::score_by_color() { + for (int c = 0; c < p_len; c++) { + int function_score = possibilities[c].score; + // Chords with some resemblance to the right function gain a color bonus as valid substitutions. + int color_modifier = (function_score > 0 && function_score < 3 && possibilities[c].color > 0) ? 2 : 0; + // Diminished and augmented chords are weird and only deserve to be played at high color. + if (possibilities[c].triad_type == CHORD_TYPES_DIMINISHED || possibilities[c].triad_type == CHORD_TYPES_AUGMENTED) { + color_modifier += 2; + } + int color_score = 3 - abs(color - (possibilities[c].color + color_modifier)); + if (color_score > 0) { + color_score += 1; + } else if (color_score < 0) { + // Increase it a little so it won't have such a huge effect when we multiply by it later. + color_score = 1; + } + possibilities[c].score *= color_score; + } +} + + +void PASSENCORE::ISR() { + // value() + uint32_t triggers = OC::DigitalInputs::clocked(); + calc_new_chord_ = triggers & DIGITAL_INPUT_MASK(values_[PASSENCORE_SETTING_SAMPLE_TRIGGER]); + play_target_ = triggers & DIGITAL_INPUT_MASK(values_[PASSENCORE_SETTING_TARGET_TRIGGER]); + play_passing_chord_ = triggers & DIGITAL_INPUT_MASK(values_[PASSENCORE_SETTING_PASSING_TRIGGER]); + reset_ = triggers & DIGITAL_INPUT_MASK(values_[PASSENCORE_SETTING_RESET_TRIGGER]); + update_scale(force_update_, false); + if (calc_new_chord_) { + find_new_chord(); + calc_new_chord_ = false; + } + if (play_passing_chord_) { + find_passing_chord(); + play_chord(possibilities[0]); + play_passing_chord_ = false; + } + if (play_target_) { + play_chord(targetChord); + play_target_ = false; + } +} + +void PASSENCORE::add_chord(int8_t root, int8_t i1, int8_t i2, int8_t i3, int8_t i4, PASSENCORE_FUNCTIONS f, PASSENCORE_COLORS c) { + if (p_len >= POSSIBLE_LEN) return; + possibilities[p_len].root = root; + possibilities[p_len].intervals[0] = i1; + possibilities[p_len].intervals[1] = i2; + possibilities[p_len].intervals[2] = i3; + possibilities[p_len].intervals[3] = i4; + possibilities[p_len].function = f; + possibilities[p_len].color = c; + possibilities[p_len].triad_type = CHORD_TYPES_NONE; + possibilities[p_len].seventh_type = CHORD_TYPES_NONE; + possibilities[p_len].ninth_type = CHORD_TYPES_NONE; + + int32_t root_note = quantizer_.Process(0, 0, root - 1); + int third = -1; + int seventh = -1; + bool borrow_dominant = values_[PASSENCORE_SETTING_BORROW_CHORDS] && f == PASSENCORE_FUNCTIONS_DOMINANT && (root == 5 || color >= PASSENCORE_COLORS_SUBSTITUTED); + + for (int i = 0; i < 4; i++) { + int32_t note = quantizer_.Process(0, 0, root + possibilities[p_len].intervals[i] - 2); + possibilities[p_len].samples[i] = note; + int halfsteps = ((note - root_note) >> 7); + switch (halfsteps) { + case 0: break; + case 1: + break; + case 2: break; + case 3: + third = i; + possibilities[p_len].triad_type = CHORD_TYPES_MINOR; + break; + case 4: + third = i; + possibilities[p_len].triad_type = CHORD_TYPES_MAJOR; + break; + case 5: break; + case 6: + possibilities[p_len].triad_type = CHORD_TYPES_DIMINISHED; + break; + case 7: break; + case 8: + possibilities[p_len].triad_type = CHORD_TYPES_AUGMENTED; + break; + case 9: + seventh = i; + possibilities[p_len].seventh_type = CHORD_TYPES_DIMINISHED; + break; + case 10: + seventh = i; + possibilities[p_len].seventh_type = CHORD_TYPES_MINOR; + break; + case 11: + seventh = i; + possibilities[p_len].seventh_type = CHORD_TYPES_MAJOR; + break; + case 12: break; + + case 13: + possibilities[p_len].ninth_type = CHORD_TYPES_MINOR; + break; + case 14: + possibilities[p_len].ninth_type = CHORD_TYPES_MAJOR; + break; + } + } + if (borrow_dominant) { + if (third >= 0 && possibilities[p_len].triad_type == CHORD_TYPES_MINOR) { + // Raise the minor third to major + possibilities[p_len].samples[third] += (1 << 7); + possibilities[p_len].triad_type = CHORD_TYPES_MAJOR; + } + if (seventh >= 0 && possibilities[p_len].seventh_type == CHORD_TYPES_MAJOR) { + possibilities[p_len].samples[seventh] -= (1 << 7); + } + } + p_len++; +} + +class PassencoreState { + public: + void Init() { + cursor.Init(PASSENCORE_SETTING_SCALE, PASSENCORE_SETTING_LAST - 1); + scale_editor.Init(false); + //chord_editor.Init(); + left_encoder_value = OC::Scales::SCALE_SEMI + 1; + } + + inline bool editing() const { + return cursor.editing(); + } + + inline int cursor_pos() const { + return cursor.cursor_pos(); + } + + menu::ScreenCursor cursor; + OC::ScaleEditor scale_editor; + //OC::ChordEditor chord_editor; + int left_encoder_value; +}; + + +SETTINGS_DECLARE(PASSENCORE, PASSENCORE_SETTING_LAST) { + //PASSENCORE_SETTING_SCALE, + { OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 14, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + //PASSENCORE_SETTING_MASK, + { 65535, 1, 65535, "scale -->", NULL, settings::STORAGE_TYPE_U16 }, // mask + //PASSENCORE_SETTING_SAMPLE_TRIGGER, + { 0, 0, 3, "sample trigger", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U4}, + //PASSENCORE_SETTING_TARGET_TRIGGER, + { 0, 0, 3, "target trigger", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U4}, + //PASSENCORE_SETTING_PASSING_TRIGGER, + { 2, 0, 3, "passing trigger", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U4}, + //PASSENCORE_SETTING_RESET_TRIGGER, + { 3, 0, 3, "reset trigger", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U4}, + //PASSENCORE_SETTING_A_OCTAVE, + { -1, -2, 3, "A octave", NULL, settings::STORAGE_TYPE_I8 }, + //PASSENCORE_SETTING_A_MIDRANGE, + { 0, -6, 6, "A mid range", NULL, settings::STORAGE_TYPE_I8}, + //PASSENCORE_SETTING_B_OCTAVE, + { -1, -2, 3, "B octave", NULL, settings::STORAGE_TYPE_I8 }, + //PASSENCORE_SETTING_B_MIDRANGE, + { 0, -6, 6, "B mid range", NULL, settings::STORAGE_TYPE_I8}, + //PASSENCORE_SETTING_C_OCTAVE, + { -1, -2, 3, "C octave", NULL, settings::STORAGE_TYPE_I8 }, + //PASSENCORE_SETTING_C_MIDRANGE, + { 0, -6, 6, "C mid range", NULL, settings::STORAGE_TYPE_I8}, + //PASSENCORE_SETTING_D_OCTAVE, + { -1, -2, 3, "D octave", NULL, settings::STORAGE_TYPE_I8 }, + //PASSENCORE_SETTING_D_MIDRANGE, + { 0, -6, 6, "D mid range", NULL, settings::STORAGE_TYPE_I8}, + //PASSENCORE_SETTING_BORROW_CHORDS, + { 1, 0, 1, "Borrow chords?", OC::Strings::no_yes, settings::STORAGE_TYPE_I8}, + // PASSENCORE_SETTING_BASE_COLOR, + { PASSENCORE_COLORS_CLASSIC, PASSENCORE_COLORS_POWER, PASSENCORE_COLORS_JAZZ, "color", passencore_color_names, settings::STORAGE_TYPE_I8}, + // PASSENCORE_SETTING_CV3_ROLE + { PASSENCORE_CV_ROLE_NONE, PASSENCORE_CV_ROLE_NONE, PASSENCORE_CV_ROLE_LAST - 1, "CV3", passencore_cv_role_names, settings::STORAGE_TYPE_I8}, + // PASSENCORE_SETTING_CV4_ROLE + + { PASSENCORE_CV_ROLE_NONE, PASSENCORE_CV_ROLE_NONE, PASSENCORE_CV_ROLE_LAST - 1, "CV4", passencore_cv_role_names, settings::STORAGE_TYPE_I8}, + +}; + +PassencoreState passencore_state; +PASSENCORE passencore_instance; + +void PASSENCORE_init() { + passencore_instance.Init(); + passencore_state.Init(); +} + +size_t PASSENCORE_storageSize() { + return PASSENCORE::storageSize(); +} + +size_t PASSENCORE_save(void *storage) { + return passencore_instance.Save(storage); +} + +void PASSENCORE_isr() { + return passencore_instance.ISR(); +} + +void PASSENCORE_leftButton() { + + if (passencore_state.left_encoder_value != passencore_instance.get_scale(DUMMY)) { + passencore_instance.set_scale(passencore_state.left_encoder_value); + } +} + + +void PASSENCORE_handleAppEvent(OC::AppEvent event) { + switch (event) { + case OC::APP_EVENT_RESUME: + passencore_state.cursor.set_editing(false); + passencore_state.scale_editor.Close(); + //passencore_state.chord_editor.Close(); + break; + case OC::APP_EVENT_SUSPEND: + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SCREENSAVER_OFF: + break; + } +} + +void PASSENCORE_loop() { + passencore_instance.Loop(); +} + +void PASSENCORE_menu() { + menu::TitleBar<0, 4, 0>::Draw(); + + // print scale + int scale = passencore_state.left_encoder_value; + graphics.movePrintPos(5, 0); + graphics.print(OC::scale_names[scale]); + if (passencore_instance.get_scale(DUMMY) == scale) + graphics.drawBitmap8(1, menu::QuadTitleBar::kTextY, 4, OC::bitmap_indicator_4x8); + menu::SettingsList < menu::kScreenLines, 0, menu::kDefaultValueX - 1 > settings_list(passencore_state.cursor); + menu::SettingsListItem list_item; + while (settings_list.available()) + { + const int current = settings_list.Next(list_item); + const int value = passencore_instance.get_value(current); + list_item.DrawDefault(value, PASSENCORE::value_attr(current)); + } +} + +void PASSENCORE_screensaver() { + passencore_instance.Draw(); +} + +void PASSENCORE_handleButtonEvent(const UI::Event & event) { + if (passencore_state.scale_editor.active()) { + passencore_state.scale_editor.HandleButtonEvent(event); + return; + } + //else if (passencore_state.chord_editor.active()) { + // passencore_state.chord_editor.HandleButtonEvent(event); + // return; + //} + if (UI::EVENT_BUTTON_PRESS == event.type) { + switch (event.control) { + case OC::CONTROL_BUTTON_UP: + //PASSENCORE_topButton(); + break; + case OC::CONTROL_BUTTON_DOWN: + //PASSENCORE_lowerButton(); + break; + case OC::CONTROL_BUTTON_L: + PASSENCORE_leftButton(); + break; + case OC::CONTROL_BUTTON_R: + passencore_state.cursor.toggle_editing(); + break; + } + } +} + +void PASSENCORE_handleEncoderEvent(const UI::Event & event) { + if (passencore_state.scale_editor.active()) { + passencore_state.scale_editor.HandleEncoderEvent(event); + return; + } + //else if (passencore_state.chord_editor.active()) { + // passencore_state.chord_editor.HandleEncoderEvent(event); + // return; + //} + if (OC::CONTROL_ENCODER_L == event.control) { + int value = passencore_state.left_encoder_value + event.value; + CONSTRAIN(value, OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 14); + passencore_state.left_encoder_value = value; + } else if (OC::CONTROL_ENCODER_R == event.control) { + if (passencore_state.cursor.editing()) { + passencore_instance.change_value(passencore_state.cursor.cursor_pos(), event.value); + } else { + passencore_state.cursor.Scroll(event.value); + } + } +} + +size_t PASSENCORE_restore(const void *storage) { + size_t storage_size = passencore_instance.Restore(storage); + passencore_state.left_encoder_value = passencore_instance.get_scale(DUMMY); + passencore_instance.set_scale(passencore_state.left_encoder_value); + return storage_size; +} + + diff --git a/software/o_c_REV/OC_apps.ino b/software/o_c_REV/OC_apps.ino index f764a6d12..dab8e2f86 100644 --- a/software/o_c_REV/OC_apps.ino +++ b/software/o_c_REV/OC_apps.ino @@ -86,6 +86,11 @@ OC::App available_apps[] = { #ifdef ENABLE_APP_FPART DECLARE_APP('F','P', "4 Parts", FPART), #endif + #ifdef ENABLE_APP_PASSENCORE + // boring name version + // DECLARE_APP('P','Q', "Tension", PASSENCORE), + DECLARE_APP('P','Q', "Passencore", PASSENCORE), + #endif #ifdef ENABLE_APP_MIDI DECLARE_APP('M','I', "Captain MIDI", MIDI), #endif diff --git a/software/o_c_REV/OC_scales.cpp b/software/o_c_REV/OC_scales.cpp index 2792e2a1d..53f542385 100644 --- a/software/o_c_REV/OC_scales.cpp +++ b/software/o_c_REV/OC_scales.cpp @@ -37,6 +37,13 @@ const char* const scale_names_short[] = { "MIXO", "AEOL", "LOCR", + "HMIN", + "LOn6", + "IAUG", + "MBKH", + "FREY", + "LY#9", + "UTLO", "BLU+", "BLU-", "PEN+", @@ -188,6 +195,13 @@ const char* const scale_names[] = { "Mixolydian", "Aeolian", "Locrian", + "Harmonic minor", + "Locrian n6", + "Ionian aug", + "Misheberakh", + "Freygish", + "Lydian #9", + "Ultralocrian", "Blues major", "Blues minor", "Pentatonic maj", diff --git a/software/o_c_REV/OC_strings.cpp b/software/o_c_REV/OC_strings.cpp index d1761dd21..1bae5ee99 100644 --- a/software/o_c_REV/OC_strings.cpp +++ b/software/o_c_REV/OC_strings.cpp @@ -31,6 +31,12 @@ namespace OC { const char * const seq_directions[] = {"fwd", "rev", "pnd1", "pnd2", "rnd", "brwn"}; + const char * const scale_degrees_maj[] = {"I", "II", "III", "IV", "V", "VI", "VII"}; + + const char* const scale_degrees_min[] = {"i", "ii", "iii", "iv", "v", "vi", "vii"}; + + const char* const accidentals[] = {"bb", "b", "", "#", "##"}; + const char * const scale_id[] = { ">#1", ">#2", ">#3", ">#4", " #1", " #2", " #3", " #4"}; const char * const note_names[12] = { "C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B " }; diff --git a/software/o_c_REV/OC_strings.h b/software/o_c_REV/OC_strings.h index 916c537aa..6a0c93d55 100644 --- a/software/o_c_REV/OC_strings.h +++ b/software/o_c_REV/OC_strings.h @@ -17,6 +17,10 @@ namespace OC { extern const char * const seq_playmodes[]; extern const char * const channel_trigger_sources[]; extern const char * const seq_directions[]; + + extern const char * const scale_degrees_maj[]; + extern const char * const scale_degrees_min[]; + extern const char * const accidentals[]; extern const char * const scale_id[]; extern const char * const channel_id[]; extern const char * const note_names[]; diff --git a/software/o_c_REV/braids_quantizer_scales.h b/software/o_c_REV/braids_quantizer_scales.h index f96514249..e79a216ee 100644 --- a/software/o_c_REV/braids_quantizer_scales.h +++ b/software/o_c_REV/braids_quantizer_scales.h @@ -52,6 +52,20 @@ const Scale scales[] = { { 12 << 7, 7, { 0, 256, 384, 640, 896, 1024, 1280} }, // Locrian (From midipal/BitT source code) { 12 << 7, 7, { 0, 128, 384, 640, 768, 1024, 1280} }, + // Harmonic minor + { 12 << 7, 7, { 0, 256, 384, 640, 896, 1024, 1408} }, + // Locrian n6 + { 12 << 7, 7, { 0, 128, 384, 640, 768, 1152, 1280} }, + // Ionian augmented + { 12 << 7, 7, { 0, 256, 512, 640, 1024, 1152, 1408} }, + // Misheberakh + { 12 << 7, 7, { 0, 256, 384, 768, 896, 1152, 1280} }, + // Freygish + { 12 << 7, 7, { 0, 128, 512, 640, 896, 1024, 1280} }, + // Lydian #9 + { 12 << 7, 7, { 0, 384, 512, 768, 896, 1152, 1408} }, + // Ultralocrian + { 12 << 7, 7, { 0, 128, 384, 512, 768, 1024, 1152} }, // Blues major (From midipal/BitT source code) { 12 << 7, 6, { 0, 384, 512, 896, 1152, 1280} }, // Blues minor (From midipal/BitT source code) From 85f684a8744db5dea064afaead2a407bcd33ebec Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 18 Jun 2021 00:20:48 -0700 Subject: [PATCH 380/417] copyright I guess --- software/o_c_REV/APP_PASSENCORE.ino | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/software/o_c_REV/APP_PASSENCORE.ino b/software/o_c_REV/APP_PASSENCORE.ino index e4071fe9e..b568509f3 100644 --- a/software/o_c_REV/APP_PASSENCORE.ino +++ b/software/o_c_REV/APP_PASSENCORE.ino @@ -1,3 +1,27 @@ +// Copyright (c) 2021 Naomi Seyfer +// +// Author of original O+C firmware: Max Stadler (mxmlnstdlr@gmail.com) +// Author of app scaffolding: Patrick Dowling (pld@gurkenkiste.com) +// Quantizer code: Emilie Gillet +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + #include #include "OC_apps.h" From c294b3c82998fe4717bc3cf4be39a34cfc6816c3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sat, 9 Dec 2023 01:19:01 -0500 Subject: [PATCH 381/417] Move new scales from Passencore to the end so as not to disturb the status quo you can always scroll backwards from OFF/SEMI --- software/o_c_REV/OC_scales.cpp | 30 +++++++++++----------- software/o_c_REV/braids_quantizer_scales.h | 27 ++++++++++--------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/software/o_c_REV/OC_scales.cpp b/software/o_c_REV/OC_scales.cpp index 53f542385..6f823c8a2 100644 --- a/software/o_c_REV/OC_scales.cpp +++ b/software/o_c_REV/OC_scales.cpp @@ -37,13 +37,6 @@ const char* const scale_names_short[] = { "MIXO", "AEOL", "LOCR", - "HMIN", - "LOn6", - "IAUG", - "MBKH", - "FREY", - "LY#9", - "UTLO", "BLU+", "BLU-", "PEN+", @@ -177,7 +170,14 @@ const char* const scale_names_short[] = { "5th", // Fifth, "3b+", // major triad (Triad), "3b-", // minor triad (3b+5), - "HAR-",// Harmonic Minor (Harm Minor), + "HAR-",// Harmonic Minor (Harm Minor), aka "HMIN" + + "LOn6", + "IAUG", + "MBKH", + "FREY", + "LY#9", + "UTLO", }; @@ -195,13 +195,6 @@ const char* const scale_names[] = { "Mixolydian", "Aeolian", "Locrian", - "Harmonic minor", - "Locrian n6", - "Ionian aug", - "Misheberakh", - "Freygish", - "Lydian #9", - "Ultralocrian", "Blues major", "Blues minor", "Pentatonic maj", @@ -337,6 +330,13 @@ const char* const scale_names[] = { "TriadMin",// minor triad (3b+5), "HarmonicMin",// Harmonic Minor (Harm Minor), + "Locrian n6", + "Ionian aug", + "Misheberakh", + "Freygish", + "Lydian #9", + "Ultralocrian", + }; const char* const voltage_scalings[] = { diff --git a/software/o_c_REV/braids_quantizer_scales.h b/software/o_c_REV/braids_quantizer_scales.h index e79a216ee..385d0e390 100644 --- a/software/o_c_REV/braids_quantizer_scales.h +++ b/software/o_c_REV/braids_quantizer_scales.h @@ -52,20 +52,6 @@ const Scale scales[] = { { 12 << 7, 7, { 0, 256, 384, 640, 896, 1024, 1280} }, // Locrian (From midipal/BitT source code) { 12 << 7, 7, { 0, 128, 384, 640, 768, 1024, 1280} }, - // Harmonic minor - { 12 << 7, 7, { 0, 256, 384, 640, 896, 1024, 1408} }, - // Locrian n6 - { 12 << 7, 7, { 0, 128, 384, 640, 768, 1152, 1280} }, - // Ionian augmented - { 12 << 7, 7, { 0, 256, 512, 640, 1024, 1152, 1408} }, - // Misheberakh - { 12 << 7, 7, { 0, 256, 384, 768, 896, 1152, 1280} }, - // Freygish - { 12 << 7, 7, { 0, 128, 512, 640, 896, 1024, 1280} }, - // Lydian #9 - { 12 << 7, 7, { 0, 384, 512, 768, 896, 1152, 1408} }, - // Ultralocrian - { 12 << 7, 7, { 0, 128, 384, 512, 768, 1024, 1152} }, // Blues major (From midipal/BitT source code) { 12 << 7, 6, { 0, 384, 512, 896, 1152, 1280} }, // Blues minor (From midipal/BitT source code) @@ -348,6 +334,19 @@ const Scale scales[] = { // Harmonic Minor (Harm Minor), { 12 << 7, 7, { 0, 256, 384, 640, 896, 1024, 1408} }, + // Locrian n6 + { 12 << 7, 7, { 0, 128, 384, 640, 768, 1152, 1280} }, + // Ionian augmented + { 12 << 7, 7, { 0, 256, 512, 640, 1024, 1152, 1408} }, + // Misheberakh + { 12 << 7, 7, { 0, 256, 384, 768, 896, 1152, 1280} }, + // Freygish + { 12 << 7, 7, { 0, 128, 512, 640, 896, 1024, 1280} }, + // Lydian #9 + { 12 << 7, 7, { 0, 384, 512, 768, 896, 1152, 1408} }, + // Ultralocrian + { 12 << 7, 7, { 0, 128, 384, 512, 768, 1024, 1152} }, + } ; }// namespace braids From 5734d306664bc3bed23cf516b55f51cc7f982815 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Tue, 28 Nov 2023 02:10:41 -0500 Subject: [PATCH 382/417] Enable Passencore (with some fixes) --- software/o_c_REV/APP_PASSENCORE.ino | 10 +++++++--- software/o_c_REV/platformio.ini | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/APP_PASSENCORE.ino b/software/o_c_REV/APP_PASSENCORE.ino index b568509f3..4eaf1e7e1 100644 --- a/software/o_c_REV/APP_PASSENCORE.ino +++ b/software/o_c_REV/APP_PASSENCORE.ino @@ -22,6 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#ifdef ENABLE_APP_PASSENCORE + #include #include "OC_apps.h" @@ -211,6 +213,7 @@ struct PassenChord { } }; +namespace menu = OC::menu; class PASSENCORE : public settings::SettingsBase { public: @@ -248,7 +251,7 @@ class PASSENCORE : public settings::SettingsBase::RotateMask(mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); + mask = OC::ScaleEditor::RotateMask(mask, OC::Scales::GetScale(scale).num_notes, mask_rotate); if (force || (last_scale_ != scale || last_mask_ != mask)) { last_scale_ = scale; @@ -944,6 +947,7 @@ class PassencoreState { }; +// TOTAL EEPROM SIZE: 17 bytes SETTINGS_DECLARE(PASSENCORE, PASSENCORE_SETTING_LAST) { //PASSENCORE_SETTING_SCALE, { OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 14, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, @@ -1110,4 +1114,4 @@ size_t PASSENCORE_restore(const void *storage) { return storage_size; } - +#endif // ENABLE_APP_PASSENCORE diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 49e717b62..b39a4d7e5 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -68,6 +68,7 @@ build_flags = -DENABLE_APP_QUANTERMAIN -DENABLE_APP_METAQ -DENABLE_APP_CHORDS + -DENABLE_APP_PASSENCORE -DENABLE_APP_SEQUINS -DENABLE_APP_AUTOMATONNETZ -DENABLE_APP_BBGEN @@ -96,6 +97,7 @@ build_flags = -DENABLE_APP_PONG -DENABLE_APP_PIQUED -DENABLE_APP_POLYLFO + -DENABLE_APP_PASSENCORE -DENABLE_APP_H1200 -DENABLE_APP_BYTEBEATGEN ; -DENABLE_APP_REFERENCES From 6b3fd0069c4b323a3bf0cf0e1b932e6be499923a Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 11 Dec 2023 04:02:20 -0500 Subject: [PATCH 383/417] Passencore: scale mask editor; allow all scales --- software/o_c_REV/APP_PASSENCORE.ino | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/APP_PASSENCORE.ino b/software/o_c_REV/APP_PASSENCORE.ino index 4eaf1e7e1..9c87da1d4 100644 --- a/software/o_c_REV/APP_PASSENCORE.ino +++ b/software/o_c_REV/APP_PASSENCORE.ino @@ -950,9 +950,9 @@ class PassencoreState { // TOTAL EEPROM SIZE: 17 bytes SETTINGS_DECLARE(PASSENCORE, PASSENCORE_SETTING_LAST) { //PASSENCORE_SETTING_SCALE, - { OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 14, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, + { OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI, OC::Scales::NUM_SCALES - 1, "scale", OC::scale_names, settings::STORAGE_TYPE_U8 }, //PASSENCORE_SETTING_MASK, - { 65535, 1, 65535, "scale -->", NULL, settings::STORAGE_TYPE_U16 }, // mask + { 65535, 1, 65535, "mask -->", NULL, settings::STORAGE_TYPE_U16 }, // mask //PASSENCORE_SETTING_SAMPLE_TRIGGER, { 0, 0, 3, "sample trigger", OC::Strings::trigger_input_names, settings::STORAGE_TYPE_U4}, //PASSENCORE_SETTING_TARGET_TRIGGER, @@ -1050,7 +1050,15 @@ void PASSENCORE_menu() { { const int current = settings_list.Next(list_item); const int value = passencore_instance.get_value(current); - list_item.DrawDefault(value, PASSENCORE::value_attr(current)); + if (current == PASSENCORE_SETTING_MASK) { + menu::DrawMask(menu::kDisplayWidth, list_item.y, passencore_instance.get_mask(), OC::Scales::GetScale(passencore_instance.get_scale(DUMMY)).num_notes); + list_item.DrawNoValue(value, PASSENCORE::value_attr(current)); + } else { + list_item.DrawDefault(value, PASSENCORE::value_attr(current)); + } + + if (passencore_state.scale_editor.active()) + passencore_state.scale_editor.Draw(); } } @@ -1079,7 +1087,13 @@ void PASSENCORE_handleButtonEvent(const UI::Event & event) { PASSENCORE_leftButton(); break; case OC::CONTROL_BUTTON_R: - passencore_state.cursor.toggle_editing(); + if (passencore_state.cursor_pos() == PASSENCORE_SETTING_MASK) { + int scale = passencore_instance.get_scale(DUMMY); + if (OC::Scales::SCALE_NONE != scale) + passencore_state.scale_editor.Edit(&passencore_instance, scale); + } else { + passencore_state.cursor.toggle_editing(); + } break; } } @@ -1096,7 +1110,7 @@ void PASSENCORE_handleEncoderEvent(const UI::Event & event) { //} if (OC::CONTROL_ENCODER_L == event.control) { int value = passencore_state.left_encoder_value + event.value; - CONSTRAIN(value, OC::Scales::SCALE_SEMI + 1, OC::Scales::SCALE_SEMI + 14); + CONSTRAIN(value, OC::Scales::SCALE_SEMI, OC::Scales::NUM_SCALES - 1); passencore_state.left_encoder_value = value; } else if (OC::CONTROL_ENCODER_R == event.control) { if (passencore_state.cursor.editing()) { From d6dd1e3f52486d5a1a458fb1223bcefedaa34f10 Mon Sep 17 00:00:00 2001 From: Bryan Head Date: Sat, 2 Dec 2023 07:12:48 -0800 Subject: [PATCH 384/417] Fix quantizer note index handling --- software/o_c_REV/braids_quantizer.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/software/o_c_REV/braids_quantizer.cpp b/software/o_c_REV/braids_quantizer.cpp index d758ec851..ee4a04f89 100644 --- a/software/o_c_REV/braids_quantizer.cpp +++ b/software/o_c_REV/braids_quantizer.cpp @@ -113,7 +113,7 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { } // set final values - note_number_ = octave * num_notes_ + q; + note_number_ = (octave + 2) * num_notes_ + q + 64; // 64 is C2 codeword_ = notes_[q] + octave * span_; transpose_ = transpose; @@ -130,8 +130,12 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root, int32_t transpose) { int32_t Quantizer::Lookup(int32_t index) const { index -= 64; - int16_t octave = index / num_notes_ - (index < 0 ? 1 : 0); - int16_t rel_ix = index - octave * num_notes_; + int16_t octave = index / num_notes_; + int16_t rel_ix = index % num_notes_; + if (rel_ix < 0) { + octave--; + rel_ix += num_notes_; + } int32_t pitch = notes_[rel_ix] + octave * span_; return pitch; } From f2e287a54e41b37a33a5890b117b3bb0da477cfc Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 3 Dec 2023 19:41:42 -0500 Subject: [PATCH 385/417] Hemisphere: EEPROM Auto-save Enabled via Config setting per preset. Stores current preset when screensaver is invoked, or when showing App Menu. EEPROM save happens when storing preset, but only if the data has changed. --- software/o_c_REV/APP_HEMISPHERE.ino | 76 ++++++++++++++++++++++------- software/o_c_REV/HEM_ClockSetup.ino | 3 +- software/o_c_REV/HSIOFrame.h | 3 +- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index b0230e0e0..e7e6dde42 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -31,7 +31,9 @@ namespace menu = OC::menu; #include "HSMIDI.h" #include "HSClockManager.h" -// The settings specify the selected applets, and 64 bits of data for each applet +// 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). +// This is the structure of a HemispherePreset in eeprom. enum HEMISPHERE_SETTINGS { HEMISPHERE_SELECTED_LEFT_ID, HEMISPHERE_SELECTED_RIGHT_ID, @@ -72,15 +74,13 @@ public: return values_[HEMISPHERE_SELECTED_LEFT_ID] != 0; } - // restore state by setting applets and giving them data - void LoadClockData() { - HS::clock_setup_applet.OnDataReceive(0, (uint64_t(values_[HEMISPHERE_CLOCK_DATA4]) << 48) | - (uint64_t(values_[HEMISPHERE_CLOCK_DATA3]) << 32) | - (uint64_t(values_[HEMISPHERE_CLOCK_DATA2]) << 16) | - uint64_t(values_[HEMISPHERE_CLOCK_DATA1])); + uint64_t GetClockData() { + return ( (uint64_t(values_[HEMISPHERE_CLOCK_DATA4]) << 48) | + (uint64_t(values_[HEMISPHERE_CLOCK_DATA3]) << 32) | + (uint64_t(values_[HEMISPHERE_CLOCK_DATA2]) << 16) | + uint64_t(values_[HEMISPHERE_CLOCK_DATA1]) ); } - void StoreClockData() { - uint64_t data = HS::clock_setup_applet.OnDataRequest(0); + void SetClockData(const uint64_t data) { apply_value(HEMISPHERE_CLOCK_DATA1, data & 0xffff); apply_value(HEMISPHERE_CLOCK_DATA2, (data >> 16) & 0xffff); apply_value(HEMISPHERE_CLOCK_DATA3, (data >> 32) & 0xffff); @@ -148,7 +148,6 @@ public: values_[HEMISPHERE_RIGHT_DATA_B3] = ((uint16_t)V[13] << 8) + V[12]; values_[HEMISPHERE_LEFT_DATA_B4] = ((uint16_t)V[15] << 8) + V[14]; values_[HEMISPHERE_RIGHT_DATA_B4] = ((uint16_t)V[17] << 8) + V[16]; - //LoadClockData(); } } @@ -190,23 +189,41 @@ public: } void Suspend() { if (hem_active_preset) { - // Preset A will auto-save - if (preset_id == 0) StoreToPreset(0); + if (HS::auto_save_enabled) StoreToPreset(preset_id); hem_active_preset->OnSendSysEx(); } } void StoreToPreset(HemispherePreset* preset) { + bool doSave = (preset != hem_active_preset); + hem_active_preset = preset; for (int h = 0; h < 2; h++) { int index = my_applet[h]; + if (hem_active_preset->GetAppletId(h) != HS::available_applets[index].id) + doSave = 1; hem_active_preset->SetAppletId(h, HS::available_applets[index].id); uint64_t data = HS::available_applets[index].OnDataRequest(h); + if (data != applet_data[h]) doSave = 1; + applet_data[h] = data; hem_active_preset->SetData(h, data); } - hem_active_preset->StoreClockData(); + uint64_t data = HS::clock_setup_applet.OnDataRequest(0); + if (data != clock_data) doSave = 1; + clock_data = data; + hem_active_preset->SetClockData(data); + + // initiate actual EEPROM save - ONLY if necessary! + if (doSave) { + OC::CORE::app_isr_enabled = false; + delay(1); + OC::save_app_data(); + delay(1); + OC::CORE::app_isr_enabled = true; + } + } void StoreToPreset(int id) { StoreToPreset( (HemispherePreset*)(hem_presets + id) ); @@ -215,12 +232,15 @@ public: void LoadFromPreset(int id) { hem_active_preset = (HemispherePreset*)(hem_presets + id); if (hem_active_preset->is_valid()) { - hem_active_preset->LoadClockData(); + clock_data = hem_active_preset->GetClockData(); + HS::clock_setup_applet.OnDataReceive(0, clock_data); + for (int h = 0; h < 2; h++) { int index = get_applet_index_by_id( hem_active_preset->GetAppletId(h) ); + applet_data[h] = hem_active_preset->GetData(h); SetApplet(h, index); - HS::available_applets[index].OnDataReceive(h, hem_active_preset->GetData(h)); + HS::available_applets[index].OnDataReceive(h, applet_data[h]); } } preset_id = id; @@ -477,6 +497,7 @@ private: int preset_id = 0; int preset_cursor = 0; int my_applet[2]; // Indexes to available_applets + uint64_t clock_data, applet_data[2]; // cache of applet data bool clock_setup; bool config_menu; bool isEditing = false; @@ -489,6 +510,7 @@ private: enum HEMConfigCursor { LOAD_PRESET, SAVE_PRESET, + AUTO_SAVE, TRIG_LENGTH, SCREENSAVER_MODE, CURSOR_MODE, @@ -537,6 +559,10 @@ private: preset_cursor = preset_id + 1; break; + case AUTO_SAVE: + HS::auto_save_enabled = !HS::auto_save_enabled; + break; + case TRIG_LENGTH: isEditing = !isEditing; break; @@ -561,7 +587,9 @@ private: // --- Config Selection gfxHeader("Hemisphere Config"); gfxPrint(1, 15, "Preset: "); - gfxPrint(48, 15, "Load / Save"); + gfxPrint(48, 15, "Load"); + gfxIcon(100, 15, HS::auto_save_enabled ? CHECK_ON_ICON : CHECK_OFF_ICON ); + gfxPrint(48, 25, "Save (auto)"); gfxPrint(1, 35, "Trig Length: "); gfxPrint(HS::trig_length); @@ -584,7 +612,11 @@ private: switch (config_cursor) { case LOAD_PRESET: case SAVE_PRESET: - gfxIcon(55 + (config_cursor - LOAD_PRESET)*45, 25, UP_ICON); + gfxIcon(73, 15 + (config_cursor - LOAD_PRESET)*10, LEFT_ICON); + break; + + case AUTO_SAVE: + gfxIcon(90, 15, RIGHT_ICON); break; case TRIG_LENGTH: @@ -694,8 +726,16 @@ void FASTRUN HEMISPHERE_isr() { } void HEMISPHERE_handleAppEvent(OC::AppEvent event) { - if (event == OC::APP_EVENT_SUSPEND) { + switch (event) { + case OC::APP_EVENT_RESUME: + break; + + case OC::APP_EVENT_SCREENSAVER_ON: + case OC::APP_EVENT_SUSPEND: manager.Suspend(); + break; + + default: break; } } diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index bcae01d2c..264b0a89e 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -179,7 +179,7 @@ public: } Pack(data, PackLocation { 52, 7 }, HS::trig_length); - // --- PackLocation { 59, 1 } --- + Pack(data, PackLocation { 59, 1 }, HS::auto_save_enabled); Pack(data, PackLocation { 60, 2 }, HS::modal_edit_mode); Pack(data, PackLocation { 62, 2 }, HS::screensaver_mode); @@ -209,6 +209,7 @@ public: } HS::trig_length = constrain( Unpack(data, PackLocation { 52, 7 }), 1, 127); + HS::auto_save_enabled = Unpack(data, PackLocation { 59, 1 }); HS::screensaver_mode = Unpack(data, PackLocation { 62, 2 }); } diff --git a/software/o_c_REV/HSIOFrame.h b/software/o_c_REV/HSIOFrame.h index 3e95f76b6..ed0086321 100644 --- a/software/o_c_REV/HSIOFrame.h +++ b/software/o_c_REV/HSIOFrame.h @@ -13,8 +13,9 @@ braids::Quantizer quantizer[4]; // global shared quantizers int quant_scale[4]; int root_note[4]; -uint8_t trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS +bool auto_save_enabled = false; int trigger_mapping[] = { 1, 2, 3, 4 }; +uint8_t trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS uint8_t screensaver_mode = 2; // 0 = blank, 1 = Meters, 2 = Zaps typedef struct MIDILogEntry { From 935d298f6060e0bfaf066c3a8d67f55da81d50dd Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 7 Dec 2023 04:16:13 -0500 Subject: [PATCH 386/417] MIDI ProgChange 0-3 loads Hemisphere Preset --- software/o_c_REV/APP_HEMISPHERE.ino | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index e7e6dde42..07d44bac8 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -274,6 +274,12 @@ public: continue; } + if (message == usbMIDI.ProgramChange) { + int slot = usbMIDI.getData1(); + if (slot < 4) LoadFromPreset(slot); + continue; + } + f.MIDIState.ProcessMIDIMsg(usbMIDI.getChannel(), message, data1, data2); } } From 1168c6bc6e518d939f28e986b397db8944b488a8 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 7 Dec 2023 06:11:44 -0500 Subject: [PATCH 387/417] Draw save message during auto-save --- software/o_c_REV/APP_CALIBR8OR.ino | 5 +---- software/o_c_REV/APP_HEMISPHERE.ino | 1 + software/o_c_REV/OC_apps.h | 3 +++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index 34b852e28..e9ed4e109 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -36,10 +36,6 @@ #include "src/drivers/FreqMeasure/OC_FreqMeasure.h" #include "HemisphereApplet.h" -namespace OC { - void save_app_data(); -} - #define CAL8_MAX_TRANSPOSE 60 const int CAL8OR_PRECISION = 10000; @@ -215,6 +211,7 @@ public: // initiate actual EEPROM save OC::CORE::app_isr_enabled = false; + OC::draw_save_message(60); delay(1); OC::save_app_data(); delay(1); diff --git a/software/o_c_REV/APP_HEMISPHERE.ino b/software/o_c_REV/APP_HEMISPHERE.ino index 07d44bac8..abcf7c58c 100644 --- a/software/o_c_REV/APP_HEMISPHERE.ino +++ b/software/o_c_REV/APP_HEMISPHERE.ino @@ -218,6 +218,7 @@ public: // initiate actual EEPROM save - ONLY if necessary! if (doSave) { OC::CORE::app_isr_enabled = false; + OC::draw_save_message(60); delay(1); OC::save_app_data(); delay(1); diff --git a/software/o_c_REV/OC_apps.h b/software/o_c_REV/OC_apps.h index 059112aa1..7d8e146ff 100644 --- a/software/o_c_REV/OC_apps.h +++ b/software/o_c_REV/OC_apps.h @@ -84,6 +84,9 @@ namespace apps { }; // namespace apps +void draw_save_message(uint8_t c); +void save_app_data(); + }; // namespace OC #endif // OC_APP_H_ From 8912b143e7e7a10a54efbd6156579d97f30b6b83 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 10 Dec 2023 02:54:28 -0500 Subject: [PATCH 388/417] Brancher: show CV modulated value --- software/o_c_REV/HEM_Brancher.ino | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/HEM_Brancher.ino b/software/o_c_REV/HEM_Brancher.ino index f4b17b477..a1303774a 100644 --- a/software/o_c_REV/HEM_Brancher.ino +++ b/software/o_c_REV/HEM_Brancher.ino @@ -31,10 +31,12 @@ public: } void Controller() { + p_mod = p; + Modulate(p_mod, 0, 0, 100); + // handles physical and logical clock if (Clock(0)) { - int prob = p + Proportion(DetentedIn(0), HEMISPHERE_MAX_INPUT_CV, 100); - choice = (random(1, 100) <= prob) ? 0 : 1; + choice = (random(1, 100) <= p_mod) ? 0 : 1; // will be true only for logical clocks clocked = !Gate(0); @@ -81,16 +83,17 @@ protected: } private: - int p; + int p, p_mod; int choice; bool clocked; // indicates a logical clock without a physical gate void DrawInterface() { // Show the probability in the middle gfxPrint(1, 15, "p="); - gfxPrint(15 + pad(100, p), 15, p); - gfxPrint(33, 15, hemisphere ? "% C" : "% A"); + gfxPrint(15 + pad(100, p_mod), 15, p_mod); + gfxPrint(33, 15, hemisphere ? "% C" : "% A"); gfxCursor(15, 23, 18); + if (p != p_mod) gfxIcon(39, 12, CV_ICON); gfxPrint(12, 45, hemisphere ? "C" : "A"); gfxPrint(44, 45, hemisphere ? "D" : "B"); From 1abcc3ba450999aac3621a8cc9ba672159d15f35 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 10 Dec 2023 04:23:22 -0500 Subject: [PATCH 389/417] Set default Trig Length to 10ms --- software/o_c_REV/HSIOFrame.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/HSIOFrame.h b/software/o_c_REV/HSIOFrame.h index ed0086321..93eaa6639 100644 --- a/software/o_c_REV/HSIOFrame.h +++ b/software/o_c_REV/HSIOFrame.h @@ -15,7 +15,7 @@ int root_note[4]; bool auto_save_enabled = false; int trigger_mapping[] = { 1, 2, 3, 4 }; -uint8_t trig_length = 2; // multiplier for HEMISPHERE_CLOCK_TICKS +uint8_t trig_length = 10; // in ms, multiplier for HEMISPHERE_CLOCK_TICKS uint8_t screensaver_mode = 2; // 0 = blank, 1 = Meters, 2 = Zaps typedef struct MIDILogEntry { From dd5e444ee81ef700aba7fa3d3532f929f18dfe65 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 10 Dec 2023 04:23:47 -0500 Subject: [PATCH 390/417] Clock off-by-one for divisions; fixes resets --- software/o_c_REV/HSClockManager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index ea3b5b49d..b4a3aa6fd 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -139,7 +139,7 @@ class ClockManager { //if (!IsRunning()) return; if (hard_reset) Reset(); - uint32_t now = OC::CORE::ticks; + const uint32_t now = OC::CORE::ticks; // Reset only when all multipliers have been met bool reset = 1; @@ -159,7 +159,7 @@ class ClockManager { } else { // division: -1 becomes /2, -2 becomes /3, etc. int div = 1 - tocks_per_beat[ch]; uint32_t next_beat = beat_tick + (count[ch] ? ticks_per_beat : 0); - bool beat_exceeded = (now > next_beat); + bool beat_exceeded = (now >= next_beat); if (beat_exceeded) { ++count[ch]; tock[ch] = (count[ch] % div) == 1; From 1061705c1ea12120a7a1a972740c6dc9d4d1c456 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 10 Dec 2023 04:45:42 -0500 Subject: [PATCH 391/417] Reset on load for sequencers --- software/o_c_REV/HEM_DrumMap.ino | 1 + software/o_c_REV/HEM_EuclidX.ino | 1 + software/o_c_REV/HEM_SequenceX.ino | 9 +++++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_DrumMap.ino b/software/o_c_REV/HEM_DrumMap.ino index 118a5ef9d..474af2e2f 100644 --- a/software/o_c_REV/HEM_DrumMap.ino +++ b/software/o_c_REV/HEM_DrumMap.ino @@ -216,6 +216,7 @@ public: mode[0] = Unpack(data, PackLocation {40,8}); mode[1] = Unpack(data, PackLocation {48,8}); cv_mode = Unpack(data, PackLocation {56,8}); + Reset(); } protected: diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index dbe16a954..a83de375c 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -189,6 +189,7 @@ public: actual_padding[ch] = padding[ch] = Unpack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}); cv_dest[ch] = (EuclidXParam) Unpack(data, PackLocation {idx++ * PARAM_SIZE, PARAM_SIZE}); } + step = 0; // reset } protected: diff --git a/software/o_c_REV/HEM_SequenceX.ino b/software/o_c_REV/HEM_SequenceX.ino index 528d01bad..f453cc005 100644 --- a/software/o_c_REV/HEM_SequenceX.ino +++ b/software/o_c_REV/HEM_SequenceX.ino @@ -54,8 +54,7 @@ public: else cv2_gate = 0; if (Clock(1)) { // reset - step = 0; - reset = true; + Reset(); } if (Clock(0)) { // clock @@ -113,6 +112,7 @@ public: note[s] = Unpack(data, PackLocation {uint8_t(s * 5),5}); } muted = Unpack(data, PackLocation {SEQX_STEPS * 5, SEQX_STEPS}); + Reset(); } protected: @@ -139,6 +139,11 @@ private: if (step_is_muted(step) && step != starting_point) Advance(starting_point); } + void Reset() { + step = 0; + reset = true; + } + void DrawPanel() { // Sliders for (int s = 0; s < SEQX_STEPS; s++) From 2e1619dc102ff13f84d74bf2f7a32ec8dc7e4d05 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 11 Dec 2023 03:08:28 -0500 Subject: [PATCH 392/417] Don't load Tempo if Clock is running --- software/o_c_REV/HEM_ClockSetup.ino | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index 264b0a89e..ac579ce7a 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -191,7 +191,8 @@ public: // bit 1 - backward compatibility with Clock Forwarding if (Unpack(data, PackLocation { 1, 1 })) HS::trigger_mapping[2] = 1; - clock_m->SetTempoBPM(Unpack(data, PackLocation { 2, 9 })); + if (!clock_m->IsRunning()) + clock_m->SetTempoBPM(Unpack(data, PackLocation { 2, 9 })); clock_m->SetClockPPQN(Unpack(data, PackLocation { 11, 5 })); for (size_t i = 0; i < 4; ++i) { clock_m->SetMultiply(Unpack(data, PackLocation { 16+i*6, 6 })-32, i); From 05e15c91cc022dc2134c2dd6c7a9a119669377dd Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Mon, 11 Dec 2023 13:23:17 -0500 Subject: [PATCH 393/417] Clock: allow 1 BPM; cleanup --- software/o_c_REV/HSClockManager.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index b4a3aa6fd..d39817fb8 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -24,9 +24,7 @@ #ifndef CLOCK_MANAGER_H #define CLOCK_MANAGER_H -#define CLOCK_PPQN 4 - -static constexpr uint16_t CLOCK_TEMPO_MIN = 10; +static constexpr uint16_t CLOCK_TEMPO_MIN = 1; static constexpr uint16_t CLOCK_TEMPO_MAX = 300; static constexpr uint32_t CLOCK_TICKS_MIN = 1000000 / CLOCK_TEMPO_MAX; static constexpr uint32_t CLOCK_TICKS_MAX = 1000000 / CLOCK_TEMPO_MIN; From 95eef1d4d3294c5d7ee6b312c0d7667358db0977 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 13 Dec 2023 06:15:16 -0500 Subject: [PATCH 394/417] Add global shuffle to Clock + Don't apply shuffle to MIDI Clock out --- software/o_c_REV/HEM_ClockSetup.ino | 18 ++++++++++++++++-- software/o_c_REV/HSClockManager.h | 7 +++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_ClockSetup.ino b/software/o_c_REV/HEM_ClockSetup.ino index ac579ce7a..868879323 100644 --- a/software/o_c_REV/HEM_ClockSetup.ino +++ b/software/o_c_REV/HEM_ClockSetup.ino @@ -24,6 +24,7 @@ public: enum ClockSetupCursor { PLAY_STOP, TEMPO, + SHUFFLE, EXT_PPQN, MULT1, MULT2, @@ -156,6 +157,9 @@ public: case TEMPO: clock_m->SetTempoBPM(clock_m->GetTempo() + direction); break; + case SHUFFLE: + clock_m->SetShuffle(clock_m->GetShuffle() + direction); + break; case MULT1: case MULT2: @@ -265,10 +269,17 @@ private: // Tempo gfxPrint(22 + pad(100, clock_m->GetTempo()), y, clock_m->GetTempo()); - gfxPrint(" BPM"); + if (cursor != SHUFFLE) + gfxPrint(" BPM"); + else { + // Shuffle + gfxIcon(44, y, METRO_R_ICON); + gfxPrint(52 + pad(10, clock_m->GetShuffle()), y, clock_m->GetShuffle()); + gfxPrint("%"); + } // Input PPQN - gfxPrint(73, y, "Sync=x"); + gfxPrint(79, y, "Sync="); gfxPrint(clock_m->GetClockPPQN()); y += 10; @@ -303,6 +314,9 @@ private: case TEMPO: gfxCursor(22, 22, 19); break; + case SHUFFLE: + gfxCursor(52, 22, 13); + break; case EXT_PPQN: gfxCursor(109,22, 13); break; diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index d39817fb8..6c051e05d 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -56,6 +56,7 @@ class ClockManager { bool tock[NR_OF_CLOCKS] = {0,0,0,0,0}; // The current tock value int16_t tocks_per_beat[NR_OF_CLOCKS] = {4,0, 8,0, MIDI_OUT_PPQN}; // Multiplier int count[NR_OF_CLOCKS] = {0,0,0,0,0}; // Multiple counter, 0 is a special case when first starting the clock + int8_t shuffle = 0; // 0 to 100 int clock_ppqn = 4; // external clock multiple bool cycle = 0; // Alternates for each tock, for display purposes @@ -110,6 +111,9 @@ class ClockManager { int GetMultiply(int ch = 0) {return tocks_per_beat[ch];} int GetClockPPQN() { return clock_ppqn; } + void SetShuffle(int8_t sh_) { shuffle = constrain(sh_, 0, 99); } + int8_t GetShuffle() { return shuffle; } + /* Gets the current tempo. This can be used between client processes, like two different * hemispheres. */ @@ -150,6 +154,9 @@ class ClockManager { if (tocks_per_beat[ch] > 0) { // multiply uint32_t next_tock_tick = beat_tick + count[ch]*ticks_per_beat / static_cast(tocks_per_beat[ch]); + if (shuffle && MIDI_CLOCK != ch && count[ch] % 2 == 1 && count[ch] < tocks_per_beat[ch]) + next_tock_tick += shuffle * ticks_per_beat / 100 / static_cast(tocks_per_beat[ch]); + tock[ch] = now >= next_tock_tick; if (tock[ch]) ++count[ch]; // increment multiplier counter From eb55a5ac74337ba8d4d47bd8df1c149913d0847c Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 13 Dec 2023 22:24:32 -0500 Subject: [PATCH 395/417] Average two clock intervals for sync ...to tolerate swing on incoming clocks; fixed --- software/o_c_REV/HSClockManager.h | 32 ++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/HSClockManager.h b/software/o_c_REV/HSClockManager.h index 6c051e05d..69d1e6b56 100644 --- a/software/o_c_REV/HSClockManager.h +++ b/software/o_c_REV/HSClockManager.h @@ -51,7 +51,8 @@ class ClockManager { bool paused = 0; // Specifies whethr the clock is paused bool midi_out_enabled = 1; - uint32_t clock_tick = 0; // tick when a physical clock was received on DIGITAL 1 + bool tickno = 0; + uint32_t clock_tick[2] = {0,0}; // previous ticks when a physical clock was received on DIGITAL 1 uint32_t beat_tick = 0; // The tick to count from bool tock[NR_OF_CLOCKS] = {0,0,0,0,0}; // The current tock value int16_t tocks_per_beat[NR_OF_CLOCKS] = {4,0, 8,0, MIDI_OUT_PPQN}; // Multiplier @@ -122,6 +123,10 @@ class ClockManager { // Reset - Resync multipliers, optionally skipping the first tock void Reset(bool count_skip = 0) { beat_tick = OC::CORE::ticks; + if (0 == count_skip) { + clock_tick[0] = 0; + clock_tick[1] = 0; + } for (int ch = 0; ch < NR_OF_CLOCKS; ch++) { if (tocks_per_beat[ch] > 0 || 0 == count_skip) count[ch] = count_skip; } @@ -181,15 +186,22 @@ class ClockManager { if (reset) Reset(1); // skip the one we're already on // handle syncing to physical clocks - if (clocked && clock_tick && clock_ppqn) { + if (clocked && clock_tick[tickno] && clock_ppqn) { + + uint32_t clock_diff = now - clock_tick[tickno]; + + // too slow, reset clock tracking + if (clock_ppqn * clock_diff > CLOCK_TICKS_MAX) { + clock_tick[0] = 0; + clock_tick[1] = 0; + } - uint32_t clock_diff = now - clock_tick; - if (clock_ppqn * clock_diff > CLOCK_TICKS_MAX) clock_tick = 0; // too slow, reset clock tracking + // if there are two previous clock ticks, update tempo and sync + if (clock_tick[1-tickno] && clock_diff) { + uint32_t avg_diff = (clock_diff + (clock_tick[tickno] - clock_tick[1-tickno])) / 2; - // if there is a previous clock tick, update tempo and sync - if (clock_tick && clock_diff) { // update the tempo - ticks_per_beat = constrain(clock_ppqn * clock_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); // time since last clock is new tempo + ticks_per_beat = constrain(clock_ppqn * avg_diff, CLOCK_TICKS_MIN, CLOCK_TICKS_MAX); tempo = 1000000 / ticks_per_beat; // imprecise, for display purposes int ticks_per_clock = ticks_per_beat / clock_ppqn; // rounded down @@ -207,8 +219,10 @@ class ClockManager { } } // clock has been physically ticked - if (clocked) clock_tick = now; - + if (clocked) { + tickno = 1 - tickno; + clock_tick[tickno] = now; + } } void Start(bool p = 0) { From f7db64fe3fb5ed7e63e3d56ae947b6192f036263 Mon Sep 17 00:00:00 2001 From: Patrick Dowling Date: Wed, 8 Apr 2020 18:58:34 +0200 Subject: [PATCH 396/417] Save space, move functions from inline to cpp --- software/o_c_REV/OC_menus.cpp | 20 ++++++++++++++++++++ software/o_c_REV/OC_menus.h | 21 ++------------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/software/o_c_REV/OC_menus.cpp b/software/o_c_REV/OC_menus.cpp index eed06a8d0..814047e03 100644 --- a/software/o_c_REV/OC_menus.cpp +++ b/software/o_c_REV/OC_menus.cpp @@ -32,6 +32,26 @@ void Init() { init_circle_lut(); }; +void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, int min_value, int max_value) { + const uint8_t *src = OC::bitmap_edit_indicators_8; + if (value == max_value) + src += OC::kBitmapEditIndicatorW * 2; + else if (value == min_value) + src += OC::kBitmapEditIndicatorW; + + graphics.drawBitmap8(x - 5, y + 1, OC::kBitmapEditIndicatorW, src); +} + +void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, const settings::value_attr &attr) { + const uint8_t *src = OC::bitmap_edit_indicators_8; + if (value == attr.max_) + src += OC::kBitmapEditIndicatorW * 2; + else if (value == attr.min_) + src += OC::kBitmapEditIndicatorW; + + graphics.drawBitmap8(x - 5, y + 1, OC::kBitmapEditIndicatorW, src); +} + }; // namespace menu diff --git a/software/o_c_REV/OC_menus.h b/software/o_c_REV/OC_menus.h index 8485f4e98..53edd7a3c 100644 --- a/software/o_c_REV/OC_menus.h +++ b/software/o_c_REV/OC_menus.h @@ -145,25 +145,8 @@ class ScreenCursor { int screen_line_; }; -inline void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, int min_value, int max_value) { - const uint8_t *src = OC::bitmap_edit_indicators_8; - if (value == max_value) - src += OC::kBitmapEditIndicatorW * 2; - else if (value == min_value) - src += OC::kBitmapEditIndicatorW; - - graphics.drawBitmap8(x - 5, y + 1, OC::kBitmapEditIndicatorW, src); -} - -inline void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, const settings::value_attr &attr) { - const uint8_t *src = OC::bitmap_edit_indicators_8; - if (value == attr.max_) - src += OC::kBitmapEditIndicatorW * 2; - else if (value == attr.min_) - src += OC::kBitmapEditIndicatorW; - - graphics.drawBitmap8(x - 5, y + 1, OC::kBitmapEditIndicatorW, src); -} +void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, int min_value, int max_value); +void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, const settings::value_attr &attr); inline void DrawChord(weegfx::coord_t x, weegfx::coord_t y, int width, int value, int mem_offset) { From b9eebd5943f0902774efdc2b411b5d1df9f4a7a3 Mon Sep 17 00:00:00 2001 From: Patrick Dowling Date: Wed, 8 Apr 2020 18:57:28 +0200 Subject: [PATCH 397/417] Save space, compile-time calculation of circle positions --- software/o_c_REV/OC_menus.cpp | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/software/o_c_REV/OC_menus.cpp b/software/o_c_REV/OC_menus.cpp index 814047e03..9cea82638 100644 --- a/software/o_c_REV/OC_menus.cpp +++ b/software/o_c_REV/OC_menus.cpp @@ -1,35 +1,41 @@ #include +#include #include "OC_config.h" #include "OC_core.h" #include "OC_bitmaps.h" #include "OC_menus.h" #include "OC_DAC.h" #include "OC_options.h" +#include "util/util_templates.h" namespace OC { -static constexpr weegfx::coord_t note_circle_r = 28; - -static struct coords { +struct coords { weegfx::coord_t x, y; -} circle_pos_lut[12]; - -static void init_circle_lut() { - static const float pi = 3.14159265358979323846f; - static const float semitone_radians = (2.f * pi / 12.f); - - for (int i = 0; i < 12; ++i) { - float rads = ((i + 12 - 3) % 12) * semitone_radians; - float x = note_circle_r * cos(rads); - float y = note_circle_r * sin(rads); - circle_pos_lut[i].x = x; - circle_pos_lut[i].y = y; - } +}; + +static constexpr float note_circle_r = 28.f; +static constexpr float pi = 3.14159265358979323846f; +static constexpr float semitone_radians = (2.f * pi / 12.f); +constexpr float index_to_rads(size_t index) { return ((index + 12 - 3) % 12) * semitone_radians; } + +template constexpr coords generate_circle_coords() { + return { + static_cast(note_circle_r * cosf(index_to_rads(index))), + static_cast(note_circle_r * sinf(index_to_rads(index))) + }; +} + +template +constexpr std::array generate_circle_pos_lut(util::index_sequence) { + return { generate_circle_coords()... }; } +static constexpr std::array circle_pos_lut = generate_circle_pos_lut(util::make_index_sequence<12>::type()); + namespace menu { -void Init() { - init_circle_lut(); +void Init() +{ }; void DrawEditIcon(weegfx::coord_t x, weegfx::coord_t y, int value, int min_value, int max_value) { From d733edbcbd7bdd337f5e5e8e6ac43af88f0027f3 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 14 Dec 2023 06:02:38 -0500 Subject: [PATCH 398/417] Calibr8or: autotune overrides scale_factor --- software/o_c_REV/APP_CALIBR8OR.ino | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index e9ed4e109..aa8da4c6c 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -267,7 +267,9 @@ public: c.last_note = quantized; } - int output_cv = c.last_note * (CAL8OR_PRECISION + c.scale_factor) / CAL8OR_PRECISION; + int output_cv = c.last_note; + if ( OC::DAC::calibration_data_used( DAC_CHANNEL(sel_chan) ) != 0x01 ) // not autotuned + output_cv *= (CAL8OR_PRECISION + c.scale_factor) / CAL8OR_PRECISION; output_cv += c.offset; Out(ch, output_cv); @@ -433,7 +435,9 @@ public: while (channel[sel_chan].transpose > CAL8_MAX_TRANSPOSE) channel[sel_chan].transpose -= s; while (channel[sel_chan].transpose < -CAL8_MAX_TRANSPOSE) channel[sel_chan].transpose += s; } - else { // Tracking compensation + else if ( OC::DAC::calibration_data_used( DAC_CHANNEL(sel_chan) ) != 0x01 ) // not autotuned + { + // Tracking compensation channel[sel_chan].scale_factor = constrain(channel[sel_chan].scale_factor + direction, -500, 500); } } @@ -546,20 +550,21 @@ public: // Tracking Compensation y += 22; gfxIcon(9, y, ZAP_ICON); - int whole = (channel[sel_chan].scale_factor + CAL8OR_PRECISION) / 100; - int decimal = (channel[sel_chan].scale_factor + CAL8OR_PRECISION) % 100; - gfxPrint(20 + pad(100, whole), y, whole); - gfxPrint("."); - if (decimal < 10) gfxPrint("0"); - gfxPrint(decimal); - gfxPrint("% "); + if ( OC::DAC::calibration_data_used( DAC_CHANNEL(sel_chan) ) == 0x01 ) { + gfxPrint(20, y, "(auto) "); + } else { + int whole = (channel[sel_chan].scale_factor + CAL8OR_PRECISION) / 100; + int decimal = (channel[sel_chan].scale_factor + CAL8OR_PRECISION) % 100; + gfxPrint(20 + pad(100, whole), y, whole); + gfxPrint("."); + if (decimal < 10) gfxPrint("0"); + gfxPrint(decimal); + gfxPrint("% "); + } if (channel[sel_chan].offset >= 0) gfxPrint("+"); gfxPrint(channel[sel_chan].offset); - if ( OC::DAC::calibration_data_used( DAC_CHANNEL(sel_chan) ) == 0x01 ) - gfxPrint(" (auto)"); - // mode indicator if (!scale_edit) gfxIcon(0, 32 + edit_mode*22, RIGHT_ICON); From 378996e6988c8dc6a9e5a928c454546ff81d912d Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 17 Dec 2023 22:12:12 -0500 Subject: [PATCH 399/417] Calibr8or: reset digital ins after autotuner --- software/o_c_REV/APP_CALIBR8OR.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/software/o_c_REV/APP_CALIBR8OR.ino b/software/o_c_REV/APP_CALIBR8OR.ino index aa8da4c6c..bb1dc3abc 100644 --- a/software/o_c_REV/APP_CALIBR8OR.ino +++ b/software/o_c_REV/APP_CALIBR8OR.ino @@ -55,6 +55,7 @@ struct Cal8ChannelConfig { DAC_CHANNEL get_channel() { return chan_; } void ExitAutotune() { FreqMeasure.end(); + OC::DigitalInputs::reInit(); } }; From dbced875d0e7b4d3690f3fc09ca7037e55acd312 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Sun, 17 Dec 2023 19:56:50 -0500 Subject: [PATCH 400/417] Run CI Workflow on all branches, only for code changes --- .github/workflows/firmware.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/firmware.yml b/.github/workflows/firmware.yml index 88877175e..eea9e3c42 100644 --- a/.github/workflows/firmware.yml +++ b/.github/workflows/firmware.yml @@ -2,8 +2,8 @@ name: PlatformIO CI on: push: - branches: - - 'dev/**' + paths: + - 'software/o_c_REV/**' jobs: build: From d0367980433ed808970901c0daa7c3781c3cb81b Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 14 Dec 2023 05:30:06 -0500 Subject: [PATCH 401/417] Version bump v1.6.999 --- software/o_c_REV/OC_version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/o_c_REV/OC_version.h b/software/o_c_REV/OC_version.h index a67dabf9f..4b3c50335 100644 --- a/software/o_c_REV/OC_version.h +++ b/software/o_c_REV/OC_version.h @@ -1,2 +1,2 @@ // NOTE: DO NOT INCLUDE DIRECTLY, USE OC::Strings::VERSION -"v1.6.777" +"v1.6.999" From 4920367eec7c217094a8dab1c6db06acfef760d2 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Wed, 20 Dec 2023 15:30:20 -0500 Subject: [PATCH 402/417] workflow tweaks/cleanup for Custom Build let's pretend it worked the first time --- .github/workflows/custom.yml | 5 +---- software/o_c_REV/resources/parse_build_request.py | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/custom.yml b/.github/workflows/custom.yml index 1cd2230db..4fdab1d5c 100644 --- a/.github/workflows/custom.yml +++ b/.github/workflows/custom.yml @@ -20,8 +20,7 @@ jobs: GH_COMMENT: ${{ github.event.comment.body }} run: | echo "OC_ARTIFACT_TAG=custom_${GITHUB_ACTOR}-$(software/o_c_REV/resources/oc_build_tag.sh)" | tr '/' '_' >> $GITHUB_ENV - echo "CUSTOM_FLAGS=$(python software/o_c_REV/resources/parse_build_request.py)" >> $GITHUB_ENV - env | grep ^GITHUB + echo "CUSTOM_BUILD_FLAGS=$(python software/o_c_REV/resources/parse_build_request.py)" >> $GITHUB_ENV - name: Cache PlatformIO uses: actions/cache@v3 @@ -41,8 +40,6 @@ jobs: pip3 install -r .github/workflows/requirements.txt - name: Build firmware - env: - CUSTOM_BUILD_FLAGS: ${{env.CUSTOM_FLAGS}} working-directory: software/o_c_REV/ run: | pio run -e custom diff --git a/software/o_c_REV/resources/parse_build_request.py b/software/o_c_REV/resources/parse_build_request.py index 1966c3649..76be508f5 100644 --- a/software/o_c_REV/resources/parse_build_request.py +++ b/software/o_c_REV/resources/parse_build_request.py @@ -27,13 +27,13 @@ custom_defines += " -DENABLE_APP_POLYLFO" if f.startswith('HARRING'): custom_defines += " -DENABLE_APP_H1200" - if f.startswith('BYTE'): + if f.startswith('BYTE') or f.startswith('VIZNUT'): custom_defines += " -DENABLE_APP_BYTEBEATGEN" if f.startswith('NEURAL'): custom_defines += " -DENABLE_APP_NEURAL_NETWORK" if f.startswith('DARKEST'): custom_defines += " -DENABLE_APP_DARKEST_TIMELINE" - if f.startswith('LOW-RENT'): + if f.startswith('LOW-RENT') or f.startswith('LORENZ'): custom_defines += " -DENABLE_APP_LORENZ" if f.startswith('COPIER'): custom_defines += " -DENABLE_APP_ASR" From e3a4fef5bd3c2d4137915293dffde84e77ddaf9e Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Wed, 10 Jan 2024 15:58:24 -0500 Subject: [PATCH 403/417] Fully functional linkable relabi app with four channel LFO output. --- software/o_c_REV/HEM_Relabi.ino | 320 ++++++++++++++++++ software/o_c_REV/HSRelabiManager.h | 82 +++++ software/o_c_REV/OC_options.h | 2 +- software/o_c_REV/hemisphere_config.h | 43 ++- software/o_c_REV/platformio.ini | 12 +- .../o_c_REV/vector_osc/HSVectorOscillator.h | 8 + 6 files changed, 455 insertions(+), 12 deletions(-) create mode 100644 software/o_c_REV/HEM_Relabi.ino create mode 100644 software/o_c_REV/HSRelabiManager.h diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino new file mode 100644 index 000000000..3dddbed5f --- /dev/null +++ b/software/o_c_REV/HEM_Relabi.ino @@ -0,0 +1,320 @@ +// Copyright (c) 2018, Jason Justian +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "vector_osc/HSVectorOscillator.h" +#include "vector_osc/WaveformManager.h" +#include "HSRelabiManager.h" + +class Relabi : public HemisphereApplet { +public: + + const char* applet_name() { + return "Relabi"; + } + + void Start() { + freq[0] = 200; + freq[1] = 300; + freq[2] = 500; + freq[3] = 700; + xmod[0] = 20; + xmod[1] = 20; + xmod[2] = 20; + xmod[3] = 20; + + + + for (uint8_t count = 0; count < 4; count++) { + if (freq[count] > 2000) {freqKnob[count] = (freq[count] - 2000) / 100 + 380;} + if (freq[count] < 2000) {freqKnob[count] = (freq[count] - 200) / 10 + 200;} + if (freq[count] < 200) {freqKnob[count] = freq[count];} + xmodKnob[count] = xmod[count]; + osc[count] = WaveformManager::VectorOscillatorFromWaveform(35); + osc[count].SetFrequency(freq[count]); + #ifdef BUCHLA_4U + osc[count].Offset((12 << 7) * 4); + osc[count].SetScale((12 << 7) * 4); + #else + osc[count].SetScale((12 << 7) * 6); + #endif + } + } + + void Controller() { + + RelabiManager * manager = RelabiManager::get(); + manager->RegisterRelabi(hemisphere); + linked = manager->IsLinked(); + int wave1; + int wave2; + uint8_t clkCalc; + + if (LEFT_HEMISPHERE) {clkCalc = 1;} + if (RIGHT_HEMISPHERE) {clkCalc = 3;} + + if (clkDiv == clkCalc) { + if (linked && hemisphere == RIGHT_HEMISPHERE) { + + // Linked: Receive lfo values from RelabiManager + manager->ReadValues(sample[0], sample[1], sample[2], sample[3]); + wave1 = sample[2]; + wave2 = sample[3]; + + } else { + cvIn = (In(0))/51.15; + + if (oldClock != Clock(0)) { //Clock(0) is TRIG1 port + if (oldClock == 1) { + for (uint8_t pcount = 0; pcount < 4; pcount++) { + if (Clock(0) > 0) { + int setPhase = round(phase[pcount] / 100 * 12); + osc[pcount].SetPhase(setPhase); + } + } + } + } + oldClock = Clock(0); + + + cvIn = 10000.0 * pow(max(((cvIn / 100.0) + 1.0), 0.01) / 2.0, 1.0); //set range for cvIn to be 0 to 10000 + for (uint8_t lfo = 0; lfo < 4; lfo++) { + // multiply an lfo's set frequency by the first cv input and by the crossmodulation amount multipled with the previous sample value of the preceding oscillator. Scale it and then add the lfo's set frequency times the cv input. + simfloat crossMod = (2 * xmod[lfo] * (((sample[(lfo + 3) % 4]) / 90.0)) / 100.0) - xmod[lfo]; + simfloat setFreq = (cvIn / 2500.0 * (freq[lfo] * crossMod/100 + freq[lfo])) * 16; + displayFreq[lfo] = setFreq; + osc[lfo].SetFrequency(setFreq); + sample[lfo] = 4608 + (osc[lfo].Next()/ 2); + } + + if (manager->IsLinked() && hemisphere == LEFT_HEMISPHERE) { + + // Linked: Send lfo values to RelabiManager + manager->WriteValues(sample[0], sample[1], sample[2], sample[3]); + } + + // CV1 outputs LFO1 // CV2 outputs LFO2 + wave1 = sample[0]; + wave2 = sample[1]; + } + + Out(0, wave1); + Out(1, wave2); + + } + + + clkDiv++; + clkDiv = clkDiv %32; + + + } + + void View() { + + if (linked && hemisphere == RIGHT_HEMISPHERE) { + + + gfxPrint(13, 15, "LINKED"); + + gfxPrint(2, 26, "C1"); + gfxPrint(17, 26, "C2"); + gfxPrint(32, 26, "C3"); + gfxPrint(47, 26, "C4"); + + gfxRect(2, 62 - (sample[0] / 300), 13, (sample[0] / 300)); + gfxRect(17, 62 - (sample[1] / 300), 13, (sample[1] / 300)); + gfxRect(32, 62 - (sample[2] / 300), 13, (sample[2] / 300)); + gfxRect(47, 62 - (sample[3] / 300), 13, (sample[3] / 300)); + + }else { + + // Display OSC label and value + gfxPrint(15, 15, "OSC"); + gfxPrint(35, 15, selectedChannel); + + // Display FREQ label and value + gfxPrint(1, 26, "FREQ"); + simfloat fDisplay = freq[selectedChannel]; + gfxPrint(1, 35, ones(fDisplay)); + if (fDisplay < 2000) { + if (fDisplay < 199) { + gfxPrint("."); + int h = hundredths(fDisplay); + if (h < 10) {gfxPrint("0");} + gfxPrint(h); + } + else { + gfxPrint("."); + int t = hundredths(fDisplay); + t = (t / 10) % 10; + gfxPrint(t); + } + } + + + + // Display MOD label and value + gfxPrint(31, 26, "XMOD"); + gfxPrint(31, 35, xmod[selectedChannel]); + + // Display PHAS label and value + gfxPrint(1, 46, "PHAS"); + gfxPrint(1, 55, phase[selectedChannel]); + + + + gfxRect(31, 62 - (sample[0] / 600), 5, (sample[0] / 600)); + gfxRect(37, 62 - (sample[1] / 600), 5, (sample[1] / 600)); + gfxRect(43, 62 - (sample[2] / 600), 5, (sample[2] / 600)); + gfxRect(49, 62 - (sample[3] / 600), 5, (sample[3] / 600)); + + + switch (selectedParam) { + case 0: + gfxCursor(15, 23, 30); + break; + case 1: + gfxCursor(1, 43, 30); + break; + case 2: + gfxCursor(31, 43, 30); + break; + case 3: + gfxCursor(1, 63, 30); + break; + case 4: + gfxCursor(31, 63, 30); + break; + } + } + } + + void OnButtonPress() { + ++cursor; + cursor = cursor % 4; + selectedParam = cursor; + ResetCursor(); + } + + void OnEncoderMove(int direction) { + switch (selectedParam) { + case 0: // Cycle through parameters when selecting OSC + selectedChannel = selectedChannel + direction + 4; + selectedChannel = selectedChannel % 4; + break; + case 1: // FREQ (0-20.0) + freqKnob[selectedChannel] += direction; + if (freqKnob[selectedChannel] < 0) {freqKnob[selectedChannel] = 510;} + if (freqKnob[selectedChannel] > 510) {freqKnob[selectedChannel] = 0;} + if (freqKnob[selectedChannel] < 200) { + freq[selectedChannel] = freqKnob[selectedChannel]; + } + else if (freqKnob[selectedChannel] < 380) { + freq[selectedChannel] = 200 + ((freqKnob[selectedChannel] - 200) * 10); + } + else { + freq[selectedChannel] = 2000 + ((freqKnob[selectedChannel] - 380) * 100); + } + break; + case 2: // XMOD (0-100) + xmodKnob[selectedChannel] += (direction); + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 101; + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 101; + xmod[selectedChannel] = xmodKnob[selectedChannel]; + break; + case 3: // PHAS (0-100) + phase[selectedChannel] += direction; + phase[selectedChannel] = phase[selectedChannel] + 101; + phase[selectedChannel] = phase[selectedChannel] % 101; + break; + + } + + } + + uint64_t OnDataRequest() { + uint64_t data = 0; + return data; + } + + void OnDataReceive(uint64_t data) { + + } + +protected: + void SetHelp() { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Reset 2=NA"; + help[HEMISPHERE_HELP_CVS] = "1=AllFreq 2=NA"; + help[HEMISPHERE_HELP_OUTS] = "A=LFO1 B=LFO2"; + help[HEMISPHERE_HELP_ENCODER] = "Freq/XMod/Phase"; + // "------------------" <-- Size Guide + } + +private: + static constexpr int pow10_lut[] = { 1, 10, 100, 1000 }; + int cursor; // 0=Freq A; 1=Cross Mod A; 2=Phase A; 3=Freq B; 4=Cross Mod B; etc. + VectorOscillator osc[4]; + constexpr static uint8_t ch = 4; + constexpr static uint8_t numParams = 5; + uint8_t selectedOsc; + simfloat freq[ch]; // in centihertz + uint8_t xmod[ch]; + uint8_t selectedXmod; + uint8_t phase[ch]; + int selectedChannel = 0; + uint8_t selectedParam = 0; + int sample[ch]; + simfloat outFreq[ch]; + simfloat freqKnob[4]; + simfloat cvIn; + uint16_t xmodKnob[4]; + uint8_t countLimit = 0; + int waveform_number[4]; + int ones(int n) {return (n / 100);} + int hundredths(int n) {return (n % 100);} + int valueToDisplay; + uint8_t clkDiv = 0; // clkDiv allows us to calculate every other tick to save cycles + uint8_t clkDivDisplay = 0; // clkDivDisplay allows us to update the display fewer times per second + uint8_t oldClock = 0; + int displayFreq[ch]; + bool linked; +}; + + + +//////////////////////////////////////////////////////////////////////////////// +//// Hemisphere Applet Functions +/// +/// Once you run the find-and-replace to make these refer to Relabi, +/// it's usually not necessary to do anything with these functions. You +/// should prefer to handle things in the HemisphereApplet child class +/// above. +//////////////////////////////////////////////////////////////////////////////// +Relabi Relabi_instance[2]; + +void Relabi_Start(bool hemisphere) {Relabi_instance[hemisphere].BaseStart(hemisphere);} +void Relabi_Controller(bool hemisphere, bool forwarding) {Relabi_instance[hemisphere].BaseController(forwarding);} +void Relabi_View(bool hemisphere) {Relabi_instance[hemisphere].BaseView();} +void Relabi_OnButtonPress(bool hemisphere) {Relabi_instance[hemisphere].OnButtonPress();} +void Relabi_OnEncoderMove(bool hemisphere, int direction) {Relabi_instance[hemisphere].OnEncoderMove(direction);} +void Relabi_ToggleHelpScreen(bool hemisphere) {Relabi_instance[hemisphere].HelpScreen();} +uint64_t Relabi_OnDataRequest(bool hemisphere) {return Relabi_instance[hemisphere].OnDataRequest();} +void Relabi_OnDataReceive(bool hemisphere, uint64_t data) {Relabi_instance[hemisphere].OnDataReceive(data);} diff --git a/software/o_c_REV/HSRelabiManager.h b/software/o_c_REV/HSRelabiManager.h new file mode 100644 index 000000000..168553eee --- /dev/null +++ b/software/o_c_REV/HSRelabiManager.h @@ -0,0 +1,82 @@ +// Copyright (c) 2024, Samuel Burt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + + + +class RelabiManager { + static RelabiManager *instance; + + int lfo1; + int lfo2; + int lfo3; + int lfo4; + uint8_t leftOn; + uint8_t rightOn; + bool linked; + uint32_t registered[2]; + uint32_t lastRegistered[2]; + uint8_t hemRelabi; + + + RelabiManager() { + leftOn = 0; + rightOn = 0; + linked = false; + registered[LEFT_HEMISPHERE] = 0; + registered[RIGHT_HEMISPHERE] = 0; + } + +public: + + static RelabiManager *get() { + if (!instance) instance = new RelabiManager; + return instance; + } + + void RegisterRelabi(bool hemisphere) { + hemRelabi = hemisphere; + registered[hemisphere] = OC::CORE::ticks; + } + + bool IsLinked() { + uint32_t t = OC::CORE::ticks; + return ((t - registered[LEFT_HEMISPHERE] < 160) + && (t - registered[RIGHT_HEMISPHERE] < 160)); + } + + void WriteValues(int value1, int value2, int value3, int value4) { + // Update individual variables + lfo1 = value1; + lfo2 = value2; + lfo3 = value3; + lfo4 = value4; + } + + void ReadValues(int &value1, int &value2, int &value3, int &value4) const { + // Read values into the referenced variables + value1 = lfo1; + value2 = lfo2; + value3 = lfo3; + value4 = lfo4; + } +}; + +RelabiManager *RelabiManager::instance = 0; + diff --git a/software/o_c_REV/OC_options.h b/software/o_c_REV/OC_options.h index 430c0c4a2..3a1d4f139 100644 --- a/software/o_c_REV/OC_options.h +++ b/software/o_c_REV/OC_options.h @@ -18,7 +18,7 @@ /* ------------ print debug messages to USB serial -------------------------------------------------- */ //#define PRINT_DEBUG /* ------------ flip screen / IO mapping ------------------------------------------------------------ */ -//#define FLIP_180 +// #define FLIP_180 /* ------------ invert screen pixels ---------------------------------------------------------------- */ //#define INVERT_DISPLAY /* ------------ use DAC8564 ------------------------------------------------------------------------- */ diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 9b1b14e83..29155a114 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -13,13 +13,49 @@ ////////////////// id cat class name #define HEMISPHERE_APPLETS { \ + DECLARE_APPLET( 47, 0x09, ASR), \ + DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ + DECLARE_APPLET( 41, 0x41, Binary), \ + DECLARE_APPLET( 55, 0x80, BootsNCat), \ + DECLARE_APPLET( 51, 0x80, BugCrack), \ + DECLARE_APPLET( 31, 0x04, Burst), \ + DECLARE_APPLET( 12, 0x10, Calculate), \ + DECLARE_APPLET( 64, 0x08, Chordinator), \ + DECLARE_APPLET( 6, 0x04, ClockDivider), \ + DECLARE_APPLET( 28, 0x04, ClockSkip), \ + DECLARE_APPLET( 30, 0x10, Compare), \ + DECLARE_APPLET( 18, 0x02, DualTM), \ + DECLARE_APPLET( 7, 0x01, EbbAndLfo), \ + DECLARE_APPLET( 42, 0x11, EnvFollow), \ + DECLARE_APPLET( 15, 0x02, EuclidX), \ + DECLARE_APPLET( 22, 0x01, GameOfLife), \ + DECLARE_APPLET( 16, 0x80, LoFiPCM), \ + DECLARE_APPLET( 10, 0x44, Logic), \ + DECLARE_APPLET( 73, 0x08, MultiScale), \ + DECLARE_APPLET( 71, 0x02, Pigeons), \ + DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ + DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ + DECLARE_APPLET (65, 0x01, Relabi), \ + DECLARE_APPLET( 69, 0x01, RndWalk), \ + DECLARE_APPLET( 44, 0x01, RunglBook), \ + DECLARE_APPLET( 40, 0x40, Schmitt), \ + DECLARE_APPLET( 23, 0x80, Scope), \ + DECLARE_APPLET( 14, 0x02, SequenceX), \ + DECLARE_APPLET( 48, 0x45, ShiftGate), \ + DECLARE_APPLET( 19, 0x01, Slew), \ + DECLARE_APPLET( 61, 0x01, Stairs), \ + DECLARE_APPLET( 39, 0x80, Tuner), \ + DECLARE_APPLET( 43, 0x10, Voltage), \ +} +/* + DECLARE_APPLET(127, 0x80, DIAGNOSTIC), \ + 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), \ @@ -76,8 +112,5 @@ 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), \ + DECLARE_APPLET( 43, 0x10, Voltage); */ diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index b39a4d7e5..92828f3f4 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -11,12 +11,12 @@ [platformio] src_dir = . default_envs = - pewpewpew - pewpewvor - wepwepwep +; pewpewpew +; pewpewvor +; wepwepwep wepwepvor - T40 - T41 +; T40 +; T41 [env] platform = teensy@4.17.0 @@ -116,7 +116,7 @@ build_flags = build_flags = ${env:pewpewpew.build_flags} -DVOR - -DFLIP_180 + #-DFLIP_180 [env:main] build_flags = diff --git a/software/o_c_REV/vector_osc/HSVectorOscillator.h b/software/o_c_REV/vector_osc/HSVectorOscillator.h index 532d82f20..2e4733b7e 100644 --- a/software/o_c_REV/vector_osc/HSVectorOscillator.h +++ b/software/o_c_REV/vector_osc/HSVectorOscillator.h @@ -136,6 +136,14 @@ class VectorOscillator { eoc = !cycle; } + void SetPhase(uint8_t index) { + segment_index = index; + signal = scale_level(segments[segment_count - 1].level); + rise = calculate_rise(segment_index); + sustained = 0; + eoc = !cycle; + } + int32_t Next() { // For non-cycling waveforms, send the level of the last step if eoc if (eoc && cycle == 0) { From 868169a3069e8384cd2a58f4816f27b37abb2f16 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Wed, 10 Jan 2024 15:58:49 -0500 Subject: [PATCH 404/417] Reactivated building for all different platforms. --- software/o_c_REV/platformio.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 92828f3f4..71f9b7218 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -11,12 +11,12 @@ [platformio] src_dir = . default_envs = -; pewpewpew -; pewpewvor -; wepwepwep + pewpewpew + pewpewvor + wepwepwep wepwepvor -; T40 -; T41 + T40 + T41 [env] platform = teensy@4.17.0 From 5b561cf1dd19b43d346c9c24a9c433ee50404bbf Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Thu, 11 Jan 2024 09:54:48 -0500 Subject: [PATCH 405/417] Reallowed building for all platforms. --- software/o_c_REV/platformio.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 71f9b7218..92828f3f4 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -11,12 +11,12 @@ [platformio] src_dir = . default_envs = - pewpewpew - pewpewvor - wepwepwep +; pewpewpew +; pewpewvor +; wepwepwep wepwepvor - T40 - T41 +; T40 +; T41 [env] platform = teensy@4.17.0 From 6910670d1d2c53b597a43dabebf67854117eaf39 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Thu, 11 Jan 2024 10:15:11 -0500 Subject: [PATCH 406/417] Update README.md --- README.md | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 82e58581c..a91c0e24c 100755 --- a/README.md +++ b/README.md @@ -1,32 +1,23 @@ [![PlatformIO CI](https://github.com/djphazer/O_C-BenisphereSuite/actions/workflows/firmware.yml/badge.svg)](https://github.com/djphazer/O_C-BenisphereSuite/actions/workflows/firmware.yml) -Phazerville Suite - an active o_C firmware fork +Phazerville Suite with Relabi - an active o_C firmware fork === [![SynthDad's video overview](http://img.youtube.com/vi/XRGlAmz3AKM/0.jpg)](http://www.youtube.com/watch?v=XRGlAmz3AKM "Phazerville; newest firmware for Ornament and Crime. Tutorial and patch ideas") Watch SynthDad's **video overview** (above) or check the [**Wiki**](https://github.com/djphazer/O_C-BenisphereSuite/wiki) for more info. [Download it here](https://github.com/djphazer/O_C-BenisphereSuite/releases). -## Stolen Ornaments +## An active fork expanding upon Hemisphere Suite. -Using [**Benisphere**](https://github.com/benirose/O_C-BenisphereSuite) as a starting point, this branch takes the **Hemisphere Suite** in new directions, with several new applets and enhancements to existing ones. I wanted to collect all the bleeding-edge features from other clever developers, with the goal of cramming as much functionality and flexibility into the nifty dual-applet design as possible! +This is a fork of djphazer's Phazerville Suite which is a fork of Benisphere which is a fork of Hemisphere! The code is firmware for the Ornament & Crime, a Eurorack synth module that can act as many different modules as selected by the user. This firmware ads the Relabi app that generates chaotic but deterministic control voltage. In addition, it adds a function to the HSVectorOscillator.h file and provides a controller file that allows two Relabi apps to be linked, providing four related chaotic LFOs. -I've also included **all of the stock O&C firmware apps**, but they don't all fit in one .hex. As a courtesy, I provide **3 different build choices** with various combinations of Apps in my [**Releases**](https://github.com/djphazer/O_C-BenisphereSuite/releases). I think of it like the boxed set of a movie trilogy or whatever. The O&C Saga. 4 different hardware format options. Free and Open Source, baby! +Read more about the suite [here](https://github.com/djphazer/O_C-Phazerville). Please, go there and read about all the people who have contributed code. -You can also customize the `platformio.ini` file to mix & match for yourself ;-) +## What is Relabi. + +Relabi is an alternative method of defining musical time. While rhythm is concerned with recurring pulses, relabi always slips the pulse. You can read all about the concept in [John Berndt's essay]((https://johnberndt.org/relabi/). -### New Crimes I've Committed -* 4 Presets in the new [**Hemisphere Config**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Hemisphere-Config) -* Modal-editing style cursor navigation (and other usability tweaks) -* Expanded internal [**Clock Setup**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Clock-Setup) -* New Apps: [**Scenes**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Scenes) and [**Calibr8or**](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Calibr8or) -* **[DualTM](https://github.com/djphazer/O_C-BenisphereSuite/wiki/DualTM)** - two 32-bit shift registers. Assignable I/O. -* **[EbbAndLfo](https://github.com/djphazer/O_C-BenisphereSuite/wiki/Ebb-&-LFO)** (via [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/trig-and-tides)) - mini implementation of MI Tides, with v/oct tracking -* **[EuclidX](https://github.com/djphazer/O_C-BenisphereSuite/wiki/EuclidX)** - AnnularFusion got a makeover, now includes padding, configurable CV input modulation - (credit to [qiemem](https://github.com/qiemem/O_C-HemisphereSuite/tree/expanded-clock-div) and [adegani](https://github.com/adegani/O_C-HemisphereSuite)) -* LoFi Tape has been transformed into **LoFi Echo** - a crazy bitcrushing digital delay line - (credit to [armandvedel](https://github.com/armandvedel/O_C-HemisphereSuite_log) for the initial idea) -* Sequence5 -> **SequenceX** (8 steps max) (from [logarhythm](https://github.com/Logarhythm1/O_C-HemisphereSuite)) -Plus lots of other small tweaks + experimental applets. ### How To Get It @@ -44,19 +35,3 @@ Have a look inside `platformio.ini` for alternative build environment configurat _**Pro-tip**_: If you decide to fork the project, and enable GitHub Actions on your own repo, GitHub will build the files for you... ;) -## Credits - -Many minds before me have made this project possible. Attribution is present in the git commit log and within individual files. -Shoutouts: -* **[Logarhythm1](https://github.com/Logarhythm1)** for the incredible **TB-3PO** sequencer, as well as **Stairs**. -* **[herrkami](https://github.com/herrkami)** and **Ben Rosenbach** for their work on **BugCrack**. -* **[benirose](https://github.com/benirose)** also gets massive props for **DrumMap** and the **ProbDiv / ProbMelo** applets. -* **[qiemem](https://github.com/qiemem)** (Bryan Head) for the **Ebb&LFO** applet and its _tideslite_ backend, among other things. - -And, of course, thank you to **[Chysn](https://github.com/Chysn)** for the clever applet framework from which we've all drawn inspiration. - -This is a fork of [Benisphere Suite](https://github.com/benirose/O_C-BenisphereSuite) which is a fork of [Hemisphere Suite](https://github.com/Chysn/O_C-HemisphereSuite) by Jason Justian (aka chysn). - -ornament**s** & crime**s** is a collaborative project by Patrick Dowling (aka pld), mxmxmx and Tim Churches (aka bennelong.bicyclist) (though mostly by pld and bennelong.bicyclist). it **(considerably) extends** the original firmware for the o_C / ASR eurorack module, designed by mxmxmx. - -http://ornament-and-cri.me/ From 68f1364934ed1480af21f49bf7c18ac711dd9698 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Thu, 11 Jan 2024 18:07:55 -0500 Subject: [PATCH 407/417] Custom hemisphere_config.h for preferred hemisphere apps. Please, change the file based on which apps you want. --- software/o_c_REV/hemisphere_config.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 29155a114..747eb0918 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -12,24 +12,21 @@ // alone to avoid breaking forked codebases by other developers. ////////////////// id cat class name + + #define HEMISPHERE_APPLETS { \ DECLARE_APPLET( 47, 0x09, ASR), \ - DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ DECLARE_APPLET( 41, 0x41, Binary), \ DECLARE_APPLET( 55, 0x80, BootsNCat), \ DECLARE_APPLET( 51, 0x80, BugCrack), \ DECLARE_APPLET( 31, 0x04, Burst), \ DECLARE_APPLET( 12, 0x10, Calculate), \ - DECLARE_APPLET( 64, 0x08, Chordinator), \ DECLARE_APPLET( 6, 0x04, ClockDivider), \ DECLARE_APPLET( 28, 0x04, ClockSkip), \ DECLARE_APPLET( 30, 0x10, Compare), \ DECLARE_APPLET( 18, 0x02, DualTM), \ - DECLARE_APPLET( 7, 0x01, EbbAndLfo), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 15, 0x02, EuclidX), \ - DECLARE_APPLET( 22, 0x01, GameOfLife), \ - DECLARE_APPLET( 16, 0x80, LoFiPCM), \ DECLARE_APPLET( 10, 0x44, Logic), \ DECLARE_APPLET( 73, 0x08, MultiScale), \ DECLARE_APPLET( 71, 0x02, Pigeons), \ From 6a7978081c76153936af420fb378ce5f8167cc9f Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Fri, 12 Jan 2024 11:24:43 -0500 Subject: [PATCH 408/417] Relabi app updated. Now, when linked, the right side provides two new onscreen controls. The first multiplies the frequency of all the LFOs. The second divides them. Also, updated the help page for the right hemisphere when linked to accurately show inputs, outputs, and encoder functions. --- software/o_c_REV/HEM_Relabi.ino | 179 +++++++++++++++++++++-------- software/o_c_REV/HSRelabiManager.h | 18 +++ 2 files changed, 148 insertions(+), 49 deletions(-) diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino index 3dddbed5f..af78ca70e 100644 --- a/software/o_c_REV/HEM_Relabi.ino +++ b/software/o_c_REV/HEM_Relabi.ino @@ -33,13 +33,15 @@ public: freq[0] = 200; freq[1] = 300; freq[2] = 500; - freq[3] = 700; + freq[3] = 100; xmod[0] = 20; xmod[1] = 20; xmod[2] = 20; xmod[3] = 20; - - + freqLinkMul = 1; + freqLinkDiv = 1; + freqKnobMul = 1; + freqKnobDiv = 1; for (uint8_t count = 0; count < 4; count++) { if (freq[count] > 2000) {freqKnob[count] = (freq[count] - 2000) / 100 + 380;} @@ -66,18 +68,35 @@ public: int wave2; uint8_t clkCalc; + if (LEFT_HEMISPHERE) {clkCalc = 1;} if (RIGHT_HEMISPHERE) {clkCalc = 3;} if (clkDiv == clkCalc) { + + if (linked && hemisphere == LEFT_HEMISPHERE) { + // Linked: read the frequency multiplier from the right hemispher to the left hemisphere from RelabiManager. + manager->ReadMul(mulLink); + manager->ReadDiv(divLink); + } else { + mulLink = 1; + divLink = 1; + } + if (linked && hemisphere == RIGHT_HEMISPHERE) { - + + // Linked: write the frequency multiplier from the right hemisphere to the RelabiManager. + manager->WriteMul(freqLinkMul); + manager->WriteDiv(freqLinkDiv); + + // Linked: Receive lfo values from RelabiManager manager->ReadValues(sample[0], sample[1], sample[2], sample[3]); wave1 = sample[2]; wave2 = sample[3]; } else { + cvIn = (In(0))/51.15; if (oldClock != Clock(0)) { //Clock(0) is TRIG1 port @@ -97,7 +116,7 @@ public: for (uint8_t lfo = 0; lfo < 4; lfo++) { // multiply an lfo's set frequency by the first cv input and by the crossmodulation amount multipled with the previous sample value of the preceding oscillator. Scale it and then add the lfo's set frequency times the cv input. simfloat crossMod = (2 * xmod[lfo] * (((sample[(lfo + 3) % 4]) / 90.0)) / 100.0) - xmod[lfo]; - simfloat setFreq = (cvIn / 2500.0 * (freq[lfo] * crossMod/100 + freq[lfo])) * 16; + simfloat setFreq = (static_cast(mulLink) / divLink * cvIn / 2500.0 * (freq[lfo] * crossMod/100 + freq[lfo])) * 16; displayFreq[lfo] = setFreq; osc[lfo].SetFrequency(setFreq); sample[lfo] = 4608 + (osc[lfo].Next()/ 2); @@ -131,17 +150,31 @@ public: if (linked && hemisphere == RIGHT_HEMISPHERE) { - gfxPrint(13, 15, "LINKED"); + gfxPrint(2, 15, "FREQ"); + //Display LINKED GLOBAL FREQ label and value + gfxPrint(26, 15, "*"); + gfxPrint(32, 15, freqKnobMul); + gfxPrint(45, 15, "/"); + gfxPrint(50, 15, freqKnobDiv); gfxPrint(2, 26, "C1"); gfxPrint(17, 26, "C2"); gfxPrint(32, 26, "C3"); gfxPrint(47, 26, "C4"); - gfxRect(2, 62 - (sample[0] / 300), 13, (sample[0] / 300)); - gfxRect(17, 62 - (sample[1] / 300), 13, (sample[1] / 300)); - gfxRect(32, 62 - (sample[2] / 300), 13, (sample[2] / 300)); - gfxRect(47, 62 - (sample[3] / 300), 13, (sample[3] / 300)); + gfxRect(2, 62 - (sample[0] / 400), 13, (sample[0] / 400)); + gfxRect(17, 62 - (sample[1] / 400), 13, (sample[1] / 400)); + gfxRect(32, 62 - (sample[2] / 400), 13, (sample[2] / 400)); + gfxRect(47, 62 - (sample[3] / 400), 13, (sample[3] / 400)); + + switch (selectedParam) { + case 0: + gfxCursor(32, 23, 13); + break; + case 1: + gfxCursor(51, 23, 13); + break; + } }else { @@ -207,46 +240,78 @@ public: } void OnButtonPress() { - ++cursor; - cursor = cursor % 4; - selectedParam = cursor; - ResetCursor(); + if (linked && hemisphere == RIGHT_HEMISPHERE) { + ++cursor; + cursor = cursor % 2; + selectedParam = cursor; + ResetCursor(); + } else { + ++cursor; + cursor = cursor % 4; + selectedParam = cursor; + ResetCursor(); + } } void OnEncoderMove(int direction) { - switch (selectedParam) { - case 0: // Cycle through parameters when selecting OSC - selectedChannel = selectedChannel + direction + 4; - selectedChannel = selectedChannel % 4; - break; - case 1: // FREQ (0-20.0) - freqKnob[selectedChannel] += direction; - if (freqKnob[selectedChannel] < 0) {freqKnob[selectedChannel] = 510;} - if (freqKnob[selectedChannel] > 510) {freqKnob[selectedChannel] = 0;} - if (freqKnob[selectedChannel] < 200) { - freq[selectedChannel] = freqKnob[selectedChannel]; + + + + if (linked && hemisphere == RIGHT_HEMISPHERE) { + switch (selectedParam) { + //Linked and right hemisphere: controls only global frequency multiplier or divider. + + case 0: //Global frequency multiplier + freqKnobMul += direction; + freqKnobMul = freqKnobMul + 65; + freqKnobMul = freqKnobMul % 65; + freqLinkMul = freqKnobMul; + break; + case 1: //Global frequency divider + freqKnobDiv += direction; + freqKnobDiv = freqKnobDiv + 65; + freqKnobDiv = freqKnobDiv % 65; + freqLinkDiv = freqKnobDiv; + break; } - else if (freqKnob[selectedChannel] < 380) { - freq[selectedChannel] = 200 + ((freqKnob[selectedChannel] - 200) * 10); + } else { + switch (selectedParam) { + //Not linked or left hemisphere: controls select LFO, freq, xmod, and phase. + + case 0: // Cycle through parameters when selecting OSC + selectedChannel = selectedChannel + direction + 4; + selectedChannel = selectedChannel % 4; + break; + case 1: // FREQ (0-20.0) + freqKnob[selectedChannel] += direction; + if (freqKnob[selectedChannel] < 0) {freqKnob[selectedChannel] = 510;} + if (freqKnob[selectedChannel] > 510) {freqKnob[selectedChannel] = 0;} + if (freqKnob[selectedChannel] < 200) { + freq[selectedChannel] = freqKnob[selectedChannel]; + } + else if (freqKnob[selectedChannel] < 380) { + freq[selectedChannel] = 200 + ((freqKnob[selectedChannel] - 200) * 10); + } + else { + freq[selectedChannel] = 2000 + ((freqKnob[selectedChannel] - 380) * 100); + } + break; + case 2: // XMOD (0-100) + xmodKnob[selectedChannel] += (direction); + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 101; + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 101; + xmod[selectedChannel] = xmodKnob[selectedChannel]; + break; + case 3: // PHAS (0-100) + phase[selectedChannel] += direction; + phase[selectedChannel] = phase[selectedChannel] + 101; + phase[selectedChannel] = phase[selectedChannel] % 101; + break; } - else { - freq[selectedChannel] = 2000 + ((freqKnob[selectedChannel] - 380) * 100); - } - break; - case 2: // XMOD (0-100) - xmodKnob[selectedChannel] += (direction); - xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 101; - xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 101; - xmod[selectedChannel] = xmodKnob[selectedChannel]; - break; - case 3: // PHAS (0-100) - phase[selectedChannel] += direction; - phase[selectedChannel] = phase[selectedChannel] + 101; - phase[selectedChannel] = phase[selectedChannel] % 101; - break; - } + + } uint64_t OnDataRequest() { @@ -260,12 +325,22 @@ public: protected: void SetHelp() { - // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Reset 2=NA"; - help[HEMISPHERE_HELP_CVS] = "1=AllFreq 2=NA"; - help[HEMISPHERE_HELP_OUTS] = "A=LFO1 B=LFO2"; - help[HEMISPHERE_HELP_ENCODER] = "Freq/XMod/Phase"; - // "------------------" <-- Size Guide + + if (linked && hemisphere == RIGHT_HEMISPHERE) { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=NA 2=NA"; + help[HEMISPHERE_HELP_CVS] = "1=NA 2=NA"; + help[HEMISPHERE_HELP_OUTS] = "A=LFO3 B=LFO4"; + help[HEMISPHERE_HELP_ENCODER] = "GlobalFreqMul/Div"; + // + } else { + // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_DIGITALS] = "1=Reset 2=NA"; + help[HEMISPHERE_HELP_CVS] = "1=AllFreq 2=NA"; + help[HEMISPHERE_HELP_OUTS] = "A=LFO1 B=LFO2"; + help[HEMISPHERE_HELP_ENCODER] = "Freq/XMod/Phase"; + // "------------------" <-- Size Guide + } } private: @@ -295,6 +370,12 @@ private: uint8_t clkDivDisplay = 0; // clkDivDisplay allows us to update the display fewer times per second uint8_t oldClock = 0; int displayFreq[ch]; + uint8_t freqLinkMul; + uint8_t freqKnobMul; + uint8_t freqLinkDiv; + uint8_t freqKnobDiv; + uint8_t mulLink; + uint8_t divLink; bool linked; }; diff --git a/software/o_c_REV/HSRelabiManager.h b/software/o_c_REV/HSRelabiManager.h index 168553eee..924b6d8b2 100644 --- a/software/o_c_REV/HSRelabiManager.h +++ b/software/o_c_REV/HSRelabiManager.h @@ -33,6 +33,8 @@ class RelabiManager { uint32_t registered[2]; uint32_t lastRegistered[2]; uint8_t hemRelabi; + uint8_t linkedFreqMultiplier; + uint8_t linkedFreqDivider; RelabiManager() { @@ -76,6 +78,22 @@ class RelabiManager { value3 = lfo3; value4 = lfo4; } + + void WriteMul(uint8_t lfm) { + linkedFreqMultiplier = lfm; + } + + void ReadMul(uint8_t &lfm) const { + lfm = linkedFreqMultiplier; + } + + void WriteDiv(uint8_t ldm) { + linkedFreqDivider = ldm; + } + + void ReadDiv(uint8_t &ldm) { + ldm = linkedFreqDivider; + } }; RelabiManager *RelabiManager::instance = 0; From 34b043eff0e616d86e439c17454e717c869f1161 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Mon, 15 Jan 2024 16:23:02 -0500 Subject: [PATCH 409/417] Relabi App updated to have better cross-modulation response. Linked mode works well and provides a mechanism to modulate all LFO frequencies by multiplication and division. --- software/o_c_REV/HEM_Relabi.ino | 56 +++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino index af78ca70e..6087f7b5b 100644 --- a/software/o_c_REV/HEM_Relabi.ino +++ b/software/o_c_REV/HEM_Relabi.ino @@ -50,12 +50,12 @@ public: xmodKnob[count] = xmod[count]; osc[count] = WaveformManager::VectorOscillatorFromWaveform(35); osc[count].SetFrequency(freq[count]); - #ifdef BUCHLA_4U - osc[count].Offset((12 << 7) * 4); - osc[count].SetScale((12 << 7) * 4); - #else - osc[count].SetScale((12 << 7) * 6); - #endif + // #ifdef BUCHLA_4U + // osc[count].Offset((12 << 7) * 4); + // osc[count].SetScale((12 << 7) * 4); + // #else + // osc[count].SetScale((12 << 7) * 6); + // #endif } } @@ -111,15 +111,19 @@ public: } oldClock = Clock(0); - cvIn = 10000.0 * pow(max(((cvIn / 100.0) + 1.0), 0.01) / 2.0, 1.0); //set range for cvIn to be 0 to 10000 for (uint8_t lfo = 0; lfo < 4; lfo++) { + + osc[lfo].SetScale(1000); // multiply an lfo's set frequency by the first cv input and by the crossmodulation amount multipled with the previous sample value of the preceding oscillator. Scale it and then add the lfo's set frequency times the cv input. - simfloat crossMod = (2 * xmod[lfo] * (((sample[(lfo + 3) % 4]) / 90.0)) / 100.0) - xmod[lfo]; - simfloat setFreq = (static_cast(mulLink) / divLink * cvIn / 2500.0 * (freq[lfo] * crossMod/100 + freq[lfo])) * 16; + //simfloat crossMod = (static_cast(2.0) * xmod[lfo] * (sample[(lfo + 3) % 4]) / 921600.0) - static_cast(1.0); + simfloat crossMod = (static_cast(xmod[0]) / 100.0) * ((static_cast(sample[3]) * 2.0) - 1000.0) / 100.0; + if (crossMod < 0) {crossMod = -1 * crossMod;} + simfloat setFreq = static_cast(mulLink) / divLink * (cvIn / 2500.0 * ((freq[lfo] * crossMod) + freq[lfo])) * 16; displayFreq[lfo] = setFreq; osc[lfo].SetFrequency(setFreq); - sample[lfo] = 4608 + (osc[lfo].Next()/ 2); + //sample[lfo] = osc[lfo].Next() + 3; + sample[lfo] = 503 + (osc[lfo].Next()/ 2); } if (manager->IsLinked() && hemisphere == LEFT_HEMISPHERE) { @@ -162,10 +166,10 @@ public: gfxPrint(32, 26, "C3"); gfxPrint(47, 26, "C4"); - gfxRect(2, 62 - (sample[0] / 400), 13, (sample[0] / 400)); - gfxRect(17, 62 - (sample[1] / 400), 13, (sample[1] / 400)); - gfxRect(32, 62 - (sample[2] / 400), 13, (sample[2] / 400)); - gfxRect(47, 62 - (sample[3] / 400), 13, (sample[3] / 400)); + gfxRect(2, 62 - (sample[0] / 40), 13, (sample[0] / 40)); + gfxRect(17, 62 - (sample[1] / 40), 13, (sample[1] / 40)); + gfxRect(32, 62 - (sample[2] / 40), 13, (sample[2] / 40)); + gfxRect(47, 62 - (sample[3] / 40), 13, (sample[3] / 40)); switch (selectedParam) { case 0: @@ -211,12 +215,18 @@ public: gfxPrint(1, 46, "PHAS"); gfxPrint(1, 55, phase[selectedChannel]); - - - gfxRect(31, 62 - (sample[0] / 600), 5, (sample[0] / 600)); - gfxRect(37, 62 - (sample[1] / 600), 5, (sample[1] / 600)); - gfxRect(43, 62 - (sample[2] / 600), 5, (sample[2] / 600)); - gfxRect(49, 62 - (sample[3] / 600), 5, (sample[3] / 600)); + //WORKING ON DISPLAYING INFORMATION ABOUT WHAT'S HAPPENING WITH CROSS MODULATION. I think the bit depth is 9216. I'm trying to make this bipolar. + //gfxPrint(31, 46, (static_cast(2.0) * xmod[0] * (sample[3]) / 9216.0) - static_cast(4608.0)); + float crossMod = (static_cast(xmod[0]) / 100.0) * ((static_cast(sample[3]) * 2.0) - 1000.0) / 10; + //gfxPrint(31, 46, crossMod); + //gfxPrint(31, 55, osc[3].Next()); + //gfxPrint(31, 55, sample[3]); + //gfxPrint(31, 55, displayFreq[0]); + + gfxRect(31, 62 - (sample[0] / 60), 5, (sample[0] / 60)); + gfxRect(37, 62 - (sample[1] / 60), 5, (sample[1] / 60)); + gfxRect(43, 62 - (sample[2] / 60), 5, (sample[2] / 60)); + gfxRect(49, 62 - (sample[3] / 60), 5, (sample[3] / 60)); switch (selectedParam) { @@ -268,9 +278,7 @@ public: freqLinkMul = freqKnobMul; break; case 1: //Global frequency divider - freqKnobDiv += direction; - freqKnobDiv = freqKnobDiv + 65; - freqKnobDiv = freqKnobDiv % 65; + freqKnobDiv = (freqKnobDiv + 64 + direction - 1) % 64 + 1; // Cycle from 1 to 64 freqLinkDiv = freqKnobDiv; break; } @@ -369,7 +377,7 @@ private: uint8_t clkDiv = 0; // clkDiv allows us to calculate every other tick to save cycles uint8_t clkDivDisplay = 0; // clkDivDisplay allows us to update the display fewer times per second uint8_t oldClock = 0; - int displayFreq[ch]; + simfloat displayFreq[ch]; uint8_t freqLinkMul; uint8_t freqKnobMul; uint8_t freqLinkDiv; From 20b105ba88e4c7c95deca03c94a874f95829b8b5 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Wed, 17 Jan 2024 16:08:00 -0500 Subject: [PATCH 410/417] In progress fixes to Relabi. --- software/o_c_REV/HEM_Relabi.ino | 99 ++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino index 6087f7b5b..e24be855d 100644 --- a/software/o_c_REV/HEM_Relabi.ino +++ b/software/o_c_REV/HEM_Relabi.ino @@ -50,12 +50,7 @@ public: xmodKnob[count] = xmod[count]; osc[count] = WaveformManager::VectorOscillatorFromWaveform(35); osc[count].SetFrequency(freq[count]); - // #ifdef BUCHLA_4U - // osc[count].Offset((12 << 7) * 4); - // osc[count].SetScale((12 << 7) * 4); - // #else - // osc[count].SetScale((12 << 7) * 6); - // #endif + } } @@ -74,8 +69,11 @@ public: if (clkDiv == clkCalc) { + + + if (linked && hemisphere == LEFT_HEMISPHERE) { - // Linked: read the frequency multiplier from the right hemispher to the left hemisphere from RelabiManager. + // Linked: read the frequency multiplier from the right hemisphere to the left hemisphere from RelabiManager. manager->ReadMul(mulLink); manager->ReadDiv(divLink); } else { @@ -83,6 +81,9 @@ public: divLink = 1; } + + + if (linked && hemisphere == RIGHT_HEMISPHERE) { // Linked: write the frequency multiplier from the right hemisphere to the RelabiManager. @@ -92,12 +93,17 @@ public: // Linked: Receive lfo values from RelabiManager manager->ReadValues(sample[0], sample[1], sample[2], sample[3]); - wave1 = sample[2]; - wave2 = sample[3]; + wave1 = (static_cast(sample[2]) + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); + wave2 = (static_cast(sample[3]) + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); + + + + } else { cvIn = (In(0))/51.15; + //Proportion(DetentedIn(0), HEMISPHERE_3V_CV, 3000 if (oldClock != Clock(0)) { //Clock(0) is TRIG1 port if (oldClock == 1) { @@ -114,16 +120,18 @@ public: cvIn = 10000.0 * pow(max(((cvIn / 100.0) + 1.0), 0.01) / 2.0, 1.0); //set range for cvIn to be 0 to 10000 for (uint8_t lfo = 0; lfo < 4; lfo++) { - osc[lfo].SetScale(1000); + osc[lfo].SetScale(HEMISPHERE_3V_CV); // multiply an lfo's set frequency by the first cv input and by the crossmodulation amount multipled with the previous sample value of the preceding oscillator. Scale it and then add the lfo's set frequency times the cv input. - //simfloat crossMod = (static_cast(2.0) * xmod[lfo] * (sample[(lfo + 3) % 4]) / 921600.0) - static_cast(1.0); - simfloat crossMod = (static_cast(xmod[0]) / 100.0) * ((static_cast(sample[3]) * 2.0) - 1000.0) / 100.0; - if (crossMod < 0) {crossMod = -1 * crossMod;} - simfloat setFreq = static_cast(mulLink) / divLink * (cvIn / 2500.0 * ((freq[lfo] * crossMod) + freq[lfo])) * 16; + //float crossMod = (static_cast(2.0) * xmod[lfo] * (sample[(lfo + 3) % 4]) / 921600.0) - static_cast(1.0); + float ratioLink = static_cast(mulLink) / static_cast(divLink); + float crossMod = (static_cast(xmod[lfo]) / 100.0) * ((static_cast(sample[(lfo + 3) % 4]) + HEMISPHERE_3V_CV) / (2 * HEMISPHERE_3V_CV)); + + // if (crossMod < 0) {crossMod = -1 * crossMod;} // Uncomment to get cross modulation based on amplitude instead of bipolar FM. + + float setFreq = ratioLink * (static_cast(cvIn) / 2500.0 * ((freq[lfo] * crossMod) + freq[lfo])) * 16; displayFreq[lfo] = setFreq; osc[lfo].SetFrequency(setFreq); - //sample[lfo] = osc[lfo].Next() + 3; - sample[lfo] = 503 + (osc[lfo].Next()/ 2); + sample[lfo] = osc[lfo].Next(); } if (manager->IsLinked() && hemisphere == LEFT_HEMISPHERE) { @@ -133,8 +141,11 @@ public: } // CV1 outputs LFO1 // CV2 outputs LFO2 - wave1 = sample[0]; - wave2 = sample[1]; + + wave1 = (static_cast(sample[0]) + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); + wave2 = (static_cast(sample[1]) * HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); + + } Out(0, wave1); @@ -165,11 +176,12 @@ public: gfxPrint(17, 26, "C2"); gfxPrint(32, 26, "C3"); gfxPrint(47, 26, "C4"); - - gfxRect(2, 62 - (sample[0] / 40), 13, (sample[0] / 40)); - gfxRect(17, 62 - (sample[1] / 40), 13, (sample[1] / 40)); - gfxRect(32, 62 - (sample[2] / 40), 13, (sample[2] / 40)); - gfxRect(47, 62 - (sample[3] / 40), 13, (sample[3] / 40)); + + int bar[4]; + for (int i = 0; i < 4; ++i) { + bar[i] = 14.0 * (sample[i] + HEMISPHERE_3V_CV) / HEMISPHERE_3V_CV; + gfxRect(2 + (15 * i), 62 - bar[i], 13, bar[i]); + }; switch (selectedParam) { case 0: @@ -182,13 +194,15 @@ public: }else { + //gfxPrint(35, 2, sample[0]); + // Display OSC label and value gfxPrint(15, 15, "OSC"); gfxPrint(35, 15, selectedChannel); // Display FREQ label and value gfxPrint(1, 26, "FREQ"); - simfloat fDisplay = freq[selectedChannel]; + float fDisplay = freq[selectedChannel]; gfxPrint(1, 35, ones(fDisplay)); if (fDisplay < 2000) { if (fDisplay < 199) { @@ -215,18 +229,15 @@ public: gfxPrint(1, 46, "PHAS"); gfxPrint(1, 55, phase[selectedChannel]); - //WORKING ON DISPLAYING INFORMATION ABOUT WHAT'S HAPPENING WITH CROSS MODULATION. I think the bit depth is 9216. I'm trying to make this bipolar. - //gfxPrint(31, 46, (static_cast(2.0) * xmod[0] * (sample[3]) / 9216.0) - static_cast(4608.0)); - float crossMod = (static_cast(xmod[0]) / 100.0) * ((static_cast(sample[3]) * 2.0) - 1000.0) / 10; - //gfxPrint(31, 46, crossMod); - //gfxPrint(31, 55, osc[3].Next()); - //gfxPrint(31, 55, sample[3]); - //gfxPrint(31, 55, displayFreq[0]); - gfxRect(31, 62 - (sample[0] / 60), 5, (sample[0] / 60)); - gfxRect(37, 62 - (sample[1] / 60), 5, (sample[1] / 60)); - gfxRect(43, 62 - (sample[2] / 60), 5, (sample[2] / 60)); - gfxRect(49, 62 - (sample[3] / 60), 5, (sample[3] / 60)); + + + int bar[4]; + for (int i = 0; i < 4; ++i) { + bar[i] = 8.0 * (sample[i] + HEMISPHERE_3V_CV) / HEMISPHERE_3V_CV; + gfxRect(31 + (6 * i), 62 - bar[i], 5, bar[i]); + }; + switch (selectedParam) { @@ -306,8 +317,8 @@ public: break; case 2: // XMOD (0-100) xmodKnob[selectedChannel] += (direction); - xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 101; - xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 101; + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 401; + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 401; xmod[selectedChannel] = xmodKnob[selectedChannel]; break; case 3: // PHAS (0-100) @@ -358,16 +369,16 @@ private: constexpr static uint8_t ch = 4; constexpr static uint8_t numParams = 5; uint8_t selectedOsc; - simfloat freq[ch]; // in centihertz - uint8_t xmod[ch]; - uint8_t selectedXmod; + float freq[ch]; // in centihertz + uint16_t xmod[ch]; + //uint8_t selectedXmod; uint8_t phase[ch]; int selectedChannel = 0; uint8_t selectedParam = 0; int sample[ch]; - simfloat outFreq[ch]; - simfloat freqKnob[4]; - simfloat cvIn; + float outFreq[ch]; + float freqKnob[4]; + float cvIn; uint16_t xmodKnob[4]; uint8_t countLimit = 0; int waveform_number[4]; @@ -377,7 +388,7 @@ private: uint8_t clkDiv = 0; // clkDiv allows us to calculate every other tick to save cycles uint8_t clkDivDisplay = 0; // clkDivDisplay allows us to update the display fewer times per second uint8_t oldClock = 0; - simfloat displayFreq[ch]; + float displayFreq[ch]; uint8_t freqLinkMul; uint8_t freqKnobMul; uint8_t freqLinkDiv; From 0da4ff781891661c79a883f1b00b4a70bc1145e9 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Wed, 17 Jan 2024 16:50:42 -0500 Subject: [PATCH 411/417] Changed underlying functions for LFO scaling and offset. Added a selector to make CV outputs bipolar or unipolar. --- software/o_c_REV/HEM_Relabi.ino | 56 ++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino index e24be855d..cbef7dde7 100644 --- a/software/o_c_REV/HEM_Relabi.ino +++ b/software/o_c_REV/HEM_Relabi.ino @@ -42,6 +42,7 @@ public: freqLinkDiv = 1; freqKnobMul = 1; freqKnobDiv = 1; + bipolar = true; for (uint8_t count = 0; count < 4; count++) { if (freq[count] > 2000) {freqKnob[count] = (freq[count] - 2000) / 100 + 380;} @@ -93,8 +94,8 @@ public: // Linked: Receive lfo values from RelabiManager manager->ReadValues(sample[0], sample[1], sample[2], sample[3]); - wave1 = (static_cast(sample[2]) + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); - wave2 = (static_cast(sample[3]) + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); + wave1 = (static_cast(sample[2])); + wave2 = (static_cast(sample[3])); @@ -142,12 +143,18 @@ public: // CV1 outputs LFO1 // CV2 outputs LFO2 - wave1 = (static_cast(sample[0]) + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); - wave2 = (static_cast(sample[1]) * HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV); - + + wave1 = (static_cast(sample[0]) /*+ HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV*/); + wave2 = (static_cast(sample[1]) /*+ HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV*/); } + + if (!bipolar) { + wave1 = wave1 + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV; + wave2 = wave2 + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV; + } + Out(0, wave1); Out(1, wave2); @@ -197,8 +204,17 @@ public: //gfxPrint(35, 2, sample[0]); // Display OSC label and value - gfxPrint(15, 15, "OSC"); - gfxPrint(35, 15, selectedChannel); + gfxPrint(1, 15, "OSC"); + gfxPrint(21, 15, selectedChannel); + + // Display POLARITY + gfxPrint(31, 15, "POL"); + if (bipolar) { + gfxPrint("+-"); + } else { + gfxPrint("+"); + } + // Display FREQ label and value gfxPrint(1, 26, "FREQ"); @@ -242,19 +258,19 @@ public: switch (selectedParam) { case 0: - gfxCursor(15, 23, 30); + gfxCursor(1, 23, 30); break; case 1: - gfxCursor(1, 43, 30); + gfxCursor(31, 23, 30); break; case 2: - gfxCursor(31, 43, 30); + gfxCursor(1, 43, 30); break; case 3: - gfxCursor(1, 63, 30); + gfxCursor(31, 43, 30); break; case 4: - gfxCursor(31, 63, 30); + gfxCursor(1, 63, 30); break; } } @@ -268,7 +284,7 @@ public: ResetCursor(); } else { ++cursor; - cursor = cursor % 4; + cursor = cursor % 5; selectedParam = cursor; ResetCursor(); } @@ -298,10 +314,13 @@ public: //Not linked or left hemisphere: controls select LFO, freq, xmod, and phase. case 0: // Cycle through parameters when selecting OSC - selectedChannel = selectedChannel + direction + 4; - selectedChannel = selectedChannel % 4; + selectedChannel = selectedChannel + direction + 5; + selectedChannel = selectedChannel % 5; + break; + case 1: // POLARITY(+ or +-) + bipolar = (bipolar + direction + 2) % 2; break; - case 1: // FREQ (0-20.0) + case 2: // FREQ (0-20.0) freqKnob[selectedChannel] += direction; if (freqKnob[selectedChannel] < 0) {freqKnob[selectedChannel] = 510;} if (freqKnob[selectedChannel] > 510) {freqKnob[selectedChannel] = 0;} @@ -315,13 +334,13 @@ public: freq[selectedChannel] = 2000 + ((freqKnob[selectedChannel] - 380) * 100); } break; - case 2: // XMOD (0-100) + case 3: // XMOD (0-100) xmodKnob[selectedChannel] += (direction); xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 401; xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 401; xmod[selectedChannel] = xmodKnob[selectedChannel]; break; - case 3: // PHAS (0-100) + case 4: // PHAS (0-100) phase[selectedChannel] += direction; phase[selectedChannel] = phase[selectedChannel] + 101; phase[selectedChannel] = phase[selectedChannel] % 101; @@ -396,6 +415,7 @@ private: uint8_t mulLink; uint8_t divLink; bool linked; + bool bipolar; }; From 5a2ec967818ba2891464947b58256e3775e5ca33 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Wed, 17 Jan 2024 16:52:09 -0500 Subject: [PATCH 412/417] Personal platformio.ini updated to compile to environment variables assigned by pewpewpew. --- software/o_c_REV/platformio.ini | 317 +++++++++++++++----------------- 1 file changed, 144 insertions(+), 173 deletions(-) diff --git a/software/o_c_REV/platformio.ini b/software/o_c_REV/platformio.ini index 92828f3f4..7b0a4631c 100644 --- a/software/o_c_REV/platformio.ini +++ b/software/o_c_REV/platformio.ini @@ -10,13 +10,8 @@ [platformio] src_dir = . -default_envs = -; pewpewpew -; pewpewvor -; wepwepwep - wepwepvor -; T40 -; T41 +default_envs = + pewpewpew [env] platform = teensy@4.17.0 @@ -24,214 +19,190 @@ framework = arduino board = teensy31 board_build.f_cpu = 120000000 lib_deps = EEPROM -build_flags = - -DTEENSY_OPT_SMALLEST_CODE - -DUSB_MIDI -; -DUSB_MIDI_SERIAL -; -DOC_DEV -; -DPRINT_DEBUG - -Iextern -build_src_filter = - +<*> - - - +build_flags = + -DTEENSY_OPT_SMALLEST_CODE + -DUSB_MIDI + -Iextern +build_src_filter = + +<*> + - extra_scripts = pre:resources/progname.py - upload_protocol = teensy-cli [env:custom] -build_flags = - ${env.build_flags} - ${sysenv.CUSTOM_BUILD_FLAGS} - -DOC_VERSION_EXTRA="\"_custom\"" +build_flags = + ${env.build_flags} + ${sysenv.CUSTOM_BUILD_FLAGS} + -DOC_VERSION_EXTRA="\"_custom\"" [env:T4] -build_flags = - -DTEENSY_OPT_SMALLEST_CODE - -DUSB_MIDI - -Iextern - -DPEWPEWPEW - -DDRUMMAP_GRIDS2 - -DENABLE_APP_CALIBR8OR - -DENABLE_APP_SCENES - -DENABLE_APP_ENIGMA - -DENABLE_APP_MIDI - -DENABLE_APP_PONG - -DENABLE_APP_PIQUED - -DENABLE_APP_POLYLFO - -DENABLE_APP_H1200 - -DENABLE_APP_BYTEBEATGEN - -DENABLE_APP_NEURAL_NETWORK - -DENABLE_APP_DARKEST_TIMELINE - -DENABLE_APP_LORENZ - -DENABLE_APP_ASR - -DENABLE_APP_QUANTERMAIN - -DENABLE_APP_METAQ - -DENABLE_APP_CHORDS - -DENABLE_APP_PASSENCORE - -DENABLE_APP_SEQUINS - -DENABLE_APP_AUTOMATONNETZ - -DENABLE_APP_BBGEN +build_flags = + -DTEENSY_OPT_SMALLEST_CODE + -DUSB_MIDI + -Iextern + -DPEWPEWPEW + -DDRUMMAP_GRIDS2 + -DENABLE_APP_CALIBR8OR + -DENABLE_APP_SCENES + -DENABLE_APP_ENIGMA + -DENABLE_APP_MIDI + -DENABLE_APP_PONG + -DENABLE_APP_PIQUED + -DENABLE_APP_POLYLFO + -DENABLE_APP_H1200 + -DENABLE_APP_BYTEBEATGEN + -DENABLE_APP_NEURAL_NETWORK + -DENABLE_APP_DARKEST_TIMELINE + -DENABLE_APP_LORENZ + -DENABLE_APP_ASR + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_METAQ + -DENABLE_APP_CHORDS + -DENABLE_APP_PASSENCORE + -DENABLE_APP_SEQUINS + -DENABLE_APP_AUTOMATONNETZ + -DENABLE_APP_BBGEN [env:T40] board = teensy40 board_build.f_cpu = 600000000 -build_flags = ${env:T4.build_flags} - -DOC_VERSION_EXTRA="\"_T40\"" +build_flags = + ${env:T4.build_flags} + -DOC_VERSION_EXTRA="\"_T40\"" [env:T41] board = teensy41 board_build.f_cpu = 600000000 -build_flags = ${env:T4.build_flags} - -DOC_VERSION_EXTRA="\"_T41\"" +build_flags = + ${env:T4.build_flags} + -DOC_VERSION_EXTRA="\"_T41\"" [env:pewpewpew] -; phazer's choice build -build_flags = - ${env.build_flags} - -DDRUMMAP_GRIDS2 - -DENABLE_APP_CALIBR8OR - -DENABLE_APP_SCENES - -DENABLE_APP_ENIGMA -; -DENABLE_APP_MIDI - -DENABLE_APP_PONG - -DENABLE_APP_PIQUED - -DENABLE_APP_POLYLFO - -DENABLE_APP_PASSENCORE - -DENABLE_APP_H1200 - -DENABLE_APP_BYTEBEATGEN -; -DENABLE_APP_REFERENCES - -DPEWPEWPEW - -DOC_VERSION_EXTRA="\"_phz\"" +build_flags = + ${env.build_flags} + -DDRUMMAP_GRIDS2 + -DENABLE_APP_CALIBR8OR + -DENABLE_APP_SCENES + -DENABLE_APP_ENIGMA + -DENABLE_APP_PONG + -DENABLE_APP_PIQUED + -DENABLE_APP_POLYLFO + -DENABLE_APP_PASSENCORE + -DENABLE_APP_H1200 + -DENABLE_APP_BYTEBEATGEN + -DPEWPEWPEW + -DOC_VERSION_EXTRA="\"_phz\"" + [env:wepwepwep] -build_flags = - ${env:pewpewpew.build_flags} - -DFLIP_180 +build_flags = + ${env:pewpewpew.build_flags} + -DFLIP_180 [env:pewpewvor] -build_flags = - ${env:pewpewpew.build_flags} - -DVOR -[env:wepwepvor] -build_flags = - ${env:pewpewpew.build_flags} - -DVOR - #-DFLIP_180 +build_flags = + ${env:pewpewpew.build_flags} + -DVOR -[env:main] -build_flags = - ${env.build_flags} - -DDRUMMAP_GRIDS2 - -DENABLE_APP_CALIBR8OR - -DENABLE_APP_SCENES - -DENABLE_APP_ENIGMA - -DENABLE_APP_MIDI - -DENABLE_APP_NEURAL_NETWORK - -DENABLE_APP_PONG - -DENABLE_APP_DARKEST_TIMELINE - -DENABLE_APP_PIQUED - -DENABLE_APP_POLYLFO -; -DENABLE_APP_LORENZ - -DOC_VERSION_EXTRA="\"+main\"" -; -; -DENABLE_APP_ASR -; -DENABLE_APP_QUANTERMAIN -; -DENABLE_APP_METAQ -; -DENABLE_APP_CHORDS -; -DENABLE_APP_SEQUINS -; -DENABLE_APP_H1200 -; -DENABLE_APP_AUTOMATONNETZ -; -DENABLE_APP_BBGEN -; -DENABLE_APP_BYTEBEATGEN +[env:wepwepvor] +build_flags = + ${env:pewpewpew.build_flags} + -DVOR [env:oc_stock1] -build_flags = - ${env.build_flags} - -DENABLE_APP_PONG - -DENABLE_APP_ASR - -DENABLE_APP_QUANTERMAIN -; -DENABLE_APP_METAQ - -DENABLE_APP_PIQUED - -DENABLE_APP_CHORDS - -DENABLE_APP_SEQUINS -; -DENABLE_APP_POLYLFO -; -DENABLE_APP_H1200 - -DENABLE_APP_AUTOMATONNETZ - -DENABLE_APP_LORENZ -; -DENABLE_APP_BBGEN -; -DENABLE_APP_BYTEBEATGEN - -DOC_VERSION_EXTRA="\"+stock1\"" +build_flags = + ${env.build_flags} + -DENABLE_APP_PONG + -DENABLE_APP_ASR + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_PIQUED + -DENABLE_APP_CHORDS + -DENABLE_APP_SEQUINS + -DENABLE_APP_AUTOMATONNETZ + -DENABLE_APP_LORENZ + -DOC_VERSION_EXTRA="\"+stock1\"" + +[env:main] +build_flags = + ${env.build_flags} + -DDRUMMAP_GRIDS2 + -DENABLE_APP_CALIBR8OR + -DENABLE_APP_SCENES + -DENABLE_APP_ENIGMA + -DENABLE_APP_MIDI + -DENABLE_APP_NEURAL_NETWORK + -DENABLE_APP_PONG + -DENABLE_APP_DARKEST_TIMELINE + -DENABLE_APP_PIQUED + -DENABLE_APP_POLYLFO + -DOC_VERSION_EXTRA="\"+main\"" [env:oc_stock2] -build_flags = - ${env.build_flags} - -DENABLE_APP_PONG - -DENABLE_APP_ASR - -DENABLE_APP_QUANTERMAIN - -DENABLE_APP_METAQ -; -DENABLE_APP_PIQUED - -DENABLE_APP_CHORDS -; -DENABLE_APP_SEQUINS - -DENABLE_APP_POLYLFO - -DENABLE_APP_H1200 - -DENABLE_APP_AUTOMATONNETZ - -DENABLE_APP_LORENZ - -DENABLE_APP_BBGEN - -DENABLE_APP_BYTEBEATGEN - -DOC_VERSION_EXTRA="\"+stock2\"" +build_flags = + ${env.build_flags} + -DENABLE_APP_PONG + -DENABLE_APP_ASR + -DENABLE_APP_QUANTERMAIN + -DENABLE_APP_METAQ + -DENABLE_APP_CHORDS + -DENABLE_APP_POLYLFO + -DENABLE_APP_H1200 + -DENABLE_APP_AUTOMATONNETZ + -DENABLE_APP_LORENZ + -DENABLE_APP_BBGEN + -DENABLE_APP_BYTEBEATGEN + -DOC_VERSION_EXTRA="\"+stock2\"" [env:main_flipped] -build_flags = - ${env:main.build_flags} - -DFLIP_180 +build_flags = + ${env:main.build_flags} + -DFLIP_180 [env:oc_stock1_flipped] -build_flags = - ${env:oc_stock1.build_flags} - -DFLIP_180 +build_flags = + ${env:oc_stock1.build_flags} + -DFLIP_180 [env:oc_stock2_flipped] -build_flags = - ${env:oc_stock2.build_flags} - -DFLIP_180 +build_flags = + ${env:oc_stock2.build_flags} + -DFLIP_180 [env:main_vor] -build_flags = - ${env:main.build_flags} - -DVOR - -[env:main_vor_flipped] -build_flags = - ${env:main_flipped.build_flags} - -DVOR +build_flags = + ${env:main.build_flags} + -DVOR [env:oc_stock1_vor] -build_flags = - ${env:oc_stock1.build_flags} - -DVOR +build_flags = + ${env:oc_stock1.build_flags} + -DVOR + +[env:main_vor_flipped] +build_flags = + ${env:main_flipped.build_flags} + -DVOR [env:oc_stock1_vor_flipped] -build_flags = - ${env:oc_stock1_flipped.build_flags} - -DVOR +build_flags = + ${env:oc_stock1_flipped.build_flags} + -DVOR [env:oc_stock2_vor] -build_flags = - ${env:oc_stock2.build_flags} - -DVOR +build_flags = + ${env:oc_stock2.build_flags} + -DVOR [env:oc_stock2_vor_flipped] -build_flags = - ${env:oc_stock2_flipped.build_flags} - -DVOR +build_flags = + ${env:oc_stock2_flipped.build_flags} + -DVOR [env:buchla] -build_flags = - ${env:main.build_flags} - -DBUCHLA_SUPPORT - -DBUCHLA_4U - -DOC_VERSION_EXTRA="\" Buchla\"" +build_flags = + ${env:main.build_flags} + -DBUCHLA_SUPPORT + -DBUCHLA_4U + -DOC_VERSION_EXTRA="\" Buchla\"" [env:oc_dev] build_flags = ${env.build_flags} -DOC_DEV -; -DPRINT_DEBUG From 9c96c47a963aeed49cf3ab4f0331d565fd178f54 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Thu, 18 Jan 2024 15:59:09 -0500 Subject: [PATCH 413/417] Fixed a bug in Relabi app that added controls for a nonexistant fifth oscillator. --- software/o_c_REV/HEM_Relabi.ino | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino index cbef7dde7..e1d24f9b7 100644 --- a/software/o_c_REV/HEM_Relabi.ino +++ b/software/o_c_REV/HEM_Relabi.ino @@ -153,6 +153,9 @@ public: if (!bipolar) { wave1 = wave1 + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV; wave2 = wave2 + HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV; + } else { + wave1 = wave1; + wave2 = wave2; } Out(0, wave1); @@ -314,8 +317,8 @@ public: //Not linked or left hemisphere: controls select LFO, freq, xmod, and phase. case 0: // Cycle through parameters when selecting OSC - selectedChannel = selectedChannel + direction + 5; - selectedChannel = selectedChannel % 5; + selectedChannel = selectedChannel + direction + 4; + selectedChannel = selectedChannel % 4; break; case 1: // POLARITY(+ or +-) bipolar = (bipolar + direction + 2) % 2; From 801f3ebf1046f6f4967258a7fca3b854d4d61c47 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Fri, 19 Apr 2024 15:55:55 -0400 Subject: [PATCH 414/417] Modified HEM_RunglBook to allow selecting a bitdepth parameter. --- software/o_c_REV/HEM_RunglBook.ino | 54 +++++++++++++++++++++++----- software/o_c_REV/hemisphere_config.h | 3 ++ 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/software/o_c_REV/HEM_RunglBook.ino b/software/o_c_REV/HEM_RunglBook.ino index ebcd087db..cb5cc24a7 100644 --- a/software/o_c_REV/HEM_RunglBook.ino +++ b/software/o_c_REV/HEM_RunglBook.ino @@ -27,6 +27,8 @@ public: void Start() { threshold = (12 << 7) * 2; + bitdepth = 3; + parameterSelection = 0; // Start by adjusting threshold } void Controller() { @@ -38,35 +40,66 @@ public: byte b0 = In(0) > threshold ? 0x01 : 0x00; reg = (reg << 1) | b0; } - - int rungle = Proportion(reg & 0x07, 0x07, HEMISPHERE_MAX_CV); - int rungle_tap = Proportion((reg >> 5) & 0x07, 0x07, HEMISPHERE_MAX_CV); + int bitmask = (1 << bitdepth) - 1; + int rungle = Proportion(reg & bitmask, bitmask, HEMISPHERE_MAX_CV); + int rungle_tap = Proportion((reg >> 5) & bitmask, bitmask, HEMISPHERE_MAX_CV); + + Out(0, rungle); Out(1, rungle_tap); } } + void View() { - gfxPrint(1, 15, "Thr:"); - gfxPrintVoltage(threshold); + if (parameterSelection == 0) { + gfxPrint(1, 15, "Thr:"); + gfxPrintVoltage(threshold); + + } else { + gfxPrint(1, 15, "Bit:"); + gfxPrint(bitdepth); + } + if (adjustingParameter) { + gfxCursor(25, 24, 36); + } gfxSkyline(); } - void OnButtonPress() { } + void OnButtonPress() { + // Toggle parameter adjustment mode when button is pressed + adjustingParameter = !adjustingParameter; + } - void OnEncoderMove(int direction) { - threshold += (direction * 128); - threshold = constrain(threshold, (12 << 7), (12 << 7) * 5); // 1V - 5V +void OnEncoderMove(int direction) { + if (!adjustingParameter) { + // Toggle between parameter selections (Thr and Bit) + parameterSelection = 1 - parameterSelection; // Toggle between 0 and 1 + } else { + // Adjust the currently selected parameter + if (parameterSelection == 0) { + // Adjust threshold + threshold += (direction * 128); + threshold = constrain(threshold, (12 << 7), (12 << 7) * 5); // 1V - 5V + } else { + // Adjust bitdepth + bitdepth += direction; + bitdepth = constrain(bitdepth, 1, 8); // Limit bitdepth between 1 and 8 + } } +} + uint64_t OnDataRequest() { uint64_t data = 0; Pack(data, PackLocation {0,16}, threshold); + Pack(data, PackLocation {16,8}, bitdepth); return data; } void OnDataReceive(uint64_t data) { threshold = Unpack(data, PackLocation {0,16}); + bitdepth = Unpack(data, PackLocation {16,8}); } protected: @@ -82,6 +115,9 @@ protected: private: byte reg; uint16_t threshold; + uint8_t bitdepth; + uint8_t parameterSelection; // 0: Threshold, 1: Bitdepth + bool adjustingParameter; // Flag to indicate parameter adjustment mode }; diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 747eb0918..36a8f9012 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -16,6 +16,7 @@ #define HEMISPHERE_APPLETS { \ DECLARE_APPLET( 47, 0x09, ASR), \ + DECLARE_APPLET( 56, 0x10, AttenuateOffset), \ DECLARE_APPLET( 41, 0x41, Binary), \ DECLARE_APPLET( 55, 0x80, BootsNCat), \ DECLARE_APPLET( 51, 0x80, BugCrack), \ @@ -24,6 +25,7 @@ DECLARE_APPLET( 6, 0x04, ClockDivider), \ DECLARE_APPLET( 28, 0x04, ClockSkip), \ DECLARE_APPLET( 30, 0x10, Compare), \ + DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 15, 0x02, EuclidX), \ @@ -41,6 +43,7 @@ DECLARE_APPLET( 48, 0x45, ShiftGate), \ DECLARE_APPLET( 19, 0x01, Slew), \ DECLARE_APPLET( 61, 0x01, Stairs), \ + DECLARE_APPLET( 13, 0x40, TLNeuron), \ DECLARE_APPLET( 39, 0x80, Tuner), \ DECLARE_APPLET( 43, 0x10, Voltage), \ } From 678fc36ec915ea81b6c1e33a11e137c8d41b592b Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Fri, 19 Apr 2024 16:16:23 -0400 Subject: [PATCH 415/417] Added bit1 output to HEM_RunglBook while removing ALT output. --- software/o_c_REV/HEM_RunglBook.ino | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/software/o_c_REV/HEM_RunglBook.ino b/software/o_c_REV/HEM_RunglBook.ino index cb5cc24a7..fab5fbefc 100644 --- a/software/o_c_REV/HEM_RunglBook.ino +++ b/software/o_c_REV/HEM_RunglBook.ino @@ -42,12 +42,13 @@ public: } int bitmask = (1 << bitdepth) - 1; int rungle = Proportion(reg & bitmask, bitmask, HEMISPHERE_MAX_CV); - int rungle_tap = Proportion((reg >> 5) & bitmask, bitmask, HEMISPHERE_MAX_CV); - + //int rungle_tap = Proportion((reg >> 5) & bitmask, bitmask, HEMISPHERE_MAX_CV); + int msb = (reg >> 7) & 0x01; + int out2 = Proportion(msb, 1, HEMISPHERE_MAX_CV); Out(0, rungle); - Out(1, rungle_tap); + Out(1, out2); } } @@ -107,8 +108,8 @@ protected: // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=Clock 2=Freeze"; help[HEMISPHERE_HELP_CVS] = "1=Signal"; - help[HEMISPHERE_HELP_OUTS] = "A=Rungle B=Alt"; - help[HEMISPHERE_HELP_ENCODER] = "Threshold"; + help[HEMISPHERE_HELP_OUTS] = "A=Rungle B=bit1"; + help[HEMISPHERE_HELP_ENCODER] = "Threshold/BitDepth"; // "------------------" <-- Size Guide } From 8592af42a2f3dfe37dd54541fc61ebdfde584029 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Mon, 23 Dec 2024 18:27:57 -0500 Subject: [PATCH 416/417] Relabi App MAJOR UPDATE. New UI with separate pages for individual LFOs and for global settings. Cross modulation is now built around true frequency modulation instead of phase modulation. New threshold setting for each LFO is used to set the start and end of gates derived from them. Each output can be set to a choice of the four LFOs or four gates. New global cross modulation FM amount setting for quickly setting the modulation level for all four LFOs. --- software/o_c_REV/HEM_ClockDivider.ino | 2 +- software/o_c_REV/HEM_ICONS.ino.disabled | 2 +- software/o_c_REV/HEM_Relabi.ino | 625 ++++++++++++++++-------- software/o_c_REV/HSRelabiManager.h | 26 +- software/o_c_REV/hemisphere_config.h | 6 + 5 files changed, 436 insertions(+), 225 deletions(-) diff --git a/software/o_c_REV/HEM_ClockDivider.ino b/software/o_c_REV/HEM_ClockDivider.ino index caef21453..0de4669fc 100644 --- a/software/o_c_REV/HEM_ClockDivider.ino +++ b/software/o_c_REV/HEM_ClockDivider.ino @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#define HEM_CLOCKDIV_MAX 8 +#define HEM_CLOCKDIV_MAX 24 class ClockDivider : public HemisphereApplet { public: diff --git a/software/o_c_REV/HEM_ICONS.ino.disabled b/software/o_c_REV/HEM_ICONS.ino.disabled index 75bce04a5..89f7febb1 100644 --- a/software/o_c_REV/HEM_ICONS.ino.disabled +++ b/software/o_c_REV/HEM_ICONS.ino.disabled @@ -115,7 +115,7 @@ private: LEFT_RIGHT_ICON, SEGMENT_ICON, WAVEFORM_ICON - }; + };gfxIcon void DrawInterface() { gfxSkyline(); diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino index e1d24f9b7..27b05ea97 100644 --- a/software/o_c_REV/HEM_Relabi.ino +++ b/software/o_c_REV/HEM_Relabi.ino @@ -21,8 +21,10 @@ #include "vector_osc/HSVectorOscillator.h" #include "vector_osc/WaveformManager.h" #include "HSRelabiManager.h" +#include "HSicons.h" class Relabi : public HemisphereApplet { + public: const char* applet_name() { @@ -30,19 +32,27 @@ public: } void Start() { - freq[0] = 200; - freq[1] = 300; - freq[2] = 500; - freq[3] = 100; - xmod[0] = 20; - xmod[1] = 20; - xmod[2] = 20; - xmod[3] = 20; + freq[0] = 120; + freq[1] = 360; + freq[2] = 600; + freq[3] = 840; + xmod[0] = 0; + xmod[1] = 0; + xmod[2] = 0; + xmod[3] = 0; + xmodplus = 0; freqLinkMul = 1; freqLinkDiv = 1; freqKnobMul = 1; freqKnobDiv = 1; bipolar = true; + for (uint8_t i = 0; i < 4; i++) { + lfoThreshold[i] = 0; // Default threshold is 0% + } + outputAssign[0] = 0; // Default to LFO1 + outputAssign[1] = 1; // Default to LFO2 + + for (uint8_t count = 0; count < 4; count++) { if (freq[count] > 2000) {freqKnob[count] = (freq[count] - 2000) / 100 + 380;} @@ -68,86 +78,131 @@ public: if (LEFT_HEMISPHERE) {clkCalc = 1;} if (RIGHT_HEMISPHERE) {clkCalc = 3;} + if (Clock(0) && oldClock == 0) { // Rising edge detected on TRIG1 input + for (uint8_t pcount = 0; pcount < 4; pcount++) { + // Get the total number of segments in the waveform + // byte totalSegments = osc[pcount].GetSegment(0).Segments(); // Use first segment's TOC + + // Calculate phase position based on phase percentage (0–100) + //int setPhase = round((phase[pcount] / 100.0) * totalSegments); + + // Reset the phase of the oscillator + //osc[pcount].SetPhase(setPhase); + osc[pcount].Reset(); + } + } + + //} + oldClock = Clock(0); + if (clkDiv == clkCalc) { - if (linked && hemisphere == LEFT_HEMISPHERE) { - // Linked: read the frequency multiplier from the right hemisphere to the left hemisphere from RelabiManager. - manager->ReadMul(mulLink); - manager->ReadDiv(divLink); - } else { - mulLink = 1; - divLink = 1; - } if (linked && hemisphere == RIGHT_HEMISPHERE) { - // Linked: write the frequency multiplier from the right hemisphere to the RelabiManager. - manager->WriteMul(freqLinkMul); - manager->WriteDiv(freqLinkDiv); + // Linked: Receive lfo values from RelabiManager manager->ReadValues(sample[0], sample[1], sample[2], sample[3]); wave1 = (static_cast(sample[2])); wave2 = (static_cast(sample[3])); + bool receivedGateStates[4]; + manager->ReadGates(receivedGateStates); + for (int i = 0; i < 4; i++) { + gateState[i] = receivedGateStates[i]; + + } + } else { - } else { + cvIn = (In(0))/51.15; + cvIn2 = (In(1))/51.15; - cvIn = (In(0))/51.15; - //Proportion(DetentedIn(0), HEMISPHERE_3V_CV, 3000 - - if (oldClock != Clock(0)) { //Clock(0) is TRIG1 port - if (oldClock == 1) { - for (uint8_t pcount = 0; pcount < 4; pcount++) { - if (Clock(0) > 0) { - int setPhase = round(phase[pcount] / 100 * 12); - osc[pcount].SetPhase(setPhase); - } - } - } - } - oldClock = Clock(0); - - cvIn = 10000.0 * pow(max(((cvIn / 100.0) + 1.0), 0.01) / 2.0, 1.0); //set range for cvIn to be 0 to 10000 - for (uint8_t lfo = 0; lfo < 4; lfo++) { - - osc[lfo].SetScale(HEMISPHERE_3V_CV); - // multiply an lfo's set frequency by the first cv input and by the crossmodulation amount multipled with the previous sample value of the preceding oscillator. Scale it and then add the lfo's set frequency times the cv input. - //float crossMod = (static_cast(2.0) * xmod[lfo] * (sample[(lfo + 3) % 4]) / 921600.0) - static_cast(1.0); - float ratioLink = static_cast(mulLink) / static_cast(divLink); - float crossMod = (static_cast(xmod[lfo]) / 100.0) * ((static_cast(sample[(lfo + 3) % 4]) + HEMISPHERE_3V_CV) / (2 * HEMISPHERE_3V_CV)); - - // if (crossMod < 0) {crossMod = -1 * crossMod;} // Uncomment to get cross modulation based on amplitude instead of bipolar FM. - - float setFreq = ratioLink * (static_cast(cvIn) / 2500.0 * ((freq[lfo] * crossMod) + freq[lfo])) * 16; - displayFreq[lfo] = setFreq; - osc[lfo].SetFrequency(setFreq); - sample[lfo] = osc[lfo].Next(); - } - - if (manager->IsLinked() && hemisphere == LEFT_HEMISPHERE) { + float normalizedCV = ((cvIn / 100.0) + 1.0) / 2.0; + cvIn = 10000.0 * normalizedCV; //set range for cvIn to be 0 to 10000 + + float normalizedCV2 = ((cvIn2 / 100.0) + 1.0) / 2.0; + cvIn2 = 10000.0 * normalizedCV2; //set range for cvIn to be 0 to 10000 + + + + // frequency modulation: + for (uint8_t lfo = 0; lfo < 4; lfo++) { + osc[lfo].SetScale(HEMISPHERE_3V_CV); + + // Use freqKnobMul and freqKnobDiv to adjust frequency + float globalFreqFactor = static_cast(freqKnobMul) / static_cast(freqKnobDiv); + + + // Calculate gate outputs based on thresholds + if (sample[lfo] >= (lfoThreshold[lfo] * HEMISPHERE_MAX_CV) / 200) { + // Gate is high + gateState[lfo] = true; + } else if (sample[lfo] < (lfoThreshold[lfo] * HEMISPHERE_MAX_CV) / 200) { + // Signal is below or equal to the threshold, clear the gate + gateState[lfo] = false; + } + + // Normalize the CV input to a range between 0 and 1 + float normalizedCV = static_cast(cvIn) / 2500.0; // Adjust divisor if needed based on CV range + + // Incorporate CV2 with cross-modulation + float xmodCombo = xmodplus + xmod[lfo] + (cvIn2 / 100) - 50; + + // Calculate cross-frequency modulation factor + float crossFreqMod = (static_cast(xmodCombo) / 100.0) * (static_cast(sample[(lfo + 3) % 4]) / HEMISPHERE_3V_CV); + + // Combine base frequency, cross-modulation, and CV input + float modulatedFreq = normalizedCV * globalFreqFactor * (freq[lfo] + (freq[lfo] * crossFreqMod)); + + // float modulatedFreq = freq[lfo] + (freq[lfo] * crossFreqMod * normalizedCV); + + // Ensure the frequency stays within valid bounds + modulatedFreq = constrain(modulatedFreq, 0.01, 15000.0); // Example: limit to 1 Hz to 20 kHz + + // Set the modulated frequency to the oscillator + osc[lfo].SetFrequency(modulatedFreq * 15); + + // Update sample + sample[lfo] = osc[lfo].Next(); + + // Store the display frequency for visualization + displayFreq[lfo] = modulatedFreq; + } + + + if (manager->IsLinked() && hemisphere == LEFT_HEMISPHERE) { // Linked: Send lfo values to RelabiManager - manager->WriteValues(sample[0], sample[1], sample[2], sample[3]); - } - - // CV1 outputs LFO1 // CV2 outputs LFO2 + manager->WriteValues(sample[0], sample[1], sample[2], sample[3]); + // Linked: Send gate values to RelabiManager + manager->WriteGates(gateState); + } - - wave1 = (static_cast(sample[0]) /*+ HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV*/); - wave2 = (static_cast(sample[1]) /*+ HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV*/); + if (manager->IsLinked() && hemisphere == RIGHT_HEMISPHERE) { + manager->ReadValues(sample[0], sample[1], sample[2], sample[3]); + manager->ReadGates(gateState); + } + + // CV1 outputs LFO1 // CV2 outputs LFO2 + + + wave1 = (static_cast(sample[0]) /*+ HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV*/); + wave2 = (static_cast(sample[1]) /*+ HEMISPHERE_CENTER_CV + HEMISPHERE_3V_CV*/); } + } + if (!bipolar) { @@ -158,202 +213,313 @@ public: wave2 = wave2; } - Out(0, wave1); - Out(1, wave2); + // Set outputs based on assignments + int outputA = GetOutputValue(outputAssign[0]); + int outputB = GetOutputValue(outputAssign[1]); + Out(0, outputA); + Out(1, outputB); + + // Out(0, wave1); + // Out(1, wave2); - } + clkDiv++; + clkDiv = clkDiv %32; + } - - clkDiv++; - clkDiv = clkDiv %32; - - } - void View() { +void View() { + if (currentPage == 0) { if (linked && hemisphere == RIGHT_HEMISPHERE) { - - gfxPrint(2, 15, "FREQ"); - //Display LINKED GLOBAL FREQ label and value - gfxPrint(26, 15, "*"); - gfxPrint(32, 15, freqKnobMul); - gfxPrint(45, 15, "/"); - gfxPrint(50, 15, freqKnobDiv); - - gfxPrint(2, 26, "C1"); - gfxPrint(17, 26, "C2"); - gfxPrint(32, 26, "C3"); - gfxPrint(47, 26, "C4"); - - int bar[4]; - for (int i = 0; i < 4; ++i) { - bar[i] = 14.0 * (sample[i] + HEMISPHERE_3V_CV) / HEMISPHERE_3V_CV; - gfxRect(2 + (15 * i), 62 - bar[i], 13, bar[i]); - }; + gfxPrint(1, 55, "C:"); + DrawOutputOption(13, 55, outputAssign[0]); // OUT3 + + gfxPrint(31, 55, "D:"); + DrawOutputOption(43, 55, outputAssign[1]); // OUT4 + + + DrawVUMetersRight(); + + // Highlight selected parameter switch (selectedParam) { - case 0: - gfxCursor(32, 23, 13); - break; - case 1: - gfxCursor(51, 23, 13); - break; + // case 0: HighlightParameter(2, 35, 14); break; // MULT + // case 1: HighlightParameter(22, 35, 14); break; // DIV + case 0: HighlightParameter(2, 63, 30); break; // OUT3 + case 1: HighlightParameter(32, 63, 30); break; // OUT4 } - }else { - - //gfxPrint(35, 2, sample[0]); - - // Display OSC label and value - gfxPrint(1, 15, "OSC"); - gfxPrint(21, 15, selectedChannel); - - // Display POLARITY - gfxPrint(31, 15, "POL"); - if (bipolar) { - gfxPrint("+-"); - } else { - gfxPrint("+"); - } + } else { + // Page 1: Main parameters for non-linked or left hemisphere + gfxPrint(1, 15, "LFO"); + gfxPrint(21, 15, selectedChannel + 1); - - // Display FREQ label and value gfxPrint(1, 26, "FREQ"); float fDisplay = freq[selectedChannel]; - gfxPrint(1, 35, ones(fDisplay)); + gfxPrint(2, 35, ones(fDisplay)); + if (fDisplay < 2000) { if (fDisplay < 199) { - gfxPrint("."); + gfxPrint("."); // Add decimal point for low frequencies int h = hundredths(fDisplay); - if (h < 10) {gfxPrint("0");} + if (h < 10) { gfxPrint("0"); } // Add leading zero for single digits gfxPrint(h); - } - else { - gfxPrint("."); + } else { + gfxPrint("."); // Add decimal point for midrange frequencies int t = hundredths(fDisplay); - t = (t / 10) % 10; + t = (t / 10) % 10; // Display only the first decimal digit gfxPrint(t); } } + + + gfxPrint(31, 26, "XFM"); + gfxPrint(32, 35, xmod[selectedChannel]); + + gfxPrint(1, 46, "PHAS"); + gfxPrint(2, 55, phase[selectedChannel]); + + gfxPrint(31, 46, "THRS"); + gfxPrint(32, 55, lfoThreshold[selectedChannel]); + + // Highlight selected parameter + switch (selectedParam) { + case 0: HighlightParameter(1, 23, 30); break; // OSC + case 1: HighlightParameter(2, 43, 30); break; // FREQ + case 2: HighlightParameter(32, 43, 30); break; // XMOD + case 3: HighlightParameter(2, 63, 30); break; // PHAS + case 4: HighlightParameter(32, 63, 30); break; // THRS + } + DrawVUMetersLeft(); + } + } else if (currentPage == 1) { + + if (linked && hemisphere == RIGHT_HEMISPHERE) { + } else { - - - // Display MOD label and value - gfxPrint(31, 26, "XMOD"); - gfxPrint(31, 35, xmod[selectedChannel]); - - // Display PHAS label and value - gfxPrint(1, 46, "PHAS"); - gfxPrint(1, 55, phase[selectedChannel]); + // Page 2: Clock mult, Polarity, and Output page + gfxPrint(1, 15, "ALL LFOs"); + gfxPrint(2, 25, "FREQx "); + gfxPrint(1, 35, freqKnobMul); + gfxPrint(16, 35, "/"); + gfxPrint(22, 35, freqKnobDiv); + gfxPrint(1, 55, "C:"); + DrawOutputOption(13, 55, outputAssign[0]); // OUT3 - int bar[4]; - for (int i = 0; i < 4; ++i) { - bar[i] = 8.0 * (sample[i] + HEMISPHERE_3V_CV) / HEMISPHERE_3V_CV; - gfxRect(31 + (6 * i), 62 - bar[i], 5, bar[i]); - }; + gfxPrint(31, 55, "D:"); + DrawOutputOption(43, 55, outputAssign[1]); // OUT4 + gfxPrint(36, 26, "XFM"); + gfxPrint(37, 35, xmodoffset); // ALL CROSS_MODULATION - + gfxPrint(1, 46, "POL"); + gfxPrint(20, 46, bipolar ? "+-" : "+ "); + + gfxPrint(1, 55, "A:"); + DrawOutputOption(13, 55, outputAssign[0]); // OUT1 + + gfxPrint(31, 55, "B:"); + DrawOutputOption(43, 55, outputAssign[1]); // OUT2 + + + // Highlight selected parameter (use your original locations) switch (selectedParam) { - case 0: - gfxCursor(1, 23, 30); - break; - case 1: - gfxCursor(31, 23, 30); - break; - case 2: - gfxCursor(1, 43, 30); - break; - case 3: - gfxCursor(31, 43, 30); - break; - case 4: - gfxCursor(1, 63, 30); - break; + case 5: HighlightParameter(1, 44, 14); break; // MULT + case 6: HighlightParameter(22, 44, 14); break; // DIV + case 7: HighlightParameter(36, 44, 20); break; // ALLMOD + case 8: HighlightParameter(1, 54, 35); break; // POL + case 9: HighlightParameter(2, 63, 30); break; // OUT1 + case 10: HighlightParameter(32, 63, 30); break; // OUT2 + } + } + } + +} + +void DrawOutputOption(int x, int y, uint8_t assign) { + if (assign < 4) { + // LFO output + gfxBitmap(x, y, 8, WAVEFORM_ICON); + const uint8_t* subscript = (assign == 0) ? SUP_ONE : (assign == 1) ? SUB_TWO : (assign == 2) ? SUP_THREE : SUB_FOUR; + gfxBitmap(x + 9, y, 3, subscript); + } else { + // Gate output + gfxBitmap(x, y, 8, GATE_ICON); + const uint8_t* subscript = (assign == 4) ? SUP_ONE : (assign == 5) ? SUB_TWO : (assign == 6) ? SUP_THREE : SUB_FOUR; + gfxBitmap(x + 9, y, 3, subscript); + } +} + + + + // A helper method to highlight parameters depending on EditMode + // If editing, invert rectangle; if not editing, draw a blinking cursor line + void HighlightParameter(int x, int y, int w) { + // y is the baseline where we normally draw the parameter text + // We'll highlight the line above it (like in original code) + // and depending on EditMode, invert or draw a line + + if (EditMode()) { + // Editing: invert the rectangular area to show a highlight + gfxInvert(x, y - 9, w, 9); // 9 is line height + } else { + // Not editing: show a blinking cursor line + // The Hemisphere system already uses CursorBlink() internally, + // so this will only draw the line half the time for blinking effect. + if (CursorBlink()) { + gfxLine(x, y, x + w - 1, y); + gfxPixel(x, y-1); + gfxPixel(x + w - 1, y-1); } } } +void DrawVUMetersRight() { + int bar[4]; + for (int i = 0; i < 4; ++i) { + // Calculate bar height based on sample value (assuming bipolar -3V to +3V range) + bar[i] = 14.0 * (sample[i] + HEMISPHERE_3V_CV) / HEMISPHERE_3V_CV; + + // Draw vertical bars (adjust x-position and width as needed) + gfxRect(2 + (15 * i), 42 - bar[i], 13, bar[i]); + } +} + +void DrawVUMetersLeft() { + int bar[4]; + for (int i = 0; i < 4; ++i) { + // Calculate bar height based on sample value (assuming bipolar -3V to +3V range) + // Smaller bars for left hemisphere + bar[i] = 4.0 * (sample[i] + HEMISPHERE_3V_CV) / HEMISPHERE_3V_CV; + gfxRect(31 + (6 * i), 22 - bar[i], 5, bar[i]); // Adjusted for smaller size + + } +} + + + + void OnButtonPress() { - if (linked && hemisphere == RIGHT_HEMISPHERE) { - ++cursor; - cursor = cursor % 2; - selectedParam = cursor; - ResetCursor(); + if (!EditMode()) { + // Enter EditMode on button press + isEditing = true; } else { - ++cursor; - cursor = cursor % 5; - selectedParam = cursor; - ResetCursor(); + // Exit EditMode on button press + isEditing = false; } + } - void OnEncoderMove(int direction) { + - - if (linked && hemisphere == RIGHT_HEMISPHERE) { + +void OnEncoderMove(int direction) { + // Determine how many parameters we have based on linkage and hemisphere + // linked && RIGHT_HEMISPHERE: 2 parameters (0 and 1) + // otherwise: adjust for both pages (parameters 0–7) + int max_param = (linked && hemisphere == RIGHT_HEMISPHERE) ? 1 : 10; + + if (!EditMode()) { + // Not editing: move the cursor through the available parameters + selectedParam = (selectedParam + direction + max_param + 1) % (max_param + 1); + + // Determine the current page based on the selected parameter + if (selectedParam <= 4) { + currentPage = 0; // Page 0: Main parameters + } else { + currentPage = 1; // Page 1: THRES, OUT1, OUT2 + } + } else { + // Editing: adjust parameters based on the current page and selected parameter + if (currentPage == 0) { + // Page 0: Main parameters + if (linked && hemisphere == RIGHT_HEMISPHERE) { + // Linked and RIGHT Hemisphere: Only global frequency multiplier or divider + switch (selectedParam) { + case 0: // OUT3 assignment + outputAssign[0] = (outputAssign[0] + direction + 8) % 8; + break; + case 1: // OUT4 assignment + outputAssign[1] = (outputAssign[1] + direction + 8) % 8; + break; + } + } else { + // Not linked or LEFT Hemisphere: Full parameter set (0–4) + switch (selectedParam) { + case 0: // Cycle through OSC selection + selectedChannel = (selectedChannel + direction + 4) % 4; + break; + + case 1: // FREQ (0–20.0) scaling + freqKnob[selectedChannel] += direction; + if (freqKnob[selectedChannel] < 0) { freqKnob[selectedChannel] = 510; } + if (freqKnob[selectedChannel] > 510) { freqKnob[selectedChannel] = 0; } + if (freqKnob[selectedChannel] < 200) { + freq[selectedChannel] = freqKnob[selectedChannel]; + } else if (freqKnob[selectedChannel] < 380) { + freq[selectedChannel] = 200 + ((freqKnob[selectedChannel] - 200) * 10); + } else { + freq[selectedChannel] = 2000 + ((freqKnob[selectedChannel] - 380) * 100); + } + break; + + case 2: // XMOD (0–100) scaling + xmodKnob[selectedChannel] += direction; + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 401; + xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 401; + xmod[selectedChannel] = xmodKnob[selectedChannel]; + break; + + case 3: // PHAS (0–100) + phase[selectedChannel] += direction; + phase[selectedChannel] = (phase[selectedChannel] + 101) % 101; + break; + + case 4: // THRS adjustment + lfoThreshold[selectedChannel] = constrain(lfoThreshold[selectedChannel] + direction, -100, 100); + break; + } + } + } else if (currentPage == 1) { + // Page 1: THRES, OUT1, OUT2 switch (selectedParam) { - //Linked and right hemisphere: controls only global frequency multiplier or divider. - case 0: //Global frequency multiplier + case 5: // Global frequency multiplier freqKnobMul += direction; freqKnobMul = freqKnobMul + 65; freqKnobMul = freqKnobMul % 65; freqLinkMul = freqKnobMul; break; - case 1: //Global frequency divider + case 6: // Global frequency divider freqKnobDiv = (freqKnobDiv + 64 + direction - 1) % 64 + 1; // Cycle from 1 to 64 freqLinkDiv = freqKnobDiv; break; - } - } else { - switch (selectedParam) { - //Not linked or left hemisphere: controls select LFO, freq, xmod, and phase. - - case 0: // Cycle through parameters when selecting OSC - selectedChannel = selectedChannel + direction + 4; - selectedChannel = selectedChannel % 4; + case 7: // Global cross modulation offset + xmodoffset += direction; + xmodoffset = xmodoffset + 101; + xmodoffset = xmodoffset % 101; + xmodplus = xmodoffset; break; - case 1: // POLARITY(+ or +-) + case 8: // POLARITY (+ or +-) bipolar = (bipolar + direction + 2) % 2; break; - case 2: // FREQ (0-20.0) - freqKnob[selectedChannel] += direction; - if (freqKnob[selectedChannel] < 0) {freqKnob[selectedChannel] = 510;} - if (freqKnob[selectedChannel] > 510) {freqKnob[selectedChannel] = 0;} - if (freqKnob[selectedChannel] < 200) { - freq[selectedChannel] = freqKnob[selectedChannel]; - } - else if (freqKnob[selectedChannel] < 380) { - freq[selectedChannel] = 200 + ((freqKnob[selectedChannel] - 200) * 10); - } - else { - freq[selectedChannel] = 2000 + ((freqKnob[selectedChannel] - 380) * 100); - } - break; - case 3: // XMOD (0-100) - xmodKnob[selectedChannel] += (direction); - xmodKnob[selectedChannel] = xmodKnob[selectedChannel] + 401; - xmodKnob[selectedChannel] = xmodKnob[selectedChannel] % 401; - xmod[selectedChannel] = xmodKnob[selectedChannel]; + case 9: // OUT1 assignment + outputAssign[0] = (outputAssign[0] + direction + 8) % 8; break; - case 4: // PHAS (0-100) - phase[selectedChannel] += direction; - phase[selectedChannel] = phase[selectedChannel] + 101; - phase[selectedChannel] = phase[selectedChannel] % 101; + case 10: // OUT2 assignment + outputAssign[1] = (outputAssign[1] + direction + 8) % 8; break; } } - - - } +} + uint64_t OnDataRequest() { uint64_t data = 0; @@ -366,47 +532,64 @@ public: protected: void SetHelp() { - if (linked && hemisphere == RIGHT_HEMISPHERE) { - // "------------------" <-- Size Guide help[HEMISPHERE_HELP_DIGITALS] = "1=NA 2=NA"; - help[HEMISPHERE_HELP_CVS] = "1=NA 2=NA"; - help[HEMISPHERE_HELP_OUTS] = "A=LFO3 B=LFO4"; - help[HEMISPHERE_HELP_ENCODER] = "GlobalFreqMul/Div"; - // - } else { - // "------------------" <-- Size Guide - help[HEMISPHERE_HELP_DIGITALS] = "1=Reset 2=NA"; - help[HEMISPHERE_HELP_CVS] = "1=AllFreq 2=NA"; - help[HEMISPHERE_HELP_OUTS] = "A=LFO1 B=LFO2"; - help[HEMISPHERE_HELP_ENCODER] = "Freq/XMod/Phase"; - // "------------------" <-- Size Guide + help[HEMISPHERE_HELP_CVS] = "1=NA 2=NA"; + help[HEMISPHERE_HELP_OUTS] = "A=LFO3 B=LFO4"; + help[HEMISPHERE_HELP_ENCODER] = "GlobalFreqMul/Div"; + } else if (currentPage == 0) { + help[HEMISPHERE_HELP_DIGITALS] = "1=ResetPhase 2=NA"; + help[HEMISPHERE_HELP_CVS] = "1=AllFreq 2=AllXFM"; + help[HEMISPHERE_HELP_OUTS] = "A=LFO1 B=LFO2"; + help[HEMISPHERE_HELP_ENCODER] = "Frq/XFM/Phs/Thrs"; + } else if (currentPage == 1) { + help[HEMISPHERE_HELP_DIGITALS] = "1=NA 2=NA"; + help[HEMISPHERE_HELP_CVS] = "1=Thresh"; + help[HEMISPHERE_HELP_OUTS] = "A=OUT1 B=OUT2"; + help[HEMISPHERE_HELP_ENCODER] = "Thresh/Pol/Out"; } } + private: static constexpr int pow10_lut[] = { 1, 10, 100, 1000 }; int cursor; // 0=Freq A; 1=Cross Mod A; 2=Phase A; 3=Freq B; 4=Cross Mod B; etc. + bool isEditing = false; // Indicates if the user is editing a parameter + uint8_t modal_edit_mode = 2; VectorOscillator osc[4]; constexpr static uint8_t ch = 4; constexpr static uint8_t numParams = 5; uint8_t selectedOsc; float freq[ch]; // in centihertz uint16_t xmod[ch]; + uint16_t xmodoffset; + uint16_t xmodplus; //uint8_t selectedXmod; uint8_t phase[ch]; int selectedChannel = 0; - uint8_t selectedParam = 0; + int selectedParam = 0; int sample[ch]; float outFreq[ch]; float freqKnob[4]; float cvIn; + float cvIn2; uint16_t xmodKnob[4]; uint8_t countLimit = 0; int waveform_number[4]; int ones(int n) {return (n / 100);} int hundredths(int n) {return (n % 100);} int valueToDisplay; + int GetOutputValue(uint8_t assign) { + if (assign < 4) { + // LFO outputs + return sample[assign]; + } else { + // Gate outputs + return gateState[assign - 4] ? HEMISPHERE_MAX_CV : 0; + } + } + int8_t lfoThreshold[ch]; // Thresholds for each LFO (0-100%) + uint8_t outputAssign[2]; // Output assignments for A and B (0-7 for LFO1-LFO4, GATE1-GATE4) uint8_t clkDiv = 0; // clkDiv allows us to calculate every other tick to save cycles uint8_t clkDivDisplay = 0; // clkDivDisplay allows us to update the display fewer times per second uint8_t oldClock = 0; @@ -417,8 +600,28 @@ private: uint8_t freqKnobDiv; uint8_t mulLink; uint8_t divLink; + uint8_t currentPage = 0; // 0 = main page, 1 = threshold/output page bool linked; bool bipolar; + bool gateState[4] = {false, false, false, false}; + bool EditMode() { return isEditing; }; + const char* GetOutputLabel(uint8_t assign) { + switch (assign) { + case 0: return "LFO1"; + case 1: return "LFO2"; + case 2: return "LFO3"; + case 3: return "LFO4"; + case 4: return "GAT1"; + case 5: return "GAT2"; + case 6: return "GAT3"; + case 7: return "GAT4"; + default: return "???"; + } + } + + const uint8_t SUP_THREE[3] = {0x09, 0x0D, 0x06}; // Superscript "3" + const uint8_t SUB_FOUR[3] = {0x30, 0x20, 0xF0}; // Subscript "4" + }; diff --git a/software/o_c_REV/HSRelabiManager.h b/software/o_c_REV/HSRelabiManager.h index 924b6d8b2..4005d27ba 100644 --- a/software/o_c_REV/HSRelabiManager.h +++ b/software/o_c_REV/HSRelabiManager.h @@ -27,6 +27,7 @@ class RelabiManager { int lfo2; int lfo3; int lfo4; + bool gateStates[4]; uint8_t leftOn; uint8_t rightOn; bool linked; @@ -43,6 +44,11 @@ class RelabiManager { linked = false; registered[LEFT_HEMISPHERE] = 0; registered[RIGHT_HEMISPHERE] = 0; + + // Initialize gate states + for (int i = 0; i < 4; i++) { + gateStates[i] = false; + } } public: @@ -79,20 +85,16 @@ class RelabiManager { value4 = lfo4; } - void WriteMul(uint8_t lfm) { - linkedFreqMultiplier = lfm; - } - - void ReadMul(uint8_t &lfm) const { - lfm = linkedFreqMultiplier; - } - - void WriteDiv(uint8_t ldm) { - linkedFreqDivider = ldm; + void WriteGates(bool gates[4]) { + for (int i = 0; i < 4; i++) { + gateStates[i] = gates[i]; + } } - void ReadDiv(uint8_t &ldm) { - ldm = linkedFreqDivider; + void ReadGates(bool gates[4]) const { + for (int i = 0; i < 4; i++) { + gates[i] = gateStates[i]; + } } }; diff --git a/software/o_c_REV/hemisphere_config.h b/software/o_c_REV/hemisphere_config.h index 36a8f9012..48a2c3a25 100644 --- a/software/o_c_REV/hemisphere_config.h +++ b/software/o_c_REV/hemisphere_config.h @@ -15,26 +15,31 @@ #define HEMISPHERE_APPLETS { \ + 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( 51, 0x80, BugCrack), \ DECLARE_APPLET( 31, 0x04, Burst), \ + DECLARE_APPLET( 65, 0x10, Button), \ DECLARE_APPLET( 12, 0x10, Calculate), \ DECLARE_APPLET( 6, 0x04, ClockDivider), \ DECLARE_APPLET( 28, 0x04, ClockSkip), \ DECLARE_APPLET( 30, 0x10, Compare), \ DECLARE_APPLET( 9, 0x08, DualQuant), \ DECLARE_APPLET( 18, 0x02, DualTM), \ + DECLARE_APPLET( 7, 0x01, EbbAndLfo), \ DECLARE_APPLET( 42, 0x11, EnvFollow), \ DECLARE_APPLET( 15, 0x02, EuclidX), \ DECLARE_APPLET( 10, 0x44, Logic), \ + DECLARE_APPLET( 50, 0x04, Metronome), \ DECLARE_APPLET( 73, 0x08, MultiScale), \ DECLARE_APPLET( 71, 0x02, Pigeons), \ DECLARE_APPLET( 59, 0x04, ProbabilityDivider), \ DECLARE_APPLET( 62, 0x04, ProbabilityMelody), \ DECLARE_APPLET (65, 0x01, Relabi), \ + DECLARE_APPLET( 70, 0x14, ResetClock), \ DECLARE_APPLET( 69, 0x01, RndWalk), \ DECLARE_APPLET( 44, 0x01, RunglBook), \ DECLARE_APPLET( 40, 0x40, Schmitt), \ @@ -43,6 +48,7 @@ DECLARE_APPLET( 48, 0x45, ShiftGate), \ DECLARE_APPLET( 19, 0x01, Slew), \ DECLARE_APPLET( 61, 0x01, Stairs), \ + DECLARE_APPLET( 3, 0x10, Switch), \ DECLARE_APPLET( 13, 0x40, TLNeuron), \ DECLARE_APPLET( 39, 0x80, Tuner), \ DECLARE_APPLET( 43, 0x10, Voltage), \ From 3dd41f969ec89568a7444121c7718cab8009aa46 Mon Sep 17 00:00:00 2001 From: TricksterSam Date: Mon, 23 Dec 2024 18:31:36 -0500 Subject: [PATCH 417/417] MAJOR UPDATE to Relabi App. Modified UI to have separate pages for each LFO and global settings. Changed cross-modulation behavior to use true frequency modulation instead of phase modulation. New settings: Threshold for generating gates from LFOs. Global frequency multiplier and divider added to global settings page. Global cross modulation. Outputs A-D can now be set to any of the four LFOs or gates derived from them. --- software/o_c_REV/HEM_Relabi.ino | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/software/o_c_REV/HEM_Relabi.ino b/software/o_c_REV/HEM_Relabi.ino index 27b05ea97..ed1b01805 100644 --- a/software/o_c_REV/HEM_Relabi.ino +++ b/software/o_c_REV/HEM_Relabi.ino @@ -1,4 +1,10 @@ +// Copyright (c) 2024, Samuel Burt +// Relabi code by Samuel Burt based on a concept by John Berndt. +// // Copyright (c) 2018, Jason Justian +// Jason Justian created the original Ornament & Crime Hemisphere applets. +// This code is built on his initial work and from an app template. +// // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal