Skip to content

Commit

Permalink
firmware: Add base CV offset setting
Browse files Browse the repository at this point in the history
firmware: Apply base CV offset setting to both oscillators
firmware: Add method to upgrade older settings data
firmware: Remove chunking of sysex commands
user guide: Add form controls for base CV offset setting
user guide: Check for firmware versions too old to use with the editor
user guide: Remove chunking of settings sysex commands
factory: Remove chunking of settings sysex commands
third party: Fix big in libwinter's sysex handling where not calling `tud_task` would lead to an incorrect sysex timeout
  • Loading branch information
theacodes committed Jun 10, 2021
1 parent 0cd678d commit 0b53021
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 159 deletions.
7 changes: 5 additions & 2 deletions factory/libgemini/gem_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

@dataclass
class GemSettings(structy.Struct):
_PACK_STRING : ClassVar[str] = "HhHiiiiiiiiiiH??i"
_PACK_STRING : ClassVar[str] = "HhHiiiiiiiiiiH??ii"

PACKED_SIZE : ClassVar[int] = 54
PACKED_SIZE : ClassVar[int] = 58
"""The total size of the struct once packed."""

adc_gain_corr: int = 2048
Expand Down Expand Up @@ -68,3 +68,6 @@ class GemSettings(structy.Struct):
response. Higher values make it easier and easier to tune the
oscillator but sacrifice the range, values lower than 0.33 will
make it harder to tune and aren't recommended."""

base_cv_offset: structy.Fix16 = 1.0
"""The base CV offset applied to the pitch inputs."""
41 changes: 11 additions & 30 deletions factory/libgemini/gemini.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class SysExCommands(enum.IntEnum):
SET_DAC = 0x05
SET_FREQ = 0x06
RESET_SETTINGS = 0x07
READ_SETTINGS = 0x08
WRITE_SETTINGS = 0x09
READ_SETTINGS = 0x18
WRITE_SETTINGS = 0x19
WRITE_LUT_ENTRY = 0x0A
WRITE_LUT = 0x0B
ERASE_LUT = 0x0C
Expand Down Expand Up @@ -57,8 +57,9 @@ def get_firmware_version(self):
return self.version

def get_serial_number(self):
resp = self.sysex(SysExCommands.GET_SERIAL_NUMBER, response=True)
self.serial_number = teeth.teeth_decode(resp[3:-1]).hex()
resp = self.sysex(SysExCommands.GET_SERIAL_NUMBER, response=True, decode=True)
self.serial_number = resp.hex()
return self.serial_number

def enter_calibration_mode(self):
self.get_firmware_version()
Expand Down Expand Up @@ -102,38 +103,18 @@ def enable_adc_error_correction(self):
def reset_settings(self):
self.sysex(SysExCommands.RESET_SETTINGS)

CHUNK_SIZE = 10
SETTINGS_ENCODED_LEN = teeth.teeth_encoded_length(
gem_settings.GemSettings.PACKED_SIZE
)
SETTINGS_CHUNKS = SETTINGS_ENCODED_LEN // CHUNK_SIZE

def read_settings(self):
settings_encoded = bytearray(self.SETTINGS_ENCODED_LEN)

for n in range(self.SETTINGS_CHUNKS):
data = self.sysex(SysExCommands.READ_SETTINGS, data=[n], response=True)
assert len(data) == self.CHUNK_SIZE + 4
settings_encoded[
self.CHUNK_SIZE * n : self.CHUNK_SIZE * n + self.CHUNK_SIZE
] = data[3:-1]

settings_buf = teeth.teeth_decode(settings_encoded)
settings_buf = self.sysex(
SysExCommands.READ_SETTINGS, response=True, decode=True
)
settings = gem_settings.GemSettings.unpack(settings_buf)
return settings

def save_settings(self, settings):
settings_buf = settings.pack()

settings_encoded = teeth.teeth_encode(settings_buf)

for n in range(self.SETTINGS_CHUNKS):
chunk = list(
settings_encoded[
self.CHUNK_SIZE * n : self.CHUNK_SIZE * n + self.CHUNK_SIZE
]
)
self.sysex(SysExCommands.WRITE_SETTINGS, data=[n] + chunk, response=True)
self.sysex(
SysExCommands.WRITE_SETTINGS, settings_buf, encode=True, response=True
)

def write_lut_entry(self, entry, period, castor, pollux):
data = struct.pack(">BIHH", entry, period, castor, pollux)
Expand Down
3 changes: 3 additions & 0 deletions firmware/data/gem_settings.structy
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ class GemSettings:
oscillator but sacrifice the range, values lower than 0.33 will
make it harder to tune and aren't recommended."""
pitch_knob_nonlinearity: fix16 = 0.6

"""The base CV offset applied to the pitch inputs."""
base_cv_offset: fix16 = 1.0
6 changes: 4 additions & 2 deletions firmware/src/gem_oscillator.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ void GemOscillator_init(
enum GemADCChannel pulse_width_knob_channel,
fix16_t smooth_initial_gain,
fix16_t smooth_sensitivity,
fix16_t base_offset,
fix16_t knob_min,
fix16_t knob_max,
bool lfo_pwm) {
Expand All @@ -44,6 +45,7 @@ void GemOscillator_init(
osc->pitch_knob_channel = pitch_knob_channel;
osc->pulse_width_cv_channel = pulse_width_cv_channel;
osc->pulse_width_knob_channel = pulse_width_knob_channel;
osc->base_offset = base_offset;
osc->knob_min = knob_min;
osc->knob_range = fix16_sub(knob_max, knob_min);
osc->follower_threshold = 0;
Expand Down Expand Up @@ -93,7 +95,7 @@ void GemOscillator_post_update(struct GemOscillator* osc, struct GemOscillatorIn
static void calculate_pitch_cv_(struct GemOscillator* osc, struct GemOscillatorInputs inputs) {
/*
The basic pitch CV determination formula is:
1.0v + (CV in * CV_RANGE) + ((CV knob * KNOB_RANGE) - KNOB_RANGE / 2)
(base offset) + (CV in * CV_RANGE) + ((CV knob * KNOB_RANGE) - KNOB_RANGE / 2)
*/

uint16_t cv_adc_code = inputs.adc[osc->pitch_cv_channel];
Expand All @@ -119,7 +121,7 @@ static void calculate_pitch_cv_(struct GemOscillator* osc, struct GemOscillatorI
*/
else {
fix16_t cv = UINT12_NORMALIZE_F(cv_adc_code_f16);
osc->pitch_cv = fix16_add(GEM_CV_BASE_OFFSET, fix16_mul(GEM_CV_INPUT_RANGE, cv));
osc->pitch_cv = fix16_add(osc->base_offset, fix16_mul(GEM_CV_INPUT_RANGE, cv));
}

/* Read the pitch knob and normalize (0.0 -> 1.0) its value. */
Expand Down
2 changes: 2 additions & 0 deletions firmware/src/gem_oscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct GemOscillator {
enum GemADCChannel pitch_knob_channel;
enum GemADCChannel pulse_width_cv_channel;
enum GemADCChannel pulse_width_knob_channel;
fix16_t base_offset;
fix16_t knob_min;
fix16_t knob_range;
uint16_t follower_threshold;
Expand Down Expand Up @@ -63,6 +64,7 @@ void GemOscillator_init(
enum GemADCChannel pulse_width_knob_channel,
fix16_t smooth_initial_gain,
fix16_t smooth_sensitivity,
fix16_t base_offset,
fix16_t knob_min,
fix16_t knob_max,
bool lfo_pwm);
Expand Down
32 changes: 21 additions & 11 deletions firmware/src/gem_settings_load_save.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@
#include "wntr_assert.h"
#include <stdarg.h>

#define SETTINGS_MARKER 0x65
#define SETTINGS_MARKER_V1 0x65
#define SETTINGS_MARKER_V2 0x66

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

extern uint8_t _nvm_settings_base_address;

bool GemSettings_check(struct GemSettings* settings) {
bool GemSettings_check(uint8_t marker, struct GemSettings* settings) {
/* This can't be fixed. If the ADC stuff is out of whack we gotta fail. */
if (settings->adc_gain_corr < 512 || settings->adc_gain_corr > 4096) {
goto fail;
Expand All @@ -30,11 +36,6 @@ bool GemSettings_check(struct GemSettings* settings) {
settings->led_brightness = defaults.led_brightness;
}

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

LIMIT_F16_FIELD(castor_knob_max, 0.0, 10.0);
LIMIT_F16_FIELD(castor_knob_min, -10.0, 0.0);
LIMIT_F16_FIELD(pollux_knob_max, 0.0, 10.0);
Expand All @@ -45,6 +46,13 @@ bool GemSettings_check(struct GemSettings* settings) {
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;
}
LIMIT_F16_FIELD(base_cv_offset, 0.0, 5.0);

return true;

fail:
Expand All @@ -61,15 +69,17 @@ bool GemSettings_load(struct GemSettings* settings) {
// NOLINTNEXTLINE(clang-diagnostic-pointer-to-int-cast)
gem_nvm_read((uint32_t)(&_nvm_settings_base_address), data, GEMSETTINGS_PACKED_SIZE + 1);

if (data[0] != SETTINGS_MARKER) {
uint8_t marker = data[0];

if (marker != SETTINGS_MARKER_V1 && marker != SETTINGS_MARKER_V2) {
printf("Invalid settings marker.\n");
goto fail;
}

struct StructyResult result = GemSettings_unpack(settings, data + 1);

if (result.status == STRUCTY_RESULT_OKAY) {
return GemSettings_check(settings);
return GemSettings_check(marker, settings);
}

printf("Failed to load settings.\n");
Expand All @@ -82,9 +92,9 @@ bool GemSettings_load(struct GemSettings* settings) {

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

GemSettings_check(settings);
GemSettings_check(data[0], settings);

struct StructyResult result = GemSettings_pack(settings, data + 1);

Expand Down
69 changes: 18 additions & 51 deletions firmware/src/gem_sysex_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@
/* Macros & defs */

#define SETTINGS_ENCODED_LEN TEETH_ENCODED_LENGTH(GEMSETTINGS_PACKED_SIZE)
#define CHUNK_SIZE 10
#define TOTAL_CHUNKS (SETTINGS_ENCODED_LEN / CHUNK_SIZE)
#define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0]))
static_assert(
SETTINGS_ENCODED_LEN % CHUNK_SIZE == 0, "Total settings encoded length must be a multiple of the chunk size");

#define DECODE_TEETH_REQUEST(size) \
WNTR_ASSERT(len == TEETH_ENCODED_LENGTH(size)); \
Expand All @@ -58,7 +54,6 @@ static_assert(

/* Static variables. */

static uint8_t chunk_buf_[SETTINGS_ENCODED_LEN];
static bool monitor_enabled_ = false;
static uint32_t last_monitor_update_ = 0;

Expand All @@ -71,8 +66,6 @@ static void cmd_0x04_read_adc_(const uint8_t* data, size_t len);
static void cmd_0x05_set_dac_(const uint8_t* data, size_t len);
static void cmd_0x06_set_period_(const uint8_t* data, size_t len);
static void cmd_0x07_erase_settings_(const uint8_t* data, size_t len);
static void cmd_0x08_read_settings_(const uint8_t* data, size_t len);
static void cmd_0x09_write_settings_(const uint8_t* data, size_t len);
static void cmd_0x0A_write_lut_entry_(const uint8_t* data, size_t len);
static void cmd_0x0B_write_lut_(const uint8_t* data, size_t len);
static void cmd_0x0C_erase_lut_(const uint8_t* data, size_t len);
Expand All @@ -83,6 +76,8 @@ static void cmd_0x10_monitor_(const uint8_t* data, size_t len);
static void cmd_0x11_soft_reset_(const uint8_t* data, size_t len);
static void cmd_0x12_enter_calibration_mode_(const uint8_t* data, size_t len);
static void cmd_0x13_reset_into_bootloader_(const uint8_t* data, size_t len);
static void cmd_0x18_read_settings_(const uint8_t* data, size_t len);
static void cmd_0x19_write_settings_(const uint8_t* data, size_t len);

/* Public functions. */

Expand All @@ -94,8 +89,6 @@ void gem_register_sysex_commands() {
wntr_midi_register_sysex_command(0x05, cmd_0x05_set_dac_);
wntr_midi_register_sysex_command(0x06, cmd_0x06_set_period_);
wntr_midi_register_sysex_command(0x07, cmd_0x07_erase_settings_);
wntr_midi_register_sysex_command(0x08, cmd_0x08_read_settings_);
wntr_midi_register_sysex_command(0x09, cmd_0x09_write_settings_);
wntr_midi_register_sysex_command(0x0A, cmd_0x0A_write_lut_entry_);
wntr_midi_register_sysex_command(0x0B, cmd_0x0B_write_lut_);
wntr_midi_register_sysex_command(0x0C, cmd_0x0C_erase_lut_);
Expand All @@ -106,6 +99,8 @@ void gem_register_sysex_commands() {
wntr_midi_register_sysex_command(0x11, cmd_0x11_soft_reset_);
wntr_midi_register_sysex_command(0x12, cmd_0x12_enter_calibration_mode_);
wntr_midi_register_sysex_command(0x13, cmd_0x13_reset_into_bootloader_);
wntr_midi_register_sysex_command(0x18, cmd_0x18_read_settings_);
wntr_midi_register_sysex_command(0x19, cmd_0x19_write_settings_);
};

void gem_sysex_send_monitor_update(struct GemMonitorUpdate* update) {
Expand Down Expand Up @@ -216,63 +211,35 @@ static void cmd_0x07_erase_settings_(const uint8_t* data, size_t len) {
GemSettings_erase();
}

static void cmd_0x08_read_settings_(const uint8_t* data, size_t len) {
/* Settings are sent in chunks to avoid overflowing midi buffers. */
/* Request: CHUNK_NUM(1) */
/* Response: SETTINGS_CHUNK(CHUNK_SIZE) */
static void cmd_0x18_read_settings_(const uint8_t* data, size_t len) {
/* Response (teeth): serialized settings */
(void)(data);
(void)(len);

const uint8_t chunk_num = data[0];
if (chunk_num > TOTAL_CHUNKS) {
printf("Invalid chunk %u.\r\n", chunk_num);
return;
}

struct GemSettings settings;
uint8_t settings_buf[GEMSETTINGS_PACKED_SIZE];
GemSettings_load(&settings);
GemSettings_pack(&settings, settings_buf);

teeth_encode(settings_buf, GEMSETTINGS_PACKED_SIZE, chunk_buf_);

PREPARE_RESPONSE(0x08, CHUNK_SIZE);
memcpy(response, chunk_buf_ + (CHUNK_SIZE * chunk_num), CHUNK_SIZE);
PREPARE_RESPONSE(0x18, TEETH_ENCODED_LENGTH(GEMSETTINGS_PACKED_SIZE));
teeth_encode(settings_buf, GEMSETTINGS_PACKED_SIZE, response);
SEND_RESPONSE();
}

static void cmd_0x09_write_settings_(const uint8_t* data, size_t len) {
/* Settings are sent in chunks to avoid overflowing midi buffers. */
/* Request: CHUNK_NUM(1) SETTINGS_CHUNK(CHUNK_SIZE) */
(void)(len);
static void cmd_0x19_write_settings_(const uint8_t* data, size_t len) {
/* Request (teeth): serialized settings */
DECODE_TEETH_REQUEST(GEMSETTINGS_PACKED_SIZE);

const uint8_t chunk_num = data[0];
if (chunk_num > TOTAL_CHUNKS) {
printf("Invalid chunk %u.\r\n", chunk_num);
return;
}

if (chunk_num == 0) {
memset(chunk_buf_, 0xFF, ARRAY_LEN(chunk_buf_));
}

memcpy(chunk_buf_ + (CHUNK_SIZE * chunk_num), data + 1, CHUNK_SIZE);

/* All data received, decode and save the settings. */
if (chunk_num == TOTAL_CHUNKS - 1) {
struct GemSettings settings;
uint8_t settings_buf[GEMSETTINGS_PACKED_SIZE];

teeth_decode(chunk_buf_, ARRAY_LEN(chunk_buf_), settings_buf);
struct GemSettings settings;

if (GemSettings_unpack(&settings, settings_buf).status == STRUCTY_RESULT_OKAY) {
GemSettings_save(&settings);
} else {
printf("Failed to save settings, unable to deserialize.\n");
}
if (GemSettings_unpack(&settings, request).status == STRUCTY_RESULT_OKAY) {
GemSettings_save(&settings);
} else {
printf("Failed to save settings, unable to deserialize.\n");
}

/* Ack the data. */
RESPONSE_0(0x09);
RESPONSE_0(0x19);
}

static void cmd_0x0A_write_lut_entry_(const uint8_t* data, size_t len) {
Expand Down
Loading

0 comments on commit 0b53021

Please sign in to comment.