// MIT License // Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include "sfx.h" #include "history.h" #define CANVAS_SIZE 6 #define CANVAS_COLS (SFX_TICKS) #define CANVAS_ROWS (16) #define CANVAS_WIDTH (CANVAS_COLS * CANVAS_SIZE) #define CANVAS_HEIGHT (CANVAS_ROWS * CANVAS_SIZE) #define PIANO_BUTTON_WIDTH 9 #define PIANO_BUTTON_HEIGHT 16 #define PIANO_WHITE_BUTTONS 7 #define PIANO_WIDTH ((PIANO_BUTTON_WIDTH+1)*PIANO_WHITE_BUTTONS) #define PIANO_HEIGHT (PIANO_BUTTON_HEIGHT) #define DEFAULT_CHANNEL 0 static void drawSwitch(Sfx* sfx, s32 x, s32 y, const char* label, s32 value, void(*set)(Sfx*, s32)) { static const u8 LeftArrow[] = { 0b00010000, 0b00110000, 0b01110000, 0b00110000, 0b00010000, 0b00000000, 0b00000000, 0b00000000, }; static const u8 RightArrow[] = { 0b01000000, 0b01100000, 0b01110000, 0b01100000, 0b01000000, 0b00000000, 0b00000000, 0b00000000, }; sfx->tic->api.text(sfx->tic, label, x, y, systemColor(tic_color_white)); { x += (s32)strlen(label)*TIC_FONT_WIDTH; SDL_Rect rect = {x, y, TIC_FONT_WIDTH, TIC_FONT_HEIGHT}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) set(sfx, -1); } drawBitIcon(rect.x, rect.y, LeftArrow, systemColor(tic_color_dark_gray)); } { char val[] = "99"; sprintf(val, "%02i", value); sfx->tic->api.fixed_text(sfx->tic, val, x += TIC_FONT_WIDTH, y, systemColor(tic_color_white)); } { x += 2*TIC_FONT_WIDTH; SDL_Rect rect = {x, y, TIC_FONT_WIDTH, TIC_FONT_HEIGHT}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) set(sfx, +1); } drawBitIcon(rect.x, rect.y, RightArrow, systemColor(tic_color_dark_gray)); } } static tic_sound_effect* getEffect(Sfx* sfx) { return sfx->tic->cart.sound.sfx.data + sfx->index; } static void setIndex(Sfx* sfx, s32 delta) { sfx->index += delta; } static void setSpeed(Sfx* sfx, s32 delta) { tic_sound_effect* effect = getEffect(sfx); effect->speed += delta; history_add(sfx->history.envelope); } static void drawTopPanel(Sfx* sfx, s32 x, s32 y) { const s32 Gap = 8*TIC_FONT_WIDTH; drawSwitch(sfx, x, y, "IDX", sfx->index, setIndex); tic_sound_effect* effect = getEffect(sfx); drawSwitch(sfx, x += Gap, y, "SPD", effect->speed, setSpeed); } static void setLoopStart(Sfx* sfx, s32 delta) { tic_sound_effect* effect = getEffect(sfx); tic_sound_loop* loop = effect->loops + sfx->canvasTab; loop->start += delta; history_add(sfx->history.envelope); } static void setLoopSize(Sfx* sfx, s32 delta) { tic_sound_effect* effect = getEffect(sfx); tic_sound_loop* loop = effect->loops + sfx->canvasTab; loop->size += delta; history_add(sfx->history.envelope); } static void drawLoopPanel(Sfx* sfx, s32 x, s32 y) { sfx->tic->api.text(sfx->tic, "LOOP:", x, y, systemColor(tic_color_dark_gray)); enum {Gap = 2}; tic_sound_effect* effect = getEffect(sfx); tic_sound_loop* loop = effect->loops + sfx->canvasTab; drawSwitch(sfx, x, y += Gap + TIC_FONT_HEIGHT, "", loop->size, setLoopSize); drawSwitch(sfx, x, y += Gap + TIC_FONT_HEIGHT, "", loop->start, setLoopStart); } static tic_waveform* getWaveformById(Sfx* sfx, s32 i) { return &sfx->tic->cart.sound.sfx.waveform.envelopes[i]; } static tic_waveform* getWaveform(Sfx* sfx) { return getWaveformById(sfx, sfx->waveform.index); } static void drawWaveButtons(Sfx* sfx, s32 x, s32 y) { static const u8 EmptyIcon[] = { 0b01110000, 0b10001000, 0b10001000, 0b10001000, 0b01110000, 0b00000000, 0b00000000, 0b00000000, }; static const u8 FullIcon[] = { 0b01110000, 0b11111000, 0b11111000, 0b11111000, 0b01110000, 0b00000000, 0b00000000, 0b00000000, }; enum {Scale = 4, Width = 10, Height = 5, Gap = 1, Count = ENVELOPES_COUNT, Rows = Count, HGap = 2}; for(s32 i = 0; i < Count; i++) { SDL_Rect rect = {x, y + (Count - i - 1)*(Height+Gap), Width, Height}; bool over = false; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); over = true; if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) { sfx->waveform.index = i; sfx->tab = SFX_WAVEFORM_TAB; return; } } bool active = false; if(sfx->play.active) { tic_sfx_pos pos = sfx->tic->api.sfx_pos(sfx->tic, DEFAULT_CHANNEL); if(pos.wave >= 0 && getEffect(sfx)->data[pos.wave].wave == i) active = true; } sfx->tic->api.rect(sfx->tic, rect.x, rect.y, rect.w, rect.h, active ? systemColor(tic_color_red) : over ? systemColor(tic_color_gray) : systemColor(tic_color_dark_gray)); { enum{Size = 5}; SDL_Rect iconRect = {x+Width+HGap, y + (Count - i - 1)*(Height+Gap), Size, Size}; bool over = false; if(checkMousePos(&iconRect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); over = true; if(checkMouseClick(&iconRect, SDL_BUTTON_LEFT)) { tic_sound_effect* effect = getEffect(sfx); for(s32 c = 0; c < SFX_TICKS; c++) effect->data[c].wave = i; } } drawBitIcon(iconRect.x, iconRect.y, EmptyIcon, systemColor(over ? tic_color_gray : tic_color_dark_gray)); } { tic_waveform* wave = getWaveformById(sfx, i); for(s32 i = 0; i < ENVELOPE_VALUES/Scale; i++) { s32 value = tic_tool_peek4(wave->data, i*Scale)/Scale; sfx->tic->api.pixel(sfx->tic, rect.x + i+1, rect.y + Height - value - 2, systemColor(tic_color_white)); } } } // draw full icon { tic_sound_effect* effect = getEffect(sfx); u8 start = effect->data[0].wave; bool full = true; for(s32 c = 1; c < SFX_TICKS; c++) if(effect->data[c].wave != start) { full = false; break; } if(full) drawBitIcon(x+Width+HGap, y + (Count - start - 1)*(Height+Gap), FullIcon, systemColor(tic_color_white)); } } static void drawCanvasTabs(Sfx* sfx, s32 x, s32 y) { static const char* Labels[] = {"WAVE", "VOLUME", "ARPEGG", "PITCH"}; enum {Height = TIC_FONT_HEIGHT+2}; for(s32 i = 0, sy = y; i < COUNT_OF(Labels); sy += Height, i++) { s32 size = sfx->tic->api.text(sfx->tic, Labels[i], 0, -TIC_FONT_HEIGHT, systemColor(tic_color_black)); SDL_Rect rect = {x - size, sy, size, TIC_FONT_HEIGHT}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) { sfx->canvasTab = i; } } sfx->tic->api.text(sfx->tic, Labels[i], rect.x, rect.y, i == sfx->canvasTab ? systemColor(tic_color_white) : systemColor(tic_color_dark_gray)); } tic_sound_effect* effect = getEffect(sfx); switch(sfx->canvasTab) { case SFX_PITCH_TAB: { static const char Label[] = "x16"; enum{Width = (sizeof Label - 1) * TIC_FONT_WIDTH}; SDL_Rect rect = {(x - Width)/2, y + Height * 6, Width, TIC_FONT_HEIGHT}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) effect->pitch16x++; } sfx->tic->api.fixed_text(sfx->tic, Label, rect.x, rect.y, systemColor(effect->pitch16x ? tic_color_white : tic_color_dark_gray)); } break; case SFX_ARPEGGIO_TAB: { static const char Label[] = "DOWN"; enum{Width = (sizeof Label - 1) * TIC_FONT_WIDTH}; SDL_Rect rect = {(x - Width)/2, y + Height * 6, Width, TIC_FONT_HEIGHT}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) effect->reverse++; } sfx->tic->api.text(sfx->tic, Label, rect.x, rect.y, systemColor(effect->reverse ? tic_color_white : tic_color_dark_gray)); } break; default: break; } } static void drawCanvas(Sfx* sfx, s32 x, s32 y) { sfx->tic->api.rect(sfx->tic, x, y, CANVAS_WIDTH, CANVAS_HEIGHT, systemColor(tic_color_dark_red)); for(s32 i = 0; i < CANVAS_HEIGHT; i += CANVAS_SIZE) sfx->tic->api.line(sfx->tic, x, y + i, x + CANVAS_WIDTH, y + i, TIC_COLOR_BG); for(s32 i = 0; i < CANVAS_WIDTH; i += CANVAS_SIZE) sfx->tic->api.line(sfx->tic, x + i, y, x + i, y + CANVAS_HEIGHT, TIC_COLOR_BG); { tic_sfx_pos pos = sfx->tic->api.sfx_pos(sfx->tic, DEFAULT_CHANNEL); s32 tickIndex = *(pos.data + sfx->canvasTab); if(tickIndex >= 0) sfx->tic->api.rect(sfx->tic, x + tickIndex * CANVAS_SIZE, y, CANVAS_SIZE + 1, CANVAS_HEIGHT + 1, systemColor(tic_color_white)); } SDL_Rect rect = {x, y, CANVAS_WIDTH, CANVAS_HEIGHT}; tic_sound_effect* effect = getEffect(sfx); if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); s32 mx = getMouseX() - x; s32 my = getMouseY() - y; mx -= mx % CANVAS_SIZE; my -= my % CANVAS_SIZE; if(checkMouseDown(&rect, SDL_BUTTON_LEFT)) { mx /= CANVAS_SIZE; my /= CANVAS_SIZE; switch(sfx->canvasTab) { case SFX_VOLUME_TAB: effect->data[mx].volume = my; break; case SFX_ARPEGGIO_TAB: effect->data[mx].arpeggio = CANVAS_ROWS - my - 1; break; case SFX_PITCH_TAB: effect->data[mx].pitch = CANVAS_ROWS / 2 - my - 1; break; case SFX_WAVE_TAB: effect->data[mx].wave = CANVAS_ROWS - my - 1; break; default: break; } history_add(sfx->history.envelope); } } for(s32 i = 0; i < CANVAS_COLS; i++) { switch(sfx->canvasTab) { case SFX_VOLUME_TAB: { for(s32 j = 1, start = CANVAS_HEIGHT - CANVAS_SIZE; j <= CANVAS_ROWS - effect->data[i].volume; j++, start -= CANVAS_SIZE) sfx->tic->api.rect(sfx->tic, x + i * CANVAS_SIZE + 1, y + 1 + start, CANVAS_SIZE-1, CANVAS_SIZE-1, systemColor(tic_color_red)); } break; case SFX_ARPEGGIO_TAB: { sfx->tic->api.rect(sfx->tic, x + i * CANVAS_SIZE + 1, y + 1 + (CANVAS_HEIGHT - (effect->data[i].arpeggio+1)*CANVAS_SIZE), CANVAS_SIZE-1, CANVAS_SIZE-1, systemColor(tic_color_red)); } break; case SFX_PITCH_TAB: { for(s32 j = SDL_min(0, effect->data[i].pitch); j <= SDL_max(0, effect->data[i].pitch); j++) sfx->tic->api.rect(sfx->tic, x + i * CANVAS_SIZE + 1, y + 1 + (CANVAS_HEIGHT/2 - (j+1)*CANVAS_SIZE), CANVAS_SIZE-1, CANVAS_SIZE-1, systemColor(tic_color_red)); } break; case SFX_WAVE_TAB: { sfx->tic->api.rect(sfx->tic, x + i * CANVAS_SIZE + 1, y + 1 + (CANVAS_HEIGHT - (effect->data[i].wave+1)*CANVAS_SIZE), CANVAS_SIZE-1, CANVAS_SIZE-1, systemColor(tic_color_red)); } break; default: break; } } { tic_sound_loop* loop = effect->loops + sfx->canvasTab; if(loop->start > 0 || loop->size > 0) { for(s32 i = 0; i < loop->size; i++) sfx->tic->api.rect(sfx->tic, x + (loop->start+i) * CANVAS_SIZE+1, y + CANVAS_HEIGHT - 2, CANVAS_SIZE-1, 2, systemColor(tic_color_yellow)); } } } static void drawPiano(Sfx* sfx, s32 x, s32 y) { tic_sound_effect* effect = getEffect(sfx); static const s32 ButtonIndixes[] = {0, 2, 4, 5, 7, 9, 11, 1, 3, -1, 6, 8, 10}; SDL_Rect buttons[COUNT_OF(ButtonIndixes)]; for(s32 i = 0; i < COUNT_OF(buttons); i++) { buttons[i] = i < PIANO_WHITE_BUTTONS ? (SDL_Rect){x + (PIANO_BUTTON_WIDTH+1)*i, y, PIANO_BUTTON_WIDTH + 1, PIANO_BUTTON_HEIGHT} : (SDL_Rect){x + (7 + 3) * (i - PIANO_WHITE_BUTTONS) + 6, y, 7, 8}; } SDL_Rect rect = {x, y, PIANO_WIDTH, PIANO_HEIGHT}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); static const char* Tooltips[] = {"C [z]", "C# [s]", "D [x]", "D# [d]", "E [c]", "F [v]", "F# [g]", "G [b]", "G# [h]", "A [n]", "A# [j]", "B [m]" }; for(s32 i = COUNT_OF(buttons) - 1; i >= 0; i--) { SDL_Rect* rect = buttons + i; if(checkMousePos(rect)) if(ButtonIndixes[i] >= 0) { showTooltip(Tooltips[ButtonIndixes[i]]); break; } } if(checkMouseDown(&rect, SDL_BUTTON_LEFT)) { for(s32 i = COUNT_OF(buttons) - 1; i >= 0; i--) { SDL_Rect* rect = buttons + i; s32 index = ButtonIndixes[i]; if(index >= 0) { if(checkMousePos(rect)) { effect->note = index; sfx->play.active = true; break; } } } } } for(s32 i = 0; i < COUNT_OF(buttons); i++) { SDL_Rect* rect = buttons + i; bool white = i < PIANO_WHITE_BUTTONS; s32 index = ButtonIndixes[i]; if(index >= 0) sfx->tic->api.rect(sfx->tic, rect->x, rect->y, rect->w - (white ? 1 : 0), rect->h, systemColor(sfx->play.active && effect->note == index ? tic_color_red : white ? tic_color_white : tic_color_black)); } } static void drawOctavePanel(Sfx* sfx, s32 x, s32 y) { tic_sound_effect* effect = getEffect(sfx); static const char Label[] = "OCT"; sfx->tic->api.text(sfx->tic, Label, x, y, systemColor(tic_color_white)); x += sizeof(Label)*TIC_FONT_WIDTH; enum {Gap = 5}; for(s32 i = 0; i < OCTAVES; i++) { SDL_Rect rect = {x + i * (TIC_FONT_WIDTH + Gap), y, TIC_FONT_WIDTH, TIC_FONT_HEIGHT}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) { effect->octave = i; } } sfx->tic->api.draw_char(sfx->tic, i + '1', rect.x, rect.y, systemColor(i == effect->octave ? tic_color_white : tic_color_dark_gray)); } } static void playSound(Sfx* sfx) { if(sfx->play.active) { tic_sound_effect* effect = getEffect(sfx); if(sfx->play.note != effect->note) { sfx->play.note = effect->note; sfx->tic->api.sfx_stop(sfx->tic, DEFAULT_CHANNEL); sfx->tic->api.sfx(sfx->tic, sfx->index, effect->note, effect->octave, -1, DEFAULT_CHANNEL); } } else { sfx->play.note = -1; sfx->tic->api.sfx_stop(sfx->tic, DEFAULT_CHANNEL); } } static void undo(Sfx* sfx) { history_undo(sfx->history.envelope); } static void redo(Sfx* sfx) { history_redo(sfx->history.envelope); } static void undoWave(Sfx* sfx) { history_undo(sfx->history.waveform); } static void redoWave(Sfx* sfx) { history_redo(sfx->history.waveform); } static void copyToClipboard(Sfx* sfx) { tic_sound_effect* effect = getEffect(sfx); toClipboard(effect, sizeof(tic_sound_effect), true); } static void copyWaveToClipboard(Sfx* sfx) { tic_waveform* wave = getWaveform(sfx); toClipboard(wave, sizeof(tic_waveform), true); } static void resetSfx(Sfx* sfx) { tic_sound_effect* effect = getEffect(sfx); memset(effect, 0, sizeof(tic_sound_effect)); history_add(sfx->history.envelope); } static void resetWave(Sfx* sfx) { tic_waveform* wave = getWaveform(sfx); memset(wave, 0, sizeof(tic_waveform)); history_add(sfx->history.waveform); } static void cutToClipboard(Sfx* sfx) { copyToClipboard(sfx); resetSfx(sfx); } static void cutWaveToClipboard(Sfx* sfx) { copyWaveToClipboard(sfx); resetWave(sfx); } static void copyFromClipboard(Sfx* sfx) { tic_sound_effect* effect = getEffect(sfx); if(fromClipboard(effect, sizeof(tic_sound_effect), true)) history_add(sfx->history.envelope); } static void copyWaveFromClipboard(Sfx* sfx) { tic_waveform* wave = getWaveform(sfx); if(fromClipboard(wave, sizeof(tic_waveform), true)) history_add(sfx->history.waveform); } static void processKeyboard(Sfx* sfx) { s32 keyboardButton = -1; static const s32 Scancodes[] = { SDL_SCANCODE_Z, SDL_SCANCODE_S, SDL_SCANCODE_X, SDL_SCANCODE_D, SDL_SCANCODE_C, SDL_SCANCODE_V, SDL_SCANCODE_G, SDL_SCANCODE_B, SDL_SCANCODE_H, SDL_SCANCODE_N, SDL_SCANCODE_J, SDL_SCANCODE_M, }; SDL_Keymod keymod = SDL_GetModState(); if(keymod & TIC_MOD_CTRL) { } else { for(int i = 0; i < COUNT_OF(Scancodes); i++) if(getKeyboard()[Scancodes[i]]) keyboardButton = i; } tic_sound_effect* effect = getEffect(sfx); if(keyboardButton >= 0) { effect->note = keyboardButton; sfx->play.active = true; } if(getKeyboard()[SDL_SCANCODE_SPACE]) sfx->play.active = true; } static void processKeydown(Sfx* sfx, SDL_Keycode keycode) { switch(getClipboardEvent(keycode)) { case TIC_CLIPBOARD_CUT: cutToClipboard(sfx); break; case TIC_CLIPBOARD_COPY: copyToClipboard(sfx); break; case TIC_CLIPBOARD_PASTE: copyFromClipboard(sfx); break; default: break; } SDL_Keymod keymod = SDL_GetModState(); if(keymod & TIC_MOD_CTRL) { switch(keycode) { case SDLK_z: undo(sfx); break; case SDLK_y: redo(sfx); break; } } switch(keycode) { case SDLK_TAB: sfx->tab = SFX_WAVEFORM_TAB; break; case SDLK_LEFT: sfx->index--; break; case SDLK_RIGHT: sfx->index++; break; case SDLK_DELETE: resetSfx(sfx); break; } } static void processWaveformKeydown(Sfx* sfx, SDL_Keycode keycode) { switch(getClipboardEvent(keycode)) { case TIC_CLIPBOARD_CUT: cutWaveToClipboard(sfx); break; case TIC_CLIPBOARD_COPY: copyWaveToClipboard(sfx); break; case TIC_CLIPBOARD_PASTE: copyWaveFromClipboard(sfx); break; default: break; } SDL_Keymod keymod = SDL_GetModState(); if(keymod & TIC_MOD_CTRL) { switch(keycode) { case SDLK_z: undoWave(sfx); break; case SDLK_y: redoWave(sfx); break; } } switch(keycode) { case SDLK_TAB: sfx->tab = SFX_ENVELOPES_TAB; break; case SDLK_LEFT: sfx->waveform.index--; break; case SDLK_RIGHT: sfx->waveform.index++; break; case SDLK_DELETE: resetWave(sfx); break; } } static void drawModeTabs(Sfx* sfx) { static const u8 Icons[] = { 0b00000000, 0b00110000, 0b01001001, 0b01001001, 0b01001001, 0b00000110, 0b00000000, 0b00000000, 0b00000000, 0b01000000, 0b01000100, 0b01010100, 0b01010101, 0b01010101, 0b00000000, 0b00000000, }; enum { Width = 9, Height = 7, Rows = 8, Count = sizeof Icons / Rows }; for (s32 i = 0; i < Count; i++) { SDL_Rect rect = { TIC80_WIDTH - Width * (Count - i), 0, Width, Height }; bool over = false; static const s32 Tabs[] = { SFX_WAVEFORM_TAB, SFX_ENVELOPES_TAB }; if (checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); over = true; static const char* Tooltips[] = { "WAVEFORMS [tab]", "ENVELOPES [tab]" }; showTooltip(Tooltips[i]); if (checkMouseClick(&rect, SDL_BUTTON_LEFT)) sfx->tab = Tabs[i]; } if (sfx->tab == Tabs[i]) sfx->tic->api.rect(sfx->tic, rect.x, rect.y, rect.w, rect.h, systemColor(tic_color_black)); drawBitIcon(rect.x, rect.y, Icons + i*Rows, systemColor(sfx->tab == Tabs[i] ? tic_color_white : over ? tic_color_dark_gray : tic_color_light_blue)); } } static void drawSfxToolbar(Sfx* sfx) { sfx->tic->api.rect(sfx->tic, 0, 0, TIC80_WIDTH, TOOLBAR_SIZE-1, systemColor(tic_color_white)); enum{Width = 3 * TIC_FONT_WIDTH}; s32 x = TIC80_WIDTH - Width - TIC_SPRITESIZE*3; s32 y = 1; SDL_Rect rect = {x, y, Width, TIC_FONT_HEIGHT}; bool over = false; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); over = true; showTooltip("PLAY SFX [space]"); if(checkMouseDown(&rect, SDL_BUTTON_LEFT)) { sfx->play.active = true; } } tic_sound_effect* effect = getEffect(sfx); { static const char* Notes[] = SFX_NOTES; char buf[] = "C#4"; sprintf(buf, "%s%i", Notes[effect->note], effect->octave+1); sfx->tic->api.fixed_text(sfx->tic, buf, x, y, systemColor(over ? tic_color_dark_gray : tic_color_light_blue)); } drawModeTabs(sfx); } static void envelopesTick(Sfx* sfx) { SDL_Event* event = NULL; while ((event = pollEvent())) { switch(event->type) { case SDL_KEYDOWN: processKeydown(sfx, event->key.keysym.sym); break; } } processKeyboard(sfx); sfx->tic->api.clear(sfx->tic, TIC_COLOR_BG); enum{ Gap = 3, Start = 40}; drawPiano(sfx, Start, TIC80_HEIGHT - PIANO_HEIGHT - Gap); drawSfxToolbar(sfx); drawToolbar(sfx->tic, TIC_COLOR_BG, false); drawTopPanel(sfx, Start, TOOLBAR_SIZE + Gap - 1); drawCanvasTabs(sfx, Start-Gap, TOOLBAR_SIZE + Gap + TIC_FONT_HEIGHT+1); if(sfx->canvasTab == SFX_WAVE_TAB) drawWaveButtons(sfx, Start + CANVAS_WIDTH + Gap-1, TOOLBAR_SIZE + Gap + TIC_FONT_HEIGHT+1); drawLoopPanel(sfx, Gap, TOOLBAR_SIZE + Gap + TIC_FONT_HEIGHT+91); drawCanvas(sfx, Start-1, TOOLBAR_SIZE + Gap + TIC_FONT_HEIGHT); drawOctavePanel(sfx, Start + Gap + PIANO_WIDTH + Gap-1, TIC80_HEIGHT - TIC_FONT_HEIGHT - (PIANO_HEIGHT - TIC_FONT_HEIGHT)/2 - Gap); } static void drawWaveformBar(Sfx* sfx, s32 x, s32 y) { enum { Border = 2, Scale = 2, Width = ENVELOPE_VALUES/Scale + Border, Height = CANVAS_HEIGHT/CANVAS_SIZE/Scale + Border, Gap = 3, Rows = 2, Cols = ENVELOPES_COUNT/Rows, }; for(s32 i = 0; i < ENVELOPES_COUNT; i++) { SDL_Rect rect = {x + (i%Cols)*(Width+Gap), y + (i/Cols)*(Height+Gap), Width, Height}; if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseClick(&rect, SDL_BUTTON_LEFT)) sfx->waveform.index = i; } bool active = false; if(sfx->play.active) { tic_sfx_pos pos = sfx->tic->api.sfx_pos(sfx->tic, DEFAULT_CHANNEL); if(pos.wave >= 0 && getEffect(sfx)->data[pos.wave].wave == i) active = true; } sfx->tic->api.rect(sfx->tic, rect.x, rect.y, rect.w, rect.h, systemColor(active ? tic_color_red : tic_color_white)); if(sfx->waveform.index == i) sfx->tic->api.rect_border(sfx->tic, rect.x-2, rect.y-2, rect.w+4, rect.h+4, systemColor(tic_color_white)); { tic_waveform* wave = getWaveformById(sfx, i); for(s32 i = 0; i < ENVELOPE_VALUES/Scale; i++) { s32 value = tic_tool_peek4(wave->data, i*Scale)/Scale; sfx->tic->api.pixel(sfx->tic, rect.x + i+1, rect.y + Height - value - 2, systemColor(tic_color_black)); } } } } static void drawWaveformCanvas(Sfx* sfx, s32 x, s32 y) { enum {Rows = CANVAS_ROWS, Width = ENVELOPE_VALUES * CANVAS_SIZE, Height = CANVAS_HEIGHT}; SDL_Rect rect = {x, y, Width, Height}; sfx->tic->api.rect(sfx->tic, rect.x, rect.y, rect.w, rect.h, systemColor(tic_color_dark_red)); for(s32 i = 0; i < Height; i += CANVAS_SIZE) sfx->tic->api.line(sfx->tic, rect.x, rect.y + i, rect.x + Width, rect.y + i, TIC_COLOR_BG); for(s32 i = 0; i < Width; i += CANVAS_SIZE) sfx->tic->api.line(sfx->tic, rect.x + i, rect.y, rect.x + i, rect.y + Width, TIC_COLOR_BG); if(checkMousePos(&rect)) { setCursor(SDL_SYSTEM_CURSOR_HAND); if(checkMouseDown(&rect, SDL_BUTTON_LEFT)) { s32 mx = getMouseX() - x; s32 my = getMouseY() - y; mx -= mx % CANVAS_SIZE; my -= my % CANVAS_SIZE; mx /= CANVAS_SIZE; my /= CANVAS_SIZE; tic_waveform* wave = getWaveform(sfx); tic_tool_poke4(wave->data, mx, Rows - my - 1); history_add(sfx->history.waveform); } } tic_waveform* wave = getWaveform(sfx); for(s32 i = 0; i < ENVELOPE_VALUES; i++) { s32 value = tic_tool_peek4(wave->data, i); sfx->tic->api.rect(sfx->tic, x + i * CANVAS_SIZE + 1, y + 1 + (Height - (value+1)*CANVAS_SIZE), CANVAS_SIZE-1, CANVAS_SIZE-1, systemColor(tic_color_red)); } } static void waveformTick(Sfx* sfx) { SDL_Event* event = NULL; while ((event = pollEvent())) { switch(event->type) { case SDL_KEYDOWN: processWaveformKeydown(sfx, event->key.keysym.sym); break; } } processKeyboard(sfx); sfx->tic->api.clear(sfx->tic, TIC_COLOR_BG); drawSfxToolbar(sfx); drawToolbar(sfx->tic, TIC_COLOR_BG, false); drawWaveformCanvas(sfx, 23, 11); drawWaveformBar(sfx, 36, 110); } static void tick(Sfx* sfx) { sfx->play.active = false; switch(sfx->tab) { case SFX_WAVEFORM_TAB: waveformTick(sfx); break; case SFX_ENVELOPES_TAB: envelopesTick(sfx); break; } playSound(sfx); } static void onStudioEnvelopeEvent(Sfx* sfx, StudioEvent event) { switch(event) { case TIC_TOOLBAR_CUT: cutToClipboard(sfx); break; case TIC_TOOLBAR_COPY: copyToClipboard(sfx); break; case TIC_TOOLBAR_PASTE: copyFromClipboard(sfx); break; case TIC_TOOLBAR_UNDO: undo(sfx); break; case TIC_TOOLBAR_REDO: redo(sfx); break; default: break; } } static void onStudioWaveformEvent(Sfx* sfx, StudioEvent event) { switch(event) { case TIC_TOOLBAR_CUT: cutWaveToClipboard(sfx); break; case TIC_TOOLBAR_COPY: copyWaveToClipboard(sfx); break; case TIC_TOOLBAR_PASTE: copyWaveFromClipboard(sfx); break; case TIC_TOOLBAR_UNDO: undoWave(sfx); break; case TIC_TOOLBAR_REDO: redoWave(sfx); break; default: break; } } static void onStudioEvent(Sfx* sfx, StudioEvent event) { switch(sfx->tab) { case SFX_WAVEFORM_TAB: onStudioWaveformEvent(sfx, event); break; case SFX_ENVELOPES_TAB: onStudioEnvelopeEvent(sfx, event); break; default: break; } } void initSfx(Sfx* sfx, tic_mem* tic) { if(sfx->history.envelope) history_delete(sfx->history.envelope); if(sfx->history.waveform) history_delete(sfx->history.waveform); *sfx = (Sfx) { .tic = tic, .tick = tick, .index = 0, .play = { .note = -1, .active = false, }, .waveform = { .index = 0, }, .canvasTab = SFX_WAVE_TAB, .tab = SFX_ENVELOPES_TAB, .history = { .envelope = history_create(&tic->cart.sound.sfx.data, sizeof tic->cart.sound.sfx.data), .waveform = history_create(&tic->cart.sound.sfx.waveform, sizeof tic->cart.sound.sfx.waveform), }, .event = onStudioEvent, }; }