diff --git a/README.md b/README.md index a3f668c16b..07e073784f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Its purpose in life is to read an audio input and to produce mesmerizing visuals ### Available For * [macOS, Linux (binary)](https://github.com/projectM-visualizer/projectm/releases) * [Windows Store (PC, XBOX, Phone)](https://www.microsoft.com/store/apps/9NDCVH0VCWJN) +* [Windows (standalone binary)](https://github.com/projectM-visualizer/projectm/releases) (Requires the latest [Visual C++ redistributable](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads)) * [Android](https://play.google.com/store/apps/details?id=com.psperl.projectM) * [iOS](https://itunes.apple.com/us/app/projectm-music-visualizer/id530922227?mt=8&ign-mpt=uo%3D4) * [iTunes plugin (macOS)](https://github.com/projectM-visualizer/projectm/releases/) diff --git a/msvc/projectM.ico b/msvc/projectM.ico new file mode 100644 index 0000000000..2f7fe13993 Binary files /dev/null and b/msvc/projectM.ico differ diff --git a/msvc/projectM.sln b/msvc/projectM.sln index 81c7ad23c6..cf70e0a902 100644 --- a/msvc/projectM.sln +++ b/msvc/projectM.sln @@ -62,27 +62,17 @@ Global {013DE011-EC24-3643-A8EE-F2609E7E4741}.Release|x64.ActiveCfg = Release|x64 {013DE011-EC24-3643-A8EE-F2609E7E4741}.Release|x64.Build.0 = Release|x64 {55A71B6A-5C7E-30D5-8210-302A8D2080DB}.Debug|x64.ActiveCfg = Debug|x64 - {55A71B6A-5C7E-30D5-8210-302A8D2080DB}.Debug|x64.Build.0 = Debug|x64 {55A71B6A-5C7E-30D5-8210-302A8D2080DB}.Release|x64.ActiveCfg = Release|x64 - {55A71B6A-5C7E-30D5-8210-302A8D2080DB}.Release|x64.Build.0 = Release|x64 {9260C46C-6BC9-396F-9310-6BAAD56A7801}.Debug|x64.ActiveCfg = Debug|x64 {9260C46C-6BC9-396F-9310-6BAAD56A7801}.Release|x64.ActiveCfg = Release|x64 {7A203034-A4D7-3A2B-9138-CB125F9B35E6}.Debug|x64.ActiveCfg = Debug|x64 - {7A203034-A4D7-3A2B-9138-CB125F9B35E6}.Debug|x64.Build.0 = Debug|x64 {7A203034-A4D7-3A2B-9138-CB125F9B35E6}.Release|x64.ActiveCfg = Release|x64 - {7A203034-A4D7-3A2B-9138-CB125F9B35E6}.Release|x64.Build.0 = Release|x64 {6E418BC8-5407-3A37-96BD-5201D47DE753}.Debug|x64.ActiveCfg = Debug|x64 - {6E418BC8-5407-3A37-96BD-5201D47DE753}.Debug|x64.Build.0 = Debug|x64 {6E418BC8-5407-3A37-96BD-5201D47DE753}.Release|x64.ActiveCfg = Release|x64 - {6E418BC8-5407-3A37-96BD-5201D47DE753}.Release|x64.Build.0 = Release|x64 {B7C4937F-A36D-3B6C-A8AC-CA99772AE5EC}.Debug|x64.ActiveCfg = Debug|x64 - {B7C4937F-A36D-3B6C-A8AC-CA99772AE5EC}.Debug|x64.Build.0 = Debug|x64 {B7C4937F-A36D-3B6C-A8AC-CA99772AE5EC}.Release|x64.ActiveCfg = Release|x64 - {B7C4937F-A36D-3B6C-A8AC-CA99772AE5EC}.Release|x64.Build.0 = Release|x64 {27DDCE71-E33B-3521-92B5-9918356D78A1}.Debug|x64.ActiveCfg = Debug|x64 - {27DDCE71-E33B-3521-92B5-9918356D78A1}.Debug|x64.Build.0 = Debug|x64 {27DDCE71-E33B-3521-92B5-9918356D78A1}.Release|x64.ActiveCfg = Release|x64 - {27DDCE71-E33B-3521-92B5-9918356D78A1}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/msvc/projectMSDL.vcxproj b/msvc/projectMSDL.vcxproj index 4b38770bb7..b5377f5a88 100644 --- a/msvc/projectMSDL.vcxproj +++ b/msvc/projectMSDL.vcxproj @@ -89,7 +89,7 @@ %(IgnoreSpecificDefaultLibraries) projectM-sdl/Debug/projectMSDL.lib projectM-sdl/Debug/projectMSDL.pdb - Console + Windows false @@ -133,7 +133,7 @@ %(IgnoreSpecificDefaultLibraries) projectM-sdl/Release/projectMSDL.lib projectM-sdl/Release/projectMSDL.pdb - Console + Windows false @@ -176,7 +176,7 @@ - + true @@ -210,6 +210,7 @@ + diff --git a/src/libprojectM/PresetFactory.cpp b/src/libprojectM/PresetFactory.cpp index aa4fc98c0a..c7b2178d8e 100644 --- a/src/libprojectM/PresetFactory.cpp +++ b/src/libprojectM/PresetFactory.cpp @@ -16,7 +16,9 @@ std::string PresetFactory::protocol(const std::string & url, std::string & path) else { path = url.substr(pos + 3, url.length()); // std::cout << "[PresetFactory] path is " << path << std::endl; +#ifdef DEBUG std::cout << "[PresetFactory] url is " << url << std::endl; +#endif return url.substr(0, pos); } diff --git a/src/libprojectM/Renderer/ShaderEngine.cpp b/src/libprojectM/Renderer/ShaderEngine.cpp index b5910ee436..05afded149 100644 --- a/src/libprojectM/Renderer/ShaderEngine.cpp +++ b/src/libprojectM/Renderer/ShaderEngine.cpp @@ -765,7 +765,9 @@ GLuint ShaderEngine::compilePresetShader(const PresentShaderType shaderType, Sha } if (ret != GL_FALSE) { +#ifdef DEBUG std::cerr << "Successful compilation of " << shaderTypeString << std::endl; +#endif } else { std::cerr << "Compilation error (step3) of " << shaderTypeString << std::endl; diff --git a/src/projectM-sdl/pmSDL.cpp b/src/projectM-sdl/pmSDL.cpp index 379fc157d7..98978d56ce 100644 --- a/src/projectM-sdl/pmSDL.cpp +++ b/src/projectM-sdl/pmSDL.cpp @@ -61,27 +61,45 @@ void projectMSDL::audioInputCallbackS16(void *userdata, unsigned char *stream, i app->pcm()->addPCM16(pcm16); } -SDL_AudioDeviceID projectMSDL::selectAudioInput(int _count) { - // TODO: implement some sort of UI allowing the user to select which audio input device they would like to use - - - // ask the user which capture device to use - // printf("Please select which audio input to use:\n"); - printf("Detected devices:\n"); - for (int i = 0; i < _count; i++) { - printf(" %i: 🎤%s\n", i, SDL_GetAudioDeviceName(i, true)); - } - - return 0; -} - int projectMSDL::toggleAudioInput() { - - CurAudioDevice++; - if (CurAudioDevice >= NumAudioDevices) + // trigger a toggle with CMD-I or CTRL-I + if (wasapi) { // we are currently on WASAPI, so we are going to revert to a microphone/line-in input. + if (this->openAudioInput()) + this->beginAudioCapture(); CurAudioDevice = 0; - selectedAudioDevice = CurAudioDevice; - initAudioInput(); + selectedAudioDevice = CurAudioDevice; + this->wasapi = false; // Track wasapi as off so projectMSDL will stop listening to WASAPI loopback in pmSDL_main. + } + else { + this->endAudioCapture(); // end current audio capture. + CurAudioDevice++; // iterate device index + if (CurAudioDevice >= NumAudioDevices) { // We reached outside the boundaries of available audio devices. + CurAudioDevice = 0; // Return to first audio device in the index. +#ifdef WASAPI_LOOPBACK + // If we are at the boundary and WASAPI is enabled then let's load WASAPI instead. + projectM::setToastMessage("Loopback audio selected"); + SDL_Log("Loopback audio selected"); + this->fakeAudio = false; // disable fakeAudio in case it was enabled. + this->wasapi = true; // Track wasapi as on so projectMSDL will listen to it. +#else + if (NumAudioDevices == 1) // If WASAPI_LOOPBACK was not enabled and there is only one audio device, it's pointless to toggle anything. + { + SDL_Log("There is only one audio capture device. There is nothing to toggle at this time."); + return 1; + } + // If WASAPI_LOOPBACK is not enabled and we have multiple input devices, return to device index 0 and let's listen to that device. + selectedAudioDevice = CurAudioDevice; + initAudioInput(); + this->beginAudioCapture(); +#endif + } + else { + // This is a normal scenario where we move forward in the audio device index. + selectedAudioDevice = CurAudioDevice; + initAudioInput(); + this->beginAudioCapture(); + } + } return 1; } @@ -105,15 +123,17 @@ int projectMSDL::initAudioInput() { if (audioDeviceID == 0) { SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to open audio capture device: %s", SDL_GetError()); - SDL_Quit(); + return 0; } // read characteristics of opened capture device SDL_Log("Opened audio capture device index=%i devId=%i: %s", selectedAudioDevice, audioDeviceID, SDL_GetAudioDeviceName(selectedAudioDevice, true)); - std::string deviceToast = "Listening to "; - deviceToast += SDL_GetAudioDeviceName(selectedAudioDevice, true); + std::string deviceToast = SDL_GetAudioDeviceName(selectedAudioDevice, true); // Example: Microphone rear + deviceToast += " selected"; projectM::setToastMessage(deviceToast); +#ifdef DEBUG SDL_Log("Samples: %i, frequency: %i, channels: %i, format: %i", have.samples, have.freq, have.channels, have.format); +#endif audioChannelsCount = have.channels; audioSampleRate = have.freq; audioSampleCount = have.samples; @@ -124,9 +144,12 @@ int projectMSDL::initAudioInput() { } int projectMSDL::openAudioInput() { + fakeAudio = false; // if we are opening an audio input then there is no need for fake audio. // get audio driver name (static) +#ifdef DEBUG const char* driver_name = SDL_GetCurrentAudioDriver(); SDL_Log("Using audio driver: %s\n", driver_name); +#endif // get audio input device unsigned int i; @@ -135,23 +158,18 @@ int projectMSDL::openAudioInput() { CurAudioDevice = 0; if (NumAudioDevices == 0) { SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "No audio capture devices found"); - SDL_Quit(); + projectM::setToastMessage("No audio capture devices found: using simulated audio"); + fakeAudio = true; + return 0; } +#ifdef DEBUG for (i = 0; i < NumAudioDevices; i++) { SDL_Log("Found audio capture device %d: %s", i, SDL_GetAudioDeviceName(i, true)); } +#endif - // device to open + // default selected Audio Device to 0. selectedAudioDevice = 0; - if (NumAudioDevices > 1) { - // need to choose which input device to use - selectedAudioDevice = selectAudioInput(CurAudioDevice); - if (selectedAudioDevice > NumAudioDevices) { - SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "No audio input device specified."); - SDL_Quit(); - } - } - initAudioInput(); return 1; @@ -164,6 +182,7 @@ void projectMSDL::beginAudioCapture() { void projectMSDL::endAudioCapture() { SDL_PauseAudioDevice(audioDeviceID, true); + SDL_CloseAudioDevice(audioDeviceID); } void projectMSDL::setHelpText(const std::string & helpText) { @@ -465,6 +484,10 @@ void projectMSDL::init(SDL_Window *window, SDL_GLContext *_glCtx, const bool _re glCtx = _glCtx; projectM_resetGL(width, height); +#ifdef WASAPI_LOOPBACK + wasapi = true; +#endif + // are we rendering to a texture? renderToTexture = _renderToTexture; if (renderToTexture) { diff --git a/src/projectM-sdl/pmSDL.hpp b/src/projectM-sdl/pmSDL.hpp index 5d25e2fbc6..531f33c137 100644 --- a/src/projectM-sdl/pmSDL.hpp +++ b/src/projectM-sdl/pmSDL.hpp @@ -88,6 +88,8 @@ class projectMSDL : public projectM { bool done; + bool wasapi = false; // Used to track if wasapi is currently active. This bool will allow us to run a WASAPI app and still toggle to microphone inputs. + bool fakeAudio = false; // Used to track fake audio, so we can turn it off and on. projectMSDL(Settings settings, int flags); projectMSDL(std::string config_file, int flags); void init(SDL_Window *window, SDL_GLContext *glCtx, const bool renderToTexture = false); @@ -135,7 +137,6 @@ class projectMSDL : public projectM { static void audioInputCallbackS16(void *userdata, unsigned char *stream, int len); void keyHandler(SDL_Event *); - SDL_AudioDeviceID selectAudioInput(int _count); void renderTexture(); }; diff --git a/src/projectM-sdl/projectM_SDL_main.cpp b/src/projectM-sdl/projectM_SDL_main.cpp index b926bf86b1..876b3f884f 100644 --- a/src/projectM-sdl/projectM_SDL_main.cpp +++ b/src/projectM-sdl/projectM_SDL_main.cpp @@ -234,7 +234,7 @@ srand((int)(time(NULL))); ERR(L"pAudioClient->Start error"); return hr; } - + bool bDone = false; bool bFirstPacket = true; UINT32 nPasses = 0; @@ -376,6 +376,7 @@ modKey = "CMD"; std::string sdlHelpMenu = "\n" "F1: This help menu""\n" "F3: Show preset name""\n" + "F4: Show details and statistics""\n" "F5: Show FPS""\n" "L or SPACE: Lock/Unlock Preset""\n" "R: Random preset""\n" @@ -404,9 +405,16 @@ modKey = "CMD"; #endif #if !FAKE_AUDIO && !WASAPI_LOOPBACK - // get an audio input device - if (app->openAudioInput()) - app->beginAudioCapture(); + // get an audio input device + if (app->openAudioInput()) + app->beginAudioCapture(); +#endif + +#ifdef WASAPI_LOOPBACK + // Default to WASAPI loopback if it was enabled at compilation. + app->wasapi = true; + // Notify that loopback capture was started. + SDL_Log("Opened audio capture loopback."); #endif #if TEST_ALL_PRESETS @@ -445,52 +453,58 @@ modKey = "CMD"; while (! app->done) { app->renderFrame(); #if FAKE_AUDIO - app->addFakePCM(); + app->fakeAudio = true; #endif + // fakeAudio can also be enabled dynamically. + if (app->fakeAudio ) + { + app->addFakePCM(); + } #ifdef WASAPI_LOOPBACK - // drain data while it is available - nPasses++; - UINT32 nNextPacketSize; - for ( - hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize); - SUCCEEDED(hr) && nNextPacketSize > 0; - hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize) - ) { - // get the captured data - BYTE *pData; - UINT32 nNumFramesToRead; - DWORD dwFlags; - - hr = pAudioCaptureClient->GetBuffer( - &pData, - &nNumFramesToRead, - &dwFlags, - NULL, - NULL - ); - if (FAILED(hr)) { - return hr; + if (app->wasapi) { + // drain data while it is available + nPasses++; + UINT32 nNextPacketSize; + for ( + hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize); + SUCCEEDED(hr) && nNextPacketSize > 0; + hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize) + ) { + // get the captured data + BYTE *pData; + UINT32 nNumFramesToRead; + DWORD dwFlags; + + hr = pAudioCaptureClient->GetBuffer( + &pData, + &nNumFramesToRead, + &dwFlags, + NULL, + NULL + ); + if (FAILED(hr)) { + return hr; + } + + LONG lBytesToWrite = nNumFramesToRead * nBlockAlign; + + /** Add the waveform data */ + app->pcm()->addPCMfloat((float *)pData, nNumFramesToRead); + + *pnFrames += nNumFramesToRead; + + hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead); + if (FAILED(hr)) { + return hr; + } + + bFirstPacket = false; } - LONG lBytesToWrite = nNumFramesToRead * nBlockAlign; - - /** Add the waveform data */ - app->pcm()->addPCMfloat((float *)pData, nNumFramesToRead); - - *pnFrames += nNumFramesToRead; - - hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead); if (FAILED(hr)) { return hr; } - - bFirstPacket = false; } - - if (FAILED(hr)) { - return hr; - } - #endif /** WASAPI_LOOPBACK */ #if UNLOCK_FPS @@ -512,8 +526,9 @@ modKey = "CMD"; } SDL_GL_DeleteContext(glCtx); -#if !FAKE_AUDIO && !WASAPI_LOOPBACK - app->endAudioCapture(); +#if !FAKE_AUDIO + if (!app->wasapi) // not currently using WASAPI, so we need to endAudioCapture. + app->endAudioCapture(); #endif delete app;