gigatron/rom/Contrib/at67/audio.cpp
2025-01-28 19:17:01 +03:00

1199 lines
40 KiB
C++

#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <math.h>
#include <algorithm>
#include <atomic>
#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 <SDL.h>
#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<int64_t> _audioInIndex(AUDIO_SAMPLES*2);
std::atomic<int64_t> _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<length/2; i++)
{
if(_audioOutIndex % AUDIO_BUFFER_SIZE == _audioInIndex % AUDIO_BUFFER_SIZE)
{
sdl2Stream[i] = _audioSamples[(_audioOutIndex-1) % AUDIO_BUFFER_SIZE];
}
else
{
sdl2Stream[i] = _audioSamples[_audioOutIndex++ % AUDIO_BUFFER_SIZE];
}
}
}
void initialise(void)
{
// Loader config
INIReader iniReader(Loader::getExePath() + "/" + AUDIO_CONFIG_INI);
_configIniReader = iniReader;
if(_configIniReader.ParseError() == 0)
{
// Parse Loader Keys
enum Section {Audio};
std::map<std::string, Section> 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<GIGA_NUM_CHANNELS; i++)
{
Cpu::setRAM(GIGA_CH0_WAV_A + i*GIGA_CHANNEL_OFFSET, 0x00); // ADD modulation, (volume)
Cpu::setRAM(GIGA_CH0_WAV_X + i*GIGA_CHANNEL_OFFSET, 0x02); // waveform index and XOR index modulation, (noise)
Cpu::setRAM(GIGA_CH0_KEY_L + i*GIGA_CHANNEL_OFFSET, 0x00); // low frequency look up from ROM
Cpu::setRAM(GIGA_CH0_KEY_H + i*GIGA_CHANNEL_OFFSET, 0x00); // high frequency look up from ROM
Cpu::setRAM(GIGA_CH0_OSC_L + i*GIGA_CHANNEL_OFFSET, 0x00); // low internal oscillator
Cpu::setRAM(GIGA_CH0_OSC_H + i*GIGA_CHANNEL_OFFSET, 0x00); // high internal oscillator
}
}
void fillCallbackBuffer(void)
{
_audioSamples[_audioInIndex++ % AUDIO_BUFFER_SIZE] = (Cpu::getXOUT() & XOUT_MASK) <<6;
}
void fillBuffer(void)
{
_audioSamples[_audioInIndex++] = (Cpu::getXOUT() & XOUT_MASK) <<6;
if(_audioInIndex == AUDIO_SAMPLES)
{
playBuffer();
_audioInIndex = 0;
}
}
void playBuffer(void)
{
SDL_QueueAudio(_audioDevice, &_audioSamples[0], uint32_t(_audioInIndex) <<1);
_audioInIndex = 0;
}
void playSample(void)
{
uint16_t sample = (Cpu::getXOUT() & XOUT_MASK) <<6;
SDL_QueueAudio(_audioDevice, &sample, 2);
}
void clearQueue(void)
{
SDL_ClearQueuedAudio(_audioDevice);
}
//**********************************************************************************************************************
//* Editor
//**********************************************************************************************************************
#ifndef STAND_ALONE
enum MenuItem {MenuLoadMidi=0, MenuLoadWave, MenuSaveWave, MenuEraseWave};
enum DialogItem {DialogFile=2, DialogNo=5, DialogYes=6};
enum WaveIndex {WaveUser=0, Wave0, Wave1, Wave2, Wave3};
enum SuffixType {GtWav=0, GtMid, GtMidi};
enum CmdLineType {CmdLineWave, CmdLineMidi, NumCmdLineTypes};
enum LeaveMode {LeavePrev, LeaveHex, LeaveDasm, LeaveImage, LeaveTerm, LeaveLoad};
const std::vector<std::string> _waveStrs = {"User", "Wav0", "Wav1", "Wav2", "Wav3"};
const std::vector<std::string> _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<std::string> menuItems = {" AUDIO ", "Load Midi", "Load Wave", "Save Wave", "Del Wave"};
Menu::createMenu("Audio", menuItems, 9, 5);
// Overwrite dialog
std::vector<Dialog::Item> 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<MAX_WAVE_X; i++)
{
dst[i] = src[i];
}
}
void loadWaveTable(uint16_t wave)
{
wave = wave & 3;
uint16_t address = 0x0700 + wave;
for(uint16_t i=0; i<MAX_WAVE_X; i++)
{
_waveBuffer[i] = Cpu::getRAM(address + i*4);
}
}
void uploadWaveTable(uint16_t waveTable, uint8_t waveBuffer[])
{
int j = 0;
waveTable = waveTable & 3;
for(uint16_t i=waveTable; i<256; i+=4) Cpu::setRAM(0x0700 + i, waveBuffer[j++]);
}
void playNote(uint8_t wave, uint16_t note, int duration)
{
wave = wave & 3;
Cpu::setRAM(GIGA_CH0_WAV_X, wave);
Cpu::setRAM(GIGA_SOUND_TIMER, uint8_t(duration));
note = (note - 11) * 2;
uint16_t romNote = Cpu::getROM16(note + 0x0900, 1);
Cpu::setRAM16(GIGA_CH0_KEY_L, romNote);
while(Cpu::getRAM(GIGA_SOUND_TIMER))
{
Cpu::process(true);
}
}
void refreshScreen(void)
{
_firstTimeRender = false;
_refreshScreen = false;
Graphics::clearScreen(0x22222222);
Graphics::drawLine(0, 43, GIGA_WIDTH-1, 43, 0x00000000);
Graphics::drawLine(0, 119, GIGA_WIDTH-1, 119, 0x00000000);
// Midi waveform window
Graphics::drawLine(BORDER_X1, 39, BORDER_X2, 39, 0x88888888);
Graphics::drawLine(BORDER_X1, 6, BORDER_X1, 39, 0x88888888);
Graphics::drawLine(BORDER_X1, 6, BORDER_X2, 6, 0x88888888);
Graphics::drawLine(BORDER_X2, 6, BORDER_X2, 39, 0x88888888);
// Wave waveform window
Graphics::drawLine(BORDER_X1, BORDER_Y2, BORDER_X2, BORDER_Y2, 0x88888888);
Graphics::drawLine(BORDER_X1, BORDER_Y1, BORDER_X1, BORDER_Y2, 0x88888888);
Graphics::drawLine(BORDER_X1, BORDER_Y1, BORDER_X2, BORDER_Y1, 0x88888888);
Graphics::drawLine(BORDER_X2, BORDER_Y1, BORDER_X2, BORDER_Y2, 0x88888888);
Editor::browseDirectory(_suffixes);
_browserPath = Editor::getBrowserPath();
}
void refreshWave(void)
{
Graphics::rectFill(BORDER_X1+1, BORDER_Y1+1, BORDER_X2, BORDER_Y2, 0x33333333);
for(int i=0; i<MAX_WAVE_X; i++)
{
Graphics::drawPixel(uint8_t((BORDER_X1 + 1) + i*2), uint8_t((BORDER_Y2 - 1) - (_waveBuffer[i] & 63)), 0xBBBBBBBB);
Graphics::drawPixel(uint8_t((BORDER_X1 + 1) + i*2 + 1), uint8_t((BORDER_Y2 - 1) - (_waveBuffer[i] & 63)), 0xBBBBBBBB);
}
}
void refreshMidi(void)
{
Graphics::rectFill(BORDER_X1+1, 7, BORDER_X2, 39, 0x33333333);
int end = (_midiLineMode) ? 127 : 128;
for(int i=0; i<end; i++) // AUDIO_BUFFER_SIZE = 2048
{
uint8_t sample0 = (_audioSamples[(i+0) <<2] >>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<MAX_WAVE_X; i++) _waveBuffer[i] = 0;
uploadWaveTable(int16_t(_waveIndex - 1), _waveBuffer);
}
else
{
for(int i=0; i<MAX_WAVE_X; i++) _waveUser[i] = 0;
}
_refreshScreen = true;
}
// Restore user wave
if(_waveIndex == WaveUser)
{
copyWaveTable(_waveUser, _waveBuffer);
}
}
int handleModalDialog(const std::string& name)
{
// Mouse button state
_mouseState._state = SDL_GetMouseState(&_mouseState._x, &_mouseState._y);
SDL_Event event;
int dialogItem = -1;
while(SDL_PollEvent(&event))
{
_mouseState._state = SDL_GetMouseState(&_mouseState._x, &_mouseState._y);
handleGuiEvents(event);
switch(event.type)
{
case SDL_MOUSEBUTTONUP: dialogItem = Dialog::getDialogItemIndex(name, _mouseState._x, _mouseState._y); break;
default: break;
}
}
return dialogItem;
}
bool loadWaveFile(std::string* filenamePtr)
{
if(filenamePtr == nullptr && _commandLine[CmdLineWave].size() == 0)
{
fprintf(stderr, "Audio::loadWaveFile() : no file to load\n");
return false;
}
// Browser get priority over command line
std::string filename;
if(filenamePtr != nullptr)
{
filename = *filenamePtr;
_commandLine[CmdLineWave] = filename;
_commandCharIndex[CmdLineWave] = (_commandLine[CmdLineWave].size()) ? int(_commandLine[CmdLineWave].size()) : 0;
}
else
{
filename = _commandLine[CmdLineWave];
}
// Read
std::string filepath = _browserPath + filename;
std::ifstream infile(filepath, std::ios::binary | std::ios::in);
if(!infile.is_open())
{
fprintf(stderr, "Audio::loadWaveFile() : failed to open file '%s' for reading\n", filepath.c_str());
return false;
}
infile.read((char *)&_waveBuffer, MAX_WAVE_X);
if(infile.bad() || infile.fail())
{
fprintf(stderr, "Audio::loadWaveFile() : read error in file '%s'\n", filepath.c_str());
return false;
}
if(_waveIndex) uploadWaveTable(int16_t(_waveIndex - 1), _waveBuffer);
_cmdLineType = CmdLineWave;
//fprintf(stderr, "Audio::loadWaveFile() : Loaded wave file '%s'\n", filepath.c_str());
return true;
}
bool saveWaveFile(void)
{
if(_commandLine[CmdLineWave].size() == 0)
{
fprintf(stderr, "Audio::saveWaveFile() : no file to save\n");
return false;
}
std::string filepath = _browserPath + _commandLine[CmdLineWave];
// Detect overwrite
std::ifstream infile(filepath, std::ios::binary | std::ios::in);
if(infile.is_open())
{
_refreshScreen = true;
refreshScreen();
refreshWave();
// Update dialog filename item
Dialog::Item dialogItem;
Dialog::getDialogItem("Overwrite", DialogFile, dialogItem);
dialogItem.setText(_commandLine[CmdLineWave]);
Dialog::setDialogItem("Overwrite", DialogFile, dialogItem);
Dialog::positionDialog("Overwrite", 62, 50);
int dialogItemIndex = -1;
do
{
Editor::getMouseState(_mouseState);
dialogItemIndex = handleModalDialog("Overwrite");
Dialog::renderDialog("Overwrite", _mouseState._x, _mouseState._y);
Graphics::render(true);
}
while(dialogItemIndex == -1);
infile.close();
if(dialogItemIndex == DialogNo) return false;
}
// Write
std::ofstream outfile(filepath, std::ios::binary | std::ios::out);
if(!outfile.is_open())
{
fprintf(stderr, "Audio::saveWaveFile() : failed to open file for writing '%s' for writing\n", filepath.c_str());
return false;
}
outfile.write((char *)&_waveBuffer, MAX_WAVE_X);
if(outfile.bad() || outfile.fail())
{
fprintf(stderr, "Audio::saveWaveFile() : write error in file '%s'\n", filepath.c_str());
return false;
}
return true;
}
bool loadMidiFile(const std::string* filenamePtr)
{
Audio::initialiseChannels();
std::string filename;
if(filenamePtr != nullptr)
{
filename = *filenamePtr;
_commandLine[CmdLineMidi] = filename;
_commandCharIndex[CmdLineMidi] = (_commandLine[CmdLineMidi].size()) ? int(_commandLine[CmdLineMidi].size()) : 0;
}
if(filename == "")
{
fprintf(stderr, "Audio::loadMidiFile() : no file to load\n");
return false;
}
// Read
std::string filepath = _browserPath + filename;
if(!Midi::loadFile(filepath, _midiBuffer, _midiBufferSize)) return false;
if(!Midi::setStream(filenamePtr, _midiBuffer, uint16_t(_midiBufferSize))) return false;
Dialog::positionDialog("PlayM", 70, 1);
Dialog::setDialogItemText("PlayM", 0, "Pause");
_midiPlaying = true;
_cmdLineType = CmdLineMidi;
return true;
}
bool loadCorrectFileType(std::string* filenamePtr)
{
if(filenamePtr == nullptr)
{
switch(_cmdLineType)
{
case CmdLineMidi: return loadMidiFile(filenamePtr); break;
case CmdLineWave: return loadWaveFile(filenamePtr); break;
default: break;
}
return false;
}
for(int i=0; i<int(_suffixes.size()); i++)
{
std::string name = Expression::strUpper(*filenamePtr);
std::string suffix = Expression::strUpper(_suffixes[i]);
if(name.find(suffix) != std::string::npos)
{
switch(i)
{
case GtMid:
case GtMidi: return loadMidiFile(filenamePtr); break;
case GtWav: return loadWaveFile(filenamePtr); break;
default: break;
}
}
}
return false;
}
void handleMenu(void)
{
Menu::Menu menu;
if(!Menu::getMenu("Audio", menu)) return;
int menuItemIndex;
Menu::getMenuItemIndex("Audio", menuItemIndex);
switch(menuItemIndex)
{
case MenuLoadMidi: loadMidiFile(nullptr); break;
case MenuLoadWave: loadWaveFile(nullptr); break;
case MenuSaveWave: saveWaveFile(); break;
case MenuEraseWave:
{
for(int i=0; i<MAX_WAVE_X; i++) _waveBuffer[i] = 0;
if(_waveIndex)
{
uploadWaveTable(int16_t(_waveIndex - 1), _waveBuffer);
}
else
{
copyWaveTable(_waveBuffer, _waveUser);
}
_refreshScreen = true;
}
break;
default: break;
}
}
void handleMouseLeftButtonDown(void)
{
chooseCmdLineOnMouse(_mouseState._x, _mouseState._y);
int pixelX, pixelY;
if(isMouseInWave(_mouseState._x, _mouseState._y, pixelX, pixelY))
{
_waveBuffer[(pixelX - (BORDER_X1 + 1))/2] = uint8_t(64 - (pixelY - BORDER_Y1));
if(_waveIndex) uploadWaveTable(int16_t(_waveIndex - 1), _waveBuffer);
_refreshScreen = true;
}
}
void handleMouseRightButtonDown(void)
{
chooseCmdLineOnMouse(_mouseState._x, _mouseState._y);
int pixelX, pixelY;
if(isMouseInWave(_mouseState._x, _mouseState._y, pixelX, pixelY))
{
_waveBuffer[(pixelX - (BORDER_X1 + 1))/2] = 0;
if(_waveIndex) uploadWaveTable(int16_t(_waveIndex - 1), _waveBuffer);
_refreshScreen = true;
}
else
{
Menu::captureItem("Audio", _mouseState._x, _mouseState._y);
Menu::renderMenu("Audio");
}
}
void handleMouseButtonDown(const SDL_Event& event)
{
UNREFERENCED_PARAM(event);
if(_mouseState._state == SDL_BUTTON_LEFT)
{
// Handle browse UI
if(Editor::getPageUpButton())
{
Editor::handleBrowsePageUp(HEX_CHARS_Y);
}
else if(Editor::getPageDnButton())
{
Editor::handleBrowsePageDown(HEX_CHARS_Y);
}
// No loading/browsing if cursor is out of bounds
else if(Editor::getCursorY() < 0 || Editor::getCursorY() >= 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
}