From 43a4c9bf00c72fbf1d4c1cc7a552cee54f03b2f8 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] 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