From 46a97772c5f8642e1b7f260c5724eb2c701c886e Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Tue, 30 Jul 2024 13:03:03 +0100 Subject: [PATCH 1/9] Add 4 CV inputs --- TimeMachine/TimeMachine.cpp | 66 +++++++++++++++++++++++++++++ TimeMachine/time_machine_hardware.h | 5 +++ 2 files changed, 71 insertions(+) diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index 270d286..366b03d 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -18,6 +18,12 @@ struct CalibrationData { float timeCvOffset = 0.0; float skewCvOffset = 0.0; float feedbackCvOffset = 0.0; + + float vca1CvOffset = 0.0; + float vca2CvOffset = 0.0; + float vca3CvOffset = 0.0; + float vca4CvOffset = 0.0; + int calibrated = false; //Overloading the != operator @@ -27,6 +33,10 @@ struct CalibrationData { a.timeCvOffset==skewCvOffset && \ a.skewCvOffset==skewCvOffset && \ a.feedbackCvOffset==feedbackCvOffset && \ + a.vca1CvOffset==vca1CvOffset && \ + a.vca2CvOffset==vca2CvOffset && \ + a.vca3CvOffset==vca3CvOffset && \ + a.vca4CvOffset==vca4CvOffset && \ a.calibrated==calibrated ); } @@ -54,10 +64,22 @@ Slew timeCvSlew; Slew feedbackCvSlew; Slew distributionCvSlew; +Slew vca1CvSlew; +Slew vca2CvSlew; +Slew vca3CvSlew; +Slew vca4CvSlew; + // global storage for CV/knobs so we don't get them twice to print diagnostics float timeCv = 0.0; float feedbackCv = 0.0; float skewCv = 0.0; + +float vca1Cv = 0.0; +float vca2Cv = 0.0; +float vca3Cv = 0.0; +float vca4Cv = 0.0; + + float timeKnob = 0.0; float feedbackKnob = 0.0; float skewKnob = 0.0; @@ -69,6 +91,11 @@ float timeCvOffset = 0.0; float feedbackCvOffset = 0.0; float skewCvOffset = 0.0; +float vca1CvOffset = 0.0; +float vca2CvOffset = 0.0; +float vca3CvOffset = 0.0; +float vca4CvOffset = 0.0; + float finalTimeValue = 0.0; float finalDistributionValue = 0.0; float finalFeedbackValue = 0.0; @@ -99,6 +126,11 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s feedbackCv = clamp(hw.GetAdcValue(FEEDBACK_CV) - feedbackCvOffset, -1, 1); skewCv = clamp(hw.GetAdcValue(SKEW_CV) - skewCvOffset, -1, 1); + vca1Cv = clamp(hw.GetAdcValue(VCA_1_CV) - vca1CvOffset, -1, 1); + vca2Cv = clamp(hw.GetAdcValue(VCA_2_CV) - vca2CvOffset, -1, 1); + vca3Cv = clamp(hw.GetAdcValue(VCA_3_CV) - vca3CvOffset, -1, 1); + vca4Cv = clamp(hw.GetAdcValue(VCA_4_CV) - vca4CvOffset, -1, 1); + drySlider = minMaxSlider(1.0 - hw.GetAdcValue(DRY_SLIDER)); // calculate time based on clock if present, otherwise simple time @@ -185,10 +217,16 @@ bool shouldCalibrate() { (hw.GetAdcValue(SKEW_CV) < 0.01) && \ (hw.GetAdcValue(TIME_CV) < 0.01) && \ (hw.GetAdcValue(FEEDBACK_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_1_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_2_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_3_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_4_CV) < 0.01) && \ hw.gate_in_2.State(); + for(int i=0; i<9; i++) { shouldCalibrate &= hw.GetSliderValue(i) < 0.01; } + shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(TIME_KNOB)) > 0.95; shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(SKEW_KNOB)) > 0.95; shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(FEEDBACK_KNOB)) > 0.95; @@ -227,10 +265,17 @@ int main(void) timeKnobSlew.Init(0.5, 0.0005); feedbackKnobSlew.Init(0.5, 0.0005); distributionKnobSlew.Init(0.5, 0.0005); + timeCvSlew.Init(0.5, 0.0005); feedbackCvSlew.Init(0.5, 0.0005); distributionCvSlew.Init(0.5, 0.0005); + vca1CvSlew.Init(0.5, 0.0005); + vca2CvSlew.Init(0.5, 0.0005); + vca3CvSlew.Init(0.5, 0.0005); + vca4CvSlew.Init(0.5, 0.0005); + + // init clock rate detector clockRateDetector.Init(hw.AudioSampleRate()); @@ -283,6 +328,12 @@ int main(void) savedCalibrationData.timeCvOffset += timeCv; savedCalibrationData.skewCvOffset += skewCv; savedCalibrationData.feedbackCvOffset += feedbackCv; + + savedCalibrationData.vca1CvOffset += vca1Cv; + savedCalibrationData.vca2CvOffset += vca2Cv; + savedCalibrationData.vca3CvOffset += vca3Cv; + savedCalibrationData.vca4CvOffset += vca4Cv; + // wait 10ms System::Delay(10); // set LEDs @@ -296,6 +347,11 @@ int main(void) savedCalibrationData.timeCvOffset = savedCalibrationData.timeCvOffset / ((float)numSamples); savedCalibrationData.skewCvOffset = savedCalibrationData.skewCvOffset / ((float)numSamples); savedCalibrationData.feedbackCvOffset = savedCalibrationData.feedbackCvOffset / ((float)numSamples); + + savedCalibrationData.vca1CvOffset = savedCalibrationData.vca1CvOffset / ((float)numSamples); + savedCalibrationData.vca2CvOffset = savedCalibrationData.vca2CvOffset / ((float)numSamples); + savedCalibrationData.vca3CvOffset = savedCalibrationData.vca3CvOffset / ((float)numSamples); + savedCalibrationData.vca4CvOffset = savedCalibrationData.vca4CvOffset / ((float)numSamples); // set calibrated value to true savedCalibrationData.calibrated = true; @@ -308,6 +364,10 @@ int main(void) timeCvOffset = savedCalibrationData.timeCvOffset; skewCvOffset = savedCalibrationData.skewCvOffset; feedbackCvOffset = savedCalibrationData.feedbackCvOffset; + vca1CvOffset = savedCalibrationData.vca1CvOffset; + vca2CvOffset = savedCalibrationData.vca2CvOffset; + vca3CvOffset = savedCalibrationData.vca3CvOffset; + vca4CvOffset = savedCalibrationData.vca4CvOffset; hw.StartLog(); @@ -319,6 +379,12 @@ int main(void) hw.PrintLine("TIME_CV: " FLT_FMT(6), FLT_VAR(6, timeCv)); hw.PrintLine("FEEDBACK_CV: " FLT_FMT(6), FLT_VAR(6, feedbackCv)); hw.PrintLine("SKEW_CV: " FLT_FMT(6), FLT_VAR(6, skewCv)); + + hw.PrintLine("VCA_1_CV: " FLT_FMT(6), FLT_VAR(6, vca1Cv)); + hw.PrintLine("VCA_2_CV: " FLT_FMT(6), FLT_VAR(6, vca2Cv)); + hw.PrintLine("VCA_3_CV: " FLT_FMT(6), FLT_VAR(6, vca3Cv)); + hw.PrintLine("VCA_4_CV: " FLT_FMT(6), FLT_VAR(6, vca4Cv)); + hw.PrintLine("TIME_KNOB: " FLT_FMT(6), FLT_VAR(6, timeKnob)); hw.PrintLine("FEEDBACK_KNOB: " FLT_FMT(6), FLT_VAR(6, feedbackKnob)); hw.PrintLine("SKEW_KNOB: " FLT_FMT(6), FLT_VAR(6, skewKnob)); diff --git a/TimeMachine/time_machine_hardware.h b/TimeMachine/time_machine_hardware.h index 7c66e82..413bb03 100644 --- a/TimeMachine/time_machine_hardware.h +++ b/TimeMachine/time_machine_hardware.h @@ -12,6 +12,11 @@ using namespace patch_sm; #define TIME_CV CV_2 #define FEEDBACK_CV CV_3 +#define VCA_1_CV CV_4 +#define VCA_2_CV CV_5 +#define VCA_3_CV CV_6 +#define VCA_4_CV CV_7 + #define TIME_KNOB ADC_10 #define FEEDBACK_KNOB CV_8 #define SKEW_KNOB ADC_9 From 1935e5e532548ec37137fa21f1eda02450129fd2 Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Tue, 30 Jul 2024 17:23:48 +0100 Subject: [PATCH 2/9] Normalization detection MVP implementation. --- TimeMachine/TimeMachine.cpp | 151 +++++++++++++++++++------- TimeMachine/time_machine_hardware.cpp | 8 ++ TimeMachine/time_machine_hardware.h | 2 + 3 files changed, 122 insertions(+), 39 deletions(-) diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index 366b03d..1e50148 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -10,7 +10,7 @@ using namespace std; #define TIME_SECONDS 150 #define BUFFER_WIGGLE_ROOM_SAMPLES 1000 - +#define DEVELOPMENT_MODE true #define LINEAR_TIME false //Setting Struct containing parameters we want to save to flash @@ -52,6 +52,22 @@ PersistentStorage CalibrationDataStorage(hw.qspi); GateIn gate; Led leds[9]; +// Keep track of the agreement between the random sequence sent to the + // switch and the value read by the ADC. + uint32_t normalization_detection_count_ = 0; + uint32_t normalization_probe_state_ = 0; + + const uint8_t kNumNormalizedChannels = 4; + const uint8_t kProbeSequenceDuration = 32; + uint8_t normalization_probe_mismatches_[kNumNormalizedChannels] = {0, 0, 0, 0}; + bool is_patched_[kNumNormalizedChannels] = {false, false, false, false}; + int normalized_channels_[kNumNormalizedChannels] = { + VCA_1_CV, + VCA_2_CV, + VCA_3_CV, + VCA_4_CV + }; + StereoTimeMachine timeMachine; ClockRateDetector clockRateDetector; ContSchmidt timeKnobSchmidt; @@ -233,6 +249,40 @@ bool shouldCalibrate() { return shouldCalibrate; } +void DetectNormalization() { + bool expected_value = normalization_probe_state_ >> 31; + for (int i = 0; i < kNumNormalizedChannels; ++i) { + int channel = normalized_channels_[i]; + float value = hw.GetAdcValue(channel); + bool read_value; + if (value > 4.95) { + read_value = true; + } else if (value < 0.05) { + read_value = false; + } + else { + ++normalization_probe_mismatches_[i]; + continue; + } + + if (expected_value != read_value) { + ++normalization_probe_mismatches_[i]; + } + } + + ++normalization_detection_count_; + if (normalization_detection_count_ == kProbeSequenceDuration) { + normalization_detection_count_ = 0; + for (int i = 0; i < kNumNormalizedChannels; ++i) { + is_patched_[i] = normalization_probe_mismatches_[i] >= 2; + normalization_probe_mismatches_[i] = 0; + } + } + + normalization_probe_state_ = 1103515245 * normalization_probe_state_ + 12345; + hw.WriteNormalization(normalization_probe_state_ >> 31); +} + int main(void) { // init time machine hardware @@ -374,48 +424,71 @@ int main(void) setLeds = true; while(1) { + DetectNormalization(); - // print diagnostics - hw.PrintLine("TIME_CV: " FLT_FMT(6), FLT_VAR(6, timeCv)); - hw.PrintLine("FEEDBACK_CV: " FLT_FMT(6), FLT_VAR(6, feedbackCv)); - hw.PrintLine("SKEW_CV: " FLT_FMT(6), FLT_VAR(6, skewCv)); - - hw.PrintLine("VCA_1_CV: " FLT_FMT(6), FLT_VAR(6, vca1Cv)); - hw.PrintLine("VCA_2_CV: " FLT_FMT(6), FLT_VAR(6, vca2Cv)); - hw.PrintLine("VCA_3_CV: " FLT_FMT(6), FLT_VAR(6, vca3Cv)); - hw.PrintLine("VCA_4_CV: " FLT_FMT(6), FLT_VAR(6, vca4Cv)); - - hw.PrintLine("TIME_KNOB: " FLT_FMT(6), FLT_VAR(6, timeKnob)); - hw.PrintLine("FEEDBACK_KNOB: " FLT_FMT(6), FLT_VAR(6, feedbackKnob)); - hw.PrintLine("SKEW_KNOB: " FLT_FMT(6), FLT_VAR(6, skewKnob)); - hw.PrintLine("GATE IN: %d", hw.gate_in_2.State()); - - hw.PrintLine("GATE IN: %d", hw.gate_in_1.State()); - hw.PrintLine("CV IN 1: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_4))); - hw.PrintLine("CV IN 2: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_5))); - hw.PrintLine("CV IN 3: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_6))); - hw.PrintLine("CV IN 4: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_7))); - - hw.PrintLine("TIME_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.timeCvOffset)); - hw.PrintLine("FEEDBACK_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.feedbackCvOffset)); - hw.PrintLine("SKEW_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.skewCvOffset)); - hw.PrintLine("CALIBRATED: %d", savedCalibrationData.calibrated); - - hw.PrintLine("FINAL TIME: " FLT_FMT(6), FLT_VAR(6, finalTimeValue)); - hw.PrintLine("FINAL DISTRIBUTION: " FLT_FMT(6), FLT_VAR(6, finalDistributionValue)); - hw.PrintLine("FINAL FEEDBACK: " FLT_FMT(6), FLT_VAR(6, finalFeedbackValue)); + if (DEVELOPMENT_MODE) { + // print diagnostics + hw.PrintLine("TIME_CV: " FLT_FMT(6), FLT_VAR(6, timeCv)); + hw.PrintLine("FEEDBACK_CV: " FLT_FMT(6), FLT_VAR(6, feedbackCv)); + hw.PrintLine("SKEW_CV: " FLT_FMT(6), FLT_VAR(6, skewCv)); + + if (is_patched_[0]) { + hw.PrintLine("VCA_1_CV: " FLT_FMT(6), FLT_VAR(6, vca1Cv)); + } else { + hw.PrintLine("VCA_1_CV is unpatched!"); + } + + if (is_patched_[1]) { + hw.PrintLine("VCA_2_CV: " FLT_FMT(6), FLT_VAR(6, vca2Cv)); + } else { + hw.PrintLine("VCA_2_CV is unpatched!"); + } - hw.PrintLine("CPU AVG: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetAvgCpuLoad())); - hw.PrintLine("CPU MIN: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetMinCpuLoad())); - hw.PrintLine("CPU MAX: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetMaxCpuLoad())); + if (is_patched_[2]) { + hw.PrintLine("VCA_3_CV: " FLT_FMT(6), FLT_VAR(6, vca3Cv)); + } else { + hw.PrintLine("VCA_3_CV is unpatched!"); + } - hw.PrintLine("DROPPED FRAMES: %d", droppedFrames); + if (is_patched_[3]) { + hw.PrintLine("VCA_4_CV: " FLT_FMT(6), FLT_VAR(6, vca4Cv)); + } else { + hw.PrintLine("VCA_4_CV is unpatched!"); + } + + hw.PrintLine("TIME_KNOB: " FLT_FMT(6), FLT_VAR(6, timeKnob)); + hw.PrintLine("FEEDBACK_KNOB: " FLT_FMT(6), FLT_VAR(6, feedbackKnob)); + hw.PrintLine("SKEW_KNOB: " FLT_FMT(6), FLT_VAR(6, skewKnob)); + hw.PrintLine("GATE IN: %d", hw.gate_in_2.State()); + + hw.PrintLine("GATE IN: %d", hw.gate_in_1.State()); + hw.PrintLine("CV IN 1: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_4))); + hw.PrintLine("CV IN 2: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_5))); + hw.PrintLine("CV IN 3: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_6))); + hw.PrintLine("CV IN 4: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_7))); + + hw.PrintLine("TIME_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.timeCvOffset)); + hw.PrintLine("FEEDBACK_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.feedbackCvOffset)); + hw.PrintLine("SKEW_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.skewCvOffset)); + hw.PrintLine("CALIBRATED: %d", savedCalibrationData.calibrated); + + hw.PrintLine("FINAL TIME: " FLT_FMT(6), FLT_VAR(6, finalTimeValue)); + hw.PrintLine("FINAL DISTRIBUTION: " FLT_FMT(6), FLT_VAR(6, finalDistributionValue)); + hw.PrintLine("FINAL FEEDBACK: " FLT_FMT(6), FLT_VAR(6, finalFeedbackValue)); + + hw.PrintLine("CPU AVG: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetAvgCpuLoad())); + hw.PrintLine("CPU MIN: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetMinCpuLoad())); + hw.PrintLine("CPU MAX: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetMaxCpuLoad())); + + hw.PrintLine("DROPPED FRAMES: %d", droppedFrames); + + for(int i=0; i<9; i++) { + hw.PrintLine("%d: " FLT_FMT(6), i, FLT_VAR(6, minMaxSlider(1.0 - hw.GetSliderValue(i)))); + } - for(int i=0; i<9; i++) { - hw.PrintLine("%d: " FLT_FMT(6), i, FLT_VAR(6, minMaxSlider(1.0 - hw.GetSliderValue(i)))); + hw.PrintLine(""); + System::Delay(250); } - - hw.PrintLine(""); - System::Delay(250); + } } diff --git a/TimeMachine/time_machine_hardware.cpp b/TimeMachine/time_machine_hardware.cpp index 047e5d0..c96162a 100644 --- a/TimeMachine/time_machine_hardware.cpp +++ b/TimeMachine/time_machine_hardware.cpp @@ -503,6 +503,14 @@ namespace time_machine } } + void TimeMachineHardware::WriteNormalization(bool value) { + if (value) { + pimpl_->WriteCvOut(CV_OUT_1, 5.0); + } else { + pimpl_->WriteCvOut(CV_OUT_1, 0.0); + } + } + dsy_gpio_pin TimeMachineHardware::GetPin(const PinBank bank, const int idx) { if(idx <= 0 || idx > 10) diff --git a/TimeMachine/time_machine_hardware.h b/TimeMachine/time_machine_hardware.h index 413bb03..fae87a0 100644 --- a/TimeMachine/time_machine_hardware.h +++ b/TimeMachine/time_machine_hardware.h @@ -162,6 +162,8 @@ namespace time_machine float GetSliderValue(int idx); + void WriteNormalization(bool value); + /** Returns the STM32 port/pin combo for the desired pin (or an invalid pin for HW only pins) * * Macros at top of file can be used in place of separate arguments (i.e. GetPin(A4), etc.) From de59d218162558debda4b89addaf6a8c5b00c4e4 Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Tue, 30 Jul 2024 18:24:49 +0100 Subject: [PATCH 3/9] Add combination that sliders become attenuators if modulation is patched --- TimeMachine/TimeMachine.cpp | 58 +++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index 1e50148..261c987 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -67,6 +67,10 @@ Led leds[9]; VCA_3_CV, VCA_4_CV }; + + float modulation_values_[kNumNormalizedChannels] = { + 0.0, 0.0, 0.0, 0.0 + }; StereoTimeMachine timeMachine; ClockRateDetector clockRateDetector; @@ -101,16 +105,14 @@ float feedbackKnob = 0.0; float skewKnob = 0.0; float drySlider = 0.0; float delaySliders = 0.0; +float sliderAmpValues_[8] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; // calibration offsets for CV float timeCvOffset = 0.0; float feedbackCvOffset = 0.0; float skewCvOffset = 0.0; -float vca1CvOffset = 0.0; -float vca2CvOffset = 0.0; -float vca3CvOffset = 0.0; -float vca4CvOffset = 0.0; +float normalized_offsets_[kNumNormalizedChannels] = {0.0, 0.0, 0.0, 0.0}; float finalTimeValue = 0.0; float finalDistributionValue = 0.0; @@ -123,6 +125,24 @@ CpuLoadMeter cpuMeter; int droppedFrames = 0; +//if modulation is patched, then slider acts as attenuverter for modulation +//if modulation is unpatched, slider is +//@sliderIdx is between 1 and 8 (incl.); +float readHeadAmp(int sliderIdx) { + float sliderAmpValue = sliderAmpValues_[sliderIdx - 1]; + // 4 modulation inputs + int modulationIndex = (sliderIdx - 1) / 2; + + if (!is_patched_[modulationIndex]) { + return sliderAmpValue; + } + // between -1 & 1 + // -1 is silent, 1 is max volume + float modulationValue = modulation_values_[modulationIndex]; + + return sliderAmpValue * modulationValue; +} + // called every N samples (search for SetAudioBlockSize) void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { @@ -142,12 +162,20 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s feedbackCv = clamp(hw.GetAdcValue(FEEDBACK_CV) - feedbackCvOffset, -1, 1); skewCv = clamp(hw.GetAdcValue(SKEW_CV) - skewCvOffset, -1, 1); - vca1Cv = clamp(hw.GetAdcValue(VCA_1_CV) - vca1CvOffset, -1, 1); - vca2Cv = clamp(hw.GetAdcValue(VCA_2_CV) - vca2CvOffset, -1, 1); - vca3Cv = clamp(hw.GetAdcValue(VCA_3_CV) - vca3CvOffset, -1, 1); - vca4Cv = clamp(hw.GetAdcValue(VCA_4_CV) - vca4CvOffset, -1, 1); - + // read modulation / normalized channels + for (int i = 0; i < kNumNormalizedChannels; i++) { + modulation_values_[i] = + clamp( + hw.GetAdcValue(normalized_channels_[i]) - normalized_offsets_[i], + -1, + 1); + } + + // read slider values drySlider = minMaxSlider(1.0 - hw.GetAdcValue(DRY_SLIDER)); + for (int i = 1; i < 9; i++) { + sliderAmpValues_[i-1] = max(0.0f, minMaxSlider(1.0f - hw.GetSliderValue(i))); + } // calculate time based on clock if present, otherwise simple time float time = 0.0; @@ -201,13 +229,13 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s // let last 8 slider time/amp/blur values for left channel time machine instance timeMachine.timeMachineLeft.readHeads[i-1].Set( spread((i / 8.0), distribution) * time, - max(0.0f, minMaxSlider(1.0f - hw.GetSliderValue(i))), + readHeadAmp(i), max(0., feedback-1.0) ); // let last 8 slider time/amp/blur values for right channel time machine instance timeMachine.timeMachineRight.readHeads[i-1].Set( spread((i / 8.0), distribution) * time, - max(0.0f, minMaxSlider(1.0f - hw.GetSliderValue(i))), + readHeadAmp(i), max(0., feedback-1.0) ); } @@ -414,10 +442,10 @@ int main(void) timeCvOffset = savedCalibrationData.timeCvOffset; skewCvOffset = savedCalibrationData.skewCvOffset; feedbackCvOffset = savedCalibrationData.feedbackCvOffset; - vca1CvOffset = savedCalibrationData.vca1CvOffset; - vca2CvOffset = savedCalibrationData.vca2CvOffset; - vca3CvOffset = savedCalibrationData.vca3CvOffset; - vca4CvOffset = savedCalibrationData.vca4CvOffset; + normalized_offsets_[0] = savedCalibrationData.vca1CvOffset; + normalized_offsets_[1] = savedCalibrationData.vca2CvOffset; + normalized_offsets_[2] = savedCalibrationData.vca3CvOffset; + normalized_offsets_[3] = savedCalibrationData.vca4CvOffset; hw.StartLog(); From 512532d5d605dbd493cf6b86dd7db75722e3d7cd Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Thu, 1 Aug 2024 20:31:27 +0100 Subject: [PATCH 4/9] move to dsp.h & biquad.h to dsp folder --- TimeMachine/TimeMachine.cpp | 2 +- TimeMachine/{ => dsp}/biquad.h | 0 TimeMachine/{ => dsp}/dsp.h | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename TimeMachine/{ => dsp}/biquad.h (100%) rename TimeMachine/{ => dsp}/dsp.h (100%) diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index 261c987..c3aef9c 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -1,6 +1,6 @@ #include "daisy_patch_sm.h" #include "daisysp.h" -#include "dsp.h" +#include "dsp/dsp.h" #include "time_machine_hardware.h" using namespace daisy; diff --git a/TimeMachine/biquad.h b/TimeMachine/dsp/biquad.h similarity index 100% rename from TimeMachine/biquad.h rename to TimeMachine/dsp/biquad.h diff --git a/TimeMachine/dsp.h b/TimeMachine/dsp/dsp.h similarity index 100% rename from TimeMachine/dsp.h rename to TimeMachine/dsp/dsp.h From ba745e0c332d622e4aa4450042f0f8b0c99a39f9 Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Thu, 1 Aug 2024 21:06:48 +0100 Subject: [PATCH 5/9] Refactor out dsp.h into separate header files --- TimeMachine/TimeMachine.cpp | 7 +- TimeMachine/dsp/clock_rate_detector.h | 40 +++ TimeMachine/dsp/continuous_schmidt.h | 20 ++ TimeMachine/dsp/dsp.h | 375 ------------------------ TimeMachine/dsp/limiter.h | 29 ++ TimeMachine/dsp/loudness_detector.h | 19 ++ TimeMachine/dsp/precise_slew.h | 35 +++ TimeMachine/dsp/read_head.h | 59 ++++ TimeMachine/dsp/slew.h | 45 +++ TimeMachine/dsp/stereo_time_machine.h | 25 ++ TimeMachine/dsp/time_machine.h | 80 +++++ TimeMachine/dsp/ultra_slow_dc_blocker.h | 18 ++ TimeMachine/dsp/util.h | 87 ++++++ 13 files changed, 463 insertions(+), 376 deletions(-) create mode 100644 TimeMachine/dsp/clock_rate_detector.h create mode 100644 TimeMachine/dsp/continuous_schmidt.h delete mode 100644 TimeMachine/dsp/dsp.h create mode 100644 TimeMachine/dsp/limiter.h create mode 100644 TimeMachine/dsp/loudness_detector.h create mode 100644 TimeMachine/dsp/precise_slew.h create mode 100644 TimeMachine/dsp/read_head.h create mode 100644 TimeMachine/dsp/slew.h create mode 100644 TimeMachine/dsp/stereo_time_machine.h create mode 100644 TimeMachine/dsp/time_machine.h create mode 100644 TimeMachine/dsp/ultra_slow_dc_blocker.h create mode 100644 TimeMachine/dsp/util.h diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index c3aef9c..f36ddb1 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -1,6 +1,11 @@ #include "daisy_patch_sm.h" #include "daisysp.h" -#include "dsp/dsp.h" + +#include "dsp/stereo_time_machine.h" +#include "dsp/slew.h" +#include "dsp/clock_rate_detector.h" +#include "dsp/continuous_schmidt.h" + #include "time_machine_hardware.h" using namespace daisy; diff --git a/TimeMachine/dsp/clock_rate_detector.h b/TimeMachine/dsp/clock_rate_detector.h new file mode 100644 index 0000000..80cde74 --- /dev/null +++ b/TimeMachine/dsp/clock_rate_detector.h @@ -0,0 +1,40 @@ +#include "daisysp.h" + +#ifndef CLOCK_RATE_DETECTOR_H_ +#define CLOCK_RATE_DETECTOR_H_ + +class ClockRateDetector { +public: + int samplesSinceLastClock; + int lastIntervalInSamples; + bool lastVal; + float sampleRate; + ClockRateDetector() { + samplesSinceLastClock = 0; + lastIntervalInSamples = 0; + lastVal = false; + } + void Init(int sr) { sampleRate = sr; } + bool isStale() { + return samplesSinceLastClock > sampleRate * 2; + } + float GetInterval() { + float interval = lastIntervalInSamples / sampleRate; + return isStale() ? 0.0 : interval; + } + void Process(bool triggered) { + if(triggered && lastVal != triggered) { + if(isStale()) { + lastIntervalInSamples = samplesSinceLastClock; + } else { + lastIntervalInSamples = (lastIntervalInSamples + samplesSinceLastClock) * 0.5; + } + samplesSinceLastClock = 0; + } else { + samplesSinceLastClock++; + } + lastVal = triggered; + } +}; + +#endif // CLOCK_RATE_DETECTOR_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/continuous_schmidt.h b/TimeMachine/dsp/continuous_schmidt.h new file mode 100644 index 0000000..1afcc81 --- /dev/null +++ b/TimeMachine/dsp/continuous_schmidt.h @@ -0,0 +1,20 @@ +#include "daisysp.h" + +#ifndef CONT_SCHMIDT_H_ +#define CONT_SCHMIDT_H_ + +class ContSchmidt { +public: + float val = 0.0; + float Process(float x, float h=0.333) { + float i, f; + float sign = x < 0.0 ? -1.0 : 1.0; + x = abs(x); + f = modf(x, &i); + if(f < h) val = i; + if(f > 1.0 - h) val = i + 1; + return val * sign; + } +}; + +#endif // CONT_SCHMIDT_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/dsp.h b/TimeMachine/dsp/dsp.h deleted file mode 100644 index 1602f42..0000000 --- a/TimeMachine/dsp/dsp.h +++ /dev/null @@ -1,375 +0,0 @@ -#include "daisysp.h" -#include "biquad.h" - -int wrap_buffer_index(int x, int size) { - while(x >= size) x -= size; - while(x < 0) x += size; - return x; -} - -int seconds_to_samples(float x, float sampleRate) { - return (int)(x * sampleRate); -} - -float mix(float x, float a, float b) { - return x*(1-x) + b*x; -} - -float clamp(float x, float a, float b) { - return std::max(a,std::min(b,x)); -} - -float fourPointWarp(float x, - float ai=0.0, - float av=0.0, - float bi=0.45, - float bv=0.5, - float ci=0.55, - float cv=0.5, - float di=1.0, - float dv=1.0) { - if(x < ai) { - return av; - } else if(x < bi) { - x = (x - ai) / (bi - ai); - return av * (1.0 - x) + bv * x; - } else if(x < ci) { - x = (x - bi) / (ci - bi); - return bv * (1.0 - x) + cv * x; - } else if(x < di) { - x = (x - ci) / (di - ci); - return cv * (1.0 - x) + dv * x; - } else { - return dv; - } -} - -float minMaxKnob(float in, float dz=0.002) { - in = in - dz * 0.5; - in = in * (1.0 + dz); - return std::min(1.0f, std::max(0.0f, in)); -} - -float minMaxSlider(float in, float dz=0.002) { - return minMaxKnob(in, dz); -} - -float softClip(float x, float kneeStart=0.9, float kneeCurve=5.0) { - float linPart = clamp(x, -kneeStart, kneeStart); - float clipPart = x - linPart; - clipPart = atan(clipPart * kneeCurve) / kneeCurve; - return linPart + clipPart; -} - -float spread(float x, float s, float e=2.5) { - s = clamp(s, 0.0, 1.0); - if(s > 0.5) { - s = (s-0.5)*2.0; - s = s*e+1.0; - return 1.0 - pow(1.0-x, s); - } else if(s < 0.5) { - s = 1.0-(s*2.0); - s = s*e+1.0; - return pow(x, s); - } else { - return x; - } -} - -class PreciseSlew -{ - public: - PreciseSlew() {} - ~PreciseSlew() {} - void Init(float sample_rate, float htime) { - lastVal = 0; - prvhtim_ = -100.0; - htime_ = htime; - - sample_rate_ = sample_rate; - onedsr_ = 1.0 / sample_rate_; - } - float Process(float in) { - if(prvhtim_ != htime_) - { - c2_ = pow(0.5, onedsr_ / htime_); - c1_ = 1.0 - c2_; - prvhtim_ = htime_; - } - return lastVal = c1_ * in + c2_ * lastVal; - } - inline void SetHtime(float htime) { htime_ = htime; } - inline float GetHtime() { return htime_; } - float htime_; - float c1_, c2_, lastVal, prvhtim_; - float sample_rate_, onedsr_; -}; - - -class Slew { -public: - double lastVal = 0.0; - double coef = 0.001; - double noiseFloor = 0.0; - double noiseCoef = 0.0; - int settleSamples = 0; - int settleSamplesThreshold = 96; - void Init(double coef = 0.001, double nf=0.0) { - this->coef = coef; - this->noiseFloor = nf; - } - float Process(float x) { - double c = coef; - double d = (x - lastVal); - // if we've set a noise floor - if(noiseFloor > 0.0) { - // if we're under the noise floor - if(abs(d) < noiseFloor) { - // if the input needs to settle - if(settleSamples < settleSamplesThreshold) { - // keep sampling - settleSamples++; - // if the input is done settling - } else { - // don't change the value - d = 0.0; - } - // if we're over the noise floor - } else { - // reset the settle wait - settleSamples = 0; - } - } - lastVal = lastVal + d * c; - return lastVal; - } -}; - -class ContSchmidt { -public: - float val = 0.0; - float Process(float x, float h=0.333) { - float i, f; - float sign = x < 0.0 ? -1.0 : 1.0; - x = abs(x); - f = modf(x, &i); - if(f < h) val = i; - if(f > 1.0 - h) val = i + 1; - return val * sign; - } -}; - -class UltraSlowDCBlocker { -public: - Slew slew; - void Init(float coef = 0.00001) { - slew.Init(coef); - } - float Process(float x) { - return x - slew.Process(x); - } -}; - -class LoudnessDetector { -public: - Slew slew; - float lastVal = 0; - void Init() { slew.Init(); } - float Get() { return this->lastVal; } - float Process(float x) { - lastVal = slew.Process(abs(x)); - return x; - } -}; - -class Limiter { -public: - float gainCoef; - float attackCoef; - float releaseCoef; - void Init(float sampleRate) { - gainCoef = 1; - releaseCoef = 16.0 / sampleRate; - } - float Process(float in) { - float targetGainCoef = 1.0 / std::max(abs(in), (float)1.0); - if(targetGainCoef < gainCoef) { - gainCoef = targetGainCoef; - } else { - gainCoef = gainCoef * (1.0 - releaseCoef) + targetGainCoef*releaseCoef; - } - return in * gainCoef; - } -}; - -class ReadHead { -public: - LoudnessDetector loudness; - float* buffer; - int bufferSize; - float delayA = 0.0; - float delayB = 0.0; - float targetDelay = -1; - float ampA = 0.0; - float ampB = 0.0; - float targetAmp = -1; - float sampleRate; - float phase = 1.0; - float delta; - float blurAmount; - void Init(float sampleRate, float* buffer, int bufferSize) { - this->sampleRate = sampleRate; - this->delta = 5.0 / sampleRate; - this->buffer = buffer; - this->bufferSize = bufferSize; - this->blurAmount = 0.0; - } - void Set(float delay, float amp, float blur = 0) { - this->targetDelay = delay; - this->targetAmp = amp; - this->blurAmount = blur; - } - float Process(float writeHeadPosition) { - if(phase >= 1.0 && (targetDelay >= 0.0 || targetAmp > 0.0)) { - delayA = delayB; - delayB = targetDelay; - targetDelay = -1.0; - ampA = ampB; - ampB = targetAmp; - targetAmp = -1.0; - phase = 0.0; - delta = (5.0 + daisy::Random::GetFloat(-blurAmount, blurAmount)) / sampleRate; - } - float outputA = this->buffer[wrap_buffer_index(writeHeadPosition - seconds_to_samples(this->delayA, this->sampleRate), bufferSize)]; - float outputB = this->buffer[wrap_buffer_index(writeHeadPosition - seconds_to_samples(this->delayB, this->sampleRate), bufferSize)]; - float output = ((1 - phase) * outputA) + (phase * outputB); - float outputAmp = ((1 - phase) * ampA) + (phase * ampB); - phase = phase <= 1.0 ? phase + delta : 1.0; - return loudness.Process(output) * outputAmp; - } -}; - -float defaultBlurFunc(float x, float mix) { return x; } -class TimeMachine { -public: - ReadHead readHeads[8]; - LoudnessDetector loudness; - float sampleRate; - float* buffer; - int bufferSize; - int writeHeadPosition; - float dryAmp; - float feedback; - float blur; - Slew dryAmpSlew; - Slew feedbackSlew; - Slew ampCoefSlew; - Slew blurSlew; - Limiter outputLimiter; - Limiter feedbackLimiter; - UltraSlowDCBlocker dcblk; - daisysp::Compressor compressor; - float (*blurFunc)(float, float) = nullptr; - void Init(float sampleRate, float maxDelay, float* buffer) { - this->sampleRate = sampleRate; - this->bufferSize = seconds_to_samples(maxDelay, sampleRate); - this->buffer = buffer; - for(int i=0; idryAmp = dryAmp; - this->feedback = feedback; - this->blur = blur; - } - void SetBlurFunc(float (*f)(float, float)) { - blurFunc = f; - } - float Process(float in) { - float out = 0; - - float ampCoef = 0.0; - for(int i=0; i<8; i++) ampCoef += readHeads[i].targetAmp; - ampCoef = ampCoefSlew.Process(1.0 / std::max(1.0f, ampCoef)); - - buffer[writeHeadPosition] = loudness.Process(in); - - for(int i=0; i<8; i++) out += readHeads[i].Process(writeHeadPosition); - out = dcblk.Process(out); - out = compressor.Process(out, buffer[writeHeadPosition] + out); - - buffer[writeHeadPosition] = -(feedbackLimiter.Process(buffer[writeHeadPosition] + (out * feedbackSlew.Process(feedback) * ampCoef))); - out = outputLimiter.Process(out + in * dryAmpSlew.Process(dryAmp)); - writeHeadPosition = wrap_buffer_index(writeHeadPosition + 1, bufferSize); - - return out; - } -}; - -class StereoTimeMachine { -public: - TimeMachine timeMachineLeft; - TimeMachine timeMachineRight; - float outputs[2]; - void Init(float sampleRate, float maxDelay, float* bufferLeft, float* bufferRight) { - timeMachineLeft.Init(sampleRate, maxDelay, bufferLeft); - timeMachineRight.Init(sampleRate, maxDelay, bufferRight); - } - void Set(float dryAmp, float feedback, float blur=0.0) { - timeMachineLeft.Set(dryAmp, feedback, blur); - timeMachineRight.Set(dryAmp, feedback, blur); - } - float* Process(float inLeft, float inRight) { - outputs[0] = timeMachineLeft.Process(inLeft); - outputs[1] = timeMachineRight.Process(inRight); - return outputs; - } -}; - -class ClockRateDetector { -public: - int samplesSinceLastClock; - int lastIntervalInSamples; - bool lastVal; - float sampleRate; - ClockRateDetector() { - samplesSinceLastClock = 0; - lastIntervalInSamples = 0; - lastVal = false; - } - void Init(int sr) { sampleRate = sr; } - bool isStale() { - return samplesSinceLastClock > sampleRate * 2; - } - float GetInterval() { - float interval = lastIntervalInSamples / sampleRate; - return isStale() ? 0.0 : interval; - } - void Process(bool triggered) { - if(triggered && lastVal != triggered) { - if(isStale()) { - lastIntervalInSamples = samplesSinceLastClock; - } else { - lastIntervalInSamples = (lastIntervalInSamples + samplesSinceLastClock) * 0.5; - } - samplesSinceLastClock = 0; - } else { - samplesSinceLastClock++; - } - lastVal = triggered; - } -}; \ No newline at end of file diff --git a/TimeMachine/dsp/limiter.h b/TimeMachine/dsp/limiter.h new file mode 100644 index 0000000..b30abb7 --- /dev/null +++ b/TimeMachine/dsp/limiter.h @@ -0,0 +1,29 @@ +#include "daisysp.h" + +#ifndef LIMITER_H_ +#define LIMITER_H_ +namespace oam { + + class Limiter { + public: + float gainCoef; + float attackCoef; + float releaseCoef; + void Init(float sampleRate) { + gainCoef = 1; + releaseCoef = 16.0 / sampleRate; + } + float Process(float in) { + float targetGainCoef = 1.0 / std::max(abs(in), (float)1.0); + if(targetGainCoef < gainCoef) { + gainCoef = targetGainCoef; + } else { + gainCoef = gainCoef * (1.0 - releaseCoef) + targetGainCoef*releaseCoef; + } + return in * gainCoef; + } + }; +} + + +#endif // LIMITER_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/loudness_detector.h b/TimeMachine/dsp/loudness_detector.h new file mode 100644 index 0000000..c9bbda9 --- /dev/null +++ b/TimeMachine/dsp/loudness_detector.h @@ -0,0 +1,19 @@ +#include "daisysp.h" +#include "slew.h" + +#ifndef LOUDNESS_DETECTOR_H_ +#define LOUDNESS_DETECTOR_H_ + +class LoudnessDetector { +public: + Slew slew; + float lastVal = 0; + void Init() { slew.Init(); } + float Get() { return this->lastVal; } + float Process(float x) { + lastVal = slew.Process(abs(x)); + return x; + } +}; + +#endif // LOUDNESS_DETECTOR_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/precise_slew.h b/TimeMachine/dsp/precise_slew.h new file mode 100644 index 0000000..c6321d4 --- /dev/null +++ b/TimeMachine/dsp/precise_slew.h @@ -0,0 +1,35 @@ +#include "daisysp.h" + +#ifndef PRECISE_SLEW_H_ +#define PRECISE_SLEW_H_ + +class PreciseSlew +{ + public: + PreciseSlew() {} + ~PreciseSlew() {} + void Init(float sample_rate, float htime) { + lastVal = 0; + prvhtim_ = -100.0; + htime_ = htime; + + sample_rate_ = sample_rate; + onedsr_ = 1.0 / sample_rate_; + } + float Process(float in) { + if(prvhtim_ != htime_) + { + c2_ = pow(0.5, onedsr_ / htime_); + c1_ = 1.0 - c2_; + prvhtim_ = htime_; + } + return lastVal = c1_ * in + c2_ * lastVal; + } + inline void SetHtime(float htime) { htime_ = htime; } + inline float GetHtime() { return htime_; } + float htime_; + float c1_, c2_, lastVal, prvhtim_; + float sample_rate_, onedsr_; +}; + +#endif // PRECISE_SLEW_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/read_head.h b/TimeMachine/dsp/read_head.h new file mode 100644 index 0000000..1278201 --- /dev/null +++ b/TimeMachine/dsp/read_head.h @@ -0,0 +1,59 @@ +#include "daisysp.h" +#include "daisy.h" +#include "util.h" +#include "loudness_detector.h" + +#ifndef READ_HEAD_H_ +#define READ_HEAD_H_ + +using namespace daisysp; +using namespace daisy; + +class ReadHead { +public: + LoudnessDetector loudness; + float* buffer; + int bufferSize; + float delayA = 0.0; + float delayB = 0.0; + float targetDelay = -1; + float ampA = 0.0; + float ampB = 0.0; + float targetAmp = -1; + float sampleRate; + float phase = 1.0; + float delta; + float blurAmount; + void Init(float sampleRate, float* buffer, int bufferSize) { + this->sampleRate = sampleRate; + this->delta = 5.0 / sampleRate; + this->buffer = buffer; + this->bufferSize = bufferSize; + this->blurAmount = 0.0; + } + void Set(float delay, float amp, float blur = 0) { + this->targetDelay = delay; + this->targetAmp = amp; + this->blurAmount = blur; + } + float Process(float writeHeadPosition) { + if(phase >= 1.0 && (targetDelay >= 0.0 || targetAmp > 0.0)) { + delayA = delayB; + delayB = targetDelay; + targetDelay = -1.0; + ampA = ampB; + ampB = targetAmp; + targetAmp = -1.0; + phase = 0.0; + delta = (5.0 + daisy::Random::GetFloat(-blurAmount, blurAmount)) / sampleRate; + } + float outputA = this->buffer[wrap_buffer_index(writeHeadPosition - seconds_to_samples(this->delayA, this->sampleRate), bufferSize)]; + float outputB = this->buffer[wrap_buffer_index(writeHeadPosition - seconds_to_samples(this->delayB, this->sampleRate), bufferSize)]; + float output = ((1 - phase) * outputA) + (phase * outputB); + float outputAmp = ((1 - phase) * ampA) + (phase * ampB); + phase = phase <= 1.0 ? phase + delta : 1.0; + return loudness.Process(output) * outputAmp; + } +}; + +#endif // READ_HEAD_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/slew.h b/TimeMachine/dsp/slew.h new file mode 100644 index 0000000..d323ab0 --- /dev/null +++ b/TimeMachine/dsp/slew.h @@ -0,0 +1,45 @@ +#include "daisysp.h" + +#ifndef SLEW_H_ +#define SLEW_H_ + +class Slew { +public: + double lastVal = 0.0; + double coef = 0.001; + double noiseFloor = 0.0; + double noiseCoef = 0.0; + int settleSamples = 0; + int settleSamplesThreshold = 96; + void Init(double coef = 0.001, double nf=0.0) { + this->coef = coef; + this->noiseFloor = nf; + } + float Process(float x) { + double c = coef; + double d = (x - lastVal); + // if we've set a noise floor + if(noiseFloor > 0.0) { + // if we're under the noise floor + if(abs(d) < noiseFloor) { + // if the input needs to settle + if(settleSamples < settleSamplesThreshold) { + // keep sampling + settleSamples++; + // if the input is done settling + } else { + // don't change the value + d = 0.0; + } + // if we're over the noise floor + } else { + // reset the settle wait + settleSamples = 0; + } + } + lastVal = lastVal + d * c; + return lastVal; + } +}; + +#endif // SLEW_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/stereo_time_machine.h b/TimeMachine/dsp/stereo_time_machine.h new file mode 100644 index 0000000..f8f37c1 --- /dev/null +++ b/TimeMachine/dsp/stereo_time_machine.h @@ -0,0 +1,25 @@ +#include "time_machine.h" + +#ifndef STEREO_TIME_MACHINE_H_ +#define STEREO_TIME_MACHINE_H_ + +class StereoTimeMachine { + public: + TimeMachine timeMachineLeft; + TimeMachine timeMachineRight; + float outputs[2]; + void Init(float sampleRate, float maxDelay, float* bufferLeft, float* bufferRight) { + timeMachineLeft.Init(sampleRate, maxDelay, bufferLeft); + timeMachineRight.Init(sampleRate, maxDelay, bufferRight); + } + void Set(float dryAmp, float feedback, float blur=0.0) { + timeMachineLeft.Set(dryAmp, feedback, blur); + timeMachineRight.Set(dryAmp, feedback, blur); + } + float* Process(float inLeft, float inRight) { + outputs[0] = timeMachineLeft.Process(inLeft); + outputs[1] = timeMachineRight.Process(inRight); + return outputs; + } + }; +#endif // STEREO_TIME_MACHINE_H_ diff --git a/TimeMachine/dsp/time_machine.h b/TimeMachine/dsp/time_machine.h new file mode 100644 index 0000000..23d141a --- /dev/null +++ b/TimeMachine/dsp/time_machine.h @@ -0,0 +1,80 @@ +#include "read_head.h" +#include "loudness_detector.h" +#include "slew.h" +#include "ultra_slow_dc_blocker.h" +#include "limiter.h" + +#ifndef TIME_MACHINE_H_ +#define TIME_MACHINE_H_ + +class TimeMachine { +public: + ReadHead readHeads[8]; + LoudnessDetector loudness; + float sampleRate; + float* buffer; + int bufferSize; + int writeHeadPosition; + float dryAmp; + float feedback; + float blur; + Slew dryAmpSlew; + Slew feedbackSlew; + Slew ampCoefSlew; + Slew blurSlew; + oam::Limiter outputLimiter; + oam::Limiter feedbackLimiter; + UltraSlowDCBlocker dcblk; + daisysp::Compressor compressor; + float (*blurFunc)(float, float) = nullptr; + void Init(float sampleRate, float maxDelay, float* buffer) { + this->sampleRate = sampleRate; + this->bufferSize = seconds_to_samples(maxDelay, sampleRate); + this->buffer = buffer; + for(int i=0; idryAmp = dryAmp; + this->feedback = feedback; + this->blur = blur; + } + void SetBlurFunc(float (*f)(float, float)) { + blurFunc = f; + } + float Process(float in) { + float out = 0; + + float ampCoef = 0.0; + for(int i=0; i<8; i++) ampCoef += readHeads[i].targetAmp; + ampCoef = ampCoefSlew.Process(1.0 / std::max(1.0f, ampCoef)); + + buffer[writeHeadPosition] = loudness.Process(in); + + for(int i=0; i<8; i++) out += readHeads[i].Process(writeHeadPosition); + out = dcblk.Process(out); + out = compressor.Process(out, buffer[writeHeadPosition] + out); + + buffer[writeHeadPosition] = -(feedbackLimiter.Process(buffer[writeHeadPosition] + (out * feedbackSlew.Process(feedback) * ampCoef))); + out = outputLimiter.Process(out + in * dryAmpSlew.Process(dryAmp)); + writeHeadPosition = wrap_buffer_index(writeHeadPosition + 1, bufferSize); + + return out; + } +}; + +#endif // TIME_MACHINE_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/ultra_slow_dc_blocker.h b/TimeMachine/dsp/ultra_slow_dc_blocker.h new file mode 100644 index 0000000..c0abaa3 --- /dev/null +++ b/TimeMachine/dsp/ultra_slow_dc_blocker.h @@ -0,0 +1,18 @@ +#include "daisysp.h" +#include "slew.h" + +#ifndef ULTRA_SLOW_DC_BLOCKER_H_ +#define ULTRA_SLOW_DC_BLOCKER_H_ + +class UltraSlowDCBlocker { +public: + Slew slew; + void Init(float coef = 0.00001) { + slew.Init(coef); + } + float Process(float x) { + return x - slew.Process(x); + } +}; + +#endif // ULTRA_SLOW_DC_BLOCKER_H_ \ No newline at end of file diff --git a/TimeMachine/dsp/util.h b/TimeMachine/dsp/util.h new file mode 100644 index 0000000..b46f2e1 --- /dev/null +++ b/TimeMachine/dsp/util.h @@ -0,0 +1,87 @@ +#include "daisysp.h" + +#ifndef UTIL_H_ +#define UTIL_H_ + +int wrap_buffer_index(int x, int size) { + while(x >= size) x -= size; + while(x < 0) x += size; + return x; +}; + +int seconds_to_samples(float x, float sampleRate) { + return (int)(x * sampleRate); +} + +float mix(float x, float a, float b) { + return x*(1-x) + b*x; +} + +float clamp(float x, float a, float b) { + return std::max(a,std::min(b,x)); +} + +float fourPointWarp(float x, + float ai=0.0, + float av=0.0, + float bi=0.45, + float bv=0.5, + float ci=0.55, + float cv=0.5, + float di=1.0, + float dv=1.0) { + if(x < ai) { + return av; + } else if(x < bi) { + x = (x - ai) / (bi - ai); + return av * (1.0 - x) + bv * x; + } else if(x < ci) { + x = (x - bi) / (ci - bi); + return bv * (1.0 - x) + cv * x; + } else if(x < di) { + x = (x - ci) / (di - ci); + return cv * (1.0 - x) + dv * x; + } else { + return dv; + } +} + +float minMaxKnob(float in, float dz=0.002) { + in = in - dz * 0.5; + in = in * (1.0 + dz); + return std::min(1.0f, std::max(0.0f, in)); +} + +float minMaxSlider(float in, float dz=0.002) { + return minMaxKnob(in, dz); +} + +float softClip(float x, float kneeStart=0.9, float kneeCurve=5.0) { + float linPart = clamp(x, -kneeStart, kneeStart); + float clipPart = x - linPart; + clipPart = atan(clipPart * kneeCurve) / kneeCurve; + return linPart + clipPart; +} + +float spread(float x, float s, float e=2.5) { + s = clamp(s, 0.0, 1.0); + if(s > 0.5) { + s = (s-0.5)*2.0; + s = s*e+1.0; + return 1.0 - pow(1.0-x, s); + } else if(s < 0.5) { + s = 1.0-(s*2.0); + s = s*e+1.0; + return pow(x, s); + } else { + return x; + } +} + +float defaultBlurFunc(float x, float mix) { + return x; +} + + +#endif //UTIL_H_ + From ef60170db8980001d221001efd4bced0b064af56 Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Thu, 1 Aug 2024 21:07:40 +0100 Subject: [PATCH 6/9] saved files --- TimeMachine/TimeMachine.cpp | 2 +- TimeMachine/dsp/time_machine.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index f36ddb1..b0d18f6 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -58,7 +58,7 @@ GateIn gate; Led leds[9]; // Keep track of the agreement between the random sequence sent to the - // switch and the value read by the ADC. +// switch and the value read by the ADC. uint32_t normalization_detection_count_ = 0; uint32_t normalization_probe_state_ = 0; diff --git a/TimeMachine/dsp/time_machine.h b/TimeMachine/dsp/time_machine.h index 23d141a..3ef819d 100644 --- a/TimeMachine/dsp/time_machine.h +++ b/TimeMachine/dsp/time_machine.h @@ -27,6 +27,7 @@ class TimeMachine { UltraSlowDCBlocker dcblk; daisysp::Compressor compressor; float (*blurFunc)(float, float) = nullptr; + void Init(float sampleRate, float maxDelay, float* buffer) { this->sampleRate = sampleRate; this->bufferSize = seconds_to_samples(maxDelay, sampleRate); From e6131b28f9b11ead3087f1d3a3e49d77a1f80ee9 Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Mon, 5 Aug 2024 00:05:38 +0300 Subject: [PATCH 7/9] Start refactoring TimeMachine.cpp for cleaner state --- TimeMachine/TimeMachine.cpp | 375 ++++++-------------------- TimeMachine/dsp/stereo_time_machine.h | 8 + TimeMachine/dsp/time_machine.h | 10 + TimeMachine/ui/calibration_data.h | 33 +++ TimeMachine/ui/calibrator.h | 118 ++++++++ TimeMachine/ui/leds.h | 82 ++++++ TimeMachine/ui/ui.h | 135 ++++++++++ 7 files changed, 467 insertions(+), 294 deletions(-) create mode 100644 TimeMachine/ui/calibration_data.h create mode 100644 TimeMachine/ui/calibrator.h create mode 100644 TimeMachine/ui/leds.h create mode 100644 TimeMachine/ui/ui.h diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index b0d18f6..98c98e4 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -6,6 +6,10 @@ #include "dsp/clock_rate_detector.h" #include "dsp/continuous_schmidt.h" +#include "ui/leds.h" +#include "ui/calibrator.h" +#include "ui/ui.h" + #include "time_machine_hardware.h" using namespace daisy; @@ -18,34 +22,6 @@ using namespace std; #define DEVELOPMENT_MODE true #define LINEAR_TIME false -//Setting Struct containing parameters we want to save to flash -struct CalibrationData { - float timeCvOffset = 0.0; - float skewCvOffset = 0.0; - float feedbackCvOffset = 0.0; - - float vca1CvOffset = 0.0; - float vca2CvOffset = 0.0; - float vca3CvOffset = 0.0; - float vca4CvOffset = 0.0; - - int calibrated = false; - - //Overloading the != operator - //This is necessary as this operator is used in the PersistentStorage source code - bool operator!=(const CalibrationData& a) const { - return !( - a.timeCvOffset==skewCvOffset && \ - a.skewCvOffset==skewCvOffset && \ - a.feedbackCvOffset==feedbackCvOffset && \ - a.vca1CvOffset==vca1CvOffset && \ - a.vca2CvOffset==vca2CvOffset && \ - a.vca3CvOffset==vca3CvOffset && \ - a.vca4CvOffset==vca4CvOffset && \ - a.calibrated==calibrated - ); - } -}; // init buffers - add an extra second just in case we somehow end up slightly beyond max time // due to precision loss in floating point arithmetic (maybe use doubles for time values???) @@ -53,64 +29,36 @@ float DSY_SDRAM_BSS bufferLeft[48000 * TIME_SECONDS + BUFFER_WIGGLE_ROOM_SAMPLES float DSY_SDRAM_BSS bufferRight[48000 * TIME_SECONDS + BUFFER_WIGGLE_ROOM_SAMPLES]; TimeMachineHardware hw; -PersistentStorage CalibrationDataStorage(hw.qspi); +Ui ui; GateIn gate; -Led leds[9]; +Leds leds; // Keep track of the agreement between the random sequence sent to the // switch and the value read by the ADC. - uint32_t normalization_detection_count_ = 0; - uint32_t normalization_probe_state_ = 0; - - const uint8_t kNumNormalizedChannels = 4; - const uint8_t kProbeSequenceDuration = 32; - uint8_t normalization_probe_mismatches_[kNumNormalizedChannels] = {0, 0, 0, 0}; - bool is_patched_[kNumNormalizedChannels] = {false, false, false, false}; - int normalized_channels_[kNumNormalizedChannels] = { +uint32_t normalization_detection_count_ = 0; +uint32_t normalization_probe_state_ = 0; + +const uint8_t kNumNormalizedChannels = 4; +const uint8_t kProbeSequenceDuration = 32; +uint8_t normalization_probe_mismatches_[kNumNormalizedChannels] = {0, 0, 0, 0}; +bool is_patched_[kNumNormalizedChannels] = {false, false, false, false}; +int normalized_channels_[kNumNormalizedChannels] = { VCA_1_CV, VCA_2_CV, VCA_3_CV, VCA_4_CV - }; +}; - float modulation_values_[kNumNormalizedChannels] = { +float modulation_values_[kNumNormalizedChannels] = { 0.0, 0.0, 0.0, 0.0 - }; +}; StereoTimeMachine timeMachine; -ClockRateDetector clockRateDetector; -ContSchmidt timeKnobSchmidt; -ContSchmidt timeCvSchmidt; - -Slew timeKnobSlew; -Slew feedbackKnobSlew; -Slew distributionKnobSlew; -Slew timeCvSlew; -Slew feedbackCvSlew; -Slew distributionCvSlew; - -Slew vca1CvSlew; -Slew vca2CvSlew; -Slew vca3CvSlew; -Slew vca4CvSlew; - -// global storage for CV/knobs so we don't get them twice to print diagnostics -float timeCv = 0.0; -float feedbackCv = 0.0; -float skewCv = 0.0; - -float vca1Cv = 0.0; -float vca2Cv = 0.0; -float vca3Cv = 0.0; -float vca4Cv = 0.0; - - -float timeKnob = 0.0; -float feedbackKnob = 0.0; -float skewKnob = 0.0; -float drySlider = 0.0; -float delaySliders = 0.0; -float sliderAmpValues_[8] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + +PersistentStorage calibrationDataStorage(hw.qspi); +Calibrator calibrator; + + // calibration offsets for CV float timeCvOffset = 0.0; @@ -130,9 +78,9 @@ CpuLoadMeter cpuMeter; int droppedFrames = 0; -//if modulation is patched, then slider acts as attenuverter for modulation -//if modulation is unpatched, slider is -//@sliderIdx is between 1 and 8 (incl.); +// if modulation is patched, then slider acts as attenuverter for modulation +// if modulation is unpatched, slider is +// @sliderIdx is between 1 and 8 (incl.); float readHeadAmp(int sliderIdx) { float sliderAmpValue = sliderAmpValues_[sliderIdx - 1]; // 4 modulation inputs @@ -155,100 +103,38 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s cpuMeter.OnBlockStart(); droppedFrames++; - // process controls - hw.ProcessAllControls(); - - // populate/update global CV/knob vars (time is slewed to reduce noise at large time values) - timeKnob = minMaxKnob(1.0 - hw.GetAdcValue(TIME_KNOB), 0.0008); - feedbackKnob = fourPointWarp(1.0 - minMaxKnob(hw.GetAdcValue(FEEDBACK_KNOB), 0.028)); - skewKnob = fourPointWarp(1.0 - minMaxKnob(hw.GetAdcValue(SKEW_KNOB), 0.0008)); - - timeCv = clamp(hw.GetAdcValue(TIME_CV) - timeCvOffset, -1, 1); - feedbackCv = clamp(hw.GetAdcValue(FEEDBACK_CV) - feedbackCvOffset, -1, 1); - skewCv = clamp(hw.GetAdcValue(SKEW_CV) - skewCvOffset, -1, 1); - - // read modulation / normalized channels - for (int i = 0; i < kNumNormalizedChannels; i++) { - modulation_values_[i] = - clamp( - hw.GetAdcValue(normalized_channels_[i]) - normalized_offsets_[i], - -1, - 1); - } - - // read slider values - drySlider = minMaxSlider(1.0 - hw.GetAdcValue(DRY_SLIDER)); - for (int i = 1; i < 9; i++) { - sliderAmpValues_[i-1] = max(0.0f, minMaxSlider(1.0f - hw.GetSliderValue(i))); - } - - // calculate time based on clock if present, otherwise simple time - float time = 0.0; - if(clockRateDetector.GetInterval() > 0.0) { - // 12 quantized steps for knob, 10 for CV (idk what these quanta should actually be) - // time doubles and halves with each step, they are additive/subtractive - float timeCoef = pow(2.0, (timeKnobSchmidt.Process((1.0-timeKnob)*12)) + (timeCvSchmidt.Process(timeCv*10))) / pow(2.0, 6.0); - time = clockRateDetector.GetInterval() / timeCoef; - // make sure time is a power of two less than the max time available in the buffer - while(time > TIME_SECONDS) time *= 0.5; - } else { - // time linear with knob, scaled v/oct style with CV - time = pow(timeKnobSlew.Process(timeKnob), 2.0) * 8.0 / pow(2.0, timeCvSlew.Process(timeCv) * 5.0); - } - - // force time down to a max value (taking whichever is lesser, the max or the time) - time = std::min((float)TIME_SECONDS, time); - // condition feedback knob to have deadzone in the middle, add CV - float feedback = clamp(fourPointWarp(feedbackKnobSlew.Process(feedbackKnob)) * 2.0 + feedbackCvSlew.Process(feedbackCv), 0, 3); - // condition distribution knob value to have deadzone in the middle, add CV - float distribution = fourPointWarp(distributionKnobSlew.Process(skewKnob)) + distributionCvSlew.Process(skewCv); - - finalTimeValue = time; - finalFeedbackValue = feedback; - finalDistributionValue = distribution; + ui.ProcessAllControls(); for(int i=0; i<9; i++) { - if(i<8) { - // set LEDs based on loudness for last 8 sliders - float loudness = timeMachine.timeMachineLeft.readHeads[i].loudness.Get(); - loudness = max(loudness, timeMachine.timeMachineRight.readHeads[i].loudness.Get()); - if(setLeds) { - leds[i+1].Set(loudness); - leds[i+1].Update(); - } - } else { - // set LEDs based on loudness for first slider - float loudness = timeMachine.timeMachineLeft.loudness.Get(); - loudness = max(loudness, timeMachine.timeMachineRight.loudness.Get()); - if(setLeds) { - leds[0].Set(loudness); - leds[0].Update(); - } - } + float loudnessLeft = timeMachine.timeMachineLeft.GetLoudness(i); + float loudnessRight = timeMachine.timeMachineRight.GetLoudness(i); + + leds.Set(i, max(loudnessLeft, loudnessRight)); } // set time machine dry slider value, feedback, "blur" which is semi-deprecated - timeMachine.Set(drySlider, feedback, feedback); // controlling "blur" with feedback now??? + timeMachine.Set(ui.drySlider, ui.feedback, ui.feedback); // controlling "blur" with feedback now??? for(int i=1; i<9; i++) { // let last 8 slider time/amp/blur values for left channel time machine instance timeMachine.timeMachineLeft.readHeads[i-1].Set( - spread((i / 8.0), distribution) * time, + spread((i / 8.0), ui.distribution) * ui.time, readHeadAmp(i), - max(0., feedback-1.0) + max(0., ui.feedback-1.0) ); // let last 8 slider time/amp/blur values for right channel time machine instance timeMachine.timeMachineRight.readHeads[i-1].Set( - spread((i / 8.0), distribution) * time, + spread((i / 8.0), ui.distribution) * ui.time, readHeadAmp(i), - max(0., feedback-1.0) + max(0., ui.feedback-1.0) ); } for (size_t i = 0; i < size; i++) { // process gate for clock rate detector at audio rate (per-sample) so it calculates clock correctly - clockRateDetector.Process(hw.gate_in_2.State()); + ui.ProcessClockRate(hw.gate_in_2.State()); + // process input into time machine float* output = timeMachine.Process(in[0][i], in[1][i]); // set hardware output to time machine output @@ -261,26 +147,6 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s droppedFrames--; } -bool shouldCalibrate() { - bool shouldCalibrate = \ - (hw.GetAdcValue(SKEW_CV) < 0.01) && \ - (hw.GetAdcValue(TIME_CV) < 0.01) && \ - (hw.GetAdcValue(FEEDBACK_CV) < 0.01) && \ - (hw.GetAdcValue(VCA_1_CV) < 0.01) && \ - (hw.GetAdcValue(VCA_2_CV) < 0.01) && \ - (hw.GetAdcValue(VCA_3_CV) < 0.01) && \ - (hw.GetAdcValue(VCA_4_CV) < 0.01) && \ - hw.gate_in_2.State(); - - for(int i=0; i<9; i++) { - shouldCalibrate &= hw.GetSliderValue(i) < 0.01; - } - - shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(TIME_KNOB)) > 0.95; - shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(SKEW_KNOB)) > 0.95; - shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(FEEDBACK_KNOB)) > 0.95; - return shouldCalibrate; -} void DetectNormalization() { bool expected_value = normalization_probe_state_ >> 31; @@ -322,53 +188,36 @@ int main(void) hw.Init(); hw.SetAudioBlockSize(4); // number of samples handled per callback + ui.Init(hw); + + calibrator.Init(&calibrationDataStorage); + dsy_gpio_pin gatePin = DaisyPatchSM::B9; gate.Init(&gatePin); // initialize LEDs - leds[0].Init(DaisyPatchSM::D1, false); - leds[1].Init(DaisyPatchSM::D2, false); - leds[2].Init(DaisyPatchSM::D3, false); - leds[3].Init(DaisyPatchSM::D4, false); - leds[4].Init(DaisyPatchSM::D5, false); - // hacky selector for my weird dev version (Eris) - #if BODGE - leds[5].Init(DaisyPatchSM::B8, false); - #else - leds[5].Init(DaisyPatchSM::A9, false); - #endif - leds[6].Init(DaisyPatchSM::D10, false); - leds[7].Init(DaisyPatchSM::D7, false); - leds[8].Init(DaisyPatchSM::D6, false); + leds.Init( + DaisyPatchSM::D1, + DaisyPatchSM::D2, + DaisyPatchSM::D3, + DaisyPatchSM::D4, + DaisyPatchSM::D5, + DaisyPatchSM::A9, + DaisyPatchSM::D10, + DaisyPatchSM::D7, + DaisyPatchSM::D6); + // set sample rate hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ); - // init slew limiter for time (we should tune this more delibrately) - timeKnobSlew.Init(0.5, 0.0005); - feedbackKnobSlew.Init(0.5, 0.0005); - distributionKnobSlew.Init(0.5, 0.0005); - - timeCvSlew.Init(0.5, 0.0005); - feedbackCvSlew.Init(0.5, 0.0005); - distributionCvSlew.Init(0.5, 0.0005); - - vca1CvSlew.Init(0.5, 0.0005); - vca2CvSlew.Init(0.5, 0.0005); - vca3CvSlew.Init(0.5, 0.0005); - vca4CvSlew.Init(0.5, 0.0005); - - - // init clock rate detector - clockRateDetector.Init(hw.AudioSampleRate()); - // init time machine - timeMachine.Init(hw.AudioSampleRate(), TIME_SECONDS + (((float)BUFFER_WIGGLE_ROOM_SAMPLES) * 0.5 / hw.AudioSampleRate()), bufferLeft, bufferRight); - - // load calibration data, using sensible defaults - CalibrationDataStorage.Init({0.0f, 0.0f, 0.0f, false}); - CalibrationDataStorage.GetSettings(); - CalibrationData &savedCalibrationData = CalibrationDataStorage.GetSettings(); + timeMachine.Init( + hw.AudioSampleRate(), + TIME_SECONDS + (((float)BUFFER_WIGGLE_ROOM_SAMPLES) * 0.5 / hw.AudioSampleRate()), + bufferLeft, + bufferRight); + // init cpu meter cpuMeter.Init(hw.AudioSampleRate(), hw.AudioBlockSize()); @@ -376,122 +225,60 @@ int main(void) // start time machine hardware audio and logging hw.StartAudio(AudioCallback); - // LED startup sequence - int ledSeqDelay = 100; - for(int i=0; i<9; i++) { - for(int j=0; j<9; j++) { - leds[j].Set(j == i ? 1.0 : 0.0); - leds[j].Update(); - } - hw.PrintLine("%d", i); - System::Delay(ledSeqDelay); - } - - if(shouldCalibrate()) { - - bool calibrationReady = true; + leds.InitStartupSequence(); - // do reverse LED startup sequence while - // checking that we definitely want to calibrate - for(int i=0; i<(5000/ledSeqDelay); i++) { - for(int j=0; j<9; j++) { - leds[j].Set(j == (8 - (i%9)) ? 1.0 : 0.0); - leds[j].Update(); - } - System::Delay(ledSeqDelay); - calibrationReady &= shouldCalibrate(); - if(!calibrationReady) break; - } - - if(calibrationReady) { - // perform calibration routine - int numSamples = 128; - for(int i = 0; i < numSamples; i++) { - // accumulate cv values - savedCalibrationData.timeCvOffset += timeCv; - savedCalibrationData.skewCvOffset += skewCv; - savedCalibrationData.feedbackCvOffset += feedbackCv; - - savedCalibrationData.vca1CvOffset += vca1Cv; - savedCalibrationData.vca2CvOffset += vca2Cv; - savedCalibrationData.vca3CvOffset += vca3Cv; - savedCalibrationData.vca4CvOffset += vca4Cv; - - // wait 10ms - System::Delay(10); - // set LEDs - for(int ledIndex=0; ledIndex<9; ledIndex++) { - leds[ledIndex].Set(i % 8 < 4 ? 1.0f : 0.0f); - leds[ledIndex].Update(); - } - } - - // divide CVs by number of samples taken to get average - savedCalibrationData.timeCvOffset = savedCalibrationData.timeCvOffset / ((float)numSamples); - savedCalibrationData.skewCvOffset = savedCalibrationData.skewCvOffset / ((float)numSamples); - savedCalibrationData.feedbackCvOffset = savedCalibrationData.feedbackCvOffset / ((float)numSamples); - - savedCalibrationData.vca1CvOffset = savedCalibrationData.vca1CvOffset / ((float)numSamples); - savedCalibrationData.vca2CvOffset = savedCalibrationData.vca2CvOffset / ((float)numSamples); - savedCalibrationData.vca3CvOffset = savedCalibrationData.vca3CvOffset / ((float)numSamples); - savedCalibrationData.vca4CvOffset = savedCalibrationData.vca4CvOffset / ((float)numSamples); - - // set calibrated value to true - savedCalibrationData.calibrated = true; - - // save calibration data - CalibrationDataStorage.Save(); - } - } + // Calibrate if needed + calibrator.Calibrate(hw, leds); - timeCvOffset = savedCalibrationData.timeCvOffset; - skewCvOffset = savedCalibrationData.skewCvOffset; - feedbackCvOffset = savedCalibrationData.feedbackCvOffset; - normalized_offsets_[0] = savedCalibrationData.vca1CvOffset; - normalized_offsets_[1] = savedCalibrationData.vca2CvOffset; - normalized_offsets_[2] = savedCalibrationData.vca3CvOffset; - normalized_offsets_[3] = savedCalibrationData.vca4CvOffset; + //TODO: CALIBRATION OFFSETS NEED INJECTING TO UI + // timeCvOffset = savedCalibrationData.timeCvOffset; + // skewCvOffset = savedCalibrationData.skewCvOffset; + // feedbackCvOffset = savedCalibrationData.feedbackCvOffset; + // normalized_offsets_[0] = savedCalibrationData.vca1CvOffset; + // normalized_offsets_[1] = savedCalibrationData.vca2CvOffset; + // normalized_offsets_[2] = savedCalibrationData.vca3CvOffset; + // normalized_offsets_[3] = savedCalibrationData.vca4CvOffset; hw.StartLog(); - setLeds = true; + leds.StartUi(); while(1) { DetectNormalization(); if (DEVELOPMENT_MODE) { // print diagnostics - hw.PrintLine("TIME_CV: " FLT_FMT(6), FLT_VAR(6, timeCv)); - hw.PrintLine("FEEDBACK_CV: " FLT_FMT(6), FLT_VAR(6, feedbackCv)); - hw.PrintLine("SKEW_CV: " FLT_FMT(6), FLT_VAR(6, skewCv)); + hw.PrintLine("TIME_CV: " FLT_FMT(6), FLT_VAR(6, ui.timeCv)); + hw.PrintLine("FEEDBACK_CV: " FLT_FMT(6), FLT_VAR(6, ui.feedbackCv)); + hw.PrintLine("SKEW_CV: " FLT_FMT(6), FLT_VAR(6, ui.skewCv)); if (is_patched_[0]) { - hw.PrintLine("VCA_1_CV: " FLT_FMT(6), FLT_VAR(6, vca1Cv)); + hw.PrintLine("VCA_1_CV: " FLT_FMT(6), FLT_VAR(6, ui.vca1Cv)); } else { hw.PrintLine("VCA_1_CV is unpatched!"); } if (is_patched_[1]) { - hw.PrintLine("VCA_2_CV: " FLT_FMT(6), FLT_VAR(6, vca2Cv)); + hw.PrintLine("VCA_2_CV: " FLT_FMT(6), FLT_VAR(6, ui.vca2Cv)); } else { hw.PrintLine("VCA_2_CV is unpatched!"); } if (is_patched_[2]) { - hw.PrintLine("VCA_3_CV: " FLT_FMT(6), FLT_VAR(6, vca3Cv)); + hw.PrintLine("VCA_3_CV: " FLT_FMT(6), FLT_VAR(6, ui.vca3Cv)); } else { hw.PrintLine("VCA_3_CV is unpatched!"); } if (is_patched_[3]) { - hw.PrintLine("VCA_4_CV: " FLT_FMT(6), FLT_VAR(6, vca4Cv)); + hw.PrintLine("VCA_4_CV: " FLT_FMT(6), FLT_VAR(6, ui.vca4Cv)); } else { hw.PrintLine("VCA_4_CV is unpatched!"); } - hw.PrintLine("TIME_KNOB: " FLT_FMT(6), FLT_VAR(6, timeKnob)); - hw.PrintLine("FEEDBACK_KNOB: " FLT_FMT(6), FLT_VAR(6, feedbackKnob)); - hw.PrintLine("SKEW_KNOB: " FLT_FMT(6), FLT_VAR(6, skewKnob)); + hw.PrintLine("TIME_KNOB: " FLT_FMT(6), FLT_VAR(6, ui.timeKnob)); + hw.PrintLine("FEEDBACK_KNOB: " FLT_FMT(6), FLT_VAR(6, ui.feedbackKnob)); + hw.PrintLine("SKEW_KNOB: " FLT_FMT(6), FLT_VAR(6, ui.skewKnob)); hw.PrintLine("GATE IN: %d", hw.gate_in_2.State()); hw.PrintLine("GATE IN: %d", hw.gate_in_1.State()); @@ -506,7 +293,7 @@ int main(void) hw.PrintLine("CALIBRATED: %d", savedCalibrationData.calibrated); hw.PrintLine("FINAL TIME: " FLT_FMT(6), FLT_VAR(6, finalTimeValue)); - hw.PrintLine("FINAL DISTRIBUTION: " FLT_FMT(6), FLT_VAR(6, finalDistributionValue)); + hw.PrintLine("FINAL DISTRIBUTION: " FLT_FMT(6), FLT_VAR(6, ui.distribution)); hw.PrintLine("FINAL FEEDBACK: " FLT_FMT(6), FLT_VAR(6, finalFeedbackValue)); hw.PrintLine("CPU AVG: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetAvgCpuLoad())); diff --git a/TimeMachine/dsp/stereo_time_machine.h b/TimeMachine/dsp/stereo_time_machine.h index f8f37c1..70fb306 100644 --- a/TimeMachine/dsp/stereo_time_machine.h +++ b/TimeMachine/dsp/stereo_time_machine.h @@ -1,4 +1,5 @@ #include "time_machine.h" +#include "daisysp.h" #ifndef STEREO_TIME_MACHINE_H_ #define STEREO_TIME_MACHINE_H_ @@ -21,5 +22,12 @@ class StereoTimeMachine { outputs[1] = timeMachineRight.Process(inRight); return outputs; } + + // 0 is DRY + // 1-8 is ReadHeads + float GetLoudness(int idx) { + float loudness = timeMachineLeft.GetLoudness(idx); + return std::max(loudness, timeMachineRight.GetLoudness(idx)); + } }; #endif // STEREO_TIME_MACHINE_H_ diff --git a/TimeMachine/dsp/time_machine.h b/TimeMachine/dsp/time_machine.h index 3ef819d..fed553f 100644 --- a/TimeMachine/dsp/time_machine.h +++ b/TimeMachine/dsp/time_machine.h @@ -76,6 +76,16 @@ class TimeMachine { return out; } + + // 0 is DRY + // 1-8 is ReadHeads + float GetLoudness(int idx) { + if (idx == 0) { + return loudness.Get(); + } else { + return readHeads[idx - 1].loudness.Get(); + } + } }; #endif // TIME_MACHINE_H_ \ No newline at end of file diff --git a/TimeMachine/ui/calibration_data.h b/TimeMachine/ui/calibration_data.h new file mode 100644 index 0000000..63546ea --- /dev/null +++ b/TimeMachine/ui/calibration_data.h @@ -0,0 +1,33 @@ +#ifndef CALIBRATION_DATA_H_ +#define CALIBRATION_DATA_H_ + +//Setting Struct containing parameters we want to save to flash +struct CalibrationData { + float timeCvOffset = 0.0; + float skewCvOffset = 0.0; + float feedbackCvOffset = 0.0; + + float vca1CvOffset = 0.0; + float vca2CvOffset = 0.0; + float vca3CvOffset = 0.0; + float vca4CvOffset = 0.0; + + int calibrated = false; + + //Overloading the != operator + //This is necessary as this operator is used in the PersistentStorage source code + bool operator!=(const CalibrationData& a) const { + return !( + a.timeCvOffset==timeCvOffset && \ + a.skewCvOffset==skewCvOffset && \ + a.feedbackCvOffset==feedbackCvOffset && \ + a.vca1CvOffset==vca1CvOffset && \ + a.vca2CvOffset==vca2CvOffset && \ + a.vca3CvOffset==vca3CvOffset && \ + a.vca4CvOffset==vca4CvOffset && \ + a.calibrated==calibrated + ); + } +}; + +#endif // CALIBRATION_DATA_H_ \ No newline at end of file diff --git a/TimeMachine/ui/calibrator.h b/TimeMachine/ui/calibrator.h new file mode 100644 index 0000000..b3985a3 --- /dev/null +++ b/TimeMachine/ui/calibrator.h @@ -0,0 +1,118 @@ +#include "../time_machine_hardware.h" +#include "calibration_data.h" + +#include "daisy.h" + +#include "leds.h" +#include "../dsp/util.h" + +#ifndef CALIBRATOR_H_ +#define CALIBRATOR_H_ + +class Calibrator { + public: + void Init(PersistentStorage* cs) { + calibration_storage_ = cs; + + // load calibration data, using sensible defaults + calibration_storage_->Init({0.0f, 0.0f, 0.0f, false}); + saved_calibration_data_ = &calibration_storage_->GetSettings(); + } + + void Calibrate( + oam::time_machine::TimeMachineHardware hw, + oam::time_machine::Leds leds) { + if(ShouldCalibrate(hw, leds)) { + + // perform calibration routine + int numSamples = 128; + for(int i = 0; i < numSamples; i++) { + + // accumulate cv values + saved_calibration_data_->timeCvOffset += timeCv; + saved_calibration_data_->skewCvOffset += skewCv; + saved_calibration_data_->feedbackCvOffset += feedbackCv; + + saved_calibration_data_->vca1CvOffset += vca1Cv; + saved_calibration_data_->vca2CvOffset += vca2Cv; + saved_calibration_data_->vca3CvOffset += vca3Cv; + saved_calibration_data_->vca4CvOffset += vca4Cv; + + // wait 10ms + System::Delay(10); + + // set LEDs + for(int ledIndex=0; ledIndex < 9; ledIndex++) { + leds.Set(ledIndex, i % 8 < 4 ? 1.0f : 0.0f); + } + } + + // divide CVs by number of samples taken to get average + saved_calibration_data_->timeCvOffset = saved_calibration_data_->timeCvOffset / ((float)numSamples); + saved_calibration_data_->skewCvOffset = saved_calibration_data_->skewCvOffset / ((float)numSamples); + saved_calibration_data_->feedbackCvOffset = saved_calibration_data_->feedbackCvOffset / ((float)numSamples); + + saved_calibration_data_->vca1CvOffset = saved_calibration_data_->vca1CvOffset / ((float)numSamples); + saved_calibration_data_->vca2CvOffset = saved_calibration_data_->vca2CvOffset / ((float)numSamples); + saved_calibration_data_->vca3CvOffset = saved_calibration_data_->vca3CvOffset / ((float)numSamples); + saved_calibration_data_->vca4CvOffset = saved_calibration_data_->vca4CvOffset / ((float)numSamples); + + // set calibrated value to true + saved_calibration_data_->calibrated = true; + + // save calibration data + calibration_storage_->Save(); + } + } + + private: + PersistentStorage* calibration_storage_; + + CalibrationData* saved_calibration_data_; + + bool ShouldCalibrate( + oam::time_machine::TimeMachineHardware hw, + oam::time_machine::Leds leds) { + + if (!CheckIfInCalibrationPositions(hw)) return; + + bool calibrationReady = true; + // do reverse LED startup sequence while + // checking that we definitely want to calibrate + int delayBetweenSequencesMs = 100; + for(int i=0; i < (5000/delayBetweenSequencesMs); i++) { + + leds.InitCalibrationSequence(i); + + System::Delay(delayBetweenSequencesMs); + + calibrationReady &= CheckIfInCalibrationPositions(hw); + if(!calibrationReady) break; + } + + return calibrationReady; + } + + bool CheckIfInCalibrationPositions(oam::time_machine::TimeMachineHardware hw) { + bool shouldCalibrate = \ + (hw.GetAdcValue(SKEW_CV) < 0.01) && \ + (hw.GetAdcValue(TIME_CV) < 0.01) && \ + (hw.GetAdcValue(FEEDBACK_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_1_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_2_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_3_CV) < 0.01) && \ + (hw.GetAdcValue(VCA_4_CV) < 0.01) && \ + hw.gate_in_2.State(); + + for(int i=0; i<9; i++) { + shouldCalibrate &= hw.GetSliderValue(i) < 0.01; + } + + shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(TIME_KNOB)) > 0.95; + shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(SKEW_KNOB)) > 0.95; + shouldCalibrate &= minMaxKnob(1.0 - hw.GetAdcValue(FEEDBACK_KNOB)) > 0.95; + return shouldCalibrate; + } +}; + +#endif // CALIBRATOR_H_ \ No newline at end of file diff --git a/TimeMachine/ui/leds.h b/TimeMachine/ui/leds.h new file mode 100644 index 0000000..00ff86e --- /dev/null +++ b/TimeMachine/ui/leds.h @@ -0,0 +1,82 @@ +#include "daisy.h" + +#ifndef LEDS_H_ +#define LEDS_H_ + +namespace oam { + namespace time_machine { + + class Leds { + public: + + void Set(int idx, float intensity) { + if (initialized_ && ui_started_) { + leds_[idx].Set(intensity); + leds_[idx].Update(); + } + } + + void Init( + daisy::Pin dryPin, + daisy::Pin pin1, + daisy::Pin pin2, + daisy::Pin pin3, + daisy::Pin pin4, + daisy::Pin pin5, + daisy::Pin pin6, + daisy::Pin pin7, + daisy::Pin pin8) { + + leds_[0].Init(dryPin, false); + leds_[1].Init(pin1, false); + leds_[2].Init(pin2, false); + leds_[3].Init(pin3, false); + leds_[4].Init(pin4, false); + leds_[5].Init(pin5, false); + leds_[6].Init(pin6, false); + leds_[7].Init(pin7, false); + leds_[8].Init(pin8, false); + + initialized_ = true; + + } + + + void InitStartupSequence() { + if(!initialized_) return; + + // LED startup sequence + for(int i=0; i<9; i++) { + for(int j=0; j<9; j++) { + leds_[j].Set(j == i ? 1.0 : 0.0); + leds_[j].Update(); + } + System::Delay(ledSeqDelayMs_); + }; + }; + + void InitCalibrationSequence(int idx) { + if (!initialized_) return; + + for(int j=0; j<9; j++) { + leds_[j].Set(j == (8 - (idx % 9)) ? 1.0 : 0.0); + leds_[j].Update(); + } + } + + void StartUi() { + ui_started_ = true; + } + private: + bool initialized_ = false; + bool ui_started_ = false; + int ledSeqDelayMs_ = 100; + + // LED at idx 0 is DRY + // read heads are at 1-8 + daisy::Led leds_[9]; + }; + } +} + +#endif // LEDS_H_ diff --git a/TimeMachine/ui/ui.h b/TimeMachine/ui/ui.h new file mode 100644 index 0000000..31c9ec4 --- /dev/null +++ b/TimeMachine/ui/ui.h @@ -0,0 +1,135 @@ +#include "daisy.h" +#include "../time_machine_hardware.h" +#include "../dsp/util.h" +#include "../dsp/slew.h" +#include "../dsp/continuous_schmidt.h" +#include "../dsp/clock_rate_detector.h" + + +#ifndef UI_H_ +#define UI_H_ + +class Ui { + public: + + ClockRateDetector clockRateDetector; + ContSchmidt timeKnobSchmidt; + ContSchmidt timeCvSchmidt; + + Slew timeKnobSlew; + Slew feedbackKnobSlew; + Slew distributionKnobSlew; + Slew timeCvSlew; + Slew feedbackCvSlew; + Slew distributionCvSlew; + + Slew vca1CvSlew; + Slew vca2CvSlew; + Slew vca3CvSlew; + Slew vca4CvSlew; + + // global storage for CV/knobs so we don't get them twice to print diagnostics + float timeCv = 0.0; + float feedbackCv = 0.0; + float skewCv = 0.0; + + float vca1Cv = 0.0; + float vca2Cv = 0.0; + float vca3Cv = 0.0; + float vca4Cv = 0.0; + + float timeKnob = 0.0; + float feedbackKnob = 0.0; + float skewKnob = 0.0; + float drySlider = 0.0; + float delaySliders = 0.0; + float sliderAmpValues_[8] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + + float distribution; + float feedback; + float time; + + void Init(oam::time_machine::TimeMachineHardware hw) { + hw_ = &hw; + + // init slew limiter for time (we should tune this more delibrately) + timeKnobSlew.Init(0.5, 0.0005); + feedbackKnobSlew.Init(0.5, 0.0005); + distributionKnobSlew.Init(0.5, 0.0005); + + timeCvSlew.Init(0.5, 0.0005); + feedbackCvSlew.Init(0.5, 0.0005); + distributionCvSlew.Init(0.5, 0.0005); + + vca1CvSlew.Init(0.5, 0.0005); + vca2CvSlew.Init(0.5, 0.0005); + vca3CvSlew.Init(0.5, 0.0005); + vca4CvSlew.Init(0.5, 0.0005); + + clockRateDetector.Init(hw_->AudioSampleRate()); + + ProcessAllControls(); + } + + void ProcessClockRate(bool triggered) { + clockRateDetector.Process(triggered); + } + + void ProcessAllControls() { + // process controls + hw_->ProcessAllControls(); + + // populate/update global CV/knob vars (time is slewed to reduce noise at large time values) + timeKnob = minMaxKnob(1.0 - hw_->GetAdcValue(TIME_KNOB), 0.0008); + feedbackKnob = fourPointWarp(1.0 - minMaxKnob(hw_->GetAdcValue(FEEDBACK_KNOB), 0.028)); + skewKnob = fourPointWarp(1.0 - minMaxKnob(hw_->GetAdcValue(SKEW_KNOB), 0.0008)); + + timeCv = clamp(hw_->GetAdcValue(TIME_CV) - timeCvOffset, -1, 1); + feedbackCv = clamp(hw_->GetAdcValue(FEEDBACK_CV) - feedbackCvOffset, -1, 1); + skewCv = clamp(hw_->GetAdcValue(SKEW_CV) - skewCvOffset, -1, 1); + + // read modulation / normalized channels + for (int i = 0; i < kNumNormalizedChannels; i++) { + modulation_values_[i] = + clamp( + hw_->GetAdcValue(normalized_channels_[i]) - normalized_offsets_[i], + -1, + 1); + } + + // read slider values + drySlider = minMaxSlider(1.0 - hw_->GetAdcValue(DRY_SLIDER)); + for (int i = 1; i < 9; i++) { + sliderAmpValues_[i-1] = std::max(0.0f, minMaxSlider(1.0f - hw_->GetSliderValue(i))); + } + + // calculate time based on clock if present, otherwise simple time + float tmp_time = 0.0; + if(clockRateDetector.GetInterval() > 0.0) { + // 12 quantized steps for knob, 10 for CV (idk what these quanta should actually be) + // time doubles and halves with each step, they are additive/subtractive + float timeCoef = pow(2.0, (timeKnobSchmidt.Process((1.0-timeKnob)*12)) + (timeCvSchmidt.Process(timeCv*10))) / pow(2.0, 6.0); + tmp_time = clockRateDetector.GetInterval() / timeCoef; + // make sure time is a power of two less than the max time available in the buffer + while(tmp_time > TIME_SECONDS) tmp_time *= 0.5; + } else { + // time linear with knob, scaled v/oct style with CV + tmp_time = pow(timeKnobSlew.Process(timeKnob), 2.0) * 8.0 / pow(2.0, timeCvSlew.Process(timeCv) * 5.0); + } + + // force time down to a max value (taking whichever is lesser, the max or the time) + time = std::min((float)TIME_SECONDS, tmp_time); + + // condition distribution knob value to have deadzone in the middle, add CV + distribution = fourPointWarp(distributionKnobSlew.Process(skewKnob)) + distributionCvSlew.Process(skewCv); + + // condition feedback knob to have deadzone in the middle, add CV + feedback = clamp(fourPointWarp(feedbackKnobSlew.Process(feedbackKnob)) * 2.0 + feedbackCvSlew.Process(feedbackCv), 0, 3); + } + + private: + oam::time_machine::TimeMachineHardware* hw_ +}; + + +#endif // UI_H_ \ No newline at end of file From 2ac08dfa8a8aeff99a2288f101ab536986761c4c Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Mon, 5 Aug 2024 22:50:48 +0300 Subject: [PATCH 8/9] comment out for easier debugging --- TimeMachine/TimeMachine.cpp | 34 ++++++++++++++++++---------------- TimeMachine/ui/calibrator.h | 18 +++++++++--------- TimeMachine/ui/ui.h | 30 ++++++++++++++++++------------ 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index 98c98e4..f054e2d 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -81,8 +81,8 @@ int droppedFrames = 0; // if modulation is patched, then slider acts as attenuverter for modulation // if modulation is unpatched, slider is // @sliderIdx is between 1 and 8 (incl.); -float readHeadAmp(int sliderIdx) { - float sliderAmpValue = sliderAmpValues_[sliderIdx - 1]; +float readHeadAmp(int sliderIdx, Ui ui_local) { + float sliderAmpValue = ui_local.sliderAmpValues_[sliderIdx - 1]; // 4 modulation inputs int modulationIndex = (sliderIdx - 1) / 2; @@ -119,13 +119,13 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s // let last 8 slider time/amp/blur values for left channel time machine instance timeMachine.timeMachineLeft.readHeads[i-1].Set( spread((i / 8.0), ui.distribution) * ui.time, - readHeadAmp(i), + readHeadAmp(i, ui), max(0., ui.feedback-1.0) ); // let last 8 slider time/amp/blur values for right channel time machine instance timeMachine.timeMachineRight.readHeads[i-1].Set( spread((i / 8.0), ui.distribution) * ui.time, - readHeadAmp(i), + readHeadAmp(i, ui), max(0., ui.feedback-1.0) ); } @@ -184,10 +184,13 @@ void DetectNormalization() { int main(void) { + // init time machine hardware hw.Init(); - hw.SetAudioBlockSize(4); // number of samples handled per callback + hw.StartLog(true); + hw.SetAudioBlockSize(4); // number of samples handled per callback + hw.PrintLine("AUDIO_INITIALIZED"); ui.Init(hw); calibrator.Init(&calibrationDataStorage); @@ -223,12 +226,12 @@ int main(void) cpuMeter.Init(hw.AudioSampleRate(), hw.AudioBlockSize()); // start time machine hardware audio and logging - hw.StartAudio(AudioCallback); - - leds.InitStartupSequence(); + //hw.StartAudio(AudioCallback); + hw.PrintLine("AUDIO_CALLBACK_STARTED"); + //leds.InitStartupSequence(); // Calibrate if needed - calibrator.Calibrate(hw, leds); + //calibrator.Calibrate(hw, leds); //TODO: CALIBRATION OFFSETS NEED INJECTING TO UI // timeCvOffset = savedCalibrationData.timeCvOffset; @@ -239,12 +242,11 @@ int main(void) // normalized_offsets_[2] = savedCalibrationData.vca3CvOffset; // normalized_offsets_[3] = savedCalibrationData.vca4CvOffset; - hw.StartLog(); leds.StartUi(); - + hw.PrintLine("UI STARTED"); while(1) { - DetectNormalization(); + //DetectNormalization(); if (DEVELOPMENT_MODE) { // print diagnostics @@ -287,10 +289,10 @@ int main(void) hw.PrintLine("CV IN 3: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_6))); hw.PrintLine("CV IN 4: " FLT_FMT(6), FLT_VAR(6, hw.GetAdcValue(CV_7))); - hw.PrintLine("TIME_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.timeCvOffset)); - hw.PrintLine("FEEDBACK_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.feedbackCvOffset)); - hw.PrintLine("SKEW_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.skewCvOffset)); - hw.PrintLine("CALIBRATED: %d", savedCalibrationData.calibrated); + // hw.PrintLine("TIME_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.timeCvOffset)); + // hw.PrintLine("FEEDBACK_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.feedbackCvOffset)); + // hw.PrintLine("SKEW_CAL: " FLT_FMT(6), FLT_VAR(6, savedCalibrationData.skewCvOffset)); + // hw.PrintLine("CALIBRATED: %d", savedCalibrationData.calibrated); hw.PrintLine("FINAL TIME: " FLT_FMT(6), FLT_VAR(6, finalTimeValue)); hw.PrintLine("FINAL DISTRIBUTION: " FLT_FMT(6), FLT_VAR(6, ui.distribution)); diff --git a/TimeMachine/ui/calibrator.h b/TimeMachine/ui/calibrator.h index b3985a3..ea6c6f7 100644 --- a/TimeMachine/ui/calibrator.h +++ b/TimeMachine/ui/calibrator.h @@ -28,15 +28,15 @@ class Calibrator { int numSamples = 128; for(int i = 0; i < numSamples; i++) { - // accumulate cv values - saved_calibration_data_->timeCvOffset += timeCv; - saved_calibration_data_->skewCvOffset += skewCv; - saved_calibration_data_->feedbackCvOffset += feedbackCv; + // // accumulate cv values TODO: properly inject new values + // saved_calibration_data_->timeCvOffset += timeCv; + // saved_calibration_data_->skewCvOffset += skewCv; + // saved_calibration_data_->feedbackCvOffset += feedbackCv; - saved_calibration_data_->vca1CvOffset += vca1Cv; - saved_calibration_data_->vca2CvOffset += vca2Cv; - saved_calibration_data_->vca3CvOffset += vca3Cv; - saved_calibration_data_->vca4CvOffset += vca4Cv; + // saved_calibration_data_->vca1CvOffset += vca1Cv; + // saved_calibration_data_->vca2CvOffset += vca2Cv; + // saved_calibration_data_->vca3CvOffset += vca3Cv; + // saved_calibration_data_->vca4CvOffset += vca4Cv; // wait 10ms System::Delay(10); @@ -74,7 +74,7 @@ class Calibrator { oam::time_machine::TimeMachineHardware hw, oam::time_machine::Leds leds) { - if (!CheckIfInCalibrationPositions(hw)) return; + if (!CheckIfInCalibrationPositions(hw)) return false; bool calibrationReady = true; // do reverse LED startup sequence while diff --git a/TimeMachine/ui/ui.h b/TimeMachine/ui/ui.h index 31c9ec4..554cae3 100644 --- a/TimeMachine/ui/ui.h +++ b/TimeMachine/ui/ui.h @@ -43,6 +43,12 @@ class Ui { float skewKnob = 0.0; float drySlider = 0.0; float delaySliders = 0.0; + + //tmp offset values for compilation + float timeCvOffset = 0.0; + float feedbackCvOffset = 0.0; + float skewCvOffset = 0.0; + float sliderAmpValues_[8] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; float distribution; @@ -67,7 +73,7 @@ class Ui { vca4CvSlew.Init(0.5, 0.0005); clockRateDetector.Init(hw_->AudioSampleRate()); - + ProcessAllControls(); } @@ -88,14 +94,14 @@ class Ui { feedbackCv = clamp(hw_->GetAdcValue(FEEDBACK_CV) - feedbackCvOffset, -1, 1); skewCv = clamp(hw_->GetAdcValue(SKEW_CV) - skewCvOffset, -1, 1); - // read modulation / normalized channels - for (int i = 0; i < kNumNormalizedChannels; i++) { - modulation_values_[i] = - clamp( - hw_->GetAdcValue(normalized_channels_[i]) - normalized_offsets_[i], - -1, - 1); - } + // // read modulation / normalized channels + // for (int i = 0; i < kNumNormalizedChannels; i++) { + // modulation_values_[i] = + // clamp( + // hw_->GetAdcValue(normalized_channels_[i]) - normalized_offsets_[i], + // -1, + // 1); + // } // read slider values drySlider = minMaxSlider(1.0 - hw_->GetAdcValue(DRY_SLIDER)); @@ -111,14 +117,14 @@ class Ui { float timeCoef = pow(2.0, (timeKnobSchmidt.Process((1.0-timeKnob)*12)) + (timeCvSchmidt.Process(timeCv*10))) / pow(2.0, 6.0); tmp_time = clockRateDetector.GetInterval() / timeCoef; // make sure time is a power of two less than the max time available in the buffer - while(tmp_time > TIME_SECONDS) tmp_time *= 0.5; + while(tmp_time > 150) tmp_time *= 0.5; } else { // time linear with knob, scaled v/oct style with CV tmp_time = pow(timeKnobSlew.Process(timeKnob), 2.0) * 8.0 / pow(2.0, timeCvSlew.Process(timeCv) * 5.0); } // force time down to a max value (taking whichever is lesser, the max or the time) - time = std::min((float)TIME_SECONDS, tmp_time); + time = std::min((float)150, tmp_time); // condition distribution knob value to have deadzone in the middle, add CV distribution = fourPointWarp(distributionKnobSlew.Process(skewKnob)) + distributionCvSlew.Process(skewCv); @@ -128,7 +134,7 @@ class Ui { } private: - oam::time_machine::TimeMachineHardware* hw_ + oam::time_machine::TimeMachineHardware* hw_; }; From 03c39c79d70f443da56069881b65a878f0feb19a Mon Sep 17 00:00:00 2001 From: dsedleckas Date: Wed, 7 Aug 2024 15:07:05 +0300 Subject: [PATCH 9/9] Working debug --- TimeMachine/TimeMachine.cpp | 49 +++++++++++++++++++-------- TimeMachine/dsp/clock_rate_detector.h | 1 + TimeMachine/dsp/limiter.h | 3 +- TimeMachine/dsp/slew.h | 1 + TimeMachine/ui/calibrator.h | 4 +-- TimeMachine/ui/ui.h | 18 +++++++--- 6 files changed, 54 insertions(+), 22 deletions(-) diff --git a/TimeMachine/TimeMachine.cpp b/TimeMachine/TimeMachine.cpp index f054e2d..5980121 100644 --- a/TimeMachine/TimeMachine.cpp +++ b/TimeMachine/TimeMachine.cpp @@ -71,12 +71,10 @@ float finalTimeValue = 0.0; float finalDistributionValue = 0.0; float finalFeedbackValue = 0.0; -// delay setting LEDs for startup sequences -bool setLeds = false; - CpuLoadMeter cpuMeter; int droppedFrames = 0; +int audioCallBackRun = 0; // if modulation is patched, then slider acts as attenuverter for modulation // if modulation is unpatched, slider is @@ -98,12 +96,16 @@ float readHeadAmp(int sliderIdx, Ui ui_local) { // called every N samples (search for SetAudioBlockSize) void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) -{ +{ // cpu meter measurements start cpuMeter.OnBlockStart(); droppedFrames++; - - ui.ProcessAllControls(); + audioCallBackRun++; + if (audioCallBackRun > 10000000) { audioCallBackRun = 0; } + //hw.PrintLine("AUDIO_CALLBACK_CPU_BLOCK_OK"); + + //ui.ProcessAllControls(); + //hw.PrintLine("AUDIO_CALLBACK_CONTROLS_PROCESSED"); for(int i=0; i<9; i++) { float loudnessLeft = timeMachine.timeMachineLeft.GetLoudness(i); @@ -115,6 +117,8 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s // set time machine dry slider value, feedback, "blur" which is semi-deprecated timeMachine.Set(ui.drySlider, ui.feedback, ui.feedback); // controlling "blur" with feedback now??? + //hw.PrintLine("AUDIO_CALLBACK_DRY_SET"); + for(int i=1; i<9; i++) { // let last 8 slider time/amp/blur values for left channel time machine instance timeMachine.timeMachineLeft.readHeads[i-1].Set( @@ -130,6 +134,7 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s ); } + for (size_t i = 0; i < size; i++) { // process gate for clock rate detector at audio rate (per-sample) so it calculates clock correctly @@ -138,10 +143,12 @@ void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, s // process input into time machine float* output = timeMachine.Process(in[0][i], in[1][i]); // set hardware output to time machine output - out[0][i] = output[0]; - out[1][i] = output[1]; + out[0][i] = 0.0; //output[0]; + out[1][i] = 0.0; //output[1]; } + //hw.PrintLine("AUDIO_CALLBACK_OUTPUT_DONE"); + //cpu meter measurement stop cpuMeter.OnBlockEnd(); droppedFrames--; @@ -187,13 +194,15 @@ int main(void) // init time machine hardware hw.Init(); - hw.StartLog(true); - hw.SetAudioBlockSize(4); // number of samples handled per callback + //hw.SetAudioBlockSize(4); // number of samples handled per callback hw.PrintLine("AUDIO_INITIALIZED"); + ui.Init(hw); + hw.PrintLine("UI_INITIALIZED"); - calibrator.Init(&calibrationDataStorage); + calibrator.Init(calibrationDataStorage); + hw.PrintLine("CALIBRATOR_INITIALIZED"); dsy_gpio_pin gatePin = DaisyPatchSM::B9; gate.Init(&gatePin); @@ -209,11 +218,14 @@ int main(void) DaisyPatchSM::D10, DaisyPatchSM::D7, DaisyPatchSM::D6); - + + hw.PrintLine("LEDS_INITIALIZED"); // set sample rate hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ); + hw.PrintLine("SAMPELRATE: %d", hw.AudioSampleRate()); + // init time machine timeMachine.Init( hw.AudioSampleRate(), @@ -221,12 +233,15 @@ int main(void) bufferLeft, bufferRight); + hw.PrintLine("SAMPELRATE: %d", hw.AudioSampleRate()); + hw.PrintLine("MAXDELAY: %d", TIME_SECONDS + (((float)BUFFER_WIGGLE_ROOM_SAMPLES) * 0.5 / hw.AudioSampleRate())); + hw.PrintLine("SAMPELRATE: %d", hw.AudioSampleRate()); // init cpu meter cpuMeter.Init(hw.AudioSampleRate(), hw.AudioBlockSize()); // start time machine hardware audio and logging - //hw.StartAudio(AudioCallback); + hw.StartAudio(AudioCallback); hw.PrintLine("AUDIO_CALLBACK_STARTED"); //leds.InitStartupSequence(); @@ -244,9 +259,12 @@ int main(void) leds.StartUi(); - hw.PrintLine("UI STARTED"); + + hw.StartLog(true); + while(1) { //DetectNormalization(); + ui.ProcessAllControls(); if (DEVELOPMENT_MODE) { // print diagnostics @@ -303,6 +321,9 @@ int main(void) hw.PrintLine("CPU MAX: " FLT_FMT(6), FLT_VAR(6, cpuMeter.GetMaxCpuLoad())); hw.PrintLine("DROPPED FRAMES: %d", droppedFrames); + hw.PrintLine("AUDIO CALLBACK RUN: %d", audioCallBackRun); + hw.PrintLine("SAMPELRATE: %d", hw.AudioSampleRate()); + for(int i=0; i<9; i++) { hw.PrintLine("%d: " FLT_FMT(6), i, FLT_VAR(6, minMaxSlider(1.0 - hw.GetSliderValue(i)))); diff --git a/TimeMachine/dsp/clock_rate_detector.h b/TimeMachine/dsp/clock_rate_detector.h index 80cde74..a3f3e3d 100644 --- a/TimeMachine/dsp/clock_rate_detector.h +++ b/TimeMachine/dsp/clock_rate_detector.h @@ -15,6 +15,7 @@ class ClockRateDetector { lastVal = false; } void Init(int sr) { sampleRate = sr; } + bool isStale() { return samplesSinceLastClock > sampleRate * 2; } diff --git a/TimeMachine/dsp/limiter.h b/TimeMachine/dsp/limiter.h index b30abb7..5ca43a9 100644 --- a/TimeMachine/dsp/limiter.h +++ b/TimeMachine/dsp/limiter.h @@ -7,14 +7,13 @@ namespace oam { class Limiter { public: float gainCoef; - float attackCoef; float releaseCoef; void Init(float sampleRate) { gainCoef = 1; releaseCoef = 16.0 / sampleRate; } float Process(float in) { - float targetGainCoef = 1.0 / std::max(abs(in), (float)1.0); + float targetGainCoef = 1.0 / std::max(std::abs(in), (float)1.0); if(targetGainCoef < gainCoef) { gainCoef = targetGainCoef; } else { diff --git a/TimeMachine/dsp/slew.h b/TimeMachine/dsp/slew.h index d323ab0..8a68777 100644 --- a/TimeMachine/dsp/slew.h +++ b/TimeMachine/dsp/slew.h @@ -11,6 +11,7 @@ class Slew { double noiseCoef = 0.0; int settleSamples = 0; int settleSamplesThreshold = 96; + void Init(double coef = 0.001, double nf=0.0) { this->coef = coef; this->noiseFloor = nf; diff --git a/TimeMachine/ui/calibrator.h b/TimeMachine/ui/calibrator.h index ea6c6f7..ac8807f 100644 --- a/TimeMachine/ui/calibrator.h +++ b/TimeMachine/ui/calibrator.h @@ -11,8 +11,8 @@ class Calibrator { public: - void Init(PersistentStorage* cs) { - calibration_storage_ = cs; + void Init(PersistentStorage cs) { + calibration_storage_ = &cs; // load calibration data, using sensible defaults calibration_storage_->Init({0.0f, 0.0f, 0.0f, false}); diff --git a/TimeMachine/ui/ui.h b/TimeMachine/ui/ui.h index 554cae3..ade5339 100644 --- a/TimeMachine/ui/ui.h +++ b/TimeMachine/ui/ui.h @@ -51,9 +51,9 @@ class Ui { float sliderAmpValues_[8] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; - float distribution; - float feedback; - float time; + float distribution = 0.0; + float feedback = 0.0; + float time = 0.0; void Init(oam::time_machine::TimeMachineHardware hw) { hw_ = &hw; @@ -62,19 +62,29 @@ class Ui { timeKnobSlew.Init(0.5, 0.0005); feedbackKnobSlew.Init(0.5, 0.0005); distributionKnobSlew.Init(0.5, 0.0005); - + + hw_->PrintLine("UI_KNOB_SLEW_INITIALIZED"); + timeCvSlew.Init(0.5, 0.0005); feedbackCvSlew.Init(0.5, 0.0005); distributionCvSlew.Init(0.5, 0.0005); + hw_->PrintLine("UI_CV_SLEW_INITIALIZED"); + vca1CvSlew.Init(0.5, 0.0005); vca2CvSlew.Init(0.5, 0.0005); vca3CvSlew.Init(0.5, 0.0005); vca4CvSlew.Init(0.5, 0.0005); + hw_->PrintLine("UI_VCA_CV_SLEW_INITIALIZED"); + clockRateDetector.Init(hw_->AudioSampleRate()); + hw_->PrintLine("UI_CLOCK_RATE_DETECTOR_INITIALIZED"); + ProcessAllControls(); + + hw_->PrintLine("UI_INIT_COMPLETE"); } void ProcessClockRate(bool triggered) {