// 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(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ä 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]; } 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); }