mirror of
https://github.com/projectM-visualizer/projectm.git
synced 2026-03-12 18:35:20 +00:00
* There was a bug that existed in open GL ES 2.0 implementations where glTexImage2D could not convert GL_RGB formed textures into GL_RGBA while generating the noise textures. The GL error was GL_INVALID_OPERATION(0x502) This probably made the noise textures blank. So a new class was added that has the alpha channel for the values. * Added a fudge for render farming milkdrop where we control the time and the ticks. * User defined time: Got rid of testing code * Attempt to fix WIN32 compile - it doesnt use or need the ability to control the time * Got rid of debug - got rid of minor warning * projectM.hpp - type in ifdef projectM_SDL_main.cpp - there is code added to make the initial random number dependent on the time of day, making the sequence of visualizers more unexpected. But the posix command time does not exist in Win32 land, so we disable it.
475 lines
13 KiB
C++
475 lines
13 KiB
C++
/**
|
|
* 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
|
|
*
|
|
*
|
|
*/
|
|
|
|
#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) {
|
|
struct stat sb;
|
|
const char *path = datadir_path.c_str();
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Looking for configuration file in data dir: %s.\n", path);
|
|
|
|
// check if datadir exists.
|
|
// it should exist if this application was installed. if not, assume we're developing it and use currect directory
|
|
if (stat(path, &sb) != 0) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, "Could not open datadir path %s\n", path);
|
|
}
|
|
|
|
std::string configFilePath = path;
|
|
configFilePath += "/config.inp";
|
|
|
|
// check if config file exists
|
|
if (stat(configFilePath.c_str(), &sb) != 0) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, "No config file %s found. Using development settings.\n", configFilePath.c_str());
|
|
return "";
|
|
}
|
|
|
|
return configFilePath;
|
|
}
|
|
|
|
|
|
// 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);
|
|
#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, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
|
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);
|
|
|
|
|
|
#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");
|
|
|
|
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());
|
|
|
|
float heightWidthRatio = (float)height / (float)width;
|
|
projectM::Settings settings;
|
|
settings.windowWidth = width;
|
|
settings.windowHeight = height;
|
|
settings.meshX = 400;
|
|
settings.meshY = settings.meshX * heightWidthRatio;
|
|
settings.fps = 60;
|
|
settings.smoothPresetDuration = 3; // seconds
|
|
settings.presetDuration = 22; // seconds
|
|
settings.beatSensitivity = 0.8;
|
|
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);
|
|
}
|
|
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
|
|
|
|
#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
|
|
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();
|
|
while (! app->done) {
|
|
app->renderFrame();
|
|
#if FAKE_AUDIO
|
|
app->addFakePCM();
|
|
#endif
|
|
#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;
|
|
}
|
|
|
|
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
|
|
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);
|
|
}
|
|
#else
|
|
app->pollEvent();
|
|
Uint32 elapsed = SDL_GetTicks() - last_time;
|
|
if (elapsed < frame_delay)
|
|
SDL_Delay(frame_delay - elapsed);
|
|
last_time = SDL_GetTicks();
|
|
#endif
|
|
}
|
|
|
|
SDL_GL_DeleteContext(glCtx);
|
|
#if !FAKE_AUDIO && !WASAPI_LOOPBACK
|
|
app->endAudioCapture();
|
|
#endif
|
|
delete app;
|
|
|
|
return PROJECTM_SUCCESS;
|
|
}
|
|
|
|
|