From 5aeebd61219573282d28f49d915262342c9f5fe6 Mon Sep 17 00:00:00 2001 From: VinsCool Date: Sat, 11 Mar 2023 10:22:43 -0500 Subject: [PATCH] Prototype WAV export added. - Note that the Altirra POKEY emulation is not compatible currently --- cpp_src/Atari6502.cpp | 10 +++++ cpp_src/Atari6502.h | 1 + cpp_src/General.h | 15 +++++-- cpp_src/IO_Song.cpp | 90 ++++++++++++++++++++++++++++++++++--- cpp_src/Rmt.vcxproj | 2 + cpp_src/Rmt.vcxproj.filters | 6 +++ cpp_src/Song.h | 2 + cpp_src/WaveFile.cpp | 73 ++++++++++++++++++++++++++++++ cpp_src/WaveFile.h | 18 ++++++++ cpp_src/XPokey.cpp | 28 ++++++++++++ cpp_src/XPokey.h | 1 + 11 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 cpp_src/WaveFile.cpp create mode 100644 cpp_src/WaveFile.h diff --git a/cpp_src/Atari6502.cpp b/cpp_src/Atari6502.cpp index de77428..ff346ef 100644 --- a/cpp_src/Atari6502.cpp +++ b/cpp_src/Atari6502.cpp @@ -169,6 +169,16 @@ void Atari_PlayRMT() C6502_JSR(&adr,&a,&x,&y,&cycles); //adr,A,X,Y } +void Atari_SetPokey() +{ + if (!g_is6502) return; + + WORD adr = RMT_SETPOKEY; + BYTE a = 0, x = 0, y = 0; + int cycles = (g_ntsc) ? MAXSCREENCYCLES_NTSC : MAXSCREENCYCLES_PAL; + C6502_JSR(&adr, &a, &x, &y, &cycles); +} + void Atari_Silence() { if (!g_is6502) return; diff --git a/cpp_src/Atari6502.h b/cpp_src/Atari6502.h index da3ce94..1cee244 100644 --- a/cpp_src/Atari6502.h +++ b/cpp_src/Atari6502.h @@ -39,6 +39,7 @@ extern int SaveBinaryBlock(std::ofstream& out, unsigned char* memory, WORD fromA extern int Atari_LoadRMTRoutines(); extern int Atari_InitRMTRoutine(); extern void Atari_PlayRMT(); +extern void Atari_SetPokey(); extern void Atari_Silence(); extern void Atari_SetTrack_NoteInstrVolume(int t,int n,int i,int v); extern void Atari_SetTrack_Volume(int t,int v); diff --git a/cpp_src/General.h b/cpp_src/General.h index 455d8f4..bbeba17 100644 --- a/cpp_src/General.h +++ b/cpp_src/General.h @@ -168,6 +168,8 @@ #define IOTYPE_LZSS_SAP 12 #define IOTYPE_LZSS_XEX 13 +#define IOTYPE_WAV 20 + #define IOTYPE_TMC 101 //import TMC #define IOINSTR_RTI 1 //corresponding IOTYPE_RMT @@ -311,6 +313,7 @@ "SAP file + LZSS driver (*.sap)|*.sap|" \ "XEX Atari executable + LZSS driver (*.xex)|*.xex|" \ "Relocatable ASM for RMTPlayer (*.asm)|*.asm|" \ + "WAV audio file (*.wav)|*.wav|" \ "|" #define FILE_EXPORT_FILTER_IDX_STRIPPED_RMT 1 #define FILE_EXPORT_FILTER_IDX_SIMPLE_ASM 2 @@ -318,11 +321,17 @@ #define FILE_EXPORT_FILTER_IDX_LZSS 4 #define FILE_EXPORT_FILTER_IDX_SAP 5 #define FILE_EXPORT_FILTER_IDX_XEX 6 +//#define FILE_EXPORT_FILTER_IDX_RELOC_ASM 7 +//#define FILE_EXPORT_FILTER_IDX_MIN FILE_EXPORT_FILTER_IDX_STRIPPED_RMT +//#define FILE_EXPORT_FILTER_IDX_MAX FILE_EXPORT_FILTER_IDX_RELOC_ASM +//#define FILE_EXPORT_EXTENSIONS_ARRAY { ".rmt",".asm",".sapr",".lzss",".sap",".xex",".asm" }; +//#define FILE_EXPORT_EXTENSIONS_LENGTH_ARRAY { 4, 4, 5, 5, 4, 4, 4} #define FILE_EXPORT_FILTER_IDX_RELOC_ASM 7 +#define FILE_EXPORT_FILTER_IDX_WAV 8 #define FILE_EXPORT_FILTER_IDX_MIN FILE_EXPORT_FILTER_IDX_STRIPPED_RMT -#define FILE_EXPORT_FILTER_IDX_MAX FILE_EXPORT_FILTER_IDX_RELOC_ASM -#define FILE_EXPORT_EXTENSIONS_ARRAY { ".rmt",".asm",".sapr",".lzss",".sap",".xex",".asm" }; -#define FILE_EXPORT_EXTENSIONS_LENGTH_ARRAY { 4, 4, 5, 5, 4, 4, 4} +#define FILE_EXPORT_FILTER_IDX_MAX FILE_EXPORT_FILTER_IDX_WAV +#define FILE_EXPORT_EXTENSIONS_ARRAY { ".rmt",".asm",".sapr",".lzss",".sap",".xex",".asm",".wav" }; +#define FILE_EXPORT_EXTENSIONS_LENGTH_ARRAY { 4, 4, 5, 5, 4, 4, 4, 4} // ---------------------------------------------------------------------------- // Pokey play to buffer diff --git a/cpp_src/IO_Song.cpp b/cpp_src/IO_Song.cpp index 0f4f737..d0ef970 100644 --- a/cpp_src/IO_Song.cpp +++ b/cpp_src/IO_Song.cpp @@ -26,6 +26,8 @@ #include "ChannelControl.h" #include "RmtMidi.h" +#include "Wavefile.h" + extern CInstruments g_Instruments; extern CTrackClipboard g_TrackClipboard; extern CXPokey g_Pokey; @@ -548,6 +550,7 @@ void CSong::FileExportAs() if (m_lastExportType == IOTYPE_LZSS_SAP) dlg.m_ofn.nFilterIndex = FILE_EXPORT_FILTER_IDX_SAP; if (m_lastExportType == IOTYPE_LZSS_XEX) dlg.m_ofn.nFilterIndex = FILE_EXPORT_FILTER_IDX_XEX; if (m_lastExportType == IOTYPE_ASM_RMTPLAYER) dlg.m_ofn.nFilterIndex = FILE_EXPORT_FILTER_IDX_RELOC_ASM; + if (m_lastExportType == IOTYPE_WAV) dlg.m_ofn.nFilterIndex = FILE_EXPORT_FILTER_IDX_WAV; // If not ok, nothing will be saved if (dlg.DoModal() == IDOK) @@ -608,6 +611,11 @@ void CSong::FileExportAs() case FILE_EXPORT_FILTER_IDX_RELOC_ASM: // Relocatable ASM for RMTPlayer m_lastExportType = IOTYPE_ASM_RMTPLAYER; break; + + case FILE_EXPORT_FILTER_IDX_WAV: + m_lastExportType = IOTYPE_WAV; + break; + } // Save the file using the set parameters @@ -1314,14 +1322,9 @@ bool CSong::ExportV2(std::ofstream& ou, int iotype, LPCTSTR filename) case IOTYPE_ASM_RMTPLAYER: return ExportAsRelocatableAsmForRmtPlayer(ou, &exportDesc); case IOTYPE_SAPR: return ExportSAP_R(ou); case IOTYPE_LZSS: return ExportLZSS(ou, filename); -/* - if (MessageBox(g_hwnd, "Process using ExportCompactLZSS?\nMany files will be created at once.", "ExportLZSS", MB_YESNO | MB_ICONINFORMATION) == IDYES) - return ExportCompactLZSS(ou, filename); // This is used for experimental stuff, however it does output valid LZSS data if needed - else - return ExportLZSS(ou, filename); // Original LZSS export method -*/ case IOTYPE_LZSS_SAP: return ExportLZSS_SAP(ou); case IOTYPE_LZSS_XEX: return ExportLZSS_XEX(ou); + case IOTYPE_WAV: return ExportWav(ou, filename); } return false; // Failed @@ -1594,5 +1597,80 @@ bool CSong::WriteToXEX(struct TExportMetadata* metadata) metadata->displayRasterbar = dlg.m_meter; metadata->autoRegion = dlg.m_region_auto; + return true; +} + +bool CSong::ExportWav(std::ofstream& ou, LPCTSTR filename) +{ + CWaveFile wavefile{}; + + BYTE* buffer = NULL; + BYTE* streambuffer = NULL; + int length = 0, frames = 0, offset = 0; + int frameSize = (g_tracks4_8 == 8) ? 18 : 9; // SAP-R bytes to copy, Stereo doubles the number + + ou.close(); // hack, just to be able to actually use the filename for now... + + if (!wavefile.OpenFile((LPTSTR)filename, OUTPUTFREQ, BITRESOLUTION, CHANNELS)) + { + MessageBox(g_hwnd, "Wav file could not be created!", "ExportWav", MB_ICONWARNING); + return false; + } + + // Dump the POKEY registers from full song playback + DumpSongToPokeyBuffer(); + + Atari_InitRMTRoutine(); // Reset the Atari memory + SetChannelOnOff(-1, 1); // Unmute all channels + + // Create the sound buffer to copy from and to + buffer = new BYTE[BUFFER_SIZE]; + memset(buffer, 0x80, BUFFER_SIZE); + + // Busy writing! TODO: Fix the timing overlap causing conflicts + g_PokeyStream.SetState(g_PokeyStream.STREAM_STATE::WRITE); + + while (frames < g_PokeyStream.GetFirstCountPoint()) + { + // Copy the SAP-R bytes to g_atarimem for this frame + streambuffer = g_PokeyStream.GetStreamBuffer() + frames * frameSize; + + //for (int i = 0; i < frameSize; i++) + //{ + // g_atarimem[0xd200 + i] = streambuffer[i]; + //} + + g_atarimem[RMTPLAYR_TRACKN_AUDF + 0] = streambuffer[0x00]; + g_atarimem[RMTPLAYR_TRACKN_AUDF + 1] = streambuffer[0x02]; + g_atarimem[RMTPLAYR_TRACKN_AUDF + 2] = streambuffer[0x04]; + g_atarimem[RMTPLAYR_TRACKN_AUDF + 3] = streambuffer[0x06]; + g_atarimem[RMTPLAYR_TRACKN_AUDC + 0] = streambuffer[0x01]; + g_atarimem[RMTPLAYR_TRACKN_AUDC + 1] = streambuffer[0x03]; + g_atarimem[RMTPLAYR_TRACKN_AUDC + 2] = streambuffer[0x05]; + g_atarimem[RMTPLAYR_TRACKN_AUDC + 3] = streambuffer[0x07]; + g_atarimem[RMTPLAYR_V_AUDCTL] = streambuffer[0x08]; + + // Fill the POKEY buffer with 1 rendered chunk + g_Pokey.RenderSoundV2(m_instrumentSpeed, buffer, length); + + //Sleep(16); + + // Write the buffer to WAV file + wavefile.WriteWave(buffer, length); + + // Update the PokeyStream offset for the next frame + //frames += frameSize; + frames++; + } + + // Clear the SAP-R dumper memory and reset RMT routines + g_PokeyStream.FinishedRecording(); + + // Finished doing WAV things... + wavefile.CloseFile(); + + // Also make sure to delete the buffer once it's no longer needed + delete buffer; + return true; } \ No newline at end of file diff --git a/cpp_src/Rmt.vcxproj b/cpp_src/Rmt.vcxproj index 2270db4..1c84c7a 100644 --- a/cpp_src/Rmt.vcxproj +++ b/cpp_src/Rmt.vcxproj @@ -511,6 +511,7 @@ + _DEBUG;WIN32;_WINDOWS;NO_CONSOL_SOUND;NO_VOL_ONLY;STEREO;_MBCS;_AFXDLL _DEBUG;WIN32;_WINDOWS;NO_CONSOL_SOUND;NO_VOL_ONLY;STEREO;_MBCS;_AFXDLL @@ -564,6 +565,7 @@ + diff --git a/cpp_src/Rmt.vcxproj.filters b/cpp_src/Rmt.vcxproj.filters index ec75ba2..eef0680 100644 --- a/cpp_src/Rmt.vcxproj.filters +++ b/cpp_src/Rmt.vcxproj.filters @@ -165,6 +165,9 @@ Source Files\Sound Generator + + Source Files\Sound Generator + @@ -263,6 +266,9 @@ Source Files\IO - Load/Save/Export/Import/Midi + + Source Files\Sound Generator + diff --git a/cpp_src/Song.h b/cpp_src/Song.h index 144c399..8cd7fa6 100644 --- a/cpp_src/Song.h +++ b/cpp_src/Song.h @@ -237,6 +237,8 @@ class CSong bool ExportLZSS_SAP(std::ofstream& ou); bool ExportLZSS_XEX(std::ofstream& ou); + bool ExportWav(std::ofstream& ou, LPCTSTR filename); + void DumpSongToPokeyBuffer(int playmode = MPLAY_SONG, int songline = 0, int trackline = 0); int BruteforceOptimalLZSS(unsigned char* src, int srclen, unsigned char* dst); diff --git a/cpp_src/WaveFile.cpp b/cpp_src/WaveFile.cpp new file mode 100644 index 0000000..d8c6ecb --- /dev/null +++ b/cpp_src/WaveFile.cpp @@ -0,0 +1,73 @@ + +#include "StdAfx.h" +#include "WaveFile.h" + +bool CWaveFile::OpenFile(LPTSTR Filename, int SampleRate, int SampleSize, int Channels) +{ + int nError; + + WaveFormat.wf.wFormatTag = WAVE_FORMAT_PCM; + WaveFormat.wf.nChannels = Channels; + WaveFormat.wf.nSamplesPerSec = SampleRate; + WaveFormat.wBitsPerSample = SampleSize; + WaveFormat.wf.nBlockAlign = (WaveFormat.wBitsPerSample / 8) * WaveFormat.wf.nChannels; + WaveFormat.wf.nAvgBytesPerSec = WaveFormat.wf.nSamplesPerSec * WaveFormat.wf.nBlockAlign; + + hmmioOut = mmioOpen(Filename, NULL, MMIO_ALLOCBUF | MMIO_READWRITE | MMIO_CREATE); + + ckOutRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E'); + ckOutRIFF.cksize = 0; + + nError = mmioCreateChunk(hmmioOut, &ckOutRIFF, MMIO_CREATERIFF); + + if (nError != MMSYSERR_NOERROR) + return false; + + ckOut.ckid = mmioFOURCC('f', 'm', 't', ' '); + ckOut.cksize = sizeof(PCMWAVEFORMAT); + + nError = mmioCreateChunk(hmmioOut, &ckOut, 0); + + if (nError != MMSYSERR_NOERROR) + return false; + + mmioWrite(hmmioOut, (HPSTR)&WaveFormat, sizeof(PCMWAVEFORMAT)); + mmioAscend(hmmioOut, &ckOut, 0); + + ckOut.ckid = mmioFOURCC('d', 'a', 't', 'a'); + ckOut.cksize = 0; + + nError = mmioCreateChunk(hmmioOut, &ckOut, 0); + + if (nError != MMSYSERR_NOERROR) + return false; + + mmioGetInfo(hmmioOut, &mmioinfoOut, 0); + + return true; +} + +void CWaveFile::CloseFile() +{ + mmioinfoOut.dwFlags |= MMIO_DIRTY; + mmioSetInfo(hmmioOut, &mmioinfoOut, 0); + mmioAscend(hmmioOut, &ckOut, 0); + mmioAscend(hmmioOut, &ckOutRIFF, 0); + mmioSeek(hmmioOut, 0, SEEK_SET); + mmioDescend(hmmioOut, &ckOutRIFF, NULL, 0); + mmioClose(hmmioOut, 0); +} + +void CWaveFile::WriteWave(BYTE* Data, int Size) +{ + for (int i = 0; i < Size; i++) + { + if (mmioinfoOut.pchNext == mmioinfoOut.pchEndWrite) + { + mmioinfoOut.dwFlags |= MMIO_DIRTY; + mmioAdvance(hmmioOut, &mmioinfoOut, MMIO_WRITE); + } + *((BYTE*)mmioinfoOut.pchNext) = *((BYTE*)Data + i); + mmioinfoOut.pchNext++; + } +} \ No newline at end of file diff --git a/cpp_src/WaveFile.h b/cpp_src/WaveFile.h new file mode 100644 index 0000000..23abc2c --- /dev/null +++ b/cpp_src/WaveFile.h @@ -0,0 +1,18 @@ +#pragma once + +#include "StdAfx.h" +#include + +class CWaveFile +{ +public: + bool OpenFile(LPTSTR Filename, int SampleRate, int SampleSize, int Channels); + void CloseFile(); + void WriteWave(BYTE* Data, int Size); + +private: + PCMWAVEFORMAT WaveFormat; + MMCKINFO ckOutRIFF, ckOut; + MMIOINFO mmioinfoOut; + HMMIO hmmioOut; +}; \ No newline at end of file diff --git a/cpp_src/XPokey.cpp b/cpp_src/XPokey.cpp index 637d0cf..877235d 100644 --- a/cpp_src/XPokey.cpp +++ b/cpp_src/XPokey.cpp @@ -213,6 +213,34 @@ BOOL CXPokey::RenderSound1_50(int instrspeed) return 0; } +// Initial WAV recorder process +// NOTE: This does NOT work with the Altirra plugin due to it hijacking the soundbuffer with its own thing... +void CXPokey::RenderSoundV2(int instrspeed, BYTE* buffer, int& length) +{ + int rendersize = CHUNK_SIZE; + int renderpartsize = 0; + int renderoffset = 0; + + for (; instrspeed > 0; instrspeed--) + { + Atari_SetPokey(); + MemToPokey(); + renderpartsize = (rendersize / instrspeed) & 0xfffe; + + switch (m_soundDriverId) + { + case SOUND_DRIVER_SA_POKEY: + Pokey_Process(buffer + renderoffset, (unsigned short)renderpartsize); + rendersize -= renderpartsize; + renderoffset += renderpartsize; + break; + } + } + + // Copy the actually generated sample data to buffer + length = renderoffset; +} + BOOL CXPokey::InitSound() { if (m_soundDriverId || m_pokey_dll) DeInitSound(); // Just in case, everything must be cleared before initialising diff --git a/cpp_src/XPokey.h b/cpp_src/XPokey.h index 196008b..35a9809 100644 --- a/cpp_src/XPokey.h +++ b/cpp_src/XPokey.h @@ -35,6 +35,7 @@ class CXPokey BOOL DeInitSound(); BOOL ReInitSound(); BOOL RenderSound1_50(int instrspeed); + void RenderSoundV2(int instrspeed, BYTE* buffer, int& length); void MemToPokey(); bool IsSoundDriverLoaded() { return m_soundDriverId; }