//============================================================================================================ //! //! \file Seq-G1.cpp //! //! \brief Seq-G1 is a thing. //! //============================================================================================================ #include "Gratrix.hpp" #include "dsp/digital.hpp" namespace rack_plugin_Gratrix { #define PROGRAMS 12 #define RATIO 2 #define LCD_ROWS 4 #define LCD_COLS 8 #define LCD_TEXT 4 #define PRG_ROWS 1 #define PRG_COLS 8 #define NOB_ROWS 0 #define NOB_COLS LCD_COLS #define BUT_ROWS 4 #define BUT_COLS (NOB_COLS*RATIO) #define OUT_LEFT 1 #define OUT_RIGHT 1 #define GATE_STATES 4 struct GtxModule_Seq_G1 : Module { enum ParamIds { CLOCK_PARAM, RUN_PARAM, RESET_PARAM, PROG_PARAM, PLAY_PARAM, EDIT_PARAM, COPY_PARAM, PASTE_PARAM, STEPS_PARAM, CLEAR_PARAM, RANDOM_PARAM, SPAN_R_PARAM, SPAN_C_PARAM, PRG_ROW_PARAM, PRG_COL_PARAM, PRG_SPAN_PARAM, PRG_STRIDE_PARAM, PRG_NOTE_PARAM, PRG_OCTAVE_PARAM, PRG_VALUE_PARAM, PRG_GATE_PARAM, NOB_PARAM, BUT_PARAM = NOB_PARAM + (NOB_COLS * NOB_ROWS), NUM_PARAMS = BUT_PARAM + (BUT_COLS * BUT_ROWS) }; enum InputIds { CLOCK_INPUT, EXT_CLOCK_INPUT, RESET_INPUT, STEPS_INPUT, PROG_INPUT, NUM_INPUTS }; enum OutputIds { LCD_OUTPUT, NOB_OUTPUT = LCD_OUTPUT + LCD_ROWS * (OUT_LEFT + OUT_RIGHT), BUT_OUTPUT = NOB_OUTPUT + NOB_ROWS * (OUT_LEFT + OUT_RIGHT), NUM_OUTPUTS = BUT_OUTPUT + BUT_ROWS * (OUT_LEFT + OUT_RIGHT), }; enum LightIds { RUNNING_LIGHT, RESET_LIGHT, PROG_LIGHT, CLEAR_LIGHT = PROG_LIGHT + PROGRAMS * 2, RANDOM_LIGHT, COPY_LIGHT, PASTE_LIGHT, BUT_LIGHT, NUM_LIGHTS = BUT_LIGHT + (BUT_COLS * BUT_ROWS) * 3 }; struct Decode { /*static constexpr*/ float e = static_cast(PROGRAMS); // Static constexpr gives /*static constexpr*/ float s = 1.0f / e; // link error on Mac build. float in = 0; float out = 0; int note = 0; int key = 0; int oct = 0; void step(float input) { int safe, fnote; in = input; fnote = std::floor(in * PROGRAMS + 0.5f); out = fnote * s; note = static_cast(fnote); safe = note + (PROGRAMS * 1000); // push away from negative numbers key = safe % PROGRAMS; oct = (safe / PROGRAMS) - 1000; } }; static constexpr bool is_nob_snap(std::size_t row) { return false; } static constexpr std::size_t lcd_val_map(std::size_t row, std::size_t col) { return LCD_OUTPUT + (OUT_LEFT + OUT_RIGHT) * row + col; } static constexpr std::size_t nob_val_map(std::size_t row, std::size_t col) { return NOB_OUTPUT + (OUT_LEFT + OUT_RIGHT) * row + col; } static constexpr std::size_t but_val_map(std::size_t row, std::size_t col) { return BUT_OUTPUT + (OUT_LEFT + OUT_RIGHT) * row + col; } static constexpr std::size_t nob_map(std::size_t row, std::size_t col) { return NOB_PARAM + NOB_COLS * row + col; } static constexpr std::size_t but_map(std::size_t row, std::size_t col) { return BUT_PARAM + BUT_COLS * row + col; } static constexpr std::size_t led_map(std::size_t row, std::size_t col, std::size_t idx) { return BUT_LIGHT + 3 * (BUT_COLS * row + col) + idx; } Decode prg_nob; Decode prg_cv; bool running = true; SchmittTrigger clockTrigger; // for external clock // For buttons SchmittTrigger runningTrigger; SchmittTrigger resetTrigger; SchmittTrigger clearTrigger; SchmittTrigger randomTrigger; SchmittTrigger copyTrigger; SchmittTrigger pasteTrigger; SchmittTrigger gateTriggers[BUT_ROWS][BUT_COLS]; float phase = 0.0f; int index = 0; int numSteps = 0; std::size_t play_prog = 0; std::size_t edit_prog = 0; struct LcdData { bool active; int8_t mode; int8_t note; // C int8_t octave; // 4 --> C4 is 0V float value; LcdData() { reset(); } void reset() { active = false; mode = 0; note = 0; // C octave = 4; // 4 --> C4 is 0V value = 0.0f; } float to_voct() const { switch (mode) { case 0 : return (octave - 4.0f) + (note / 12.0f); case 1 : return value; default : return 0.0f; } } #if 0 std::ostream &debug(std::ostream &os) const { os << static_cast(mode) << " " << static_cast(note) << " " << static_cast(octave) << " " << value; return os; } #endif }; #if LCD_ROWS LcdData lcd_state[PROGRAMS][LCD_ROWS][LCD_COLS] = {}; LcdData lcd_cache [LCD_ROWS][LCD_COLS] = {}; #endif #if PRG_ROWS struct Caches { Cache prg_row; Cache prg_col; Cache prg_note; Cache prg_octave; Cache prg_value; Cache prg_gate; Cache prg_rows; Cache prg_cols; void reset() { prg_row .reset(); prg_col .reset(); prg_note .reset(); prg_octave.reset(); prg_value .reset(); prg_gate .reset(); prg_rows .reset(); prg_cols .reset(); } }; Caches caches; #endif #if BUT_ROWS uint8_t but_state[PROGRAMS][BUT_ROWS][BUT_COLS] = {}; uint8_t but_cache [BUT_ROWS][BUT_COLS] = {}; float but_lights [BUT_ROWS][BUT_COLS] = {}; #endif float resetLight = 0.0f; float clearLight = 0.0f; float randomLight = 0.0f; float copyLight = 0.0f; float pasteLight = 0.0f; enum GateMode { GM_OFF, GM_CONTINUOUS, GM_RETRIGGER, GM_TRIGGER, }; PulseGenerator gatePulse; //-------------------------------------------------------------------------------------------------------- //! \brief Constructor. GtxModule_Seq_G1() : Module( NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { onReset(); } //-------------------------------------------------------------------------------------------------------- //! \brief Step function. void step() override { const float lightLambda = 0.075f; // Decode program info prg_nob.step(params[PROG_PARAM].value / 12.0f); prg_cv .step(inputs[PROG_INPUT].value); // Input leds float prog_leds[PROGRAMS * 2] = {}; prog_leds[prg_nob.key * 2 ] = 1.0f; // Green prog_leds[prg_cv .key * 2 + 1] = 1.0f; // Red // Determine what is playing and what is editing bool play_is_cv = (params[PLAY_PARAM].value < 0.5f); bool edit_is_cv = (params[EDIT_PARAM].value < 0.5f); play_prog = play_is_cv ? prg_cv.key : prg_nob.key; edit_prog = edit_is_cv ? prg_cv.key : prg_nob.key; // Run if (runningTrigger.process(params[RUN_PARAM].value)) { running = !running; } bool nextStep = false; if (running) { if (inputs[EXT_CLOCK_INPUT].active) { // External clock if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) { phase = 0.0f; nextStep = true; } } else { // Internal clock float clockTime = powf(2.0f, params[CLOCK_PARAM].value + inputs[CLOCK_INPUT].value); phase += clockTime * engineGetSampleTime(); if (phase >= 1.0f) { phase -= 1.0f; nextStep = true; } } } // Update knobs knob_pull(edit_prog); // Trigger buttons { const float dim = 1.0f / lightLambda * engineGetSampleTime(); // Reset if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) { phase = 0.0f; index = BUT_COLS; nextStep = true; resetLight = 1.0f; } resetLight -= resetLight * dim; // Clear current program if (clearTrigger.process(params[CLEAR_PARAM].value)) { clear_prog(edit_prog); clearLight = 1.0f; } clearLight -= clearLight * dim; // Randomise current program if (randomTrigger.process(params[RANDOM_PARAM].value)) { randomize_prog(edit_prog); randomLight = 1.0f; } randomLight -= randomLight * dim; // Copy current program if (copyTrigger.process(params[COPY_PARAM].value)) { copy_prog(edit_prog); copyLight = 1.0f; } copyLight -= copyLight * dim; // Paste current program if (pasteTrigger.process(params[PASTE_PARAM].value)) { paste_prog(edit_prog); pasteLight = 1.0f; } pasteLight -= pasteLight * dim; } numSteps = RATIO * clamp(roundf(params[STEPS_PARAM].value + inputs[STEPS_INPUT].value), 1.0f, static_cast(LCD_COLS)); if (nextStep) { // Advance step index += 1; if (index >= numSteps) { index = 0; } #if BUT_ROWS for (int row = 0; row < BUT_ROWS; row++) { but_lights[row][index] = 1.0f; } #endif gatePulse.trigger(1e-3); } bool pulse = gatePulse.process(engineGetSampleTime()); #if BUT_ROWS // Gate buttons for (int col = 0; col < BUT_COLS; ++col) { for (int row = 0; row < BUT_ROWS; ++row) { // User input to alter state of buttons if (gateTriggers[row][col].process(params[but_map(row, col)].value)) { auto state = but_state[edit_prog][row][col]; if (++state >= GATE_STATES) { state = GM_OFF; } std::size_t span_r = static_cast(params[SPAN_R_PARAM].value + 0.5f); std::size_t span_c = static_cast(params[SPAN_C_PARAM].value + 0.5f); for (std::size_t r = row; r < row + span_r && r < BUT_ROWS; ++r) { for (std::size_t c = col; c < col + span_c && c < BUT_COLS; ++c) { but_state[edit_prog][r][c] = state; } } } // Get state of buttons for lights { bool gateOn = (running && (col == index) && (but_state[edit_prog][row][col] > 0)); switch (but_state[edit_prog][row][col]) { case GM_CONTINUOUS : break; case GM_RETRIGGER : gateOn = gateOn && !pulse; break; case GM_TRIGGER : gateOn = gateOn && pulse; break; default : break; } but_lights[row][col] -= but_lights[row][col] / lightLambda * engineGetSampleTime(); if (col < numSteps) { float val = (play_prog == edit_prog) ? 1.0f : 0.1f; lights[led_map(row, col, 1)].value = but_state[edit_prog][row][col] == GM_CONTINUOUS ? 1.0f - val * but_lights[row][col] : val * but_lights[row][col]; // Green lights[led_map(row, col, 2)].value = but_state[edit_prog][row][col] == GM_RETRIGGER ? 1.0f - val * but_lights[row][col] : val * but_lights[row][col]; // Blue lights[led_map(row, col, 0)].value = but_state[edit_prog][row][col] == GM_TRIGGER ? 1.0f - val * but_lights[row][col] : val * but_lights[row][col]; // Red } else { lights[led_map(row, col, 1)].value = 0.01f; // Green lights[led_map(row, col, 2)].value = 0.01f; // Blue lights[led_map(row, col, 0)].value = 0.01f; // Red } } } } #endif // Compute row outputs #if LCD_ROWS std::size_t lcd_index = index / RATIO; float lcd_val[LCD_ROWS]; for (std::size_t row = 0; row < LCD_ROWS; ++row) { lcd_val[row] = lcd_state[play_prog][row][lcd_index].to_voct(); } #endif #if NOB_ROWS std::size_t nob_index = index / RATIO; float nob_val[NOB_ROWS]; for (std::size_t row = 0; row < NOB_ROWS; ++row) { nob_val[row] = params[nob_map(row, nob_index)].value; if (is_nob_snap(row)) nob_val[row] /= 12.0f; } #endif #if BUT_ROWS bool but_val[BUT_ROWS]; for (std::size_t row = 0; row < BUT_ROWS; ++row) { but_val[row] = running && (but_state[play_prog][row][index] > 0); switch (but_state[play_prog][row][index]) { case GM_CONTINUOUS : break; case GM_RETRIGGER : but_val[row] = but_val[row] && !pulse; break; case GM_TRIGGER : but_val[row] = but_val[row] && pulse; break; default : break; } } #endif // Write row outputs #if LCD_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row) { if (OUT_LEFT || OUT_RIGHT) outputs[lcd_val_map(row, 0)].value = lcd_val[row]; if (OUT_LEFT && OUT_RIGHT) outputs[lcd_val_map(row, 1)].value = lcd_val[row]; } #endif #if NOB_ROWS for (std::size_t row = 0; row < NOB_ROWS; ++row) { if (OUT_LEFT || OUT_RIGHT) outputs[nob_val_map(row, 0)].value = nob_val[row]; if (OUT_LEFT && OUT_RIGHT) outputs[nob_val_map(row, 1)].value = nob_val[row]; } #endif #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; ++row) { if (OUT_LEFT || OUT_RIGHT) outputs[but_val_map(row, 0)].value = but_val[row] ? 10.0f : 0.0f; if (OUT_LEFT && OUT_RIGHT) outputs[but_val_map(row, 1)].value = but_val[row] ? 10.0f : 0.0f; } #endif // Update LEDs lights[RUNNING_LIGHT].value = running ? 1.0f : 0.0f; lights[RESET_LIGHT] .value = resetLight; lights[CLEAR_LIGHT] .value = clearLight; lights[RANDOM_LIGHT] .value = randomLight; lights[COPY_LIGHT] .value = copyLight; lights[PASTE_LIGHT] .value = pasteLight; for (std::size_t i=0; i(current.mode ))) json_object_set_new(jo_data, "mode", ji); if (json_t *ji = json_integer(static_cast(current.note ))) json_object_set_new(jo_data, "note", ji); if (json_t *ji = json_integer(static_cast(current.octave))) json_object_set_new(jo_data, "octave", ji); if (json_t *ji = json_real(static_cast(current.value ))) json_object_set_new(jo_data, "value", ji); json_array_append_new(ja_cols, jo_data ); } } json_array_append_new(ja_rows, ja_cols ); } } json_array_append_new(ja_progs, ja_rows ); } } json_object_set_new (jo_root, "lcd", ja_progs); } #endif // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Button state #if BUT_ROWS if (json_t *ja_progs = json_array()) { for (std::size_t prog = 0; prog < PROGRAMS; ++prog) { if (json_t *ja_rows = json_array()) { for (std::size_t row = 0; row < BUT_ROWS; ++row ) { if (json_t *ja_cols = json_array()) { for (std::size_t col = 0; col < BUT_COLS; ++col ) { if (json_t *jo_data = json_object()) { auto ¤t = but_state[prog][row][col]; if (json_t *ji = json_integer(static_cast(current))) json_object_set_new(jo_data, "mode", ji); json_array_append_new(ja_cols, jo_data ); } } json_array_append_new(ja_rows, ja_cols ); } } json_array_append_new(ja_progs, ja_rows ); } } json_object_set_new (jo_root, "but", ja_progs); } #endif return jo_root; } return nullptr; } //-------------------------------------------------------------------------------------------------------- //! \brief Load state. void fromJson(json_t *jo_root) override { if (jo_root) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Running if (json_t *jb = json_object_get(jo_root, "running")) { running = json_is_true(jb); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LCD state #if LCD_ROWS for (std::size_t prog = 0; prog < PROGRAMS; ++prog) { for (std::size_t row = 0; row < LCD_ROWS; ++row ) { for (std::size_t col = 0; col < LCD_COLS; ++col ) { lcd_state[prog][row][col].reset(); } } } if (json_t *ja_progs = json_object_get(jo_root, "lcd")) { for (std::size_t prog = 0; prog < PROGRAMS && prog < json_array_size(ja_progs); ++prog) { if (json_t *ja_rows = json_array_get (ja_progs, prog)) { for (std::size_t row = 0; row < LCD_ROWS && row < json_array_size(ja_rows); ++row ) { if (json_t *ja_cols = json_array_get (ja_rows, row )) { for (std::size_t col = 0; col < LCD_COLS && col < json_array_size(ja_cols); ++col ) { if (json_t *jo_data = json_array_get (ja_cols, col )) { auto ¤t = lcd_state[prog][row][col]; if (json_t *jo = json_object_get(jo_data, "mode" )) current.mode = static_cast(json_integer_value(jo)); if (json_t *jo = json_object_get(jo_data, "note" )) current.note = static_cast(json_integer_value(jo)); if (json_t *jo = json_object_get(jo_data, "octave")) current.octave = static_cast(json_integer_value(jo)); if (json_t *jo = json_object_get(jo_data, "value" )) current.value = static_cast (json_real_value (jo)); } } } } } } } caches.reset(); #endif // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Button state #if BUT_ROWS for (std::size_t prog = 0; prog < PROGRAMS; ++prog) { for (std::size_t row = 0; row < LCD_ROWS; ++row ) { for (std::size_t col = 0; col < LCD_COLS; ++col ) { but_state[prog][row][col] = GM_OFF; } } } if (json_t *ja_progs = json_object_get(jo_root, "but")) { for (std::size_t prog = 0; prog < PROGRAMS && prog < json_array_size(ja_progs); ++prog) { if (json_t *ja_rows = json_array_get (ja_progs, prog)) { for (std::size_t row = 0; row < BUT_ROWS && row < json_array_size(ja_rows); ++row ) { if (json_t *ja_cols = json_array_get (ja_rows, row )) { for (std::size_t col = 0; col < BUT_COLS && col < json_array_size(ja_cols); ++col ) { if (json_t *jo_data = json_array_get (ja_cols, col )) { auto ¤t = but_state[prog][row][col]; if (json_t *jo = json_object_get(jo_data, "mode" )) current = static_cast(json_integer_value(jo)); } } } } } } } #endif } } //-------------------------------------------------------------------------------------------------------- //! \brief Reset state. void onReset() override { for (std::size_t prog = 0; prog < PROGRAMS; ++prog) { clear_prog(prog); } } //-------------------------------------------------------------------------------------------------------- //! \brief Random state. void randomize() override { for (std::size_t prog = 0; prog < PROGRAMS; ++prog) { randomize_prog(prog); } } //-------------------------------------------------------------------------------------------------------- //! \brief Knob params to state. //! //! Only updates on knob value change, otheriwse current value always applied to current program. void knob_pull(std::size_t prog) { #if LCD_ROWS && PRG_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row) { for (std::size_t col = 0; col < LCD_COLS; ++col) { lcd_state[prog][row][col].active = false; } } { std::size_t prg_row = static_cast(params[PRG_ROW_PARAM].value + 0.5f); if (prg_row < LCD_ROWS) { std::size_t prg_col = static_cast(params[PRG_COL_PARAM].value + 0.5f); if (prg_col < LCD_COLS) { std::size_t prg_span = static_cast(params[PRG_SPAN_PARAM ].value + 0.5f); std::size_t prg_stride = static_cast(params[PRG_STRIDE_PARAM].value + 0.5f); int8_t prg_note = static_cast(params[PRG_NOTE_PARAM ].value + 0.5f); int8_t prg_octave = static_cast(params[PRG_OCTAVE_PARAM].value + 0.5f); float prg_value = params[PRG_VALUE_PARAM ].value; // int8_t prg_gate = static_cast(params[PRG_GATE_PARAM ].value + 0.5f); std::size_t col_max = prg_col + prg_span * prg_stride; if (col_max > LCD_COLS) { col_max = LCD_COLS; } for (std::size_t col = prg_col; col < col_max; col += prg_stride) { auto ¤t = lcd_state[prog][prg_row][col]; current.active = true; if (caches.prg_note.test(prg_note)) { current.note = prg_note; current.mode = 0; } if (caches.prg_octave.test(prg_octave)) { current.octave = prg_octave; current.mode = 0; } if (caches.prg_value.test(prg_value)) { current.value = prg_value; current.mode = 1; } } caches.prg_note .set(prg_note); caches.prg_octave.set(prg_octave); caches.prg_value .set(prg_value); } } } #endif } //-------------------------------------------------------------------------------------------------------- //! \brief Clear a program. void clear_prog(std::size_t prog) { #if LCD_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row) { for (std::size_t col = 0; col < LCD_COLS; col++) { lcd_state[prog][row][col] = LcdData(); } } #endif #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; row++) { for (std::size_t col = 0; col < BUT_COLS; col++) { but_state[prog][row][col] = GM_OFF; } } #endif caches.reset(); } //-------------------------------------------------------------------------------------------------------- //! \brief Randomize a program. void randomize_prog(std::size_t prog) { #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; row++) { for (std::size_t col = 0; col < BUT_COLS; col++) { uint32_t r = randomu32() % (GATE_STATES + 1); if (r >= GATE_STATES) r = GM_CONTINUOUS; but_state[prog][row][col] = r; } } #endif caches.reset(); } //-------------------------------------------------------------------------------------------------------- //! \brief Copy a program. void copy_prog(std::size_t prog) { #if LCD_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row) { for (std::size_t col = 0; col < LCD_COLS; col++) { lcd_cache[row][col] = lcd_state[prog][row][col]; } } #endif #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; row++) { for (std::size_t col = 0; col < BUT_COLS; col++) { but_cache[row][col] = but_state[prog][row][col]; } } #endif } //-------------------------------------------------------------------------------------------------------- //! \brief Paste a program. void paste_prog(std::size_t prog) { #if LCD_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row) { for (std::size_t col = 0; col < LCD_COLS; col++) { lcd_state[prog][row][col] = lcd_cache[row][col]; } } #endif #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; row++) { for (std::size_t col = 0; col < BUT_COLS; col++) { but_state[prog][row][col] = but_cache[row][col]; } } #endif caches.reset(); } }; #if LCD_ROWS //============================================================================================================ //! \brief Display. struct Display : TransparentWidget { GtxModule_Seq_G1 *module; int frame = 0; std::shared_ptr font; float tx[LCD_COLS] = {}; float ty[LCD_ROWS] = {}; char text[LCD_ROWS][LCD_COLS][LCD_TEXT + 1] = {}; //-------------------------------------------------------------------------------------------------------- //! \brief Constructor. Display(GtxModule_Seq_G1 *module_, const Rect &box_) : module(module_) { box = box_; font = Font::load(assetPlugin(plugin, "res/fonts/lcd-solid/LCD_Solid.ttf")); for (std::size_t col = 0; col < LCD_COLS; col++) { tx[col] = 4.0f + col * box.size.x / static_cast(LCD_COLS); } for (std::size_t row = 0; row < LCD_ROWS; row++) { ty[row] = (row + 1) * 18.0f; } for (std::size_t col = 0; col < LCD_COLS; ++col) { for (std::size_t row = 0; row < LCD_ROWS; ++row) { std::strncpy(text[row][col], "01234567012345670123456701234567", LCD_TEXT); } } } //-------------------------------------------------------------------------------------------------------- //! \brief ... void draw_main(NVGcontext *vg) { nvgFontSize(vg, 16); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, -2); char old = 'x'; for (std::size_t col = 0; col < LCD_COLS; ++col) { for (std::size_t row = 0; row < LCD_ROWS; ++row) { if (old != text[row][col][0]) { switch (text[row][col][0]) { case 'p' : nvgFillColor(vg, nvgRGBA(0xe1, 0x02, 0x78, 0xc0)); break; default : nvgFillColor(vg, nvgRGBA(0x28, 0xb0, 0xf3, 0xc0)); break; } old = text[row][col][0]; } nvgText(vg, tx[col], ty[row], text[row][col] + 1, NULL); } } } //-------------------------------------------------------------------------------------------------------- //! \brief ... void draw(NVGcontext *vg) override { // Calculate if (++frame >= 4) { frame = 0; static const char *note_names[13] = {"C-", "C#", "D-", "Eb", "E-", "F-", "F#", "G-", "Ab", "A-", "Bb", "B-", "??"}; static const char *octave_names[10] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "?"}; for (std::size_t col = 0; col < LCD_COLS; ++col) { for (std::size_t row = 0; row < LCD_ROWS; ++row) { bool active = module->lcd_state[module->edit_prog][row][col].active; int mode = module->lcd_state[module->edit_prog][row][col].mode; text[row][col][0] = active ? 'p' : 'b'; switch (mode) { case 0 : { int note = module->lcd_state[module->edit_prog][row][col].note; int octave = module->lcd_state[module->edit_prog][row][col].octave; if (note < 0 || note > 12) note = 12; if (octave < 0 || octave > 9) octave = 9; text[row][col][1] = note_names[ note][0]; text[row][col][2] = note_names[ note][1]; text[row][col][3] = octave_names[octave][0]; text[row][col][4] = '\0'; } break; case 1 : { float value = module->lcd_state[module->edit_prog][row][col].value; snprintf(&text[row][col][1], 4, "%4.2f", value); } break; default : { text[row][col][1] = '?'; text[row][col][2] = '\0'; } break; } } } } draw_main(vg); } }; #endif //============================================================================================================ //! \brief The widget. struct GtxWidget_Seq_G1 : ModuleWidget { GtxWidget_Seq_G1(GtxModule_Seq_G1 *module) : ModuleWidget(module) { GTX__WIDGET(); box.size = Vec((OUT_LEFT+LCD_COLS+OUT_RIGHT)*3*15, 380); float grid_left = 3*15*OUT_LEFT; float grid_right = 3*15*OUT_RIGHT; float grid_size = box.size.x - grid_left - grid_right; auto display_rect = Rect(Vec(grid_left, 35), Vec(grid_size, (rad_but()+1.5) * 2 * LCD_ROWS)); #if LCD_ROWS float g_lcdX[LCD_COLS] = {}; for (std::size_t i = 0; i < LCD_COLS; i++) { float x = grid_size / static_cast(LCD_COLS); g_lcdX[i] = grid_left + x * (i + 0.5); } #endif #if NOB_ROWS float g_nobX[NOB_COLS] = {}; for (std::size_t i = 0; i < NOB_COLS; i++) { float x = grid_size / static_cast(NOB_COLS); g_nobX[i] = grid_left + x * (i + 0.5); } #endif #if BUT_ROWS float g_butX[BUT_COLS] = {}; for (std::size_t i = 0; i < BUT_COLS; i++) { float x = grid_size / static_cast(BUT_COLS); g_butX[i] = grid_left + x * (i + 0.5); } #endif float gridXl = grid_left / 2; float gridXr = box.size.x - grid_right / 2; float portX[10] = {}; for (std::size_t i = 0; i < 10; i++) { float x = 5*6*15 / static_cast(10); portX[i] = x * (i + 0.5); } float dX = 0.5*(portX[1]-portX[0]); float portY[4] = {}; portY[0] = gy(2-0.24); portY[1] = gy(2+0.22); float dY = 0.5*(portY[1]-portY[0]); portY[2] = portY[0] + 0.45 * dY; portY[3] = portY[0] + dY; float gridY[LCD_ROWS + PRG_ROWS + NOB_ROWS + BUT_ROWS] = {}; { std::size_t j = 0; float pos = 35; #if LCD_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row, ++j) { pos += rad_but() + 1.5; gridY[j] = pos; pos += rad_but() + 1.5; } #endif pos += 13; #if PRG_ROWS { pos += rad_n_s() + 4.5; gridY[j] = pos; pos += rad_n_s() + 4.5; ++j; } #endif #if NOB_ROWS for (std::size_t row = 0; row < NOB_ROWS; ++row, ++j) { pos += rad_n_s() + 4.5; gridY[j] = pos; pos += rad_n_s() + 4.5; } #endif #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; ++row, ++j) { pos += rad_but() + 1.5; gridY[j] = pos; pos += rad_but() + 1.5; } #endif } #if GTX__SAVE_SVG { PanelGen pg(assetPlugin(plugin, "build/res/Seq-G1.svg"), box.size, "SEQ-G1"); pg.rect(display_rect.pos, display_rect.size, "fill:#222222;stroke:none"); { float y0 = display_rect.pos.y - 2; float y1 = display_rect.pos.y + display_rect.size.y + 3; pg.line(Vec(g_lcdX[0]-dX, y0), Vec(g_lcdX[0]-dX, y1), "fill:none;stroke:#CEE1FD;stroke-width:3"); for (std::size_t i=3; i(Vec(15, 0))); addChild(Widget::create(Vec(box.size.x-30, 0))); addChild(Widget::create(Vec(15, 365))); addChild(Widget::create(Vec(box.size.x-30, 365))); addChild(new Display(module, display_rect)); addParam(createParamGTX (Vec(portX[0], portY[0]), module, GtxModule_Seq_G1::CLOCK_PARAM, -2.0f, 6.0f, 2.0f)); addParam(ParamWidget::create (but(portX[1], portY[0]), module, GtxModule_Seq_G1::RUN_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(l_m(portX[1], portY[0]), module, GtxModule_Seq_G1::RUNNING_LIGHT)); addParam(ParamWidget::create (but(portX[2], portY[0]), module, GtxModule_Seq_G1::RESET_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(l_m(portX[2], portY[0]), module, GtxModule_Seq_G1::RESET_LIGHT)); addParam(createParamGTX (Vec(portX[3], portY[0]), module, GtxModule_Seq_G1::PROG_PARAM, 0.0f, 11.0f, 0.0f)); addParam(ParamWidget::create (tog(portX[4], portY[2]), module, GtxModule_Seq_G1::PLAY_PARAM, 0.0f, 1.0f, 1.0f)); addParam(ParamWidget::create (tog(portX[5], portY[2]), module, GtxModule_Seq_G1::EDIT_PARAM, 0.0f, 1.0f, 1.0f)); addChild(ModuleLightWidget::create>(l_s(portX[4] + dX - 30, portY[1] + 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 0*2)); // C addChild(ModuleLightWidget::create>(l_s(portX[4] + dX - 25, portY[1] - 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 1*2)); // C# addChild(ModuleLightWidget::create>(l_s(portX[4] + dX - 20, portY[1] + 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 2*2)); // D addChild(ModuleLightWidget::create>(l_s(portX[4] + dX - 15, portY[1] - 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 3*2)); // Eb addChild(ModuleLightWidget::create>(l_s(portX[4] + dX - 10, portY[1] + 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 4*2)); // E addChild(ModuleLightWidget::create>(l_s(portX[4] + dX , portY[1] + 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 5*2)); // F addChild(ModuleLightWidget::create>(l_s(portX[4] + dX + 5, portY[1] - 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 6*2)); // Fs addChild(ModuleLightWidget::create>(l_s(portX[4] + dX + 10, portY[1] + 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 7*2)); // G addChild(ModuleLightWidget::create>(l_s(portX[4] + dX + 15, portY[1] - 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 8*2)); // Ab addChild(ModuleLightWidget::create>(l_s(portX[4] + dX + 20, portY[1] + 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 9*2)); // A addChild(ModuleLightWidget::create>(l_s(portX[4] + dX + 25, portY[1] - 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 10*2)); // Bb addChild(ModuleLightWidget::create>(l_s(portX[4] + dX + 30, portY[1] + 5 + 1), module, GtxModule_Seq_G1::PROG_LIGHT + 11*2)); // B addParam(ParamWidget::create (but(portX[6], portY[0]), module, GtxModule_Seq_G1::COPY_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(l_m(portX[6], portY[0]), module, GtxModule_Seq_G1::COPY_LIGHT)); addParam(ParamWidget::create (but(portX[6], portY[1]), module, GtxModule_Seq_G1::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(l_m(portX[6], portY[1]), module, GtxModule_Seq_G1::PASTE_LIGHT)); addParam(createParamGTX (Vec(portX[7], portY[0]), module, GtxModule_Seq_G1::STEPS_PARAM, 1.0f, NOB_COLS, NOB_COLS)); addParam(ParamWidget::create (but(portX[8], portY[0]), module, GtxModule_Seq_G1::CLEAR_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(l_m(portX[8], portY[0]), module, GtxModule_Seq_G1::CLEAR_LIGHT)); addParam(ParamWidget::create (but(portX[8], portY[1]), module, GtxModule_Seq_G1::RANDOM_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(l_m(portX[8], portY[1]), module, GtxModule_Seq_G1::RANDOM_LIGHT)); addParam(createParamGTX (Vec(portX[9], portY[0]), module, GtxModule_Seq_G1::SPAN_R_PARAM, 1.0f, 8.0f, 1.0f)); addParam(createParamGTX (Vec(portX[9], portY[1]), module, GtxModule_Seq_G1::SPAN_C_PARAM, 1.0f, 8.0f, 1.0f)); addInput(createInputGTX(Vec(portX[0], portY[1]), module, GtxModule_Seq_G1::CLOCK_INPUT)); addInput(createInputGTX(Vec(portX[1], portY[1]), module, GtxModule_Seq_G1::EXT_CLOCK_INPUT)); addInput(createInputGTX(Vec(portX[2], portY[1]), module, GtxModule_Seq_G1::RESET_INPUT)); addInput(createInputGTX(Vec(portX[3], portY[1]), module, GtxModule_Seq_G1::PROG_INPUT)); addInput(createInputGTX(Vec(portX[7], portY[1]), module, GtxModule_Seq_G1::STEPS_INPUT)); { std::size_t j = 0; #if LCD_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row, ++j) { if (OUT_LEFT ) addOutput(createOutputGTX(Vec(gridXl, gridY[j]), module, GtxModule_Seq_G1::lcd_val_map(row, 0))); if (OUT_RIGHT) addOutput(createOutputGTX(Vec(gridXr, gridY[j]), module, GtxModule_Seq_G1::lcd_val_map(row, 1))); } #endif #if PRG_ROWS ++j; #endif #if NOB_ROWS for (std::size_t row = 0; row < NOB_ROWS; ++row, ++j) { if (OUT_LEFT ) addOutput(createOutputGTX(Vec(gridXl, gridY[j]), module, GtxModule_Seq_G1::nob_val_map(row, 0))); if (OUT_RIGHT) addOutput(createOutputGTX(Vec(gridXr, gridY[j]), module, GtxModule_Seq_G1::nob_val_map(row, 1))); } #endif #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; ++row, ++j) { if (OUT_LEFT ) addOutput(createOutputGTX(Vec(gridXl, gridY[j]), module, GtxModule_Seq_G1::but_val_map(row, 0))); if (OUT_RIGHT) addOutput(createOutputGTX(Vec(gridXr, gridY[j]), module, GtxModule_Seq_G1::but_val_map(row, 1))); } #endif } { std::size_t j = 0; #if LCD_ROWS for (std::size_t row = 0; row < LCD_ROWS; ++row, ++j) { ; } #endif #if PRG_ROWS { addParam(createParamGTX(Vec(g_lcdX[0], gridY[j]), module, GtxModule_Seq_G1::PRG_ROW_PARAM, 0.0f, LCD_ROWS - 1, 0.0f)); addParam(createParamGTX(Vec(g_lcdX[1], gridY[j]), module, GtxModule_Seq_G1::PRG_COL_PARAM, 0.0f, LCD_COLS - 1, 0.0f)); addParam(createParamGTX(Vec(g_lcdX[2], gridY[j]), module, GtxModule_Seq_G1::PRG_SPAN_PARAM, 1.0f, LCD_COLS , 1.0f)); addParam(createParamGTX(Vec(g_lcdX[3], gridY[j]), module, GtxModule_Seq_G1::PRG_STRIDE_PARAM, 1.0f, LCD_COLS - 1, 1.0f)); addParam(createParamGTX(Vec(g_lcdX[4], gridY[j]), module, GtxModule_Seq_G1::PRG_NOTE_PARAM, 0.0f, 11.0f, 0.0f)); addParam(createParamGTX(Vec(g_lcdX[5], gridY[j]), module, GtxModule_Seq_G1::PRG_OCTAVE_PARAM, 0.0f, 8.0f, 4.0f)); addParam(createParamGTX(Vec(g_lcdX[6], gridY[j]), module, GtxModule_Seq_G1::PRG_VALUE_PARAM, 0.0f, 10.0f, 0.0f)); addParam(createParamGTX(Vec(g_lcdX[7], gridY[j]), module, GtxModule_Seq_G1::PRG_GATE_PARAM, 0.0f, 3.0f, 0.0f)); ++j; } #endif #if NOB_ROWS for (std::size_t row = 0; row < NOB_ROWS; ++row, ++j) { for (std::size_t col = 0; col < NOB_COLS; ++col) { if (GtxModule_Seq_G1::is_nob_snap(row)) { addParam(createParamGTX(Vec(g_nobX[col], gridY[j]), module, GtxModule_Seq_G1::nob_map(row, col), 0.0f, 12.0f, 0.0f)); } else { addParam(createParamGTX(Vec(g_nobX[col], gridY[j]), module, GtxModule_Seq_G1::nob_map(row, col), 0.0f, 10.0f, 0.0f)); } } } #endif #if BUT_ROWS for (std::size_t row = 0; row < BUT_ROWS; ++row, ++j) { for (std::size_t col = 0; col < BUT_COLS; ++col) { addParam(ParamWidget::create (but(g_butX[col], gridY[j]), module, GtxModule_Seq_G1::but_map(row, col), 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(l_m(g_butX[col], gridY[j]), module, GtxModule_Seq_G1::led_map(row, col, 0))); } } #endif } } }; } // namespace rack_plugin_Gratrix using namespace rack_plugin_Gratrix; RACK_PLUGIN_MODEL_INIT(Gratrix, Seq_G1) { Model *model = Model::create("Gratrix", "Seq-G1-alpha1", "Seq-G1 (alpha)", SEQUENCER_TAG); return model; }