Implement hidden attenuverters for volume

This commit is contained in:
Jan-Henrik 2020-04-25 18:30:09 +02:00
parent 1f57683c0c
commit af0de40517
8 changed files with 359 additions and 70 deletions

View file

@ -2,10 +2,6 @@
#include "peripherals.h" #include "peripherals.h"
#include <stm32f0xx_hal.h> #include <stm32f0xx_hal.h>
extern "C" {
void DMA_TransferComplete(DMA_HandleTypeDef* dma); // declared in stereo_mix.cc
}
namespace stereo_mix { namespace stereo_mix {
Adc::Adc() Adc::Adc()

View file

@ -35,7 +35,21 @@ class Adc {
void OnDMATransferComplete(); void OnDMATransferComplete();
inline const uint16_t* values() { return &values_[0]; } inline const uint16_t* values() { return &values_[0]; }
inline int32_t value(int32_t channel) const
inline int16_t cv_value(AdcChannel channel)
{
#ifdef NEW_HARDWARE
return this->values_[ADC_GROUP_CV + channel] - 32768;
#else
if (channel >= ADC_CHANNEL_PAN_1) {
return this->values_[ADC_GROUP_CV + channel] - 32768;
} else {
return this->values_[ADC_GROUP_CV + channel] >> 1;
}
#endif
}
inline uint16_t value(int32_t channel) const
{ {
return static_cast<int32_t>(values_[channel]); return static_cast<int32_t>(values_[channel]);
} }

View file

@ -13,11 +13,12 @@ class Dac { // MCP4xx2 dac implementation
: ssGpioPort(ssGpioPort_) : ssGpioPort(ssGpioPort_)
, ssGpioPin(ssGpioPin_) , ssGpioPin(ssGpioPin_)
{ {
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
ssGpioPort = ssGpioPort_; ssGpioPort = ssGpioPort_;
ssGpioPin = ssGpioPin_; ssGpioPin = ssGpioPin_;
// init SS/CS/RST GPIO // init SS/CS/RST GPIO
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef gpio_init; GPIO_InitTypeDef gpio_init;
gpio_init.Mode = GPIO_MODE_OUTPUT_PP; gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
@ -38,10 +39,6 @@ class Dac { // MCP4xx2 dac implementation
HAL_GPIO_Init(GPIOB, &gpio_init); HAL_GPIO_Init(GPIOB, &gpio_init);
// init SPI // init SPI
__HAL_RCC_SPI1_CLK_ENABLE();
// HAL_SPI_DeInit(&spi);
// Initialize SPI TODO: check which config we need
hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.DataSize = SPI_DATASIZE_16BIT; hspi1.Init.DataSize = SPI_DATASIZE_16BIT;

View file

@ -22,18 +22,23 @@ class Leds {
gpioInit.Pull = GPIO_NOPULL; gpioInit.Pull = GPIO_NOPULL;
gpioInit.Speed = GPIO_SPEED_FREQ_MEDIUM; gpioInit.Speed = GPIO_SPEED_FREQ_MEDIUM;
HAL_GPIO_Init(kGpioPorts[i], &gpioInit); HAL_GPIO_Init(kGpioPorts[i], &gpioInit);
intensities[i] = 0;
blinking[i] = false;
} }
} }
void Write() void Write()
{ {
pwm_counter++; pwm_counter++;
pwm_counter &= 0x1ff; // equals to if(pwm_counter > 512) pwm_counter = 0; pwm_counter &= 0x1ff; // equals to if(pwm_counter > 512) pwm_counter = 0;
blink_counter++;
blink_counter &= 0x7FFF;
for (size_t i = 0; i < kNumChannels; i++) { for (size_t i = 0; i < kNumChannels; i++) {
if (intensities[0] && lut_led_gamma[intensities[i]] >= pwm_counter) { bool in_blink_phase = blink_counter < 16383 || !blinking[i];
HAL_GPIO_WritePin(kGpioPorts[i], kGpioPins[i], GPIO_PIN_SET); if (intensities[0] && lut_led_gamma[intensities[i]] >= pwm_counter && in_blink_phase) {
kGpioPorts[i]->BSRR |= kGpioPins[i];
} else { } else {
HAL_GPIO_WritePin(kGpioPorts[i], kGpioPins[i], GPIO_PIN_RESET); kGpioPorts[i]->BRR |= kGpioPins[i];
} }
} }
} }
@ -43,8 +48,16 @@ class Leds {
return; return;
intensities[channel] = intensity; intensities[channel] = intensity;
} }
void set_blinking(uint8_t channel, bool blink)
{
if (channel >= kNumChannels)
return;
blinking[channel] = blink;
}
private: private:
uint16_t pwm_counter; uint16_t pwm_counter;
uint16_t blink_counter;
uint8_t intensities[kNumChannels]; uint8_t intensities[kNumChannels];
bool blinking[kNumChannels];
}; };

170
stereo_mix/pot_controller.h Normal file
View file

@ -0,0 +1,170 @@
// Copyright 2015 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
// modified for ints by Jan-Henrik Bruhn (hi@jhbruhn.de)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// See http://creativecommons.org/licenses/MIT/ for more information.
//
// -----------------------------------------------------------------------------
//
// Handles the "hold button to tweak a hidden parameter" behaviour of pots.
// A pot can be in 4 states:
// - POT_STATE_TRACKING: the main parameter tracks the position of the pot.
// - POT_STATE_LOCKING: the main parameter no longer tracks the position of
// the pot. We wait for the pot to move further from its original position
// to start modifying the hidden parameter.
// - POT_STATE_HIDDEN_PARAMETER: the hidden parameter tracks the position of
// the pot.
// - POT_STATE_CATCHING_UP: the pot adjusts the main parameter in a relative
// way, until the position of the pot and the value of the parameter match
// again.
#pragma once
#include "stmlib/stmlib.h"
#include "stmlib/dsp/dsp.h"
enum PotState {
POT_STATE_TRACKING,
POT_STATE_LOCKING,
POT_STATE_HIDDEN_PARAMETER,
POT_STATE_CATCHING_UP
};
class PotController {
public:
PotController() { }
~PotController() { }
inline void Init(
uint16_t* main_parameter,
uint16_t* hidden_parameter // this pot controller always works on values between 0 and 65535
) {
state_ = POT_STATE_TRACKING;
was_catching_up_ = false;
main_parameter_ = main_parameter;
hidden_parameter_ = hidden_parameter;
value_ = 0;
stored_value_ = 0;
previous_value_ = 0;
}
inline void Lock() {
if (state_ == POT_STATE_LOCKING || state_ == POT_STATE_HIDDEN_PARAMETER) {
return;
}
if (hidden_parameter_) {
was_catching_up_ = state_ == POT_STATE_CATCHING_UP;
state_ = POT_STATE_LOCKING;
}
}
inline bool editing_hidden_parameter() const {
return state_ == POT_STATE_HIDDEN_PARAMETER;
}
inline void Unlock() {
if (state_ == POT_STATE_HIDDEN_PARAMETER || was_catching_up_) {
state_ = POT_STATE_CATCHING_UP;
} else {
state_ = POT_STATE_TRACKING;
}
}
inline void Realign() {
state_ = POT_STATE_TRACKING;
}
inline void ProcessControlRate(uint16_t adc_value) {
value_ += (adc_value - value_) >> 6;
CONSTRAIN(value_, 0, 65535);
// approximately this:
// ONE_POLE(value_, adc_value, 0.01f);
if (state_ == POT_STATE_TRACKING) {
*main_parameter_ = value_;
}
}
inline void ProcessUIRate() {
switch (state_) {
case POT_STATE_TRACKING:
previous_value_ = value_;
break;
case POT_STATE_LOCKING:
if (abs(value_ - previous_value_) > 1966) {
stored_value_ = previous_value_;
CONSTRAIN(value_, 0, 65535);
*hidden_parameter_ = value_;
state_ = POT_STATE_HIDDEN_PARAMETER;
previous_value_ = value_;
}
break;
case POT_STATE_HIDDEN_PARAMETER:
CONSTRAIN(value_, 0, 65535);
*hidden_parameter_ = value_;
previous_value_ = value_;
break;
case POT_STATE_CATCHING_UP:
{
if (abs(value_ - previous_value_) > 327) {
int32_t delta = value_ - previous_value_;
int32_t skew_ratio = delta > 0
? (65600 - stored_value_) / (65535 - previous_value_)
: (66 + stored_value_) / (66 + previous_value_);
CONSTRAIN(skew_ratio, 6553, 655350);
stored_value_ += (skew_ratio * delta) >> 11;
CONSTRAIN(stored_value_, 0, 65535);
if (abs(stored_value_ - value_) < 327) {
state_ = POT_STATE_TRACKING;
}
previous_value_ = value_;
*main_parameter_ = stored_value_;
}
}
break;
}
}
private:
PotState state_;
bool was_catching_up_;
uint16_t* main_parameter_;
uint16_t* hidden_parameter_;
int32_t value_;
int32_t stored_value_;
int32_t previous_value_;
DISALLOW_COPY_AND_ASSIGN(PotController);
};

View file

@ -5,6 +5,7 @@
#include "drivers/switches.h" #include "drivers/switches.h"
#include "resources.h" #include "resources.h"
#include "stmlib/ui/event_queue.h" #include "stmlib/ui/event_queue.h"
#include "ui.h"
#include <stm32f0xx_hal.h> #include <stm32f0xx_hal.h>
using namespace stereo_mix; using namespace stereo_mix;
@ -14,14 +15,11 @@ Dac dacs[8] = {
{ GPIOB, GPIO_PIN_8 }, { GPIOB, GPIO_PIN_9 }, { GPIOB, GPIO_PIN_10 }, { GPIOB, GPIO_PIN_11 }, { GPIOB, GPIO_PIN_8 }, { GPIOB, GPIO_PIN_9 }, { GPIOB, GPIO_PIN_10 }, { GPIOB, GPIO_PIN_11 },
{ GPIOA, GPIO_PIN_8 }, { GPIOA, GPIO_PIN_9 }, { GPIOA, GPIO_PIN_10 }, { GPIOA, GPIO_PIN_11 } { GPIOA, GPIO_PIN_8 }, { GPIOA, GPIO_PIN_9 }, { GPIOA, GPIO_PIN_10 }, { GPIOA, GPIO_PIN_11 }
}; };
Adc adc; Adc adc;
Leds leds; Leds leds;
Switches switches; Switches switches;
UI ui(&adc, &switches, &leds);
const int32_t kLongPressDuration = 2000;
EventQueue<> queue;
uint32_t press_time[SWITCH_COUNT];
bool ignore_release[SWITCH_COUNT];
bool mute[4]; bool mute[4];
@ -65,28 +63,7 @@ void SysTick_Handler()
system_clock.Tick(); system_clock.Tick();
switches.Debounce(); switches.Debounce();
ui.Poll();
for (size_t i = 0; i < SWITCH_COUNT; i++) {
if (switches.just_pressed(Switch(i))) {
queue.AddEvent(CONTROL_SWITCH, i, 0);
press_time[i] = HAL_GetTick();
ignore_release[i] = false;
}
if (switches.pressed(Switch(i)) && !ignore_release[i]) {
int32_t pressed_time = HAL_GetTick() - press_time[i];
if (pressed_time > kLongPressDuration) {
queue.AddEvent(CONTROL_SWITCH, i, pressed_time);
ignore_release[i] = true;
}
}
if (switches.released(Switch(i)) && !ignore_release[i]) {
queue.AddEvent(
CONTROL_SWITCH,
i,
system_clock.milliseconds() - press_time[i] + 1);
ignore_release[i] = true;
}
}
} }
void SystemClock_Config(void) void SystemClock_Config(void)
@ -124,7 +101,8 @@ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{ {
adc.OnDMATransferComplete(); adc.OnDMATransferComplete();
} }
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) // called with 1kHz (OPTIMIZE!) the display should get its own spi bus
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim)
{ {
if (htim != &htim3) { if (htim != &htim3) {
return; return;
@ -135,18 +113,25 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim) // called with 1kHz
void Init(void) void Init(void)
{ {
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_SPI1_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn); HAL_NVIC_EnableIRQ(TIM3_IRQn);
__HAL_RCC_TIM3_CLK_ENABLE(); htim3.Init.Prescaler = 10;
htim3.Init.Prescaler = 14;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 128; htim3.Init.Period = 128;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.RepetitionCounter = 0; htim3.Init.RepetitionCounter = 0;
HAL_TIM_Base_Init(&htim3); HAL_TIM_Base_Init(&htim3);
HAL_TIM_Base_Start_IT(&htim3); HAL_TIM_Base_Start_IT(&htim3);
system_clock.Init(); system_clock.Init();
queue.Init();
} }
void WriteOutputs(void) void WriteOutputs(void)
@ -157,8 +142,8 @@ void WriteOutputs(void)
uint16_t pan_pot = adc.value(ADC_GROUP_POT + ADC_CHANNEL_PAN_1 + i) >> (16 - 12); // adc is only 12 bit anyways uint16_t pan_pot = adc.value(ADC_GROUP_POT + ADC_CHANNEL_PAN_1 + i) >> (16 - 12); // adc is only 12 bit anyways
uint16_t vol_pot = adc.value(ADC_GROUP_POT + ADC_CHANNEL_VOL_1 + i) >> (16 - 12); uint16_t vol_pot = adc.value(ADC_GROUP_POT + ADC_CHANNEL_VOL_1 + i) >> (16 - 12);
int16_t pan_cv = (adc.value(ADC_GROUP_CV + ADC_CHANNEL_PAN_1 + i) - 32768) >> (16 - 12); int16_t pan_cv = adc.cv_value(AdcChannel(ADC_CHANNEL_PAN_1 + i)) >> (16 - 12);
uint16_t vol_cv = adc.value(ADC_GROUP_CV + ADC_CHANNEL_VOL_1 + i) >> (16 - 12); int16_t vol_cv = adc.cv_value(AdcChannel(ADC_CHANNEL_VOL_1 + i)) >> (16 - 12);
int32_t pan = pan_pot + pan_cv; int32_t pan = pan_pot + pan_cv;
int32_t vol = vol_pot + vol_cv; int32_t vol = vol_pot + vol_cv;
@ -167,7 +152,7 @@ void WriteOutputs(void)
CONSTRAIN(pan, 0, (1 << 12) - 1); CONSTRAIN(pan, 0, (1 << 12) - 1);
CONSTRAIN(vol, 0, (1 << 12) - 1); CONSTRAIN(vol, 0, (1 << 12) - 1);
leds.set_intensity(i, vol >> 4); // leds.set_intensity(i, vol >> 4);
value_l = (lut_left_sin_pan[pan] * lut_linear_to_exp[vol]) >> 16; value_l = (lut_left_sin_pan[pan] * lut_linear_to_exp[vol]) >> 16;
value_r = (lut_right_cos_pan[pan] * lut_linear_to_exp[vol]) >> 16; value_r = (lut_right_cos_pan[pan] * lut_linear_to_exp[vol]) >> 16;
@ -178,36 +163,16 @@ void WriteOutputs(void)
} }
} }
void HandleEvent(void)
{
while (queue.available()) {
Event ev = queue.PullEvent();
switch (ev.control_type) {
case CONTROL_SWITCH:
if (ev.data != 0) { // switch released
mute[ev.control_id] = !mute[ev.control_id];
}
break;
default:
break;
}
}
}
int main(void) int main(void)
{ {
HAL_Init(); HAL_Init();
SystemClock_Config(); SystemClock_Config();
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
Init(); Init();
while (true) { while (true) {
HandleEvent(); ui.DoEvents();
WriteOutputs(); WriteOutputs();
} }

84
stereo_mix/ui.cc Normal file
View file

@ -0,0 +1,84 @@
#include "ui.h"
#include "pot_controller.h"
#include <stm32f0xx_hal.h>
const int kLongPressDuration = 2000;
void UI::Poll()
{
for (size_t i = 0; i < SWITCH_COUNT; i++) {
if (switches->just_pressed(Switch(i))) {
queue.AddEvent(CONTROL_SWITCH, i, 0);
press_time[i] = HAL_GetTick();
ignore_release[i] = false;
potControllers[0].Lock();
}
if (switches->pressed(Switch(i)) && !ignore_release[i]) {
bool suppress_release_hidden_parameters = false;
for (size_t j = 0; j < kNumChannels * 2; j++) {
suppress_release_hidden_parameters |= potControllers[j].editing_hidden_parameter();
}
int32_t pressed_time = HAL_GetTick() - press_time[i];
if (!suppress_release_hidden_parameters && pressed_time > kLongPressDuration) {
queue.AddEvent(CONTROL_SWITCH, i, pressed_time);
ignore_release[i] = true;
}
}
if (switches->released(Switch(i))) {
bool suppress_release_hidden_parameters = false;
for (size_t j = 0; j < kNumChannels * 2; j++) {
suppress_release_hidden_parameters |= potControllers[j].editing_hidden_parameter();
}
if (!suppress_release_hidden_parameters && !ignore_release[i]) {
queue.AddEvent(
CONTROL_SWITCH,
i,
system_clock.milliseconds() - press_time[i] + 1);
ignore_release[i] = true;
}
potControllers[0].Unlock();
}
}
for (size_t i = 0; i < kNumChannels * 2; i++) {
potControllers[i].ProcessControlRate(adc->value(ADC_GROUP_POT + i));
}
}
void UI::OnSwitchPressed(const Event& e)
{
}
void UI::OnSwitchReleased(const Event& e)
{
mute[e.control_id] = !mute[e.control_id];
}
void UI::DoEvents()
{
for (size_t i = 0; i < kNumChannels * 2; i++) {
potControllers[i].ProcessUIRate();
if (potControllers[i].editing_hidden_parameter()) {
leds->set_intensity(i, abs(volume_att_pots[i] - 32767) >> 7);
leds->set_blinking(i, volume_att_pots[i] - 32767 < 0);
} else {
leds->set_intensity(i, mute[i] ? 0 : volume_pots[i] >> 8);
leds->set_blinking(i, false);
}
}
while (queue.available()) {
Event ev = queue.PullEvent();
switch (ev.control_type) {
case CONTROL_SWITCH:
if (ev.data != 0) {
OnSwitchReleased(ev);
} else {
OnSwitchPressed(ev);
}
break;
default:
break;
}
}
}

50
stereo_mix/ui.h Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include "drivers/adc.h"
#include "drivers/leds.h"
#include "drivers/switches.h"
#include "pot_controller.h"
#include "stmlib/ui/event_queue.h"
using namespace stmlib;
extern const uint8_t kNumChannels; // TODO
class UI {
public:
UI(Adc* adc_, Switches* switches_, Leds* leds_)
: adc(adc_)
, switches(switches_)
, leds(leds_)
{
queue.Init();
for (size_t i = 0; i < kNumChannels; i++) {
potControllers[i].Init(&volume_pots[i], &volume_att_pots[i]);
potControllers[i + kNumChannels].Init(&pan_pots[i], &pan_att_pots[i]);
volume_att_pots[i] = pan_att_pots[i] = 65535;
}
};
void Poll();
void DoEvents();
private:
void OnSwitchPressed(const Event&);
void OnSwitchReleased(const Event&);
EventQueue<> queue;
uint32_t press_time[SWITCH_COUNT];
bool ignore_release[SWITCH_COUNT];
Adc* adc;
Switches* switches;
Leds* leds;
uint16_t volume_pots[kNumChannels];
uint16_t pan_pots[kNumChannels];
uint16_t volume_att_pots[kNumChannels];
uint16_t pan_att_pots[kNumChannels];
bool mute[kNumChannels];
PotController potControllers[kNumChannels * 2]; // todo: count
};