Files
4k_synthwave/src/main.cpp

253 lines
7.9 KiB
C++
Raw Blame History

// 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"
#pragma data_seg(".shader")
#include "shaders/fragment.inl"
#if POST_PASS
#pragma data_seg(".shader")
#include "shaders/post.inl"
#endif
#include "fft.h"
#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")
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
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);
static float syncs[1 + SU_NUMSYNCS];
// Init FFT
init_hamming_window();
// FFT buffers
static float fft_input[FFT_SIZE];
static float fft_output[FFT_SIZE / 2]; // Magnitudes
static float fft_uniform[FFT_SIZE / 4];
do
{
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
/******************
* 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 / 2); i++)
{
//maximum = max(fft_output[i], maximum);
//fft_output[i] = fft_output[i] / maximum;
if (i < (FFT_SIZE / 4)) // Limit uniform fft size. High frequencys contain nothing interesing.
{
float gain = 50.0f;
float alpha = 0.15f; // "Hidastaa" FFT:n piikkej<65>
float threshhold = 0.05f; // Alin arvo mik<69> p<><70>stet<65><74>n shaderille (v<>hent<6E><74> "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];
}
PFNGLUNIFORM1FVPROC glUniform1fvProc = ((PFNGLUNIFORM1FVPROC)wglGetProcAddress("glUniform1fv"));
glUniform1fvProc(0, SU_NUMSYNCS + 1, syncs);
glUniform1fvProc(8, FFT_SIZE / 4, fft_uniform);
glRects(-1, -1, 1, 1);
//syncs[0] = -syncs[0];
//glUniform1fvProc(0, SU_NUMSYNCS + 1, syncs);
// 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);
}