diff --git a/leviathan.vcxproj b/leviathan.vcxproj index 9cfaf55..e15b617 100644 --- a/leviathan.vcxproj +++ b/leviathan.vcxproj @@ -108,7 +108,7 @@ $(SolutionDir);$(ExecutablePath) leviathan-release - true + false out\ @@ -146,7 +146,7 @@ MinSpace ../;%(AdditionalIncludeDirectories) - EDITOR_CONTROLS;_DEBUG;WINDOWS;DEBUG;SIMD;A32BITS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _DEBUG;WINDOWS;DEBUG;SIMD;A32BITS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) Default MultiThreaded @@ -167,9 +167,11 @@ Sync false StdCall - false + true + NoExtensions + Fast _DEBUG;%(PreprocessorDefinitions) @@ -333,7 +335,7 @@ shader_minifier.exe -v -o src\shaders\post.inl src\shaders\post.frag false false Default - NotSet + NoExtensions true false ProgramDatabase @@ -585,6 +587,7 @@ shader_minifier.exe -v -o src\shaders\post.inl src\shaders\post.frag true true true + true false @@ -592,6 +595,7 @@ shader_minifier.exe -v -o src\shaders\post.inl src\shaders\post.frag false + @@ -605,10 +609,10 @@ shader_minifier.exe -v -o src\shaders\post.inl src\shaders\post.frag - true + true true - true + false diff --git a/leviathan.vcxproj.filters b/leviathan.vcxproj.filters index 0576a43..1a02fb0 100644 --- a/leviathan.vcxproj.filters +++ b/leviathan.vcxproj.filters @@ -4,6 +4,7 @@ + @@ -16,6 +17,8 @@ + + diff --git a/song.yml b/song.yml index daacf9a..a901ad2 100644 --- a/song.yml +++ b/song.yml @@ -27,7 +27,7 @@ score: order: [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1] patterns: [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 81, 83, 84, 86], [88, 83, 84, 81, 88, 83, 84, 81, 88, 83, 84, 81, 88, 83, 84, 81], [91, 83, 84, 81, 91, 83, 84, 81, 91, 83, 84, 81, 91, 83, 84, 81], [93, 83, 84, 81, 93, 83, 84, 81, 93, 83, 84, 81, 93, 83, 84, 81], [89, 81, 83, 84, 86, 88, 89, 91, 89, 81, 83, 84, 83, 84, 86, 88], [89, 79, 81, 83, 81, 83, 84, 86, 84, 86, 88, 89, 88, 84, 86, 83], [91, 88, 89, 86, 88, 84, 86, 83, 84, 81, 83, 79, 81, 77, 79, 76], [77, 74, 76, 72, 74, 71, 72, 69, 71, 67, 69, 65, 67, 69, 71, 74], [77, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 76, 1, 77, 76], [74, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 69, 71, 72, 76], [84, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 83, 1, 1, 1], [81, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 79, 1, 1, 1], [81, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] rowsperpattern: 16 - length: 12 + length: 108 patch: - name: 80s lead numvoices: 2 diff --git a/src/fft.cpp b/src/fft.cpp new file mode 100644 index 0000000..7f66f45 --- /dev/null +++ b/src/fft.cpp @@ -0,0 +1,74 @@ +#include "fft.h" +#include + +static float window[FFT_SIZE]; + +// Call once before use +void init_hamming_window() { + for (int i = 0; i < FFT_SIZE; i++) { + window[i] = 0.54f - 0.46f * cosf(2.0f * (float)M_PI * i / (FFT_SIZE - 1)); + } +} + +static unsigned int bit_reverse(unsigned int x, int log2n) { + unsigned int n = 0; + for (int i = 0; i < log2n; i++) { + n <<= 1; + n |= (x & 1); + x >>= 1; + } + return n; +} + +void compute_fft(float* time_data, float* freq_out) { + static float real[FFT_SIZE]; + static float imag[FFT_SIZE]; + + int log2n = 0; + for (int t = FFT_SIZE; t > 1; t >>= 1) ++log2n; + + // Apply Hamming window + for (int i = 0; i < FFT_SIZE; i++) { + real[i] = time_data[i] * window[i]; + imag[i] = 0.0f; + } + + // Bit reversal + for (int i = 0; i < FFT_SIZE; ++i) { + int j = bit_reverse(i, log2n); + if (j > i) { + float tmp_re = real[i], tmp_im = imag[i]; + real[i] = real[j]; imag[i] = imag[j]; + real[j] = tmp_re; imag[j] = tmp_im; + } + } + + // Cooley-Tukey FFT + for (int s = 1; s <= log2n; ++s) { + int m = 1 << s; + for (int k = 0; k < FFT_SIZE; k += m) { + for (int j = 0; j < m / 2; ++j) { + int t = k + j; + int u = t + m / 2; + + float angle = -2.0f * (float)M_PI * j / m; + float w_real = cosf(angle); + float w_imag = sinf(angle); + + float re = w_real * real[u] - w_imag * imag[u]; + float im = w_real * imag[u] + w_imag * real[u]; + + real[u] = real[t] - re; + imag[u] = imag[t] - im; + real[t] += re; + imag[t] += im; + } + } + } + + for (int i = 0; i < FFT_SIZE / 2; ++i) { + float mag = sqrtf(real[i] * real[i] + imag[i] * imag[i]) / FFT_SIZE; + + freq_out[i] = mag; + } +} diff --git a/src/fft.h b/src/fft.h new file mode 100644 index 0000000..9c74bb7 --- /dev/null +++ b/src/fft.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include + + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define FFT_SIZE 4096 +void init_hamming_window(); +void compute_fft(float* time_data, float* freq_out); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f7ec04e..9052d3b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ // custom build and feature flags #ifdef DEBUG - #define OPENGL_DEBUG 1 + #define OPENGL_DEBUG 0 #define FULLSCREEN 0 #define DESPERATE 0 #define BREAK_COMPATIBILITY 0 @@ -29,6 +29,7 @@ #include "shaders/post.inl" #endif +#include "fft.h" #pragma data_seg(".pids") // static allocation saves a few bytes @@ -74,13 +75,13 @@ int __cdecl main(int argc, char* argv[]) #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); @@ -91,6 +92,7 @@ int __cdecl main(int argc, char* argv[]) 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 @@ -108,13 +110,26 @@ int __cdecl main(int argc, char* argv[]) long playCursor = 0; long lastPlayCursor = -1; - // main loop + 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); - float syncs[1 + SU_NUMSYNCS]; #if !(DESPERATE) // do minimal message handling so windows doesn't kill your application @@ -144,7 +159,39 @@ int __cdecl main(int argc, char* argv[]) #endif - syncs[0] = (float)playCursor / (2 * sizeof(SUsample)); + /****************** + * 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) { + 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. + { + fft_uniform[i] = fft_output[i]; + } + } + + syncs[0] = (float)playCursor / (2 * sizeof(SUsample)); for (int i = 0; i < SU_NUMSYNCS; ++i) { @@ -153,6 +200,7 @@ int __cdecl main(int argc, char* argv[]) PFNGLUNIFORM1FVPROC glUniform1fvProc = ((PFNGLUNIFORM1FVPROC)wglGetProcAddress("glUniform1fv")); glUniform1fvProc(0, SU_NUMSYNCS + 1, syncs); + glUniform1fvProc(8, FFT_SIZE / 4, fft_uniform); glRects(-1, -1, 1, 1); diff --git a/src/shaders/fragment.frag b/src/shaders/fragment.frag index 6f0865c..7f92b95 100644 --- a/src/shaders/fragment.frag +++ b/src/shaders/fragment.frag @@ -3,6 +3,7 @@ precision mediump float; out vec4 o; const float PI = 22./7.; layout(location = 0) uniform float syncs[7]; +layout(location = 8) uniform float fft_output[512]; // FFT_SIZE / 4 float u_time = syncs[0]; vec3 render(vec2 uv, float time) { @@ -23,5 +24,18 @@ vec3 render(vec2 uv, float time) { void main() { vec2 uv = gl_FragCoord.xy * 2. / vec2(1920,1080); vec3 col = render(uv, u_time); - o = vec4(col,1.0); + + // Determine FFT bin index for current x position + int index = int(floor(uv.x*128.0)); + index = clamp(index, 0, 255); + + // Get the FFT energy (clamped to avoid NaNs or overflow) + float energy = clamp(fft_output[index], .0, 1.0); + + float bar_height = energy; + float fade = smoothstep(bar_height, bar_height + 0.02, 1.0 - uv.y); + + vec3 color = vec3(fade); + + o = vec4(color, 1.0); } diff --git a/src/shaders/fragment.inl b/src/shaders/fragment.inl index 23b7705..409ee35 100644 --- a/src/shaders/fragment.inl +++ b/src/shaders/fragment.inl @@ -1,6 +1,7 @@ // Generated with Shader Minifier 1.5.1 (https://github.com/laurentlb/Shader_Minifier/) #ifndef FRAGMENT_INL_ # define FRAGMENT_INL_ +# define VAR_fft_output "l" # define VAR_o "f" # define VAR_syncs "v" @@ -10,20 +11,13 @@ const char *fragment_frag = "out vec4 f;" "const float m=22./7.;" "layout(location=0)uniform float v[7];" + "layout(location=8)uniform float l[512];" "float n=v[0];" - "vec3 s(vec2 f)" - "{" - "float m=v[4];" - "return f.x>=m&&f.x<=m+.01?" - "vec3(1):" - "f.x>=0.&&f.x<=.01?" - "vec3(abs(v[3]*2)):" - "vec3(.1,.2,.3)*abs(v[1]*1.2);" - "}" "void main()" "{" "vec2 m=gl_FragCoord.xy*2./vec2(1920,1080);" - "f=vec4(s(m),1);" + "float n=clamp(l[clamp(int(floor(m.x*128.)),0,255)],0.,1.);" + "f=vec4(vec3(smoothstep(n,n+.02,1.-m.y)),1);" "}"; #endif // FRAGMENT_INL_ diff --git a/src/sointu/song.h b/src/sointu/song.h index bb8a206..3518e73 100644 --- a/src/sointu/song.h +++ b/src/sointu/song.h @@ -3,14 +3,14 @@ #define SU_RENDER_H #define SU_CHANNEL_COUNT 2 -#define SU_LENGTH_IN_SAMPLES 920256 +#define SU_LENGTH_IN_SAMPLES 8282304 #define SU_BUFFER_LENGTH (SU_LENGTH_IN_SAMPLES*SU_CHANNEL_COUNT) #define SU_SAMPLE_RATE 44100 #define SU_BPM 138 #define SU_ROWS_PER_BEAT 4 #define SU_ROWS_PER_PATTERN 16 -#define SU_LENGTH_IN_PATTERNS 12 +#define SU_LENGTH_IN_PATTERNS 108 #define SU_LENGTH_IN_ROWS (SU_LENGTH_IN_PATTERNS*SU_PATTERN_SIZE) #define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*60/(SU_BPM*SU_ROWS_PER_BEAT)) #define SU_NUMSYNCS 4