252 lines
7.9 KiB
C++
252 lines
7.9 KiB
C++
// custom build and feature flags
|
|
#ifdef DEBUG
|
|
#define OPENGL_DEBUG 0
|
|
#define FULLSCREEN 0
|
|
#define DESPERATE 0
|
|
#define BREAK_COMPATIBILITY 0
|
|
#else
|
|
#define OPENGL_DEBUG 0
|
|
#define FULLSCREEN 0
|
|
#define DESPERATE 0
|
|
#define BREAK_COMPATIBILITY 0
|
|
#endif
|
|
|
|
#define POST_PASS 0
|
|
#define USE_MIPMAPS 1
|
|
#define USE_AUDIO 1
|
|
#define NO_UNIFORMS 0
|
|
|
|
#include "definitions.h"
|
|
#if OPENGL_DEBUG
|
|
#include "debug.h"
|
|
#endif
|
|
|
|
#include "glext.h"
|
|
#include "fft.h"
|
|
|
|
#pragma data_seg(".shader")
|
|
#include "shaders/fragment.inl"
|
|
#if POST_PASS
|
|
#pragma data_seg(".shader")
|
|
#include "shaders/post.inl"
|
|
#endif
|
|
|
|
#pragma data_seg(".pids")
|
|
// static allocation saves a few bytes
|
|
static int pidMain;
|
|
static int pidPost;
|
|
// static HDC hDC;
|
|
|
|
#ifndef EDITOR_CONTROLS
|
|
#pragma code_seg(".main")
|
|
// FFT buffers
|
|
static float fft_input[FFT_SIZE];
|
|
static float fft_output[FFT_SIZE / 2];
|
|
static float fft_uniform[FFT_SIZE / 4];
|
|
|
|
void entrypoint(void)
|
|
#else
|
|
#include "editor.h"
|
|
|
|
int __cdecl main(int argc, char* argv[])
|
|
#endif
|
|
|
|
{
|
|
// initialize window
|
|
#if FULLSCREEN
|
|
ChangeDisplaySettings(&screenSettings, CDS_FULLSCREEN);
|
|
ShowCursor(0);
|
|
HWND window = CreateWindow((LPCSTR)0xC018, 0, WS_POPUP | WS_VISIBLE | WS_MAXIMIZE, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
const HDC hDC = GetDC(window);
|
|
#else
|
|
#ifdef EDITOR_CONTROLS
|
|
HWND window = CreateWindow("static", 0, WS_POPUP | WS_VISIBLE, 0, 0, XRES, YRES, 0, 0, 0, 0);
|
|
HDC hDC = GetDC(window);
|
|
#else
|
|
// you can create a pseudo fullscreen window by similarly enabling the WS_MAXIMIZE flag as above
|
|
// in which case you can replace the resolution parameters with 0s and save a couple bytes
|
|
// this only works if the resolution is set to the display device's native resolution
|
|
HWND window = CreateWindow((LPCSTR)0xC018, 0, WS_POPUP | WS_VISIBLE, 0, 0, XRES, YRES, 0, 0, 0, 0);
|
|
HDC hDC = GetDC(window);
|
|
#endif
|
|
#endif
|
|
|
|
// initalize opengl context
|
|
SetPixelFormat(hDC, ChoosePixelFormat(hDC, &pfd), &pfd);
|
|
wglMakeCurrent(hDC, wglCreateContext(hDC));
|
|
|
|
// create and compile shader programs
|
|
pidMain = ((PFNGLCREATESHADERPROGRAMVPROC)wglGetProcAddress("glCreateShaderProgramv"))(GL_FRAGMENT_SHADER, 1, &fragment_frag);
|
|
#if POST_PASS
|
|
pidPost = ((PFNGLCREATESHADERPROGRAMVPROC)wglGetProcAddress("glCreateShaderProgramv"))(GL_FRAGMENT_SHADER, 1, &post_frag);
|
|
#endif
|
|
|
|
#ifdef SU_LOAD_GMDLS
|
|
su_load_gmdls();
|
|
#endif // SU_LOAD_GMDLS
|
|
// initialize sound
|
|
#ifndef EDITOR_CONTROLS
|
|
#if USE_AUDIO
|
|
LPDIRECTSOUND direct_sound;
|
|
LPDIRECTSOUNDBUFFER direct_sound_buffer;
|
|
DirectSoundCreate(0, &direct_sound, 0);
|
|
|
|
IDirectSound_SetCooperativeLevel(direct_sound, window, DSSCL_PRIORITY);
|
|
IDirectSound_CreateSoundBuffer(direct_sound, &buffer_description, &direct_sound_buffer, NULL);
|
|
|
|
LPVOID p1;
|
|
DWORD l1;
|
|
IDirectSoundBuffer_Lock(direct_sound_buffer, 0, SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE, &p1, &l1, NULL, NULL, 0);
|
|
|
|
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)su_render_song, p1, 0, 0);
|
|
|
|
#endif
|
|
|
|
#else
|
|
Leviathan::Editor editor = Leviathan::Editor();
|
|
editor.updateShaders(&pidMain, &pidPost, true);
|
|
|
|
// absolute path always works here
|
|
// relative path works only when not ran from visual studio directly
|
|
Leviathan::Song track(L"audio.wav");
|
|
track.play();
|
|
double position = 0.0;
|
|
#endif
|
|
static float syncs[1 + SU_NUMSYNCS];
|
|
long playCursor = 0;
|
|
long lastPlayCursor = -1;
|
|
volatile float maximum = 0.0; // Helper variable to calculate maximum fft output for normalization
|
|
|
|
// Unlock buffer for next use
|
|
IDirectSoundBuffer_Unlock(direct_sound_buffer, p1, SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE, NULL, NULL);
|
|
|
|
// Play sound
|
|
direct_sound_buffer->Play(0, 0, 0);
|
|
|
|
// Init FFT
|
|
init_hamming_window();
|
|
|
|
PFNGLUNIFORM1FVPROC glUniform1fvProc = ((PFNGLUNIFORM1FVPROC)wglGetProcAddress("glUniform1fv"));
|
|
|
|
const ULONGLONG targetIntervalMs = 1000 / 60; // For 60 FPS FFT updates
|
|
|
|
do
|
|
{
|
|
static ULONGLONG lastFFTTime = 0;
|
|
ULONGLONG currentTime = GetTickCount64();
|
|
|
|
direct_sound_buffer->GetCurrentPosition((DWORD*)&playCursor, NULL);
|
|
|
|
#if !(DESPERATE)
|
|
// do minimal message handling so windows doesn't kill your application
|
|
// not always strictly necessary but increases compatibility and reliability a lot
|
|
// normally you'd pass an msg struct as the first argument but it's just an
|
|
// output parameter and the implementation presumably does a NULL check
|
|
PeekMessage(0, 0, 0, 0, PM_REMOVE);
|
|
#endif
|
|
|
|
// render with the primary shader
|
|
((PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram"))(pidMain);
|
|
#ifndef EDITOR_CONTROLS
|
|
// if you don't have an audio system figure some other way to pass time to your shader
|
|
#if USE_AUDIO
|
|
|
|
// it is possible to upload your vars as vertex color attribute (gl_Color) to save one function import
|
|
#if NO_UNIFORMS
|
|
glColor3ui(MMTime.u.sample, 0, 0);
|
|
#else
|
|
// remember to divide your shader time variable with the SAMPLE_RATE (44100 with 4klang)
|
|
//((PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i"))(0, MMTime.u.sample);
|
|
#endif
|
|
#endif
|
|
#else
|
|
position = track.getTime();
|
|
((PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i"))(0, (static_cast<int>(position*44100.0)));
|
|
|
|
#endif
|
|
|
|
if (currentTime - lastFFTTime >= targetIntervalMs) {
|
|
lastFFTTime = currentTime;
|
|
/******************
|
|
* FFT
|
|
*******************/
|
|
LPVOID audio_ptr = NULL;
|
|
DWORD audio_size = 0;
|
|
|
|
// Read audio
|
|
HRESULT hr = IDirectSoundBuffer_Lock(direct_sound_buffer, 0, FFT_SIZE * sizeof(SUsample), &audio_ptr, &audio_size, NULL, NULL, DSBLOCK_FROMWRITECURSOR);
|
|
|
|
if (SUCCEEDED(hr) && audio_ptr) {
|
|
if (playCursor < ((SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE) - (FFT_SIZE * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE)))
|
|
{
|
|
SUsample* samples = (SUsample*)audio_ptr;
|
|
for (int i = 0; i < FFT_SIZE; ++i) {
|
|
fft_input[i] = (float)samples[i];
|
|
}
|
|
}
|
|
|
|
IDirectSoundBuffer_Unlock(direct_sound_buffer, audio_ptr, audio_size, NULL, 0);
|
|
}
|
|
|
|
// Calculate FFT
|
|
compute_fft(fft_input, fft_output);
|
|
|
|
// Normalize output
|
|
for (int i = 0; i < (FFT_SIZE / 4); i++)
|
|
{
|
|
float gain = 50.0f;
|
|
float alpha = 0.10f; // "Hidastaa" FFT:n piikkejä
|
|
float threshhold = 0.05f; // Alin arvo mikä päästetään shaderille (vähentää "noisea")
|
|
float x_t = fft_output[i] * gain;
|
|
// Exponential smoothing kaava
|
|
// s(t) = alpha*x(t)+(1-alpha)*s(t-1)
|
|
fft_uniform[i] = (x_t < threshhold) ? 0.f : alpha * (x_t)+(1 - alpha) * fft_uniform[i];
|
|
}
|
|
}
|
|
|
|
syncs[0] = (float)playCursor / (SU_SAMPLE_RATE * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE); // Aika sekunteina.
|
|
|
|
for (int i = 0; i < SU_NUMSYNCS; ++i)
|
|
{
|
|
syncs[i + 1] = syncBuf[(playCursor / (2 * sizeof(SUsample)) >> 8) * SU_NUMSYNCS + i];
|
|
}
|
|
glUniform1fvProc(0, SU_NUMSYNCS + 1, syncs);
|
|
glUniform1fvProc(20, FFT_SIZE / 4, fft_uniform);
|
|
|
|
glRects(-1, -1, 1, 1);
|
|
|
|
// render "post process" using the opengl backbuffer
|
|
#if POST_PASS
|
|
glBindTexture(GL_TEXTURE_2D, 1);
|
|
#if USE_MIPMAPS
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, XRES, YRES, 0);
|
|
((PFNGLGENERATEMIPMAPPROC)wglGetProcAddress("glGenerateMipmap"))(GL_TEXTURE_2D);
|
|
#else
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, XRES, YRES, 0);
|
|
#endif
|
|
((PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture"))(GL_TEXTURE0);
|
|
((PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram"))(pidPost);
|
|
((PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i"))(0, 0);
|
|
glRects(-1, -1, 1, 1);
|
|
#endif
|
|
|
|
SwapBuffers(hDC);
|
|
|
|
// handle functionality of the editor
|
|
#ifdef EDITOR_CONTROLS
|
|
editor.endFrame(timeGetTime());
|
|
position = editor.handleEvents(&track, position);
|
|
editor.printFrameStatistics();
|
|
editor.updateShaders(&pidMain, &pidPost);
|
|
#endif
|
|
if (playCursor < lastPlayCursor) {
|
|
break;
|
|
}
|
|
lastPlayCursor = playCursor;
|
|
} while(!GetAsyncKeyState(VK_ESCAPE));
|
|
|
|
ExitProcess(0);
|
|
}
|