diff --git a/include/checkbox.h b/include/checkbox.h index 553fc1a..1ab65c8 100644 --- a/include/checkbox.h +++ b/include/checkbox.h @@ -20,6 +20,13 @@ typedef uint8_t Checkbox; ? CHECKBOX_ON_COLOR \ : CHECKBOX_OFF_COLOR))) +/// c: bitfield where the checkbox lives +/// b: bit the checkbox represents (1 << x) +/// p: the position to draw the checkbox +#define checkbox_draw_inv(c, b, p) (plot_pad((p), (flag_is_set((c), (b)) \ + ? CHECKBOX_OFF_COLOR \ + : CHECKBOX_ON_COLOR))) + /// c: bitfield where the checkbox bit lives /// b: bit the checkbox represents (1 << x) /// i: index of button that was pressed diff --git a/include/colors.h b/include/colors.h index b4cc95e..c2d9b7e 100644 --- a/include/colors.h +++ b/include/colors.h @@ -5,13 +5,25 @@ extern const uint8_t root_note_color[3]; extern const uint8_t white_note_color[3]; extern const uint8_t black_note_color[3]; +extern const uint8_t invalid_note_color[3]; extern const uint8_t c_note_color[3]; extern const uint8_t no_note_color[3]; + +extern const uint8_t layout_octave_color[3]; +extern const uint8_t layout_transpose_color[3]; +extern const uint8_t scale_button_color[3]; + extern const uint8_t white_key_color[3]; extern const uint8_t black_key_color[3]; extern const uint8_t slider_color[3]; extern const uint8_t off_color[3]; extern const uint8_t on_color[3]; + +extern const uint8_t note_octave_up_colors[10][3]; +extern const uint8_t note_octave_down_colors[10][3]; +extern const uint8_t note_transpose_up_colors[12][3]; +extern const uint8_t note_transpose_down_colors[12][3]; + extern const uint8_t sequence_colors[8][3]; extern const uint8_t number_colors[4][3]; extern const uint8_t drum_colors[4][3]; diff --git a/include/data.h b/include/data.h index 095e13f..c11b853 100644 --- a/include/data.h +++ b/include/data.h @@ -18,7 +18,7 @@ typedef enum LP_NOTES_MODE, LP_SEQUENCER_MODE, LP_USER_MODE, - LP_NUM_MODES + LP_NUM_MODES, } LpState; /// Global program flags. @@ -33,7 +33,8 @@ typedef enum LP_ARMED = 1 << 6, LP_RCV_CLOCK = 1 << 7, LP_RCV_CLOCK_PORT = 1 << 8, - LP_IS_ARP = 1 << 9 + LP_IS_ARP = 1 << 9, + LP_IS_SETUP2 = 1 << 10 } LpFlags; /// The number of notes needed for all 8 sequences. Two of these are used: one @@ -74,8 +75,12 @@ extern Note* lp_note_storage; extern Scale lp_scale; extern Voices lp_voices; extern PadNotes lp_pad_notes; +extern PadNotes lp_pad_highlights; extern Sequencer lp_sequencer; +extern TextDisplayer lp_text_displayer; +extern int8_t lp_quick_scale_current; + // Setup UI elements extern Slider lp_tempo_slider; extern Slider lp_swing_slider; diff --git a/include/interface.h b/include/interface.h index c15226c..6302c96 100644 --- a/include/interface.h +++ b/include/interface.h @@ -52,6 +52,10 @@ #define MULTICHANNEL_CHECKBOX_POS (55) #define CONTROL_CHECKBOX_POS (81) +#define NOTE_HIGHLIGHT_ONLY_POS (28) +#define SCALE_HIGHLIGHT_ONLY_POS (48) +#define SCALE_HIGHLIGHT_ONLY_INV_POS (28) + #define MOD_WHEEL_POS (51) #define MOD_WHEEL_X (0) #define MOD_WHEEL_Y (4) diff --git a/include/keyboard.h b/include/keyboard.h index 41143dc..a01e568 100644 --- a/include/keyboard.h +++ b/include/keyboard.h @@ -30,10 +30,11 @@ typedef struct void keyboard_init(Keyboard* k, Layout* l); /// Toggles a note on or off and updates the layout and scale to match. -uint8_t keyboard_handle_press(Keyboard* k, uint8_t index, uint8_t value); +uint8_t keyboard_handle_press(Keyboard* k, uint8_t index, uint8_t value, uint8_t y, uint8_t is_highlight_press); /// Draws the keyboard to the grid. -void keyboard_draw(Keyboard* k); +void keyboard_draw(Keyboard* k, uint8_t draw_highlighted); +void keyboard_draw_y(Keyboard* k, uint8_t draw_highlighted, uint8_t y); /// When a change is made to the layout or scale, this function updates the /// keyboard to reflect the current state. diff --git a/include/layout.h b/include/layout.h index 649e43e..f41357a 100644 --- a/include/layout.h +++ b/include/layout.h @@ -5,11 +5,11 @@ #include "app.h" #include "buttons.h" #include "colors.h" -#include "keyboard.h" -#include "scale.h" #include "voices.h" #include "util.h" +#define LAYOUT_DEFAULT_OCTAVE 3 + /// The bits where row offset lives. Excludes the drum flag bit, so that /// the drums state doesn't get trashed when you use the row offset slider. #define ROW_OFFSET_MASK (0x7F) @@ -26,6 +26,9 @@ typedef enum /// calculating every time a pad is pressed. typedef int8_t PadNotes[GRID_SIZE][GRID_SIZE]; +/// Cache of which pads are highlighted for note mode +typedef int8_t PadHighlights[GRID_SIZE]; + /// Represents a layout of a scale on a grid. Determines which note and octave /// to start from, and the distance in scale steps between rows of the grid. typedef struct @@ -41,6 +44,9 @@ typedef struct /// rows. To tune in 4ths for a chromatic scale, this would be set to 5. int8_t row_offset; + /// How many spaces up/down to translate the view + int8_t offset_horizontal; + int8_t offset_vertical; } Layout; /// Initialize the layout data. @@ -64,11 +70,14 @@ void layout_set_drums(Layout* l); /// Toggles the note in the scale, and updates the layout. void layout_toggle_note(Layout* l, uint8_t note); +/// Toggles the note highlight +void layout_toggle_highlight(Layout* l, uint8_t note_highlight); /// Draws the layout onto the grid. void layout_draw(Layout* l); void layout_draw_scale(Layout* l); void layout_draw_drums(Layout* l); +void layout_draw_transpose_octave_buttons(Layout* l); /// Increases or decreases the root note by a half step. void layout_transpose(Layout* l, int8_t direction); diff --git a/include/scale.h b/include/scale.h index 0f29cdb..d424eb5 100644 --- a/include/scale.h +++ b/include/scale.h @@ -4,6 +4,7 @@ #include "app.h" #include "util.h" +#include "layout.h" /// Represents a musical scale as a subset of 12 chromatic notes. typedef struct @@ -15,6 +16,11 @@ typedef struct /// note) is enabled. [0] (the root note) is always on. uint16_t notes; + /// Bitfield indicating whether the nth note is lighted or not + /// which applies if the highlight setting is turned on + /// Otherwise, white keys are lighted and black keys are dark + uint16_t notes_highlighted; + /// Array of ints indicating how many half steps the nth scale note is /// offset from the root note (only the first num_notes entries are set) int8_t offsets[NUM_NOTES]; @@ -28,6 +34,10 @@ void scale_init(Scale* s); /// root note) is contained in the scale. uint8_t scale_contains_note(Scale* s, uint8_t note); +/// Returns true or false depending on whether then nth note (counted from the +/// root note) is contained in the scale's highlight list +uint8_t scale_contains_highlight(Scale* s, uint8_t note); + /// Sets all the notes of the scale at once. void scale_set_notes(Scale* s, uint16_t notes); @@ -35,4 +45,16 @@ void scale_set_notes(Scale* s, uint16_t notes); /// offsets. void scale_toggle_note(Scale* s, uint8_t note); +/// Toggles the nth note's highlight +void scale_toggle_highlight(Scale* s, uint8_t note); + +extern const uint16_t quick_scale_bitfields[32]; +extern const char* quick_scale_names[32]; + +void quick_scale_draw(); +uint16_t quick_scale_convert_notes(uint16_t in); +uint8_t quick_scale_handle_press(Layout* l, uint8_t index, uint8_t value, uint8_t is_highlight_mode); +uint8_t quick_scale_handle_prev_next(Layout* l, uint8_t index, uint8_t value, uint8_t note_held, uint8_t is_highlight_mode); +void quick_scale_apply(Layout* l, uint8_t is_highlight_mode); + #endif diff --git a/include/seq.h b/include/seq.h index 026fe37..7e2c7ae 100644 --- a/include/seq.h +++ b/include/seq.h @@ -69,9 +69,14 @@ void notes_setup_become_active(); void notes_setup_become_inactive(); void notes_mode_draw(); void notes_setup_draw(); -uint8_t notes_mode_handle_press(uint8_t index, uint8_t value); +uint8_t notes_mode_handle_press(uint8_t index, uint8_t value, uint8_t shift_held, uint8_t note_held); uint8_t notes_setup_handle_press(uint8_t index, uint8_t value); +void scales_setup_become_active(); +void scales_setup_become_inactive(); +void scales_setup_draw(); +uint8_t scales_setup_handle_press(uint8_t index, uint8_t value); + void user_mode_become_active(); void user_mode_become_inactive(); void user_setup_become_active(); @@ -86,6 +91,6 @@ uint8_t user_setup_handle_press(uint8_t index, uint8_t value); ******************************************************************************/ /// Switch the mode or switch between normal and setup modes. -void set_state(LpState st, uint8_t setup); +void set_state(LpState st, uint8_t setup, uint8_t shift_held); #endif diff --git a/include/sequence.h b/include/sequence.h index 9aa42cf..8fcde68 100644 --- a/include/sequence.h +++ b/include/sequence.h @@ -43,7 +43,8 @@ typedef enum SEQ_DRUM_MULTICHANNEL = 1 << 11, // Send each note on its own channel SEQ_FULL_VELOCITY = 1 << 12, // Always send notes at full velocity SEQ_MOD_WHEEL = 1 << 13, // Show the mod wheel in notes mode - SEQ_MOD_CC = 1 << 14 // Send CC from mod wheel instead of aftertouch + SEQ_MOD_CC = 1 << 14, // Send CC from mod wheel instead of aftertouch + NOTE_HIGHLIGHT_ONLY = 1 << 15 // Always show all notes and only highlight the ones in the scale } SequenceFlags; #define SEQ_QUEUED_OFFSET (4) diff --git a/include/util.h b/include/util.h index 31c1426..2d73bb1 100644 --- a/include/util.h +++ b/include/util.h @@ -3,6 +3,7 @@ #define UTIL_H #include "app.h" +#include "colors.h" /******************************************************************************* * Defines/helpers @@ -101,6 +102,31 @@ void modifier_index_assign(uint8_t index, uint8_t value); void clear_leds(); void clear_pad_leds(); +const uint16_t font_4x4[64]; + +typedef struct +{ + uint8_t active; + const char *text; + char text_visible[3]; + uint8_t y; // 0-4 (0 is bottom, 4 is top) + const uint8_t *color; + + uint8_t text_index; + uint8_t millis_per_frame; // Setting for how many milliseconds each 'frame' of the animation should last + uint8_t frame_millis_remain; // Counter for millisecons + uint8_t frame_offset; // Number from 0 to 3 indicating how many pixels left the current text has moved so far + + void (*callback_finished)(void); +} TextDisplayer; + +void text_display_init(TextDisplayer* td); +void text_display_tick(TextDisplayer* td); +void text_display(TextDisplayer* td, const char *text, uint8_t y, const uint8_t *color, void (*callback_finished)(void)); +void text_display_stop(TextDisplayer* td); +void text_draw(TextDisplayer *td); +void text_draw_letter(char c, int8_t x, int8_t y, const uint8_t *color); + #ifdef VIRTUAL_LPP #include #define LP_LOG(x, ...) printf(x "\n", __VA_ARGS__) diff --git a/resources/subsequencely.syx b/resources/subsequencely.syx index 175ce3c..6827572 100644 Binary files a/resources/subsequencely.syx and b/resources/subsequencely.syx differ diff --git a/src/app.c b/src/app.c index 442f762..41fb862 100644 --- a/src/app.c +++ b/src/app.c @@ -36,11 +36,13 @@ #include "app.h" #include "seq.h" +#include "colors.h" /******************************************************************************* * Event handlers ******************************************************************************/ - +int8_t shift_held = 0; +int8_t note_held = 0; void app_surface_event(uint8_t type, uint8_t index, uint8_t value) { #ifndef SEQ_DEBUG @@ -51,37 +53,59 @@ void app_surface_event(uint8_t type, uint8_t index, uint8_t value) sequencer_play_draw(&lp_sequencer); arp_draw(); + if (index == LP_SHIFT) { + if (value > 0) { + shift_held = 1; + } else { + shift_held = 0; + } + } + + if (index == LP_NOTE) { + if (value > 0) { + note_held = 1; + } else { + note_held = 0; + } + } + if (index == LP_SESSION) { if (value > 0) { - set_state(LP_SESSION_MODE, 0); + set_state(LP_SESSION_MODE, 0, 0); } } else if (index == LP_NOTE) { if (value > 0) { - set_state(LP_NOTES_MODE, 0); + set_state(LP_NOTES_MODE, 0, 0); + plot_pad(LP_TRANSPOSE_UP, scale_button_color); + plot_pad(LP_TRANSPOSE_DOWN, scale_button_color); + } + else + { + layout_draw_transpose_octave_buttons(sequencer_get_layout(&lp_sequencer)); } } else if (index == LP_DEVICE) { if (value > 0) { - set_state(LP_SEQUENCER_MODE, 0); + set_state(LP_SEQUENCER_MODE, 0, 0); } } else if (index == LP_USER) { if (value > 0) { - set_state(LP_USER_MODE, 0); + set_state(LP_USER_MODE, 0, 0); } } else if (type == TYPESETUP && value > 0) { - set_state(lp_state, !flag_is_set(lp_flags, LP_IS_SETUP)); + set_state(lp_state, (shift_held && flag_is_set(lp_flags, LP_IS_SETUP)) || !flag_is_set(lp_flags, LP_IS_SETUP), shift_held); } else if (sequencer_handle_play(&lp_sequencer, index, value)) { } // Don't let tap tempo be used when tempo is controlled by clock. @@ -97,7 +121,7 @@ void app_surface_event(uint8_t type, uint8_t index, uint8_t value) } else if (lp_state == LP_NOTES_MODE) { - notes_mode_handle_press(index, value); + notes_mode_handle_press(index, value, shift_held, note_held); notes_mode_draw(); } else if (lp_state == LP_SEQUENCER_MODE) @@ -120,8 +144,15 @@ void app_surface_event(uint8_t type, uint8_t index, uint8_t value) } else if (lp_state == LP_NOTES_MODE) { - notes_setup_handle_press(index, value); - notes_setup_draw(); + if (flag_is_set(lp_flags, LP_IS_SETUP2)) { + if (scales_setup_handle_press(index, value)) + { + scales_setup_draw(); + } + } else { + notes_setup_handle_press(index, value); + notes_setup_draw(); + } } else if (lp_state == LP_SEQUENCER_MODE) { @@ -244,6 +275,9 @@ void app_timer_event() { #ifndef SEQ_DEBUG sequencer_tick(&lp_sequencer, 0); + if (lp_text_displayer.active) { + text_display_tick(&lp_text_displayer); + } lp_tap_tempo_timer++; if (flag_is_set(lp_flags, LP_SQR_DIRTY)) @@ -282,6 +316,8 @@ void app_timer_event() void app_init() { #ifndef SEQ_DEBUG + text_display_init(&lp_text_displayer); + slider_init( &lp_tempo_slider, TEMPO_RESOLUTION, 60 / TEMPO_MUL + 1, @@ -313,10 +349,11 @@ void app_init() lp_mod_wheel = 0; lp_flags = 0; + lp_quick_scale_current = 1; deserialize_app(); - set_state(LP_NOTES_MODE, 0); + set_state(LP_NOTES_MODE, 0, 0); #else for (uint8_t i = 0; i < 100; i++) { diff --git a/src/colors.c b/src/colors.c index 5eb0a07..f86ef40 100644 --- a/src/colors.c +++ b/src/colors.c @@ -2,18 +2,79 @@ #include "app.h" #include "colors.h" -const uint8_t root_note_color[3] = {0x0F, 0x04, 0x04}; -const uint8_t white_note_color[3] = {0x02, 0x02, 0x06}; -const uint8_t black_note_color[3] = {0x00, 0x00, 0x00}; +const uint8_t root_note_color[3] = {0x78, 0x20, 0x20}; +const uint8_t white_note_color[3] = {0x10, 0x10, 0x30}; +const uint8_t black_note_color[3] = {0x04, 0x04, 0x04}; +const uint8_t invalid_note_color[3] = {0x3F, 0x00, 0x00}; const uint8_t c_note_color[3] = {0x02, 0x06, 0x02}; const uint8_t no_note_color[3] = {0x0F, 0x00, 0x00}; +const uint8_t layout_octave_color[3] = {0x00, 0x00, 0xFF}; +const uint8_t layout_transpose_color[3] = {0x00, 0xFF, 0x00}; +const uint8_t scale_button_color[3] = {0x3F, 0x1F, 0x00}; + const uint8_t white_key_color[3] = {0x0F, 0x2F, 0x7F}; const uint8_t black_key_color[3] = {0x3F, 0x00, 0x17}; const uint8_t slider_color[3] = {0x07, 0x7F, 0x0F}; const uint8_t off_color[3] = {0x00, 0x00, 0x00}; const uint8_t on_color[3] = {0xFF, 0xFF, 0xFF}; +const uint8_t note_octave_up_colors[10][3] = { + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x02}, + {0x00, 0x00, 0x08}, + {0x00, 0x00, 0x20}, // Default Octave + {0x00, 0x00, 0x28}, + {0x00, 0x00, 0x30}, + {0x00, 0x00, 0x3F}, + {0x10, 0x00, 0x3F}, + {0x3F, 0x00, 0x20}, + {0x3F, 0x00, 0x00} +}; + +const uint8_t note_octave_down_colors[10][3] = { + {0x00, 0x00, 0x3F}, + {0x00, 0x00, 0x30}, + {0x00, 0x00, 0x28}, + {0x00, 0x00, 0x20}, // Default Octave + {0x00, 0x00, 0x08}, + {0x00, 0x00, 0x02}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00} +}; + +const uint8_t note_transpose_up_colors[12][3] = { + {0x00, 0x00, 0x00}, // 0 (default / no transposition) + {0x00, 0x04, 0x00}, // +1 + {0x00, 0x20, 0x00}, // +2 + {0x00, 0x3F, 0x00}, // +3 + {0x0F, 0x37, 0x00}, // +4 + {0x1F, 0x2F, 0x00}, // +5 + {0x2F, 0x27, 0x00}, //+-6 + {0x00, 0x00, 0x00}, // -5 + {0x00, 0x00, 0x00}, // -4 + {0x00, 0x00, 0x00}, // -3 + {0x00, 0x00, 0x00}, // -2 + {0x00, 0x00, 0x00} // -1 +}; + +const uint8_t note_transpose_down_colors[12][3] = { + {0x00, 0x00, 0x00}, // 0 (default / no transposition) + {0x00, 0x00, 0x00}, // +1 + {0x00, 0x00, 0x00}, // +2 + {0x00, 0x00, 0x00}, // +3 + {0x00, 0x00, 0x00}, // +4 + {0x00, 0x00, 0x00}, // +5 + {0x2F, 0x27, 0x00}, //+-6 + {0x1F, 0x2F, 0x00}, // -5 + {0x0F, 0x37, 0x00}, // -4 + {0x00, 0x3F, 0x00}, // -3 + {0x00, 0x20, 0x00}, // -2 + {0x00, 0x04, 0x00} // -1 +}; + const uint8_t sequence_colors[8][3] = { {0x7F, 0x00, 0x00}, {0x3F, 0x0F, 0x00}, diff --git a/src/grid.c b/src/grid.c index 60b5f04..ebc1a02 100644 --- a/src/grid.c +++ b/src/grid.c @@ -23,7 +23,7 @@ void grid_update_cache(Sequencer* sr, int8_t translation) } else if (translation == -1) { - uint8_t scale_deg = (s->y + GRID_SIZE) % lp_scale.num_notes; + //uint8_t scale_deg = (s->y + GRID_SIZE) % lp_scale.num_notes; for (uint8_t i = GRID_SIZE; i > 0; i--) { note_numbers[i] = note_numbers[i - 1]; @@ -63,7 +63,7 @@ void grid_draw(Sequencer* sr) for (uint8_t x = 0; x < GRID_SIZE; x++) { uint8_t seq_x = grid_to_sequence_x(s, x); - Note* n; + Note* n = 0; uint8_t y; for (uint8_t n_i = 0; n_i < zoom; n_i++) diff --git a/src/keyboard.c b/src/keyboard.c index 2fca073..99bc0a4 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -58,8 +58,9 @@ uint8_t keyboard_index_is_key(Keyboard* k, uint8_t index) return k->index_to_note[index - FIRST_KEYBOARD_PAD] != -1; } -uint8_t keyboard_handle_press(Keyboard* k, uint8_t index, uint8_t value) +uint8_t keyboard_handle_press(Keyboard* k, uint8_t index, uint8_t value, uint8_t y, uint8_t is_highlight_press) { + index -= y * 10; if (value == 0 || !keyboard_index_is_key(k, index)) { return 0; @@ -68,12 +69,23 @@ uint8_t keyboard_handle_press(Keyboard* k, uint8_t index, uint8_t value) uint8_t deg = (k->index_to_note[index - FIRST_KEYBOARD_PAD] - k->layout->root_note + NUM_NOTES) % NUM_NOTES; - layout_toggle_note(k->layout, deg); - + if (is_highlight_press) + { + layout_toggle_highlight(k->layout, deg); + } + else + { + layout_toggle_note(k->layout, deg); + } return 1; } -void keyboard_draw(Keyboard* k) +void keyboard_draw(Keyboard* k, uint8_t draw_highlighted) +{ + keyboard_draw_y(k, draw_highlighted, 0); +} + +void keyboard_draw_y(Keyboard* k, uint8_t draw_highlighted, uint8_t y) { const uint8_t* color = white_key_color; @@ -99,8 +111,11 @@ void keyboard_draw(Keyboard* k) } int8_t deg = (note + NUM_NOTES - k->layout->root_note) % NUM_NOTES; - uint8_t is_in_scale = scale_contains_note(&lp_scale, deg); + if (color != off_color && draw_highlighted && !scale_contains_note(&lp_scale, deg)) { + color = invalid_note_color; + } + uint8_t is_in_scale = draw_highlighted ? scale_contains_highlight(&lp_scale, deg) : scale_contains_note(&lp_scale, deg); uint8_t dimness = !is_in_scale * 4; - plot_pad_dim(i + FIRST_KEYBOARD_PAD, color, dimness); + plot_pad_dim(i + FIRST_KEYBOARD_PAD + (10*y), color, dimness); } } diff --git a/src/layout.c b/src/layout.c index 6cc289e..ffa3053 100644 --- a/src/layout.c +++ b/src/layout.c @@ -2,6 +2,8 @@ #include "data.h" #include "layout.h" +#include "scale.h" + #define DRUM_RANGE (4 * NUM_NOTES + (16 - NUM_NOTES)) #define DRUM_SIZE (GRID_SIZE / 2) @@ -9,8 +11,10 @@ void layout_init(Layout* l) { l->root_note = 0; - l->octave = 2; + l->octave = LAYOUT_DEFAULT_OCTAVE; l->row_offset = 5; + l->offset_horizontal = 0; + l->offset_vertical = 0; } /******************************************************************************* @@ -86,28 +90,26 @@ void layout_assign_drums(Layout* l) void layout_assign_scale(Layout* l) { - uint8_t start_scale_deg = 0; - uint8_t octave = l->octave; + int8_t row_start_scale_deg = l->offset_horizontal + (l->row_offset * l->offset_vertical); + int8_t octave; - for (uint8_t y = 0; y < GRID_SIZE; y++) + for (int8_t y = 0; y < GRID_SIZE; y++) { - for (uint8_t x = 0; x < GRID_SIZE; x++) + for (int8_t x = 0; x < GRID_SIZE; x++) { - uint8_t scale_deg = start_scale_deg + x; + int8_t scale_deg = row_start_scale_deg + x; octave = l->octave + scale_deg / lp_scale.num_notes; - - if (scale_deg >= lp_scale.num_notes) - { - scale_deg = scale_deg % lp_scale.num_notes; + if (scale_deg < 0) { + octave -= 1; } - - uint8_t note_number = l->root_note + octave * NUM_NOTES; - note_number += lp_scale.offsets[scale_deg]; - - lp_pad_notes[y][x] = note_number; + scale_deg %= lp_scale.num_notes; + while (scale_deg < 0) { + scale_deg += lp_scale.num_notes; + } + lp_pad_notes[y][x] = l->root_note + (octave * NUM_NOTES) + lp_scale.offsets[scale_deg]; + lp_pad_highlights[y][x] = scale_contains_highlight(&lp_scale, lp_scale.offsets[scale_deg]); } - - start_scale_deg += l->row_offset; + row_start_scale_deg += l->row_offset; } } @@ -117,15 +119,59 @@ void layout_toggle_note(Layout* l, uint8_t note) layout_assign_pads(l); } +void layout_toggle_highlight(Layout* l, uint8_t note_highlight) +{ + scale_toggle_highlight(&lp_scale, note_highlight); + layout_assign_pads(l); +} + +void layout_shift_view(Layout *l, int8_t delta_x, int8_t delta_y, int8_t reset) +{ + if (reset) { + if (delta_x != 0) + { + l->offset_horizontal = 0; + } + if (delta_y != 0) + { + l->offset_vertical = 0; + } + } else { + l->offset_horizontal = clamp(l->offset_horizontal + delta_x, -4, 4); + l->offset_vertical = clamp(l->offset_vertical + delta_y, -4, 4); + } + layout_assign_pads(l); +} + void layout_transpose(Layout* l, int8_t direction) { - l->root_note = clamp(l->root_note + direction, 0, NUM_NOTES - 1); + if (direction == 0) { + l->root_note = 0; + } + else if (direction == -1 && l->root_note == 0 && l->octave > 0) + { + l->octave -= 1; + l->root_note = NUM_NOTES -1; + } + else if (direction == 1 && l->root_note == NUM_NOTES -1 && l->octave < NUM_OCTAVES - 1) + { + l->octave += 1; + l->root_note = 0; + } + else + { + l->root_note = clamp(l->root_note + direction, 0, NUM_NOTES - 1); + } layout_assign_pads(l); } void layout_transpose_octave(Layout* l, int8_t direction) { - l->octave = clamp(l->octave + direction, 0, NUM_OCTAVES - 1); + if (direction == 0) { + l->octave = LAYOUT_DEFAULT_OCTAVE; + } else { + l->octave = clamp(l->octave + direction, 0, NUM_OCTAVES - 1); + } layout_assign_pads(l); } @@ -183,19 +229,18 @@ void layout_light_scale(Layout* l, uint8_t note_number, uint8_t on) { uint8_t index = FIRST_PAD; const uint8_t* color = off_color; + uint8_t dimness = 3; + uint8_t draw_using_highlights = flag_is_set(sequencer_get_active(&lp_sequencer)->flags, NOTE_HIGHLIGHT_ONLY); if (on) { color = on_color; + dimness = 0; } else if (layout_is_root_note(l, note_number)) { color = root_note_color; } - else if (note_number % NUM_NOTES == 0) - { - color = c_note_color; - } else if (diatonic_notes[note_number % NUM_NOTES]) { color = white_note_color; @@ -205,6 +250,14 @@ void layout_light_scale(Layout* l, uint8_t note_number, uint8_t on) color = black_note_color; } + if (note_number < 0) { + color = invalid_note_color; + } + else if (note_number % NUM_NOTES == 0) + { + dimness = 0; + } + for (uint8_t y = 0; y < GRID_SIZE; y++) { if (lp_pad_notes[y][GRID_SIZE - 1] < note_number) @@ -225,7 +278,19 @@ void layout_light_scale(Layout* l, uint8_t note_number, uint8_t on) continue; } - plot_pad(coord_to_index(x, y), color); + if (draw_using_highlights && !layout_is_root_note(l, note_number)) + { + if (lp_pad_highlights[y][x]) + { + color = white_note_color; + } + else + { + color = black_note_color; + } + } + + plot_pad_dim(coord_to_index(x, y), color, dimness); index++; } @@ -237,35 +302,71 @@ void layout_light_scale(Layout* l, uint8_t note_number, uint8_t on) /******************************************************************************* * Event handling functions ******************************************************************************/ - +uint8_t last_pressed = 0; uint8_t layout_handle_transpose(Layout* l, uint8_t index, uint8_t value) { if (value == 0) { + last_pressed = 0; return 0; } if (index == LP_TRANSPOSE_UP) { - layout_transpose(l, 1); + if (!modifier_held(LP_SHIFT)) { + // Transposing note values in the next/previous musical key onto the same view + if (last_pressed == LP_TRANSPOSE_DOWN) { + layout_transpose(l, 0); // Reset to default + } else { + layout_transpose(l, 1); + } + } else { + // Shifting the "view" horizontally + layout_shift_view(l, 1, 0, last_pressed == LP_TRANSPOSE_DOWN); + } } else if (index == LP_TRANSPOSE_DOWN) { - layout_transpose(l, -1); + if (!modifier_held(LP_SHIFT)) { + if (last_pressed == LP_TRANSPOSE_UP) { + layout_transpose(l, 0); // Reset to default + } else { + layout_transpose(l, -1); + } + } else { + layout_shift_view(l, -1, 0, last_pressed == LP_TRANSPOSE_UP); + } } else if (index == LP_OCTAVE_UP) { - layout_transpose_octave(l, 1); + if (!modifier_held(LP_SHIFT)) { + if (last_pressed == LP_OCTAVE_DOWN) { + layout_transpose_octave(l, 0); // Reset to default + } else { + layout_transpose_octave(l, 1); + } + } else { + layout_shift_view(l, 0, 1, last_pressed == LP_OCTAVE_DOWN); + } } else if (index == LP_OCTAVE_DOWN) { - layout_transpose_octave(l, -1); + if (!modifier_held(LP_SHIFT)) { + if (last_pressed == LP_OCTAVE_UP) { + layout_transpose_octave(l, 0); // Reset to default + } else { + layout_transpose_octave(l, -1); + } + } else { + layout_shift_view(l, 0, -1, last_pressed == LP_OCTAVE_UP); + } } else { + last_pressed = 0; return 0; } - + last_pressed = index; return 1; } @@ -289,21 +390,30 @@ void layout_draw(Layout* l) void layout_draw_scale(Layout* l) { uint8_t index = FIRST_PAD; + uint8_t draw_using_highlights = flag_is_set(sequencer_get_active(&lp_sequencer)->flags, NOTE_HIGHLIGHT_ONLY); for (uint8_t y = 0; y < GRID_SIZE; y++) { for (uint8_t x = 0; x < GRID_SIZE; x++) { const uint8_t* color = off_color; + uint8_t dimness = 3; int8_t note_number = lp_pad_notes[y][x]; if (layout_is_root_note(l, note_number)) { color = root_note_color; } - else if (note_number % NUM_NOTES == 0) + else if (draw_using_highlights) { - color = c_note_color; + if (lp_pad_highlights[y][x]) + { + color = white_note_color; + } + else + { + color = black_note_color; + } } else if (diatonic_notes[note_number % NUM_NOTES]) { @@ -314,12 +424,31 @@ void layout_draw_scale(Layout* l) color = black_note_color; } - plot_pad(index, color); + if (note_number < 0) { + color = invalid_note_color; + } + else if (note_number % NUM_NOTES == 0) + { + dimness = 0; + } + + plot_pad_dim(index, color, dimness); index++; } index += ROW_GAP; } + + layout_draw_transpose_octave_buttons(l); + +} + +void layout_draw_transpose_octave_buttons(Layout* l) +{ + plot_pad(LP_OCTAVE_UP, note_octave_up_colors[l->octave]); + plot_pad(LP_OCTAVE_DOWN, note_octave_down_colors[l->octave]); + plot_pad(LP_TRANSPOSE_UP, note_transpose_up_colors[l->root_note]); + plot_pad(LP_TRANSPOSE_DOWN, note_transpose_down_colors[l->root_note]); } void layout_draw_drums(Layout* l) diff --git a/src/scale.c b/src/scale.c index 9884e1a..ef82f5e 100644 --- a/src/scale.c +++ b/src/scale.c @@ -1,11 +1,13 @@ #include "scale.h" - +#include "data.h" +#include "seq.h" void scale_init(Scale* s) { s->num_notes = NUM_NOTES; s->notes = 0x0FFF; + s->notes_highlighted = 0x0AB5; for (uint8_t i = 0; i < NUM_NOTES; i++) { @@ -42,6 +44,11 @@ uint8_t scale_contains_note(Scale* s, uint8_t note) return flag_is_set(s->notes, 1 << note); } +uint8_t scale_contains_highlight(Scale* s, uint8_t note) +{ + return flag_is_set(s->notes_highlighted, 1 << note); +} + void scale_set_notes(Scale* s, uint16_t notes) { s->notes = notes; @@ -56,6 +63,11 @@ void scale_set_notes(Scale* s, uint16_t notes) scale_update_offsets(s); } +void scale_set_highlights(Scale* s, uint16_t notes_highlighted) +{ + s->notes_highlighted = notes_highlighted; +} + void scale_toggle_note(Scale* s, uint8_t note) { if (note < 1 || note >= NUM_NOTES) @@ -79,4 +91,173 @@ void scale_toggle_note(Scale* s, uint8_t note) scale_update_offsets(s); } +void scale_toggle_highlight(Scale* s, uint8_t note) +{ + if (note < 1 || note >= NUM_NOTES) + { + return; + } + s->notes_highlighted = toggle_flag(s->notes_highlighted, 1 << note); +} + +const uint16_t white_and_black_key_hightlight = 0b1010110101010000; +const uint16_t all_notes_available = 0b1111111111110000; +/// Semitone bitmaps for each of the 32 scales built in +const uint16_t quick_scale_bitfields[32] = { + 0b1011010110100000, //Minor + 0b1010110101010000, //Major + 0b1011010101100000, //Dorian + 0b1101010110100000, //Phrygian + 0b1010110101100000, //Mixolydian + 0b1011010101010000, //Melodic Minor (ascending) + 0b1011010110010000, //Harmonic Minor + 0b1011110101100000, //BeBop Dorian + + 0b1001011100100000, //Blues + 0b1001010100100000, //Minor Pentatonic + 0b1011001110010000, //Hungarian Minor + 0b1011001101100000, //Ukranian Dorian + 0b1100101101010000, //Marva + 0b1101011100010000, //Todi + 0b1010101010100000, //Whole Tone + 0b1111111111110000, //Chromatic + + 0b1010101101010000, //Lydian + 0b1101011010100000, //Locrain + 0b1010100101000000, //Major Pentatonic + 0b1100110110100000, //Phyrigian Dominant + 0b1101101101100000, //Half-Whole Diminished + 0b1010110101110000, //Mixolydian Bebop + 0b1101101010100000, //Super Locrian + 0b1011000110000000, //Hirajoshi + + 0b1100010100100000, //In Sen + 0b1010010101000000, //Yo scale + 0b1100011000100000, //Iwato + 0b1011011011010000, //Whole Half + 0b1011010110110000, //Bebop Minor + 0b1011100101000000, //Major blues + 0b1011000101000000, //Kumoi + 0b1010110111010000 //BeBop Major +}; + +const char* quick_scale_names[32] = { + "MINOR", + "MAJOR", + "DORIAN", + "PHRYGIAN", + "MIXOLYDIAN", + "MELODIC MINOR", + "HARMONIC MINOR", + "BEBOP DORIAN", + + "BLUES", + "MINOR PENTATONIC", + "HUNGARIAN MINOR", + "UKRANIAN DORIAN", + "MARVA", + "TODI", + "WHOLE TONE", + "CHROMATIC", + + "LYDIAN", + "LOCRAIN", + "MAJOR PENTATONIC", + "PHYRIGIAN DOMINANT", + "HALF-WHOLE DIMINISHED", + "MIXOLYDIAN BEBOP", + "SUPER LOCRAIN", + "HIRAJOSHI", + + "IN SEN", + "YO SCALE", + "IWATO", + "WHOLE HALF", + "BEBOP MINOR", + "MAJOR BLUES", + "KUMOI", + "BEBOP MAJOR" +}; + +void quick_scale_draw() { + uint8_t scale_number = 0; + for (uint8_t y = 7; y >= 4; y--) { + for (uint8_t x = 0; x < 8; x++) { + plot_pad_dim(coord_to_index(x, y), scale_button_color, scale_number == lp_quick_scale_current ? 0 : 2); + scale_number += 1; + } + } +} + +uint16_t quick_scale_convert_notes(uint16_t in) { + uint16_t out = in; + int s = sizeof(in) * 8 - 1; // extra shift needed at end + for (in >>= 1; in; in >>= 1) { + out <<= 1; + out |= in & 1; + s--; + } + out <<= s; // shift when v's highest bits are zero + return out; +} + +void quick_scale_apply(Layout* l, uint8_t is_highlight_mode) { + if (lp_quick_scale_current < 0) + { + lp_quick_scale_current = 0; + } + if (lp_quick_scale_current > 31) + { + lp_quick_scale_current = 31; + } + + if (is_highlight_mode) + { + scale_set_notes(&lp_scale, quick_scale_convert_notes(all_notes_available)); + scale_set_highlights(&lp_scale, quick_scale_convert_notes(quick_scale_bitfields[lp_quick_scale_current])); + } + else + { + scale_set_notes(&lp_scale, quick_scale_convert_notes(quick_scale_bitfields[lp_quick_scale_current])); + scale_set_highlights(&lp_scale, quick_scale_convert_notes(white_and_black_key_hightlight)); + } + + layout_assign_pads(l); +} + +uint8_t quick_scale_handle_press(Layout* l, uint8_t index, uint8_t value, uint8_t is_highlight_mode) +{ + if (value == 0 || index % 10 < 1 || index % 10 > 9 || index <= 50 || index >= 90) + { + return 0; + } + lp_quick_scale_current = (8 - index/10)*8 + (index % 10 - 1); + quick_scale_apply(l, is_highlight_mode); + if (value <= 60) { + text_display(&lp_text_displayer, quick_scale_names[lp_quick_scale_current], 4, on_color, &scales_setup_draw); + } + return 1; +} + +uint8_t quick_scale_handle_prev_next(Layout* l, uint8_t index, uint8_t value, uint8_t note_held, uint8_t is_highlight_mode) +{ + if (value == 0 || !note_held) + { + return 0; + } + else if (index == LP_TRANSPOSE_DOWN) + { + lp_quick_scale_current -= 1; + } + else if (index == LP_TRANSPOSE_UP) + { + lp_quick_scale_current += 1; + } + else + { + return 0; + } + quick_scale_apply(l, is_highlight_mode); + return 1; +} \ No newline at end of file diff --git a/src/seq.c b/src/seq.c index d2e6a6f..d25781b 100644 --- a/src/seq.c +++ b/src/seq.c @@ -6,8 +6,8 @@ ******************************************************************************/ // Global settings -uint8_t lp_midi_port = USBMIDI; -uint8_t lp_rcv_clock_port = USBMIDI; +uint8_t lp_midi_port = DINMIDI; +uint8_t lp_rcv_clock_port = DINMIDI; // Program state LpState lp_state = LP_NUM_MODES; @@ -26,8 +26,12 @@ Note* lp_note_storage = (Note*)(lp_buffer + sizeof(uint32_t)) + NOTE_BANK_SIZE; Scale lp_scale; Voices lp_voices; PadNotes lp_pad_notes; +PadNotes lp_pad_highlights; Sequencer lp_sequencer; +TextDisplayer lp_text_displayer; +int8_t lp_quick_scale_current; + // UI Slider lp_tempo_slider; Slider lp_swing_slider; @@ -350,7 +354,7 @@ void notes_setup_become_active() void notes_setup_become_inactive() { - + } void notes_mode_draw() @@ -363,7 +367,7 @@ void notes_setup_draw() Sequence* s = sequencer_get_active(&lp_sequencer); Layout* l = &s->layout; - keyboard_draw(&lp_keyboard); + keyboard_draw(&lp_keyboard, flag_is_set(s->flags, NOTE_HIGHLIGHT_ONLY)); slider_draw(&lp_row_offset_slider, ROW_OFFSET_POS, ROW_OFFSET_COLOR); checkbox_draw(s->flags, SEQ_RECORD_CONTROL, CONTROL_CHECKBOX_POS); @@ -372,6 +376,7 @@ void notes_setup_draw() checkbox_draw(s->flags, SEQ_FULL_VELOCITY, VELOCITY_CHECKBOX_POS); checkbox_draw(s->flags, SEQ_MOD_WHEEL, MOD_WHEEL_CHECKBOX_POS); checkbox_draw(s->flags, SEQ_MOD_CC, MOD_CC_CHECKBOX_POS); + checkbox_draw(s->flags, NOTE_HIGHLIGHT_ONLY, NOTE_HIGHLIGHT_ONLY_POS); number_draw(s->control_code, CC_POS, CC_BITS, CC_COLOR); @@ -386,12 +391,21 @@ void notes_setup_draw() CC_OFFSET_COLOR); } -uint8_t notes_mode_handle_press(uint8_t index, uint8_t value) +uint8_t notes_mode_handle_press(uint8_t index, uint8_t value, uint8_t shift_held, uint8_t note_held) { Sequence* s = sequencer_get_active(&lp_sequencer); Layout* l = &s->layout; - if (layout_handle_transpose(l, index, value)) + if (note_held) + { + if (quick_scale_handle_prev_next(l, index, value, note_held, flag_is_set(s->flags, NOTE_HIGHLIGHT_ONLY))) + { + sequence_kill_voices(s, 0); + layout_draw(l); + keyboard_update_indices(&lp_keyboard); + } + } + else if (layout_handle_transpose(l, index, value)) { sequence_kill_voices(s, 0); layout_draw(l); @@ -441,6 +455,12 @@ uint8_t notes_setup_handle_press(uint8_t index, uint8_t value) if (slider_handle_press(&lp_row_offset_slider, index, value, ROW_OFFSET_POS)) { layout_set_row_offset(l, lp_row_offset_slider.value + 1); + } + else if (checkbox_handle_press( + s->flags, NOTE_HIGHLIGHT_ONLY, + index, value, NOTE_HIGHLIGHT_ONLY_POS)) + { + } else if (checkbox_handle_press( s->flags, SEQ_RECORD_CONTROL, @@ -519,7 +539,73 @@ uint8_t notes_setup_handle_press(uint8_t index, uint8_t value) { keyboard_update_indices(&lp_keyboard); } - else if (keyboard_handle_press(&lp_keyboard, index, value)) { } + else if (keyboard_handle_press(&lp_keyboard, index, value, 0, flag_is_set(s->flags, NOTE_HIGHLIGHT_ONLY))) + { + + } + else + { + return 0; + } + + return 1; +} + + +void scales_setup_become_active() { + +} + +void scales_setup_become_inactive() { + text_display_stop(&lp_text_displayer); +} + +void scales_setup_draw() { + Sequence* s = sequencer_get_active(&lp_sequencer); + //Layout* l = &s->layout; + + quick_scale_draw(); + keyboard_draw_y(&lp_keyboard, 0, 0); + keyboard_draw_y(&lp_keyboard, 1, 2); + checkbox_draw(s->flags, NOTE_HIGHLIGHT_ONLY, SCALE_HIGHLIGHT_ONLY_POS); + checkbox_draw_inv(s->flags, NOTE_HIGHLIGHT_ONLY, SCALE_HIGHLIGHT_ONLY_INV_POS); +} + +uint8_t scales_setup_handle_press(uint8_t index, uint8_t value) { + Sequence* s = sequencer_get_active(&lp_sequencer); + Layout* l = &s->layout; + + if (checkbox_handle_press( + s->flags, NOTE_HIGHLIGHT_ONLY, + index, value, SCALE_HIGHLIGHT_ONLY_POS)) + { + + } + else if (checkbox_handle_press( + s->flags, NOTE_HIGHLIGHT_ONLY, + index, value, SCALE_HIGHLIGHT_ONLY_INV_POS)) + { + + } + else if (quick_scale_handle_press(lp_keyboard.layout, index, value, flag_is_set(s->flags, NOTE_HIGHLIGHT_ONLY))) + { + if (value > 60) { + set_state(LP_NOTES_MODE, 0, 0); + return 0; + } + } + else if (layout_handle_transpose(l, index, value)) + { + keyboard_update_indices(&lp_keyboard); + } + else if (keyboard_handle_press(&lp_keyboard, index, value, 0, 0)) + { + + } + else if (keyboard_handle_press(&lp_keyboard, index, value, 2, 1)) + { + + } else { return 0; @@ -528,6 +614,7 @@ uint8_t notes_setup_handle_press(uint8_t index, uint8_t value) return 1; } + void user_mode_become_active() { @@ -585,7 +672,7 @@ uint8_t user_setup_handle_press(uint8_t index, uint8_t value) * State management ******************************************************************************/ -void set_state(LpState st, uint8_t setup) +void set_state(LpState st, uint8_t setup, uint8_t shift_held) { if (lp_state == st && flag_is_set(lp_flags, LP_IS_SETUP) == setup) { @@ -607,7 +694,11 @@ void set_state(LpState st, uint8_t setup) { if (flag_is_set(lp_flags, LP_IS_SETUP)) { - notes_setup_become_inactive(); + if (flag_is_set(lp_flags, LP_IS_SETUP2)) { + scales_setup_become_inactive(); + } else { + notes_setup_become_inactive(); + } } else { @@ -662,9 +753,15 @@ void set_state(LpState st, uint8_t setup) if (setup) { - notes_setup_become_active(); - plot_setup(on_color); - notes_setup_draw(); + if (shift_held) { + scales_setup_become_active(); + plot_setup(on_color); + scales_setup_draw(); + } else { + notes_setup_become_active(); + plot_setup(on_color); + notes_setup_draw(); + } } else { @@ -712,6 +809,7 @@ void set_state(LpState st, uint8_t setup) lp_state = st; lp_flags = assign_flag(lp_flags, LP_IS_SETUP, setup); + lp_flags = assign_flag(lp_flags, LP_IS_SETUP2, setup && shift_held); } diff --git a/src/sequence.c b/src/sequence.c index 26e92bd..33d7183 100644 --- a/src/sequence.c +++ b/src/sequence.c @@ -23,7 +23,7 @@ void sequence_init(Sequence* s, uint8_t channel, Note* notes) s->x = 0; s->y = 3 * NUM_NOTES; s->zoom = 0; - s->flags = 0x00; + s->flags = 0x8000; // Make note highlight mode default s->notes = notes; sequence_clear_notes(s); diff --git a/src/util.c b/src/util.c index d011f5a..81c626c 100644 --- a/src/util.c +++ b/src/util.c @@ -91,3 +91,171 @@ void modifier_index_assign(uint8_t index, uint8_t value) lp_modifiers = assign_flag(lp_modifiers, flag, value); } + +const uint16_t font_4x4[64] = { + 0b0000000000000000, // space + 0b0100010000000100, // ! + 0b1010101000000000, // " + 0b1010000010100000, // # + 0b0100111011100100, // $ + 0b1001001001001001, // % + 0b0010011110100110, // & + 0b0100010000000000, // ' + 0b0010010001000010, // ( + 0b0100001000100100, // ) + 0b1010010010100000, // * + 0b0100111001000000, // + + 0b0000000000100100, // , + 0b0000111000000000, // - + 0b0000000000000100, // . + 0b0010010001001000, // / (forward slash) + 0b0100101010100100, // 0 + 0b1100010001000100, // 1 + 0b0100101001001110, // 2 + 0b1110011000101110, // 3 + 0b1010111000100010, // 4 + 0b1110100001101110, // 5 + 0b1000110010101110, // 6 + 0b1110001001000100, // 7 + 0b1110111011101110, // 8 + 0b1110101001100010, // 9 + 0b0100000001000000, // : + 0b0100000001001000, // ; + 0b0010110000100000, // < + 0b1110000011100000, // = + 0b1000011010000000, // > + 0b0100101001100100, // ? + + 0b0111101110000110, // @ + 0b1110101011101010, // A + 0b1000111010101110, // B + 0b0110100010000110, // C + 0b1100101010101100, // D + 0b1110100011001110, // E + 0b1110100011001000, // F + 0b0110100010100110, // G + 0b1010101011101010, // H + 0b0100010001000100, // I + 0b0010001000100110, // J + 0b1010110010101010, // K + 0b0100010001000110, // L + 0b1110111010101010, // M + 0b1010111011101010, // N + 0b1110101010101110, // O + 0b1110101011101000, // P + 0b1110101010101111, // Q + 0b1100101011001010, // R + 0b1110100000101110, // S + 0b1110010001000100, // T + 0b1010101010101110, // U + 0b1010101010100100, // V + 0b1010101011101110, // W + 0b1010010001001010, // X + 0b1010101001000100, // Y + 0b1110001010001110, // Z + 0b0110010001000110, // [ + 0b1000010001000010, // \ (backslash) + 0b0110001000100110, // ] + 0b0100101000000000, // ^ + 0b0000000000001110 // _ +}; + +void text_display_init(TextDisplayer* td) { + td->active = 0; + td->text = 0; + td->y = 0; + td->color = on_color; + td->callback_finished = 0; + td->text_visible[0] = ' '; + td->text_visible[1] = ' '; + td->text_visible[2] = ' '; +} + +void text_display_tick(TextDisplayer* td) { + if (!td->active) { + return; + } + if (td->frame_millis_remain > 0) { + td->frame_millis_remain -= 1; + return; + } + // Next frame + td->frame_millis_remain = td->millis_per_frame; + td->frame_offset += 1; + if (td->frame_offset == 4) { + td->frame_offset = 0; + td->text_visible[0] = td->text_visible[1]; + td->text_visible[1] = td->text_visible[2]; + td->text_visible[2] = td->text[td->text_index]; + if (td->text_visible[2] != 0) { + td->text_index += 1; + } + } + + text_draw(td); + + if(td->text_visible[0] == 0) { + td->active = 0; + if(td->callback_finished != 0) { + (*td->callback_finished)(); // Call text display finish callback, if one is set + } + return; + } + +} + +void text_draw(TextDisplayer *td) { + text_draw_letter(td->text_visible[0], -(td->frame_offset), td->y, td->color); + text_draw_letter(td->text_visible[1], 4-(td->frame_offset), td->y, td->color); + text_draw_letter(td->text_visible[2], 8-(td->frame_offset), td->y, td->color); +} + +void text_draw_letter(char c, int8_t x, int8_t y, const uint8_t *color) { + if (x <= -4 || x >= 8 || y > 4) { + return; // Letter not visible + } + if (c < 32) { + c = 32; + } else if (c >= 96) { + c &= 0x5F; // Convert lower case to upper case + } + uint16_t letter = font_4x4[c-32]; + for (int8_t fy = 3; fy >= 0; fy--) { + for (int8_t fx = 0; fx < 4; fx++) { + uint16_t pixel_on = letter & 0x8000; + letter = letter << 1; + if (x + fx < 0 || x + fx >= 8) { + continue; + } + uint8_t coord = coord_to_index(x + fx, y + fy); + if (pixel_on) { + plot_pad(coord, color); + } else { + plot_pad(coord, off_color); + } + } + } +} + +void text_display(TextDisplayer *td, const char *text, uint8_t y, const uint8_t *color, void (*callback_finished)(void)) { + td->active = 0; + + td->text = text; + td->y = y; + td->color = color; + td->text_visible[0] = ' '; + td->text_visible[1] = ' '; + td->text_visible[2] = ' '; + + td->text_index = 0; + td->millis_per_frame = 90; + td->frame_millis_remain = 0; + td->frame_offset = 3; + + td->callback_finished = callback_finished; + td->active = 1; +} + +void text_display_stop(TextDisplayer* td) { + td->active = 0; +}