diff --git a/include/colors.h b/include/colors.h index b4cc95e..134e777 100644 --- a/include/colors.h +++ b/include/colors.h @@ -5,13 +5,24 @@ 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 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..635d9ce 100644 --- a/include/data.h +++ b/include/data.h @@ -74,6 +74,7 @@ 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; // Setup UI elements diff --git a/include/interface.h b/include/interface.h index c15226c..40de8c8 100644 --- a/include/interface.h +++ b/include/interface.h @@ -48,6 +48,7 @@ #define VELOCITY_CHECKBOX_POS (41) #define MOD_WHEEL_CHECKBOX_POS (43) #define MOD_CC_CHECKBOX_POS (53) +#define NOTE_HIGHLIGHT_ONLY_POS (28) #define DRUM_CHECKBOX_POS (45) #define MULTICHANNEL_CHECKBOX_POS (55) #define CONTROL_CHECKBOX_POS (81) diff --git a/include/keyboard.h b/include/keyboard.h index 41143dc..ae3c51c 100644 --- a/include/keyboard.h +++ b/include/keyboard.h @@ -30,10 +30,10 @@ 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 is_highlight_press); /// Draws the keyboard to the grid. -void keyboard_draw(Keyboard* k); +void keyboard_draw(Keyboard* k, uint8_t draw_highlighted); /// 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..cbf658b 100644 --- a/include/layout.h +++ b/include/layout.h @@ -10,6 +10,8 @@ #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 +28,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 @@ -64,6 +69,8 @@ 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); diff --git a/include/scale.h b/include/scale.h index 0f29cdb..bdd6852 100644 --- a/include/scale.h +++ b/include/scale.h @@ -15,6 +15,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 +33,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 +44,7 @@ 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); + #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/resources/subsequencely.syx b/resources/subsequencely.syx index 175ce3c..1c52164 100644 Binary files a/resources/subsequencely.syx and b/resources/subsequencely.syx differ diff --git a/src/colors.c b/src/colors.c index 5eb0a07..08adae6 100644 --- a/src/colors.c +++ b/src/colors.c @@ -2,18 +2,78 @@ #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 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..4e45cae 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -58,7 +58,7 @@ 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 is_highlight_press) { if (value == 0 || !keyboard_index_is_key(k, index)) { @@ -68,12 +68,18 @@ 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) { const uint8_t* color = white_key_color; @@ -99,7 +105,10 @@ 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 (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); } diff --git a/src/layout.c b/src/layout.c index 706d2b1..3aec9ce 100644 --- a/src/layout.c +++ b/src/layout.c @@ -9,7 +9,7 @@ void layout_init(Layout* l) { l->root_note = 0; - l->octave = 2; + l->octave = LAYOUT_DEFAULT_OCTAVE; l->row_offset = 5; } @@ -105,6 +105,7 @@ void layout_assign_scale(Layout* l) note_number += lp_scale.offsets[scale_deg]; lp_pad_notes[y][x] = note_number; + lp_pad_highlights[y][x] = scale_contains_highlight(&lp_scale, lp_scale.offsets[scale_deg]); } start_scale_deg += l->row_offset; @@ -117,9 +118,18 @@ 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_transpose(Layout* l, int8_t direction) { - if (direction == -1 && l->root_note == 0 && l->octave > 0) + 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; @@ -138,7 +148,11 @@ void layout_transpose(Layout* l, int8_t direction) 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); } @@ -196,19 +210,17 @@ 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; 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; @@ -218,6 +230,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) @@ -238,7 +258,7 @@ void layout_light_scale(Layout* l, uint8_t note_number, uint8_t on) continue; } - plot_pad(coord_to_index(x, y), color); + plot_pad_dim(coord_to_index(x, y), color, dimness); index++; } @@ -250,35 +270,53 @@ 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 (last_pressed == LP_TRANSPOSE_DOWN) { + layout_transpose(l, 0); // Reset to default + } else { + layout_transpose(l, 1); + } } else if (index == LP_TRANSPOSE_DOWN) { - layout_transpose(l, -1); + if (last_pressed == LP_TRANSPOSE_UP) { + layout_transpose(l, 0); // Reset to default + } else { + layout_transpose(l, -1); + } } else if (index == LP_OCTAVE_UP) { - layout_transpose_octave(l, 1); + if (last_pressed == LP_OCTAVE_DOWN) { + layout_transpose_octave(l, 0); // Reset to default + } else { + layout_transpose_octave(l, 1); + } } else if (index == LP_OCTAVE_DOWN) { - layout_transpose_octave(l, -1); + if (last_pressed == LP_OCTAVE_UP) { + layout_transpose_octave(l, 0); // Reset to default + } else { + layout_transpose_octave(l, -1); + } } else { + last_pressed = 0; return 0; } - + last_pressed = index; return 1; } @@ -302,21 +340,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]) { @@ -327,12 +374,26 @@ 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; } + + 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..8a93072 100644 --- a/src/scale.c +++ b/src/scale.c @@ -6,6 +6,7 @@ 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 +43,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 +62,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 +90,14 @@ 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); +} + + diff --git a/src/seq.c b/src/seq.c index d2e6a6f..deb4de1 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,6 +26,7 @@ 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; // UI @@ -363,7 +364,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 +373,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); @@ -441,6 +443,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 +527,10 @@ 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, flag_is_set(s->flags, NOTE_HIGHLIGHT_ONLY))) + { + + } else { return 0;