Lots of improvements. Better UI, time scrolling, scenario system

This commit is contained in:
max
2026-05-03 11:21:24 +02:00
parent c427c1f7d3
commit 7dfc8fa2d2
4 changed files with 340 additions and 97 deletions

View File

@@ -9,61 +9,172 @@ namespace FluidSim;
public class Program
{
private const int SampleRate = 44100;
private const double DrawFrequency = 60.0;
private static Scenario scenario;
// Speed control
private static double desiredSpeed = 1.0;
private static double currentSpeed = desiredSpeed;
private const double MinSpeed = 0.0001;
private const double MaxSpeed = 1.0;
private const double ScrollFactor = 1.1;
// Spacetoggle state
private static double lastDesiredSpeed = 0.1; // remembers the last non1.0 scroll speed
private static bool isRealTime = true; // true when desiredSpeed == 1.0
private static volatile bool running = true;
public static void Main()
{
var mode = new VideoMode(new Vector2u(1280, 720));
var window = new RenderWindow(mode, "Fluid Simulation");
var window = new RenderWindow(mode, "Pipe Resonator");
window.SetVerticalSyncEnabled(true);
window.Closed += (_, _) => { running = false; window.Close(); };
window.MouseWheelScrolled += OnMouseWheel;
window.KeyPressed += OnKeyPressed;
var soundEngine = new SoundEngine(bufferCapacity: 2048);
var soundEngine = new SoundEngine(bufferCapacity: 16384);
soundEngine.Volume = 70;
soundEngine.Start();
double lastAudioTime = 0.0;
scenario = new PipeResonatorScenario();
scenario.Initialize(SampleRate);
var stopwatch = Stopwatch.StartNew();
double lastDrawTime = 0.0;
double drawInterval = 1.0 / DrawFrequency;
double lastSpeedUpdateTime = stopwatch.Elapsed.TotalSeconds;
int warmupSamples = SampleRate / 2;
float[] warmup = new float[warmupSamples];
for (int i = 0; i < warmupSamples; i++)
warmup[i] = 0;
// Resampling buffer
List<float> simBuffer = new List<float>(4096);
double readIndex = 0.0;
soundEngine.WriteSamples(warmup, warmupSamples);
lastAudioTime = stopwatch.Elapsed.TotalSeconds;
for (int i = 0; i < 4; i++)
simBuffer.Add(scenario.Process());
const int chunkSize = 2048;
float[] buffer = new float[chunkSize];
long totalSimSteps = simBuffer.Count;
long totalOutputSamples = 0;
Simulation.Initialize(SampleRate);
double lastRealTime = stopwatch.Elapsed.TotalSeconds;
const int outputChunk = 256;
float[] outputBuf = new float[outputChunk];
while (window.IsOpen)
{
window.DispatchEvents();
double currentTime = stopwatch.Elapsed.TotalSeconds;
double elapsed = currentTime - lastAudioTime;
int samplesNeeded = (int)(elapsed * SampleRate);
double currentRealTime = stopwatch.Elapsed.TotalSeconds;
double dtSpeed = currentRealTime - lastSpeedUpdateTime;
lastSpeedUpdateTime = currentRealTime;
while (samplesNeeded > 0 && running)
// Smoothly transition currentSpeed → desiredSpeed
// When toggling, desiredSpeed jumps, but currentSpeed follows with a smooth lerp
double smoothingRate = 8.0; // higher = faster catchup
currentSpeed += (desiredSpeed - currentSpeed) * (1.0 - Math.Exp(-smoothingRate * dtSpeed));
// ---------- Generate audio ----------
double targetAudioClock = currentRealTime + 0.05;
while (totalOutputSamples < targetAudioClock * SampleRate && running)
{
int toGenerate = Math.Min(samplesNeeded, chunkSize);
int toGenerate = (int)Math.Min(
(long)outputChunk,
(long)(targetAudioClock * SampleRate) - totalOutputSamples
);
if (toGenerate <= 0) break;
double maxIndex = readIndex + (toGenerate - 1) * currentSpeed + 2;
int requiredSimIndex = (int)Math.Ceiling(maxIndex);
while (simBuffer.Count - 1 < requiredSimIndex)
{
simBuffer.Add(scenario.Process());
totalSimSteps++;
}
for (int i = 0; i < toGenerate; i++)
{
buffer[i] = Simulation.Process();
int i0 = (int)readIndex;
int i1 = i0 + 1;
double frac = readIndex - i0;
float y0 = simBuffer[Math.Clamp(i0, 0, simBuffer.Count - 1)];
float y1 = simBuffer[Math.Clamp(i1, 0, simBuffer.Count - 1)];
outputBuf[i] = (float)(y0 + (y1 - y0) * frac);
readIndex += currentSpeed;
while (readIndex >= 1.0 && simBuffer.Count > 2)
{
simBuffer.RemoveAt(0);
readIndex -= 1.0;
}
}
soundEngine.WriteSamples(buffer, toGenerate);
samplesNeeded -= toGenerate;
int accepted = soundEngine.WriteSamples(outputBuf, toGenerate);
totalOutputSamples += accepted;
if (accepted < toGenerate)
break;
}
lastAudioTime = currentTime;
// ---------- Drawing & title ----------
if (currentRealTime - lastDrawTime >= drawInterval)
{
double actualSpeed = totalOutputSamples / (currentRealTime * SampleRate);
double simTime = totalSimSteps / (double)SampleRate;
string toggleHint = isRealTime ? "[Space] slow mo" : "[Space] real time";
window.SetTitle(
$"{toggleHint} Sim: {simTime:F2}s | " +
$"Speed: {currentSpeed:F4}x → {desiredSpeed:F4}x | " +
$"Actual: {actualSpeed:F2}x"
);
window.Clear(Color.Black);
window.Display();
window.Clear(Color.Black);
scenario.Draw(window);
window.Display();
lastDrawTime = currentRealTime;
}
}
soundEngine.Dispose();
window.Dispose();
}
private static void OnMouseWheel(object? sender, MouseWheelScrollEventArgs e)
{
bool wasRealTime = Math.Abs(desiredSpeed - 1.0) < 1e-6;
if (e.Delta > 0)
desiredSpeed *= ScrollFactor;
else if (e.Delta < 0)
desiredSpeed /= ScrollFactor;
desiredSpeed = Math.Clamp(desiredSpeed, MinSpeed, MaxSpeed);
// Update the remembered slow-mo speed (unless we are exactly at 1.0)
if (!wasRealTime || Math.Abs(desiredSpeed - 1.0) > 1e-6)
lastDesiredSpeed = desiredSpeed;
// Update isRealTime flag
isRealTime = Math.Abs(desiredSpeed - 1.0) < 1e-6;
}
private static void OnKeyPressed(object? sender, KeyEventArgs e)
{
if (e.Code == Keyboard.Key.Space)
{
if (isRealTime)
{
// Switch to the remembered slow speed
desiredSpeed = lastDesiredSpeed;
}
else
{
// Switch back to real time
desiredSpeed = 1.0;
}
isRealTime = !isRealTime;
}
}
}