diff --git a/Makefile b/Makefile index ae4679b..acadbd4 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ #Set all your object files (the object files of all the .c files in your project, e.g. main.o my_sub_functions.o ) -OBJ = src/main.o src/serial.o src/slip.o src/command.o src/render.o src/ini.o src/config.o src/input.o src/gamecontrollers.o src/fx_cube.o src/usb.o src/audio.o src/usb_audio.o src/ringbuffer.o src/inprint2.o +OBJ = src/main.o src/serial.o src/slip.o src/command.o src/render.o src/ini.o src/config.o src/input.o src/gamecontrollers.o src/fx_cube.o src/fx_piano.o src/usb.o src/audio.o src/usb_audio.o src/ringbuffer.o src/inprint2.o #Set any dependant header files so that if they are edited they cause a complete re-compile (e.g. main.h some_subfunctions.h some_definitions_file.h ), or leave blank -DEPS = src/serial.h src/slip.h src/command.h src/render.h src/ini.h src/config.h src/input.h src/gamecontrollers.h src/fx_cube.h src/audio.h src/ringbuffer.h src/inline_font.h +DEPS = src/serial.h src/slip.h src/command.h src/render.h src/ini.h src/config.h src/input.h src/gamecontrollers.h src/fx_cube.h src/fx_piano.h src/audio.h src/ringbuffer.h src/inline_font.h #Any special libraries you are using in your project (e.g. -lbcm2835 -lrt `pkg-config --libs gtk+-3.0` ), or leave blank INCLUDES = $(shell pkg-config --libs sdl2 libserialport | sed 's/-mwindows//') diff --git a/src/command.c b/src/command.c index 14a922e..5f622ab 100644 --- a/src/command.c +++ b/src/command.c @@ -152,12 +152,12 @@ int process_command(uint8_t *data, uint32_t size) { break; } - char *hwtype[4] = {"Headless", "Beta M8", "Production M8", "Production M8 Model:02"}; + const char *hw_type[4] = {"Headless", "Beta M8", "Production M8", "Production M8 Model:02"}; static int system_info_printed = 0; if (system_info_printed == 0) { - SDL_Log("** Hardware info ** Device type: %s, Firmware ver %d.%d.%d", hwtype[recv_buf[1]], + SDL_Log("** Hardware info ** Device type: %s, Firmware ver %d.%d.%d", hw_type[recv_buf[1]], recv_buf[2], recv_buf[3], recv_buf[4]); system_info_printed = 1; } diff --git a/src/config.c b/src/config.c index 50848a9..7cb0d68 100644 --- a/src/config.c +++ b/src/config.c @@ -52,6 +52,7 @@ config_params_s init_config() { c.key_jazz_inc_velocity = SDL_SCANCODE_KP_MINUS; c.key_jazz_dec_velocity = SDL_SCANCODE_KP_PLUS; c.key_toggle_audio = SDL_SCANCODE_F12; + c.key_toggle_overlay_fx = SDL_SCANCODE_F11; c.gamepad_up = SDL_CONTROLLER_BUTTON_DPAD_UP; c.gamepad_left = SDL_CONTROLLER_BUTTON_DPAD_LEFT; @@ -86,7 +87,7 @@ void write_config(const config_params_s *conf) { SDL_Log("Writing config file to %s", config_path); - const unsigned int INI_LINE_COUNT = 50; + const unsigned int INI_LINE_COUNT = 51; const unsigned int LINELEN = 50; // Entries for the config file @@ -132,6 +133,7 @@ void write_config(const config_params_s *conf) { snprintf(ini_values[initPointer++], LINELEN, "key_jazz_dec_velocity=%d\n", conf->key_jazz_dec_velocity); snprintf(ini_values[initPointer++], LINELEN, "key_toggle_audio=%d\n", conf->key_toggle_audio); + snprintf(ini_values[initPointer++], LINELEN, "key_toggle_overlay_fx=%d\n", conf->key_toggle_overlay_fx); snprintf(ini_values[initPointer++], LINELEN, "[gamepad]\n"); snprintf(ini_values[initPointer++], LINELEN, "gamepad_up=%d\n", conf->gamepad_up); snprintf(ini_values[initPointer++], LINELEN, "gamepad_left=%d\n", conf->gamepad_left); @@ -281,6 +283,7 @@ void read_key_config(const ini_t *ini, config_params_s *conf) { const char *key_jazz_inc_velocity = ini_get(ini, "keyboard", "key_jazz_inc_velocity"); const char *key_jazz_dec_velocity = ini_get(ini, "keyboard", "key_jazz_dec_velocity"); const char *key_toggle_audio = ini_get(ini, "keyboard", "key_toggle_audio"); + const char *key_toggle_overlay_fx = ini_get(ini, "keyboard", "key_toggle_overlay_fx"); if (key_up) conf->key_up = SDL_atoi(key_up); @@ -320,6 +323,8 @@ void read_key_config(const ini_t *ini, config_params_s *conf) { conf->key_jazz_dec_velocity = SDL_atoi(key_jazz_dec_velocity); if (key_toggle_audio) conf->key_jazz_dec_velocity = SDL_atoi(key_toggle_audio); + if (key_toggle_overlay_fx) + conf->key_jazz_dec_velocity = SDL_atoi(key_toggle_overlay_fx); } void read_gamepad_config(const ini_t *ini, config_params_s *conf) { diff --git a/src/config.h b/src/config.h index 0a65907..20f6edd 100644 --- a/src/config.h +++ b/src/config.h @@ -36,6 +36,7 @@ typedef struct config_params_s { unsigned int key_jazz_inc_velocity; unsigned int key_jazz_dec_velocity; unsigned int key_toggle_audio; + unsigned int key_toggle_overlay_fx; int gamepad_up; int gamepad_left; diff --git a/src/fx_piano.c b/src/fx_piano.c new file mode 100644 index 0000000..12110c4 --- /dev/null +++ b/src/fx_piano.c @@ -0,0 +1,169 @@ +// +// Created by jonne on 9/8/24. +// + +#include "fx_piano.h" +#include "render.h" +#include "inline_font.h" +#include + +struct active_notes { + uint8_t note; + uint8_t sharp; + uint8_t octave; +}; + +static int width = 320; +static int height = 240; +static SDL_Texture *fx_texture; +static uint32_t *framebuffer; +struct active_notes active_notes[8] = {0}; +static int is_initialized = 0; + +static const uint32_t bgcolor = 0xA0000000; + +// Pastel Rainbow from https://colorkit.co/palettes/8-colors/ +uint32_t palette[8] = {0xEFFFADAD, 0xEFFFD6A5, 0xEFFDFFB6, 0xEFCAFFBF, + 0xEF9BF6FF, 0xEFA0C4FF, 0xEFBDB2FF, 0xEFFFC6FF}; + +static int get_active_note_from_channel(int ch) { + if (ch >= 0 && ch < 8) + return active_notes[ch].note + active_notes[ch].sharp + active_notes[ch].octave; + else + return 0; +} + +void fx_piano_init(SDL_Texture *output_texture) { + fx_texture = output_texture; + SDL_QueryTexture(fx_texture, NULL, NULL, &width, &height); + framebuffer = SDL_malloc(sizeof(uint32_t) * width * height); + SDL_memset4(framebuffer, bgcolor, width * height); + is_initialized = 1; +} + +void fx_piano_update() { + //Shift everything up by one row and clear bottom row + for (int y = 1; y < height; y++) { + for (int x = 0; x < width; x++) { + int buffer_pos = x + (y * width); + // Top rows have a fading alpha value + if (y<60 && (framebuffer[buffer_pos] >> 24) > 0x04){ + framebuffer[buffer_pos-width] = framebuffer[buffer_pos]-((0x05 << 24)); + } else { + framebuffer[buffer_pos-width] = framebuffer[buffer_pos]; + } + } + } + + for (int x = 0; x < width; x++) { + framebuffer[width*(height-1)+x] = bgcolor; + framebuffer[x] = bgcolor; + } + + int notes[8]; + for (int i = 0; i < 8; i++) { + notes[i] = get_active_note_from_channel(i); + + int last_row = (height-1)*width; + int spacing = width / 128; + if (notes[i]>0) { + int pos = last_row + (notes[i] * spacing); + uint32_t color = (framebuffer[pos] + palette[i]) | (0xCF <<24); + for (int bar_width = 0; bar_width < spacing; bar_width++) { + framebuffer[pos++] = color; + } + framebuffer[pos] = color | (0x20 << 24); + } + } + + SDL_UpdateTexture(fx_texture, NULL, framebuffer, + width * sizeof(framebuffer[0])); +}; + +void fx_piano_destroy() { + if (is_initialized) { + SDL_free(framebuffer); + is_initialized = 0; + } +} + +/* Converts the note characters displayed on M8 screen to hex values for use in + * visualizers */ +int note_to_hex(int note_char) { + switch (note_char) { + case 45: // - = note off + return 0x00; + case 67: // C + return 0x01; + case 68: // D + return 0x03; + case 69: // E + return 0x05; + case 70: // F + return 0x06; + case 71: // G + return 0x08; + case 65: // A + return 0x0A; + case 66: // B + return 0x0C; + default: + return 0x00; + } +} + +// Updates the active notes information array with the current command's data +void update_active_notes_data(struct draw_character_command *command, int font_mode) { + + int line_spacing; + int character_spacing; + int playback_info_offset_x; + + switch (font_mode) { + case 1: + case 4: + // Note name sidebar not visible all the time on MK1 / MK2 + return; + case 2: + case 3: + line_spacing = 14; + character_spacing = 12; + playback_info_offset_x = 432; + break; + default: + line_spacing = 10; + character_spacing = 8; + playback_info_offset_x = 288; + break; + } + + // Channels are 10 pixels apart, starting from y=70 + int channel_number = command->pos.y / line_spacing - 7; + + + // Note playback information starts from X=288 + if (command->pos.x == playback_info_offset_x) { + active_notes[channel_number].note = note_to_hex(command->c); + } + + // Sharp notes live at x = 296 + if (command->pos.x == playback_info_offset_x + character_spacing && !strcmp((char *)&command->c, "#")) + active_notes[channel_number].sharp = 1; + else + active_notes[channel_number].sharp = 0; + + // Note octave, x = 304 + if (command->pos.x == playback_info_offset_x + 2*character_spacing) { + int8_t octave = command->c - 48; + + // Octaves A-B + if (octave == 17 || octave == 18) + octave -= 7; + + // If octave hasn't been applied to the note yet, do it + if (octave > 0) + active_notes[channel_number].octave = octave * 0x0C; + else + active_notes[channel_number].octave = 0; + } +} \ No newline at end of file diff --git a/src/fx_piano.h b/src/fx_piano.h new file mode 100644 index 0000000..8646f5b --- /dev/null +++ b/src/fx_piano.h @@ -0,0 +1,17 @@ +// +// Created by jonne on 9/8/24. +// + +#ifndef FX_PIANO_H +#define FX_PIANO_H + +#include "SDL_render.h" +#include "command.h" + +void fx_piano_init(SDL_Texture *output_texture); +void fx_piano_update(); +void fx_piano_destroy(); + +void update_active_notes_data(struct draw_character_command *command, int font_mode); + +#endif //FX_PIANO_H diff --git a/src/input.c b/src/input.c index 97c0414..3de60fd 100644 --- a/src/input.c +++ b/src/input.c @@ -177,6 +177,8 @@ static input_msg_s handle_normal_keys(const SDL_Event *event, const config_param key = (input_msg_s){special, msg_reset_display, 0, 0}; } else if (event->key.keysym.scancode == conf->key_toggle_audio) { key = (input_msg_s){special, msg_toggle_audio, 0, 0}; + } else if (event->key.keysym.scancode == conf->key_toggle_overlay_fx) { + key = (input_msg_s){special, msg_toggle_overlay_fx, 0, 0}; } else { key.value = 0; } diff --git a/src/input.h b/src/input.h index ad1bf2c..a56b7d0 100644 --- a/src/input.h +++ b/src/input.h @@ -36,7 +36,8 @@ typedef enum input_type_t { normal, keyjazz, special } input_type_t; typedef enum special_messages_t { msg_quit = 1, msg_reset_display = 2, - msg_toggle_audio = 3 + msg_toggle_audio = 3, + msg_toggle_overlay_fx = 4 } special_messages_t; typedef struct input_msg_s { diff --git a/src/main.c b/src/main.c index c16bca4..fca5d97 100644 --- a/src/main.c +++ b/src/main.c @@ -12,8 +12,8 @@ #include "audio.h" #include "command.h" #include "config.h" -#include "input.h" #include "gamecontrollers.h" +#include "input.h" #include "render.h" #include "serial.h" #include "slip.h" @@ -220,6 +220,9 @@ int main(const int argc, char *argv[]) { case msg_toggle_audio: toggle_audio(conf.audio_buffer_size, conf.audio_device_name); break; + case msg_toggle_overlay_fx: + toggle_special_fx(); + break; default: break; } @@ -284,9 +287,7 @@ int main(const int argc, char *argv[]) { // exit, clean up SDL_Log("Shutting down\n"); - if (conf.audio_enabled == 1) { - audio_destroy(); - } + audio_destroy(); gamecontrollers_close(); close_renderer(); close_serial_port(); diff --git a/src/render.c b/src/render.c index 1acf6b7..563b7c4 100644 --- a/src/render.c +++ b/src/render.c @@ -9,6 +9,7 @@ #include "SDL2_inprint.h" #include "command.h" #include "fx_cube.h" +#include "fx_piano.h" #include "font1.h" #include "font2.h" @@ -19,7 +20,7 @@ SDL_Window *win; SDL_Renderer *rend; -SDL_Texture *maintexture; +SDL_Texture *main_texture, *m8_texture, *fx_texture; SDL_Color background_color = (SDL_Color){.r = 0x00, .g = 0x00, .b = 0x00, .a = 0x00}; static uint32_t ticks_fps; @@ -29,6 +30,9 @@ static int m8_hardware_model = 0; static int screen_offset_y = 0; static int text_offset_y = 0; static int waveform_max_height = 24; +static int note_column_y_min = 70; +static int note_column_y_max = 140; +static unsigned int special_fx = 0; static int texture_width = 320; static int texture_height = 240; @@ -60,16 +64,28 @@ int initialize_sdl(const int init_fullscreen, const int init_use_gpu) { SDL_RenderSetLogicalSize(rend, texture_width, texture_height); - maintexture = NULL; + fx_texture = NULL; + fx_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, + texture_width, texture_height); - maintexture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, - texture_width, texture_height); + m8_texture = NULL; + m8_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, + texture_width, texture_height); - SDL_SetRenderTarget(rend, maintexture); + SDL_SetTextureBlendMode(fx_texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureBlendMode(m8_texture, SDL_BLENDMODE_BLEND); + main_texture = NULL; + main_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, + texture_width, texture_height); + + SDL_SetRenderTarget(rend, fx_texture); + SDL_SetRenderDrawColor(rend, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); + SDL_RenderClear(rend); + + SDL_SetRenderTarget(rend, m8_texture); SDL_SetRenderDrawColor(rend, background_color.r, background_color.g, background_color.b, background_color.a); - SDL_RenderClear(rend); set_font_mode(0); @@ -101,14 +117,23 @@ static void check_and_adjust_window_and_texture_size(const unsigned int new_widt SDL_SetWindowSize(win, texture_width * 2, texture_height * 2); } - SDL_DestroyTexture(maintexture); + SDL_DestroyTexture(main_texture); + SDL_DestroyTexture(m8_texture); + SDL_DestroyTexture(fx_texture); SDL_RenderSetLogicalSize(rend, texture_width, texture_height); - maintexture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, - texture_width, texture_height); + main_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, + texture_width, texture_height); + m8_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, + texture_width, texture_height); + fx_texture = SDL_CreateTexture(rend, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, + texture_width, texture_height); - SDL_SetRenderTarget(rend, maintexture); + SDL_SetTextureBlendMode(fx_texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureBlendMode(m8_texture, SDL_BLENDMODE_BLEND); + + SDL_SetRenderTarget(rend, m8_texture); } // Set M8 hardware model in use. 0 = MK1, 1 = MK2 @@ -127,6 +152,10 @@ void set_m8_model(const unsigned int model) { } void set_font_mode(int mode) { + + fx_piano_destroy(); + special_fx = 0; + if (mode < 0 || mode > 2) { // bad font mode return; @@ -141,6 +170,10 @@ void set_font_mode(int mode) { screen_offset_y = fonts[mode]->screen_offset_y; text_offset_y = fonts[mode]->text_offset_y; waveform_max_height = fonts[mode]->waveform_max_height; + if (font_mode > 1) { + note_column_y_min = 96; + note_column_y_max = 196; + } change_font(fonts[mode]); SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Font mode %i, Screen offset %i", mode, screen_offset_y); @@ -148,7 +181,10 @@ void set_font_mode(int mode) { void close_renderer() { kill_inline_font(); - SDL_DestroyTexture(maintexture); + fx_piano_destroy(); + SDL_DestroyTexture(m8_texture); + SDL_DestroyTexture(fx_texture); + SDL_DestroyTexture(main_texture); SDL_DestroyRenderer(rend); SDL_DestroyWindow(win); } @@ -165,11 +201,17 @@ void toggle_fullscreen() { int draw_character(struct draw_character_command *command) { - const uint32_t fgcolor = + const uint32_t fg_color = command->foreground.r << 16 | command->foreground.g << 8 | command->foreground.b; - const uint32_t bgcolor = + const uint32_t bg_color = command->background.r << 16 | command->background.g << 8 | command->background.b; + /* Note characters appear between y=70 and y=140, update the active notes + array used by visual effects */ + if (command->pos.y >= note_column_y_min && command->pos.y <= note_column_y_max) { + update_active_notes_data(command, font_mode); + } + /* Notes: If large font is enabled, offset the screen elements by a fixed amount. If background and foreground colors are the same, draw transparent @@ -177,8 +219,7 @@ int draw_character(struct draw_character_command *command) { both*/ inprint(rend, (char *)&command->c, command->pos.x, - command->pos.y + text_offset_y + screen_offset_y, fgcolor, - bgcolor); + command->pos.y + text_offset_y + screen_offset_y, fg_color, bg_color); dirty = 1; @@ -223,7 +264,7 @@ void draw_waveform(struct draw_oscilloscope_waveform_command *command) { static uint8_t wfm_cleared = 0; static int prev_waveform_size = 0; - // If the waveform is not being displayed and it's already been cleared, skip + // If the waveform is not being displayed, and it's already been cleared, skip // rendering it if (!(wfm_cleared && command->waveform_size == 0)) { @@ -278,34 +319,52 @@ void display_keyjazz_overlay(const uint8_t show, const uint8_t base_octave, const Uint16 overlay_offset_x = texture_width - (fonts[font_mode]->glyph_x * 7 + 1); const Uint16 overlay_offset_y = texture_height - (fonts[font_mode]->glyph_y + 1); - const Uint32 bgcolor = - background_color.r << 16 | background_color.g << 8 | background_color.b; + const Uint32 bg_color = background_color.r << 16 | background_color.g << 8 | background_color.b; if (show) { char overlay_text[7]; snprintf(overlay_text, sizeof(overlay_text), "%02X %u", velocity, base_octave); - inprint(rend, overlay_text, overlay_offset_x, overlay_offset_y, 0xC8C8C8, bgcolor); + inprint(rend, overlay_text, overlay_offset_x, overlay_offset_y, 0xC8C8C8, bg_color); inprint(rend, "*", overlay_offset_x + (fonts[font_mode]->glyph_x * 5 + 5), overlay_offset_y, - 0xFF0000, bgcolor); + 0xFF0000, bg_color); } else { - inprint(rend, " ", overlay_offset_x, overlay_offset_y, 0xC8C8C8, bgcolor); + inprint(rend, " ", overlay_offset_x, overlay_offset_y, 0xC8C8C8, bg_color); } dirty = 1; } +static void render_special_fx() { + switch (special_fx) { + case 1: + fx_piano_update(); + break; + default: + break; + } +} + void render_screen() { if (dirty) { dirty = 0; + + SDL_SetRenderTarget(rend, main_texture); + SDL_RenderCopy(rend, m8_texture, NULL, NULL); + + if (special_fx) { + render_special_fx(); + SDL_RenderCopy(rend, fx_texture, NULL, NULL); + } + SDL_SetRenderTarget(rend, NULL); SDL_SetRenderDrawColor(rend, background_color.r, background_color.g, background_color.b, background_color.a); SDL_RenderClear(rend); - SDL_RenderCopy(rend, maintexture, NULL, NULL); + SDL_RenderCopy(rend, main_texture, NULL, NULL); SDL_RenderPresent(rend); - SDL_SetRenderTarget(rend, maintexture); + SDL_SetRenderTarget(rend, m8_texture); fps++; @@ -334,3 +393,19 @@ void screensaver_destroy() { set_font_mode(0); SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Screensaver destroyed"); } + +int toggle_special_fx() { + special_fx++; + switch (special_fx) { + case 1: + fx_piano_init(fx_texture); + break; + case 2: + fx_piano_destroy(); + special_fx = 0; + break; + default: + break; + } + return special_fx; +} \ No newline at end of file diff --git a/src/render.h b/src/render.h index 9c2c589..944ea11 100644 --- a/src/render.h +++ b/src/render.h @@ -20,6 +20,7 @@ void view_changed(int view); void render_screen(); void toggle_fullscreen(); void display_keyjazz_overlay(uint8_t show, uint8_t base_octave, uint8_t velocity); +int toggle_special_fx(); void screensaver_init(); void screensaver_draw();