Skip to content

Commit

Permalink
firmware: Add advanced LFO support.
Browse files Browse the repository at this point in the history
- Added WntrMixedPeriodicWaveform for combining multiple periodic waveforms together.
- Added sawtooth & square waveshapes to wntr_waveforms.
- Changed the LFO from a single, fixed sawtooth to a combination of two waveforms with control over waveshape, amplitude, and frequency.
- Added new settings fields for the LFO, and logic to upgrade older settings.
  • Loading branch information
theacodes committed Aug 31, 2021
1 parent 1043815 commit 3adf2cf
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 61 deletions.
21 changes: 20 additions & 1 deletion firmware/data/gem_settings.structy
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class GemSettings:
chorus_max_intensity: fix16 = 0.05

"""The default LFO frequency in hertz."""
lfo_frequency: fix16 = 0.2
lfo_1_frequency: fix16 = 0.2

"""Error correction for the ADC readings for the CV input."""
cv_offset_error: fix16 = 0.0
Expand Down Expand Up @@ -50,5 +50,24 @@ class GemSettings:
make it harder to tune and aren't recommended."""
pitch_knob_nonlinearity: fix16 = 0.6

# Added in V2

"""The base CV offset applied to the pitch inputs."""
base_cv_offset: fix16 = 1.0

# Added in V3

"""The ratio of the second LFO's frequency to the first."""
lfo_2_frequency_ratio: fix16 = 2

"""LFO 1's waveshape."""
lfo_1_waveshape: uint8 = 0

"""LFO 2's waveshape."""
lfo_2_waveshape: uint8 = 0

"""LFO 1's factor."""
lfo_1_factor: fix16 = 1

"""LFO 2's factor."""
lfo_2_factor: fix16 = 0
2 changes: 1 addition & 1 deletion firmware/src/gem_led_animation.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ static void animation_step_tweak_(uint32_t delta) {
}

fix16_t lfoadj = fix16_div(fix16_add(gem_led_tweak_data.lfo_value, F16(1.0)), F16(2.0));
uint8_t lfo_value = fix16_to_int(fix16_mul(F16(255.0), lfoadj));
uint8_t lfo_value = fix16_to_int(fix16_mul(F16(255.0), lfoadj)) & 0xFF;
gem_dotstar_set32(4, wntr_colorspace_hsv_to_rgb(UINT16_MAX / 12 * 2, 255, lfo_value));
gem_dotstar_set32(5, wntr_colorspace_hsv_to_rgb(UINT16_MAX / 12 * 2, 255, lfo_value));
gem_dotstar_set32(6, wntr_colorspace_hsv_to_rgb(UINT16_MAX / 12 * 2, 255, lfo_value));
Expand Down
33 changes: 28 additions & 5 deletions firmware/src/gem_settings_load_save.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@

#define SETTINGS_MARKER_V1 0x65
#define SETTINGS_MARKER_V2 0x66
#define SETTINGS_MARKER_V3 0x67

#define LIMIT_F16_FIELD(field, min, max) \
if (settings->field < F16(min) || settings->field > F16(max)) { \
settings->field = defaults.field; \
}

#define LIMIT_INT_FIELD(field, min, max) \
if (settings->field < min || settings->field > max) { \
settings->field = defaults.field; \
}

#define DEFAULT_FIELD(field) settings->field = defaults.field;

extern uint8_t _nvm_settings_base_address;

bool GemSettings_check(uint8_t marker, struct GemSettings* settings) {
Expand All @@ -41,18 +49,33 @@ bool GemSettings_check(uint8_t marker, struct GemSettings* settings) {
LIMIT_F16_FIELD(pollux_knob_max, 0.0, 10.0);
LIMIT_F16_FIELD(pollux_knob_min, -10.0, 0.0);
LIMIT_F16_FIELD(chorus_max_intensity, 0.0, 1.0);
LIMIT_F16_FIELD(lfo_frequency, 0.0, 50.0);
LIMIT_F16_FIELD(lfo_1_frequency, 0.0, 50.0);
LIMIT_F16_FIELD(smooth_initial_gain, 0.0, 1.0);
LIMIT_F16_FIELD(smooth_sensitivity, 0.0, 100.0);
LIMIT_F16_FIELD(pitch_knob_nonlinearity, 0.3, 1.0);

/* V2 added base_cv_offset field. */
if (marker == SETTINGS_MARKER_V1) {
printf("Upgrading setings from v1 to v2.\n");
settings->base_cv_offset = defaults.base_cv_offset;
printf("Upgrading settings from v1 to v2.\n");
DEFAULT_FIELD(base_cv_offset);
}
LIMIT_F16_FIELD(base_cv_offset, 0.0, 5.0);

/* V3 added several lfo options. */
if (marker == SETTINGS_MARKER_V2) {
printf("Upgrading settings from v2 to v3.\n");
DEFAULT_FIELD(lfo_2_frequency_ratio)
DEFAULT_FIELD(lfo_1_waveshape)
DEFAULT_FIELD(lfo_2_waveshape)
DEFAULT_FIELD(lfo_1_factor)
DEFAULT_FIELD(lfo_2_factor)
}
LIMIT_F16_FIELD(lfo_2_frequency_ratio, 0.0, 10.0);
LIMIT_INT_FIELD(lfo_1_waveshape, 0, 4);
LIMIT_INT_FIELD(lfo_2_waveshape, 0, 4);
LIMIT_F16_FIELD(lfo_1_factor, 0.0, 1.0);
LIMIT_F16_FIELD(lfo_2_factor, 0.0, 1.0);

return true;

fail:
Expand All @@ -71,7 +94,7 @@ bool GemSettings_load(struct GemSettings* settings) {

uint8_t marker = data[0];

if (marker != SETTINGS_MARKER_V1 && marker != SETTINGS_MARKER_V2) {
if (marker != SETTINGS_MARKER_V1 && marker != SETTINGS_MARKER_V2 && marker != SETTINGS_MARKER_V3) {
printf("Invalid settings marker.\n");
goto fail;
}
Expand All @@ -92,7 +115,7 @@ bool GemSettings_load(struct GemSettings* settings) {

void GemSettings_save(struct GemSettings* settings) {
uint8_t data[GEMSETTINGS_PACKED_SIZE + 1];
data[0] = SETTINGS_MARKER_V2;
data[0] = SETTINGS_MARKER_V3;

GemSettings_check(data[0], settings);

Expand Down
48 changes: 40 additions & 8 deletions firmware/src/generated/gem_settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include "gem_settings.h"

#define _PACK_STRING "HhHiiiiiiiiiiH??ii"
#define _PACK_STRING "HhHiiiiiiiiiiH??iiiBBii"

void GemSettings_init(struct GemSettings* inst) {
inst->adc_gain_corr = 2048;
Expand All @@ -16,7 +16,7 @@ void GemSettings_init(struct GemSettings* inst) {
inst->pollux_knob_min = F16(-1.2);
inst->pollux_knob_max = F16(1.2);
inst->chorus_max_intensity = F16(0.05);
inst->lfo_frequency = F16(0.2);
inst->lfo_1_frequency = F16(0.2);
inst->cv_offset_error = F16(0.0);
inst->cv_gain_error = F16(1.0);
inst->smooth_initial_gain = F16(0.1);
Expand All @@ -26,6 +26,11 @@ void GemSettings_init(struct GemSettings* inst) {
inst->pollux_lfo_pwm = false;
inst->pitch_knob_nonlinearity = F16(0.6);
inst->base_cv_offset = F16(1.0);
inst->lfo_2_frequency_ratio = F16(2);
inst->lfo_1_waveshape = 0;
inst->lfo_2_waveshape = 0;
inst->lfo_1_factor = F16(1);
inst->lfo_2_factor = F16(0);
}

struct StructyResult GemSettings_pack(const struct GemSettings* inst, uint8_t* buf) {
Expand All @@ -41,7 +46,7 @@ struct StructyResult GemSettings_pack(const struct GemSettings* inst, uint8_t* b
inst->pollux_knob_min,
inst->pollux_knob_max,
inst->chorus_max_intensity,
inst->lfo_frequency,
inst->lfo_1_frequency,
inst->cv_offset_error,
inst->cv_gain_error,
inst->smooth_initial_gain,
Expand All @@ -50,7 +55,12 @@ struct StructyResult GemSettings_pack(const struct GemSettings* inst, uint8_t* b
inst->castor_lfo_pwm,
inst->pollux_lfo_pwm,
inst->pitch_knob_nonlinearity,
inst->base_cv_offset);
inst->base_cv_offset,
inst->lfo_2_frequency_ratio,
inst->lfo_1_waveshape,
inst->lfo_2_waveshape,
inst->lfo_1_factor,
inst->lfo_2_factor);
}

struct StructyResult GemSettings_unpack(struct GemSettings* inst, const uint8_t* buf) {
Expand All @@ -66,7 +76,7 @@ struct StructyResult GemSettings_unpack(struct GemSettings* inst, const uint8_t*
&inst->pollux_knob_min,
&inst->pollux_knob_max,
&inst->chorus_max_intensity,
&inst->lfo_frequency,
&inst->lfo_1_frequency,
&inst->cv_offset_error,
&inst->cv_gain_error,
&inst->smooth_initial_gain,
Expand All @@ -75,7 +85,12 @@ struct StructyResult GemSettings_unpack(struct GemSettings* inst, const uint8_t*
&inst->castor_lfo_pwm,
&inst->pollux_lfo_pwm,
&inst->pitch_knob_nonlinearity,
&inst->base_cv_offset);
&inst->base_cv_offset,
&inst->lfo_2_frequency_ratio,
&inst->lfo_1_waveshape,
&inst->lfo_2_waveshape,
&inst->lfo_1_factor,
&inst->lfo_2_factor);
}

void GemSettings_print(const struct GemSettings* inst) {
Expand Down Expand Up @@ -111,8 +126,8 @@ void GemSettings_print(const struct GemSettings* inst) {
}
{
char fix16buf[13];
fix16_to_str(inst->lfo_frequency, fix16buf, 2);
STRUCTY_PRINTF("- lfo_frequency: %s\n", fix16buf);
fix16_to_str(inst->lfo_1_frequency, fix16buf, 2);
STRUCTY_PRINTF("- lfo_1_frequency: %s\n", fix16buf);
}
{
char fix16buf[13];
Expand Down Expand Up @@ -147,4 +162,21 @@ void GemSettings_print(const struct GemSettings* inst) {
fix16_to_str(inst->base_cv_offset, fix16buf, 2);
STRUCTY_PRINTF("- base_cv_offset: %s\n", fix16buf);
}
{
char fix16buf[13];
fix16_to_str(inst->lfo_2_frequency_ratio, fix16buf, 2);
STRUCTY_PRINTF("- lfo_2_frequency_ratio: %s\n", fix16buf);
}
STRUCTY_PRINTF("- lfo_1_waveshape: %u\n", inst->lfo_1_waveshape);
STRUCTY_PRINTF("- lfo_2_waveshape: %u\n", inst->lfo_2_waveshape);
{
char fix16buf[13];
fix16_to_str(inst->lfo_1_factor, fix16buf, 2);
STRUCTY_PRINTF("- lfo_1_factor: %s\n", fix16buf);
}
{
char fix16buf[13];
fix16_to_str(inst->lfo_2_factor, fix16buf, 2);
STRUCTY_PRINTF("- lfo_2_factor: %s\n", fix16buf);
}
}
14 changes: 12 additions & 2 deletions firmware/src/generated/gem_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

#include "fix16.h"

#define GEMSETTINGS_PACKED_SIZE 58
#define GEMSETTINGS_PACKED_SIZE 72

struct GemSettings {
/* The ADC's internal gain correction register. */
Expand All @@ -26,7 +26,7 @@ struct GemSettings {
/* Maximum amount that the chorus can impact Pollux's frequency. */
fix16_t chorus_max_intensity;
/* The default LFO frequency in hertz. */
fix16_t lfo_frequency;
fix16_t lfo_1_frequency;
/* Error correction for the ADC readings for the CV input. */
fix16_t cv_offset_error;
fix16_t cv_gain_error;
Expand All @@ -52,6 +52,16 @@ struct GemSettings {
fix16_t pitch_knob_nonlinearity;
/* The base CV offset applied to the pitch inputs. */
fix16_t base_cv_offset;
/* The ratio of the second LFO's frequency to the first. */
fix16_t lfo_2_frequency_ratio;
/* LFO 1's waveshape. */
uint8_t lfo_1_waveshape;
/* LFO 2's waveshape. */
uint8_t lfo_2_waveshape;
/* LFO 1's factor. */
fix16_t lfo_1_factor;
/* LFO 2's factor. */
fix16_t lfo_2_factor;
};

void GemSettings_init(struct GemSettings* inst);
Expand Down
49 changes: 40 additions & 9 deletions firmware/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ static struct WntrButton hard_sync_button_ = {.port = GEM_HARD_SYNC_BUTTON_PORT,

/* State */
static struct GemSettings settings_;
static struct WntrPeriodicWaveform lfo_;
static struct {
wntr_periodic_waveform_function functions[2];
fix16_t frequencies[2];
fix16_t factors[2];
fix16_t phases[2];
} lfo_state_;
static struct WntrMixedPeriodicWaveform lfo_;
static struct GemOscillator castor_;
static struct GemOscillator pollux_;
static bool hard_sync_ = false;
Expand All @@ -29,6 +35,22 @@ static uint32_t animation_time_ = 0;
static uint32_t sample_time_ = 0;
static uint32_t idle_cycles_ = 0;

/* Helpers for init_ */
wntr_periodic_waveform_function _lfo_waveshape_setting_to_func(uint8_t n) {
switch (n) {
case 0:
return wntr_triangle;
case 1:
return wntr_sine;
case 2:
return wntr_sawtooth;
case 3:
return wntr_square;
default:
return wntr_triangle;
}
}

/*
Initializes the core processor, clocks, peripherals, drivers, settings,
and global state.
Expand Down Expand Up @@ -150,10 +172,19 @@ static void init_() {
Gemini has an internal low-frequency oscillator that can be used to
modulate the pitch and pulse width of the oscillators.
For the time being it's hardcoded to use a triangle waveform, but
this is definitely something that could be pulled out into a setting.
It's current limited to just combining two waveforms, but it could
support more.
*/
WntrPeriodicWaveform_init(&lfo_, wntr_triangle, settings_.lfo_frequency);
lfo_state_.functions[0] = _lfo_waveshape_setting_to_func(settings_.lfo_1_waveshape);
lfo_state_.functions[1] = _lfo_waveshape_setting_to_func(settings_.lfo_2_waveshape);
lfo_state_.frequencies[0] = settings_.lfo_1_frequency;
lfo_state_.frequencies[1] = fix16_mul(settings_.lfo_1_frequency, settings_.lfo_2_frequency_ratio);
lfo_state_.factors[0] = settings_.lfo_1_factor;
lfo_state_.factors[1] = settings_.lfo_2_factor;
lfo_state_.phases[0] = F16(0);
lfo_state_.phases[1] = F16(0);
WntrMixedPeriodicWaveform_init(
&lfo_, 2, lfo_state_.functions, lfo_state_.frequencies, lfo_state_.factors, lfo_state_.phases, wntr_ticks());

/*
Gemini has two oscillators - Castor & Pollux. For the most part they're
Expand Down Expand Up @@ -237,15 +268,16 @@ static void oscillator_task_() {
/*
Update the internal LFO used for modulating pitch and pulse width.
*/
WntrPeriodicWaveform_step(&lfo_);
fix16_t lfo_value = WntrMixedPeriodicWaveform_step(&lfo_, loop_start_time);
gem_led_tweak_data.lfo_value = lfo_value;
fix16_t pitch_lfo_intensity = UINT12_NORMALIZE(UINT12_INVERT(adc_results_[GEM_IN_CHORUS_POT]));
fix16_t pitch_lfo_value = fix16_mul(settings_.chorus_max_intensity, fix16_mul(pitch_lfo_intensity, lfo_.value));
fix16_t pitch_lfo_value = fix16_mul(settings_.chorus_max_intensity, fix16_mul(pitch_lfo_intensity, lfo_value));

/*
Update both oscillator's internal state based on the ADC inputs.
*/
struct GemOscillatorInputs inputs = {
.adc = adc_results_, .lfo_pulse_width = lfo_.value, .lfo_pitch = pitch_lfo_value};
.adc = adc_results_, .lfo_pulse_width = lfo_value, .lfo_pitch = pitch_lfo_value};

GemOscillator_update(&castor_, inputs);

Expand Down Expand Up @@ -360,7 +392,7 @@ static void tweak_task_() {
/* Chorus intensity knob controls the LFO frequency in tweak mode. */
IF_WAGGLED(chorus_pot_code, GEM_IN_CHORUS_POT)
fix16_t frequency_value = UINT12_NORMALIZE(chorus_pot_code);
lfo_.frequency = fix16_mul(frequency_value, GEM_TWEAK_MAX_LFO_FREQUENCY);
lfo_state_.frequencies[0] = fix16_mul(frequency_value, GEM_TWEAK_MAX_LFO_FREQUENCY);
IF_WAGGLED_END

/* PWM Knobs control whether or not the LFO gets routed to them. */
Expand All @@ -383,7 +415,6 @@ static void tweak_task_() {
/*
Tweak mode uses the LEDs to tell the user what the settings are.
*/
gem_led_tweak_data.lfo_value = lfo_.value;
gem_led_tweak_data.castor_pwm = castor_.lfo_pwm;
gem_led_tweak_data.pollux_pwm = pollux_.lfo_pwm;

Expand Down

0 comments on commit 3adf2cf

Please sign in to comment.