From 711c807d5d855d72fe1c30b18fcade008520a04b Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Fri, 9 Aug 2024 19:37:23 +0200 Subject: [PATCH 1/9] style: remove whitespaces --- dsp/src/dsp/Recorder.h | 8 ++++---- dsp/tests/Recorder_gtest.cpp | 8 ++++---- firmware/src/LooperController.h | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dsp/src/dsp/Recorder.h b/dsp/src/dsp/Recorder.h index 47fd6ff..6645163 100644 --- a/dsp/src/dsp/Recorder.h +++ b/dsp/src/dsp/Recorder.h @@ -1,16 +1,16 @@ -/** +/** * Copyright (C) Johannes Elliesen, 2021 - * + * * 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 * 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 . */ diff --git a/dsp/tests/Recorder_gtest.cpp b/dsp/tests/Recorder_gtest.cpp index efcd11c..068822e 100644 --- a/dsp/tests/Recorder_gtest.cpp +++ b/dsp/tests/Recorder_gtest.cpp @@ -1,16 +1,16 @@ -/** +/** * Copyright (C) Johannes Elliesen, 2021 - * + * * 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 * 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 . */ diff --git a/firmware/src/LooperController.h b/firmware/src/LooperController.h index 44a6a4e..12cbc0a 100644 --- a/firmware/src/LooperController.h +++ b/firmware/src/LooperController.h @@ -1,16 +1,16 @@ -/** +/** * Copyright (C) Johannes Elliesen, 2021 - * + * * 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 * 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 . */ From 8ea658ee41a90690a1cb92a884d816d6e3b240e8 Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Fri, 16 Aug 2024 08:13:14 +0200 Subject: [PATCH 2/9] feat: apply CV inputs to volume and pitch --- firmware/src/ui/LooperParameterProvider.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/firmware/src/ui/LooperParameterProvider.h b/firmware/src/ui/LooperParameterProvider.h index 1cce526..a879b16 100644 --- a/firmware/src/ui/LooperParameterProvider.h +++ b/firmware/src/ui/LooperParameterProvider.h @@ -42,8 +42,10 @@ class LooperParameterProvider /** Called by the LooperController */ float getGainParameter(size_t looperChannel) const { - return volumeSliderToGainMultiplier(controlInputs_[looperChannel].volumeSliderPosition) - * volumeCvToGainMultiplier(controlInputs_[looperChannel].volumeCvVolts); + return std::clamp(volumeSliderToGainMultiplier(controlInputs_[looperChannel].volumeSliderPosition) + * volumeCvToGainMultiplier(controlInputs_[looperChannel].volumeCvVolts), + 0.0f, + 1.0f); } /** Called by the LooperController */ TapeProcessorParameters getProcessorParameters(size_t looperChannel) const @@ -118,8 +120,7 @@ class LooperParameterProvider float pitchCvToSemitones(float cvVolts) const { - (void) (cvVolts); - return 0.0f; // TODO + return cvVolts * 12.0f; } float volumeSliderToGainMultiplier(float sliderValue) const @@ -130,8 +131,7 @@ class LooperParameterProvider float volumeCvToGainMultiplier(float cvVolts) const { - (void) (cvVolts); - return 1.0f; // TODO + return cvVolts / 5.0f; } float getUnclampedSpeedParameter(size_t looperChannel) const From 0dd02a48ce10f0c617f2999b1d09323fae341301 Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Fri, 16 Aug 2024 08:13:31 +0200 Subject: [PATCH 3/9] hardware: ignore some irrelevant DRC errors --- .../mainboard/TapeLooper_MainBoard.kicad_pro | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/hardware/mainboard/TapeLooper_MainBoard.kicad_pro b/hardware/mainboard/TapeLooper_MainBoard.kicad_pro index 37f9264..ae40d8f 100644 --- a/hardware/mainboard/TapeLooper_MainBoard.kicad_pro +++ b/hardware/mainboard/TapeLooper_MainBoard.kicad_pro @@ -52,7 +52,30 @@ } }, "diff_pair_dimensions": [], - "drc_exclusions": [], + "drc_exclusions": [ + "clearance|148441180|66660180|d210c00d-e8ca-453f-98c4-8eff554d0be9|446f493e-4139-4f9a-a333-e468b58079db", + "clearance|148441180|67160180|002b59ad-d45d-4ad6-b168-b0124bfe813e|446f493e-4139-4f9a-a333-e468b58079db", + "clearance|148841180|66660180|c0aec2fe-75c6-4fbc-b287-4598f2405880|9652f17e-4ac6-45a7-9fda-e684b061b4a8", + "clearance|148841180|66660180|c5bc26d4-d522-41fc-be0e-1aef511fa28a|9652f17e-4ac6-45a7-9fda-e684b061b4a8", + "clearance|148841180|66660180|d210c00d-e8ca-453f-98c4-8eff554d0be9|9652f17e-4ac6-45a7-9fda-e684b061b4a8", + "clearance|148841180|67160180|002b59ad-d45d-4ad6-b168-b0124bfe813e|9652f17e-4ac6-45a7-9fda-e684b061b4a8", + "copper_edge_clearance|203100000|74589640|3e6b317e-2525-4d23-8ecc-5108681fb852|f60fdbbd-6d66-470c-9136-5f6506bd4020", + "courtyards_overlap|109538121|112399639|00000000-0000-0000-0000-000061a1cf37|00000000-0000-0000-0000-000061a1cf5f", + "courtyards_overlap|109550821|133250499|00000000-0000-0000-0000-000061a1cf87|00000000-0000-0000-0000-000061a1cfaf", + "courtyards_overlap|133490321|112394559|00000000-0000-0000-0000-000061a1cfd7|00000000-0000-0000-0000-000061a1cfff", + "courtyards_overlap|133503021|133263096|00000000-0000-0000-0000-000061a1d027|00000000-0000-0000-0000-000061a1d04f", + "courtyards_overlap|157457761|133241334|00000000-0000-0000-0000-000061a1cf9b|00000000-0000-0000-0000-000061a1cfc3", + "courtyards_overlap|157465381|112387385|00000000-0000-0000-0000-000061a1cf4b|00000000-0000-0000-0000-000061a1cf73", + "courtyards_overlap|181420121|112386836|00000000-0000-0000-0000-000061a1cfeb|00000000-0000-0000-0000-000061a1d013", + "courtyards_overlap|181425201|133268279|00000000-0000-0000-0000-000061a1d03b|00000000-0000-0000-0000-000061bacfa0", + "courtyards_overlap|90226321|76198401|00000000-0000-0000-0000-0000619b7ab8|00000000-0000-0000-0000-0000619b7acc", + "courtyards_overlap|94666241|76198401|00000000-0000-0000-0000-0000619b7acc|00000000-0000-0000-0000-0000619b7ae0", + "courtyards_overlap|99027421|76198401|00000000-0000-0000-0000-0000619b7ae0|00000000-0000-0000-0000-0000619b7af4", + "solder_mask_bridge|148441180|66660180|446f493e-4139-4f9a-a333-e468b58079db|d210c00d-e8ca-453f-98c4-8eff554d0be9", + "solder_mask_bridge|148441180|67160180|446f493e-4139-4f9a-a333-e468b58079db|002b59ad-d45d-4ad6-b168-b0124bfe813e", + "solder_mask_bridge|148841180|66660180|9652f17e-4ac6-45a7-9fda-e684b061b4a8|d210c00d-e8ca-453f-98c4-8eff554d0be9", + "solder_mask_bridge|148841180|67160180|9652f17e-4ac6-45a7-9fda-e684b061b4a8|002b59ad-d45d-4ad6-b168-b0124bfe813e" + ], "meta": { "filename": "board_design_settings.json", "version": 2 From 6e0e717bfb90ec8b6f53cad2c19e2dfb6afe6fdc Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:37:34 +0200 Subject: [PATCH 4/9] feat: passthrough CV values --- firmware/src/main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 0242ceb..f648dbe 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -147,6 +147,16 @@ void audioCallback(const float* const* in, float** out, size_t size) { cpuLoadMeter.OnBlockStart(); + // update CV values + looperParameterProvider->controlInputs_[0].pitchCvVolts = uiHardware->getCvVolts(CvInput::chA_speed); + looperParameterProvider->controlInputs_[1].pitchCvVolts = uiHardware->getCvVolts(CvInput::chB_speed); + looperParameterProvider->controlInputs_[2].pitchCvVolts = uiHardware->getCvVolts(CvInput::chC_speed); + looperParameterProvider->controlInputs_[3].pitchCvVolts = uiHardware->getCvVolts(CvInput::chD_speed); + looperParameterProvider->controlInputs_[0].volumeCvVolts = uiHardware->getCvVolts(CvInput::chA_volume); + looperParameterProvider->controlInputs_[1].volumeCvVolts = uiHardware->getCvVolts(CvInput::chB_volume); + looperParameterProvider->controlInputs_[2].volumeCvVolts = uiHardware->getCvVolts(CvInput::chC_volume); + looperParameterProvider->controlInputs_[3].volumeCvVolts = uiHardware->getCvVolts(CvInput::chD_volume); + // peak meters peakMeters[0].readPeaks(in[0]); peakMeters[1].readPeaks(in[1]); From be61dda0dfa1693472556563babb3e071f29276c Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Thu, 15 Aug 2024 23:52:25 +0200 Subject: [PATCH 5/9] feat: add a calibration page --- firmware/src/ui/TapeLooperUi.h | 7 ++++- firmware/src/ui/UiCalibrationPage.h | 47 +++++++++++++++++++++++++++++ firmware/src/ui/UiSettingsPage.h | 39 ++++++++++++++++++++---- 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 firmware/src/ui/UiCalibrationPage.h diff --git a/firmware/src/ui/TapeLooperUi.h b/firmware/src/ui/TapeLooperUi.h index e208ff6..7686ffc 100644 --- a/firmware/src/ui/TapeLooperUi.h +++ b/firmware/src/ui/TapeLooperUi.h @@ -24,6 +24,7 @@ #include "LooperParameterProvider.h" #include "UiBasePage.h" +#include "UiCalibrationPage.h" #include "UiSettingsPage.h" #include "UiSavePage.h" #include "UiLoadPage.h" @@ -45,8 +46,10 @@ class TapeLooperUi ParameterProviderType& looperParameterProvider) : uiHardware_(uiHardware), eventQueue_(eventQueue), + calibrationPage_(uiHardware), settingsPage_(looperController, - looperParameterProvider), + looperParameterProvider, + calibrationPage_), savePage_(looperController), loadPage_(looperController), recordingPage_(looperController), @@ -83,6 +86,8 @@ class TapeLooperUi UiHardwareType& uiHardware_; daisy::UiEventQueue& eventQueue_; daisy::UI ui_; + UiCalibrationPage + calibrationPage_; UiSettingsPage diff --git a/firmware/src/ui/UiCalibrationPage.h b/firmware/src/ui/UiCalibrationPage.h new file mode 100644 index 0000000..4c7e3b0 --- /dev/null +++ b/firmware/src/ui/UiCalibrationPage.h @@ -0,0 +1,47 @@ +/** + * Copyright (C) Johannes Elliesen, 2024 + * + * 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 + * 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 . + */ + +#pragma once + +#include +#include + +#include + +#include "../constants.h" +#include "../hardware/UiHardwareTypes.h" +#include "LooperParameterProvider.h" + +template +class UiCalibrationPage : public daisy::UiPage +{ +public: + UiCalibrationPage(UiHardwareType& uiHardware) : + uiHardware_(uiHardware) + { + } + + virtual ~UiCalibrationPage() {} + + bool IsOpaque(const daisy::UiCanvasDescriptor&) override { return false; } + +private: + UiCalibrationPage(const UiCalibrationPage&) = delete; + UiCalibrationPage& operator=(const UiCalibrationPage&) = delete; + + UiHardwareType& uiHardware_; +}; \ No newline at end of file diff --git a/firmware/src/ui/UiSettingsPage.h b/firmware/src/ui/UiSettingsPage.h index a880e38..c417913 100644 --- a/firmware/src/ui/UiSettingsPage.h +++ b/firmware/src/ui/UiSettingsPage.h @@ -33,9 +33,11 @@ class UiSettingsPage : public daisy::UiPage { public: UiSettingsPage(LooperControllerType& looperController, - LooperParameterProviderType& looperParameterProvider) : + LooperParameterProviderType& looperParameterProvider, + daisy::UiPage& calibrationPage) : looperController_(looperController), - looperParameterProvider_(looperParameterProvider) + looperParameterProvider_(looperParameterProvider), + calibrationPage_(calibrationPage) { } @@ -120,17 +122,22 @@ class UiSettingsPage : public daisy::UiPage void OnFocusGained() override { currentSetting_ = Setting::direction; + saveButtonState_ = false; + loadButtonState_ = false; } bool OnButton(uint16_t buttonID, uint8_t numberOfPresses, bool isRetriggering) override { (void) (isRetriggering); // ignore this argument + const Button button = Button(buttonID); + + trackLoadAndSaveButtonStates(button, numberOfPresses); + // swallow but ignore button-up messages if (numberOfPresses < 1) return true; - const Button button = Button(buttonID); switch (button) { // channel up rocker switch @@ -168,11 +175,14 @@ class UiSettingsPage : public daisy::UiPage else if (currentSetting_ == Setting::motorLag) Close(); // close this page break; - case Button::record: + + // open calibration menu when load and save pressed at the same time case Button::load: case Button::save: - Close(); // close this page - return false; // pass event to the page below to open the respective page + if (saveButtonState_ && loadButtonState_) + { + GetParentUI()->OpenPage(calibrationPage_); + } default: break; } @@ -188,6 +198,18 @@ class UiSettingsPage : public daisy::UiPage UiSettingsPage(const UiSettingsPage&) = delete; UiSettingsPage& operator=(const UiSettingsPage&) = delete; + void trackLoadAndSaveButtonStates(Button button, int numberOfPresses) + { + if (button == Button::save) + { + saveButtonState_ = numberOfPresses > 0; + } + if (button == Button::load) + { + loadButtonState_ = numberOfPresses > 0; + } + } + void onUpButton(size_t looperChannel) { if (currentSetting_ == Setting::direction) @@ -266,6 +288,11 @@ class UiSettingsPage : public daisy::UiPage }; Setting currentSetting_ = Setting::direction; + bool saveButtonState_ = false; + bool loadButtonState_ = false; + LooperControllerType& looperController_; LooperParameterProviderType& looperParameterProvider_; + + daisy::UiPage& calibrationPage_; }; \ No newline at end of file From cf0d980f866e0453da2719ca5f3845c9a495ac68 Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Thu, 15 Aug 2024 23:54:00 +0200 Subject: [PATCH 6/9] refactor: expose KnobAndCvReader instead of raw CV values --- firmware/src/hardware/UiHardware.h | 5 +---- firmware/src/main.cpp | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/firmware/src/hardware/UiHardware.h b/firmware/src/hardware/UiHardware.h index 5404970..d063dba 100644 --- a/firmware/src/hardware/UiHardware.h +++ b/firmware/src/hardware/UiHardware.h @@ -340,10 +340,7 @@ class UiHardware ledDriver_.SwapBuffersAndTransmit(); } - float getCvVolts(CvInput cv) const - { - return knobsAndCv_.getCvVolts(cv); - } + KnobAndCvReader& getKnobsAndCv() { return knobsAndCv_; } // =================================================================== // implements the button reader interface for the libDaisy UI system diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index f648dbe..e333c2a 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -148,14 +148,22 @@ void audioCallback(const float* const* in, float** out, size_t size) cpuLoadMeter.OnBlockStart(); // update CV values - looperParameterProvider->controlInputs_[0].pitchCvVolts = uiHardware->getCvVolts(CvInput::chA_speed); - looperParameterProvider->controlInputs_[1].pitchCvVolts = uiHardware->getCvVolts(CvInput::chB_speed); - looperParameterProvider->controlInputs_[2].pitchCvVolts = uiHardware->getCvVolts(CvInput::chC_speed); - looperParameterProvider->controlInputs_[3].pitchCvVolts = uiHardware->getCvVolts(CvInput::chD_speed); - looperParameterProvider->controlInputs_[0].volumeCvVolts = uiHardware->getCvVolts(CvInput::chA_volume); - looperParameterProvider->controlInputs_[1].volumeCvVolts = uiHardware->getCvVolts(CvInput::chB_volume); - looperParameterProvider->controlInputs_[2].volumeCvVolts = uiHardware->getCvVolts(CvInput::chC_volume); - looperParameterProvider->controlInputs_[3].volumeCvVolts = uiHardware->getCvVolts(CvInput::chD_volume); + looperParameterProvider->controlInputs_[0].pitchCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chA_speed); + looperParameterProvider->controlInputs_[1].pitchCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chB_speed); + looperParameterProvider->controlInputs_[2].pitchCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chC_speed); + looperParameterProvider->controlInputs_[3].pitchCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chD_speed); + looperParameterProvider->controlInputs_[0].volumeCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chA_volume); + looperParameterProvider->controlInputs_[1].volumeCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chB_volume); + looperParameterProvider->controlInputs_[2].volumeCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chC_volume); + looperParameterProvider->controlInputs_[3].volumeCvVolts = + uiHardware->getKnobsAndCv().getCvVolts(CvInput::chD_volume); // peak meters peakMeters[0].readPeaks(in[0]); From 413c8690d7f5986bec3ecb87ed5e0cefefe7190c Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Thu, 15 Aug 2024 23:56:25 +0200 Subject: [PATCH 7/9] feat: add CV calibration storage to UiHardware --- firmware/src/hardware/UiHardware.h | 97 ++++++++++++++++++++++-------- firmware/src/main.cpp | 3 +- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/firmware/src/hardware/UiHardware.h b/firmware/src/hardware/UiHardware.h index d063dba..9a5fdf5 100644 --- a/firmware/src/hardware/UiHardware.h +++ b/firmware/src/hardware/UiHardware.h @@ -154,8 +154,15 @@ class ButtonReader class KnobAndCvReader { public: + KnobAndCvReader(daisy::QSPIHandle& qspi) : + calibrationStorage_(qspi) + { + } + void init() { + calibrationStorage_.Init(getDefaultCalibrationData(), kCalibrationDataOffset); + const dsy_gpio_pin mux0 = { DSY_GPIOA, 1 }; const dsy_gpio_pin mux1 = { DSY_GPIOA, 0 }; const dsy_gpio_pin mux2 = { DSY_GPIOD, 11 }; @@ -224,7 +231,55 @@ class KnobAndCvReader return 0.0f; } + float getCvRaw(CvInput cv) const + { + switch (cv) + { + case CvInput::chA_speed: + return adc_.GetMuxFloat(0, 5); + case CvInput::chA_volume: + return adc_.GetMuxFloat(0, 6); + case CvInput::chB_speed: + return adc_.GetMuxFloat(1, 5); + case CvInput::chB_volume: + return adc_.GetMuxFloat(1, 6); + case CvInput::chC_speed: + return adc_.GetMuxFloat(2, 5); + case CvInput::chC_volume: + return adc_.GetMuxFloat(2, 6); + case CvInput::chD_speed: + return adc_.GetMuxFloat(3, 5); + case CvInput::chD_volume: + return adc_.GetMuxFloat(3, 6); + case CvInput::NUM_CVS: + break; + } + return 0.0f; + } + float getCvVolts(CvInput cv) const + { + const auto& coeffs = calibrationStorage_.GetSettings(); + return getCvRaw(cv) * coeffs[int(cv)].scale + + coeffs[int(cv)].offset; + } + + struct CvInCoefficients + { + bool operator==(const CvInCoefficients& other) const + { + return scale == other.scale && offset == other.offset; + } + float scale; + float offset; + }; + using CalibrationData = std::array; + + CalibrationData& getCalibrationData() { return calibrationStorage_.GetSettings(); } + void saveCalibrationData() { calibrationStorage_.Save(); } + +private: + static constexpr CalibrationData getDefaultCalibrationData() { /** The general formula for the CV inputs is @@ -262,31 +317,23 @@ class KnobAndCvReader constexpr auto kScaleVolume = -4.852941f; constexpr auto kOffsetVolume = 5.0f; - switch (cv) + constexpr CalibrationData defaults = []() constexpr { - case CvInput::chA_speed: - return adc_.GetMuxFloat(0, 5) * kScaleSpeed + kOffsetSpeed; - case CvInput::chA_volume: - return adc_.GetMuxFloat(0, 6) * kScaleVolume + kOffsetVolume; - case CvInput::chB_speed: - return adc_.GetMuxFloat(1, 5) * kScaleSpeed + kOffsetSpeed; - case CvInput::chB_volume: - return adc_.GetMuxFloat(1, 6) * kScaleVolume + kOffsetVolume; - case CvInput::chC_speed: - return adc_.GetMuxFloat(2, 5) * kScaleSpeed + kOffsetSpeed; - case CvInput::chC_volume: - return adc_.GetMuxFloat(2, 6) * kScaleVolume + kOffsetVolume; - case CvInput::chD_speed: - return adc_.GetMuxFloat(3, 5) * kScaleSpeed + kOffsetSpeed; - case CvInput::chD_volume: - return adc_.GetMuxFloat(3, 6) * kScaleVolume + kOffsetVolume; - case CvInput::NUM_CVS: - break; - } - return 0.0f; + CalibrationData result = { { 0.0f, 0.0f } }; + for (size_t i = 0; i < result.size(); i++) + { + const auto isSpeedCv = i % 2 == 0; + result[i] = isSpeedCv ? CvInCoefficients { kScaleSpeed, kOffsetSpeed } : CvInCoefficients { kScaleVolume, kOffsetVolume }; + } + return result; + }(); + + return defaults; } + static constexpr auto kCalibrationDataOffset = 0; + + mutable daisy::PersistentStorage calibrationStorage_; -private: daisy::AdcHandle adc_; }; @@ -296,10 +343,12 @@ class UiHardware using LedDriverType = daisy::LedDriverPca9685<3, false>; using LedDmaBufferType = LedDriverType::DmaBuffer; - UiHardware(daisy::UiEventQueue& eventQueue, + UiHardware(daisy::QSPIHandle& qspi, + daisy::UiEventQueue& eventQueue, LedDmaBufferType bufferA, LedDmaBufferType bufferB, - uint16_t* shiftRegisterDmaBuffer) + uint16_t* shiftRegisterDmaBuffer) : + knobsAndCv_(qspi) { potMonitor_.Init(eventQueue, *this); buttonMonitor_.Init(eventQueue, diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index e333c2a..4ee83ca 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -85,7 +85,8 @@ void flushLedDisplay(const daisy::UiCanvasDescriptor&) void initUi() { // init the UI hardware - auto& hardware = *uiHardware.create(uiEventQueue, + auto& hardware = *uiHardware.create(seed.qspi, + uiEventQueue, ledDmaBufferA, ledDmaBufferB, &buttonShiftRegisterDmaBuffer); From 0b514aa3b898f908c423d5d8ea1d2916fae65316 Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Thu, 15 Aug 2024 23:56:47 +0200 Subject: [PATCH 8/9] feat: implement calibration UI page --- firmware/src/ui/UiCalibrationPage.h | 202 ++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/firmware/src/ui/UiCalibrationPage.h b/firmware/src/ui/UiCalibrationPage.h index 4c7e3b0..17206f4 100644 --- a/firmware/src/ui/UiCalibrationPage.h +++ b/firmware/src/ui/UiCalibrationPage.h @@ -39,9 +39,211 @@ class UiCalibrationPage : public daisy::UiPage bool IsOpaque(const daisy::UiCanvasDescriptor&) override { return false; } + void OnShow() override + { + state_ = State::idle; + recordedMin_ = 0.0f; + selectedChannel_ = -1; + } + + void Draw(const daisy::UiCanvasDescriptor& canvas) override + { + UiHardwareType& hardware = *((UiHardwareType*) canvas.handle_); + + // light up the load & save page leds to indicate calibration + hardware.setLed(Led::load, LedColour::pulsingRed); + hardware.setLed(Led::save, LedColour::pulsingRed); + + // clear all channel octave leds + for (auto led : { Led::chA_m2, + Led::chA_m1, + Led::chA_p1, + Led::chA_p2, + Led::chB_m2, + Led::chB_m1, + Led::chB_p1, + Led::chB_p2, + Led::chC_m2, + Led::chC_m1, + Led::chC_p1, + Led::chC_p2, + Led::chD_m2, + Led::chD_m1, + Led::chD_p1, + Led::chD_p2 }) + { + hardware.setLed(led, LedColour::off); + } + + // still selecting an input ? + if (selectedChannel_ < 0) + { + hardware.setLed(Led::chA_m2, LedColour::pulsingYellow); + hardware.setLed(Led::chA_p2, LedColour::pulsingYellow); + hardware.setLed(Led::chB_m2, LedColour::pulsingYellow); + hardware.setLed(Led::chB_p2, LedColour::pulsingYellow); + hardware.setLed(Led::chC_m2, LedColour::pulsingYellow); + hardware.setLed(Led::chC_p2, LedColour::pulsingYellow); + hardware.setLed(Led::chD_m2, LedColour::pulsingYellow); + hardware.setLed(Led::chD_p2, LedColour::pulsingYellow); + } + else + { + constexpr Led lowestLeds[4] = { Led::chA_m2, Led::chB_m2, Led::chC_m2, Led::chD_m2 }; + constexpr Led highestLeds[4] = { Led::chA_p2, Led::chB_p2, Led::chC_p2, Led::chD_p2 }; + + switch (state_) + { + case State::readMinSpeed: + hardware.setLed(lowestLeds[selectedChannel_], LedColour::pulsingRed); + break; + case State::readMaxSpeed: + hardware.setLed(highestLeds[selectedChannel_], LedColour::pulsingRed); + break; + case State::readMinVolume: + hardware.setLed(lowestLeds[selectedChannel_], LedColour::pulsingGreen); + break; + case State::readMaxVolume: + hardware.setLed(highestLeds[selectedChannel_], LedColour::pulsingGreen); + break; + default: + break; + } + } + } + + bool OnButton(uint16_t buttonID, uint8_t numberOfPresses, bool isRetriggering) override + { + (void) (isRetriggering); // ignore + + // consume but ignore button-up messages + if (numberOfPresses < 1) + return true; + + const Button button = Button(buttonID); + switch (button) + { + // channel play buttons + case Button::chA_play: + channelButtonPressed(0); + break; + case Button::chB_play: + channelButtonPressed(1); + break; + case Button::chC_play: + channelButtonPressed(2); + break; + case Button::chD_play: + channelButtonPressed(3); + break; + + case Button::save: + uiHardware_.getKnobsAndCv().getCalibrationData() = newCalibrationData_; + uiHardware_.getKnobsAndCv().saveCalibrationData(); + Close(); + break; + + case Button::record: + case Button::load: + case Button::settings: + Close(); // close without saving + break; + + default: + break; + } + + return true; + } + + bool OnPotMoved(uint16_t, float) override + { + return false; // passthrough pot events to the base page + } + private: UiCalibrationPage(const UiCalibrationPage&) = delete; UiCalibrationPage& operator=(const UiCalibrationPage&) = delete; + void channelButtonPressed(const int channel) + { + // a channel button was pressed. + // If it's the same channel, advance the calibration procedure. + if (channel == selectedChannel_) + { + constexpr CvInput speedCvForChannel[4] = { CvInput::chA_speed, + CvInput::chB_speed, + CvInput::chC_speed, + CvInput::chD_speed }; + constexpr CvInput volumeCvForChannel[4] = { CvInput::chA_volume, + CvInput::chB_volume, + CvInput::chC_volume, + CvInput::chD_volume }; + switch (state_) + { + case State::readMinSpeed: + { + recordedMin_ = uiHardware_.getKnobsAndCv().getCvRaw(speedCvForChannel[selectedChannel_]); + state_ = State::readMaxSpeed; + break; + } + case State::readMaxSpeed: + { + const auto recordedMax = uiHardware_.getKnobsAndCv().getCvRaw(speedCvForChannel[selectedChannel_]); + const auto coeffs = calculateCoefficients(recordedMin_, 0.0f, recordedMax, 2.0f); + newCalibrationData_[int(speedCvForChannel[selectedChannel_])] = coeffs; + state_ = State::readMinVolume; + break; + } + case State::readMinVolume: + { + recordedMin_ = uiHardware_.getKnobsAndCv().getCvRaw(volumeCvForChannel[selectedChannel_]); + state_ = State::readMaxVolume; + break; + } + case State::readMaxVolume: + { + const auto recordedMax = uiHardware_.getKnobsAndCv().getCvRaw(volumeCvForChannel[selectedChannel_]); + const auto coeffs = calculateCoefficients(recordedMin_, 0.0f, recordedMax, 5.0f); + newCalibrationData_[int(volumeCvForChannel[selectedChannel_])] = coeffs; + selectedChannel_ = -1; + state_ = State::idle; + break; + } + default: + break; + } + } + // if it's a different channel, select that channel and start the calibration readings + else + { + selectedChannel_ = channel; + state_ = State::readMinSpeed; + } + } + + typename KnobAndCvReader::CvInCoefficients calculateCoefficients(float rawMin, + float realMin, + float rawMax, + float realMax) + { + const auto scale = (realMax - realMin) / (rawMax - rawMin); + const auto offset = realMax - scale * (rawMax); + return { scale, offset }; + } + + enum class State + { + readMinSpeed, + readMaxSpeed, + readMinVolume, + readMaxVolume, + idle + }; + UiHardwareType& uiHardware_; + int selectedChannel_ = -1; + State state_ = State::idle; + float recordedMin_; + typename KnobAndCvReader::CalibrationData newCalibrationData_; }; \ No newline at end of file From 9d3aa44838c122680c576c90224122413cf668fa Mon Sep 17 00:00:00 2001 From: TheSlowGrowth <9356320+TheSlowGrowth@users.noreply.github.com> Date: Fri, 16 Aug 2024 00:17:09 +0200 Subject: [PATCH 9/9] feat: close settings page before entering calibration page --- firmware/src/ui/UiSettingsPage.h | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/src/ui/UiSettingsPage.h b/firmware/src/ui/UiSettingsPage.h index c417913..091c0e2 100644 --- a/firmware/src/ui/UiSettingsPage.h +++ b/firmware/src/ui/UiSettingsPage.h @@ -181,6 +181,7 @@ class UiSettingsPage : public daisy::UiPage case Button::save: if (saveButtonState_ && loadButtonState_) { + Close(); GetParentUI()->OpenPage(calibrationPage_); } default: