From 602b370d831147dcab06ab331834270823537d53 Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Sat, 25 Nov 2023 23:25:13 -0500 Subject: [PATCH] gh-65: Improves Calibrator implementation. Comparison of two calibration methods (current version works reasonably well, commented out version less so). --- .vscode/launch.json | 30 ++++++++ .../calibrator/src/patch-init-calibrator.cpp | 28 +------ hosts/daisy/include/daisy-patch-sm-host.h | 2 +- libsignaletic/include/libsignaletic.h | 20 +++-- libsignaletic/src/libsignaletic.c | 76 ++++++++++++------- 5 files changed, 93 insertions(+), 63 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index fe74fdc..bb8c093 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -224,6 +224,36 @@ "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", "type": "cortex-debug" }, + { + "name": "Remote Patch.Init() Calibrator", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}/hosts/daisy/examples/patch_init/calibrator", + "debuggerArgs": [ + "-d", + "${workspaceRoot}/hosts/daisy/examples/patch_init/calibrator" + ], + "executable": "${workspaceRoot}/hosts/daisy/examples/patch_init/calibrator/build/patch-init-calibrator.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init" + ], + "preLaunchTask": "Debug Build Patch.Init() Examples", + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToEntryPoint": "main", + "servertype": "openocd", + "showDevDebugOutput": "parsed", + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" + }, { "name": "Remote Versio Filter", "configFiles": [ diff --git a/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp b/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp index cdc180d..8bef9b3 100644 --- a/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp +++ b/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp @@ -26,15 +26,9 @@ struct sig_daisy_Host* host; struct sig_dsp_ConstantValue* ampScale; struct sig_daisy_SwitchIn* button; struct sig_daisy_CVIn* cv1In; -struct sig_daisy_CVIn* cv2In; struct sig_dsp_Calibrator* cv1Calibrator; -struct sig_dsp_Calibrator* cv2Calibrator; - struct sig_dsp_Oscillator* sine1; struct sig_dsp_LinearToFreq* sine1Voct; -struct sig_dsp_Oscillator* sine2; -struct sig_dsp_LinearToFreq* sine2Voct; - struct sig_daisy_AudioOut* audio1Out; struct sig_daisy_AudioOut* audio2Out; @@ -54,16 +48,6 @@ void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { cv1Calibrator->inputs.source = cv1In->outputs.main; cv1Calibrator->inputs.gate = button->outputs.main; - cv2In = sig_daisy_CVIn_new(&allocator, context, host); - sig_List_append(&signals, cv2In, status); - cv2In->parameters.control = sig_daisy_PatchInit_CV_IN_2; - cv2In->parameters.scale = 5.0f; - - cv2Calibrator = sig_dsp_Calibrator_new(&allocator, context); - sig_List_append(&signals, cv2Calibrator, status); - cv2Calibrator->inputs.source = cv2In->outputs.main; - cv2Calibrator->inputs.gate = button->outputs.main; - ampScale = sig_dsp_ConstantValue_new(&allocator, context, 0.5f); sine1Voct = sig_dsp_LinearToFreq_new(&allocator, context); @@ -80,19 +64,11 @@ void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { audio1Out->parameters.channel = sig_daisy_AUDIO_OUT_1; audio1Out->inputs.source = sine1->outputs.main; - sine2Voct = sig_dsp_LinearToFreq_new(&allocator, context); - sig_List_append(&signals, sine2Voct, status); - sine2Voct->inputs.source = cv2Calibrator->outputs.main; - - sine2 = sig_dsp_Sine_new(&allocator, context); - sig_List_append(&signals, sine2, status); - sine2->inputs.freq = sine2Voct->outputs.main; - sine2->inputs.mul = ampScale->outputs.main; - audio2Out = sig_daisy_AudioOut_new(&allocator, context, host); sig_List_append(&signals, audio2Out, status); audio2Out->parameters.channel = sig_daisy_AUDIO_OUT_2; - audio2Out->inputs.source = sine2->outputs.main; + audio2Out->inputs.source = sine1->outputs.main; + } int main(void) { diff --git a/hosts/daisy/include/daisy-patch-sm-host.h b/hosts/daisy/include/daisy-patch-sm-host.h index 6a36c07..4dd36a4 100644 --- a/hosts/daisy/include/daisy-patch-sm-host.h +++ b/hosts/daisy/include/daisy-patch-sm-host.h @@ -141,4 +141,4 @@ void sig_daisy_PatchSMHost_destroy( struct sig_Allocator* allocator, struct sig_daisy_Host* self); -#endif SIGNALETIC_DAISY_PATCH_SM_H +#endif // SIGNALETIC_DAISY_PATCH_SM_H diff --git a/libsignaletic/include/libsignaletic.h b/libsignaletic/include/libsignaletic.h index 7c7beaa..54d4eeb 100644 --- a/libsignaletic/include/libsignaletic.h +++ b/libsignaletic/include/libsignaletic.h @@ -1989,25 +1989,31 @@ struct sig_dsp_Calibrator_Inputs { float_array_ptr gate; }; -struct sig_dsp_Calibrator_RecordingState { - float minValue; - float maxValue; +struct sig_dsp_Calibrator_State { + float target; size_t numSamplesRecorded; + float min; + float max; float sum; + float avg; + float diff; }; -void sig_dsp_Calibrator_RecordingState_init(struct - sig_dsp_Calibrator_RecordingState* state); +void sig_dsp_Calibrator_State_init(struct sig_dsp_Calibrator_State* states, + float* targetValues, size_t numStages); + +// TODO: Don't hardcode this. +#define sig_dsp_Calibrator_NUM_STAGES 6 +#define sig_dsp_Calibrator_TARGET_VALUES {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 4.75f} struct sig_dsp_Calibrator { struct sig_dsp_Signal signal; struct sig_dsp_Calibrator_Inputs inputs; struct sig_dsp_Signal_SingleMonoOutput outputs; - struct sig_dsp_Calibrator_RecordingState state; + struct sig_dsp_Calibrator_State states[sig_dsp_Calibrator_NUM_STAGES]; float previousGate; size_t stage; - float calibrationReadings[3]; }; struct sig_dsp_Calibrator* sig_dsp_Calibrator_new( diff --git a/libsignaletic/src/libsignaletic.c b/libsignaletic/src/libsignaletic.c index c3eec6a..064f8be 100644 --- a/libsignaletic/src/libsignaletic.c +++ b/libsignaletic/src/libsignaletic.c @@ -2808,25 +2808,30 @@ struct sig_dsp_Calibrator* sig_dsp_Calibrator_new( return self; } -inline void sig_dsp_Calibrator_RecordingState_init( - struct sig_dsp_Calibrator_RecordingState* state) { - state->minValue = 1.0f; - state->maxValue = 0.0f; - state->numSamplesRecorded = 0; - state->sum = 0.0f; +inline void sig_dsp_Calibrator_State_init( + struct sig_dsp_Calibrator_State* states, + float* targetValues, size_t numStages) { + for (size_t i = 0; i < numStages; i++) { + struct sig_dsp_Calibrator_State* state = &states[i]; + state->target = state->avg = targetValues[i]; + state->numSamplesRecorded = 0; + state->min = INFINITY; + state->max = -INFINITY; + state->sum = 0.0f; + state->diff = 0.0f; + } } void sig_dsp_Calibrator_init(struct sig_dsp_Calibrator* self, struct sig_SignalContext* context) { sig_dsp_Signal_init(self, context, *sig_dsp_Calibrator_generate); - self->previousGate = 0.0f; self->stage = 0; - self->calibrationReadings[0] = 0.0f; - self->calibrationReadings[1] = 1.0f; - self->calibrationReadings[2] = 3.0f; - sig_dsp_Calibrator_RecordingState_init(&self->state); + float targetValues[sig_dsp_Calibrator_NUM_STAGES] = + sig_dsp_Calibrator_TARGET_VALUES; + sig_dsp_Calibrator_State_init(self->states, + targetValues, sig_dsp_Calibrator_NUM_STAGES); sig_CONNECT_TO_SILENCE(self, source, context); sig_CONNECT_TO_SILENCE(self, gate, context); @@ -2835,45 +2840,58 @@ void sig_dsp_Calibrator_init(struct sig_dsp_Calibrator* self, void sig_dsp_Calibrator_generate(void* signal) { struct sig_dsp_Calibrator* self = (struct sig_dsp_Calibrator*) signal; - for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { float source = FLOAT_ARRAY(self->inputs.source)[i]; float gate = FLOAT_ARRAY(self->inputs.gate)[i]; + size_t stage = self->stage; + struct sig_dsp_Calibrator_State* state = &self->states[stage]; if (gate <= 0.0f && self->previousGate > 0.0f) { // Gate is low; recording has just stopped. // Calculate offset by discarding the highest and lowest values, // and then averaging the rest. - self->state.sum -= self->state.minValue; - self->state.sum -= self->state.maxValue; - self->calibrationReadings[self->stage] = self->state.sum / - (self->state.numSamplesRecorded - 2); - sig_dsp_Calibrator_RecordingState_init(&self->state); - self->stage = (self->stage + 1) % 3; + state->sum -= state->min; + state->sum -= state->max; + state->avg = state->sum / + (state->numSamplesRecorded - 2); + state->diff = -(state->target - state->avg); + self->stage = (stage + 1) % sig_dsp_Calibrator_NUM_STAGES; } else if (gate > 0.0f) { // Gate is high; we're recording. - self->state.sum += source; - self->state.numSamplesRecorded++; + state->sum += source; + state->numSamplesRecorded++; - if (source < self->state.minValue) { - self->state.minValue = source; + if (source < state->min) { + state->min = source; } - if (source > self->state.maxValue) { - self->state.maxValue = source; + if (source > state->max) { + state->max = source; } } // Calibrate using piecewise linear fit from - // readings sampled at 1.0V and 3.0V. - float dcOffset = self->calibrationReadings[0]; - float yk = self->calibrationReadings[1]; - float ykplus1 = self->calibrationReadings[2]; + // readings sampled between 0.0 and 4.75V. + // The ADC tops out slightly before 5 volts, + // so this keeps it the in the range of 9 semitones + // above the fourth octave. + // size_t calibrationIdx = (size_t) floorf(fabsf(source)); + // struct sig_dsp_Calibrator_State start = self->states[calibrationIdx]; + // struct sig_dsp_Calibrator_State end = self->states[calibrationIdx + 1]; + // float yk = start.avg; + // float ykplus1 = end.avg; + // float tk = start.target; + // float tkplus1 = end.target; + // float px = yk + ((ykplus1 - yk) / (tkplus1 - tk)) * (source - tk); + // FLOAT_ARRAY(self->outputs.main)[i] = px; + + float yk = self->states[1].avg; + float ykplus1 = self->states[3].avg; float delta = ykplus1 - yk; float scale = 2.0f / delta; float offset = 1.0f - scale * yk; float px = offset + (scale * source); - FLOAT_ARRAY(self->outputs.main)[i] = px - dcOffset; + FLOAT_ARRAY(self->outputs.main)[i] = px; self->previousGate = gate; }