mirror of
https://github.com/projectM-visualizer/projectm.git
synced 2026-02-04 13:25:34 +00:00
Split SDL project up into more managable pieces (#473)
* Enable text for SDL app on mac * fix screenshots * brew readme * wip * Split SDL project up into more managable pieces * clean * clean * Fixed Windows build, made (unused) FPS counter portable to non-POSIX platforms. Co-authored-by: Kai Blaschke <kai.blaschke@kb-dev.net>
This commit is contained in:
committed by
GitHub
parent
4184f237bd
commit
f9cfdfed77
@ -207,7 +207,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "set -euxo pipefail\n\necho BUILT_PRODUCTS_DIR $BUILT_PRODUCTS_DIR\nls \"$BUILT_PRODUCTS_DIR\"\n\nmkdir -p \"$TEMP_DIR\"\n\nSDL_PKG=\"$BUILT_PRODUCTS_DIR/ProjectM-SDL.pkg\"\nMUSIC_PLUGIN_PKG=\"$BUILT_PRODUCTS_DIR/ProjectM-MusicPlugin.pkg\"\n\n#productbuild --timestamp --sign '5926VBQM6Y' --package $SDL_PKG --package $MUSIC_PLUGIN_PKG \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\"\nproductbuild --timestamp --sign '5926VBQM6Y' --distribution mac/Distribution.xml --package-path \"$BUILT_PRODUCTS_DIR\" \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\"\n#productbuild --package \"$SDL_PKG\" --package \"$MUSIC_PLUGIN_PKG\" \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\"\n\necho \"Created installer package $BUILT_PRODUCTS_DIR/ProjectM.pkg\"\n\ncp -rp \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\" \"$SRCROOT/\"\n";
|
||||
shellScript = "set -euxo pipefail\n\necho BUILT_PRODUCTS_DIR $BUILT_PRODUCTS_DIR\nls \"$BUILT_PRODUCTS_DIR\"\n\nmkdir -p \"$TEMP_DIR\"\n\nSDL_PKG=\"$BUILT_PRODUCTS_DIR/ProjectM-SDL.pkg\"\nMUSIC_PLUGIN_PKG=\"$BUILT_PRODUCTS_DIR/ProjectM-MusicPlugin.pkg\"\n\n\n#productbuild --timestamp --sign '5926VBQM6Y' --package $SDL_PKG --package $MUSIC_PLUGIN_PKG \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\"\nproductbuild --timestamp --sign '5926VBQM6Y' --distribution mac/Distribution.xml --package-path \"$BUILT_PRODUCTS_DIR\" \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\"\n#productbuild --package \"$SDL_PKG\" --package \"$MUSIC_PLUGIN_PKG\" \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\"\n\necho \"Created installer package $BUILT_PRODUCTS_DIR/ProjectM.pkg\"\n\ncp -rp \"$BUILT_PRODUCTS_DIR/ProjectM.pkg\" \"$SRCROOT/\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@ -142,6 +142,9 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)../src\projectM-sdl\projectM_SDL_main.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)../src\projectM-sdl\pmSDL.cpp" />
|
||||
<ClCompile Include="..\src\projectM-sdl\audioCapture.cpp" />
|
||||
<ClCompile Include="..\src\projectM-sdl\loopback.cpp" />
|
||||
<ClCompile Include="..\src\projectM-sdl\setup.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="\MilkdropPresetFactory.vcxproj">
|
||||
@ -181,6 +184,10 @@
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\src\projectM-sdl\audioCapture.hpp" />
|
||||
<ClInclude Include="..\src\projectM-sdl\loopback.hpp" />
|
||||
<ClInclude Include="..\src\projectM-sdl\pmSDL.hpp" />
|
||||
<ClInclude Include="..\src\projectM-sdl\setup.hpp" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@ -244,7 +244,6 @@
|
||||
1668542B2105E4BD0042793A /* Renderable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Renderable.cpp; path = Renderer/Renderable.cpp; sourceTree = "<group>"; };
|
||||
1668542C2105E4BD0042793A /* PipelineContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = PipelineContext.cpp; path = Renderer/PipelineContext.cpp; sourceTree = "<group>"; };
|
||||
1668542D2105E4BD0042793A /* MilkdropWaveform.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MilkdropWaveform.cpp; path = Renderer/MilkdropWaveform.cpp; sourceTree = "<group>"; };
|
||||
168404F425D82ED70001F02C /* StaticGlShaders.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = StaticGlShaders.cpp; path = Renderer/StaticGlShaders.cpp; sourceTree = "<group>"; };
|
||||
168404FE25D82FB80001F02C /* Intrinsics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Intrinsics.h; sourceTree = "<group>"; };
|
||||
1687172320C33DF300947E7E /* TextureManager.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TextureManager.hpp; path = Renderer/TextureManager.hpp; sourceTree = "<group>"; };
|
||||
1687172420C33DF300947E7E /* Renderable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Renderable.hpp; path = Renderer/Renderable.hpp; sourceTree = "<group>"; };
|
||||
@ -446,7 +445,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
168A57C62516226900E802A0 /* StaticGlShaders.cpp */,
|
||||
168404F425D82ED70001F02C /* StaticGlShaders.cpp */,
|
||||
166854412105E4C20042793A /* etc */,
|
||||
1687172220C33DDF00947E7E /* headers */,
|
||||
168C689F20BB265D000AFC1B /* hlslparser */,
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
// Enable openGL extra checks, better not be enabled in release build
|
||||
#define OGL_DEBUG 0
|
||||
|
||||
// Unlock FPS for rendering benchmarks, it disables Vblank/Vsync and prints drawned frames count within a 5s test run
|
||||
// Unlock FPS for rendering benchmarks, it disables Vblank/Vsync and prints drawn frame count within a 5s test run
|
||||
#define UNLOCK_FPS 0
|
||||
|
||||
// If a shader compilation failure occurs, it dumps shader source into /tmp instead of stderr
|
||||
|
||||
@ -154,7 +154,7 @@ bool projectM::writeConfig(const std::string & configFile, const Settings & sett
|
||||
config.add("Easter Egg Parameter", settings.easterEgg);
|
||||
config.add("Shuffle Enabled", settings.shuffleEnabled);
|
||||
config.add("Soft Cut Ratings Enabled", settings.softCutRatingsEnabled);
|
||||
std::fstream file(configFile.c_str());
|
||||
std::fstream file(configFile.c_str(), std::ios_base::trunc | std::ios_base::out);
|
||||
if (file) {
|
||||
file << config;
|
||||
return true;
|
||||
@ -164,7 +164,7 @@ bool projectM::writeConfig(const std::string & configFile, const Settings & sett
|
||||
|
||||
|
||||
|
||||
void projectM::readConfig (const std::string & configFile )
|
||||
void projectM::readConfig (const std::string & configFile)
|
||||
{
|
||||
std::cout << "[projectM] config file: " << configFile << std::endl;
|
||||
|
||||
|
||||
@ -8,7 +8,9 @@ ${my_CFLAGS} \
|
||||
${SDL_CFLAGS}
|
||||
|
||||
bin_PROGRAMS = projectMSDL
|
||||
projectMSDL_SOURCES = pmSDL.cpp projectM_SDL_main.cpp pmSDL.hpp
|
||||
projectMSDL_SOURCES = pmSDL.cpp projectM_SDL_main.cpp pmSDL.hpp \
|
||||
setup.cpp setup.hpp loopback.cpp loopback.hpp \
|
||||
audioCapture.cpp audioCapture.hpp
|
||||
projectMSDL_LDADD =
|
||||
#projectMSDL_LDADD += -lasan
|
||||
projectMSDL_LDADD += ${SDL_LIBS} ../libprojectM/libprojectM.la
|
||||
|
||||
@ -17,9 +17,12 @@
|
||||
168F715921124C0E001806E7 /* config.inp in Support files */ = {isa = PBXBuildFile; fileRef = 16B52AAA21105A6900830F34 /* config.inp */; };
|
||||
168F715A21124C14001806E7 /* fonts in Support files */ = {isa = PBXBuildFile; fileRef = C3D30B8F1BF02BE5009AAACD /* fonts */; };
|
||||
168F718021126256001806E7 /* AppIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 168F717F21126256001806E7 /* AppIcon.icns */; };
|
||||
168FECCD25EA83F800E3E133 /* loopback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 168FECCB25EA83F800E3E133 /* loopback.cpp */; };
|
||||
168FECD425EA86E900E3E133 /* setup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 168FECD225EA86E900E3E133 /* setup.cpp */; };
|
||||
169BC64024CC3B56007B7829 /* presets in Copy Files */ = {isa = PBXBuildFile; fileRef = C307DFD31D565B57002F6B9E /* presets */; };
|
||||
169BC64224CC3FCA007B7829 /* presets in Support files */ = {isa = PBXBuildFile; fileRef = C307DFD31D565B57002F6B9E /* presets */; };
|
||||
169BC65024CC8401007B7829 /* fonts in Copy Files */ = {isa = PBXBuildFile; fileRef = C3D30B8F1BF02BE5009AAACD /* fonts */; };
|
||||
16CFA3AC25EABCB100E7893C /* audioCapture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 16CFA3AA25EABCB100E7893C /* audioCapture.cpp */; };
|
||||
C345215C1BF025A9001707D2 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C345215B1BF025A9001707D2 /* OpenGL.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -144,12 +147,18 @@
|
||||
168F714921120210001806E7 /* ProjectM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ProjectM.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
168F714B21120211001806E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
168F717F21126256001806E7 /* AppIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = AppIcon.icns; sourceTree = "<group>"; };
|
||||
168FECCB25EA83F800E3E133 /* loopback.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = loopback.cpp; sourceTree = "<group>"; };
|
||||
168FECCC25EA83F800E3E133 /* loopback.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = loopback.hpp; sourceTree = "<group>"; };
|
||||
168FECD225EA86E900E3E133 /* setup.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = setup.cpp; sourceTree = "<group>"; };
|
||||
168FECD325EA86E900E3E133 /* setup.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = setup.hpp; sourceTree = "<group>"; };
|
||||
169501FE1F7009E9008FAF86 /* pmSDL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pmSDL.cpp; sourceTree = "<group>"; };
|
||||
169501FF1F7009E9008FAF86 /* pmSDL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = pmSDL.hpp; sourceTree = "<group>"; };
|
||||
169BC64B24CC8353007B7829 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = "<group>"; };
|
||||
169BC64D24CC83CD007B7829 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = "<group>"; };
|
||||
16B52AA8211054E900830F34 /* projectMSDL-pkg.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "projectMSDL-pkg.plist"; sourceTree = "<group>"; };
|
||||
16B52AAA21105A6900830F34 /* config.inp */ = {isa = PBXFileReference; lastKnownFileType = text; path = config.inp; sourceTree = "<group>"; };
|
||||
16CFA3AA25EABCB100E7893C /* audioCapture.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = audioCapture.cpp; sourceTree = "<group>"; };
|
||||
16CFA3AB25EABCB100E7893C /* audioCapture.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = audioCapture.hpp; sourceTree = "<group>"; };
|
||||
C307DFD31D565B57002F6B9E /* presets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = presets; path = ../../presets; sourceTree = "<group>"; };
|
||||
C34521441BF02294001707D2 /* SDLprojectM */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SDLprojectM; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C345214E1BF022A5001707D2 /* projectM_SDL_main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = projectM_SDL_main.cpp; sourceTree = SOURCE_ROOT; };
|
||||
@ -233,6 +242,12 @@
|
||||
C345213B1BF02293001707D2 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
16CFA3AA25EABCB100E7893C /* audioCapture.cpp */,
|
||||
16CFA3AB25EABCB100E7893C /* audioCapture.hpp */,
|
||||
168FECD225EA86E900E3E133 /* setup.cpp */,
|
||||
168FECD325EA86E900E3E133 /* setup.hpp */,
|
||||
168FECCB25EA83F800E3E133 /* loopback.cpp */,
|
||||
168FECCC25EA83F800E3E133 /* loopback.hpp */,
|
||||
C60BD8E1259CF3CA0038831F /* SDLprojectM.entitlements */,
|
||||
169501FF1F7009E9008FAF86 /* pmSDL.hpp */,
|
||||
169501FE1F7009E9008FAF86 /* pmSDL.cpp */,
|
||||
@ -412,6 +427,9 @@
|
||||
files = (
|
||||
16355BFE2143C0F400B3748F /* projectM_SDL_main.cpp in Sources */,
|
||||
16355BFF2143C0F600B3748F /* pmSDL.cpp in Sources */,
|
||||
168FECCD25EA83F800E3E133 /* loopback.cpp in Sources */,
|
||||
168FECD425EA86E900E3E133 /* setup.cpp in Sources */,
|
||||
16CFA3AC25EABCB100E7893C /* audioCapture.cpp in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
152
src/projectM-sdl/audioCapture.cpp
Normal file
152
src/projectM-sdl/audioCapture.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
#include "audioCapture.hpp"
|
||||
|
||||
int projectMSDL::initAudioInput() {
|
||||
// params for audio input
|
||||
SDL_AudioSpec want, have;
|
||||
|
||||
// requested format
|
||||
// https://wiki.libsdl.org/SDL_AudioSpec#Remarks
|
||||
SDL_zero(want);
|
||||
want.freq = 44100;
|
||||
want.format = AUDIO_F32; // float
|
||||
want.channels = 2; // mono might be better?
|
||||
// lower might reduce latency
|
||||
want.samples = PCM::maxsamples;
|
||||
want.callback = projectMSDL::audioInputCallbackF32;
|
||||
want.userdata = this;
|
||||
|
||||
audioDeviceID = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(selectedAudioDevice, true), true, &want, &have, 0);
|
||||
|
||||
if (audioDeviceID == 0) {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to open audio capture device: %s", SDL_GetError());
|
||||
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 = 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;
|
||||
audioFormat = have.format;
|
||||
audioInputDevice = audioDeviceID;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void projectMSDL::audioInputCallbackF32(void *userdata, unsigned char *stream, int len) {
|
||||
projectMSDL *app = (projectMSDL *) userdata;
|
||||
// printf("\nLEN: %i\n", len);
|
||||
// for (int i = 0; i < 64; i++)
|
||||
// printf("%X ", stream[i]);
|
||||
// stream is (i think) samples*channels floats (native byte order) of len BYTES
|
||||
if (app->audioChannelsCount == 1)
|
||||
app->pcm()->addPCMfloat((float *)stream, len/sizeof(float));
|
||||
else if (app->audioChannelsCount == 2)
|
||||
app->pcm()->addPCMfloat_2ch((float *)stream, len/sizeof(float));
|
||||
else {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Multichannel audio not supported");
|
||||
SDL_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
void projectMSDL::audioInputCallbackS16(void *userdata, unsigned char *stream, int len) {
|
||||
// printf("LEN: %i\n", len);
|
||||
projectMSDL *app = (projectMSDL *) userdata;
|
||||
short pcm16[2][512];
|
||||
|
||||
for (int i = 0; i < 512; i++) {
|
||||
for (int j = 0; j < app->audioChannelsCount; j++) {
|
||||
pcm16[j][i] = stream[i+j];
|
||||
}
|
||||
}
|
||||
app->pcm()->addPCM16(pcm16);
|
||||
}
|
||||
|
||||
int projectMSDL::toggleAudioInput() {
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
NumAudioDevices = SDL_GetNumAudioDevices(true); // capture, please
|
||||
|
||||
CurAudioDevice = 0;
|
||||
if (NumAudioDevices == 0) {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "No audio capture devices found");
|
||||
projectM::setToastMessage("No audio capture devices found: using simulated audio");
|
||||
fakeAudio = true;
|
||||
return 0;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
for (unsigned int i = 0; i < NumAudioDevices; i++) {
|
||||
SDL_Log("Found audio capture device %d: %s", i, SDL_GetAudioDeviceName(i, true));
|
||||
}
|
||||
#endif
|
||||
|
||||
// default selected Audio Device to 0.
|
||||
selectedAudioDevice = 0;
|
||||
initAudioInput();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void projectMSDL::beginAudioCapture() {
|
||||
// allocate a buffer to store PCM data for feeding in
|
||||
SDL_PauseAudioDevice(audioDeviceID, false);
|
||||
}
|
||||
|
||||
void projectMSDL::endAudioCapture() {
|
||||
SDL_PauseAudioDevice(audioDeviceID, true);
|
||||
SDL_CloseAudioDevice(audioDeviceID);
|
||||
}
|
||||
6
src/projectM-sdl/audioCapture.hpp
Normal file
6
src/projectM-sdl/audioCapture.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef audioCapture_hpp
|
||||
#define audioCapture_hpp
|
||||
|
||||
#include "pmSDL.hpp"
|
||||
|
||||
#endif /* audioCapture_hpp */
|
||||
@ -1,28 +1,23 @@
|
||||
# config.inp
|
||||
# Configuration File for projectM
|
||||
|
||||
#Texture Size = 1024 # Size of internal rendering texture
|
||||
|
||||
Mesh X = 708 # Width of PerPixel Equation mesh
|
||||
Mesh Y = 400 # Height of PerPixel Equation mesh
|
||||
FPS = 60 # Frames Per Second
|
||||
Fullscreen = false
|
||||
Window Width = 512 # startup window width
|
||||
Window Height = 512 # startup window height
|
||||
|
||||
Mesh X = 708 # Width of PerPixel Equation mesh
|
||||
Mesh Y = 400 # Height of PerPixel Equation mesh
|
||||
FPS = 60 # Frames Per Second
|
||||
Smooth Transition Duration = 1 # in seconds
|
||||
Preset Duration = 10 # in seconds
|
||||
|
||||
Hard Cuts Enabled = false # Hard Cuts are preset transitions that occur when your music becomes louder. They only occur after a hard cut duration threshold has passed.
|
||||
Hard Cut Duration = 60 # Number of seconds before you become eligible for a hard cut.
|
||||
Hard Cut Sensitivity = 1.0 # Volume sensitivity before a hard cut is triggered.
|
||||
|
||||
Beat Sensitivity = 1.0 # Beat Sensitivity impacts how reactive your visualizations are to volume, bass, mid-range, and treble. Default 1.0. Range: 0 - 5 (from "dead" to VERY reactive).
|
||||
Preset Duration = 10 # in seconds
|
||||
Hard Cuts Enabled = false # Hard Cuts are preset transitions that occur when your music becomes louder. They only occur after a hard cut duration threshold has passed.
|
||||
Hard Cut Duration = 60 # Number of seconds before you become eligible for a hard cut.
|
||||
Hard Cut Sensitivity = 1.0 # Volume sensitivity before a hard cut is triggered.
|
||||
Beat Sensitivity = 1.0 # Beat Sensitivity impacts how reactive your visualizations are to volume, bass, mid-range, and treble. Default 1.0. Range: 0 - 5 (from "dead" to VERY reactive).
|
||||
|
||||
# mostly ignored in projectM-SDL
|
||||
#Texture Size = 1024 # Size of internal rendering texture
|
||||
Window Width = 512 # startup window width
|
||||
Window Height = 512 # startup window height
|
||||
Fullscreen = false
|
||||
Easter Egg Parameter = 1
|
||||
|
||||
Aspect Correction = true # Custom Shape Aspect Correction
|
||||
|
||||
Preset Path = presets # preset location
|
||||
Title Font = Vera.ttf
|
||||
Menu Font = VeraMono.ttf
|
||||
|
||||
228
src/projectM-sdl/loopback.cpp
Normal file
228
src/projectM-sdl/loopback.cpp
Normal file
@ -0,0 +1,228 @@
|
||||
// Handles audio loopback
|
||||
|
||||
#include "loopback.hpp"
|
||||
|
||||
|
||||
// ref https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
|
||||
IAudioCaptureClient *pAudioCaptureClient;
|
||||
UINT32 foo = 0;
|
||||
PUINT32 pnFrames = &foo;
|
||||
UINT32 nBlockAlign = 0;
|
||||
UINT32 nPasses = 0;
|
||||
bool bFirstPacket = true;
|
||||
|
||||
HRESULT get_default_device(IMMDevice **ppMMDevice) {
|
||||
HRESULT hr = S_OK;
|
||||
IMMDeviceEnumerator *pMMDeviceEnumerator;
|
||||
|
||||
// activate a device enumerator
|
||||
hr = CoCreateInstance(
|
||||
__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
|
||||
__uuidof(IMMDeviceEnumerator),
|
||||
(void**)&pMMDeviceEnumerator
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the default render endpoint
|
||||
hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, ppMMDevice);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
#endif /** WASAPI_LOOPBACK */
|
||||
|
||||
bool initLoopback()
|
||||
{
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
HRESULT hr;
|
||||
|
||||
hr = CoInitialize(NULL);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"CoInitialize failed: hr = 0x%08x", hr);
|
||||
}
|
||||
|
||||
|
||||
IMMDevice *pMMDevice(NULL);
|
||||
// open default device if not specified
|
||||
if (NULL == pMMDevice) {
|
||||
hr = get_default_device(&pMMDevice);
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool bInt16 = false;
|
||||
|
||||
// activate an IAudioClient
|
||||
IAudioClient *pAudioClient;
|
||||
hr = pMMDevice->Activate(
|
||||
__uuidof(IAudioClient),
|
||||
CLSCTX_ALL, NULL,
|
||||
(void**)&pAudioClient
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the default device periodicity
|
||||
REFERENCE_TIME hnsDefaultDevicePeriod;
|
||||
hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IAudioClient::GetDevicePeriod failed: hr = 0x%08x", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the default device format
|
||||
WAVEFORMATEX *pwfx;
|
||||
hr = pAudioClient->GetMixFormat(&pwfx);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IAudioClient::GetMixFormat failed: hr = 0x%08x", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bInt16) {
|
||||
// coerce int-16 wave format
|
||||
// can do this in-place since we're not changing the size of the format
|
||||
// also, the engine will auto-convert from float to int for us
|
||||
switch (pwfx->wFormatTag) {
|
||||
case WAVE_FORMAT_IEEE_FLOAT:
|
||||
pwfx->wFormatTag = WAVE_FORMAT_PCM;
|
||||
pwfx->wBitsPerSample = 16;
|
||||
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
|
||||
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
|
||||
break;
|
||||
|
||||
case WAVE_FORMAT_EXTENSIBLE:
|
||||
{
|
||||
// naked scope for case-local variable
|
||||
PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx);
|
||||
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) {
|
||||
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
pEx->Samples.wValidBitsPerSample = 16;
|
||||
pwfx->wBitsPerSample = 16;
|
||||
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
|
||||
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
|
||||
}
|
||||
else {
|
||||
ERR(L"%s", L"Don't know how to coerce mix format to int-16");
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ERR(L"Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16", pwfx->wFormatTag);
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
nBlockAlign = pwfx->nBlockAlign;
|
||||
*pnFrames = 0;
|
||||
|
||||
// call IAudioClient::Initialize
|
||||
// note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK
|
||||
// do not work together...
|
||||
// the "data ready" event never gets set
|
||||
// so we're going to do a timer-driven loop
|
||||
hr = pAudioClient->Initialize(
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_LOOPBACK,
|
||||
0, 0, pwfx, 0
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"pAudioClient->Initialize error");
|
||||
return false;
|
||||
}
|
||||
|
||||
// activate an IAudioCaptureClient
|
||||
hr = pAudioClient->GetService(
|
||||
__uuidof(IAudioCaptureClient),
|
||||
(void**)&pAudioCaptureClient
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"pAudioClient->GetService error");
|
||||
return false;
|
||||
}
|
||||
|
||||
// call IAudioClient::Start
|
||||
hr = pAudioClient->Start();
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"pAudioClient->Start error");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bDone = false;
|
||||
#endif /** WASAPI_LOOPBACK */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void configureLoopback(projectMSDL *app) {
|
||||
#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
|
||||
}
|
||||
|
||||
bool processLoopbackFrame(projectMSDL *app) {
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
HRESULT 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 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 false;
|
||||
}
|
||||
|
||||
bFirstPacket = false;
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif /** WASAPI_LOOPBACK */
|
||||
|
||||
}
|
||||
14
src/projectM-sdl/loopback.hpp
Normal file
14
src/projectM-sdl/loopback.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
#ifndef loopback_hpp
|
||||
#define loopback_hpp
|
||||
|
||||
#include "pmSDL.hpp"
|
||||
|
||||
class projectMSDL;
|
||||
|
||||
bool initLoopback();
|
||||
void configureLoopback(projectMSDL *app);
|
||||
bool processLoopbackFrame(projectMSDL *app);
|
||||
|
||||
#endif /* loopback_hpp */
|
||||
@ -29,163 +29,6 @@
|
||||
*/
|
||||
|
||||
#include "pmSDL.hpp"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include "Renderer/ShaderEngine.hpp"
|
||||
#include "Renderer/StaticGlShaders.h"
|
||||
|
||||
|
||||
void projectMSDL::audioInputCallbackF32(void *userdata, unsigned char *stream, int len) {
|
||||
projectMSDL *app = (projectMSDL *) userdata;
|
||||
// printf("\nLEN: %i\n", len);
|
||||
// for (int i = 0; i < 64; i++)
|
||||
// printf("%X ", stream[i]);
|
||||
// stream is (i think) samples*channels floats (native byte order) of len BYTES
|
||||
if (app->audioChannelsCount == 1)
|
||||
app->pcm()->addPCMfloat((float *)stream, len/sizeof(float));
|
||||
else if (app->audioChannelsCount == 2)
|
||||
app->pcm()->addPCMfloat_2ch((float *)stream, len/sizeof(float));
|
||||
else {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Multichannel audio not supported");
|
||||
SDL_Quit();
|
||||
}
|
||||
}
|
||||
|
||||
void projectMSDL::audioInputCallbackS16(void *userdata, unsigned char *stream, int len) {
|
||||
// printf("LEN: %i\n", len);
|
||||
projectMSDL *app = (projectMSDL *) userdata;
|
||||
short pcm16[2][512];
|
||||
|
||||
for (int i = 0; i < 512; i++) {
|
||||
for (int j = 0; j < app->audioChannelsCount; j++) {
|
||||
pcm16[j][i] = stream[i+j];
|
||||
}
|
||||
}
|
||||
app->pcm()->addPCM16(pcm16);
|
||||
}
|
||||
|
||||
int projectMSDL::toggleAudioInput() {
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
|
||||
int projectMSDL::initAudioInput() {
|
||||
|
||||
|
||||
// params for audio input
|
||||
SDL_AudioSpec want, have;
|
||||
|
||||
// requested format
|
||||
// https://wiki.libsdl.org/SDL_AudioSpec#Remarks
|
||||
SDL_zero(want);
|
||||
want.freq = 44100;
|
||||
want.format = AUDIO_F32; // float
|
||||
want.channels = 2;
|
||||
want.samples = PCM::maxsamples;
|
||||
want.callback = projectMSDL::audioInputCallbackF32;
|
||||
want.userdata = this;
|
||||
|
||||
audioDeviceID = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(selectedAudioDevice, true), true, &want, &have, 0);
|
||||
|
||||
if (audioDeviceID == 0) {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to open audio capture device: %s", SDL_GetError());
|
||||
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 = 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;
|
||||
audioFormat = have.format;
|
||||
audioInputDevice = audioDeviceID;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
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
|
||||
NumAudioDevices = SDL_GetNumAudioDevices(true); // capture, please
|
||||
|
||||
CurAudioDevice = 0;
|
||||
if (NumAudioDevices == 0) {
|
||||
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "No audio capture devices found");
|
||||
projectM::setToastMessage("No audio capture devices found: using simulated audio");
|
||||
fakeAudio = true;
|
||||
return 0;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
for (unsigned int i = 0; i < NumAudioDevices; i++) {
|
||||
SDL_Log("Found audio capture device %d: %s", i, SDL_GetAudioDeviceName(i, true));
|
||||
}
|
||||
#endif
|
||||
|
||||
// default selected Audio Device to 0.
|
||||
selectedAudioDevice = 0;
|
||||
initAudioInput();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void projectMSDL::beginAudioCapture() {
|
||||
// allocate a buffer to store PCM data for feeding in
|
||||
SDL_PauseAudioDevice(audioDeviceID, false);
|
||||
}
|
||||
|
||||
void projectMSDL::endAudioCapture() {
|
||||
SDL_PauseAudioDevice(audioDeviceID, true);
|
||||
SDL_CloseAudioDevice(audioDeviceID);
|
||||
}
|
||||
|
||||
void projectMSDL::setHelpText(const std::string & helpText) {
|
||||
projectM::setHelpText(helpText);
|
||||
@ -568,23 +411,22 @@ void projectMSDL::renderFrame() {
|
||||
SDL_GL_SwapWindow(win);
|
||||
}
|
||||
|
||||
projectMSDL::projectMSDL(Settings settings, int flags) : projectM(settings, flags) {
|
||||
projectMSDL::projectMSDL(SDL_GLContext glCtx, Settings settings, int flags) : projectM(settings, flags), glCtx(glCtx) {
|
||||
width = getWindowWidth();
|
||||
height = getWindowHeight();
|
||||
done = 0;
|
||||
isFullScreen = false;
|
||||
}
|
||||
|
||||
projectMSDL::projectMSDL(std::string config_file, int flags) : projectM(config_file, flags) {
|
||||
projectMSDL::projectMSDL(SDL_GLContext glCtx, std::string config_file, int flags) : projectM(config_file, flags), glCtx(glCtx) {
|
||||
width = getWindowWidth();
|
||||
height = getWindowHeight();
|
||||
done = 0;
|
||||
isFullScreen = false;
|
||||
}
|
||||
|
||||
void projectMSDL::init(SDL_Window *window, SDL_GLContext *_glCtx, const bool _renderToTexture) {
|
||||
void projectMSDL::init(SDL_Window *window, const bool _renderToTexture) {
|
||||
win = window;
|
||||
glCtx = _glCtx;
|
||||
projectM_resetGL(width, height);
|
||||
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
|
||||
@ -39,12 +39,29 @@
|
||||
#define TEST_ALL_PRESETS 0
|
||||
#define STEREOSCOPIC_SBS 0
|
||||
|
||||
// projectM
|
||||
#include "projectM-opengl.h"
|
||||
#include <projectM.hpp>
|
||||
#include "Renderer/ShaderEngine.hpp"
|
||||
#include "Renderer/StaticGlShaders.h"
|
||||
#include <sdltoprojectM.h>
|
||||
|
||||
// projectM SDL
|
||||
#include "setup.hpp"
|
||||
#include "loopback.hpp"
|
||||
#include "audioCapture.hpp"
|
||||
|
||||
|
||||
#if defined _MSC_VER
|
||||
# include <direct.h>
|
||||
#endif
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
#include <stdio.h>
|
||||
@ -60,8 +77,6 @@
|
||||
|
||||
#endif /** WASAPI_LOOPBACK */
|
||||
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include "SDL.h"
|
||||
@ -69,6 +84,7 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#endif /** WIN32 */
|
||||
|
||||
|
||||
// DATADIR_PATH should be set by the root Makefile if this is being
|
||||
// built with autotools.
|
||||
#ifndef DATADIR_PATH
|
||||
@ -84,17 +100,18 @@
|
||||
#endif
|
||||
|
||||
class projectMSDL : public projectM {
|
||||
|
||||
public:
|
||||
|
||||
|
||||
bool done;
|
||||
bool mouseDown = false;
|
||||
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.
|
||||
bool stretch = false; // used for toggling stretch mode
|
||||
projectMSDL(Settings settings, int flags);
|
||||
projectMSDL(std::string config_file, int flags);
|
||||
void init(SDL_Window *window, SDL_GLContext *glCtx, const bool renderToTexture = false);
|
||||
SDL_GLContext glCtx;
|
||||
|
||||
projectMSDL(SDL_GLContext glCtx, Settings settings, int flags);
|
||||
projectMSDL(SDL_GLContext glCtx, std::string config_file, int flags);
|
||||
void init(SDL_Window *window, const bool renderToTexture = false);
|
||||
int openAudioInput();
|
||||
int toggleAudioInput();
|
||||
int initAudioInput();
|
||||
@ -120,7 +137,6 @@ public:
|
||||
|
||||
private:
|
||||
SDL_Window *win;
|
||||
SDL_GLContext *glCtx;
|
||||
bool isFullScreen;
|
||||
SDL_AudioDeviceID audioInputDevice;
|
||||
unsigned int width, height;
|
||||
|
||||
@ -1,568 +1,64 @@
|
||||
/**
|
||||
* projectM -- Milkdrop-esque visualisation SDK
|
||||
* Copyright (C)2003-2019 projectM Team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
* See 'LICENSE.txt' included within this release
|
||||
*
|
||||
* projectM-sdl
|
||||
* This is an implementation of projectM using libSDL2
|
||||
*
|
||||
* main.cpp
|
||||
* Authors: Created by Mischa Spiegelmock on 6/3/15.
|
||||
*
|
||||
*
|
||||
* RobertPancoast77@gmail.com :
|
||||
* experimental Stereoscopic SBS driver functionality
|
||||
* WASAPI looback implementation
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined _MSC_VER
|
||||
# include <direct.h>
|
||||
#endif
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
* projectM -- Milkdrop-esque visualisation SDK
|
||||
* Copyright (C)2003-2021 projectM Team
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
* See 'LICENSE.txt' included within this release
|
||||
*
|
||||
* projectM-sdl
|
||||
* This is an implementation of projectM using libSDL2
|
||||
*
|
||||
* main.cpp
|
||||
* Authors: Created by Mischa Spiegelmock on 6/3/15.
|
||||
*
|
||||
*
|
||||
* RobertPancoast77@gmail.com :
|
||||
* experimental Stereoscopic SBS driver functionality
|
||||
* WASAPI looback implementation
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "pmSDL.hpp"
|
||||
|
||||
#if OGL_DEBUG
|
||||
void DebugLog(GLenum source,
|
||||
GLenum type,
|
||||
GLuint id,
|
||||
GLenum severity,
|
||||
GLsizei length,
|
||||
const GLchar* message,
|
||||
const void* userParam) {
|
||||
|
||||
/*if (type != GL_DEBUG_TYPE_OTHER)*/
|
||||
{
|
||||
std::cerr << " -- \n" << "Type: " <<
|
||||
type << "; Source: " <<
|
||||
source <<"; ID: " << id << "; Severity: " <<
|
||||
severity << "\n" << message << "\n";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// return path to config file to use
|
||||
std::string getConfigFilePath(std::string datadir_path) {
|
||||
char* home = NULL;
|
||||
std::string projectM_home;
|
||||
std::string projectM_config = DATADIR_PATH;
|
||||
|
||||
projectM_config = datadir_path;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
home=getenv("USERPROFILE");
|
||||
#else
|
||||
home=getenv("HOME");
|
||||
#endif
|
||||
static int mainLoop(void *userData) {
|
||||
projectMSDL **appRef = (projectMSDL **)userData;
|
||||
auto app = *appRef;
|
||||
|
||||
projectM_home = std::string(home);
|
||||
projectM_home += "/.projectM";
|
||||
|
||||
// Create the ~/.projectM directory. If it already exists, mkdir will do nothing
|
||||
#if defined _MSC_VER
|
||||
_mkdir(projectM_home.c_str());
|
||||
#else
|
||||
mkdir(projectM_home.c_str(), 0755);
|
||||
#endif
|
||||
|
||||
projectM_home += "/config.inp";
|
||||
projectM_config += "/config.inp";
|
||||
|
||||
std::ifstream f_home(projectM_home);
|
||||
std::ifstream f_config(projectM_config);
|
||||
|
||||
if (f_config.good() && !f_home.good()) {
|
||||
std::ifstream f_src;
|
||||
std::ofstream f_dst;
|
||||
|
||||
f_src.open(projectM_config, std::ios::in | std::ios::binary);
|
||||
f_dst.open(projectM_home, std::ios::out | std::ios::binary);
|
||||
f_dst << f_src.rdbuf();
|
||||
f_dst.close();
|
||||
f_src.close();
|
||||
return std::string(projectM_home);
|
||||
} else if (f_home.good()) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "created ~/.projectM/config.inp successfully\n");
|
||||
return std::string(projectM_home);
|
||||
} else if (f_config.good()) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot create ~/.projectM/config.inp, using default config file\n");
|
||||
return std::string(projectM_config);
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, "Using implementation defaults, your system is really messed up, I'm surprised we even got this far\n");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ref https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
|
||||
HRESULT get_default_device(IMMDevice **ppMMDevice) {
|
||||
HRESULT hr = S_OK;
|
||||
IMMDeviceEnumerator *pMMDeviceEnumerator;
|
||||
|
||||
// activate a device enumerator
|
||||
hr = CoCreateInstance(
|
||||
__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
|
||||
__uuidof(IMMDeviceEnumerator),
|
||||
(void**)&pMMDeviceEnumerator
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
// get the default render endpoint
|
||||
hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, ppMMDevice);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
#endif /** WASAPI_LOOPBACK */
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
#ifndef WIN32
|
||||
srand((int)(time(NULL)));
|
||||
#endif
|
||||
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
HRESULT hr;
|
||||
|
||||
hr = CoInitialize(NULL);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"CoInitialize failed: hr = 0x%08x", hr);
|
||||
}
|
||||
|
||||
|
||||
IMMDevice *pMMDevice(NULL);
|
||||
// open default device if not specified
|
||||
if (NULL == pMMDevice) {
|
||||
hr = get_default_device(&pMMDevice);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
bool bInt16 = false;
|
||||
UINT32 foo = 0;
|
||||
PUINT32 pnFrames = &foo;
|
||||
|
||||
// activate an IAudioClient
|
||||
IAudioClient *pAudioClient;
|
||||
hr = pMMDevice->Activate(
|
||||
__uuidof(IAudioClient),
|
||||
CLSCTX_ALL, NULL,
|
||||
(void**)&pAudioClient
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
// get the default device periodicity
|
||||
REFERENCE_TIME hnsDefaultDevicePeriod;
|
||||
hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IAudioClient::GetDevicePeriod failed: hr = 0x%08x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
// get the default device format
|
||||
WAVEFORMATEX *pwfx;
|
||||
hr = pAudioClient->GetMixFormat(&pwfx);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"IAudioClient::GetMixFormat failed: hr = 0x%08x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
if (bInt16) {
|
||||
// coerce int-16 wave format
|
||||
// can do this in-place since we're not changing the size of the format
|
||||
// also, the engine will auto-convert from float to int for us
|
||||
switch (pwfx->wFormatTag) {
|
||||
case WAVE_FORMAT_IEEE_FLOAT:
|
||||
pwfx->wFormatTag = WAVE_FORMAT_PCM;
|
||||
pwfx->wBitsPerSample = 16;
|
||||
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
|
||||
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
|
||||
break;
|
||||
|
||||
case WAVE_FORMAT_EXTENSIBLE:
|
||||
{
|
||||
// naked scope for case-local variable
|
||||
PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx);
|
||||
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) {
|
||||
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
pEx->Samples.wValidBitsPerSample = 16;
|
||||
pwfx->wBitsPerSample = 16;
|
||||
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
|
||||
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
|
||||
}
|
||||
else {
|
||||
ERR(L"%s", L"Don't know how to coerce mix format to int-16");
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ERR(L"Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16", pwfx->wFormatTag);
|
||||
return E_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 nBlockAlign = pwfx->nBlockAlign;
|
||||
*pnFrames = 0;
|
||||
|
||||
// call IAudioClient::Initialize
|
||||
// note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK
|
||||
// do not work together...
|
||||
// the "data ready" event never gets set
|
||||
// so we're going to do a timer-driven loop
|
||||
hr = pAudioClient->Initialize(
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_LOOPBACK,
|
||||
0, 0, pwfx, 0
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"pAudioClient->Initialize error");
|
||||
return hr;
|
||||
}
|
||||
|
||||
// activate an IAudioCaptureClient
|
||||
IAudioCaptureClient *pAudioCaptureClient;
|
||||
hr = pAudioClient->GetService(
|
||||
__uuidof(IAudioCaptureClient),
|
||||
(void**)&pAudioCaptureClient
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"pAudioClient->GetService error");
|
||||
return hr;
|
||||
}
|
||||
|
||||
// call IAudioClient::Start
|
||||
hr = pAudioClient->Start();
|
||||
if (FAILED(hr)) {
|
||||
ERR(L"pAudioClient->Start error");
|
||||
return hr;
|
||||
}
|
||||
|
||||
bool bDone = false;
|
||||
bool bFirstPacket = true;
|
||||
UINT32 nPasses = 0;
|
||||
|
||||
#endif /** WASAPI_LOOPBACK */
|
||||
|
||||
#if UNLOCK_FPS
|
||||
setenv("vblank_mode", "0", 1);
|
||||
auto start = startUnlockedFPSCounter();
|
||||
#endif
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||
|
||||
if (! SDL_VERSION_ATLEAST(2, 0, 5)) {
|
||||
SDL_Log("SDL version 2.0.5 or greater is required. You have %i.%i.%i", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// default window size to usable bounds (e.g. minus menubar and dock)
|
||||
SDL_Rect initialWindowBounds;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
||||
// new and better
|
||||
SDL_GetDisplayUsableBounds(0, &initialWindowBounds);
|
||||
#else
|
||||
SDL_GetDisplayBounds(0, &initialWindowBounds);
|
||||
#endif
|
||||
int width = initialWindowBounds.w;
|
||||
int height = initialWindowBounds.h;
|
||||
|
||||
#ifdef USE_GLES
|
||||
// use GLES 2.0 (this may need adjusting)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
|
||||
#else
|
||||
// Disabling compatibility profile
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
SDL_Window *win = SDL_CreateWindow("projectM", 0, 0, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
SDL_GL_GetDrawableSize(win,&width,&height);
|
||||
|
||||
#if STEREOSCOPIC_SBS
|
||||
|
||||
// enable stereo
|
||||
if (SDL_GL_SetAttribute(SDL_GL_STEREO, 1) == 0)
|
||||
{
|
||||
SDL_Log("SDL_GL_STEREO: true");
|
||||
}
|
||||
|
||||
// requires fullscreen mode
|
||||
SDL_ShowCursor(false);
|
||||
SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
SDL_GLContext glCtx = SDL_GL_CreateContext(win);
|
||||
|
||||
|
||||
SDL_Log("GL_VERSION: %s", glGetString(GL_VERSION));
|
||||
SDL_Log("GL_SHADING_LANGUAGE_VERSION: %s", glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
SDL_Log("GL_VENDOR: %s", glGetString(GL_VENDOR));
|
||||
|
||||
SDL_SetWindowTitle(win, "projectM Visualizer");
|
||||
|
||||
SDL_GL_MakeCurrent(win, glCtx); // associate GL context with main window
|
||||
int avsync = SDL_GL_SetSwapInterval(-1); // try to enable adaptive vsync
|
||||
if (avsync == -1) { // adaptive vsync not supported
|
||||
SDL_GL_SetSwapInterval(1); // enable updates synchronized with vertical retrace
|
||||
}
|
||||
|
||||
|
||||
projectMSDL *app;
|
||||
|
||||
std::string base_path = DATADIR_PATH;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using data directory: %s\n", base_path.c_str());
|
||||
|
||||
// load configuration file
|
||||
std::string configFilePath = getConfigFilePath(base_path);
|
||||
|
||||
if (! configFilePath.empty()) {
|
||||
// found config file, use it
|
||||
app = new projectMSDL(configFilePath, 0);
|
||||
SDL_Log("Using config from %s", configFilePath.c_str());
|
||||
} else {
|
||||
base_path = SDL_GetBasePath();
|
||||
SDL_Log("Config file not found, using built-in settings. Data directory=%s\n", base_path.c_str());
|
||||
|
||||
// Get max refresh rate from attached displays to use as built-in max FPS.
|
||||
int i = 0;
|
||||
int maxRefreshRate = 0;
|
||||
SDL_DisplayMode current;
|
||||
for (i = 0; i < SDL_GetNumVideoDisplays(); ++i)
|
||||
{
|
||||
if (SDL_GetCurrentDisplayMode(i, ¤t) == 0)
|
||||
{
|
||||
if (current.refresh_rate > maxRefreshRate) maxRefreshRate = current.refresh_rate;
|
||||
}
|
||||
}
|
||||
if (maxRefreshRate <= 60) maxRefreshRate = 60;
|
||||
|
||||
float heightWidthRatio = (float)height / (float)width;
|
||||
projectM::Settings settings;
|
||||
settings.windowWidth = width;
|
||||
settings.windowHeight = height;
|
||||
settings.meshX = 128;
|
||||
settings.meshY = settings.meshX * heightWidthRatio;
|
||||
settings.fps = maxRefreshRate;
|
||||
settings.smoothPresetDuration = 3; // seconds
|
||||
settings.presetDuration = 22; // seconds
|
||||
settings.hardcutEnabled = true;
|
||||
settings.hardcutDuration = 60;
|
||||
settings.hardcutSensitivity = 1.0;
|
||||
settings.beatSensitivity = 1.0;
|
||||
settings.aspectCorrection = 1;
|
||||
settings.shuffleEnabled = 1;
|
||||
settings.softCutRatingsEnabled = 1; // ???
|
||||
// get path to our app, use CWD or resource dir for presets/fonts/etc
|
||||
settings.presetURL = base_path + "presets";
|
||||
// settings.presetURL = base_path + "presets/presets_shader_test";
|
||||
settings.menuFontURL = base_path + "fonts/Vera.ttf";
|
||||
settings.titleFontURL = base_path + "fonts/Vera.ttf";
|
||||
// init with settings
|
||||
app = new projectMSDL(settings, 0);
|
||||
}
|
||||
|
||||
// If our config or hard-coded settings create a resolution smaller than the monitors, then resize the SDL window to match.
|
||||
if (height > app->getWindowHeight() || width > app->getWindowWidth()) {
|
||||
SDL_SetWindowSize(win, app->getWindowWidth(),app->getWindowHeight());
|
||||
SDL_SetWindowPosition(win, (width / 2)-(app->getWindowWidth()/2), (height / 2)-(app->getWindowHeight()/2));
|
||||
} else if (height < app->getWindowHeight() || width < app->getWindowWidth()) {
|
||||
// If our config is larger than our monitors resolution then reduce it.
|
||||
SDL_SetWindowSize(win, width, height);
|
||||
SDL_SetWindowPosition(win, 0, 0);
|
||||
}
|
||||
|
||||
// Create a help menu specific to SDL
|
||||
std::string modKey = "CTRL";
|
||||
|
||||
#if __APPLE__
|
||||
modKey = "CMD";
|
||||
#endif
|
||||
|
||||
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"
|
||||
"N: Next preset""\n"
|
||||
"P: Previous preset""\n"
|
||||
"UP: Increase Beat Sensitivity""\n"
|
||||
"DOWN: Decrease Beat Sensitivity""\n"
|
||||
#ifdef PROJECTM_TOUCH_ENABLED
|
||||
"Left Click: Drop Random Waveform on Screen""\n"
|
||||
"Right Click: Remove Random Waveform""\n" +
|
||||
modKey + "+Right Click: Remove All Random Waveforms""\n"
|
||||
#endif
|
||||
+ modKey + "+I: Audio Input (listen to next device)""\n" +
|
||||
modKey + "+M: Change Monitor""\n" +
|
||||
modKey + "+S: Stretch Monitors""\n" +
|
||||
modKey + "+F: Fullscreen""\n" +
|
||||
modKey + "+Q: Quit";
|
||||
app->setHelpText(sdlHelpMenu.c_str());
|
||||
app->init(win, &glCtx);
|
||||
|
||||
#if STEREOSCOPIC_SBS
|
||||
app->toggleFullScreen();
|
||||
#endif
|
||||
|
||||
#if OGL_DEBUG && !USE_GLES
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
glDebugMessageCallback(DebugLog, NULL);
|
||||
#endif
|
||||
|
||||
#if !FAKE_AUDIO && !WASAPI_LOOPBACK
|
||||
// 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
|
||||
uint buildErrors = 0;
|
||||
for(unsigned int i = 0; i < app->getPlaylistSize(); i++) {
|
||||
std::cout << i << "\t" << app->getPresetName(i) << std::endl;
|
||||
app->selectPreset(i);
|
||||
if (app->getErrorLoadingCurrentPreset()) {
|
||||
buildErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
if (app->getPlaylistSize()) {
|
||||
fprintf(stdout, "Preset loading errors: %d/%d [%d%%]\n", buildErrors, app->getPlaylistSize(), (buildErrors*100) / app->getPlaylistSize());
|
||||
}
|
||||
|
||||
delete app;
|
||||
|
||||
return PROJECTM_SUCCESS;
|
||||
#endif
|
||||
|
||||
#if UNLOCK_FPS
|
||||
int32_t frame_count = 0;
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
|
||||
int64_t start = ( ((int64_t)now.tv_sec * 1000L) + (now.tv_nsec / 1000000L) );
|
||||
#endif
|
||||
|
||||
// standard main loop
|
||||
// frame rate limiter
|
||||
int fps = app->settings().fps;
|
||||
printf("fps: %d\n", fps);
|
||||
if (fps <= 0)
|
||||
fps = 60;
|
||||
const Uint32 frame_delay = 1000/fps;
|
||||
Uint32 last_time = SDL_GetTicks();
|
||||
|
||||
// loop
|
||||
while (! app->done) {
|
||||
// render
|
||||
app->renderFrame();
|
||||
#if FAKE_AUDIO
|
||||
app->fakeAudio = true;
|
||||
#endif
|
||||
// fakeAudio can also be enabled dynamically.
|
||||
if (app->fakeAudio )
|
||||
{
|
||||
app->addFakePCM();
|
||||
}
|
||||
#ifdef WASAPI_LOOPBACK
|
||||
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;
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
#endif /** WASAPI_LOOPBACK */
|
||||
|
||||
|
||||
if (app->fakeAudio)
|
||||
app->addFakePCM();
|
||||
processLoopbackFrame(app);
|
||||
|
||||
#if UNLOCK_FPS
|
||||
frame_count++;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
|
||||
if (( ((int64_t)now.tv_sec * 1000L) + (now.tv_nsec / 1000000L) ) - start > 5000) {
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
delete(app);
|
||||
fprintf(stdout, "Frames[%d]\n", frame_count);
|
||||
exit(0);
|
||||
}
|
||||
advanceUnlockedFPSCounterFrame(start);
|
||||
#else
|
||||
app->pollEvent();
|
||||
Uint32 elapsed = SDL_GetTicks() - last_time;
|
||||
@ -572,19 +68,42 @@ modKey = "CMD";
|
||||
#endif
|
||||
}
|
||||
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
#if !FAKE_AUDIO
|
||||
if (!app->wasapi) // not currently using WASAPI, so we need to endAudioCapture.
|
||||
app->endAudioCapture();
|
||||
#endif
|
||||
|
||||
// Write back config with current app settings (if we loaded from a config file to begin with)
|
||||
if (!configFilePath.empty()) {
|
||||
projectM::writeConfig(configFilePath, app->settings());
|
||||
}
|
||||
delete app;
|
||||
|
||||
return PROJECTM_SUCCESS;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
projectMSDL *app = setupSDLApp();
|
||||
|
||||
int status = mainLoop(&app);
|
||||
|
||||
// SDL_Thread *mainLoopThread;
|
||||
// int threadReturnValue;
|
||||
//
|
||||
// mainLoopThread = SDL_CreateThread(mainLoop, "MainLoop", &app);
|
||||
//
|
||||
// if (NULL == mainLoopThread) {
|
||||
// printf("SDL_CreateThread failed: %s\n", SDL_GetError());
|
||||
// return PROJECTM_ERROR;
|
||||
// } else {
|
||||
// SDL_WaitThread(mainLoopThread, &threadReturnValue);
|
||||
// printf("Thread returned value: %d\n", threadReturnValue);
|
||||
// }
|
||||
|
||||
// Write back config with current app settings (if we loaded from a config file to begin with)
|
||||
std::string configFilePath = getConfigFilePath(DATADIR_PATH);
|
||||
if (!configFilePath.empty()) {
|
||||
projectM::writeConfig(configFilePath, app->settings());
|
||||
}
|
||||
|
||||
// cleanup
|
||||
SDL_GL_DeleteContext(app->glCtx);
|
||||
#if !FAKE_AUDIO
|
||||
if (!app->wasapi) // not currently using WASAPI, so we need to endAudioCapture.
|
||||
app->endAudioCapture();
|
||||
#endif
|
||||
delete app;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
334
src/projectM-sdl/setup.cpp
Normal file
334
src/projectM-sdl/setup.cpp
Normal file
@ -0,0 +1,334 @@
|
||||
#include "setup.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
#if OGL_DEBUG
|
||||
void debugGL(GLenum source,
|
||||
GLenum type,
|
||||
GLuint id,
|
||||
GLenum severity,
|
||||
GLsizei length,
|
||||
const GLchar* message,
|
||||
const void* userParam) {
|
||||
|
||||
/*if (type != GL_DEBUG_TYPE_OTHER)*/
|
||||
{
|
||||
std::cerr << " -- \n" << "Type: " <<
|
||||
type << "; Source: " <<
|
||||
source <<"; ID: " << id << "; Severity: " <<
|
||||
severity << "\n" << message << "\n";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// return path to config file to use
|
||||
std::string getConfigFilePath(std::string datadir_path) {
|
||||
char* home = NULL;
|
||||
std::string projectM_home;
|
||||
std::string projectM_config = DATADIR_PATH;
|
||||
|
||||
projectM_config = datadir_path;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
home=getenv("USERPROFILE");
|
||||
#else
|
||||
home=getenv("HOME");
|
||||
#endif
|
||||
|
||||
projectM_home = std::string(home);
|
||||
projectM_home += "/.projectM";
|
||||
|
||||
// Create the ~/.projectM directory. If it already exists, mkdir will do nothing
|
||||
#if defined _MSC_VER
|
||||
_mkdir(projectM_home.c_str());
|
||||
#else
|
||||
mkdir(projectM_home.c_str(), 0755);
|
||||
#endif
|
||||
|
||||
projectM_home += "/config.inp";
|
||||
projectM_config += "/config.inp";
|
||||
|
||||
std::ifstream f_home(projectM_home);
|
||||
std::ifstream f_config(projectM_config);
|
||||
std::cout << "f_home " << f_home.good() << "\n";
|
||||
|
||||
if (f_config.good() && !f_home.good()) {
|
||||
std::ifstream f_src;
|
||||
std::ofstream f_dst;
|
||||
|
||||
f_src.open(projectM_config, std::ios::in | std::ios::binary);
|
||||
f_dst.open(projectM_home, std::ios::out | std::ios::binary);
|
||||
f_dst << f_src.rdbuf();
|
||||
f_dst.close();
|
||||
f_src.close();
|
||||
return std::string(projectM_home);
|
||||
} else if (f_home.good()) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Read ~/.projectM/config.inp\n");
|
||||
return std::string(projectM_home);
|
||||
} else if (f_config.good()) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot create ~/.projectM/config.inp, using %s\n", projectM_config.c_str());
|
||||
return std::string(projectM_config);
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, "Using implementation defaults, your system is really messed up, I'm surprised we even got this far\n");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void seedRand() {
|
||||
#ifndef WIN32
|
||||
srand((int)(time(NULL)));
|
||||
#endif
|
||||
}
|
||||
|
||||
void initGL() {
|
||||
#ifdef USE_GLES
|
||||
// use GLES 2.0 (this may need adjusting)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
#else
|
||||
// Disabling compatibility profile
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dumpOpenGLInfo() {
|
||||
SDL_Log("- GL_VERSION: %s", glGetString(GL_VERSION));
|
||||
SDL_Log("- GL_SHADING_LANGUAGE_VERSION: %s", glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
SDL_Log("- GL_VENDOR: %s", glGetString(GL_VENDOR));
|
||||
}
|
||||
|
||||
void initStereoscopicView(SDL_Window *win) {
|
||||
#if STEREOSCOPIC_SB
|
||||
// enable stereo
|
||||
if (SDL_GL_SetAttribute(SDL_GL_STEREO, 1) == 0)
|
||||
{
|
||||
SDL_Log("SDL_GL_STEREO: true");
|
||||
}
|
||||
|
||||
// requires fullscreen mode
|
||||
SDL_ShowCursor(false);
|
||||
SDL_SetWindowFullscreen(win, SDL_WINDOW_FULLSCREEN);
|
||||
#endif
|
||||
}
|
||||
|
||||
void enableGLDebugOutput() {
|
||||
#if OGL_DEBUG && !USE_GLES
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||
glDebugMessageCallback(debugGL, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void testAllPresets(projectMSDL *app) {
|
||||
uint buildErrors = 0;
|
||||
for(unsigned int i = 0; i < app->getPlaylistSize(); i++) {
|
||||
std::cout << i << "\t" << app->getPresetName(i) << std::endl;
|
||||
app->selectPreset(i);
|
||||
if (app->getErrorLoadingCurrentPreset()) {
|
||||
buildErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
if (app->getPlaylistSize()) {
|
||||
fprintf(stdout, "Preset loading errors: %d/%d [%d%%]\n", buildErrors, app->getPlaylistSize(), (buildErrors*100) / app->getPlaylistSize());
|
||||
}
|
||||
|
||||
delete app;
|
||||
}
|
||||
|
||||
// initialize SDL, openGL, config
|
||||
projectMSDL *setupSDLApp() {
|
||||
projectMSDL *app;
|
||||
seedRand();
|
||||
|
||||
if (!initLoopback())
|
||||
{
|
||||
SDL_Log("Failed to initialize audio loopback devide.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#if UNLOCK_FPS
|
||||
setenv("vblank_mode", "0", 1);
|
||||
#endif
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||
|
||||
if (! SDL_VERSION_ATLEAST(2, 0, 5)) {
|
||||
SDL_Log("SDL version 2.0.5 or greater is required. You have %i.%i.%i", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// default window size to usable bounds (e.g. minus menubar and dock)
|
||||
SDL_Rect initialWindowBounds;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
||||
// new and better
|
||||
SDL_GetDisplayUsableBounds(0, &initialWindowBounds);
|
||||
#else
|
||||
SDL_GetDisplayBounds(0, &initialWindowBounds);
|
||||
#endif
|
||||
int width = initialWindowBounds.w;
|
||||
int height = initialWindowBounds.h;
|
||||
|
||||
initGL();
|
||||
|
||||
SDL_Window *win = SDL_CreateWindow("projectM", 0, 0, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
SDL_GL_GetDrawableSize(win,&width,&height);
|
||||
|
||||
initStereoscopicView(win);
|
||||
|
||||
SDL_GLContext glCtx = SDL_GL_CreateContext(win);
|
||||
dumpOpenGLInfo();
|
||||
|
||||
SDL_SetWindowTitle(win, "projectM");
|
||||
|
||||
SDL_GL_MakeCurrent(win, glCtx); // associate GL context with main window
|
||||
int avsync = SDL_GL_SetSwapInterval(-1); // try to enable adaptive vsync
|
||||
if (avsync == -1) { // adaptive vsync not supported
|
||||
SDL_GL_SetSwapInterval(1); // enable updates synchronized with vertical retrace
|
||||
}
|
||||
|
||||
std::string base_path = DATADIR_PATH;
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using data directory: %s\n", base_path.c_str());
|
||||
|
||||
// load configuration file
|
||||
std::string configFilePath = getConfigFilePath(base_path);
|
||||
|
||||
if (! configFilePath.empty()) {
|
||||
// found config file, use it
|
||||
app = new projectMSDL(glCtx, configFilePath, 0);
|
||||
SDL_Log("Using config from %s", configFilePath.c_str());
|
||||
} else {
|
||||
// use some sane defaults if config file not found
|
||||
base_path = SDL_GetBasePath();
|
||||
SDL_Log("Config file not found, using built-in settings. Data directory=%s\n", base_path.c_str());
|
||||
|
||||
// Get max refresh rate from attached displays to use as built-in max FPS.
|
||||
int i = 0;
|
||||
int maxRefreshRate = 0;
|
||||
SDL_DisplayMode current;
|
||||
for (i = 0; i < SDL_GetNumVideoDisplays(); ++i)
|
||||
{
|
||||
if (SDL_GetCurrentDisplayMode(i, ¤t) == 0)
|
||||
{
|
||||
if (current.refresh_rate > maxRefreshRate) maxRefreshRate = current.refresh_rate;
|
||||
}
|
||||
}
|
||||
if (maxRefreshRate <= 60) maxRefreshRate = 60;
|
||||
|
||||
float heightWidthRatio = (float)height / (float)width;
|
||||
projectM::Settings settings;
|
||||
settings.windowWidth = width;
|
||||
settings.windowHeight = height;
|
||||
settings.meshX = 128;
|
||||
settings.meshY = settings.meshX * heightWidthRatio;
|
||||
settings.fps = maxRefreshRate;
|
||||
settings.smoothPresetDuration = 3; // seconds
|
||||
settings.presetDuration = 22; // seconds
|
||||
settings.hardcutEnabled = true;
|
||||
settings.hardcutDuration = 60;
|
||||
settings.hardcutSensitivity = 1.0;
|
||||
settings.beatSensitivity = 1.0;
|
||||
settings.aspectCorrection = 1;
|
||||
settings.shuffleEnabled = 1;
|
||||
settings.softCutRatingsEnabled = 1; // ???
|
||||
// get path to our app, use CWD or resource dir for presets/fonts/etc
|
||||
settings.presetURL = base_path + "presets";
|
||||
// settings.presetURL = base_path + "presets/presets_shader_test";
|
||||
settings.menuFontURL = base_path + "fonts/Vera.ttf";
|
||||
settings.titleFontURL = base_path + "fonts/Vera.ttf";
|
||||
// init with settings
|
||||
app = new projectMSDL(glCtx, settings, 0);
|
||||
}
|
||||
|
||||
// center window and full desktop screen
|
||||
SDL_DisplayMode dm;
|
||||
if (SDL_GetDesktopDisplayMode(0, &dm) == 0) {
|
||||
width = dm.w;
|
||||
height = dm.h;
|
||||
} else {
|
||||
SDL_Log("SDL_GetDesktopDisplayMode failed: %s", SDL_GetError());
|
||||
}
|
||||
SDL_SetWindowPosition(win, initialWindowBounds.x, initialWindowBounds.y);
|
||||
SDL_SetWindowSize(win, width, height);
|
||||
app->resize(width, height);
|
||||
|
||||
// Create a help menu specific to SDL
|
||||
std::string modKey = "CTRL";
|
||||
|
||||
#if __APPLE__
|
||||
modKey = "CMD";
|
||||
#endif
|
||||
|
||||
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"
|
||||
"N: Next preset""\n"
|
||||
"P: Previous preset""\n"
|
||||
"UP: Increase Beat Sensitivity""\n"
|
||||
"DOWN: Decrease Beat Sensitivity""\n"
|
||||
#ifdef PROJECTM_TOUCH_ENABLED
|
||||
"Left Click: Drop Random Waveform on Screen""\n"
|
||||
"Right Click: Remove Random Waveform""\n" +
|
||||
modKey + "+Right Click: Remove All Random Waveforms""\n"
|
||||
#endif
|
||||
+ modKey + "+I: Audio Input (listen to next device)""\n" +
|
||||
modKey + "+M: Change Monitor""\n" +
|
||||
modKey + "+S: Stretch Monitors""\n" +
|
||||
modKey + "+F: Fullscreen""\n" +
|
||||
modKey + "+Q: Quit";
|
||||
app->setHelpText(sdlHelpMenu.c_str());
|
||||
app->init(win);
|
||||
|
||||
#if STEREOSCOPIC_SBS
|
||||
app->toggleFullScreen();
|
||||
#endif
|
||||
#if FAKE_AUDIO
|
||||
app->fakeAudio = true;
|
||||
#endif
|
||||
|
||||
enableGLDebugOutput();
|
||||
configureLoopback(app);
|
||||
|
||||
#if !FAKE_AUDIO && !WASAPI_LOOPBACK
|
||||
// get an audio input device
|
||||
if (app->openAudioInput())
|
||||
app->beginAudioCapture();
|
||||
#endif
|
||||
|
||||
#if TEST_ALL_PRESETS
|
||||
testAllPresets(app);
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
int64_t startUnlockedFPSCounter() {
|
||||
using namespace std::chrono;
|
||||
auto currentTime = steady_clock::now();
|
||||
auto currentTimeMs = time_point_cast<milliseconds>(currentTime);
|
||||
auto elapsedMs = currentTime.time_since_epoch();
|
||||
|
||||
return elapsedMs.count();
|
||||
}
|
||||
|
||||
void advanceUnlockedFPSCounterFrame(int64_t startFrame) {
|
||||
static int32_t frameCount = 0;
|
||||
|
||||
frameCount++;
|
||||
auto currentElapsedMs = startUnlockedFPSCounter();
|
||||
if (currentElapsedMs - startFrame > 5000)
|
||||
{
|
||||
printf("Frames[%d]\n", frameCount);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
27
src/projectM-sdl/setup.hpp
Normal file
27
src/projectM-sdl/setup.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef setup_hpp
|
||||
#define setup_hpp
|
||||
|
||||
#include "pmSDL.hpp"
|
||||
|
||||
class projectMSDL;
|
||||
|
||||
void debugGL(GLenum source,
|
||||
GLenum type,
|
||||
GLuint id,
|
||||
GLenum severity,
|
||||
GLsizei length,
|
||||
const GLchar* message,
|
||||
const void* userParam);
|
||||
|
||||
std::string getConfigFilePath(std::string datadir_path);
|
||||
void seedRand();
|
||||
void initGL();
|
||||
void dumpOpenGLInfo();
|
||||
void initStereoscopicView(SDL_Window *win);
|
||||
void enableGLDebugOutput();
|
||||
void testAllPresets(projectMSDL *app);
|
||||
projectMSDL *setupSDLApp();
|
||||
int64_t startUnlockedFPSCounter();
|
||||
void advanceUnlockedFPSCounterFrame(int64_t startFrame);
|
||||
|
||||
#endif /* setup_hpp */
|
||||
Reference in New Issue
Block a user