#include #include #include #include #include #include #include "memory.h" #include "loader.h" #include "cpu.h" #include "audio.h" #include "midi.h" #include "timing.h" #include "editor.h" #include "graphics.h" #include "menu.h" #include "dialog.h" #include "terminal.h" #include "expression.h" #include "inih/INIReader.h" #include #define AUDIO_SAMPLES (SCAN_LINES) #define AUDIO_BUFFER_SIZE (SCAN_LINES*4) #define AUDIO_FREQUENCY (int(SCAN_LINES*59.98)) #define XOUT_MASK 0xFC #define MAX_WAVE_X 64 #define MAX_WAVE_Y 64 #define BORDER_X1 15 #define BORDER_X2 144 #define BORDER_Y1 45 #define BORDER_Y2 (BORDER_Y1 + MAX_WAVE_Y + 1) #define BOTTOM_ROW (BORDER_Y2 + 2) #define MAX_COMMAND_CHARS 79 #define MIDI_TEXT_ROW 164 #define WAVE_TEXT_ROW (SCREEN_HEIGHT - (FONT_HEIGHT+4)) namespace Audio { bool _realTimeAudio = true; SDL_AudioDeviceID _audioDevice = 1; std::atomic _audioInIndex(AUDIO_SAMPLES*2); std::atomic _audioOutIndex(0); uint16_t _audioSamples[AUDIO_BUFFER_SIZE] = {0}; uint8_t _waveTables[256]; INIReader _configIniReader; bool getRealTimeAudio(void) {return _realTimeAudio;} bool getKeyAsString(const std::string& sectionString, const std::string& iniKey, const std::string& defaultKey, std::string& result) { result = _configIniReader.Get(sectionString, iniKey, defaultKey); if(result == defaultKey) return false; Expression::strToUpper(result); return true; } void sdl2AudioCallback(void* userData, unsigned char *stream, int length) { UNREFERENCED_PARAM(userData); int16_t *sdl2Stream = (int16_t *)stream; for(int i=0; i section; section["Audio"] = Audio; for(auto sectionString : _configIniReader.Sections()) { if(section.find(sectionString) == section.end()) { fprintf(stderr, "Audio::initialise() : INI file '%s' has bad Sections : reverting to default values.\n", AUDIO_CONFIG_INI); break; } std::string result; switch(section[sectionString]) { case Audio: { getKeyAsString(sectionString, "RealTimeAudio", "1", result); _realTimeAudio = strtol(result.c_str(), nullptr, 10); } break; } } } else { fprintf(stderr, "Audio::initialise() : couldn't find audio configuration INI file '%s' : reverting to default values.\n", AUDIO_CONFIG_INI); } SDL_AudioSpec audSpec; SDL_zero(audSpec); audSpec.freq = AUDIO_FREQUENCY; audSpec.format = AUDIO_S16; audSpec.channels = 1; audSpec.callback = sdl2AudioCallback; audSpec.samples = AUDIO_SAMPLES; if(SDL_OpenAudio(&audSpec, NULL) < 0) { Cpu::shutdown(); fprintf(stderr, "Audio::initialise() : failed to initialise SDL audio\n"); _EXIT_(EXIT_FAILURE); } initialiseChannels(); SDL_PauseAudio(0); initialiseEditor(); } void saveWaveTables(void) { for(uint16_t i=0; i<256; i++) _waveTables[i] = Cpu::getRAM(0x0700 + i); } void restoreWaveTables(void) { for(uint16_t i=0; i<256; i++) Cpu::setRAM(0x0700 + i, _waveTables[i]); } void restoreWaveTable(uint16_t waveTable) { // Interlaced wavetables for(uint16_t i=waveTable & 3; i<256; i+=4) Cpu::setRAM(0x0700 + i, _waveTables[i]); } void initialiseChannels(void) { // TODO: put this somewhere more appropriate Cpu::setRAM(46, 0); // reset LED sequencer for ROMv2 and higher // Reset channels for(uint16_t i=0; i _waveStrs = {"User", "Wav0", "Wav1", "Wav2", "Wav3"}; const std::vector _suffixes = {".gtwav", ".gtmid", ".gtmidi"}; const std::string _eraseLine = std::string(MAX_COMMAND_CHARS+1, 32); std::string _browserPath, _browserPathBackup; bool _firstTimeRender = true; bool _refreshScreen = false; bool _midiPlaying = false; bool _midiLineMode = false; uint8_t _waveUser[MAX_WAVE_X] = {0}; uint8_t _waveBuffer[MAX_WAVE_X] = {0}; uint8_t _midiBuffer[MIDI_MAX_BUFFER_SIZE] = {0}; int _midiBufferSize = 0; int _waveIndex = WaveUser; int _commandCharIndex[NumCmdLineTypes] = {0}; std::string _commandLine[NumCmdLineTypes]; CmdLineType _cmdLineType = CmdLineMidi; Editor::MouseState _mouseState; void initialiseEditor(void) { // Menu std::vector menuItems = {" AUDIO ", "Load Midi", "Load Wave", "Save Wave", "Del Wave"}; Menu::createMenu("Audio", menuItems, 9, 5); // Overwrite dialog std::vector dialogItems = { Dialog::Item(false, "Overwrite?"), Dialog::Item(false, ""), Dialog::Item(false, ""), Dialog::Item(false, ""), Dialog::Item(false, ""), Dialog::Item(true, "No", Dialog::Item::LeftX, Dialog::Item::Bd), Dialog::Item(true, "Yes", Dialog::Item::RightX, Dialog::Item::CurrentY, Dialog::Item::Bd) }; Dialog::createDialog("Overwrite", "Overwrite", dialogItems, 3, 5, Dialog::Dialog::DoubleWidth, 0, 1); // Wave dialogItems = {Dialog::Item(false, "Wave")}; Dialog::createDialog("Wave", "Wave", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("Wave", 1, BORDER_Y1); dialogItems = {Dialog::Item(false, "Load")}; Dialog::createDialog("LoadW", "Load", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("LoadW", 1, BORDER_Y1 + 6); // Play Wave dialogItems = {Dialog::Item(true, "Play", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("PlayW", "Play", dialogItems, 5, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); // Reset Wave dialogItems = {Dialog::Item(true, "Reset", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("Reset", "Reset", dialogItems, 5, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("Reset", 78, BOTTOM_ROW); // Erase Wave dialogItems = {Dialog::Item(true, "Erase", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("Erase", "Erase", dialogItems, 5, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("Erase", 84, BOTTOM_ROW); // Prev Wave dialogItems = {Dialog::Item(true, "Prev", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("Prev", "Prev", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("Prev", 15, BOTTOM_ROW); // Next Wave dialogItems = {Dialog::Item(true, "Next", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("Next", "Next", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("Next", 132, BOTTOM_ROW); // Midi dialogItems = {Dialog::Item(false, "Midi")}; Dialog::createDialog("Midi", "Midi", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("Midi", 1, 1); dialogItems = {Dialog::Item(true, "Play", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("PlayM", "Play", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("PlayM", 71, 1); dialogItems = {Dialog::Item(true, "4Bit", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("6Bit", "4Bit", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("6Bit", 132, 1); dialogItems = {Dialog::Item(true, "Pixel", Dialog::Item::CenterX, Dialog::Item::NextY, Dialog::Item::Bd)}; Dialog::createDialog("LineM", "Pixel", dialogItems, 4, 1, Dialog::Dialog::Regular, 1, 0, 106, 60); Dialog::positionDialog("LineM", 115, 1); _browserPath = Editor::getBrowserPath(); } void leave(LeaveMode leaveMode) { Midi::stop(); _firstTimeRender = true; Editor::setBrowserPath(_browserPathBackup); switch(leaveMode) { case LeavePrev: Editor::setEditorToPrevMode(); break; case LeaveHex: Editor::setEditorMode(Editor::Hex); break; case LeaveDasm: Editor::setEditorMode(Editor::Dasm); break; case LeaveImage: Editor::setEditorMode(Editor::Image); break; case LeaveTerm: Terminal::switchToTerminal(); break; case LeaveLoad: { Editor::setEditorMode(Editor::Load); Editor::browseDirectory(); } break; default: break; } } void clearCommandLine(void) { _commandCharIndex[_cmdLineType] = 0; _commandLine[_cmdLineType].clear(); } void prevCommandLineChar(void) { if(--_commandCharIndex[_cmdLineType] < 0) _commandCharIndex[_cmdLineType] = 0; } void nextCommandLineChar(void) { if(++_commandCharIndex[_cmdLineType] > int(_commandLine[_cmdLineType].size())) _commandCharIndex[_cmdLineType] = int(_commandLine[_cmdLineType].size()); } void homeCommandLineChar(void) { _commandCharIndex[_cmdLineType] = 0; } void endCommandLineChar(void) { _commandCharIndex[_cmdLineType] = (_commandLine[_cmdLineType].size()) ? int(_commandLine[_cmdLineType].size()) - 1 : 0; } void backspaceCommandLineChar(void) { if(_commandLine[_cmdLineType].size() && _commandCharIndex[_cmdLineType] > 0 && _commandCharIndex[_cmdLineType] <= int(_commandLine[_cmdLineType].size())) { _commandLine[_cmdLineType].erase(--_commandCharIndex[_cmdLineType], 1); } } void deleteCommandLineChar(void) { if(_commandLine[_cmdLineType].size() && _commandCharIndex[_cmdLineType] >= 0 && _commandCharIndex[_cmdLineType] < int(_commandLine[_cmdLineType].size())) { _commandLine[_cmdLineType].erase(_commandCharIndex[_cmdLineType], 1); if(_commandCharIndex[_cmdLineType] > int(_commandLine[_cmdLineType].size())) _commandCharIndex[_cmdLineType] = int(_commandLine[_cmdLineType].size()); } } void copyWaveTable(uint8_t src[], uint8_t dst[]) { for(uint16_t i=0; i>9) & 31; uint8_t sample1 = (_audioSamples[(i+1) <<2] >>9) & 31; if(_midiLineMode) { Graphics::drawLine(uint8_t((BORDER_X1 + 1) + i), uint8_t(38 - sample0), uint8_t((BORDER_X1 + 1) + i + 1), uint8_t(38 - sample1), 0xBBBBBBBB); } else { Graphics::drawPixel(uint8_t((BORDER_X1 + 1) + i), uint8_t(38 - sample0), 0xBBBBBBBB); } } } void refreshCommandLine(CmdLineType cmdLineType) { int row = (_cmdLineType == CmdLineWave) ? WAVE_TEXT_ROW : MIDI_TEXT_ROW; std::string commandLine = _commandLine[cmdLineType]; if(_commandCharIndex[cmdLineType] >= int(commandLine.size())) { _commandCharIndex[cmdLineType] = int(commandLine.size()); commandLine += char(32); } Graphics::drawText(_eraseLine, 0, row, 0x55555555, true, MAX_COMMAND_CHARS+1); // Flash wave cursor, refreshUi() is run 60 times per second static uint8_t toggle = 0; bool invert = ((toggle++) >>4) & 1; Graphics::drawText(commandLine, FONT_WIDTH, row, 0xFFFFFFFF, invert, 1, _commandCharIndex[cmdLineType]); } void refreshUi(void) { // Update wave Dialog::Item dialogItem; Dialog::getDialogItem("LoadW", 0, dialogItem); dialogItem.setText(_waveStrs[_waveIndex]); Dialog::setDialogItem("LoadW", 0, dialogItem); Dialog::renderDialog("Wave", 0, 0); Dialog::renderDialog("LoadW", 0, 0); // Wave interactables Dialog::renderDialog("Prev", _mouseState._x, _mouseState._y); Dialog::renderDialog("Next", _mouseState._x, _mouseState._y); if(!_waveIndex) { Dialog::positionDialog("PlayW", 63, BOTTOM_ROW); Dialog::renderDialog("PlayW", _mouseState._x, _mouseState._y); Dialog::positionDialog("Erase", 78, BOTTOM_ROW); Dialog::renderDialog("Erase", _mouseState._x, _mouseState._y); } else { Dialog::positionDialog("PlayW", 55, BOTTOM_ROW); Dialog::renderDialog("PlayW", _mouseState._x, _mouseState._y); Dialog::positionDialog("Reset", 69, BOTTOM_ROW); Dialog::renderDialog("Reset", _mouseState._x, _mouseState._y); Dialog::positionDialog("Erase", 85, BOTTOM_ROW); Dialog::renderDialog("Erase", _mouseState._x, _mouseState._y); } // Midi Dialog::renderDialog("Midi", 0, 0); Dialog::renderDialog("PlayM", _mouseState._x, _mouseState._y); Dialog::renderDialog("6Bit", _mouseState._x, _mouseState._y); Dialog::renderDialog("LineM", _mouseState._x, _mouseState._y); // Command line refreshCommandLine(_cmdLineType); } void chooseCmdLineOnMouse(int mouseX, int mouseY) { static CmdLineType cmdLineType = _cmdLineType; // Normalised mouse position float mx = float(mouseX) / float(Graphics::getWidth()) * 4.0f/3.0f; float my = float(mouseY) / float(Graphics::getHeight()); int pixelX = int(mx * float(GIGA_WIDTH)); int pixelY = int(my * float(GIGA_HEIGHT)); if(pixelX > 0 && pixelX < GIGA_WIDTH) { if(pixelY < 42) _cmdLineType = CmdLineMidi; if(pixelY > 42) _cmdLineType = CmdLineWave; } if(cmdLineType != _cmdLineType) { _refreshScreen = true; cmdLineType = _cmdLineType; } return; } bool isMouseInWave(int mouseX, int mouseY, int& pixelX, int& pixelY) { Menu::Menu menu; if(Menu::getMenu("Audio", menu)) { if(menu.getIsActive()) return false; } // Normalised mouse position float mx = float(mouseX) / float(Graphics::getWidth()) * 4.0f/3.0f; float my = float(mouseY) / float(Graphics::getHeight()); pixelX = int(mx * float(GIGA_WIDTH)); pixelY = int(my * float(GIGA_HEIGHT)); if(pixelX > BORDER_X1 && pixelX < BORDER_X2 && pixelY > BORDER_Y1 && pixelY < BORDER_Y2) { return true; } return false; } void handleGuiEvents(SDL_Event& event) { switch(event.type) { case SDL_WINDOWEVENT: { switch(event.window.event) { case SDL_WINDOWEVENT_RESIZED: case SDL_WINDOWEVENT_SIZE_CHANGED: { Graphics::setWidthHeight(event.window.data1, event.window.data2); } break; default: break; } } break; case SDL_QUIT: { Cpu::shutdown(); exit(0); } default: break; } } void handleNonModalDialogs(void) { // Save user wave if(_waveIndex == WaveUser) { copyWaveTable(_waveBuffer, _waveUser); } if(Dialog::getDialogItemIndex("Prev", _mouseState._x, _mouseState._y) != -1) { if(--_waveIndex < WaveUser) _waveIndex = Wave3; if(_waveIndex) loadWaveTable(uint16_t(_waveIndex - 1)); } else if(Dialog::getDialogItemIndex("Next", _mouseState._x, _mouseState._y) != -1) { if(++_waveIndex > Wave3) _waveIndex = WaveUser; if(_waveIndex) loadWaveTable(uint16_t(_waveIndex - 1)); } else if(Dialog::getDialogItemIndex("PlayW", _mouseState._x, _mouseState._y) != -1) { // Play internal wave if(_waveIndex) { playNote(uint8_t(_waveIndex - 1), 60, 30); } // Upload user wave to internal wave 0 play it, then restore else { uploadWaveTable(0, _waveBuffer); playNote(0, 60, 30); restoreWaveTable(0); } } else if(Dialog::getDialogItemIndex("PlayM", _mouseState._x, _mouseState._y) != -1) { _midiPlaying = !_midiPlaying; Midi::pause(!_midiPlaying); if(_midiPlaying) { Dialog::positionDialog("PlayM", 70, 1); Dialog::setDialogItemText("PlayM", 0, "Pause"); if(Midi::getStream() == nullptr && _midiBuffer) { Midi::setStream(nullptr, _midiBuffer, uint16_t(_midiBufferSize)); } } else { Dialog::positionDialog("PlayM", 71, 1); Dialog::setDialogItemText("PlayM", 0, "Play"); } } else if(Dialog::getDialogItemIndex("6Bit", _mouseState._x, _mouseState._y) != -1) { static bool quality = false; quality = !quality; if(!quality) { Cpu::enable6BitSound(Cpu::ROMv5a, false); Dialog::setDialogItemText("6Bit", 0, "4Bit"); } else { Cpu::enable6BitSound(Cpu::ROMv5a, true); Dialog::setDialogItemText("6Bit", 0, "6Bit"); } } else if(Dialog::getDialogItemIndex("LineM", _mouseState._x, _mouseState._y) != -1) { _midiLineMode = !_midiLineMode; (_midiLineMode) ? Dialog::setDialogItemText("LineM", 0, "Line ") : Dialog::setDialogItemText("LineM", 0, "Pixel"); } else if(Dialog::getDialogItemIndex("Reset", _mouseState._x, _mouseState._y) != -1) { if(_waveIndex) { restoreWaveTable(uint16_t(_waveIndex - 1)); loadWaveTable(uint16_t(_waveIndex - 1)); } } else if(Dialog::getDialogItemIndex("Erase", _mouseState._x, _mouseState._y) != -1) { if(_waveIndex) { for(int i=0; i= Editor::getFileEntriesSize()) { return; } else { Editor::FileType fileType = Editor::getCurrentFileEntryType(); switch(fileType) { case Editor::File: loadCorrectFileType(Editor::getCurrentFileEntryName()); break; case Editor::Dir: Editor::changeBrowseDirectory(); break; default: break; } } _refreshScreen = true; } if(_mouseState._state == SDL_BUTTON_X1) { Menu::captureMenu("Audio", _mouseState._x, _mouseState._y); } } void handleMouseButtonUp(const SDL_Event& event) { UNREFERENCED_PARAM(event); handleMenu(); handleNonModalDialogs(); _refreshScreen = true; } void handleMouseWheel(const SDL_Event& event) { if(event.wheel.y > 0) Editor::handleBrowsePageUp(1); if(event.wheel.y < 0) Editor::handleBrowsePageDown(1); _refreshScreen = true; } void handleKey(const SDL_Event& event) { char keyCode = event.text.text[0]; if(keyCode >= 32 && keyCode <= 126) { // Accept alpha for first char and alphanumeric or underscore or period for rest if((_commandLine[_cmdLineType].size() == 0 && _commandCharIndex[_cmdLineType] == 0 && (isalpha(keyCode) || keyCode == 32)) || (_commandLine[_cmdLineType].size() != 0 && (isalnum(keyCode) || keyCode == 32 || keyCode == '_' || keyCode == '.'))) { // Max of one period in a filename if(keyCode == '.' && std::count(_commandLine[_cmdLineType].begin(), _commandLine[_cmdLineType].end(), '.') == 1) return; if(_commandLine[_cmdLineType].size() >= MAX_COMMAND_CHARS-1) return; _commandLine[_cmdLineType].insert(_commandLine[_cmdLineType].begin() + _commandCharIndex[_cmdLineType], char(keyCode)); _commandCharIndex[_cmdLineType]++; } } } void handleKeyDown(SDL_Keycode keyCode, Uint16 keyMod) { // Leave audio editor if(keyCode == Editor::getEmulatorScanCode("AudioEditor") && keyMod == Editor::getEmulatorKeyMod("AudioEditor")) { leave(LeavePrev); } // Quit else if(keyCode == Editor::getEmulatorScanCode("Quit") && keyMod == Editor::getEmulatorKeyMod("Quit")) { Cpu::shutdown(); exit(0); } // Image editor else if(keyCode == Editor::getEmulatorScanCode("ImageEditor") && keyMod == Editor::getEmulatorKeyMod("ImageEditor")) { leave(LeaveImage); } // Terminal mode else if(keyCode == Editor::getEmulatorScanCode("Terminal") && keyMod == Editor::getEmulatorKeyMod("Terminal")) { leave(LeaveTerm); } if(keyMod == 0x0000) { switch(keyCode) { case SDLK_LEFT: prevCommandLineChar(); break; case SDLK_RIGHT: nextCommandLineChar(); break; case SDLK_HOME: homeCommandLineChar(); break; case SDLK_END: endCommandLineChar(); break; case SDLK_DELETE: deleteCommandLineChar(); break; case SDLK_BACKSPACE: backspaceCommandLineChar(); break; case '\r': case '\n': loadCorrectFileType(&_commandLine[_cmdLineType]); default : break; } } } void handleKeyUp(SDL_Keycode keyCode, Uint16 keyMod) { // Help screen if(keyCode == Editor::getEmulatorScanCode("Help") && keyMod == Editor::getEmulatorKeyMod("Help")) { static bool helpScreen = false; helpScreen = !helpScreen; Graphics::setDisplayHelpScreen(helpScreen); } // Disassembler else if(keyCode == Editor::getEmulatorScanCode("Disassembler") && keyMod == Editor::getEmulatorKeyMod("Disassembler")) { leave(LeaveDasm); } // Browser else if(keyCode == Editor::getEmulatorScanCode("Browse") && keyMod == Editor::getEmulatorKeyMod("Browse")) { leave(LeaveLoad); } // Hex monitor else if(keyCode == Editor::getEmulatorScanCode("HexMonitor") && keyMod == Editor::getEmulatorKeyMod("HexMonitor")) { leave(LeaveHex); } } void handleInput(void) { // Mouse button state _mouseState._state = SDL_GetMouseState(&_mouseState._x, &_mouseState._y); //chooseCmdLineOnMouse(_mouseState._x, _mouseState._y); SDL_Event event; while(SDL_PollEvent(&event)) { SDL_Keycode keyCode = event.key.keysym.sym; Uint16 keyMod = event.key.keysym.mod & (KMOD_LCTRL | KMOD_LALT | KMOD_LSHIFT); _mouseState._state = SDL_GetMouseState(&_mouseState._x, &_mouseState._y); Editor::setMouseState(_mouseState); handleGuiEvents(event); switch(event.type) { case SDL_MOUSEBUTTONDOWN: handleMouseButtonDown(event); break; case SDL_MOUSEBUTTONUP: handleMouseButtonUp(event); break; case SDL_MOUSEWHEEL: handleMouseWheel(event); break; case SDL_TEXTINPUT: handleKey(event); break; case SDL_KEYDOWN: handleKeyDown(keyCode, keyMod); break; case SDL_KEYUP: handleKeyUp(keyCode, keyMod); break; default: break; } } switch(_mouseState._state) { case SDL_BUTTON_LEFT: handleMouseLeftButtonDown(); break; case SDL_BUTTON_X1: handleMouseRightButtonDown(); break; default: break; } } void process(void) { bool vBlank = false; if(_firstTimeRender) { _browserPathBackup = Editor::getBrowserPath(); Editor::setBrowserPath(_browserPath); refreshScreen(); } if(_refreshScreen) refreshScreen(); if(Midi::getStream()) { vBlank = Cpu::process(true); if(vBlank) Midi::play(); } if(Midi::getStream() == nullptr && _midiPlaying) { refreshScreen(); _midiPlaying = false; Dialog::positionDialog("PlayM", 71, 1); Dialog::setDialogItemText("PlayM", 0, "Play"); } if(Midi::getStream() == nullptr || vBlank) { refreshWave(); refreshMidi(); refreshUi(); handleInput(); Graphics::render(!vBlank); } } #endif }