From ee24062ba68dc72c5381bd84cfc892acf224f7bf Mon Sep 17 00:00:00 2001 From: John Young Date: Mon, 6 Jan 2020 08:24:02 +0000 Subject: [PATCH 01/23] Can now select folder for .scr files --- SpectREM/Info.txt | 2 +- SpectREM/SpectREM/Win32/PMDawn.cpp | 45 +++++++++++++++++++++++++++++ SpectREM/SpectREM/Win32/WinMain.cpp | 3 +- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/SpectREM/Info.txt b/SpectREM/Info.txt index 2580b55..0ad5b2f 100644 --- a/SpectREM/Info.txt +++ b/SpectREM/Info.txt @@ -20,7 +20,7 @@ ALT+F1 : Eject currently inserted tape F9 : Start/Stop currently inserted tape SHIFT+F9 : Rewind tape if inserted PAGEUP : Increase volume -PAGEDOWN : Decreas volume +PAGEDOWN : Decrease volume diff --git a/SpectREM/SpectREM/Win32/PMDawn.cpp b/SpectREM/SpectREM/Win32/PMDawn.cpp index f6eef75..1ea164b 100644 --- a/SpectREM/SpectREM/Win32/PMDawn.cpp +++ b/SpectREM/SpectREM/Win32/PMDawn.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace PMDawn { @@ -175,7 +176,51 @@ namespace PMDawn //----------------------------------------------------------------------------------------- + static std::string GetFolderUsingDialog(std::string initialFolder = "") + { + wchar_t *fold; + std::string fl; + bool error = true; + + IFileDialog* pfd; + if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd)))) + { + DWORD dwOptions; + if (SUCCEEDED(pfd->GetOptions(&dwOptions))) + { + pfd->SetOptions(dwOptions | FOS_PICKFOLDERS); // FOS_FORCEFILESYSTEM + } + if (SUCCEEDED(pfd->Show(NULL))) + { + IShellItem* psi; + if (SUCCEEDED(pfd->GetResult(&psi))) + { + if (!SUCCEEDED(psi->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &fold))) + { + MessageBoxA(NULL, "Failed to get folder path", "Error", NULL); + + } + else + { + // If we get here then we should have a path :) + error = false; + } + psi->Release(); + } + } + pfd->Release(); + } + if (!error) + { + std::wstring fstr(fold); + std::string fs(fstr.begin(), fstr.end()); + return fs; + } else + { + return ""; + } + } //----------------------------------------------------------------------------------------- diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index f8b0540..278b6c6 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -495,7 +495,8 @@ static void RunSlideshow(int secs) Sleep(1000); PMDawn::Log(PMDawn::LOG_INFO, "Running slideshow (" + std::to_string(secs) + " secs) from " + PMDawn::GetApplicationBasePath() + slideshowDirectory); fileList.clear(); - fileList = PMDawn::GetFilesInDirectory(PMDawn::GetApplicationBasePath() + slideshowDirectory, "*.scr"); + std::string fpath = PMDawn::GetFolderUsingDialog("meh"); + fileList = PMDawn::GetFilesInDirectory(fpath + "\\", "*.scr"); PMDawn::Log(PMDawn::LOG_DEBUG, "Found " + std::to_string(fileList.size()) + " matching files"); // iterate (randomly maybe) through the list of files as long as there is at least one file :) if (fileList.size() < 1) From c3fa66dd3d22bb113271ffd1666aa1d80604cb4b Mon Sep 17 00:00:00 2001 From: John Young Date: Mon, 6 Jan 2020 09:36:33 +0000 Subject: [PATCH 02/23] OpenSCR (open a single .scr file) now uses the new file dialog handler --- SpectREM/SpectREM/Win32/PMDawn.cpp | 45 +++++++++++++++++++++++++++++ SpectREM/SpectREM/Win32/WinMain.cpp | 26 ++++------------- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/SpectREM/SpectREM/Win32/PMDawn.cpp b/SpectREM/SpectREM/Win32/PMDawn.cpp index 1ea164b..3bd4447 100644 --- a/SpectREM/SpectREM/Win32/PMDawn.cpp +++ b/SpectREM/SpectREM/Win32/PMDawn.cpp @@ -224,7 +224,52 @@ namespace PMDawn //----------------------------------------------------------------------------------------- + static std::string GetFilenameUsingDialog(std::string initialFolder = "") + { + wchar_t* fold; + std::string fl; + bool error = true; + IFileDialog* pfd; + if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd)))) + { + DWORD dwOptions; + if (SUCCEEDED(pfd->GetOptions(&dwOptions))) + { + pfd->SetOptions(dwOptions ); // FOS_FORCEFILESYSTEM + } + if (SUCCEEDED(pfd->Show(NULL))) + { + IShellItem* psi; + if (SUCCEEDED(pfd->GetResult(&psi))) + { + if (!SUCCEEDED(psi->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &fold))) + { + MessageBoxA(NULL, "Failed to get file path", "Error", NULL); + + } + else + { + // If we get here then we should have a path :) + error = false; + } + psi->Release(); + } + } + pfd->Release(); + } + if (!error) + { + std::wstring fstr(fold); + std::string fs(fstr.begin(), fstr.end()); + + return fs; + } + else + { + return ""; + } + } //----------------------------------------------------------------------------------------- diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 278b6c6..15a36dd 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -453,35 +453,21 @@ static void OpenSCR() HardReset(); Sleep(1000); - OPENFILENAMEA ofn; - char szFile[_MAX_PATH]; - // Setup the ofn structure - ZeroMemory(&ofn, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = NULL; - ofn.lpstrFile = szFile; - ofn.lpstrFile[0] = '\0'; - ofn.nMaxFile = sizeof(szFile); - ofn.lpstrFilter = "All\0*.*\0Screen File\0*.SCR\0\0"; - ofn.nFilterIndex = 1; - ofn.lpstrFileTitle = NULL; - ofn.nMaxFileTitle = 0; - ofn.lpstrInitialDir = NULL; - ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + std::string scrPath = PMDawn::GetFilenameUsingDialog(""); - if (GetOpenFileNameA(&ofn)) + if (scrPath != "") { - ZXSpectrum::Response sR = m_pMachine->scrLoadWithPath(szFile); + ZXSpectrum::Response sR = m_pMachine->scrLoadWithPath(scrPath); if (sR.success) { Sleep(1); m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer); - PMDawn::Log(PMDawn::LOG_INFO, "Loaded .scr file - " + std::string(szFile)); + PMDawn::Log(PMDawn::LOG_INFO, "Loaded .scr file - " + std::string(scrPath)); } else { MessageBox(mainWindow, TEXT("Invalid SCR file"), TEXT("Gimme SCR's !!!"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); - PMDawn::Log(PMDawn::LOG_INFO, "Failed to load .scr file - " + std::string(szFile) + " > " + sR.responseMsg); + PMDawn::Log(PMDawn::LOG_INFO, "Failed to load .scr file - " + std::string(scrPath) + " > " + sR.responseMsg); return; } } @@ -495,7 +481,7 @@ static void RunSlideshow(int secs) Sleep(1000); PMDawn::Log(PMDawn::LOG_INFO, "Running slideshow (" + std::to_string(secs) + " secs) from " + PMDawn::GetApplicationBasePath() + slideshowDirectory); fileList.clear(); - std::string fpath = PMDawn::GetFolderUsingDialog("meh"); + std::string fpath = PMDawn::GetFolderUsingDialog(""); fileList = PMDawn::GetFilesInDirectory(fpath + "\\", "*.scr"); PMDawn::Log(PMDawn::LOG_DEBUG, "Found " + std::to_string(fileList.size()) + " matching files"); // iterate (randomly maybe) through the list of files as long as there is at least one file :) From 8a1703ac6dbc697bb83f8ca7fe75e3a9a574f9b6 Mon Sep 17 00:00:00 2001 From: John Young Date: Mon, 6 Jan 2020 09:40:22 +0000 Subject: [PATCH 03/23] Loading .sna/.z80/.tap now uses the new file open dialog --- SpectREM/SpectREM/Win32/WinMain.cpp | 33 ++++++++--------------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 15a36dd..f0d6d74 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -664,42 +664,27 @@ static void HardReset() static void LoadSnapshot() { - OPENFILENAMEA ofn; - char szFile[_MAX_PATH]; - - // Setup the ofn structure - ZeroMemory(&ofn, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = NULL; - ofn.lpstrFile = szFile; - ofn.lpstrFile[0] = '\0'; - ofn.nMaxFile = sizeof(szFile); - ofn.lpstrFilter = "All\0*.*\0Snapshot\0*.SNA\0Z80\0*.Z80\0Tapes\0*.TAP\0\0"; - ofn.nFilterIndex = 1; - ofn.lpstrFileTitle = NULL; - ofn.nMaxFileTitle = 0; - ofn.lpstrInitialDir = NULL; - ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + std::string filePath = PMDawn::GetFilenameUsingDialog(""); - if (GetOpenFileNameA(&ofn)) + if (filePath != "") { - int32_t mType = m_pMachine->snapshotMachineInSnapshotWithPath(szFile); - std::string s(szFile, sizeof(szFile)); + int32_t mType = m_pMachine->snapshotMachineInSnapshotWithPath(filePath.c_str()); + std::string s(filePath, sizeof(filePath)); std::string extension = s.substr(s.find_last_of(".") + 1, s.find_last_of(".") + 4); // Check the machine type returned from the user supplied snapshot if not a tape file if (_stricmp(extension.c_str(), EXT_TAP.c_str()) == 0) { EjectTape(); // Eject the current tape if inserted - Tape::TapResponse tR = m_pTape->loadWithPath(szFile); + Tape::TapResponse tR = m_pTape->loadWithPath(filePath); if (tR.success) { - PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(szFile)); + PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(filePath)); } else { MessageBox(mainWindow, TEXT("Unable to load tape >> "), TEXT("Tape Loader"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); - PMDawn::Log(PMDawn::LOG_INFO, "Failed to load tape - " + std::string(szFile) + " > " + tR.responseMsg); + PMDawn::Log(PMDawn::LOG_INFO, "Failed to load tape - " + std::string(filePath) + " > " + tR.responseMsg); } } else @@ -719,7 +704,7 @@ static void LoadSnapshot() if (_stricmp(extension.c_str(), EXT_Z80.c_str()) == 0) { PMDawn::Log(PMDawn::LOG_INFO, "Loading Z80 Snapshot - " + s); - ZXSpectrum::Response sR = m_pMachine->snapshotZ80LoadWithPath(szFile); + ZXSpectrum::Response sR = m_pMachine->snapshotZ80LoadWithPath(filePath); if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); @@ -732,7 +717,7 @@ static void LoadSnapshot() else if (_stricmp(extension.c_str(), EXT_SNA.c_str()) == 0) { PMDawn::Log(PMDawn::LOG_DEBUG, "Loading SNA Snapshot - " + s); - ZXSpectrum::Response sR = m_pMachine->snapshotSNALoadWithPath(szFile); + ZXSpectrum::Response sR = m_pMachine->snapshotSNALoadWithPath(filePath); if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); From fca4b5b0a92f071d2bb0c84f9b311cd1be46359c Mon Sep 17 00:00:00 2001 From: John Young Date: Sat, 18 Jan 2020 10:53:28 +0000 Subject: [PATCH 04/23] ADDED: Tape window (threaded so should be able to update without causing the main window issues), now buttons work and displays tape data, save not working yet, not sure how I want to use that yet. --- SpectREM/SpectREM.aps | Bin 187016 -> 188188 bytes SpectREM/SpectREM.rc | 20 ++ SpectREM/SpectREM.vcxproj | 5 + SpectREM/SpectREM.vcxproj.filters | 3 + SpectREM/SpectREM/Win32/OpenGLView.cpp | 4 +- SpectREM/SpectREM/Win32/OpenGLView.hpp | 2 +- SpectREM/SpectREM/Win32/PMDawn.cpp | 150 ++++++-- SpectREM/SpectREM/Win32/PMDawn.hpp | 67 ++++ SpectREM/SpectREM/Win32/TapeViewerWindow.cpp | 323 ++++++++++++++++- SpectREM/SpectREM/Win32/TapeViewerWindow.hpp | 22 +- SpectREM/SpectREM/Win32/WinMain.cpp | 360 +++++++++++++++---- SpectREM/resource.h | 8 +- 12 files changed, 833 insertions(+), 131 deletions(-) create mode 100644 SpectREM/SpectREM/Win32/PMDawn.hpp diff --git a/SpectREM/SpectREM.aps b/SpectREM/SpectREM.aps index 97e3107f302fb14039208eaaf1b6c420f693750e..7ea4f066ebbff2ef925a1a478381a4a87014b5b2 100644 GIT binary patch delta 1929 zcmZ`)O>7%Q6n-;y94AhkAN-RzagzR}K!aniW5-oZn@zllw{^V1yJ^y<$_BN8B1-xX zLL6K{fhuZCqB(4Yiqzf^LV&OkQdN~6IFt(#m$u0vaN$QB$^j1TrRL2zKTZi=&Ai#~ z`{wPN_h!bIzHwcjR7q)I-G?_Bmyn~HDwyg zxG|_^6p&(ZcA9wEg@$Tc3v8|-*zOhz&YIeYlGU^Fh@RFA=n03z5vXHd5wEQtfbFaC zv!98hu|epEr)SO!Ne?tKt+A#183E@MT`|nGoJ$EZ4xTbt*9J>&d%??O8X!J)gf_MK zg+w1yje)djXb~xKfKQHup^1G&{kxh4pH?y>rrfWM3KtOx0;OqbZERHKdMN=d>?hjM z-C80P!%(wBx_MaBhB+UVASf6Pn2~XTMWKy#*u1;i%b0LjP%-FO2c;D-4qMoi&Ewm; zPN8d|Prx=dYYUR?Y|7I~c3|&kzuD?Yrx?}R>&Z?rdhBu2XMxpv{q#9tA#Z>_4=n3# zqPu{dz_%_ei?=!dg7|_Vk0_>Y$T_j<*lq|3SQ#G8$cCzA&4awu3J)MILAM}Fkf~+| za*C{%xG03nxs+1I^+06(TRCM6vu4OFX&?cw7mv9jxVhw5{N&oVU3Ts>)btKMv&JG`mrK(GQ_#&(Ia4l&U*kRWX^m|~R zxPokltC>yIEad+;kWC}rG@7!IH!gqEAj&{2b!Dm6dG=S$ZqI67HY{&vtzPvzCb?LQ zLoa)&Uka+JjrYY#!Eyy&D20o)PC}A}EACO^!WjT@NB}IeU+cD!bfKy~N}^9Y;1>=M zeB5~H=w^RL8#rJb!cs->yi!iovG44yuSn=$DEO_Dm2v+@BGDCrz~xax6WPe9Sx)wB zAX6#4W^{yOmwU-%k4J zpSPR3FrW@>#i9cnZbtlt>#LlYr_Q_sbT|huV>=IL;S^FG*h*3-j!IH7z^)ql;RKw7 zqwqTF40s!7OoI~!;WfmkVFo?Dfwl@{^mh)GXNC7O*jFNU44*T|pT)-k8JKqBlvj~F zj=}iJ5lg*Chboc|fY0Zzv^(|^fSO9{CY~H|icUHCDcVZs>e*~WWT#(TCuxQS<3C$B{2B z9mlsQK$pZm4h1@o+W?#~?utu4+f+K2U{87i4*X|=3wzj7Z^*j-fZFMuJuHw2InV*r zB(0?fRHEUez2T(oAEXrPZ*{g*|Gk#4Ly!s5qE-LdDqphtb_z}_c!=k1jEgq_P@gibH zMT@9dUSSpdz>5TwQixU&sow`r>Om>Cw$_WD3LXWu&Tdi~9C*9${oili%sggpjN1nv z+OM{9iBA-J>~R|a%x?n(B-spJA02Fx8x{Ppo4me3x8Hgq!OE{MBRXzq^MOE zxwkW!-jc|qQ_w=Un~$u*zcwFT z4VZEJI1L)GN$~U04WQ$GAxI;Dqm^C~#p^;U_g&~E^q7#_CFVGM&AD(u7IDqeKKw@9 zxTLHG_m#aNW2`mr@U6WOUzYD#@&Q)B{Jd$oey2!?o;zOAPDFsN5@0Fg)+7y(mfSB_ zgkX=`%WLdX2>|XAL!YG8G+SnLu;m5aOgr#IML;ljB|6Pr zRnyZRoK{ugAluPN8OOYFBQ!;M`dh|spK5G2{g8sEnBQai59*D+DH@;+7iD%Z!Q~=Fc${Lj)kJ&G=(ae`J z>$5}+7W0SMSwnF=)vSgGO98SiCdx6R7V0c2m1WOl_YNFMso`u5fP7#9WhK}sM(bFa zuV*lRUacKW1Ju1)Kv@_6L$kvG?-gRK^%FHL61X-(3OmT3p}6cT_5!|+QDf5+s)Ww5 RLa#qk20NJbTZM~H{ts_I11tal diff --git a/SpectREM/SpectREM.rc b/SpectREM/SpectREM.rc index 85cbe04..970f207 100644 --- a/SpectREM/SpectREM.rc +++ b/SpectREM/SpectREM.rc @@ -230,6 +230,26 @@ END // remains consistent on all systems. IDI_ICON2 ICON "SpectREM\\Win32\\SpectREM.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_FIRSTCOLUMN "Status" +END + +STRINGTABLE +BEGIN + IDS_BLOCKTYPE "Block Type" + IDS_FILENAME "Filename" + IDS_AUTOSTARTLINE "Autostart" + IDS_ADDRESS "Address" + IDS_LENGTH "Length" +END + #endif // English (United Kingdom) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/SpectREM/SpectREM.vcxproj b/SpectREM/SpectREM.vcxproj index 744cf5c..0ecd5ca 100644 --- a/SpectREM/SpectREM.vcxproj +++ b/SpectREM/SpectREM.vcxproj @@ -91,6 +91,7 @@ SpectREM\Emulator Core\Base;SpectREM\Emulator Core\Z80 Core;SpectREM\Emulator Core\Tape;SpectREM\Win32;SpectREM\Emulator Core\ZX Spectrum +2;SpectREM\Emulator Core\ZX Spectrum 48k;SpectREM\Emulator Core\ZX Spectrum 128k;SpectREM;%(AdditionalIncludeDirectories) 4068 false + MultiThreadedDebug Windows @@ -112,6 +113,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* Disabled _DEBUG;_WINDOWS;%(PreprocessorDefinitions) false + MultiThreadedDebug Windows @@ -135,6 +137,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* true WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) false + MultiThreadedDebug Windows @@ -160,6 +163,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* true NDEBUG;_WINDOWS;%(PreprocessorDefinitions) false + MultiThreadedDebug Windows @@ -220,6 +224,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* + diff --git a/SpectREM/SpectREM.vcxproj.filters b/SpectREM/SpectREM.vcxproj.filters index ecd91df..2998a76 100644 --- a/SpectREM/SpectREM.vcxproj.filters +++ b/SpectREM/SpectREM.vcxproj.filters @@ -156,6 +156,9 @@ Win32 + + Win32 + diff --git a/SpectREM/SpectREM/Win32/OpenGLView.cpp b/SpectREM/SpectREM/Win32/OpenGLView.cpp index d17eb0c..2b9a3e1 100644 --- a/SpectREM/SpectREM/Win32/OpenGLView.cpp +++ b/SpectREM/SpectREM/Win32/OpenGLView.cpp @@ -356,14 +356,14 @@ void OpenGLView::SetupTexture() //----------------------------------------------------------------------------------------- -void OpenGLView::UpdateTextureData(unsigned char *pData) +void OpenGLView::UpdateTextureData(unsigned char *pData, GLint vX, GLint vY) { glClearColor(0.0f, 1.0f, 1.0f, 0.5f); glClear(GL_COLOR_BUFFER_BIT); // Render the output to a texture which has the default dimensions of the output image GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, _clutFrameBuffer)); - GL_CHECK(glViewport(0, 0, screenWidth, screenHeight)); + GL_CHECK(glViewport(vX, vY, screenWidth, screenHeight)); GL_CHECK(glUseProgram(_clutShaderProg)); GL_CHECK(glActiveTexture(GL_TEXTURE0)); GL_CHECK(glBindTexture(GL_TEXTURE_2D, _clutInputTexture)); diff --git a/SpectREM/SpectREM/Win32/OpenGLView.hpp b/SpectREM/SpectREM/Win32/OpenGLView.hpp index 277c0b5..aa4618d 100644 --- a/SpectREM/SpectREM/Win32/OpenGLView.hpp +++ b/SpectREM/SpectREM/Win32/OpenGLView.hpp @@ -107,7 +107,7 @@ class OpenGLView void Deinit(); bool Init(HWND hWnd, int width, int height, const uint16_t idClutVert, uint16_t idClutFrag, uint16_t idDisplayVert, uint16_t idDisplayFrag, LPWSTR idType); - void UpdateTextureData(unsigned char *pData); + void UpdateTextureData(unsigned char *pData, GLint vX, GLint vY); void OpenGLView::Resize(int width, int height); private: bool InitialiseExtensions(); diff --git a/SpectREM/SpectREM/Win32/PMDawn.cpp b/SpectREM/SpectREM/Win32/PMDawn.cpp index 3bd4447..fa50eeb 100644 --- a/SpectREM/SpectREM/Win32/PMDawn.cpp +++ b/SpectREM/SpectREM/Win32/PMDawn.cpp @@ -6,8 +6,6 @@ // // -#pragma once - #include #include #include @@ -16,37 +14,19 @@ #include #include #include +#include +#include "PMDawn.hpp" namespace PMDawn { - // LogType: - // LOG_INFO is for general info - // LOG_DEBUG is for debugging info, note that it will also include INFO - enum LogType - { - LOG_NONE, LOG_INFO, LOG_DEBUG, LOG_FULL - }; //----------------------------------------------------------------------------------------- + - static bool LogOpenOrCreate(std::string filename); - static bool Log(LogType lType, std::string text); - static bool LogClose(); - static std::string GetTimeAsString(); - static std::string GetApplicationBasePath(); - static std::string GetCurrentDirectoryAsString(); - static std::vector GetFilesInDirectory(std::string folder, std::string filter); //----------------------------------------------------------------------------------------- - static uint8_t logLevel = LOG_NONE; - static const std::string logFilename = "spectrem_win32.log"; - static std::string logFullFilename = ""; - static std::ofstream logFileStream; - - //----------------------------------------------------------------------------------------- - - static bool fileExists(const std::string& filename) + bool PMDawn::fileExists(const std::string& filename) { struct stat fileBuffer; return (stat(filename.c_str(), &fileBuffer) == 0); @@ -54,7 +34,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static std::string GetTimeAsString() + std::string PMDawn::GetTimeAsString() { time_t rawtime; struct tm timeinfo; @@ -68,7 +48,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static std::string GetCurrentDirectoryAsString() + std::string PMDawn::GetCurrentDirectoryAsString() { char basePT[MAX_PATH]; GetCurrentDirectoryA(MAX_PATH, basePT); @@ -80,7 +60,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static std::string GetApplicationBasePath() + std::string PMDawn::GetApplicationBasePath() { char appDirT[MAX_PATH]; GetModuleFileNameA(NULL, appDirT, MAX_PATH); @@ -90,7 +70,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static bool LogOpenOrCreate(std::string filename) + bool PMDawn::LogOpenOrCreate(std::string filename) { logFileStream.open(filename, std::ios::ate | std::ios::app); if (logFileStream.is_open()) @@ -106,7 +86,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static bool Log(LogType lType, std::string text) + bool PMDawn::Log(LogType lType, std::string text) { // we will use the file and always append to it if (logFileStream.is_open()) @@ -124,7 +104,7 @@ namespace PMDawn lty = "[UNKNOWN]"; break; } - logFileStream << GetTimeAsString().c_str() << " : " << lty.c_str() << " : " << text.c_str() << std::endl; + logFileStream << GetTimeAsString().c_str() << " : " << lty.c_str() << " : " << text.c_str() << "\n"; return true; } else @@ -135,7 +115,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static bool LogClose() + bool PMDawn::LogClose() { if (logFileStream.is_open()) { @@ -150,7 +130,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static std::vector GetFilesInDirectory(std::string folder, std::string filter) + std::vector PMDawn::GetFilesInDirectory(std::string folder, std::string filter) { std::vector fileList; std::string fullPath = folder + filter; @@ -176,9 +156,9 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static std::string GetFolderUsingDialog(std::string initialFolder = "") + std::string GetFolderUsingDialog(std::string initialFolder = "") { - wchar_t *fold; + wchar_t* fold; std::string fl; bool error = true; @@ -216,7 +196,8 @@ namespace PMDawn std::string fs(fstr.begin(), fstr.end()); return fs; - } else + } + else { return ""; } @@ -224,7 +205,7 @@ namespace PMDawn //----------------------------------------------------------------------------------------- - static std::string GetFilenameUsingDialog(std::string initialFolder = "") + std::string PMDawn::GetFilenameUsingDialog(std::string initialFolder) { wchar_t* fold; std::string fl; @@ -236,7 +217,7 @@ namespace PMDawn DWORD dwOptions; if (SUCCEEDED(pfd->GetOptions(&dwOptions))) { - pfd->SetOptions(dwOptions ); // FOS_FORCEFILESYSTEM + pfd->SetOptions(dwOptions); // FOS_FORCEFILESYSTEM } if (SUCCEEDED(pfd->Show(NULL))) { @@ -273,18 +254,111 @@ namespace PMDawn //----------------------------------------------------------------------------------------- + //HWND CreateButton(HWND owner, std::string buttonText, int x, int y, int w, int h) + //{ + // //LPCWSTR lpS = stdStringToLpwstr(buttonText); + // HWND btn = CreateWindow( + // L"BUTTON", // Class + // L"Ok",//buttonText.c_str(), // Button text + // WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles + // x, // x position + // y, // y position + // w, // Button width + // h, // Button height + // owner, // Parent window + // NULL, // No menu. + // NULL, // Instance + // NULL); // Pointer not needed. + // return btn; + //} + + ////----------------------------------------------------------------------------------------- + + LPCWSTR stdStringToLpcwstr(std::string input) + { + std::wstring stemp = std::wstring(input.begin(), input.end()); + LPCWSTR sw = stemp.c_str(); + return sw; + } //----------------------------------------------------------------------------------------- + std::wstring PMDawn::s2ws(const std::string& s) + { + int len; + int slength = (int)s.length() + 1; + len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); + wchar_t* buf = new wchar_t[len]; + MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); + std::wstring r(buf); + delete[] buf; + return r; + } + void AddItemToListView(gTAPEBLOCK& theBlock, HWND hwndListView) + { + // Status / BlockType / Filename / AutostartLine / Address / Length + // TAPE block types + /*enum + { + ePROGRAM_HEADER = 0, + eNUMERIC_DATA_HEADER, + eALPHANUMERIC_DATA_HEADER, + eBYTE_HEADER, + eDATA_BLOCK, + eFRAGMENTED_DATA_BLOCK, + eUNKNOWN_BLOCK = 99 + };*/ + + if (!ShowAlternativeTapeLengths) + { + theBlock.length -= 2; + } - //----------------------------------------------------------------------------------------- + LVITEM lvi; + lvi.mask = LVIF_TEXT | LVIF_COLFMT; + lvi.iItem = 0; + lvi.iSubItem = 0; + std::wstring ws; + ws.assign(theBlock.status.begin(), theBlock.status.end()); + lvi.pszText = &ws[0]; + ListView_InsertItem(hwndListView, &lvi); + std::wstring bt; + bt.assign(theBlock.blocktype.begin(), theBlock.blocktype.end()); + ListView_SetItemText(hwndListView, 0, 1, &bt[0]); + std::wstring fn; + fn.assign(theBlock.filename.begin(), theBlock.filename.end()); + ListView_SetItemText(hwndListView, 0, 2, &fn[0]); + std::wstring asl; + if (theBlock.autostartline != 0) + { + asl = std::to_wstring(theBlock.autostartline); + } + else + { + asl = L""; + } + ListView_SetItemText(hwndListView, 0, 3, &asl[0]); + std::wstring addy; + if (theBlock.address != 0) + { + addy = std::to_wstring(theBlock.address); + } + else + { + addy = L""; + } + ListView_SetItemText(hwndListView, 0, 4, &addy[0]); + std::wstring length; + length = std::to_wstring(theBlock.length); + ListView_SetItemText(hwndListView, 0, 5, &length[0]); + } } \ No newline at end of file diff --git a/SpectREM/SpectREM/Win32/PMDawn.hpp b/SpectREM/SpectREM/Win32/PMDawn.hpp new file mode 100644 index 0000000..322ba50 --- /dev/null +++ b/SpectREM/SpectREM/Win32/PMDawn.hpp @@ -0,0 +1,67 @@ +// +// PMDawn.hpp +// This window holds the tape viewer which allows the user to interact with the tape blocks etc. +// +// Created by John Young on 05-01-2020 +// +// + +#ifndef PMDawn_hpp +#define PMDawn_hpp + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace PMDawn +{ + struct gTAPEBLOCK { + std::string status; + std::string blocktype; + std::string filename; + uint16_t autostartline; + uint16_t address; + uint16_t length; + }; + + static std::vector pData; + + // LogType: + // LOG_INFO is for general info + // LOG_DEBUG is for debugging info, note that it will also include INFO + enum LogType + { + LOG_NONE, LOG_INFO, LOG_DEBUG, LOG_FULL + }; + + static uint8_t logLevel = LOG_NONE; + const std::string logFilename = "spectrem_win32.log"; + static std::string logFullFilename = ""; + static std::ofstream logFileStream; + static bool ShowAlternativeTapeLengths = false; + + bool Log(LogType lType, std::string text); + bool LogClose(void); + std::string GetTimeAsString(void); + std::string GetApplicationBasePath(void); + std::string GetCurrentDirectoryAsString(void); + std::vector GetFilesInDirectory(std::string folder, std::string filter); + //static HWND PMDawn::CreateButton(HWND owner, std::string buttonText, int x, int y, int w, int h); + LPCWSTR stdStringToLpcwstr(std::string input); + std::wstring s2ws(const std::string& s); + std::string GetFilenameUsingDialog(std::string initialFolder); + std::string GetFolderUsingDialog(std::string initialFolder); + bool fileExists(const std::string& filename); + bool LogOpenOrCreate(std::string filename); + void AddItemToListView(gTAPEBLOCK& theBlock, HWND hwndListView); + + +} +#endif /* PMDawn_hpp */ + diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp index 9f62af2..60822d8 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp @@ -1,28 +1,256 @@ // -// TapeViewerWindow.cpp -// This window holds the tape viewer which allows the user to interact with the tape blocks etc. +// PMDawn.hpp +// // -// Created by John Young on 05-01-2020 +// Created by John Young on 11-01-2020 // // #include #include +#include #include "../../resource.h" -#include "PMDawn.cpp" +#include "PMDawn.hpp" #include "TapeViewerWindow.hpp" +#include "..\Emulation Core\Tape\Tape.hpp" +#include +#include + +#define PM_TAPEDATA_FULL 77 +#define PM_TAPE_COMMAND 79 +#define PM_TAPE_EJECTED 80 +#define PM_TAPE_ACTIVEBLOCK 81 +#define PM_TAPE_PLAY 82 +#define PM_TAPE_PAUSE 83 +#define PM_TAPE_REWIND 84 +#define PM_TAPE_INSERT 85 +#define PM_TAPE_EJECT 86 +#define PM_TAPE_SAVE 87 + +// Status / BlockType / Filename / AutostartLine / Address / Length //----------------------------------------------------------------------------------------- +HWND TapeViewer::tapeViewerWindowInternal = nullptr; +HWND mHandle; +static std::vector* myP; + +TapeViewer::~TapeViewer() +{ + //PostMessage(mHandle, WM_USER + 2, PM_TAPE_VIEWER_CLOSING, (LPARAM)0); +} + LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + static HWND hwndOKButton; + static HWND hwndCancelButton; + static HWND hwndPlayButton; + static HWND hwndPauseButton; + static HWND hwndRewindButton; + static HWND hwndInsertButton; + static HWND hwndEjectButton; + static HWND hwndSaveButton; + static HWND hwndListView; + static TCHAR szTempListData[] = TEXT("Temporary listbox data"); + switch (msg) { + case WM_CREATE: + RECT rect; + + GetClientRect(hwnd, &rect); + + // Create buttons for tape control (Play/Pause/Rewind/Eject/Save) + hwndPlayButton = CreateWindow(TEXT("button"), + TEXT("Play"), + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + rect.right - 100 - windowFrameBuffer, rect.top + windowFrameBuffer, + 100, 20, + hwnd, + (HMENU)IDC_BUTTON_PLAY, + ((LPCREATESTRUCT)lParam)->hInstance, + NULL); + hwndPauseButton = CreateWindow(TEXT("button"), + TEXT("Pause"), + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + rect.right - 100 - windowFrameBuffer, rect.top + windowFrameBuffer + (2 * buttonYBuffer), + 100, 20, + hwnd, + (HMENU)IDC_BUTTON_PAUSE, + ((LPCREATESTRUCT)lParam)->hInstance, + NULL); + hwndRewindButton = CreateWindow(TEXT("button"), + TEXT("Rewind"), + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + rect.right - 100 - windowFrameBuffer, rect.top + windowFrameBuffer + (4 * buttonYBuffer), + 100, 20, + hwnd, + (HMENU)IDC_BUTTON_REWIND, + ((LPCREATESTRUCT)lParam)->hInstance, + NULL); + hwndInsertButton = CreateWindow(TEXT("button"), + TEXT("Insert"), + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + rect.right - 100 - windowFrameBuffer, rect.top + windowFrameBuffer + (7 * buttonYBuffer), + 100, 20, + hwnd, + (HMENU)IDC_BUTTON_INSERT, + ((LPCREATESTRUCT)lParam)->hInstance, + NULL); + hwndEjectButton = CreateWindow(TEXT("button"), + TEXT("Eject"), + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + rect.right - 100 - windowFrameBuffer, rect.top + windowFrameBuffer + (9 * buttonYBuffer), + 100, 20, + hwnd, + (HMENU)IDC_BUTTON_EJECT, + ((LPCREATESTRUCT)lParam)->hInstance, + NULL); + hwndSaveButton = CreateWindow(TEXT("button"), + TEXT("Save"), + WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, + rect.right - 100 - windowFrameBuffer, rect.top + windowFrameBuffer + (12 * buttonYBuffer), + 100, 20, + hwnd, + (HMENU)IDC_BUTTON_SAVE, + ((LPCREATESTRUCT)lParam)->hInstance, + NULL); + + + // Create a listbox to hold the tape data - Status/BlockType/Filename/AutostartLine/Address/Length + hwndListView = CreateListView(hwnd, lParam, rect); + if (hwndListView == NULL) + { + MessageBox(hwnd, TEXT("Couldn't create listview!"), TEXT("LISTVIEW"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); + return 0; + } + else + { + bool addItems = InitListViewColumns(hwndListView, ((LPCREATESTRUCT)lParam)->hInstance); + if (addItems == false) + { + MessageBox(hwnd, TEXT("Couldn't add items!"), TEXT("LISTVIEW"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); + return 0; + } + else + { + LVITEM lvi; + + lvi.mask = LVIF_TEXT | LVIF_COLFMT; + //lvi.iItem = 0; + //lvi.iSubItem = 0; + //lvi.pszText = TEXT("Kiss"); + //ListView_InsertItem(hwndListView, &lvi); + //ListView_SetItemText(hwndListView, 0, 1, TEXT("My")); + //ListView_SetItemText(hwndListView, 0, 2, TEXT("Ass")); + //ListView_SetItemText(hwndListView, 0, 3, TEXT("!!!")); + ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_FULLROWSELECT); + } + } + return 0; + break; + + + case WM_USER + 2: + { + if (wParam == PM_TAPEDATA_FULL) + { + if (hwndListView != nullptr) { + ListView_DeleteAllItems(hwndListView); + size_t numBlocks = lParam; + + myP = (std::vector*)lParam; + + size_t nn = myP->size(); + if (nn == 0) + { + MessageBox(hwnd, TEXT("PMDawn::pData.size() is 0 !!"), TEXT("Tape Viewer"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); + return 0; + } + + for (int i = nn - 1; i >= 0; i--) + { + PMDawn::gTAPEBLOCK theBlock = myP->at(i); + PMDawn::AddItemToListView(theBlock, hwndListView); + } + + ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_FULLROWSELECT); + return 0; + } + } + if (wParam == PM_TAPE_EJECTED) + { + if (hwndListView != nullptr) { + ListView_DeleteAllItems(hwndListView); + return 0; + } + } + if (wParam == PM_TAPE_ACTIVEBLOCK) + { + if (hwndListView != nullptr) { + uint16_t blockNumber = (uint16_t)lParam; + + //ListView_DeleteAllItems(hwndListView); + return 0; + } + } + + + return 0; + break; + } + + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_BUTTON_PLAY: + PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_PLAY); + break; + case IDC_BUTTON_PAUSE: + PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_PAUSE); + break; + case IDC_BUTTON_REWIND: + PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_REWIND); + break; + case IDC_BUTTON_INSERT: + PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_INSERT); + break; + case IDC_BUTTON_EJECT: + PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_EJECT); + break; + case IDC_BUTTON_SAVE: + MessageBox(hwnd, TEXT("SAVE"), TEXT("BUTTON"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); + break; + } + + return 0; + break; + + case WM_NOTIFY: + { + if ((((LPNMHDR)lParam)->hwndFrom) == hwndListView) + { + switch (((LPNMHDR)lParam)->code) + { + case NM_DBLCLK: + { + // code here <-- + LPNMITEMACTIVATE lpNMItem = (LPNMITEMACTIVATE)lParam; + return 0; + } + break; + } + } + break; + } case WM_CLOSE: DestroyWindow(hwnd); + return 0; break; case WM_DESTROY: PostQuitMessage(0); + return 0; break; default: return DefWindowProc(hwnd, msg, wParam, lParam); @@ -32,13 +260,85 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA //----------------------------------------------------------------------------------------- +// InitListViewColumns: Adds columns to a list-view control. +// hWndListView: Handle to the list-view control. +// Returns TRUE if successful, and FALSE otherwise. +BOOL TapeViewer::InitListViewColumns(HWND hWndListView, HINSTANCE hInst) +{ + WCHAR szText[256]; // Temporary buffer. + LVCOLUMN lvc; + int iCol; + + // Initialize the LVCOLUMN structure. + // The mask specifies that the format, width, text, + // and subitem members of the structure are valid. + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + + // Add the columns. + for (iCol = 0; iCol < IDS_NUMBEROFCOLUMNS; iCol++) + { + + lvc.iSubItem = iCol; + lvc.pszText = szText; + lvc.cx = 85; // Width of column in pixels. -TapeViewer::TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle) + //if (iCol < 2) + // lvc.fmt = LVCFMT_LEFT; // Left-aligned column. + //else + // lvc.fmt = LVCFMT_RIGHT; // Right-aligned column. + + // Load the names of the column headings from the string resources. + LoadString(GetModuleHandle(NULL), + IDS_FIRSTCOLUMN + iCol, + szText, + sizeof(szText) / sizeof(szText[0])); + + // Insert the columns into the list view. + auto lvCol = ListView_InsertColumn(hWndListView, iCol, &lvc); + if (lvCol == -1) + { + return FALSE; + } + } + + return TRUE; +} + +//----------------------------------------------------------------------------------------- + +HWND TapeViewer::CreateListView(HWND hwnd, LPARAM lParam, RECT rect) +{ + // Initialize the common controls + INITCOMMONCONTROLSEX icex; + icex.dwICC = ICC_LISTVIEW_CLASSES; + InitCommonControlsEx(&icex); + + HWND hlv = CreateWindow(WC_LISTVIEW, + TEXT("ViewList"), + WS_VISIBLE | WS_BORDER | WS_CHILD | LVS_REPORT | CS_DBLCLKS, + rect.left + windowFrameBuffer, rect.top + windowFrameBuffer, + rect.right - (3 * windowFrameBuffer) - 100, rect.bottom - windowFrameBuffer, + hwnd, + (HMENU)IDC_LISTVIEW_TAPEDATA, + GetModuleHandle(NULL), + NULL); + //ShowWindow(hlv, SW_SHOWNORMAL); + return hlv; +} + +//----------------------------------------------------------------------------------------- + +TapeViewer::TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle, DWORD dwTlsIndex) { bool exit_tapeviewer = false; MSG msg; WNDCLASSEX wcextv; + mHandle = mainHandle; + + // thread data + //PMDawn::pData = (PMDawn::PTHREADDATA)TlsGetValue(dwTlsIndex); + memset(&wcextv, 0, sizeof(WNDCLASSEX)); wcextv.cbSize = sizeof(WNDCLASSEX); wcextv.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; @@ -54,15 +354,23 @@ TapeViewer::TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle) RegisterClassEx(&wcextv); // Make sure the client size is correct - RECT wr = { 0, 0, 640, 480 }; + RECT wr = { 0, 0, 640, 240 }; AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX, FALSE); - tapeViewerWindowInternal = CreateWindowEx(WS_EX_APPWINDOW, TEXT("SpectREM_TapeViewer"), TEXT("SpectREM - Tape Viewer"), WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX, 0, 0, wr.right - wr.left, wr.bottom - wr.top, 0, 0, mainWindowInst, 0); + + // Now create the window + tapeViewerWindowInternal = CreateWindowEx(WS_EX_APPWINDOW, TEXT("SpectREM_TapeViewer"), TEXT("SpectREM - Tape Viewer"), + WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, wr.right - wr.left, wr.bottom - wr.top, 0, 0, mainWindowInst, 0); if (tapeViewerWindowInternal == NULL) { MessageBoxA(NULL, "Tape window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return; } + + // let the main window know that we are cool for receiving updates + PostMessage(mainHandle, WM_USER + 1, 1, 1); + ShowWindow(tapeViewerWindowInternal, SW_SHOWNORMAL); UpdateWindow(tapeViewerWindowInternal); @@ -102,3 +410,4 @@ TapeViewer::TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle) + diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp index f697591..ae81acf 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp @@ -10,16 +10,28 @@ #include #include +#define IDC_BUTTON_PLAY 100 +#define IDC_BUTTON_PAUSE 101 +#define IDC_BUTTON_REWIND 102 +#define IDC_BUTTON_INSERT 103 +#define IDC_BUTTON_EJECT 104 +#define IDC_BUTTON_SAVE 105 +#define IDC_LISTVIEW_TAPEDATA 106 +#define IDS_NUMBEROFCOLUMNS 006 + + class TapeViewer { public: - TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle); + TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle, DWORD dwTlsIndex);// , std::vectormyPTAPE); ~TapeViewer(); static LRESULT CALLBACK WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - - -public: - HWND tapeViewerWindowInternal; + static HWND TapeViewer::CreateListView(HWND hwnd, LPARAM lParam, RECT rect); + static BOOL TapeViewer::InitListViewColumns(HWND hWndListView, HINSTANCE hInst); + static const uint8_t windowFrameBuffer = 8; + static const uint8_t buttonYBuffer = 12; + static HWND tapeViewerWindowInternal; + static HINSTANCE g_hInst; }; \ No newline at end of file diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index f0d6d74..6309a0f 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -10,6 +10,17 @@ #define _CRT_RAND_S #define WIN32API_GUI +#define PM_TAPEDATA_FULL 77 +#define PM_TAPE_VIEWER_CLOSED 78 +#define PM_TAPE_COMMAND 79 +#define PM_TAPE_EJECTED 80 +#define PM_TAPE_ACTIVEBLOCK 81 +#define PM_TAPE_PLAY 82 +#define PM_TAPE_PAUSE 83 +#define PM_TAPE_REWIND 84 +#define PM_TAPE_INSERT 85 +#define PM_TAPE_EJECT 86 +#define PM_TAPE_SAVE 87 #include #include @@ -30,9 +41,10 @@ #include "..\OSX\AudioQueue.hpp" #include "OpenGLView.hpp" #include "../../resource.h" -#include "PMDawn.cpp" +#include "PMDawn.hpp" #include #include "TapeViewerWindow.hpp" +#include #pragma comment(lib, "comctl32.lib") @@ -62,12 +74,17 @@ static void OpenTapeViewer(); static void SetOutputVolume(float vol); static void IncreaseApplicationVolume(); static void DecreaseApplicationVolume(); +unsigned int __stdcall mythread(void* data); +static void SendTapeBlockDataToViewer(); +static void GetTapeViewerHwnd(); +static void SetupThreadLocalStorageForTapeData(); ZXSpectrum* m_pMachine; Tape* m_pTape; AudioCore* m_pAudioCore; AudioQueue* m_pAudioQueue; OpenGLView* m_pOpenGLView; +static TapeViewer* tvWindow; enum MachineType { @@ -79,7 +96,10 @@ enum SnapType SNA, Z80 }; +// Status / BlockType / Filename / AutostartLine / Address / Length +static DWORD dwTlsIndex; +static int cxClient, cyClient; const UINT PM_UPDATESPECTREM = 7777; const std::string EXT_Z80 = "z80"; const std::string EXT_SNA = "sna"; @@ -90,7 +110,6 @@ HACCEL hAcc; bool isResetting = false; HWND mainWindow; HWND statusWindow; -//HWND tapeViewerWindow; HMENU mainMenu; bool TurboMode = false; bool menuDisplayed = true; @@ -104,72 +123,76 @@ bool slideshowTimerRunning = false; bool slideshowRandom = true; const float volumeStep = 0.1f; float applicationVolume = 0.75f; +GLint viewportX; +GLint viewportY; +HANDLE tapeViewerThread; +HWND tvHwnd; -std::unordered_map KeyMappings +std::unordered_map KeyMappings { - { VK_UP, ZXSpectrum::ZXSpectrumKey::Key_ArrowUp }, - { VK_DOWN, ZXSpectrum::ZXSpectrumKey::Key_ArrowDown }, - { VK_LEFT, ZXSpectrum::ZXSpectrumKey::Key_ArrowLeft }, - { VK_RIGHT, ZXSpectrum::ZXSpectrumKey::Key_ArrowRight }, - { VK_RETURN, ZXSpectrum::ZXSpectrumKey::Key_Enter }, - { VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_Shift }, - { VK_RSHIFT, ZXSpectrum::ZXSpectrumKey::Key_Shift }, - { VK_SPACE, ZXSpectrum::ZXSpectrumKey::Key_Space }, - { VK_CONTROL, ZXSpectrum::ZXSpectrumKey::Key_SymbolShift }, - { VK_RCONTROL, ZXSpectrum::ZXSpectrumKey::Key_SymbolShift }, + { VK_UP, ZXSpectrum::eZXSpectrumKey::Key_ArrowUp }, + { VK_DOWN, ZXSpectrum::eZXSpectrumKey::Key_ArrowDown }, + { VK_LEFT, ZXSpectrum::eZXSpectrumKey::Key_ArrowLeft }, + { VK_RIGHT, ZXSpectrum::eZXSpectrumKey::Key_ArrowRight }, + { VK_RETURN, ZXSpectrum::eZXSpectrumKey::Key_Enter }, + { VK_SHIFT, ZXSpectrum::eZXSpectrumKey::Key_Shift }, + { VK_RSHIFT, ZXSpectrum::eZXSpectrumKey::Key_Shift }, + { VK_SPACE, ZXSpectrum::eZXSpectrumKey::Key_Space }, + { VK_CONTROL, ZXSpectrum::eZXSpectrumKey::Key_SymbolShift }, + { VK_RCONTROL, ZXSpectrum::eZXSpectrumKey::Key_SymbolShift }, //{ VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_InvVideo }, //{ VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_TrueVideo }, - { VK_DELETE, ZXSpectrum::ZXSpectrumKey::Key_Backspace }, - { VK_BACK, ZXSpectrum::ZXSpectrumKey::Key_Backspace }, + { VK_DELETE, ZXSpectrum::eZXSpectrumKey::Key_Backspace }, + { VK_BACK, ZXSpectrum::eZXSpectrumKey::Key_Backspace }, //{ VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_Quote }, - { VK_OEM_1, ZXSpectrum::ZXSpectrumKey::Key_SemiColon }, - { VK_OEM_COMMA, ZXSpectrum::ZXSpectrumKey::Key_Comma }, - { VK_OEM_MINUS, ZXSpectrum::ZXSpectrumKey::Key_Minus }, - { VK_OEM_PLUS, ZXSpectrum::ZXSpectrumKey::Key_Plus }, - { VK_OEM_PERIOD, ZXSpectrum::ZXSpectrumKey::Key_Period }, + { VK_OEM_1, ZXSpectrum::eZXSpectrumKey::Key_SemiColon }, + { VK_OEM_COMMA, ZXSpectrum::eZXSpectrumKey::Key_Comma }, + { VK_OEM_MINUS, ZXSpectrum::eZXSpectrumKey::Key_Minus }, + { VK_OEM_PLUS, ZXSpectrum::eZXSpectrumKey::Key_Plus }, + { VK_OEM_PERIOD, ZXSpectrum::eZXSpectrumKey::Key_Period }, //{ VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_Edit }, //{ VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_Graph }, //{ VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_Break }, //{ VK_SHIFT, ZXSpectrum::ZXSpectrumKey::Key_ExtendMode }, - { VK_CAPITAL, ZXSpectrum::ZXSpectrumKey::Key_CapsLock }, + { VK_CAPITAL, ZXSpectrum::eZXSpectrumKey::Key_CapsLock }, // Numbers - { 0x30, ZXSpectrum::ZXSpectrumKey::Key_0 }, - { 0x31, ZXSpectrum::ZXSpectrumKey::Key_1 }, - { 0x32, ZXSpectrum::ZXSpectrumKey::Key_2 }, - { 0x33, ZXSpectrum::ZXSpectrumKey::Key_3 }, - { 0x34, ZXSpectrum::ZXSpectrumKey::Key_4 }, - { 0x35, ZXSpectrum::ZXSpectrumKey::Key_5 }, - { 0x36, ZXSpectrum::ZXSpectrumKey::Key_6 }, - { 0x37, ZXSpectrum::ZXSpectrumKey::Key_7 }, - { 0x38, ZXSpectrum::ZXSpectrumKey::Key_8 }, - { 0x39, ZXSpectrum::ZXSpectrumKey::Key_9 }, + { 0x30, ZXSpectrum::eZXSpectrumKey::Key_0 }, + { 0x31, ZXSpectrum::eZXSpectrumKey::Key_1 }, + { 0x32, ZXSpectrum::eZXSpectrumKey::Key_2 }, + { 0x33, ZXSpectrum::eZXSpectrumKey::Key_3 }, + { 0x34, ZXSpectrum::eZXSpectrumKey::Key_4 }, + { 0x35, ZXSpectrum::eZXSpectrumKey::Key_5 }, + { 0x36, ZXSpectrum::eZXSpectrumKey::Key_6 }, + { 0x37, ZXSpectrum::eZXSpectrumKey::Key_7 }, + { 0x38, ZXSpectrum::eZXSpectrumKey::Key_8 }, + { 0x39, ZXSpectrum::eZXSpectrumKey::Key_9 }, // Letters - { 0x41, ZXSpectrum::ZXSpectrumKey::Key_A }, - { 0x42, ZXSpectrum::ZXSpectrumKey::Key_B }, - { 0x43, ZXSpectrum::ZXSpectrumKey::Key_C }, - { 0x44, ZXSpectrum::ZXSpectrumKey::Key_D }, - { 0x45, ZXSpectrum::ZXSpectrumKey::Key_E }, - { 0x46, ZXSpectrum::ZXSpectrumKey::Key_F }, - { 0x47, ZXSpectrum::ZXSpectrumKey::Key_G }, - { 0x48, ZXSpectrum::ZXSpectrumKey::Key_H }, - { 0x49, ZXSpectrum::ZXSpectrumKey::Key_I }, - { 0x4a, ZXSpectrum::ZXSpectrumKey::Key_J }, - { 0x4b, ZXSpectrum::ZXSpectrumKey::Key_K }, - { 0x4c, ZXSpectrum::ZXSpectrumKey::Key_L }, - { 0x4d, ZXSpectrum::ZXSpectrumKey::Key_M }, - { 0x4e, ZXSpectrum::ZXSpectrumKey::Key_N }, - { 0x4f, ZXSpectrum::ZXSpectrumKey::Key_O }, - { 0x50, ZXSpectrum::ZXSpectrumKey::Key_P }, - { 0x51, ZXSpectrum::ZXSpectrumKey::Key_Q }, - { 0x52, ZXSpectrum::ZXSpectrumKey::Key_R }, - { 0x53, ZXSpectrum::ZXSpectrumKey::Key_S }, - { 0x54, ZXSpectrum::ZXSpectrumKey::Key_T }, - { 0x55, ZXSpectrum::ZXSpectrumKey::Key_U }, - { 0x56, ZXSpectrum::ZXSpectrumKey::Key_V }, - { 0x57, ZXSpectrum::ZXSpectrumKey::Key_W }, - { 0x58, ZXSpectrum::ZXSpectrumKey::Key_X }, - { 0x59, ZXSpectrum::ZXSpectrumKey::Key_Y }, - { 0x5a, ZXSpectrum::ZXSpectrumKey::Key_Z }, + { 0x41, ZXSpectrum::eZXSpectrumKey::Key_A }, + { 0x42, ZXSpectrum::eZXSpectrumKey::Key_B }, + { 0x43, ZXSpectrum::eZXSpectrumKey::Key_C }, + { 0x44, ZXSpectrum::eZXSpectrumKey::Key_D }, + { 0x45, ZXSpectrum::eZXSpectrumKey::Key_E }, + { 0x46, ZXSpectrum::eZXSpectrumKey::Key_F }, + { 0x47, ZXSpectrum::eZXSpectrumKey::Key_G }, + { 0x48, ZXSpectrum::eZXSpectrumKey::Key_H }, + { 0x49, ZXSpectrum::eZXSpectrumKey::Key_I }, + { 0x4a, ZXSpectrum::eZXSpectrumKey::Key_J }, + { 0x4b, ZXSpectrum::eZXSpectrumKey::Key_K }, + { 0x4c, ZXSpectrum::eZXSpectrumKey::Key_L }, + { 0x4d, ZXSpectrum::eZXSpectrumKey::Key_M }, + { 0x4e, ZXSpectrum::eZXSpectrumKey::Key_N }, + { 0x4f, ZXSpectrum::eZXSpectrumKey::Key_O }, + { 0x50, ZXSpectrum::eZXSpectrumKey::Key_P }, + { 0x51, ZXSpectrum::eZXSpectrumKey::Key_Q }, + { 0x52, ZXSpectrum::eZXSpectrumKey::Key_R }, + { 0x53, ZXSpectrum::eZXSpectrumKey::Key_S }, + { 0x54, ZXSpectrum::eZXSpectrumKey::Key_T }, + { 0x55, ZXSpectrum::eZXSpectrumKey::Key_U }, + { 0x56, ZXSpectrum::eZXSpectrumKey::Key_V }, + { 0x57, ZXSpectrum::eZXSpectrumKey::Key_W }, + { 0x58, ZXSpectrum::eZXSpectrumKey::Key_X }, + { 0x59, ZXSpectrum::eZXSpectrumKey::Key_Y }, + { 0x5a, ZXSpectrum::eZXSpectrumKey::Key_Z }, }; @@ -193,7 +216,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara { switch (msg) { - #ifdef WIN32API_GUI case WM_COMMAND: switch (LOWORD(wparam)) @@ -286,6 +308,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara break; #endif + + case WM_CLOSE: PostQuitMessage(0); return 0; @@ -349,7 +373,18 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara break; case WM_SIZE: - glViewport(0, 0, LOWORD(lparam), HIWORD(lparam)); + //cxClient = LOWORD(lparam); + //cyClient = HIWORD(lparam); + //if (menuDisplayed) + //{ + // viewportY += 20; + //} + //else + //{ + // viewportY -= 20; + //} + //glViewport(viewportX, viewportY, LOWORD(lparam), HIWORD(lparam)); + return 0; break; case WM_USER: @@ -357,12 +392,73 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara { case PM_UPDATESPECTREM: Sleep(50); - m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer); + m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); PMDawn::Log(PMDawn::LOG_DEBUG, "Changed slideshow image"); break; } break; + case WM_USER+1: + switch (LOWORD(wparam)) + { + case 1: + GetTapeViewerHwnd(); + break; + } + break; + + case WM_USER+2: + switch (LOWORD(wparam)) + { + case PM_TAPE_VIEWER_CLOSED: + if (tapeViewerThread) + { + CloseHandle(tapeViewerThread); + tapeViewerThread = nullptr; + } + break; + case PM_TAPE_COMMAND: + // The tape window is sending a command to do something with the tape + switch (lparam) + { + case PM_TAPE_PLAY: + m_pTape->startPlaying(); + break; + + case PM_TAPE_PAUSE: + m_pTape->stopPlaying(); + break; + + case PM_TAPE_REWIND: + RewindTape(); + break; + + case PM_TAPE_INSERT: + InsertTape(); + break; + + case PM_TAPE_EJECT: + EjectTape(); + break; + + } + break; + } + break; + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(mainWindow, &ps); + //if (m_pMachine) + //{ + // m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); + //} + EndPaint(mainWindow, &ps); + return 0; + break; + } + default: return DefWindowProc(hwnd, msg, wparam, lparam); } @@ -398,6 +494,7 @@ static void InsertTape() if (tR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(szFile)); + SendTapeBlockDataToViewer(); } else { @@ -433,6 +530,7 @@ static void EjectTape() { m_pTape->stopPlaying(); m_pTape->eject(); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_EJECTED, (LPARAM)0); } } @@ -457,11 +555,11 @@ static void OpenSCR() if (scrPath != "") { - ZXSpectrum::Response sR = m_pMachine->scrLoadWithPath(scrPath); + ZXSpectrum::FileResponse sR = m_pMachine->scrLoadWithPath(scrPath); if (sR.success) { Sleep(1); - m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer); + m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); PMDawn::Log(PMDawn::LOG_INFO, "Loaded .scr file - " + std::string(scrPath)); } else @@ -519,15 +617,15 @@ static void IterateSCRImagesOnTimerCallback() if (slideshowRandom) { int randomIndex = (int)rand() % fileList.size(); - ZXSpectrum::Response sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[randomIndex]); + ZXSpectrum::FileResponse sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[randomIndex]); Sleep(1); - m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer); + m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); } else { - ZXSpectrum::Response sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[fileListIndex]); + ZXSpectrum::FileResponse sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[fileListIndex]); Sleep(1); - m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer); + m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); fileListIndex++; if (fileListIndex >= fileList.size()) { @@ -549,7 +647,7 @@ static void IterateSCRImages(HWND mWindow, std::vector fileList, ZX std::chrono::milliseconds msecs(delaysecs * 1000); for (std::size_t i = 0; i < 10; i++)//< fileList.size(); i++) { - ZXSpectrum::Response sR = machine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[i]); + ZXSpectrum::FileResponse sR = machine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[i]); //SendMessageCallback(mWindow, WM_USER, PM_UPDATESPECTREM, PM_UPDATESPECTREM, nullptr, 0); PostMessage(mWindow, WM_USER, PM_UPDATESPECTREM, PM_UPDATESPECTREM); std::this_thread::sleep_for(msecs); @@ -589,10 +687,10 @@ static void ShowHideUI(HWND hWnd = mainWindow) static void ShowUI(HWND hWnd = mainWindow) { PMDawn::Log(PMDawn::LOG_DEBUG, "ShowUI()"); - SetMenu(hWnd, mainMenu); menuDisplayed = true; - ShowWindow(statusWindow, SW_SHOW); statusDisplayed = true; + SetMenu(hWnd, mainMenu); + ShowWindow(statusWindow, SW_SHOW); } //----------------------------------------------------------------------------------------- @@ -600,10 +698,10 @@ static void ShowUI(HWND hWnd = mainWindow) static void HideUI(HWND hWnd = mainWindow) { PMDawn::Log(PMDawn::LOG_DEBUG, "HideUI()"); - SetMenu(hWnd, NULL); menuDisplayed = false; - ShowWindow(statusWindow, SW_HIDE); statusDisplayed = false; + SetMenu(hWnd, NULL); + ShowWindow(statusWindow, SW_HIDE); } //----------------------------------------------------------------------------------------- @@ -680,6 +778,7 @@ static void LoadSnapshot() if (tR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(filePath)); + SendTapeBlockDataToViewer(); } else { @@ -704,7 +803,7 @@ static void LoadSnapshot() if (_stricmp(extension.c_str(), EXT_Z80.c_str()) == 0) { PMDawn::Log(PMDawn::LOG_INFO, "Loading Z80 Snapshot - " + s); - ZXSpectrum::Response sR = m_pMachine->snapshotZ80LoadWithPath(filePath); + ZXSpectrum::FileResponse sR = m_pMachine->snapshotZ80LoadWithPath(filePath); if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); @@ -717,7 +816,7 @@ static void LoadSnapshot() else if (_stricmp(extension.c_str(), EXT_SNA.c_str()) == 0) { PMDawn::Log(PMDawn::LOG_DEBUG, "Loading SNA Snapshot - " + s); - ZXSpectrum::Response sR = m_pMachine->snapshotSNALoadWithPath(filePath); + ZXSpectrum::FileResponse sR = m_pMachine->snapshotSNALoadWithPath(filePath); if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); @@ -736,6 +835,8 @@ static void LoadSnapshot() static void tapeStatusCallback(int blockIndex, int bytes) { if (blockIndex < 1 && m_pTape->playing ==false) return; + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_ACTIVEBLOCK, (LPARAM)blockIndex); + //SendTapeBlockDataToViewer(); //TapeBlock* currentTBI = m_pTape->blocks[blockIndex]; //PMDawn::Log(PMDawn::LOG_DEBUG, "Tape block : " + std::to_string(blockIndex)); //PMDawn::Log(PMDawn::LOG_DEBUG, " Block name : " + currentTBI->getBlockName()); @@ -809,9 +910,14 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) unsigned int cThreads = std::thread::hardware_concurrency(); PMDawn::Log(PMDawn::LOG_INFO, "Maximum available threads = " + std::to_string(cThreads)); + SetupThreadLocalStorageForTapeData(); + + slideshowTimerRunning = false; slideshowRandom = true; //srand((unsigned int)time(NULL)); + viewportX = 0; + viewportY = 0; bool exit_emulator = false; LARGE_INTEGER perf_freq, time, last_time; @@ -849,7 +955,9 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) Log(PMDawn::LOG_INFO, "Current zoom level is " + std::to_string(zoomLevel)); - mainWindow = CreateWindowEx(WS_EX_APPWINDOW, TEXT("SpectREM"), TEXT("SpectREM"), WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX, 0, 0, wr.right - wr.left, wr.bottom - wr.top, 0, 0, inst, 0); + mainWindow = CreateWindowEx(WS_EX_APPWINDOW, TEXT("SpectREM"), TEXT("SpectREM"), + WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, wr.right - wr.left, wr.bottom - wr.top, 0, 0, inst, 0); #ifdef WIN32API_GUI statusWindow = CreateStatusWindow(WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW, TEXT("Welcome to SpyWindows"), mainWindow, 9000); #endif @@ -859,6 +967,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) menuDisplayed = true; statusDisplayed = true; + QueryPerformanceFrequency(&perf_freq); QueryPerformanceCounter(&last_time); @@ -912,7 +1021,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) { last_time = time; - m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer); + m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); // Set the time char specType[20]; @@ -962,6 +1071,12 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) } } } + // if tape viewer is running on it's thread then wait before closing + if (tapeViewerThread != nullptr) + { + WaitForSingleObject(tapeViewerThread, INFINITE); + CloseHandle(tapeViewerThread); + } return 0; } @@ -1063,18 +1178,109 @@ static void DecreaseApplicationVolume() static void OpenTapeViewer() { - TapeViewer* tvWindow = new TapeViewer(GetModuleHandle(NULL), mainWindow); - //int retval = TapeViewer::OpenTapeViewerWindow(GetModuleHandle(NULL), mainWindow); + if (tapeViewerThread) return; + + tapeViewerThread = (HANDLE)_beginthreadex(0, 0, &mythread, 0, 0, 0); } +unsigned int __stdcall mythread(void* data) +{ + + tvWindow = new TapeViewer(GetModuleHandle(NULL), mainWindow, dwTlsIndex); + tvWindow = nullptr; + PostMessage(mainWindow, WM_USER + 2, PM_TAPE_VIEWER_CLOSED, (LPARAM)0); + return 0; +} //----------------------------------------------------------------------------------------- +static void SendTapeBlockDataToViewer() +{ + size_t numBlocks = m_pTape->numberOfTapeBlocks(); + PMDawn::pData.clear(); + for (int i = 0; i < numBlocks; i++) + { + PMDawn::gTAPEBLOCK gT; + + gT.status = " "; + switch (m_pTape->blocks[i]->getDataType()) + { + case 0: // ePROGRAM_HEADER + gT.blocktype = "PROGRAM:"; + break; + + case 1: // eNUMERIC_DATA_HEADER + gT.blocktype = "DATA():"; + break; + + case 2: // eALPHANUMERIC_DATA_HEADER + gT.blocktype = "STRING():"; + break; + + case 3: // eBYTE_HEADER + gT.blocktype = "CODE:"; + break; + + case 4: // eDATA_BLOCK + gT.blocktype = "DATA:"; + break; + + case 5: // eFRAGMENTED_DATA_BLOCK + gT.blocktype = "FRAGMENTED:"; + break; + + case 99: // eUNKNOWN_BLOCK + gT.blocktype = "UNKNOWN:"; + break; + + default: // Uh oh... + gT.blocktype = " DATA:"; + break; + } + + if (m_pTape->blocks[i]->getDataType() < 4) + { + gT.filename = m_pTape->blocks[i]->getFilename(); + gT.autostartline = m_pTape->blocks[i]->getAutoStartLine(); + } + else + { + gT.filename = ""; + gT.autostartline = 0; + } + gT.address = m_pTape->blocks[i]->getStartAddress(); + gT.length = m_pTape->blocks[i]->getDataLength(); + + PMDawn::pData.push_back(gT); + } + + size_t num = PMDawn::pData.size(); + + if (tvHwnd != nullptr) + { + //PostMessage(tvHwnd, WM_USER + 2, reinterpret_cast(&tBlocks), static_cast < LPARAM>(numBlocks)); + //PostMessage(tvHwnd, WM_USER + 2, PM_TAPEDATA_FULL, numBlocks); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPEDATA_FULL, (LPARAM)&PMDawn::pData); + } +} //----------------------------------------------------------------------------------------- +static void GetTapeViewerHwnd() +{ + tvHwnd = TapeViewer::tapeViewerWindowInternal; +} //----------------------------------------------------------------------------------------- +static void SetupThreadLocalStorageForTapeData() +{ + return; + //// Setup the thread local storage + //dwTlsIndex = TlsAlloc(); + //TlsSetValue(dwTlsIndex, GlobalAlloc(GPTR, sizeof(PMDawn::THREADDATA))); + //PMDawn::pData = (PMDawn::PTHREADDATA)TlsGetValue(dwTlsIndex); + //PMDawn::pData->filename = "POLO2"; +} //----------------------------------------------------------------------------------------- diff --git a/SpectREM/resource.h b/SpectREM/resource.h index 87a0946..bae33d7 100644 --- a/SpectREM/resource.h +++ b/SpectREM/resource.h @@ -8,6 +8,12 @@ #define IDR_MENUACCELERATORS 102 #define IDI_ICON1 105 #define IDI_ICON2 108 +#define IDS_FIRSTCOLUMN 111 +#define IDS_BLOCKTYPE 112 +#define IDS_FILENAME 113 +#define IDS_AUTOSTARTLINE 114 +#define IDS_ADDRESS 115 +#define IDS_LENGTH 116 #define ID_TEXTFILE 256 #define IDC_BTN_SETTINGS_SAVE 1001 #define IDC_BTN_SETTINGS_CLOSE 1002 @@ -58,7 +64,7 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 110 +#define _APS_NEXT_RESOURCE_VALUE 112 #define _APS_NEXT_COMMAND_VALUE 40075 #define _APS_NEXT_CONTROL_VALUE 1002 #define _APS_NEXT_SYMED_VALUE 101 From 09cee9c0277ac1ba9f4e134317d39a735762eae3 Mon Sep 17 00:00:00 2001 From: John Young Date: Sat, 18 Jan 2020 11:43:43 +0000 Subject: [PATCH 05/23] FIXED: Forgot to send data to tape window when it was reopened with a tape already inserted. --- SpectREM/SpectREM/Win32/TapeViewerWindow.cpp | 30 ++++++++++++++++++-- SpectREM/SpectREM/Win32/WinMain.cpp | 4 +++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp index 60822d8..c65bff1 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp @@ -16,6 +16,8 @@ #include #include +#define PM_TAPE_PAUSED 60 +#define PM_TAPE_PLAYING 61 #define PM_TAPEDATA_FULL 77 #define PM_TAPE_COMMAND 79 @@ -34,6 +36,8 @@ HWND TapeViewer::tapeViewerWindowInternal = nullptr; HWND mHandle; static std::vector* myP; +static uint16_t currentActiveBlock; +static bool bIsPlaying = false; // false is paused, true is playing TapeViewer::~TapeViewer() { @@ -175,6 +179,8 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA } ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_FULLROWSELECT); + currentActiveBlock = 0; + ListView_SetItemText(hwndListView, currentActiveBlock, 0, TEXT("PAUSED")); return 0; } } @@ -182,6 +188,7 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA { if (hwndListView != nullptr) { ListView_DeleteAllItems(hwndListView); + currentActiveBlock = -1; return 0; } } @@ -189,8 +196,19 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA { if (hwndListView != nullptr) { uint16_t blockNumber = (uint16_t)lParam; - - //ListView_DeleteAllItems(hwndListView); + if (currentActiveBlock >= 0) + { + ListView_SetItemText(hwndListView, currentActiveBlock, 0, TEXT("")); + if (bIsPlaying) + { + ListView_SetItemText(hwndListView, blockNumber, 0, TEXT("PLAYING")); + } + else + { + ListView_SetItemText(hwndListView, blockNumber, 0, TEXT("PAUSED")); + } + currentActiveBlock = blockNumber; + } return 0; } } @@ -206,18 +224,26 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA { case IDC_BUTTON_PLAY: PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_PLAY); + ListView_SetItemText(hwndListView, currentActiveBlock, 0, TEXT("PLAYING")); + bIsPlaying = true; break; case IDC_BUTTON_PAUSE: PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_PAUSE); + ListView_SetItemText(hwndListView, currentActiveBlock, 0, TEXT("PAUSED")); + bIsPlaying = false; break; case IDC_BUTTON_REWIND: PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_REWIND); + ListView_SetItemText(hwndListView, currentActiveBlock, 0, TEXT("")); + ListView_SetItemText(hwndListView, 0, 0, TEXT("PAUSED")); break; case IDC_BUTTON_INSERT: PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_INSERT); + bIsPlaying = false; break; case IDC_BUTTON_EJECT: PostMessage(mHandle, WM_USER + 2, PM_TAPE_COMMAND, (LPARAM)PM_TAPE_EJECT); + bIsPlaying = false; break; case IDC_BUTTON_SAVE: MessageBox(hwnd, TEXT("SAVE"), TEXT("BUTTON"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 6309a0f..4fbd59c 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -403,6 +403,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara { case 1: GetTapeViewerHwnd(); + if (m_pTape->numberOfTapeBlocks() > 0) + { + SendTapeBlockDataToViewer(); + } break; } break; From 71f4fcc148de983bf5907df008d638a50af099cf Mon Sep 17 00:00:00 2001 From: John Young Date: Sat, 18 Jan 2020 12:17:08 +0000 Subject: [PATCH 06/23] Just some tidying up --- SpectREM/SpectREM/Win32/PMDawn.cpp | 5 +---- SpectREM/SpectREM/Win32/TapeViewerWindow.cpp | 9 +-------- SpectREM/SpectREM/Win32/TapeViewerWindow.hpp | 2 -- SpectREM/SpectREM/Win32/WinMain.cpp | 1 + 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/SpectREM/SpectREM/Win32/PMDawn.cpp b/SpectREM/SpectREM/Win32/PMDawn.cpp index fa50eeb..39602d2 100644 --- a/SpectREM/SpectREM/Win32/PMDawn.cpp +++ b/SpectREM/SpectREM/Win32/PMDawn.cpp @@ -19,11 +19,8 @@ namespace PMDawn { - //----------------------------------------------------------------------------------------- - - //----------------------------------------------------------------------------------------- bool PMDawn::fileExists(const std::string& filename) @@ -299,7 +296,7 @@ namespace PMDawn void AddItemToListView(gTAPEBLOCK& theBlock, HWND hwndListView) { // Status / BlockType / Filename / AutostartLine / Address / Length - // TAPE block types + // TAPE block types /*enum { ePROGRAM_HEADER = 0, diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp index c65bff1..39f67b4 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp @@ -41,7 +41,7 @@ static bool bIsPlaying = false; // false is paused, true is playing TapeViewer::~TapeViewer() { - //PostMessage(mHandle, WM_USER + 2, PM_TAPE_VIEWER_CLOSING, (LPARAM)0); + // } LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) @@ -55,7 +55,6 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA static HWND hwndEjectButton; static HWND hwndSaveButton; static HWND hwndListView; - static TCHAR szTempListData[] = TEXT("Temporary listbox data"); switch (msg) { @@ -212,8 +211,6 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA return 0; } } - - return 0; break; } @@ -261,7 +258,6 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA { case NM_DBLCLK: { - // code here <-- LPNMITEMACTIVATE lpNMItem = (LPNMITEMACTIVATE)lParam; return 0; } @@ -362,9 +358,6 @@ TapeViewer::TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle, DWORD dwTlsInd mHandle = mainHandle; - // thread data - //PMDawn::pData = (PMDawn::PTHREADDATA)TlsGetValue(dwTlsIndex); - memset(&wcextv, 0, sizeof(WNDCLASSEX)); wcextv.cbSize = sizeof(WNDCLASSEX); wcextv.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp index ae81acf..9403de2 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp @@ -32,6 +32,4 @@ class TapeViewer static const uint8_t buttonYBuffer = 12; static HWND tapeViewerWindowInternal; static HINSTANCE g_hInst; - - }; \ No newline at end of file diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 4fbd59c..a55c6fd 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -406,6 +406,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara if (m_pTape->numberOfTapeBlocks() > 0) { SendTapeBlockDataToViewer(); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_ACTIVEBLOCK, (LPARAM)0); } break; } From 35a48fd6e6fc1420ab2ca745266c3e57d87ae2e5 Mon Sep 17 00:00:00 2001 From: John Young Date: Sat, 18 Jan 2020 20:18:16 +0000 Subject: [PATCH 07/23] ADDED: Title bar text indicating loaded file (playing/paused for taps, filename for sna/z80) --- SpectREM/SpectREM/Win32/TapeViewerWindow.cpp | 1 - SpectREM/SpectREM/Win32/WinMain.cpp | 40 ++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp index 39f67b4..d506039 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp @@ -215,7 +215,6 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA break; } - case WM_COMMAND: switch (LOWORD(wParam)) { diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index a55c6fd..db26e92 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -85,6 +85,7 @@ AudioCore* m_pAudioCore; AudioQueue* m_pAudioQueue; OpenGLView* m_pOpenGLView; static TapeViewer* tvWindow; +std::string loadedFile; enum MachineType { @@ -499,12 +500,15 @@ static void InsertTape() if (tR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(szFile)); + PathStripPathA(szFile); + loadedFile = "TAPE: " + std::string(szFile); SendTapeBlockDataToViewer(); } else { MessageBox(mainWindow, TEXT("Unable to load tape >> "), TEXT("Tape Loader"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); PMDawn::Log(PMDawn::LOG_INFO, "Failed to load tape - " + std::string(szFile) + " > " + tR.responseMsg); + loadedFile = "-empty-"; return; } } @@ -783,16 +787,20 @@ static void LoadSnapshot() if (tR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(filePath)); + PathStripPathA(const_cast(filePath.c_str())); + loadedFile = "TAPE: " + filePath; SendTapeBlockDataToViewer(); } else { MessageBox(mainWindow, TEXT("Unable to load tape >> "), TEXT("Tape Loader"), MB_OK | MB_ICONINFORMATION | MB_APPLMODAL); + loadedFile = "-empty-"; PMDawn::Log(PMDawn::LOG_INFO, "Failed to load tape - " + std::string(filePath) + " > " + tR.responseMsg); } } else { + EjectTape(); if (mType <= ZX48) { // 48 based @@ -812,10 +820,14 @@ static void LoadSnapshot() if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); + PMDawn::Log(PMDawn::LOG_INFO, "Loaded snapshot - " + std::string(filePath)); + PathStripPathA(const_cast(filePath.c_str())); + loadedFile = ".Z80: " + filePath; } else { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loading failed : " + sR.responseMsg); + loadedFile = "-empty-"; } } else if (_stricmp(extension.c_str(), EXT_SNA.c_str()) == 0) @@ -825,10 +837,14 @@ static void LoadSnapshot() if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); + PMDawn::Log(PMDawn::LOG_INFO, "Loaded snapshot - " + std::string(filePath)); + PathStripPathA(const_cast(filePath.c_str())); + loadedFile = ".SNA: " + filePath; } else { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loading failed : " + sR.responseMsg); + loadedFile = "-empty"; } } } @@ -917,7 +933,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) SetupThreadLocalStorageForTapeData(); - + loadedFile = "-empty-"; slideshowTimerRunning = false; slideshowRandom = true; //srand((unsigned int)time(NULL)); @@ -1070,8 +1086,26 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) sprintf_s(lLevel, sizeof(lLevel), " "); } - char buff[100]; - sprintf_s(buff, sizeof(buff), "SpectREM - %4.1f fps - [%s] - %s %s", 1.0f / delta_time, specType, zoom, lLevel); + char lfBuff[300]; + if (m_pTape->playing) + { + sprintf_s(lfBuff, sizeof(lfBuff), "%s > Playing", loadedFile.c_str()); + } + else + { + if (m_pTape->loaded) + { + sprintf_s(lfBuff, sizeof(lfBuff), "%s > Paused", loadedFile.c_str()); + } + else + { + sprintf_s(lfBuff, sizeof(lfBuff), "%s", loadedFile.c_str()); + } + } + + + char buff[512]; + sprintf_s(buff, sizeof(buff), "SpectREM - %4.1f fps - [%s] - %s - [%s] %s", 1.0f / delta_time, specType, zoom, lfBuff, lLevel); SetWindowTextA(mainWindow, buff); } } From 588cef0d5bc26efc3354228f3f421681151b5ee2 Mon Sep 17 00:00:00 2001 From: John Young Date: Sat, 18 Jan 2020 20:44:12 +0000 Subject: [PATCH 08/23] UPDATED: Info.txt file --- SpectREM/Info.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SpectREM/Info.txt b/SpectREM/Info.txt index 0ad5b2f..07c1be8 100644 --- a/SpectREM/Info.txt +++ b/SpectREM/Info.txt @@ -24,6 +24,13 @@ PAGEDOWN : Decrease volume +2020-01-18 +----------- +ADDED : Tape viewer window with controls and updates (minor bug found, https://github.com/polomint/SpectREMCPP/issues/28) +FIXED : Forgot to resend the currently inserted .tap data to the tape viewer in between invocations :/ +ADDED : File loaded now displays in title bar, shows Playing/Paused if it is a .tap file +UPDATED : File loading and folder choosing now uses the new updated file open dialog + 2020-01-05 ----------- ADDED : Volume control using PageUp/PageDown From a2769851adf02be1b68e966a305933119aff55d4 Mon Sep 17 00:00:00 2001 From: John Young Date: Fri, 24 Jan 2020 13:00:53 +0000 Subject: [PATCH 09/23] FIXED: When opening the tape viewer while a tape is either already playing or is paused on any block it will update the information in the tape viewer window correctly. --- SpectREM/Info.txt | 11 + .../SpectREM/Emulation Core/Tape/Tape.hpp | 1 + SpectREM/SpectREM/Win32/OpenGLView.cpp | 5 +- SpectREM/SpectREM/Win32/TapeViewerWindow.cpp | 63 +- SpectREM/SpectREM/Win32/TapeViewerWindow.hpp | 3 + SpectREM/SpectREM/Win32/WinMain.cpp | 150 +++-- SpectREM/SpectREM/Win32/modTZX.bas | 569 ++++++++++++++++++ 7 files changed, 740 insertions(+), 62 deletions(-) create mode 100644 SpectREM/SpectREM/Win32/modTZX.bas diff --git a/SpectREM/Info.txt b/SpectREM/Info.txt index 07c1be8..b7d5ac7 100644 --- a/SpectREM/Info.txt +++ b/SpectREM/Info.txt @@ -19,17 +19,28 @@ SHIFT+F1 : Insert a tape ALT+F1 : Eject currently inserted tape F9 : Start/Stop currently inserted tape SHIFT+F9 : Rewind tape if inserted +ALT+F9 : Open tape viewer window PAGEUP : Increase volume PAGEDOWN : Decrease volume +2020-01-24 +----------- +FIXED : When opening the tape viewer while a tape is either already playing or is paused, + on any block it will update the information in the tape viewer window correctly. + 2020-01-18 ----------- ADDED : Tape viewer window with controls and updates (minor bug found, https://github.com/polomint/SpectREMCPP/issues/28) FIXED : Forgot to resend the currently inserted .tap data to the tape viewer in between invocations :/ ADDED : File loaded now displays in title bar, shows Playing/Paused if it is a .tap file + +2020-01-06 +----------- UPDATED : File loading and folder choosing now uses the new updated file open dialog +ADDED : Can now select folder for .scr files +ADDED : Open single .scr 2020-01-05 ----------- diff --git a/SpectREM/SpectREM/Emulation Core/Tape/Tape.hpp b/SpectREM/SpectREM/Emulation Core/Tape/Tape.hpp index 99bb79b..e34614b 100755 --- a/SpectREM/SpectREM/Emulation Core/Tape/Tape.hpp +++ b/SpectREM/SpectREM/Emulation Core/Tape/Tape.hpp @@ -12,6 +12,7 @@ #include #include #include +#include // - Tape Block diff --git a/SpectREM/SpectREM/Win32/OpenGLView.cpp b/SpectREM/SpectREM/Win32/OpenGLView.cpp index 2b9a3e1..524c2cb 100644 --- a/SpectREM/SpectREM/Win32/OpenGLView.cpp +++ b/SpectREM/SpectREM/Win32/OpenGLView.cpp @@ -137,6 +137,7 @@ void OpenGLView::Deinit() void OpenGLView::Resize(int width, int height) { + GL_CHECK(glViewport(0, 0, width, height)); _viewWidth = width; _viewHeight = height; } @@ -188,7 +189,7 @@ bool OpenGLView::Init(HWND hWnd, int width, int height, uint16_t idClutVert, uin return false; } - // Set the 4.0 version of OpenGL in the attribute list. + // Set the 3.2 version of OpenGL in the attribute list. int contextAL[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, @@ -358,7 +359,7 @@ void OpenGLView::SetupTexture() void OpenGLView::UpdateTextureData(unsigned char *pData, GLint vX, GLint vY) { - glClearColor(0.0f, 1.0f, 1.0f, 0.5f); + glClearColor(1.0f, 1.0f, 0.0f, 0.5f); glClear(GL_COLOR_BUFFER_BIT); // Render the output to a texture which has the default dimensions of the output image diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp index d506039..826ea3a 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.cpp @@ -29,6 +29,7 @@ #define PM_TAPE_INSERT 85 #define PM_TAPE_EJECT 86 #define PM_TAPE_SAVE 87 +#define PM_TAPE_UPDATE_PLAYPAUSEETC 88 // Status / BlockType / Filename / AutostartLine / Address / Length //----------------------------------------------------------------------------------------- @@ -140,13 +141,6 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA LVITEM lvi; lvi.mask = LVIF_TEXT | LVIF_COLFMT; - //lvi.iItem = 0; - //lvi.iSubItem = 0; - //lvi.pszText = TEXT("Kiss"); - //ListView_InsertItem(hwndListView, &lvi); - //ListView_SetItemText(hwndListView, 0, 1, TEXT("My")); - //ListView_SetItemText(hwndListView, 0, 2, TEXT("Ass")); - //ListView_SetItemText(hwndListView, 0, 3, TEXT("!!!")); ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_FULLROWSELECT); } } @@ -193,23 +187,24 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA } if (wParam == PM_TAPE_ACTIVEBLOCK) { - if (hwndListView != nullptr) { - uint16_t blockNumber = (uint16_t)lParam; - if (currentActiveBlock >= 0) - { - ListView_SetItemText(hwndListView, currentActiveBlock, 0, TEXT("")); - if (bIsPlaying) - { - ListView_SetItemText(hwndListView, blockNumber, 0, TEXT("PLAYING")); - } - else - { - ListView_SetItemText(hwndListView, blockNumber, 0, TEXT("PAUSED")); - } - currentActiveBlock = blockNumber; - } - return 0; + UpdateActiveBlock(hwndListView, lParam); + return 0; + } + if (wParam == PM_TAPE_UPDATE_PLAYPAUSEETC) + { + if (lParam == 1) + { + // The tape is playing the current block + bIsPlaying = true; + UpdateActiveBlock(hwndListView, currentActiveBlock); + } + else + { + // The tape is paused on the current block + bIsPlaying = false; + UpdateActiveBlock(hwndListView, currentActiveBlock); } + return 0; } return 0; break; @@ -281,6 +276,28 @@ LRESULT CALLBACK TapeViewer::WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARA //----------------------------------------------------------------------------------------- +void TapeViewer::UpdateActiveBlock(HWND hwndListV, LPARAM lP) +{ + if (hwndListV != nullptr) { + uint16_t blockNumber = (uint16_t)lP; + if (currentActiveBlock >= 0) + { + ListView_SetItemText(hwndListV, currentActiveBlock, 0, TEXT("")); + if (bIsPlaying) + { + ListView_SetItemText(hwndListV, blockNumber, 0, TEXT("PLAYING")); + } + else + { + ListView_SetItemText(hwndListV, blockNumber, 0, TEXT("PAUSED")); + } + currentActiveBlock = blockNumber; + } + } +} + +//----------------------------------------------------------------------------------------- + // InitListViewColumns: Adds columns to a list-view control. // hWndListView: Handle to the list-view control. // Returns TRUE if successful, and FALSE otherwise. diff --git a/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp b/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp index 9403de2..74470e4 100644 --- a/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp +++ b/SpectREM/SpectREM/Win32/TapeViewerWindow.hpp @@ -25,6 +25,9 @@ class TapeViewer public: TapeViewer(HINSTANCE mainWindowInst, HWND mainHandle, DWORD dwTlsIndex);// , std::vectormyPTAPE); ~TapeViewer(); + + + static void UpdateActiveBlock(HWND hwndListV, LPARAM lP); static LRESULT CALLBACK WndProcTV(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); static HWND TapeViewer::CreateListView(HWND hwnd, LPARAM lParam, RECT rect); static BOOL TapeViewer::InitListViewColumns(HWND hWndListView, HINSTANCE hInst); diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index db26e92..3d52df6 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -21,6 +21,7 @@ #define PM_TAPE_INSERT 85 #define PM_TAPE_EJECT 86 #define PM_TAPE_SAVE 87 +#define PM_TAPE_UPDATE_PLAYPAUSEETC 88 #include #include @@ -78,6 +79,7 @@ unsigned int __stdcall mythread(void* data); static void SendTapeBlockDataToViewer(); static void GetTapeViewerHwnd(); static void SetupThreadLocalStorageForTapeData(); +RECT GetWindowResizeWithUI(HWND mWin, HWND sWin, HMENU menuWin, bool visible); ZXSpectrum* m_pMachine; Tape* m_pTape; @@ -374,17 +376,9 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara break; case WM_SIZE: - //cxClient = LOWORD(lparam); - //cyClient = HIWORD(lparam); - //if (menuDisplayed) - //{ - // viewportY += 20; - //} - //else - //{ - // viewportY -= 20; - //} - //glViewport(viewportX, viewportY, LOWORD(lparam), HIWORD(lparam)); + cxClient = LOWORD(lparam); + cyClient = HIWORD(lparam); + //glViewport(viewportX, viewportY, 256 * zoomLevel, 192 * zoomLevel); return 0; break; @@ -399,7 +393,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; - case WM_USER+1: + case WM_USER + 1: switch (LOWORD(wparam)) { case 1: @@ -407,13 +401,21 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara if (m_pTape->numberOfTapeBlocks() > 0) { SendTapeBlockDataToViewer(); - PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_ACTIVEBLOCK, (LPARAM)0); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_ACTIVEBLOCK, (LPARAM)m_pTape->currentBlockIndex); + if (m_pTape->playing) + { + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_UPDATE_PLAYPAUSEETC, 1); // Indicate tape is playing + } + else + { + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_UPDATE_PLAYPAUSEETC, 0); // Indicate tape is paused + } } break; } break; - case WM_USER+2: + case WM_USER + 2: switch (LOWORD(wparam)) { case PM_TAPE_VIEWER_CLOSED: @@ -428,11 +430,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara switch (lparam) { case PM_TAPE_PLAY: - m_pTape->startPlaying(); + m_pTape->play(); break; case PM_TAPE_PAUSE: - m_pTape->stopPlaying(); + m_pTape->stop();// stopPlaying(); break; case PM_TAPE_REWIND: @@ -449,7 +451,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara } break; - } + } break; case WM_PAINT: @@ -496,7 +498,7 @@ static void InsertTape() if (GetOpenFileNameA(&ofn)) { EjectTape(); // Eject the current tape if inserted - Tape::TapResponse tR = m_pTape->loadWithPath(szFile); + Tape::FileResponse tR = m_pTape->insertTapeWithPath(szFile); if (tR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(szFile)); @@ -522,11 +524,11 @@ static void PlayPauseTape() { if (m_pTape->playing) { - m_pTape->stopPlaying(); + m_pTape->stop(); } else { - m_pTape->startPlaying(); + m_pTape->play(); } } } @@ -537,7 +539,7 @@ static void EjectTape() { if (m_pTape->loaded) { - m_pTape->stopPlaying(); + m_pTape->stop(); m_pTape->eject(); PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_EJECTED, (LPARAM)0); } @@ -549,7 +551,7 @@ static void RewindTape() { if (m_pTape->loaded) { - m_pTape->stopPlaying(); + m_pTape->stop(); m_pTape->rewindTape(); } } @@ -564,7 +566,7 @@ static void OpenSCR() if (scrPath != "") { - ZXSpectrum::FileResponse sR = m_pMachine->scrLoadWithPath(scrPath); + Tape::FileResponse sR = m_pMachine->scrLoadWithPath(scrPath); if (sR.success) { Sleep(1); @@ -626,13 +628,13 @@ static void IterateSCRImagesOnTimerCallback() if (slideshowRandom) { int randomIndex = (int)rand() % fileList.size(); - ZXSpectrum::FileResponse sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[randomIndex]); + Tape::FileResponse sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[randomIndex]); Sleep(1); m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); } else { - ZXSpectrum::FileResponse sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[fileListIndex]); + Tape::FileResponse sR = m_pMachine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[fileListIndex]); Sleep(1); m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); fileListIndex++; @@ -656,7 +658,7 @@ static void IterateSCRImages(HWND mWindow, std::vector fileList, ZX std::chrono::milliseconds msecs(delaysecs * 1000); for (std::size_t i = 0; i < 10; i++)//< fileList.size(); i++) { - ZXSpectrum::FileResponse sR = machine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[i]); + Tape::FileResponse sR = machine->scrLoadWithPath(PMDawn::GetApplicationBasePath() + slideshowDirectory + fileList[i]); //SendMessageCallback(mWindow, WM_USER, PM_UPDATESPECTREM, PM_UPDATESPECTREM, nullptr, 0); PostMessage(mWindow, WM_USER, PM_UPDATESPECTREM, PM_UPDATESPECTREM); std::this_thread::sleep_for(msecs); @@ -698,7 +700,10 @@ static void ShowUI(HWND hWnd = mainWindow) PMDawn::Log(PMDawn::LOG_DEBUG, "ShowUI()"); menuDisplayed = true; statusDisplayed = true; + RECT newSize = GetWindowResizeWithUI(mainWindow, statusWindow, mainMenu, true); SetMenu(hWnd, mainMenu); + SetWindowPos(mainWindow, HWND_NOTOPMOST, newSize.left, newSize.top, newSize.right, newSize.bottom, 0); + m_pOpenGLView->Resize(256 * zoomLevel, 192 * zoomLevel); ShowWindow(statusWindow, SW_SHOW); } @@ -709,12 +714,77 @@ static void HideUI(HWND hWnd = mainWindow) PMDawn::Log(PMDawn::LOG_DEBUG, "HideUI()"); menuDisplayed = false; statusDisplayed = false; + RECT newSize = GetWindowResizeWithUI(mainWindow, statusWindow, mainMenu, false); SetMenu(hWnd, NULL); + SetWindowPos(mainWindow, HWND_NOTOPMOST, newSize.left, newSize.top, newSize.right, newSize.bottom, 0); + m_pOpenGLView->Resize(256 * zoomLevel, 192 * zoomLevel); ShowWindow(statusWindow, SW_HIDE); } //----------------------------------------------------------------------------------------- +RECT GetWindowResizeWithUI(HWND mWin, HWND sWin, HMENU menu, bool visible) +{ + // Get the new window size needed for the status bar to be below the GLView... + if (visible) + { + Log(PMDawn::LOG_INFO, "Resizing with UI"); + Log(PMDawn::LOG_INFO, "Menu height == " + GetSystemMetrics(SM_CYMENU)); + } + else + { + Log(PMDawn::LOG_INFO, "Resizing without UI"); + Log(PMDawn::LOG_INFO, "Menu height == " + GetSystemMetrics(SM_CYMENU)); + } + RECT m, s; + GetWindowRect(mWin, &m); + GetWindowRect(sWin, &s); + + Log(PMDawn::LOG_INFO, "Main window RECT = t" + + std::to_string(m.top) + " l" + + std::to_string(m.left) + " b" + + std::to_string(m.bottom) + " r" + + std::to_string(m.right)); + Log(PMDawn::LOG_INFO, "Status bar RECT = t" + + std::to_string(s.top) + " l" + + std::to_string(s.left) + " b" + + std::to_string(s.bottom) + " r" + + std::to_string(s.right)); + + if (visible) + { + RECT nWin = { + m.left, + m.top, + (m.right - m.left), + (m.bottom - m.top) + (s.bottom - s.top) + GetSystemMetrics(SM_CYMENU) + }; + Log(PMDawn::LOG_INFO, "Output RECT = t" + + std::to_string(nWin.top) + " l" + + std::to_string(nWin.left) + " b" + + std::to_string(nWin.bottom) + " r" + + std::to_string(nWin.right)); + return nWin; + } + else + { + RECT nWin = { + m.left, + m.top, + (m.right - m.left), + (m.bottom - m.top) - (s.bottom - s.top) - GetSystemMetrics(SM_CYMENU) + }; + Log(PMDawn::LOG_INFO, "Output RECT = t" + + std::to_string(nWin.top) + " l" + + std::to_string(nWin.left) + " b" + + std::to_string(nWin.bottom) + " r" + + std::to_string(nWin.right)); + return nWin; + } +} + +//----------------------------------------------------------------------------------------- + static void ShowHelpAbout() { PMDawn::Log(PMDawn::LOG_DEBUG, "ShowHelpAbout()"); @@ -783,7 +853,7 @@ static void LoadSnapshot() if (_stricmp(extension.c_str(), EXT_TAP.c_str()) == 0) { EjectTape(); // Eject the current tape if inserted - Tape::TapResponse tR = m_pTape->loadWithPath(filePath); + Tape::FileResponse tR = m_pTape->insertTapeWithPath(filePath); if (tR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Loaded tape - " + std::string(filePath)); @@ -816,7 +886,7 @@ static void LoadSnapshot() if (_stricmp(extension.c_str(), EXT_Z80.c_str()) == 0) { PMDawn::Log(PMDawn::LOG_INFO, "Loading Z80 Snapshot - " + s); - ZXSpectrum::FileResponse sR = m_pMachine->snapshotZ80LoadWithPath(filePath); + Tape::FileResponse sR = m_pMachine->snapshotZ80LoadWithPath(filePath); if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); @@ -833,7 +903,7 @@ static void LoadSnapshot() else if (_stricmp(extension.c_str(), EXT_SNA.c_str()) == 0) { PMDawn::Log(PMDawn::LOG_DEBUG, "Loading SNA Snapshot - " + s); - ZXSpectrum::FileResponse sR = m_pMachine->snapshotSNALoadWithPath(filePath); + Tape::FileResponse sR = m_pMachine->snapshotSNALoadWithPath(filePath); if (sR.success) { PMDawn::Log(PMDawn::LOG_INFO, "Snapshot loaded successfully"); @@ -855,7 +925,7 @@ static void LoadSnapshot() static void tapeStatusCallback(int blockIndex, int bytes) { - if (blockIndex < 1 && m_pTape->playing ==false) return; + if (blockIndex < 1 && m_pTape->playing == false) return; PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_ACTIVEBLOCK, (LPARAM)blockIndex); //SendTapeBlockDataToViewer(); //TapeBlock* currentTBI = m_pTape->blocks[blockIndex]; @@ -958,7 +1028,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) wcex.cbWndExtra = 0; wcex.hInstance = inst; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); - wcex.hbrBackground = NULL; + wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wcex.lpszClassName = TEXT("SpectREM"); wcex.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON2)); wcex.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON2), IMAGE_ICON, 16, 16, 0); @@ -976,18 +1046,23 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) Log(PMDawn::LOG_INFO, "Current zoom level is " + std::to_string(zoomLevel)); - mainWindow = CreateWindowEx(WS_EX_APPWINDOW, TEXT("SpectREM"), TEXT("SpectREM"), - WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX, + mainWindow = CreateWindowEx(WS_EX_APPWINDOW, TEXT("SpectREM"), TEXT("SpectREM"), + WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, wr.right - wr.left, wr.bottom - wr.top, 0, 0, inst, 0); #ifdef WIN32API_GUI - statusWindow = CreateStatusWindow(WS_CHILD | WS_VISIBLE | WS_OVERLAPPEDWINDOW, TEXT("Welcome to SpyWindows"), mainWindow, 9000); + //statusWindow = CreateStatusWindow(WS_CHILD | WS_VISIBLE, TEXT("Welcome to SpectREM for Windows"), mainWindow, 9000); + statusWindow = CreateWindow(STATUSCLASSNAME, TEXT("Welcome to SpectREM for Windows"), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, mainWindow, NULL, inst, NULL); #endif ShowWindow(mainWindow, ncmd); UpdateWindow(mainWindow); mainMenu = GetMenu(mainWindow); menuDisplayed = true; statusDisplayed = true; - + //ShowHideUI(mainWindow); +#ifdef WIN32API_GUI + RECT newSize = GetWindowResizeWithUI(mainWindow, statusWindow, mainMenu, true); + SetWindowPos(mainWindow, HWND_NOTOPMOST, newSize.left, newSize.top, newSize.right, newSize.bottom, 0); +#endif QueryPerformanceFrequency(&perf_freq); QueryPerformanceCounter(&last_time); @@ -1044,6 +1119,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) m_pOpenGLView->UpdateTextureData(m_pMachine->displayBuffer, viewportX, viewportY); + // Set the time char specType[20]; if (m_pMachine->machineInfo.machineType == eZXSpectrum48) @@ -1219,7 +1295,7 @@ static void OpenTapeViewer() { if (tapeViewerThread) return; - tapeViewerThread = (HANDLE)_beginthreadex(0, 0, &mythread, 0, 0, 0); + tapeViewerThread = (HANDLE)_beginthreadex(0, 0, &mythread, 0, 0, 0); } unsigned int __stdcall mythread(void* data) @@ -1275,7 +1351,7 @@ static void SendTapeBlockDataToViewer() gT.blocktype = " DATA:"; break; } - + if (m_pTape->blocks[i]->getDataType() < 4) { gT.filename = m_pTape->blocks[i]->getFilename(); diff --git a/SpectREM/SpectREM/Win32/modTZX.bas b/SpectREM/SpectREM/Win32/modTZX.bas new file mode 100644 index 0000000..fdbbc89 --- /dev/null +++ b/SpectREM/SpectREM/Win32/modTZX.bas @@ -0,0 +1,569 @@ +Attribute VB_Name = "modTZX" +' /******************************************************************************* +' modTAP.bas within vbSpec.vbp +' +' Routines to handle loading of ".TZX" files (Spectrum tape images) +' +' Authors: Mark Woodmass +' Paul Dunn +' +' This program is free software; you can redistribute it and/or +' modify it under the terms of the GNU General Public License +' as published by the Free Software Foundation; either version 2 +' of the License, or (at your option) any later version. +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY; without even the implied warranty of +' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +' GNU General Public License for more details. +' +' You should have received a copy of the GNU General Public License +' along with this program; if not, write to the Free Software +' Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +' +' *******************************************************************************/ +Option Explicit + +Public gbTZXInserted As Long, gbTZXPlaying As Long +Public glEarBit As Long +Public TZXCurBlock As Long +Public TZXNumBlocks As Long + +Private TZXArray() As Long +Private TZXOffsets() As Long +Private TZXBlockLength() As Long +Private BitValues(7) As Long + +Private TZXBlockIsStandardTiming As Boolean +Private TZXCallList() As Long +Private TZXCallCounter As Long, TZXNumCalls As Long, TZXCallByte As Long +Private TZXTotalTs As Long + +Private TZXState As Long, TZXAimTStates As Long +Private TZXPointer As Long, TZXCurBlockID As Long +Private TZXPulseLen As Long, TZXSync1Len As Long, TZXSync2Len As Long +Private TZXZeroLen As Long, TZXOneLen As Long, TZXPauseLen As Long +Private TZXDataLen As Long, TZXROMDataLen As Long, TZXUsedBits As Long +Private TZXByte As Long +Private TZXDataPos As Long, TZXPulsesDone As Long +Private TZXPulseToneLen As Long, TZXBitLimit As Long, TZXBitCounter As Long +Private TZXLoopCounter As Long, TZXLoopPoint As Long + +' //////////////////////////////////////////////////////////////////////////////// +' // GetTZXBlockInfo() +' // +' // Retreives information about a specific TZX block in the current file +' // +' // lBlockNum IN Number of block to retrieve information on (zero based) +' // lType OUT The type of block (see the TZX specification document) +' // sText OUT Human-readable text describing the block +' // lLen OUT Length of the block in bytes +Public Sub GetTZXBlockInfo(lBlockNum As Long, lType As Long, sText As String, lLen As Long) + Dim lPtr As Long, l As Long + + lPtr = TZXOffsets(lBlockNum) + lType = TZXArray(lPtr) + + Select Case lType + Case &H10 + sText = "Standard Block" + Case &H11 + sText = "Turbo Block" + Case &H12 + sText = "Pure Tone" + Case &H13 + sText = "Pulse Sequence" + Case &H14 + sText = "Pure Data Block" + Case &H15 + sText = "Direct Recording" + Case &H16 + sText = "C64 Standard Block" + Case &H17 + sText = "C64 Turbo Block" + Case &H20 + ' // Pause/StopTape + l = TZXArray(lPtr + 1) + (TZXArray(lPtr + 2) * 256&) + If l = 0 Then + sText = "Stop Tape" + Else + sText = "Pause Tape for " & CStr(l) & "ms" + End If + Case &H21 + sText = "Group Start" + Case &H22 + sText = "Group End" + Case &H23 + sText = "Jump to Block" + Case &H24 + sText = "Loop Start" + Case &H25 + sText = "Loop End" + Case &H2A + sText = "Stop Tape if 48K" + Case &H30 + sText = "" + l = TZXArray(lPtr + 1) + For l = lPtr + 2 To lPtr + 1 + l + sText = sText & Chr(TZXArray(l)) + Next l + Case &H31 + sText = "Message Block" + Case &H32 + sText = "Archive Info" + Case &H33 + sText = "Hardware Type" + Case &H34 + sText = "Emulation Info" + Case &H35 + sText = "Custom Info Block" + Case &H40 + sText = "Snapshot Block" + Case &H5A + sText = "Block Merge Marker" + Case &HFE + sText = "End of Tape" + End Select + + lLen = TZXBlockLength(lBlockNum) +End Sub + +Public Sub StartTape() + gbTZXPlaying = True + TZXTotalTs = 0 + glEarBit = 0 +End Sub + +Public Sub StopTape() + If gbTZXPlaying Then gbTZXPlaying = False +End Sub + +Public Sub StartStopTape() + If gbTZXPlaying Then StopTape Else StartTape +End Sub + +Public Sub OpenTZXFile(sName As String) + Dim ReadLength As Long + Dim s As String, b As Long, lCounter As Long + Dim F As Long, BlockLen As Long, BlockID As Long, ArrayLength As Long + Dim BlockList(2048) As Long + Dim BlockListNum As Long + Dim BlockLengths(2048) As Long + Dim BlockLengthsNum As Long + + Dim hTZXFile As Long + + Let b = 1 + For F = 0 To 7 + BitValues(F) = b + Let b = b * 2 + Next F + + ' // If we currently have a TAP file open, then close it + CloseTAPFile + + If Dir$(sName) = "" Then Exit Sub + + hTZXFile = FreeFile + Open sName For Binary As hTZXFile + + ReadLength = LOF(hTZXFile) + If ReadLength = 0 Then + Close #hTZXFile + Exit Sub + End If + + frmMainWnd.NewCaption = App.ProductName & " - " & GetFilePart(sName) + + ' Read the TZX file into TZXArray + ReDim TZXArray(ReadLength + 1) + + On Error Resume Next + + s = Input(ReadLength, #hTZXFile) + For lCounter = 1 To Len(s) + TZXArray(lCounter - 1) = Asc(Mid$(s, lCounter, 1)) + Next lCounter + TZXArray(ReadLength) = &HFE& ' end-of-tape block + + Close #hTZXFile + + ' Now decode the TZX file into an individual blocks list + gbTZXPlaying = False + gbTZXInserted = False + + s = "" + ArrayLength = ReadLength + 1 + + For F = 0 To 6 + s = s & Chr(TZXArray(F)) + Next F + + If s <> "ZXTape!" Then + Close #hTZXFile + End If + + BlockListNum = 0 + BlockLengthsNum = 0 + gbTZXInserted = True + F = 10 + + Do + BlockID = TZXArray(F) + BlockList(BlockListNum) = F + BlockListNum = BlockListNum + 1 + + F = F + 1 + + Select Case BlockID + Case &H10: BlockLen = 256& * TZXArray(F + 3) + TZXArray(F + 2) + 4 + Case &H11: BlockLen = TZXArray(F + 15) + (TZXArray(F + 16) * 256&) + (TZXArray(F + 17) * 65536) + 18 + Case &H12: BlockLen = 4 + Case &H13: BlockLen = 1 + (TZXArray(F) * 2) + Case &H14: BlockLen = TZXArray(F + 7) + (TZXArray(F + 8) * 256&) + (TZXArray(F + 9) * 65536) + 10 + Case &H15: BlockLen = TZXArray(F + 5) + (TZXArray(F + 6) * 256&) + (TZXArray(F + 7) * 65536) + 8 + Case &H20: BlockLen = 2 + Case &H21: BlockLen = TZXArray(F) + 1 + Case &H22: BlockLen = 0 + Case &H23: BlockLen = 2 + Case &H24: BlockLen = 2 + Case &H25: BlockLen = 0 + Case &H26: BlockLen = (TZXArray(F) + (TZXArray(F + 1) * 256&) * 2) + 2 + Case &H27: BlockLen = 0 + Case &H28: BlockLen = TZXArray(F) + (TZXArray(F + 1) * 256&) + 2 + Case &H2A: BlockLen = 4 + Case &H30: BlockLen = TZXArray(F) + 1 + Case &H31: BlockLen = TZXArray(F + 1) + 2 + Case &H32: BlockLen = TZXArray(F) + (TZXArray(F + 1) * 256&) + 2 + Case &H33: BlockLen = (TZXArray(F) * 3) + 1 + Case &H34: BlockLen = 8 + Case &H35: BlockLen = TZXArray(F + 16) + (TZXArray(F + 17) * 256&) + (TZXArray(F + 18) * 65536) + (TZXArray(F + 19) * 16777216) + 20 + Case &H40: BlockLen = TZXArray(F + 1) + (TZXArray(F + 2) * 256&) + (TZXArray(F + 3) * 65536) + 4 + Case &H5A: BlockLen = 9 + Case &HFE: BlockLen = 0 + Case &HFF: BlockLen = 0 + Case Else: BlockLen = TZXArray(F) + (TZXArray(F + 1) * 256&) + (TZXArray(F + 2) * 65536) + (TZXArray(F + 3) * 16777216) + 4 + End Select + + F = F + BlockLen + BlockLengths(BlockLengthsNum) = BlockLen + 1 + BlockLengthsNum = BlockLengthsNum + 1 + Loop Until F >= ArrayLength + + TZXNumBlocks = BlockListNum + + ReDim TZXOffsets(TZXNumBlocks) + ReDim TZXBlockLength(TZXNumBlocks) + + For F = 0 To TZXNumBlocks - 1 + TZXOffsets(F) = BlockList(F) + TZXBlockLength(F) = BlockLengths(F) + Next F + SetCurTZXBlock 0 + + frmTapePlayer.UpdateTapeList +End Sub + +Public Sub SetCurTZXBlock(BlockNum As Long) + Dim F As Long + + TZXBlockIsStandardTiming = False + TZXState = 5 + TZXAimTStates = 0 + TZXPointer = TZXOffsets(BlockNum) + TZXCurBlockID = TZXArray(TZXPointer) + Select Case TZXCurBlockID + Case &H10: ' Standard ROM Loader block + TZXPulseLen = 2168 + If TZXArray(TZXPointer + 5) = &HFF Then TZXPulseToneLen = 3220 Else TZXPulseToneLen = 8064 + TZXSync1Len = 667 + TZXSync2Len = 735 + TZXZeroLen = 855 + TZXOneLen = 1710 + TZXPauseLen = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) + TZXDataLen = TZXBlockLength(BlockNum) + TZXOffsets(BlockNum) + TZXROMDataLen = TZXArray(TZXPointer + 3) + (TZXArray(TZXPointer + 4) * 256&) + TZXUsedBits = 8 + TZXState = 0 ' State 0 - playing Pulse + TZXAimTStates = TZXPulseLen + TZXByte = 0 + TZXCurBlock = BlockNum + TZXDataPos = TZXPointer + 5 + TZXPulsesDone = 2 + TZXBlockIsStandardTiming = True + + Case &H11: ' Non-Standard TAP block + TZXPulseLen = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) + TZXPulseToneLen = TZXArray(TZXPointer + 11) + (TZXArray(TZXPointer + 12) * 256&) + TZXSync1Len = TZXArray(TZXPointer + 3) + (TZXArray(TZXPointer + 4) * 256&) + TZXSync2Len = TZXArray(TZXPointer + 5) + (TZXArray(TZXPointer + 6) * 256&) + TZXZeroLen = TZXArray(TZXPointer + 7) + (TZXArray(TZXPointer + 8) * 256&) + TZXOneLen = TZXArray(TZXPointer + 9) + (TZXArray(TZXPointer + 10) * 256&) + TZXUsedBits = TZXArray(TZXPointer + 13) + TZXPauseLen = TZXArray(TZXPointer + 14) + (TZXArray(TZXPointer + 15) * 256&) + TZXState = 0 ' State 0 - playing Pulse. + TZXAimTStates = TZXPulseLen + TZXByte = 0 + TZXCurBlock = BlockNum + TZXDataPos = TZXPointer + 19 + TZXDataLen = TZXBlockLength(BlockNum) + TZXOffsets(BlockNum) + TZXROMDataLen = TZXArray(TZXPointer + 3) + (TZXArray(TZXPointer + 4) * 256&) + If (TZXPulseLen = 2168) And ((TZXPulseToneLen = 3220) Or (TZXPulseToneLen = 8064)) And (TZXSync1Len = 667) And (TZXSync2Len = 735) And (TZXZeroLen = 855) And (TZXOneLen = 1710) Then TZXBlockIsStandardTiming = True Else TZXBlockIsStandardTiming = False + + Case &H12: ' Pure Tone + TZXState = 0 ' playing a possible pilot tone + TZXPulseLen = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) + TZXPulseToneLen = TZXArray(TZXPointer + 3) + (TZXArray(TZXPointer + 4) * 256&) + TZXAimTStates = TZXPulseLen + TZXByte = 1 + TZXCurBlock = BlockNum + + Case &H13: ' Row of Pulses + TZXState = 0 ' playing a possible pilot tone + TZXPulseToneLen = TZXArray(TZXPointer + 1) ' // NUMBER OF PULSES + TZXPulseLen = TZXArray(TZXPointer + 2) + (TZXArray(TZXPointer + 3) * 256&) + TZXPulsesDone = 1 + TZXByte = TZXPointer + 4 + TZXAimTStates = TZXPulseLen + TZXCurBlock = BlockNum + + Case &H14: ' Pure Data block + TZXZeroLen = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) + TZXOneLen = TZXArray(TZXPointer + 3) + (TZXArray(TZXPointer + 4) * 256&) + TZXUsedBits = TZXArray(TZXPointer + 5) + TZXPauseLen = TZXArray(TZXPointer + 6) + (TZXArray(TZXPointer + 7) * 256&) + TZXDataLen = TZXBlockLength(BlockNum) + TZXOffsets(BlockNum) + TZXState = 3 ' Set to DATA Byte(s) output. + ' // CC IN + TZXDataPos = TZXPointer + 11 + ' // CC OUT + TZXByte = TZXPointer + 11 + If (TZXArray(TZXByte) And 128) > 0 Then TZXAimTStates = TZXOneLen Else TZXAimTStates = TZXZeroLen + TZXPulsesDone = 2 + If TZXByte = TZXDataLen - 1 Then TZXBitLimit = BitValues(8 - TZXUsedBits) Else TZXBitLimit = 1 + TZXBitCounter = 128 + TZXCurBlock = BlockNum + + Case &H15: ' Direct Recording Block + TZXOneLen = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) ' Length of Sample (Ts) + TZXPauseLen = TZXArray(TZXPointer + 3) + (TZXArray(TZXPointer + 4) * 256&) ' (ms) + TZXUsedBits = TZXArray(TZXPointer + 5) ' Samples used in last byte + TZXDataLen = TZXArray(TZXPointer + 6) + (TZXArray(TZXPointer + 7) * 256&) + TZXArray(TZXPointer + 8) * 65536 ' TZXBlockLength(BlockNum) + TZXOffsets(BlockNum) + TZXByte = TZXPointer + 9 + TZXState = 3 ' Set to DATA bytes output + TZXAimTStates = TZXOneLen + If TZXByte = TZXDataLen - 1 Then TZXBitLimit = BitValues(8 - TZXUsedBits) Else TZXBitLimit = 1 + TZXBitCounter = 128 + glEarBit = 64 * (TZXArray(TZXByte) \ 128) + TZXCurBlock = BlockNum + + Case &H20: ' Pause or STOP tape. + TZXCurBlock = BlockNum + TZXPauseLen = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) + If TZXPauseLen = 0 Then + If gbTZXPlaying Then StartStopTape + Else + TZXAimTStates = TZXPauseLen * 3500 + TZXState = 4 ' When the TZXTStates gets past TZXAimStates, the next block will be used + End If + + Case &H23: ' Jump to block + TZXByte = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) + If TZXByte < 32768 Then SetCurTZXBlock (BlockNum + TZXByte) Else SetCurTZXBlock (BlockNum - (65536 - TZXByte)) + + Case &H24: ' Loop Start + TZXLoopCounter = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) + TZXLoopPoint = BlockNum + 1 + SetCurTZXBlock (BlockNum + 1) + + Case &H25: ' Loop End + TZXLoopCounter = TZXLoopCounter - 1 + If TZXLoopCounter > 0 Then SetCurTZXBlock (TZXLoopPoint) Else SetCurTZXBlock (BlockNum + 1) + + Case &H26: ' Call Sequence + TZXNumCalls = TZXArray(TZXPointer + 1) + (TZXArray(TZXPointer + 2) * 256&) - 1 + TZXCallByte = TZXNumCalls + TZXCallCounter = 0 + ReDim TZXCallList(TZXNumCalls) + For F = 0 To TZXNumCalls - 1 + TZXCallList(F) = TZXArray((TZXPointer + 4) + (F * 2)) + (TZXArray((TZXPointer + 5) + (F * 2) + 1) * 256&) + Next F + TZXCallByte = BlockNum + TZXByte = TZXArray(TZXPointer + 3) + (TZXArray(TZXPointer + 4) * 256&) + If TZXByte < 32768 Then SetCurTZXBlock (BlockNum + TZXByte) Else SetCurTZXBlock (BlockNum - (65536 - TZXByte)) + + Case &H27: ' CALL Return + If TZXCallCounter < TZXNumCalls Then + TZXCallCounter = TZXCallCounter + 1 + TZXByte = TZXCallList(TZXCallCounter) + If TZXByte < 32768 Then SetCurTZXBlock (TZXCallByte + TZXByte) Else SetCurTZXBlock (TZXCallByte - (65536 - TZXByte)) + End If + + Case &H2A: ' Stop tape in 48k Mode + If glEmulatedModel = 0 Then ' 48k Speccy? + If gbTZXPlaying Then StartStopTape + End If + TZXCurBlock = BlockNum + + Case &HFE: ' End of Tape + TZXAimTStates = 30 + TZXCurBlock = BlockNum + If gbTZXPlaying Then SetCurTZXBlock (0) + StopTape + + Case Else: TZXCurBlock = BlockNum + End Select + +' If TZXCurBlock > TZXLastDataBlock Then TZXState = 5 + If frmTapePlayer.Visible Then frmTapePlayer.UpdateCurBlock +End Sub + +Public Sub UpdateTZXState(TapeTStates As Long) + Dim LastEarBit As Long, F As Long + + TZXTotalTs = TZXTotalTs + TapeTStates + While (TZXTotalTs >= TZXAimTStates) And gbTZXPlaying + TZXTotalTs = TZXTotalTs - TZXAimTStates + Select Case TZXCurBlockID + Case &H10&, &H11&, &H14& + Select Case TZXState + Case 0 'Playing Pilot tone. + glEarBit = glEarBit Xor 64 + If TZXByte < TZXPulseToneLen Then ' TZXByte holds number of pulses + TZXAimTStates = TZXPulseLen + TZXByte = TZXByte + 1& + Else + TZXByte = 0 + TZXState = 1 ' Set to SYNC1 Pulse output + TZXAimTStates = TZXSync1Len + End If + + Case 1 ' SYNC 1 + glEarBit = glEarBit Xor 64 + TZXState = 2 ' Set to SYNC2 Pulse output + TZXAimTStates = TZXSync2Len + + Case 2 ' SYNC 2 + glEarBit = glEarBit Xor 64 + TZXState = 3 ' Set to DATA Byte(s) output + TZXByte = TZXDataPos + If (TZXArray(TZXByte) And 128) > 0 Then ' Set next pulse length + TZXAimTStates = TZXOneLen + Else + TZXAimTStates = TZXZeroLen + End If + TZXPulsesDone = 2 ' *2* edges per Data BIT, one on, one off + TZXBitCounter = 128 ' Start with the full byte + TZXBitLimit = 1 + + Case 3 ' DATA Bytes out + glEarBit = glEarBit Xor 64 + TZXPulsesDone = TZXPulsesDone - 1 + If TZXPulsesDone = 0 Then ' Done both pulses for this bit? + If TZXBitCounter > TZXBitLimit Then ' Done all the bits for this byte? + TZXBitCounter = TZXBitCounter \ 2 ' Bitcounter counts *down* + TZXPulsesDone = 2 + If (TZXArray(TZXByte) And TZXBitCounter) > 0 Then + TZXAimTStates = TZXOneLen + Else + TZXAimTStates = TZXZeroLen + End If + Else ' all bits done, setup for next byte + TZXByte = TZXByte + 1 + If TZXByte < TZXDataLen Then ' last byte? + If TZXByte = TZXDataLen - 1 Then + TZXBitLimit = BitValues(8 - TZXUsedBits) ' if so, set up the last bits used + Else + TZXBitLimit = 1 ' else use full 8 bits + End If + TZXBitCounter = 128 + TZXPulsesDone = 2 + If (TZXArray(TZXByte) And 128) > 0 Then + TZXAimTStates = TZXOneLen + Else + TZXAimTStates = TZXZeroLen + End If + Else + If (TZXPauseLen > 0) Then + TZXAimTStates = TZXPauseLen * 3500 + TZXState = 4 ' Set to Pause output + Else + TZXState = 0 + SetCurTZXBlock (TZXCurBlock + 1) + End If + End If + End If + Else ' Not done both pulses, flip the ear bit next time + If (TZXArray(TZXByte) And TZXBitCounter) > 0 Then + TZXAimTStates = TZXOneLen + Else + TZXAimTStates = TZXZeroLen + End If + End If + + Case 4: ' End Pause output + SetCurTZXBlock (TZXCurBlock + 1) + End Select + + Case &H12& + glEarBit = glEarBit Xor 64 + If TZXByte < TZXPulseToneLen Then + TZXAimTStates = TZXPulseLen + TZXByte = TZXByte + 1 + Else + SetCurTZXBlock (TZXCurBlock + 1) + End If + + Case &H13& + glEarBit = glEarBit Xor 64 + If TZXPulsesDone < TZXPulseToneLen Then + TZXPulseLen = TZXArray(TZXByte) + (TZXArray(TZXByte + 1) * 256&) + TZXAimTStates = TZXPulseLen + TZXByte = TZXByte + 2 + TZXPulsesDone = TZXPulsesDone + 1 + Else + SetCurTZXBlock (TZXCurBlock + 1) + End If + + Case &H15& ' *UnTested* - any TZX actually use a DRB block? + LastEarBit = glEarBit + If TZXBitCounter > TZXBitLimit Then ' Done all the bits for this byte? + TZXBitCounter = TZXBitCounter \ 2 ' Bitcounter counts *down* + If (TZXArray(TZXByte) And TZXBitCounter) > 0 Then ' Set the ear bit + glEarBit = 64 + Else + glEarBit = 0 + End If + TZXAimTStates = TZXOneLen + Else ' all bits done, setup for next byte + TZXByte = TZXByte + 1 + If TZXByte < TZXDataLen Then ' last byte? + If TZXByte = TZXDataLen - 1 Then + TZXBitLimit = BitValues(8 - TZXUsedBits) ' if so, set up the last bits used + Else + TZXBitLimit = 1 ' else use full 8 bits + End If + TZXBitCounter = 128 + If (TZXArray(TZXByte) And TZXBitCounter) > 0 Then ' Set the ear bit + glEarBit = 64 + Else + glEarBit = 0 + End If + TZXAimTStates = TZXOneLen + Else + If TZXPauseLen > 0 Then + TZXAimTStates = TZXPauseLen * 3500 + TZXState = 4 ' Set to Pause output + Else + TZXState = 0 + SetCurTZXBlock (TZXCurBlock + 1) + End If + End If + End If + Case &HFE + StopTape + Case Else + SetCurTZXBlock (TZXCurBlock + 1) + End Select + Wend +End Sub + From 230e531a476c72c47c4bbdfb9c94ac19ea7caf31 Mon Sep 17 00:00:00 2001 From: John Young Date: Fri, 24 Jan 2020 13:10:27 +0000 Subject: [PATCH 10/23] FIXED: Play/Pause from main menu now updates the tape window if visible. --- SpectREM/SpectREM/Win32/WinMain.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 3d52df6..90a7838 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -525,10 +525,12 @@ static void PlayPauseTape() if (m_pTape->playing) { m_pTape->stop(); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_UPDATE_PLAYPAUSEETC, 0); } else { m_pTape->play(); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_UPDATE_PLAYPAUSEETC, 1); } } } @@ -552,6 +554,7 @@ static void RewindTape() if (m_pTape->loaded) { m_pTape->stop(); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_UPDATE_PLAYPAUSEETC, 0); m_pTape->rewindTape(); } } From 690f5bf4053001e55f2ea71dd3b32827a2d36613 Mon Sep 17 00:00:00 2001 From: John Young Date: Fri, 24 Jan 2020 13:16:23 +0000 Subject: [PATCH 11/23] FIXED : Rewind from main menu updates the tape window if visible. --- SpectREM/Info.txt | 3 ++- SpectREM/SpectREM/Win32/WinMain.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SpectREM/Info.txt b/SpectREM/Info.txt index b7d5ac7..6f12cd0 100644 --- a/SpectREM/Info.txt +++ b/SpectREM/Info.txt @@ -28,7 +28,8 @@ PAGEDOWN : Decrease volume ----------- FIXED : When opening the tape viewer while a tape is either already playing or is paused, on any block it will update the information in the tape viewer window correctly. - +FIXED : Play/Pause from main menu now updates the tape window if visible. +FIXED : Rewind from main menu updates the tape window if visible. 2020-01-18 ----------- diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 90a7838..f186e8f 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -554,8 +554,9 @@ static void RewindTape() if (m_pTape->loaded) { m_pTape->stop(); - PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_UPDATE_PLAYPAUSEETC, 0); m_pTape->rewindTape(); + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_ACTIVEBLOCK, (LPARAM)0); // Let the tape viewer know we are on block number 0 + PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_UPDATE_PLAYPAUSEETC, 0); // Let the tape viewer know that we are paused } } //----------------------------------------------------------------------------------------- From 9922e16644c8154fffaab4e1405ffcb94a17701e Mon Sep 17 00:00:00 2001 From: John Young Date: Sun, 26 Jan 2020 10:29:28 +0000 Subject: [PATCH 12/23] FIXED : Incorrect reporting of tap blocks under 2 bytes --- SpectREM/Info.txt | 5 +++++ SpectREM/SpectREM/Win32/PMDawn.cpp | 9 ++++++++- SpectREM/SpectREM/Win32/WinMain.cpp | 2 -- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/SpectREM/Info.txt b/SpectREM/Info.txt index 6f12cd0..cb5a195 100644 --- a/SpectREM/Info.txt +++ b/SpectREM/Info.txt @@ -24,6 +24,11 @@ PAGEUP : Increase volume PAGEDOWN : Decrease volume +2020-01-26 +----------- +FIXED : Incorrect reporting of tap blocks under 2 bytes :/ + + 2020-01-24 ----------- FIXED : When opening the tape viewer while a tape is either already playing or is paused, diff --git a/SpectREM/SpectREM/Win32/PMDawn.cpp b/SpectREM/SpectREM/Win32/PMDawn.cpp index 39602d2..bac94b0 100644 --- a/SpectREM/SpectREM/Win32/PMDawn.cpp +++ b/SpectREM/SpectREM/Win32/PMDawn.cpp @@ -310,7 +310,14 @@ namespace PMDawn if (!ShowAlternativeTapeLengths) { - theBlock.length -= 2; + if (theBlock.length < 2) + { + theBlock.length = 0; + } + else + { + theBlock.length -= 2; + } } LVITEM lvi; diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index f186e8f..5c0d484 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -1376,8 +1376,6 @@ static void SendTapeBlockDataToViewer() if (tvHwnd != nullptr) { - //PostMessage(tvHwnd, WM_USER + 2, reinterpret_cast(&tBlocks), static_cast < LPARAM>(numBlocks)); - //PostMessage(tvHwnd, WM_USER + 2, PM_TAPEDATA_FULL, numBlocks); PostMessage(tvHwnd, WM_USER + 2, PM_TAPEDATA_FULL, (LPARAM)&PMDawn::pData); } } From 58e9604b1e76710cc0ddcea18255b1f82f2f84c4 Mon Sep 17 00:00:00 2001 From: John Young Date: Sun, 26 Jan 2020 18:03:49 +0000 Subject: [PATCH 13/23] FIXED : Strange filename issue causing an exception --- SpectREM/Info.txt | 2 ++ SpectREM/SpectREM/Win32/WinMain.cpp | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SpectREM/Info.txt b/SpectREM/Info.txt index cb5a195..c1a3e5d 100644 --- a/SpectREM/Info.txt +++ b/SpectREM/Info.txt @@ -27,6 +27,8 @@ PAGEDOWN : Decrease volume 2020-01-26 ----------- FIXED : Incorrect reporting of tap blocks under 2 bytes :/ +FIXED : Strange filename issue causing an exception + 2020-01-24 diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 5c0d484..8a4abe2 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -850,8 +850,9 @@ static void LoadSnapshot() if (filePath != "") { int32_t mType = m_pMachine->snapshotMachineInSnapshotWithPath(filePath.c_str()); - std::string s(filePath, sizeof(filePath)); - std::string extension = s.substr(s.find_last_of(".") + 1, s.find_last_of(".") + 4); + size_t sizeOfFile = sizeof(filePath) - 1; + std::string s(filePath.c_str(), sizeOfFile); + std::string extension = filePath.substr(filePath.find_last_of(".") + 1, filePath.find_last_of(".") + 4); // Check the machine type returned from the user supplied snapshot if not a tape file if (_stricmp(extension.c_str(), EXT_TAP.c_str()) == 0) From 4f145a42ee8846a9ee29ec6eb016055e2e5fd5d4 Mon Sep 17 00:00:00 2001 From: John Young Date: Sun, 26 Jan 2020 18:41:31 +0000 Subject: [PATCH 14/23] FIXED : AY can now be enabled on a 48k snapshot (retrieves the flag from the snapshot when loading) HACK : Hack added to get it to load that uspirit.z80 file, ay works with it also (48k mode) --- SpectREM/Info.txt | 3 ++- .../ZX_Spectrum_Core/Snapshot.cpp | 23 ++++++++++++++++++- .../ZX_Spectrum_Core/ZXSpectrum.hpp | 3 +++ SpectREM/SpectREM/Win32/WinMain.cpp | 22 +++++++++--------- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/SpectREM/Info.txt b/SpectREM/Info.txt index c1a3e5d..525d654 100644 --- a/SpectREM/Info.txt +++ b/SpectREM/Info.txt @@ -28,7 +28,8 @@ PAGEDOWN : Decrease volume ----------- FIXED : Incorrect reporting of tap blocks under 2 bytes :/ FIXED : Strange filename issue causing an exception - +FIXED : AY can now be enabled on a 48k snapshot (retrieves the flag from the snapshot when loading) +HACK : Hack added to get it to load that uspirit.z80 file, ay works with it also (48k mode) 2020-01-24 diff --git a/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp b/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp index 35ccaea..8cd4cd5 100755 --- a/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp +++ b/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp @@ -567,7 +567,7 @@ void ZXSpectrum::snapshotExtractMemoryBlock(const char *buffer, size_t bufferSiz } else { - while (memoryPtr < unpackedLength + memAddr) + while (memoryPtr < unpackedLength + memAddr - 1) { uint8_t byte1 = fileBytes[filePtr]; uint8_t byte2 = fileBytes[filePtr + 1]; @@ -652,6 +652,8 @@ std::string ZXSpectrum::snapshotHardwareTypeForVersion(uint32_t version, uint32_ int32_t ZXSpectrum::snapshotMachineInSnapshotWithPath(const char *path) { + ayEnabledSnapshot = false; + std::ifstream stream(path, std::ios::binary | std::ios::ate); if (!stream.ios_base::good()) { return -1; @@ -694,6 +696,7 @@ int32_t ZXSpectrum::snapshotMachineInSnapshotWithPath(const char *path) switch (version) { case 1: machineType = eZXSpectrum48; + ayEnabledSnapshot = false; break; case 2: @@ -713,6 +716,8 @@ int32_t ZXSpectrum::snapshotMachineInSnapshotWithPath(const char *path) machineType = eZXSpectrum48; break; } + IsAYSnapshot(((uint8_t*)&pFileBytes[37])[0]); + break; case 3: @@ -732,6 +737,8 @@ int32_t ZXSpectrum::snapshotMachineInSnapshotWithPath(const char *path) default: break; } + IsAYSnapshot(((uint8_t*)&pFileBytes[37])[0]); + break; } } @@ -739,3 +746,17 @@ int32_t ZXSpectrum::snapshotMachineInSnapshotWithPath(const char *path) return machineType; } +bool ZXSpectrum::IsAYSnapshot(uint8_t infoByte) +{ + if (infoByte & 4) + { + ayEnabledSnapshot = true; + } + else + { + ayEnabledSnapshot = false; + } + return ayEnabledSnapshot; +} + + diff --git a/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/ZXSpectrum.hpp b/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/ZXSpectrum.hpp index a330d79..c0a9442 100755 --- a/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/ZXSpectrum.hpp +++ b/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/ZXSpectrum.hpp @@ -168,6 +168,8 @@ class ZXSpectrum float b; float a; } Color; + // Holds whether AY is enabled for a snapshot (handy for 48k + AY) + bool ayEnabledSnapshot = false; public: @@ -195,6 +197,7 @@ class ZXSpectrum Tape::FileResponse snapshotSNALoadWithPath(const std::string path); Tape::FileResponse snapshotSNALoadWithBuffer(const char *buffer, size_t size); int snapshotMachineInSnapshotWithPath(const char *path); + bool IsAYSnapshot(uint8_t infoByte); SnapshotData snapshotCreateSNA(); SnapshotData snapshotCreateZ80(); diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 8a4abe2..c7b2889 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -61,7 +61,7 @@ static void ShowHelpAbout(); static void ShowHideUI(HWND hWnd); static void ShowUI(HWND hWnd); static void HideUI(HWND hWnd); -static void ResetMachineForSnapshot(uint8_t mc); +static void ResetMachineForSnapshot(uint8_t mc, bool ayEnabled); static void ShowSettingsDialog(); static void RunSlideshow(int secs); void IterateSCRImages(HWND mWindow, std::vector fileList, ZXSpectrum* m_pMachine, int secs); @@ -239,10 +239,10 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara SoftReset(); break; case ID_SWITCH_TO48K: - ResetMachineForSnapshot(ZX48); + ResetMachineForSnapshot(ZX48, m_pMachine->ayEnabledSnapshot); break; case ID_SWITCH_TO128K: - ResetMachineForSnapshot(ZX128); + ResetMachineForSnapshot(ZX128, true); break; case ID_SWITCH_FLIP: SwitchMachines(); @@ -805,14 +805,14 @@ static void SwitchMachines() if (isResetting != true) { PMDawn::Log(PMDawn::LOG_DEBUG, "Flip machine requested"); - ResetMachineForSnapshot(ZX48); + ResetMachineForSnapshot(ZX48, m_pMachine->ayEnabledSnapshot); } } else { if (isResetting != true) { - ResetMachineForSnapshot(ZX128); + ResetMachineForSnapshot(ZX128, true); } } } @@ -824,7 +824,7 @@ static void SoftReset() // Soft reset if (isResetting != true) { - ResetMachineForSnapshot(m_pMachine->machineInfo.machineType); + ResetMachineForSnapshot(m_pMachine->machineInfo.machineType, m_pMachine->ayEnabledSnapshot); PMDawn::Log(PMDawn::LOG_INFO, "Soft reset completed"); } } @@ -836,7 +836,7 @@ static void HardReset() // Hard reset if (isResetting != true) { - ResetMachineForSnapshot(m_pMachine->machineInfo.machineType); + ResetMachineForSnapshot(m_pMachine->machineInfo.machineType, m_pMachine->ayEnabledSnapshot); PMDawn::Log(PMDawn::LOG_INFO, "Hard reset completed"); } } @@ -879,13 +879,13 @@ static void LoadSnapshot() if (mType <= ZX48) { // 48 based - ResetMachineForSnapshot(ZX48); + ResetMachineForSnapshot(ZX48, m_pMachine->ayEnabledSnapshot); Sleep(500); } else { // 128 based - ResetMachineForSnapshot(ZX128); + ResetMachineForSnapshot(ZX128, true); Sleep(500); } if (_stricmp(extension.c_str(), EXT_Z80.c_str()) == 0) @@ -1205,7 +1205,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) //----------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------- -static void ResetMachineForSnapshot(uint8_t mc) +static void ResetMachineForSnapshot(uint8_t mc, bool ayEnabled) { isResetting = true; @@ -1229,7 +1229,7 @@ static void ResetMachineForSnapshot(uint8_t mc) case ZX48: PMDawn::Log(PMDawn::LOG_INFO, "SpectREM changed to 48K Mode"); m_pMachine = new ZXSpectrum48(m_pTape); - m_pMachine->emuUseAYSound = false; + m_pMachine->emuUseAYSound = ayEnabled; break; case ZX128: PMDawn::Log(PMDawn::LOG_INFO, "SpectREM changed to 128K Mode"); From 0d82ad0ecf27dbf2097b631e8c04c2c4fdb6bde5 Mon Sep 17 00:00:00 2001 From: John Young Date: Mon, 27 Jan 2020 10:01:55 +0000 Subject: [PATCH 15/23] Removed hack for uspirit.z80 --- SpectREM/SpectREM.aps | Bin 188188 -> 189616 bytes SpectREM/SpectREM.rc | 123 +++++++++++++++--- .../ZX_Spectrum_Core/Snapshot.cpp | 3 +- SpectREM/resource.h | 13 +- 4 files changed, 114 insertions(+), 25 deletions(-) diff --git a/SpectREM/SpectREM.aps b/SpectREM/SpectREM.aps index 7ea4f066ebbff2ef925a1a478381a4a87014b5b2..2606ea3b9174690f5b966927b6ec9d37021c505f 100644 GIT binary patch delta 2684 zcmb7FZERCz6h8O%gLRu-+pXRDv2MaPF(TY`>jpBAt*u*Ov~~9mgn-=?5QiDtgaizs zjURxCU*y%)D8YXQ6GA2uHENK=Xe4Sh{_qE%Gcl1wqWm%ZQR_MHd+*&Hn?HP;bMJZ2 zInVie&wG2~eAV>Ds@IaXW!GgkQnmK|7yj@_x7mL05_Z;G&*?>1Gh5{Qt`LWIr5n=8 zWIi{P&E4D+{5 z`&(z-Vo|C**=H!2Y(?IQcP(ZHwJw-4+^ZF$B8blum8VXLG5Hni!%Go!Gml61sLr(qjZWl(J-Y08uu11Kju87kc(=a$)0Fo(o7l=wt7K!xUA5j#sUZ#G-HOsK zM$OJv`25u*hzWs%l^x+I&)yu>$k!OK8c+TcW?K&>@@teY)Iv@18EPC zbRF&=GG$|2c4Urhip##$Whs0wLZa1=^b4?PB%r*(1^Dm?1~as*J3-$F5Hn2v2S{Qp z9%*9J;Z;G83%>_qN%#^~O@ga}$&r}!9@#y2Aae9TMEdAJL~c1>Q$(v~({c~{HPqNN z02c-b4e%SB;O`_n2~WcWJO!h0kadTbtl!1u;vwaFU^B$P4L7ls<#G}FU_b1`-(DEU z&k?P?a1Ni%NJdV`f`a+7TOofnd|i}D0C8!WdBe*kiLDEVMswS^5Fhwm$$bTH^C+US zB4lBMq1NA0?l0Yjyiq0z%y=9ghn<)^twE$_B$34!)f=XIQH1M9c#FSP%y&QHc3`b7 z9@{hun~?-)ia__yHpu{O6y30RxV50taqCOQcBNq}RG>fd`7CHyN)qV- zjKW*Y!|%i01WX}6fu5$Yod>yO6rR96Eh@4Zsa*c2K*LK5TWStA?YT`QGQwz*HTC${ z&jpT=-NHZck?r!Kj?%6xa#Vh#i~ZiI%Vt)U-X+-&J?z@*2peDRa|Y0v zXAR$;!7W|oZEH%)ugbB$;uO5z^#jh zsdK*9*Ri8fskEX|3bj>8F!F(_!qpZ>@Y8 z>Xpl+cOr?;mQ_E2`a`B(1heXksIR_4c=5r+t#xz?{GOjS4JJv??>T4iK{v2?)o?I> t_UN+RH1+f6j!xjSYlL5D=pPdkKVurqPkKP#U?-nm>zPekRXCa zDiTT%ZBZ{FWu$%%wB^(*v7l46Bv1wgHX@6}S>h1Ttn{E(a4q8NLj4!C$f8 z#QRNz^bnR_qN(r@DGk`Wi9rMcx7xdLf7<@B5C$U@Z#Hy;AdWvcEvj(*oo(;7AiY#8 z73~p&7!7*lpthY-zq=mklJ6N1#_HcVhSmm1ilMPP#45zLJ<$6wZMcV2VC#Gw88VPOS^g;48>}wZ&hm*d$3A+bzScx4awb! zJ}>2PLi4=ogz9;h6N=|8BqY!4jL1AC?kY63n}qk;%40Tr&Qfu1Jpy~um0mWt#({(r@F4Xq1p zWhH<%&W`;_wbsR8y&pd{uB`Ag`{fUsW}EL*)pj6n6UqzZ8)A|OdE(>g)1*Q^B9mmi zk%Ycbe&O++db}bzN{*2eWE?cDUnU{{Y6fq}>jP1k)J)+TEF?81KA_QNx3G?pvvbL9`v*H1A0fyWBSDyoQkEK>-l<}b3NPmfUlLc!fa}Z#_3BHHfb57L7R_z zGRbLgrtln`mbjMV;5HG*>Pfh*DdHS*x0vi7y!%b_X%vkS4IHl%NEH0*U^}q(_MD|B15mH>k|YkC2(a%f=9; ZOLy5MK9gJSQ5;i53%X-QL#~5wZvj~K9;E;P diff --git a/SpectREM/SpectREM.rc b/SpectREM/SpectREM.rc index 970f207..6de945d 100644 --- a/SpectREM/SpectREM.rc +++ b/SpectREM/SpectREM.rc @@ -19,21 +19,6 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_SETTINGS_DIALOG DIALOGEX 0, 0, 309, 189 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_CHILD | WS_CAPTION -CAPTION "SpectREM Settings" -FONT 8, "MS Shell Dlg", 400, 0, 0x0 -BEGIN - PUSHBUTTON "Save",IDC_BTN_SETTINGS_SAVE,191,157,50,14 - PUSHBUTTON "Close",IDC_BTN_SETTINGS_CLOSE,243,157,50,14 -END - - ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO @@ -42,23 +27,78 @@ END #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO BEGIN - IDD_SETTINGS_DIALOG, DIALOG + IDD_PROPPAGE_DISPLAY, DIALOG BEGIN LEFTMARGIN, 7 - RIGHTMARGIN, 302 + RIGHTMARGIN, 228 + TOPMARGIN, 7 + BOTTOMMARGIN, 149 + END + + IDD_PROPPAGE_SOUND, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 203 + TOPMARGIN, 7 + BOTTOMMARGIN, 147 + END + + IDD_PROPPAGE_MISC, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 203 TOPMARGIN, 7 - BOTTOMMARGIN, 182 + BOTTOMMARGIN, 147 END END #endif // APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PROPPAGE_DISPLAY DIALOGEX 0, 0, 235, 156 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Display" +FONT 8, "MS Shell Dlg", 400, 0, 0x0 +BEGIN + PUSHBUTTON "Button1",IDC_BUTTON1,60,87,50,14 +END + +IDD_PROPPAGE_SOUND DIALOGEX 0, 0, 210, 154 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Sound" +FONT 8, "MS Shell Dlg", 400, 0, 0x0 +BEGIN +END + +IDD_PROPPAGE_MISC DIALOGEX 0, 0, 210, 154 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION +CAPTION "Miscellaneous" +FONT 8, "MS Shell Dlg", 400, 0, 0x0 +BEGIN + LTEXT "TODO: layout property page",IDC_STATIC,60,73,90,8 +END + + ///////////////////////////////////////////////////////////////////////////// // // AFX_DIALOG_LAYOUT // -IDD_SETTINGS_DIALOG AFX_DIALOG_LAYOUT +IDD_PROPPAGE_DISPLAY AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROPPAGE_SOUND AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_PROPPAGE_MISC AFX_DIALOG_LAYOUT BEGIN 0 END @@ -74,6 +114,25 @@ END LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK #pragma code_page(1252) +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG_SETTINGS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 302 + TOPMARGIN, 7 + BOTTOMMARGIN, 169 + END +END +#endif // APSTUDIO_INVOKED + + ///////////////////////////////////////////////////////////////////////////// // // RCDATA @@ -231,6 +290,32 @@ END IDI_ICON2 ICON "SpectREM\\Win32\\SpectREM.ico" +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG_SETTINGS DIALOGEX 0, 0, 309, 176 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "SpectREM Settings" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,198,155,50,14 + PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_DIALOG_SETTINGS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + ///////////////////////////////////////////////////////////////////////////// // // String Table diff --git a/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp b/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp index 8cd4cd5..79c5cd3 100755 --- a/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp +++ b/SpectREM/SpectREM/Emulation Core/ZX_Spectrum_Core/Snapshot.cpp @@ -567,8 +567,9 @@ void ZXSpectrum::snapshotExtractMemoryBlock(const char *buffer, size_t bufferSiz } else { - while (memoryPtr < unpackedLength + memAddr - 1) + while (memoryPtr < unpackedLength + memAddr) { + std::string n = std::string("memoryPtr: ") + std::to_string(memoryPtr) + std::string(" unpackedLength: ") + std::to_string(unpackedLength) + std::string(" memAddr: ") + std::to_string(memAddr) + std::string(" filePtr+1: ") + std::to_string(filePtr + 1) + " - " + std::to_string(fileBytes.size()) + "\n"; uint8_t byte1 = fileBytes[filePtr]; uint8_t byte2 = fileBytes[filePtr + 1]; diff --git a/SpectREM/resource.h b/SpectREM/resource.h index bae33d7..838a89e 100644 --- a/SpectREM/resource.h +++ b/SpectREM/resource.h @@ -3,20 +3,23 @@ // Used by SpectREM.rc // #define IDR_MENU1 101 -#define IDD_SETTINGS_DIALOG 101 #define IDR_ACCELERATOR1 102 #define IDR_MENUACCELERATORS 102 #define IDI_ICON1 105 +#define IDD_PROPPAGE_SOUND 106 +#define IDD_PROPPAGE_DISPLAY 107 #define IDI_ICON2 108 #define IDS_FIRSTCOLUMN 111 #define IDS_BLOCKTYPE 112 #define IDS_FILENAME 113 +#define IDD_DIALOG_SETTINGS 113 #define IDS_AUTOSTARTLINE 114 +#define IDD_PROPPAGE_MEDIUM 114 +#define IDD_PROPPAGE_MISC 114 #define IDS_ADDRESS 115 #define IDS_LENGTH 116 #define ID_TEXTFILE 256 -#define IDC_BTN_SETTINGS_SAVE 1001 -#define IDC_BTN_SETTINGS_CLOSE 1002 +#define IDC_BUTTON1 1004 #define ID_FILE_OPENSNAPSHOT 40001 #define ID_FILE_EXIT 40002 #define ID_EMULATION_FULLSPEED 40003 @@ -64,9 +67,9 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 112 +#define _APS_NEXT_RESOURCE_VALUE 119 #define _APS_NEXT_COMMAND_VALUE 40075 -#define _APS_NEXT_CONTROL_VALUE 1002 +#define _APS_NEXT_CONTROL_VALUE 1005 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif From 8cdc2c4de813be58b5c127bd454ed4437316333e Mon Sep 17 00:00:00 2001 From: John Young Date: Mon, 27 Jan 2020 18:18:50 +0000 Subject: [PATCH 16/23] UPDATED: Some changes that Mike made which needed some method signatures changing. --- SpectREM/SpectREM.aps | Bin 189616 -> 189616 bytes .../SpectREM/Emulation Core/Tape/Tape.hpp | 1 - .../ZX_Spectrum_128k/ZXSpectrum128.cpp | 2 -- SpectREM/SpectREM/Win32/CSettingsDialog.cpp | 16 ++++++++++++++++ SpectREM/SpectREM/Win32/CSettingsDialog.h | 17 +++++++++++++++++ SpectREM/SpectREM/Win32/WinMain.cpp | 4 ++-- 6 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 SpectREM/SpectREM/Win32/CSettingsDialog.cpp create mode 100644 SpectREM/SpectREM/Win32/CSettingsDialog.h diff --git a/SpectREM/SpectREM.aps b/SpectREM/SpectREM.aps index 2606ea3b9174690f5b966927b6ec9d37021c505f..5fd8a994101d3ea7767ee7c1e9b9ed9defdbabbe 100644 GIT binary patch delta 22 ecmdmRl6%8R?uHh|ElhlOnerI63*2Q|zzzUwn+R+G delta 22 ecmdmRl6%8R?uHh|ElhlOnF3emuLoadTrapTriggered = true; - machine->tapePlayer->updateStatus(); return true; } } @@ -369,7 +368,6 @@ bool ZXSpectrum128::opcodeCallback(uint8_t opcode, uint16_t address, void *param if (opcode == 0x08) { machine->emuSaveTrapTriggered = true; - machine->tapePlayer->updateStatus(); return true; } } diff --git a/SpectREM/SpectREM/Win32/CSettingsDialog.cpp b/SpectREM/SpectREM/Win32/CSettingsDialog.cpp new file mode 100644 index 0000000..d172503 --- /dev/null +++ b/SpectREM/SpectREM/Win32/CSettingsDialog.cpp @@ -0,0 +1,16 @@ +#include "CSettingsDialog.h" + + +CSettingsDialog::CSettingsDialog() +{ + +} + +CSettingsDialog::~CSettingsDialog() +{ + +} + + + + diff --git a/SpectREM/SpectREM/Win32/CSettingsDialog.h b/SpectREM/SpectREM/Win32/CSettingsDialog.h new file mode 100644 index 0000000..7cbd7ad --- /dev/null +++ b/SpectREM/SpectREM/Win32/CSettingsDialog.h @@ -0,0 +1,17 @@ +#ifndef CSettingsDialog_hpp +#define CSettingsDialog_hpp + + +#pragma once +class CSettingsDialog +{ +public: + CSettingsDialog(); + ~CSettingsDialog(); + + + +}; + + +#endif /* CSettingsDialog_hpp */ diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index c7b2889..5615ec8 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -52,7 +52,7 @@ // WinMain.cpp static void audio_callback(uint32_t nNumSamples, uint8_t* pBuffer); -static void tapeStatusCallback(int blockIndex, int bytes); +static void tapeStatusCallback(int blockIndex, int bytes, int action); static void LoadSnapshot(); static void HardReset(); static void SoftReset(); @@ -928,7 +928,7 @@ static void LoadSnapshot() //----------------------------------------------------------------------------------------- -static void tapeStatusCallback(int blockIndex, int bytes) +static void tapeStatusCallback(int blockIndex, int bytes, int action) { if (blockIndex < 1 && m_pTape->playing == false) return; PostMessage(tvHwnd, WM_USER + 2, PM_TAPE_ACTIVEBLOCK, (LPARAM)blockIndex); From 706c6ba8cff9c0c09b3eb9bb5a9cd3a4ceca9824 Mon Sep 17 00:00:00 2001 From: John Young Date: Fri, 31 Jan 2020 13:18:23 +0000 Subject: [PATCH 17/23] Cleanup --- SpectREM/ClassDiagram.cd | 586 +++++++++++++++++++++++++++++ SpectREM/ClassDiagram.png | Bin 0 -> 180726 bytes SpectREM/SpectREM.aps | Bin 189616 -> 188012 bytes SpectREM/SpectREM.vcxproj | 5 + SpectREM/SpectREM.vcxproj.filters | 13 + SpectREM/SpectREM/Win32/PMDawn.cpp | 4 + 6 files changed, 608 insertions(+) create mode 100644 SpectREM/ClassDiagram.cd create mode 100644 SpectREM/ClassDiagram.png diff --git a/SpectREM/ClassDiagram.cd b/SpectREM/ClassDiagram.cd new file mode 100644 index 0000000..357ac6d --- /dev/null +++ b/SpectREM/ClassDiagram.cd @@ -0,0 +1,586 @@ + + + + + + AAAQAAIAAAAAQAGQACAAAAAAAAAAAQAAAQAAAAAAAAA= + SpectREM\OSX\AudioQueue.hpp + + + + + + AAAAgABAACAAQAIAIAAAAAEAAEEAAAEgIAASJBEAAAA= + SpectREM\Win32\AudioCore.hpp + + + + + + + + + + 4BJQmIUvACCTAwg0YdQU4YSgEkIAiBKaABhuA3kI0Zg= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAACAQAAAAAgSAgAAABAAAACAAACAAACA= + SpectREM\Win32\TapeViewerWindow.hpp + + + + + + ASQAICAAAAAQAIACQAAIBAAAIAAAEIIAAiAAACAAAEg= + SpectREM\Emulation Core\Debugger\Debug.hpp + + + + + + CCAQAAAAAAIAACAAAACgAAIAAAAAIAAAAABAAAAAjAA= + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + + CAAQAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAgAAAAAAAA= + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + + AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + + AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + + CAAQAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + + CAAQAAAAAAIAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAA= + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + + + + + + + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + FBBIIBCgACYkJEABAQgAACQAEgTgCAAEBCGAAUCgZgA= + SpectREM\Emulation Core\Tape\Tape.hpp + + + + + + JPgCshACMFCTN4SADiRT5IJAxSgggEQwAJiAQ8uMICE= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + ECgAAAACAAAQAAAEABEQQAAgAAAAAAAAABACAAAAAAA= + SpectREM\Emulation Core\ZX_Spectrum_48k\ZXSpectrum48.hpp + + + + + + ECgAAAAKBAAQAAAEAAEQQAAgAAAAAAAAEAACAAAAAAA= + SpectREM\Emulation Core\ZX_Spectrum_128k\ZXSpectrum128.hpp + + + + + + HCveBSk3Wy5esSLXtJkzwCagyJIkAxBU0SLHQDHLLik= + SpectREM\Emulation Core\ZX_Spectrum_Core\ZXSpectrum.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAQABAAAAAAA= + SpectREM\Win32\OpenGLView.cpp + + + + + + AAAgBAAwBEAAACAAAJAgAAACACAkAAoAEIAhAIIBAQA= + SpectREM\Emulation Core\ZX_Spectrum_Core\MachineInfo.h + + + + + + AAQAAABAAAAAAAAAAECAAAAAAAIAAAAAIAAAAAAAAAA= + SpectREM\Win32\PMDawn.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.cpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\AudioCore.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\OpenGLView.hpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\Z80_Core\Z80Core.h + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + SpectREM\Emulation Core\ZX_Spectrum_Core\MachineInfo.h + + + + + + QAAAAAAAABAAAQAAAAAAAAAAAAAAAQAAAAAAAAAACAA= + SpectREM\Win32\WinMain.cpp + + + + + + AAABAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAA= + SpectREM\Win32\WinMain.cpp + + + + + + AAAAAAAAAAAAAIAAAgAAAAAAAIAAAAAIAAAAAAAAAAA= + SpectREM\Emulation Core\ZX_Spectrum_Core\Audio.cpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAgAAAAAAAAgA= + SpectREM\Emulation Core\ZX_Spectrum_Core\Display.cpp + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAIICA= + SpectREM\Emulation Core\ZX_Spectrum_Core\MachineInfo.h + + + + + + AAAAACgAAAAAAAAAAAAAAEAQAAAAAAAAAAAAAAAAAAA= + SpectREM\Win32\PMDawn.hpp + + + + \ No newline at end of file diff --git a/SpectREM/ClassDiagram.png b/SpectREM/ClassDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..87e096087106714287af9ad1166479a75d06a44a GIT binary patch literal 180726 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

V#xWk#%L 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≶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 literal 0 HcmV?d00001 diff --git a/SpectREM/SpectREM.aps b/SpectREM/SpectREM.aps index 5fd8a994101d3ea7767ee7c1e9b9ed9defdbabbe..d588f183a6bf0250323f073258cbc4ac7b29e89f 100644 GIT binary patch delta 2501 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 b{bCW2DM;L*rm^N;$>xN#>D*cds*~;i%6GKv diff --git a/SpectREM/SpectREM.vcxproj b/SpectREM/SpectREM.vcxproj index 0ecd5ca..2ee829d 100644 --- a/SpectREM/SpectREM.vcxproj +++ b/SpectREM/SpectREM.vcxproj @@ -189,7 +189,9 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* + + @@ -217,7 +219,9 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* + + @@ -228,6 +232,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* + false diff --git a/SpectREM/SpectREM.vcxproj.filters b/SpectREM/SpectREM.vcxproj.filters index 2998a76..998a14a 100644 --- a/SpectREM/SpectREM.vcxproj.filters +++ b/SpectREM/SpectREM.vcxproj.filters @@ -99,6 +99,12 @@ Win32 + + Emulation Core + + + Emulation Core + @@ -159,6 +165,12 @@ Win32 + + Emulation Core + + + Emulation Core + @@ -169,6 +181,7 @@ + diff --git a/SpectREM/SpectREM/Win32/PMDawn.cpp b/SpectREM/SpectREM/Win32/PMDawn.cpp index bac94b0..35482e1 100644 --- a/SpectREM/SpectREM/Win32/PMDawn.cpp +++ b/SpectREM/SpectREM/Win32/PMDawn.cpp @@ -40,6 +40,10 @@ namespace PMDawn localtime_s(&timeinfo, &rawtime); strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo); std::string str(buffer); + std::string str(buffer); + + std::string str(buffer); + return str; } From b0cd3bfc523a74a631f39b9197f3193d2b596b5f Mon Sep 17 00:00:00 2001 From: John Young Date: Fri, 31 Jan 2020 17:27:56 +0000 Subject: [PATCH 18/23] +2/2A changes, not quite perfect. --- CppProperties.json | 21 +++++++ SpectREM/SpectREM.aps | Bin 188012 -> 188188 bytes SpectREM/SpectREM.rc | 59 ++++++++++-------- SpectREM/SpectREM.vcxproj.filters | 16 ++--- .../SpectREM/Emulation Core/ROMS/plus3-0.rom | Bin 0 -> 16384 bytes .../SpectREM/Emulation Core/ROMS/plus3-1.rom | Bin 0 -> 16384 bytes .../SpectREM/Emulation Core/ROMS/plus3-2.rom | Bin 0 -> 16384 bytes .../SpectREM/Emulation Core/ROMS/plus3-3.rom | Bin 0 -> 16384 bytes SpectREM/SpectREM/Win32/PMDawn.cpp | 4 -- SpectREM/SpectREM/Win32/WinMain.cpp | 43 +++++++++++-- SpectREM/resource.h | 5 +- 11 files changed, 99 insertions(+), 49 deletions(-) create mode 100644 CppProperties.json create mode 100644 SpectREM/SpectREM/Emulation Core/ROMS/plus3-0.rom create mode 100644 SpectREM/SpectREM/Emulation Core/ROMS/plus3-1.rom create mode 100644 SpectREM/SpectREM/Emulation Core/ROMS/plus3-2.rom create mode 100644 SpectREM/SpectREM/Emulation Core/ROMS/plus3-3.rom diff --git a/CppProperties.json b/CppProperties.json new file mode 100644 index 0000000..659bf4e --- /dev/null +++ b/CppProperties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "inheritEnvironments": [ + "msvc_x86" + ], + "name": "x86-Debug", + "includePath": [ + "${env.INCLUDE}", + "${workspaceRoot}\\**" + ], + "defines": [ + "WIN32", + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "intelliSenseMode": "windows-msvc-x86" + } + ] +} \ No newline at end of file diff --git a/SpectREM/SpectREM.aps b/SpectREM/SpectREM.aps index d588f183a6bf0250323f073258cbc4ac7b29e89f..6774f5985ae0bb6e5cf84e022586670807f213c2 100644 GIT binary patch delta 850 zcmZXST}YEr7{{OU%x&31A~1B$sWD|vk$37`3aM+GINOJ9Q!AoZB7{P)VplAIr$bpAXO}22Mz%BE#PGqqro{m&4%cg zB2VemdlcC)O)Bz?jPrP#&B_z=xX#M?#r4N_-dJ=8v%>&ANTGyzj7CzJFCYDg3oNsq znP6k12JF(cib`r)Kerxx)C)4brX>s8aGDb!Q%DrR7#hWv4?(_F{~IHRHDCp6;E zJo=_+%1FDHxt=^g7Dc_qbY&S0)U}NJF`7;)ad8t$&rSO@BN+g{i>0JVe|LqiJ!6qh zl#|n8eF4c|c>5JlO)zUg= zs+<_3;rlF?3YC3#KBAIUG#0ty$Pb&vdw2Tl3VSnmr2{^c^Zu0*t5~3_o<{l{$6T$T f1(@MU&opL}VGZ}7;SE{V(5T7M0Yu)BpT~ay6R`kz delta 551 zcmWmBUr19?90%~<`QGg=Q-p#=yVHp_AH0f5NVyxEo&_iz#f=>m5CP*)pM1QCU zGvls)mA(Z@5P>bb1R~`o#4aV9!?bOe9`?|K!Ge$;qIK@mkMHMv&*2AWZn<@PsdXl* zlsCcSYZzI$Gw;eq)hAsdovrn5DSK{EVlGB0xeK9id%xgM zIaEdZqyBhWjj?jxd!AWv5j#s`Nw*sfx)nsyP@gS_KZ zC;E)ny2hazTuAC1sYxpj8toyC(~ey`!dD!N#*N7{e)mbDw#)p^k#h^i^c#&EPV29m zccdx#XM&rv!p9+7YTI*;*isuG!TmY-Ja6SfnHBKW3Obd0Iq+-+!=3@DuVh%06`qk7 ll|M51^YFRrD$x`#+Bl$OEO66;mm4;Asm?!af98Mk`Tyi$zpelP diff --git a/SpectREM/SpectREM.rc b/SpectREM/SpectREM.rc index 6de945d..014c939 100644 --- a/SpectREM/SpectREM.rc +++ b/SpectREM/SpectREM.rc @@ -133,6 +133,32 @@ END #endif // APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG_SETTINGS DIALOGEX 0, 0, 309, 176 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "SpectREM Settings" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,198,155,50,14 + PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_DIALOG_SETTINGS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + ///////////////////////////////////////////////////////////////////////////// // // RCDATA @@ -232,7 +258,9 @@ BEGIN BEGIN MENUITEM "to 48K\tShift+F5", ID_SWITCH_TO48K MENUITEM "to 128K\tCtrl+F5", ID_SWITCH_TO128K - MENUITEM "Flip\tF5", ID_SWITCH_FLIP + MENUITEM "to +2\tAlt+F5", ID_SWITCH_TOPLUS2 + MENUITEM "to +2A\tShift+Ctrl+F5", ID_SWITCH_TOPLUS2A + MENUITEM "to +3\tCtrl+Alt+F5", ID_SWITCH_TOPLUS3 END END POPUP "&Settings" @@ -265,6 +293,9 @@ BEGIN VK_F5, ID_SWITCH_FLIP, VIRTKEY, NOINVERT VK_F5, ID_SWITCH_TO128K, VIRTKEY, CONTROL, NOINVERT VK_F5, ID_SWITCH_TO48K, VIRTKEY, SHIFT, NOINVERT + VK_F5, ID_SWITCH_TOPLUS2, VIRTKEY, ALT + VK_F5, ID_SWITCH_TOPLUS2A, VIRTKEY, SHIFT, CONTROL + VK_F5, ID_SWITCH_TOPLUS3, VIRTKEY, CONTROL, ALT VK_F1, ID_TAPE_EJECTTAPE, VIRTKEY, ALT, NOINVERT VK_F1, ID_TAPE_INSERTTAPE, VIRTKEY, SHIFT, NOINVERT VK_F9, ID_TAPE_REWINDTAPE, VIRTKEY, SHIFT, NOINVERT @@ -290,32 +321,6 @@ END IDI_ICON2 ICON "SpectREM\\Win32\\SpectREM.ico" -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_DIALOG_SETTINGS DIALOGEX 0, 0, 309, 176 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "SpectREM Settings" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,198,155,50,14 - PUSHBUTTON "Cancel",IDCANCEL,252,155,50,14 -END - - -///////////////////////////////////////////////////////////////////////////// -// -// AFX_DIALOG_LAYOUT -// - -IDD_DIALOG_SETTINGS AFX_DIALOG_LAYOUT -BEGIN - 0 -END - - ///////////////////////////////////////////////////////////////////////////// // // String Table diff --git a/SpectREM/SpectREM.vcxproj.filters b/SpectREM/SpectREM.vcxproj.filters index 998a14a..db4e6b0 100644 --- a/SpectREM/SpectREM.vcxproj.filters +++ b/SpectREM/SpectREM.vcxproj.filters @@ -99,12 +99,8 @@ Win32 - - Emulation Core - - - Emulation Core - + + @@ -165,12 +161,8 @@ Win32 - - Emulation Core - - - Emulation Core - + + diff --git a/SpectREM/SpectREM/Emulation Core/ROMS/plus3-0.rom b/SpectREM/SpectREM/Emulation Core/ROMS/plus3-0.rom new file mode 100644 index 0000000000000000000000000000000000000000..29e047c403ca34fa8e98e99a1438d74bfbedf244 GIT binary patch literal 16384 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 XXXX9hfCJ&%pQrx|fQ#%) literal 0 HcmV?d00001 diff --git a/SpectREM/SpectREM/Emulation Core/ROMS/plus3-1.rom b/SpectREM/SpectREM/Emulation Core/ROMS/plus3-1.rom new file mode 100644 index 0000000000000000000000000000000000000000..0f8113b26e7d0baf79635a5e60613245ef20c07c GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/SpectREM/SpectREM/Emulation Core/ROMS/plus3-2.rom b/SpectREM/SpectREM/Emulation Core/ROMS/plus3-2.rom new file mode 100644 index 0000000000000000000000000000000000000000..d4b02bfe8d5e7da20d7ee63ff143013c1ace145f GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/SpectREM/SpectREM/Emulation Core/ROMS/plus3-3.rom b/SpectREM/SpectREM/Emulation Core/ROMS/plus3-3.rom new file mode 100644 index 0000000000000000000000000000000000000000..e83937de5961d412171e901d33091bda6bf72847 GIT binary patch literal 16384 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 literal 0 HcmV?d00001 diff --git a/SpectREM/SpectREM/Win32/PMDawn.cpp b/SpectREM/SpectREM/Win32/PMDawn.cpp index 35482e1..bac94b0 100644 --- a/SpectREM/SpectREM/Win32/PMDawn.cpp +++ b/SpectREM/SpectREM/Win32/PMDawn.cpp @@ -40,10 +40,6 @@ namespace PMDawn localtime_s(&timeinfo, &rawtime); strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo); std::string str(buffer); - std::string str(buffer); - - std::string str(buffer); - return str; } diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 5615ec8..f00fc58 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -38,6 +38,8 @@ #include "..\Emulation Core\ZX_Spectrum_Core\ZXSpectrum.hpp" #include "..\Emulation Core\ZX_Spectrum_48k\ZXSpectrum48.hpp" #include "..\Emulation Core\ZX_Spectrum_128k\ZXSpectrum128.hpp" +#include "..\Emulation Core\ZX_Spectrum_128k_2\ZXSpectrum128_2.hpp" +#include "..\Emulation Core\ZXSpectrum_128k_2A\ZXSpectrum128_2A.hpp" #include "..\Emulation Core\Tape\Tape.hpp" #include "..\OSX\AudioQueue.hpp" #include "OpenGLView.hpp" @@ -91,9 +93,18 @@ std::string loadedFile; enum MachineType { - ZX48, ZX128, PLUS2, PLUS3, UNKNOWN + ZX48, ZX128, PLUS2, PLUS2A, PLUS3, UNKNOWN } mType; +//enum +//{ +// eZXSpectrum48 = 0, +// eZXSpectrum128 = 1, +// eZXSpectrum128_2 = 2, +// eZXSpectrum128_2A = 3, +// eZXSpectrum128_3 = 4 +//}; + enum SnapType { SNA, Z80 @@ -244,8 +255,14 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpara case ID_SWITCH_TO128K: ResetMachineForSnapshot(ZX128, true); break; - case ID_SWITCH_FLIP: - SwitchMachines(); + case ID_SWITCH_TOPLUS2: + ResetMachineForSnapshot(PLUS2, true); + break; + case ID_SWITCH_TOPLUS2A: + ResetMachineForSnapshot(PLUS2A, true); + break; + case ID_SWITCH_TOPLUS3: + ResetMachineForSnapshot(PLUS3, true); break; case ID_HELP_ABOUT: ShowHelpAbout(); @@ -1006,7 +1023,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) unsigned int cThreads = std::thread::hardware_concurrency(); PMDawn::Log(PMDawn::LOG_INFO, "Maximum available threads = " + std::to_string(cThreads)); - SetupThreadLocalStorageForTapeData(); + //SetupThreadLocalStorageForTapeData(); loadedFile = "-empty-"; slideshowTimerRunning = false; @@ -1078,7 +1095,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) m_pAudioCore = new AudioCore(); m_pAudioCore->Init(44100, 50, audio_callback); m_pTape = new Tape(tapeStatusCallback); - m_pMachine = new ZXSpectrum128(m_pTape); + m_pMachine = new ZXSpectrum128_2A(m_pTape);// ZXSpectrum128(m_pTape); m_pMachine->emuUseAYSound = true; m_pMachine->emuBasePath = PMDawn::GetApplicationBasePath(); PMDawn::Log(PMDawn::LOG_INFO, "ROMs path = " + m_pMachine->emuBasePath + romPath); @@ -1236,6 +1253,22 @@ static void ResetMachineForSnapshot(uint8_t mc, bool ayEnabled) m_pMachine = new ZXSpectrum128(m_pTape); m_pMachine->emuUseAYSound = true; break; + case PLUS2: + PMDawn::Log(PMDawn::LOG_INFO, "SpectREM changed to 128K +2 Mode"); + m_pMachine = new ZXSpectrum128_2(m_pTape); + m_pMachine->emuUseAYSound = true; + break; + case PLUS2A: + PMDawn::Log(PMDawn::LOG_INFO, "SpectREM changed to 128K +2A Mode"); + m_pMachine = new ZXSpectrum128_2A(m_pTape); + m_pMachine->emuUseAYSound = true; + break; + case PLUS3: + PMDawn::Log(PMDawn::LOG_INFO, "SpectREM changed to 128K +3 Mode"); + m_pMachine = new ZXSpectrum128_2A(m_pTape); + //m_pMachine = new ZXSpectrum128_3(m_pTape); + m_pMachine->emuUseAYSound = true; + break; default: // default to 128K PMDawn::Log(PMDawn::LOG_INFO, "UNKNOWN MACHINE TYPE, Defaulting to 128K Mode"); diff --git a/SpectREM/resource.h b/SpectREM/resource.h index 838a89e..8879117 100644 --- a/SpectREM/resource.h +++ b/SpectREM/resource.h @@ -58,6 +58,9 @@ #define ID_TAPE_REWINGTAPE 40068 #define ID_TAPE_REWINDTAPE 40069 #define ID_TAPE_TAPEVIEWER 40072 +#define ID_SWITCH_TOPLUS2 40075 +#define ID_SWITCH_TOPLUS2A 40076 +#define ID_SWITCH_TOPLUS3 40077 #define ID_SHADER_DISPLAY_FRAG 57000 #define ID_SHADER_DISPLAY_VERT 57001 #define ID_SHADER_CLUT_FRAG 57002 @@ -68,7 +71,7 @@ #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 119 -#define _APS_NEXT_COMMAND_VALUE 40075 +#define _APS_NEXT_COMMAND_VALUE 40078 #define _APS_NEXT_CONTROL_VALUE 1005 #define _APS_NEXT_SYMED_VALUE 101 #endif From 723186959eb60e23d2d27e5d453ef3842e8f3fb0 Mon Sep 17 00:00:00 2001 From: John Young Date: Wed, 22 Apr 2020 20:56:03 +0100 Subject: [PATCH 19/23] Had to add CoInitialize to get XAudio2 working again, I believe a change to the newest version (2.9) probably. Never need the init for the COM object previously... --- SpectREM/SpectREM.vcxproj | 8 ++++---- SpectREM/SpectREM/Win32/WinMain.cpp | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/SpectREM/SpectREM.vcxproj b/SpectREM/SpectREM.vcxproj index 2ee829d..09359e4 100644 --- a/SpectREM/SpectREM.vcxproj +++ b/SpectREM/SpectREM.vcxproj @@ -102,7 +102,7 @@ if not exist $(TargetDir)\ROMS mkdir $(TargetDir)\ROMS copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* - Copies the shaders and speccy roms to the output folder + Copying the speccy roms to the output folder @@ -124,7 +124,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* if not exist $(TargetDir)\ROMS mkdir $(TargetDir)\ROMS copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* - Copies the shaders and speccy roms to the output folder + Copying the speccy roms to the output folder @@ -150,7 +150,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* if not exist $(TargetDir)\ROMS mkdir $(TargetDir)\ROMS copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* - Copies the shaders and speccy roms to the output folder + Copying the speccy roms to the output folder @@ -176,7 +176,7 @@ copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* if not exist $(TargetDir)\ROMS mkdir $(TargetDir)\ROMS copy "$(ProjectDir)SpectREM\Emulation Core\ROMS\*.*" $(TargetDir)ROMS\*.* - Copies the shaders and speccy roms to the output folder + Copying the speccy roms to the output folder diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index f00fc58..2c8fde0 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -989,6 +989,7 @@ static void audio_callback(uint32_t nNumSamples, uint8_t* pBuffer) int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) { + CoInitializeEx(NULL, COINIT_MULTITHREADED); // Check for logging type if needed, CTRL = LOG_INFO, ALT = LOG_DEBUG PMDawn::logLevel = PMDawn::LOG_NONE; if (GetAsyncKeyState(VK_MENU)) @@ -1001,6 +1002,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) // CTRL is pressed PMDawn::logLevel = PMDawn::LOG_INFO; } + PMDawn::logLevel = PMDawn::LOG_INFO; if (PMDawn::logLevel != PMDawn::LOG_NONE) { if (PMDawn::LogOpenOrCreate(PMDawn::GetApplicationBasePath() + "\\" + PMDawn::logFilename)) @@ -1214,6 +1216,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) WaitForSingleObject(tapeViewerThread, INFINITE); CloseHandle(tapeViewerThread); } + CoUninitialize(); return 0; } From cbb7a812c904a196e96a38c799ea39cc21e077cb Mon Sep 17 00:00:00 2001 From: John Young Date: Wed, 22 Apr 2020 21:24:43 +0100 Subject: [PATCH 20/23] ADDED: Some error checking for AudioCore::Init --- SpectREM/SpectREM/Win32/WinMain.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 2c8fde0..2db0f92 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -1095,7 +1095,13 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) m_pOpenGLView->Init(mainWindow, 256 * zoomLevel, 192 * zoomLevel, ID_SHADER_CLUT_VERT, ID_SHADER_CLUT_FRAG, ID_SHADER_DISPLAY_VERT, ID_SHADER_DISPLAY_FRAG, RT_RCDATA); m_pAudioQueue = new AudioQueue(); m_pAudioCore = new AudioCore(); - m_pAudioCore->Init(44100, 50, audio_callback); + bool rV = m_pAudioCore->Init(44100, 50, audio_callback); + if (!rV) + { + // AudioCore::Init failed + CoUninitialize(); + return 0; + } m_pTape = new Tape(tapeStatusCallback); m_pMachine = new ZXSpectrum128_2A(m_pTape);// ZXSpectrum128(m_pTape); m_pMachine->emuUseAYSound = true; @@ -1104,6 +1110,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) m_pMachine->initialise(romPath); m_pAudioCore->Start(); m_pMachine->resume(); + m_pMachine->resetMachine(true); // Do the main message loop while (!exit_emulator) From 9ed541f4f51233a8ad1e7abcfeb0bcfa012169fc Mon Sep 17 00:00:00 2001 From: John Young Date: Thu, 23 Apr 2020 18:04:10 +0100 Subject: [PATCH 21/23] EOD: Turning the menu on/off works again, still no status bar arghh --- SpectREM/SpectREM/Win32/OpenGLView.cpp | 49 +++++++++++++++++++++++--- SpectREM/SpectREM/Win32/OpenGLView.hpp | 7 +++- SpectREM/SpectREM/Win32/WinMain.cpp | 13 ++++--- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/SpectREM/SpectREM/Win32/OpenGLView.cpp b/SpectREM/SpectREM/Win32/OpenGLView.cpp index 524c2cb..4a435a7 100644 --- a/SpectREM/SpectREM/Win32/OpenGLView.cpp +++ b/SpectREM/SpectREM/Win32/OpenGLView.cpp @@ -144,6 +144,17 @@ void OpenGLView::Resize(int width, int height) //----------------------------------------------------------------------------------------- +void OpenGLView::Resize(int x, int y, int width, int height) +{ + GL_CHECK(glViewport(x, y, width, height)); + _viewTop = y; + _viewLeft = x; + _viewWidth = width; + _viewHeight = height; +} + +//----------------------------------------------------------------------------------------- + bool OpenGLView::Init(HWND hWnd, int width, int height, uint16_t idClutVert, uint16_t idClutFrag, uint16_t idDisplayVert, uint16_t idDisplayFrag, LPWSTR idType) { unsigned int formatCount; @@ -203,7 +214,7 @@ bool OpenGLView::Init(HWND hWnd, int width, int height, uint16_t idClutVert, uin return false; } - glClearColor(1.0f, 0.0f, 0.4f, 1.0f); + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); LoadShaders(idClutVert, idClutFrag, idDisplayVert, idDisplayFrag, idType); SetupTexture(); @@ -359,7 +370,7 @@ void OpenGLView::SetupTexture() void OpenGLView::UpdateTextureData(unsigned char *pData, GLint vX, GLint vY) { - glClearColor(1.0f, 1.0f, 0.0f, 0.5f); + glClearColor(1.0f, 0.0f, 0.0f, 0.5f); // this changes the main colour behind the texture... glClear(GL_COLOR_BUFFER_BIT); // Render the output to a texture which has the default dimensions of the output image @@ -515,7 +526,9 @@ void OpenGLView::paintGL() { // Render the texture to the actual screen, this time using the size of the screen as the viewport GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0)); - GL_CHECK(glViewport(0, 0, static_cast(_viewWidth), static_cast(_viewHeight))); + GL_CHECK(glViewport( + static_cast(_viewLeft), static_cast(_viewTop), + static_cast(_viewWidth), static_cast(_viewHeight))); GL_CHECK(glUseProgram(_displayShaderProg)); GL_CHECK(glActiveTexture(GL_TEXTURE0)); GL_CHECK(glBindTexture(GL_TEXTURE_2D, _clutOutputTexture)); @@ -528,7 +541,7 @@ void OpenGLView::paintGL() GL_CHECK(glProgramUniform1f(_displayShaderProg, u_brightness, 1.0f)); GL_CHECK(glProgramUniform1f(_displayShaderProg, u_scanlineSize, 960)); GL_CHECK(glProgramUniform1f(_displayShaderProg, u_scanlines, 0)); - GL_CHECK(glProgramUniform1f(_displayShaderProg, u_screenCurve, 0.3f)); + GL_CHECK(glProgramUniform1f(_displayShaderProg, u_screenCurve, 0.0));// 0.3f)); GL_CHECK(glProgramUniform1f(_displayShaderProg, u_pixelFilterValue, 0.15f)); GL_CHECK(glProgramUniform1f(_displayShaderProg, u_rgbOffset, 0)); GL_CHECK(glProgramUniform1i(_displayShaderProg, u_showVignette, true)); @@ -609,3 +622,31 @@ void OpenGLView::CheckOpenGLError(const char* stmt, const char* fname, int line) //----------------------------------------------------------------------------------------- +void OpenGLView::ShaderSetScreenCurve(GLint curve) +{ + //u_screenCurve = curve; + //GL_CHECK(glProgramUniform1f(_displayShaderProg, u_screenCurve, curve)); +} + +//----------------------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/SpectREM/SpectREM/Win32/OpenGLView.hpp b/SpectREM/SpectREM/Win32/OpenGLView.hpp index aa4618d..244b394 100644 --- a/SpectREM/SpectREM/Win32/OpenGLView.hpp +++ b/SpectREM/SpectREM/Win32/OpenGLView.hpp @@ -109,6 +109,8 @@ class OpenGLView void UpdateTextureData(unsigned char *pData, GLint vX, GLint vY); void OpenGLView::Resize(int width, int height); + void OpenGLView::Resize(int x, int y, int width, int height); + void OpenGLView::ShaderSetScreenCurve(GLint curve); private: bool InitialiseExtensions(); bool LoadExtensionList(); @@ -192,7 +194,9 @@ class OpenGLView */ GLuint _viewWidth = 320; - GLuint _viewHeight = 256; + GLuint _viewHeight = 256; + GLuint _viewTop = 0; + GLuint _viewLeft = 0; GLuint _vertexBuffer; GLuint _vertexArray; @@ -205,6 +209,7 @@ class OpenGLView GLuint _clutTexture; GLuint _clutOutputTexture; + // Display shader uniforms/samplers GLuint displayDepthBuffer; GLuint reflectionTexture; diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 2db0f92..ebc8059 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -724,7 +724,7 @@ static void ShowUI(HWND hWnd = mainWindow) RECT newSize = GetWindowResizeWithUI(mainWindow, statusWindow, mainMenu, true); SetMenu(hWnd, mainMenu); SetWindowPos(mainWindow, HWND_NOTOPMOST, newSize.left, newSize.top, newSize.right, newSize.bottom, 0); - m_pOpenGLView->Resize(256 * zoomLevel, 192 * zoomLevel); + m_pOpenGLView->Resize(0, 0, 256 * zoomLevel, 192 * zoomLevel); ShowWindow(statusWindow, SW_SHOW); } @@ -738,7 +738,7 @@ static void HideUI(HWND hWnd = mainWindow) RECT newSize = GetWindowResizeWithUI(mainWindow, statusWindow, mainMenu, false); SetMenu(hWnd, NULL); SetWindowPos(mainWindow, HWND_NOTOPMOST, newSize.left, newSize.top, newSize.right, newSize.bottom, 0); - m_pOpenGLView->Resize(256 * zoomLevel, 192 * zoomLevel); + m_pOpenGLView->Resize(0, 0, 256 * zoomLevel, 192 * zoomLevel); ShowWindow(statusWindow, SW_HIDE); } @@ -778,7 +778,7 @@ RECT GetWindowResizeWithUI(HWND mWin, HWND sWin, HMENU menu, bool visible) m.left, m.top, (m.right - m.left), - (m.bottom - m.top) + (s.bottom - s.top) + GetSystemMetrics(SM_CYMENU) + (m.bottom - m.top) + GetSystemMetrics(SM_CYMENU)// + (s.bottom - s.top)// + GetSystemMetrics(SM_CYMENU) }; Log(PMDawn::LOG_INFO, "Output RECT = t" + std::to_string(nWin.top) + " l" + @@ -793,7 +793,7 @@ RECT GetWindowResizeWithUI(HWND mWin, HWND sWin, HMENU menu, bool visible) m.left, m.top, (m.right - m.left), - (m.bottom - m.top) - (s.bottom - s.top) - GetSystemMetrics(SM_CYMENU) + (m.bottom - m.top) - GetSystemMetrics(SM_CYMENU)// - (s.bottom - s.top)// - GetSystemMetrics(SM_CYMENU) }; Log(PMDawn::LOG_INFO, "Output RECT = t" + std::to_string(nWin.top) + " l" + @@ -1092,6 +1092,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) QueryPerformanceCounter(&last_time); m_pOpenGLView = new OpenGLView(); + // * zoomLevel m_pOpenGLView->Init(mainWindow, 256 * zoomLevel, 192 * zoomLevel, ID_SHADER_CLUT_VERT, ID_SHADER_CLUT_FRAG, ID_SHADER_DISPLAY_VERT, ID_SHADER_DISPLAY_FRAG, RT_RCDATA); m_pAudioQueue = new AudioQueue(); m_pAudioCore = new AudioCore(); @@ -1103,7 +1104,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) return 0; } m_pTape = new Tape(tapeStatusCallback); - m_pMachine = new ZXSpectrum128_2A(m_pTape);// ZXSpectrum128(m_pTape); + m_pMachine = new ZXSpectrum48(m_pTape);// ZXSpectrum128(m_pTape); m_pMachine->emuUseAYSound = true; m_pMachine->emuBasePath = PMDawn::GetApplicationBasePath(); PMDawn::Log(PMDawn::LOG_INFO, "ROMs path = " + m_pMachine->emuBasePath + romPath); @@ -1112,6 +1113,8 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) m_pMachine->resume(); m_pMachine->resetMachine(true); + m_pOpenGLView->ShaderSetScreenCurve((GLint)0.0); + // Do the main message loop while (!exit_emulator) { From 9b5758e4ca029512d23a6bdcd671b3bca0c9fc30 Mon Sep 17 00:00:00 2001 From: John Young Date: Thu, 30 Apr 2020 20:34:05 +0100 Subject: [PATCH 22/23] UPDATE: Playing with reflections. --- SpectREM/SpectREM/Win32/OpenGLView.cpp | 102 +++++++++++++++++++++---- SpectREM/SpectREM/Win32/OpenGLView.hpp | 20 ++++- SpectREM/SpectREM/Win32/WinMain.cpp | 6 +- SpectREM/SpectREM/Win32/display.frag | 11 +-- 4 files changed, 115 insertions(+), 24 deletions(-) diff --git a/SpectREM/SpectREM/Win32/OpenGLView.cpp b/SpectREM/SpectREM/Win32/OpenGLView.cpp index 4a435a7..fa8cee1 100644 --- a/SpectREM/SpectREM/Win32/OpenGLView.cpp +++ b/SpectREM/SpectREM/Win32/OpenGLView.cpp @@ -13,6 +13,8 @@ #include #include "OpenGLView.hpp" #include +#include "PMDawn.hpp" + #ifdef _DEBUG #define GL_CHECK(stmt) do { \ @@ -29,11 +31,13 @@ static const GLint textureUnit0 = 0; static const GLint textureUnit1 = 1; +static const GLint textureUnit2 = 2; static const GLuint borderWidth = 32; static const GLuint screenWidth = borderWidth + 256 + borderWidth; static const GLuint screenHeight = borderWidth + 192 + borderWidth; +static char const * reflectionFilename = "bluesbro.png"; static char const * cS_DISPLAY_TEXTURE = "s_displayTexture"; static char const * cS_CLUT_TEXTURE = "s_clutTexture"; static char const * cS_REFLECTION_TEXTURE = "s_reflectionTexture"; @@ -111,9 +115,9 @@ const Color CLUT[] = { //----------------------------------------------------------------------------------------- -OpenGLView::OpenGLView() +OpenGLView::OpenGLView(std::string bpath) { - + appBasePath = bpath; } @@ -217,11 +221,54 @@ bool OpenGLView::Init(HWND hWnd, int width, int height, uint16_t idClutVert, uin glClearColor(1.0f, 0.0f, 0.0f, 1.0f); LoadShaders(idClutVert, idClutFrag, idDisplayVert, idDisplayFrag, idType); + LoadFileTextures(); SetupTexture(); SetupQuad(); return true; } +//----------------------------------------------------------------------------------------- + +void OpenGLView::LoadFileTextures() +{ + // bluesbro.png + //LoadBitmap(appBasePath + std::string("bluesbro.png"), _reflectionTexture); + LoadBitmap(L"C:\\Users\\polom\\source\\repos\\SpectREMCPP\\SpectREM\\x64\\Debug\\bluesbro.bmp", _reflectionTexture); +} + +bool OpenGLView::LoadBitmap(LPTSTR szFileName, GLuint& texid) // Creates Texture From A Bitmap File +{ + HBITMAP hBMP; // Handle Of The Bitmap + BITMAP BMP; // Bitmap Structure + + glGenTextures(1, &texid); // Create The Texture + hBMP = (HBITMAP)LoadImage(GetModuleHandle(NULL), szFileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE); + + if (!hBMP) // Does The Bitmap Exist? + return FALSE; // If Not Return False + + GetObject(hBMP, sizeof(BMP), &BMP); // Get The Object + // hBMP: Handle To Graphics Object + // sizeof(BMP): Size Of Buffer For Object Information + // &BMP: Buffer For Object Information + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // Pixel Storage Mode (Word Alignment / 4 Bytes) + + //// Typical Texture Generation Using Data From The Bitmap + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, _reflectionTexture); // Bind To The Texture ID + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear Min Filter + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear Mag Filter + //glTexImage2D(GL_TEXTURE_2D, 0, 3, BMP.bmWidth, BMP.bmHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits); + + DeleteObject(hBMP); // Delete The Object + //GL_CHECK(glUniform1i(s_reflectionTexture, textureUnit2)); + return TRUE; // Loading Was Successful + + //GL_CHECK(glActiveTexture(GL_TEXTURE1)); + //GL_CHECK(glBindTexture(GL_TEXTURE_2D, _clutTexture)); + //GL_CHECK(glUniform1i(s_clutTexture, textureUnit1)); +} //----------------------------------------------------------------------------------------- @@ -265,7 +312,8 @@ void OpenGLView::LoadShaders(uint16_t vertCLUT, uint16_t fragCLUT, uint16_t vert _clutShaderProg = prepareShaderProgram(vertCLUTR, fragCLUTR); GL_CHECK(s_displayTexture = glGetUniformLocation(_clutShaderProg, cS_DISPLAY_TEXTURE)); GL_CHECK(s_clutTexture = glGetUniformLocation(_clutShaderProg, cS_CLUT_TEXTURE)); - + //GL_CHECK(s_reflectionTexture = glGetUniformLocation(_displayShaderProg, cS_DISPLAY_TEXTURE)); + // Display Shader program std::string vertDisplayR; hRes = FindResource(0, MAKEINTRESOURCE(vertDISPLAY), MAKEINTRESOURCE(idtype)); @@ -386,6 +434,10 @@ void OpenGLView::UpdateTextureData(unsigned char *pData, GLint vX, GLint vY) GL_CHECK(glBindTexture(GL_TEXTURE_2D, _clutTexture)); GL_CHECK(glUniform1i(s_clutTexture, textureUnit1)); + //GL_CHECK(glActiveTexture(GL_TEXTURE2)); + //GL_CHECK(glBindTexture(GL_TEXTURE_2D, _reflectionTexture)); + //GL_CHECK(glUniform1i(s_clutTexture, textureUnit2)); + GL_CHECK(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); paintGL(); glFlush(); @@ -541,13 +593,13 @@ void OpenGLView::paintGL() GL_CHECK(glProgramUniform1f(_displayShaderProg, u_brightness, 1.0f)); GL_CHECK(glProgramUniform1f(_displayShaderProg, u_scanlineSize, 960)); GL_CHECK(glProgramUniform1f(_displayShaderProg, u_scanlines, 0)); - GL_CHECK(glProgramUniform1f(_displayShaderProg, u_screenCurve, 0.0));// 0.3f)); - GL_CHECK(glProgramUniform1f(_displayShaderProg, u_pixelFilterValue, 0.15f)); + GL_CHECK(glProgramUniform1f(_displayShaderProg, u_screenCurve, u_screenCurveValue));// 0.3f)); + GL_CHECK(glProgramUniform1f(_displayShaderProg, u_pixelFilterValue, 0.15));// 0.15f)); GL_CHECK(glProgramUniform1f(_displayShaderProg, u_rgbOffset, 0)); - GL_CHECK(glProgramUniform1i(_displayShaderProg, u_showVignette, true)); - GL_CHECK(glProgramUniform1f(_displayShaderProg, u_vignetteX, 0.31f)); - GL_CHECK(glProgramUniform1f(_displayShaderProg, u_vignetteY, 6.53f)); - GL_CHECK(glProgramUniform1i(_displayShaderProg, u_showReflection, false)); + GL_CHECK(glProgramUniform1i(_displayShaderProg, u_showVignette, u_showVignetteValue)); + GL_CHECK(glProgramUniform1f(_displayShaderProg, u_vignetteX, 0.31f));// 0.31f)); + GL_CHECK(glProgramUniform1f(_displayShaderProg, u_vignetteY, 7.10f));// 6.53f)); + GL_CHECK(glProgramUniform1i(_displayShaderProg, u_showReflection, u_showReflectionValue)); //GL_CHECK(glProgramUniform1f(_displayShaderProg, u_time, static_cast(QDateTime::currentMSecsSinceEpoch()))); GL_CHECK(glProgramUniform2f(_displayShaderProg, u_screenSize, static_cast(_viewWidth), static_cast(_viewHeight))); @@ -622,17 +674,41 @@ void OpenGLView::CheckOpenGLError(const char* stmt, const char* fname, int line) //----------------------------------------------------------------------------------------- -void OpenGLView::ShaderSetScreenCurve(GLint curve) +void OpenGLView::ShaderSetScreenCurve(GLfloat curve) { - //u_screenCurve = curve; - //GL_CHECK(glProgramUniform1f(_displayShaderProg, u_screenCurve, curve)); + u_screenCurveValue = curve; + PMDawn::Log(PMDawn::LOG_INFO, "ShaderSetScreenCurve = " + std::to_string(curve)); } //----------------------------------------------------------------------------------------- +void OpenGLView::ShaderSetVignette(bool onoff) +{ + u_showVignetteValue = onoff; + if (onoff) + { + PMDawn::Log(PMDawn::LOG_INFO, "ShaderSetVignette = true"); + } + else + { + PMDawn::Log(PMDawn::LOG_INFO, "ShaderSetVignette = false"); + } +} //----------------------------------------------------------------------------------------- +void OpenGLView::ShaderSetReflection(bool onoff) +{ + u_showReflectionValue = onoff; + if (onoff) + { + PMDawn::Log(PMDawn::LOG_INFO, "ShaderSetReflection = true"); + } + else + { + PMDawn::Log(PMDawn::LOG_INFO, "ShaderSetReflection = false"); + } +} //----------------------------------------------------------------------------------------- @@ -649,4 +725,4 @@ void OpenGLView::ShaderSetScreenCurve(GLint curve) //----------------------------------------------------------------------------------------- -//----------------------------------------------------------------------------------------- \ No newline at end of file +//----------------------------------------------------------------------------------------- diff --git a/SpectREM/SpectREM/Win32/OpenGLView.hpp b/SpectREM/SpectREM/Win32/OpenGLView.hpp index 244b394..2342748 100644 --- a/SpectREM/SpectREM/Win32/OpenGLView.hpp +++ b/SpectREM/SpectREM/Win32/OpenGLView.hpp @@ -40,6 +40,7 @@ #define GL_INFO_LOG_LENGTH 0x8B84 #define GL_TEXTURE0 0x84C0 #define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 #define GL_BGRA 0x80E1 #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 @@ -100,7 +101,7 @@ typedef void (APIENTRY * PFNGLPROGRAMUNIFORM2FPROC) (GLuint program, GLint locat class OpenGLView { public: - OpenGLView(); + OpenGLView(std::string bpath); ~OpenGLView(); public: @@ -110,7 +111,13 @@ class OpenGLView void UpdateTextureData(unsigned char *pData, GLint vX, GLint vY); void OpenGLView::Resize(int width, int height); void OpenGLView::Resize(int x, int y, int width, int height); - void OpenGLView::ShaderSetScreenCurve(GLint curve); + void OpenGLView::ShaderSetScreenCurve(GLfloat curve); + void OpenGLView::ShaderSetVignette(bool onoff); + void OpenGLView::ShaderSetReflection(bool onoff); + void OpenGLView::LoadFileTextures(); + bool OpenGLView::LoadBitmap(LPTSTR szFileName, GLuint& texid); + + private: bool InitialiseExtensions(); bool LoadExtensionList(); @@ -125,6 +132,8 @@ class OpenGLView public: + std::string appBasePath; + PFNGLATTACHSHADERPROC glAttachShader; PFNGLBINDBUFFERPROC glBindBuffer; PFNGLBINDVERTEXARRAYPROC glBindVertexArray; @@ -208,11 +217,12 @@ class OpenGLView GLuint _clutInputTexture; GLuint _clutTexture; GLuint _clutOutputTexture; + GLuint _reflectionTexture; // Display shader uniforms/samplers GLuint displayDepthBuffer; - GLuint reflectionTexture; + GLint s_displayTexture; GLint s_texture; GLint s_reflectionTexture; @@ -233,7 +243,9 @@ class OpenGLView GLint u_time; GLint u_screenSize; - + bool u_showVignetteValue; + GLfloat u_screenCurveValue; + bool u_showReflectionValue; diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index ebc8059..058e6c5 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -1091,7 +1091,7 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) QueryPerformanceFrequency(&perf_freq); QueryPerformanceCounter(&last_time); - m_pOpenGLView = new OpenGLView(); + m_pOpenGLView = new OpenGLView(bpath); // * zoomLevel m_pOpenGLView->Init(mainWindow, 256 * zoomLevel, 192 * zoomLevel, ID_SHADER_CLUT_VERT, ID_SHADER_CLUT_FRAG, ID_SHADER_DISPLAY_VERT, ID_SHADER_DISPLAY_FRAG, RT_RCDATA); m_pAudioQueue = new AudioQueue(); @@ -1113,7 +1113,9 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) m_pMachine->resume(); m_pMachine->resetMachine(true); - m_pOpenGLView->ShaderSetScreenCurve((GLint)0.0); + m_pOpenGLView->ShaderSetScreenCurve((GLfloat)0.10); + m_pOpenGLView->ShaderSetVignette(true); + m_pOpenGLView->ShaderSetReflection(true); // Do the main message loop while (!exit_emulator) diff --git a/SpectREM/SpectREM/Win32/display.frag b/SpectREM/SpectREM/Win32/display.frag index 7b103e6..1be72be 100644 --- a/SpectREM/SpectREM/Win32/display.frag +++ b/SpectREM/SpectREM/Win32/display.frag @@ -9,6 +9,7 @@ out vec4 out_fragColor; // Texture to be processed uniform sampler2D s_displayTexture; uniform sampler2D s_reflectionTexture; +uniform sampler2D s_poloTexture; // Uniforms linked to different screen settings uniform int u_borderSize; @@ -87,7 +88,7 @@ void main() // Anything outside the texture should be black, otherwise sample the texel in the texture if (texCoord.x < 0 || texCoord.y < 0 || texCoord.x > 1 || texCoord.y > 1) { - color = vec4(0, 0, 0, 1); + color = vec4(0.0, 0.0, 0.0, 1); } else { @@ -119,10 +120,10 @@ void main() // Adjust colour based on contrast, saturation and brightness color.rgb = colorCorrection(color.rgb, u_saturation, u_contrast, u_brightness); -// if (u_showReflection == 1) -// { -// color = mix(color, vec4(colorCorrection(vec3(texture( s_reflectionTexture, texCoord * vec2(-1.0, 1.0))), 0.2, 0.5, 0.8), 1.0), 0.18); -// } + if (u_showReflection == 1) + { + color = mix(color, vec4(colorCorrection(vec3(texture( s_reflectionTexture, texCoord * vec2(1.0, -0.4) ) ), 0.2, 0.5, 0.8), 1.0), 0.18); + } // Add scanlines float scanline = sin(scanTexCoord.y * u_scanlineSize) * 0.09 * u_scanlines; From 79075a141413673fa7328d27b28e05bde48e0034 Mon Sep 17 00:00:00 2001 From: John Young Date: Sun, 26 Sep 2021 16:05:18 +0100 Subject: [PATCH 23/23] FIXED: Audio volume is kept between resets of the z80 machine (AudioCore) --- SpectREM/SpectREM/Win32/WinMain.cpp | 24 ++++++++++++++++++++++-- SpectREM/SpectREM/Win32/display.frag | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/SpectREM/SpectREM/Win32/WinMain.cpp b/SpectREM/SpectREM/Win32/WinMain.cpp index 058e6c5..f0f0fbd 100644 --- a/SpectREM/SpectREM/Win32/WinMain.cpp +++ b/SpectREM/SpectREM/Win32/WinMain.cpp @@ -135,8 +135,9 @@ uint8_t fileListIndex = 0; std::thread scrDisplayThread; bool slideshowTimerRunning = false; bool slideshowRandom = true; -const float volumeStep = 0.1f; -float applicationVolume = 0.75f; +const float volumeStep = 0.05f; +const float startupVolume = 0.50f; +float applicationVolume = 0.50f; GLint viewportX; GLint viewportY; HANDLE tapeViewerThread; @@ -1103,7 +1104,17 @@ int __stdcall WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int ncmd) CoUninitialize(); return 0; } + // Get the current application volume (if changed previously) + if (applicationVolume < 0.0f || applicationVolume > 1.0f) + { + m_pAudioCore->SetOutputVolume(startupVolume); + } + else + { + m_pAudioCore->SetOutputVolume(applicationVolume); + } m_pTape = new Tape(tapeStatusCallback); + // Default to a Speccy 48k :) m_pMachine = new ZXSpectrum48(m_pTape);// ZXSpectrum128(m_pTape); m_pMachine->emuUseAYSound = true; m_pMachine->emuBasePath = PMDawn::GetApplicationBasePath(); @@ -1295,6 +1306,15 @@ static void ResetMachineForSnapshot(uint8_t mc, bool ayEnabled) m_pMachine->emuBasePath = PMDawn::GetApplicationBasePath(); m_pMachine->initialise(romPath); m_pAudioCore->Start(); + // Get the current application volume (if changed previously) + if (applicationVolume < 0.0f || applicationVolume > 1.0f) + { + m_pAudioCore->SetOutputVolume(startupVolume); + } + else + { + m_pAudioCore->SetOutputVolume(applicationVolume); + } m_pMachine->resume(); isResetting = false; diff --git a/SpectREM/SpectREM/Win32/display.frag b/SpectREM/SpectREM/Win32/display.frag index 1be72be..8c2603d 100644 --- a/SpectREM/SpectREM/Win32/display.frag +++ b/SpectREM/SpectREM/Win32/display.frag @@ -122,7 +122,7 @@ void main() if (u_showReflection == 1) { - color = mix(color, vec4(colorCorrection(vec3(texture( s_reflectionTexture, texCoord * vec2(1.0, -0.4) ) ), 0.2, 0.5, 0.8), 1.0), 0.18); + //color = mix(color, vec4(colorCorrection(vec3(texture( s_reflectionTexture, texCoord * vec2(1.0, -0.4) ) ), 0.2, 0.5, 0.8), 1.0), 0.18); } // Add scanlines