/****************************************************************************** Copyright (c) 2015, Focusrite Audio Engineering Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Focusrite Audio Engineering Ltd., nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #include "app.h" // Font. u16 font_numbers[10] = { /* * 0: 111 | 1111 0110 1101 1110 * 101 | * 101 | * 101 | * 111 | */ 0xF6DE, /* * 1: 010 | 0101 1001 0010 1110 * 110 | * 010 | * 010 | * 111 | */ 0x592E, /* * 2: 111 | 1110 0111 1100 1110 * 001 | * 111 | * 100 | * 111 | */ 0xE7CE, /* * 3: 111 | 1110 0101 1001 1110 * 001 | * 011 | * 001 | * 111 | */ 0xE59E, /* * 4: 101 | 1011 0110 1111 0010 * 101 | * 101 | * 111 | * 001 | */ 0xB6F2, /* * 5: 111 | 1111 0011 1001 1110 * 100 | * 111 | * 001 | * 111 | */ 0xF39E, /* * 6: 111 | 1111 0011 1101 1110 * 100 | * 111 | * 101 | * 111 | */ 0xF3DE, /* * 7: 111 | 1110 0101 0100 1000 * 001 | * 010 | * 100 | * 100 | */ 0xE548, /* * 8: 111 | 1111 0111 1101 1110 * 101 | * 111 | * 101 | * 111 | */ 0xF7DE, /* * 9: 111 | 1111 0111 1001 0010 * 101 | * 111 | * 001 | * 001 | */ 0xF792, }; // Enums and structs. typedef enum Button { POLY_PAD_ACTIVE = 11, POLY_PAD_STEAL = 11 + 7, POLY_PAD_MIN_CH_0 = 11 + 10 * 7 + 0, POLY_PAD_MIN_CH_1 = 11 + 10 * 7 + 1, POLY_PAD_MIN_CH_2 = 11 + 10 * 7 + 2, POLY_PAD_MIN_CH_3 = 11 + 10 * 7 + 3, POLY_PAD_MIN_CH_4 = 11 + 10 * 7 + 4, POLY_PAD_MIN_CH_5 = 11 + 10 * 7 + 5, POLY_PAD_MIN_CH_6 = 11 + 10 * 7 + 6, POLY_PAD_MIN_CH_7 = 11 + 10 * 7 + 7, POLY_PAD_MIN_CH_8 = 11 + 10 * 6 + 0, POLY_PAD_MIN_CH_9 = 11 + 10 * 6 + 1, POLY_PAD_MIN_CH_A = 11 + 10 * 6 + 2, POLY_PAD_MIN_CH_B = 11 + 10 * 6 + 3, POLY_PAD_MIN_CH_C = 11 + 10 * 6 + 4, POLY_PAD_MIN_CH_D = 11 + 10 * 6 + 5, POLY_PAD_MIN_CH_E = 11 + 10 * 6 + 6, POLY_PAD_MIN_CH_F = 11 + 10 * 6 + 7, POLY_PAD_MAX_CH_0 = 11 + 10 * 5 + 0, POLY_PAD_MAX_CH_1 = 11 + 10 * 5 + 1, POLY_PAD_MAX_CH_2 = 11 + 10 * 5 + 2, POLY_PAD_MAX_CH_3 = 11 + 10 * 5 + 3, POLY_PAD_MAX_CH_4 = 11 + 10 * 5 + 4, POLY_PAD_MAX_CH_5 = 11 + 10 * 5 + 5, POLY_PAD_MAX_CH_6 = 11 + 10 * 5 + 6, POLY_PAD_MAX_CH_7 = 11 + 10 * 5 + 7, POLY_PAD_MAX_CH_8 = 11 + 10 * 4 + 0, POLY_PAD_MAX_CH_9 = 11 + 10 * 4 + 1, POLY_PAD_MAX_CH_A = 11 + 10 * 4 + 2, POLY_PAD_MAX_CH_B = 11 + 10 * 4 + 3, POLY_PAD_MAX_CH_C = 11 + 10 * 4 + 4, POLY_PAD_MAX_CH_D = 11 + 10 * 4 + 5, POLY_PAD_MAX_CH_E = 11 + 10 * 4 + 6, POLY_PAD_MAX_CH_F = 11 + 10 * 4 + 7, POLY_PAD_SEL_CH_0 = 11 + 10 * 3 + 0, POLY_PAD_SEL_CH_1 = 11 + 10 * 3 + 1, POLY_PAD_SEL_CH_2 = 11 + 10 * 3 + 2, POLY_PAD_SEL_CH_3 = 11 + 10 * 3 + 3, POLY_PAD_SEL_CH_4 = 11 + 10 * 3 + 4, POLY_PAD_SEL_CH_5 = 11 + 10 * 3 + 5, POLY_PAD_SEL_CH_6 = 11 + 10 * 3 + 6, POLY_PAD_SEL_CH_7 = 11 + 10 * 3 + 7, POLY_PAD_SEL_CH_8 = 11 + 10 * 2 + 0, POLY_PAD_SEL_CH_9 = 11 + 10 * 2 + 1, POLY_PAD_SEL_CH_A = 11 + 10 * 2 + 2, POLY_PAD_SEL_CH_B = 11 + 10 * 2 + 3, POLY_PAD_SEL_CH_C = 11 + 10 * 2 + 4, POLY_PAD_SEL_CH_D = 11 + 10 * 2 + 5, POLY_PAD_SEL_CH_E = 11 + 10 * 2 + 6, POLY_PAD_SEL_CH_F = 11 + 10 * 2 + 7, } Button; typedef enum Mode { MOD_POLY_MAIN = 0, MOD_POLY_SETUP = 1, } Mode; typedef enum Stealing { POLY_STEAL_OFF = 0, POLY_STEAL_OLD = 1, POLY_STEAL_HIGH = 2, POLY_STEAL_LOW = 3, } Stealing; typedef struct Voice { u8 active; u8 channel; u8 d1; u8 d2; u8 port; u8 age; } Voice; typedef struct State { // Polyphony. u8 active; u8 stealing; u8 ch_min; u8 ch_max; u8 ch_listen[16]; u8 mode; } State; // Globals. u16 frame = 0; u16 n_voices = 0; Voice voices[16] = {0}; State state = {0}; u32 note_counter = 0; u8 midi_in_connected = 0; u8 midi_out_connected = 0; // store ADC frame pointer static const u16 *g_ADC = 0; void poly_stop_all(void) { for (u8 i = 0; i < 16; i++) { Voice *voice = &voices[i]; if (voice->active) { voice->active = 0; hal_send_midi( voice->port, NOTEOFF | voice->channel, voice->d1, voice->d2); } } n_voices = 0; } void poly_active_toggle(void) { poly_stop_all(); state.active = !state.active; } void poly_steal_cycle(void) { poly_stop_all(); state.stealing += 1; if (state.stealing > POLY_STEAL_LOW) { state.stealing = POLY_STEAL_OFF; } } void select_ch_min(u8 target) { if (target <= state.ch_max) { state.ch_min = target; } } void select_ch_max(u8 target) { if (target >= state.ch_min) { state.ch_max = target; } } void select_ch_sel(u8 target) { poly_stop_all(); state.ch_listen[target] = !state.ch_listen[target]; } void app_surface_event(u8 type, u8 index, u8 value) { switch (type) { case TYPEPAD: { switch (state.mode) { case MOD_POLY_MAIN: { if (value) { switch (index) { case POLY_PAD_ACTIVE: { poly_active_toggle(); } break; case POLY_PAD_STEAL: { poly_steal_cycle(); } break; default: break; } } } break; case MOD_POLY_SETUP: { if (value) { switch (index) { // Handle min channel selection. case POLY_PAD_MIN_CH_0: { select_ch_min(0); } break; case POLY_PAD_MIN_CH_1: { select_ch_min(1); } break; case POLY_PAD_MIN_CH_2: { select_ch_min(2); } break; case POLY_PAD_MIN_CH_3: { select_ch_min(3); } break; case POLY_PAD_MIN_CH_4: { select_ch_min(4); } break; case POLY_PAD_MIN_CH_5: { select_ch_min(5); } break; case POLY_PAD_MIN_CH_6: { select_ch_min(6); } break; case POLY_PAD_MIN_CH_7: { select_ch_min(7); } break; case POLY_PAD_MIN_CH_8: { select_ch_min(8); } break; case POLY_PAD_MIN_CH_9: { select_ch_min(9); } break; case POLY_PAD_MIN_CH_A: { select_ch_min(10); } break; case POLY_PAD_MIN_CH_B: { select_ch_min(11); } break; case POLY_PAD_MIN_CH_C: { select_ch_min(12); } break; case POLY_PAD_MIN_CH_D: { select_ch_min(13); } break; case POLY_PAD_MIN_CH_E: { select_ch_min(14); } break; case POLY_PAD_MIN_CH_F: { select_ch_min(15); } break; // Handle max channel selection. case POLY_PAD_MAX_CH_0: { select_ch_max(0); } break; case POLY_PAD_MAX_CH_1: { select_ch_max(1); } break; case POLY_PAD_MAX_CH_2: { select_ch_max(2); } break; case POLY_PAD_MAX_CH_3: { select_ch_max(3); } break; case POLY_PAD_MAX_CH_4: { select_ch_max(4); } break; case POLY_PAD_MAX_CH_5: { select_ch_max(5); } break; case POLY_PAD_MAX_CH_6: { select_ch_max(6); } break; case POLY_PAD_MAX_CH_7: { select_ch_max(7); } break; case POLY_PAD_MAX_CH_8: { select_ch_max(8); } break; case POLY_PAD_MAX_CH_9: { select_ch_max(9); } break; case POLY_PAD_MAX_CH_A: { select_ch_max(10); } break; case POLY_PAD_MAX_CH_B: { select_ch_max(11); } break; case POLY_PAD_MAX_CH_C: { select_ch_max(12); } break; case POLY_PAD_MAX_CH_D: { select_ch_max(13); } break; case POLY_PAD_MAX_CH_E: { select_ch_max(14); } break; case POLY_PAD_MAX_CH_F: { select_ch_max(15); } break; // Handle listen channel selection. case POLY_PAD_SEL_CH_0: { select_ch_sel(0); } break; case POLY_PAD_SEL_CH_1: { select_ch_sel(1); } break; case POLY_PAD_SEL_CH_2: { select_ch_sel(2); } break; case POLY_PAD_SEL_CH_3: { select_ch_sel(3); } break; case POLY_PAD_SEL_CH_4: { select_ch_sel(4); } break; case POLY_PAD_SEL_CH_5: { select_ch_sel(5); } break; case POLY_PAD_SEL_CH_6: { select_ch_sel(6); } break; case POLY_PAD_SEL_CH_7: { select_ch_sel(7); } break; case POLY_PAD_SEL_CH_8: { select_ch_sel(8); } break; case POLY_PAD_SEL_CH_9: { select_ch_sel(9); } break; case POLY_PAD_SEL_CH_A: { select_ch_sel(10); } break; case POLY_PAD_SEL_CH_B: { select_ch_sel(11); } break; case POLY_PAD_SEL_CH_C: { select_ch_sel(12); } break; case POLY_PAD_SEL_CH_D: { select_ch_sel(13); } break; case POLY_PAD_SEL_CH_E: { select_ch_sel(14); } break; case POLY_PAD_SEL_CH_F: { select_ch_sel(15); } break; // Handle active toggle. case POLY_PAD_ACTIVE: { poly_active_toggle(); } break; case POLY_PAD_STEAL: { poly_steal_cycle(); } break; default: break; } } } break; } } break; case TYPESETUP: { if (value) { switch (state.mode) { case MOD_POLY_MAIN: { state.mode = MOD_POLY_SETUP; } break; case MOD_POLY_SETUP: { state.mode = MOD_POLY_MAIN; hal_write_flash(0, (u8*)&state, sizeof(State)); } break; } } } break; } } void app_midi_event(u8 port, u8 status, u8 d1, u8 d2) { if (!state.active) { hal_send_midi(port, status, d1, d2); return; } u8 channel = status & 0x0F; // If the channel isn't set to listen, just passthrough the message. if (!state.ch_listen[channel]) { hal_send_midi(port, status, d1, d2); return; } if ((status & 0xF0) == NOTEON) { if (n_voices == (state.ch_max - state.ch_min + 1)) { u8 selected = 0; switch (state.stealing) { case POLY_STEAL_OFF: { return; } break; case POLY_STEAL_OLD: { u32 age = -1; for (u8 i = state.ch_min; i <= state.ch_max; i++) { Voice *voice = &voices[i]; if (voice->age < age) { age = voice->age; selected = i; } } } break; case POLY_STEAL_HIGH: { u8 pitch = 0; for (u8 i = state.ch_min; i <= state.ch_max; i++) { Voice *voice = &voices[i]; if (voice->d1 >= pitch) { pitch = voice->d1; selected = i; } } } break; case POLY_STEAL_LOW: { u8 pitch = -1; for (u8 i = state.ch_min; i <= state.ch_max; i++) { Voice *voice = &voices[i]; if (voice->d1 < pitch) { pitch = voice->d1; selected = i; } } } break; } Voice *voice = &voices[selected]; voice->active = 0; hal_send_midi(voice->port, NOTEOFF | voice->channel, voice->d1, voice->d2); n_voices--; } // Find if the note was already pressed. for (u8 i = state.ch_min; i <= state.ch_max; i++) { Voice *voice = &voices[i]; // Stop voice if needed. if (voice->active && voice->d1 == d1) { voice->active = 0; hal_send_midi(voice->port, NOTEOFF | voice->channel, voice->d1, voice->d2); break; } } // Find next free voice slot. for (u8 i = state.ch_min; i <= state.ch_max; i++) { Voice *voice = &voices[i]; // Register voice. if (!voice->active) { voice->active = 1; voice->port = port; voice->channel = i; voice->d1 = d1; voice->d2 = d2; hal_send_midi(voice->port, NOTEON | voice->channel, voice->d1, voice->d2); voice->age = note_counter++; break; } } n_voices++; } else if ((status & 0xF0) == NOTEOFF) { for (u8 i = state.ch_min; i <= state.ch_max; i++) { Voice *voice = &voices[i]; // Register voice. if (voice->active && voice->d1 == d1) { voice->active = 0; hal_send_midi(port, NOTEOFF | i, d1, d2); n_voices--; break; } } } else { hal_send_midi(port, status, d1, d2); } } void app_sysex_event(u8 port, u8 * data, u16 count) { // ... } void app_aftertouch_event(u8 index, u8 value) { // ... } void app_cable_event(u8 type, u8 value) { if (type == MIDI_IN_CABLE) { if (value) { midi_in_connected = 1; } else { midi_in_connected = 0; } } else if (type == MIDI_OUT_CABLE) { if (value) { midi_out_connected = 1; } else { midi_out_connected = 0; } } if (midi_in_connected && midi_out_connected) { hal_plot_led(TYPESETUP, 0, 0, value, 0); } else { hal_plot_led(TYPESETUP, 0, value, 0, 0); } } void print_number(u8 n, u16 r, u16 g, u16 b, u8 x, u8 y) { u16 number = font_numbers[n]; for (u8 row = 0; row < 5; row++) { u8 pos = 11 + 10 * (7 - y) - row * 10 + x; u16 cur = number >> (13 - 3 * row); for (u8 i = 0; i < 3; i++) { if ((cur >> (2 - i)) & 0x1) { hal_plot_led(TYPEPAD, pos + i, r, g, b); } } } } void update(void) { // for (int i = 0; i < PAD_COUNT; ++i) { // // Get the first detected button n_voices. // u16 x = (99 * g_ADC[i]) >> 12; // if (x > 3) { // n_voices = x; // return; // } // } // n_voices = 0; } void clear_pads(void) { for (int i = 0; i < PAD_COUNT; ++i) { hal_plot_led(TYPEPAD, ADC_MAP[i], 0, 0, 0); } } void draw_poly_active_button(void) { if (state.active) { hal_plot_led(TYPEPAD, POLY_PAD_ACTIVE, 0, MAXLED, 0); } else { hal_plot_led(TYPEPAD, POLY_PAD_ACTIVE, MAXLED, 0, 0); } } void draw_poly_steal_button(void) { switch (state.stealing) { case POLY_STEAL_OFF: { hal_plot_led(TYPEPAD, POLY_PAD_STEAL, MAXLED, 0, 0); } break; case POLY_STEAL_OLD: { hal_plot_led(TYPEPAD, POLY_PAD_STEAL, 0, 24, 24); } break; case POLY_STEAL_HIGH: { hal_plot_led(TYPEPAD, POLY_PAD_STEAL, 0, 24, 0); } break; case POLY_STEAL_LOW: { hal_plot_led(TYPEPAD, POLY_PAD_STEAL, 0, 0, 24); } break; } } void draw_poly_main(void) { print_number((n_voices / 10) % 10, MAXLED, MAXLED, MAXLED, 0, 0); print_number( n_voices % 10, MAXLED, MAXLED, MAXLED, 4, 0); draw_poly_active_button(); draw_poly_steal_button(); } void draw_min_channel(void) { for (u8 i = 0; i < 8; i++) { if (i == state.ch_min) { hal_plot_led(TYPEPAD, POLY_PAD_MIN_CH_0 + i, MAXLED, MAXLED, MAXLED); continue; } if (state.ch_max < 8 && i > state.ch_max) { hal_plot_led(TYPEPAD, POLY_PAD_MIN_CH_0 + i, 5, 0, 0); continue; } hal_plot_led(TYPEPAD, POLY_PAD_MIN_CH_0 + i, MAXLED / 2, 0, 0); } for (u8 i = 0; i < 8; i++) { if ((i + 8) == state.ch_min) { hal_plot_led(TYPEPAD, POLY_PAD_MIN_CH_8 + i, MAXLED, MAXLED, MAXLED); continue; } if (state.ch_max < 16 && (i + 8) > state.ch_max) { hal_plot_led(TYPEPAD, POLY_PAD_MIN_CH_8 + i, 5, 0, 0); continue; } hal_plot_led(TYPEPAD, POLY_PAD_MIN_CH_8 + i, MAXLED / 2, 0, 0); } } void draw_max_channel(void) { for (u8 i = 0; i < 8; i++) { if (i == state.ch_max) { hal_plot_led(TYPEPAD, POLY_PAD_MAX_CH_0 + i, MAXLED, MAXLED, MAXLED); continue; } if (i < state.ch_min) { hal_plot_led(TYPEPAD, POLY_PAD_MAX_CH_0 + i, 5, 5, 0); continue; } hal_plot_led(TYPEPAD, POLY_PAD_MAX_CH_0 + i, MAXLED / 2, MAXLED / 2, 0); } for (u8 i = 0; i < 8; i++) { if ((i + 8) == state.ch_max) { hal_plot_led(TYPEPAD, POLY_PAD_MAX_CH_8 + i, MAXLED, MAXLED, MAXLED); continue; } if (state.ch_min >= 8 && (i + 8) < state.ch_min) { hal_plot_led(TYPEPAD, POLY_PAD_MAX_CH_8 + i, 5, 5, 0); continue; } hal_plot_led(TYPEPAD, POLY_PAD_MAX_CH_8 + i, MAXLED / 2, MAXLED / 2, 0); } } void draw_sel_channel(void) { for (u8 i = 0; i < 8; i++) { if (state.ch_listen[i]) { hal_plot_led(TYPEPAD, POLY_PAD_SEL_CH_0 + i, 20, 0, 40); } else { hal_plot_led(TYPEPAD, POLY_PAD_SEL_CH_0 + i, 2, 0, 5); } } for (u8 i = 0; i < 8; i++) { if (state.ch_listen[i + 8]) { hal_plot_led(TYPEPAD, POLY_PAD_SEL_CH_8 + i, 20, 0, 40); } else { hal_plot_led(TYPEPAD, POLY_PAD_SEL_CH_8 + i, 2, 0, 5); } } } void draw_poly_setup(void) { draw_min_channel(); draw_max_channel(); draw_sel_channel(); draw_poly_steal_button(); draw_poly_active_button(); } void draw_scene(void) { switch (state.mode) { case MOD_POLY_MAIN: { draw_poly_main(); } break; case MOD_POLY_SETUP: { draw_poly_setup(); } break; } } void render(void) { if (frame++ == 1000 / 10) { clear_pads(); draw_scene(); frame = 0; } } void app_timer_event() { update(); render(); } void app_init(const u16 *adc_raw) { // example - load button states from flash state = (State){ .active = 1, .stealing = POLY_STEAL_OFF, .ch_min = 0, .ch_max = 7, .ch_listen = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, .mode = MOD_POLY_MAIN, }; hal_read_flash(0, (u8*)&state, sizeof(State)); // store off the raw ADC frame pointer for later use g_ADC = adc_raw; }