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