Lots of improvements. Better UI, time scrolling, scenario system
This commit is contained in:
@@ -1,74 +0,0 @@
|
|||||||
using System;
|
|
||||||
using FluidSim.Components;
|
|
||||||
using FluidSim.Interfaces;
|
|
||||||
using FluidSim.Utils;
|
|
||||||
|
|
||||||
namespace FluidSim.Core
|
|
||||||
{
|
|
||||||
public static class Simulation
|
|
||||||
{
|
|
||||||
private static Solver solver;
|
|
||||||
private static Pipe1D pipe;
|
|
||||||
private static int stepCount;
|
|
||||||
private static double time;
|
|
||||||
private static double dt;
|
|
||||||
private static float sample;
|
|
||||||
private static double ambientPressure = 1.0 * Units.atm;
|
|
||||||
|
|
||||||
private static bool enableLogging = false;
|
|
||||||
|
|
||||||
public static void Initialize(int sampleRate)
|
|
||||||
{
|
|
||||||
dt = 1.0 / sampleRate;
|
|
||||||
|
|
||||||
double length = 2;
|
|
||||||
double radius = 20 * Units.mm;
|
|
||||||
double area = Units.AreaFromDiameter(radius);
|
|
||||||
|
|
||||||
pipe = new Pipe1D(length, area, sampleRate, forcedCellCount: 80);
|
|
||||||
pipe.SetUniformState(1.225, 0.0, ambientPressure);
|
|
||||||
|
|
||||||
solver = new Solver();
|
|
||||||
solver.SetTimeStep(dt);
|
|
||||||
solver.AddPipe(pipe);
|
|
||||||
solver.SetPipeBoundary(pipe, isLeft: true, BoundaryType.OpenEnd, ambientPressure);
|
|
||||||
solver.SetPipeBoundary(pipe, isLeft: false, BoundaryType.ClosedEnd);
|
|
||||||
|
|
||||||
// Excite the pipe with an initial pressure pulse near the open end
|
|
||||||
int pulseCells = 5;
|
|
||||||
double pulsePressure = 4 * ambientPressure;
|
|
||||||
for (int i = 0; i < pulseCells; i++)
|
|
||||||
pipe.SetCellState(i, 1.225, 0.0, pulsePressure);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float Process()
|
|
||||||
{
|
|
||||||
sample = solver.Step();
|
|
||||||
time += dt;
|
|
||||||
stepCount++;
|
|
||||||
|
|
||||||
// Override the audio sample with mid-pipe pressure deviation
|
|
||||||
double pMid = pipe.GetPressureAtFraction(0.5);
|
|
||||||
sample = (float)((pMid - ambientPressure) / ambientPressure);
|
|
||||||
|
|
||||||
Log();
|
|
||||||
return sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Log()
|
|
||||||
{
|
|
||||||
if (!enableLogging) return;
|
|
||||||
if (stepCount % 10 == 0 && stepCount < 1000)
|
|
||||||
{
|
|
||||||
double pMid = pipe.GetPressureAtFraction(0.5);
|
|
||||||
double pOpen = pipe.GetCellPressure(0);
|
|
||||||
double pClosed = pipe.GetCellPressure(pipe.GetCellCount() - 1);
|
|
||||||
Console.WriteLine(
|
|
||||||
$"t = {time * 1e3:F3} ms Step {stepCount:D4}: " +
|
|
||||||
$"Sample: = {sample:F3}, " +
|
|
||||||
$"P_mid = {pMid:F2} Pa ({pMid / ambientPressure:F4} atm), " +
|
|
||||||
$"P_open = {pOpen:F2} Pa, P_closed = {pClosed:F2} Pa");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
153
Program.cs
153
Program.cs
@@ -9,61 +9,172 @@ namespace FluidSim;
|
|||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
private const int SampleRate = 44100;
|
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;
|
||||||
|
|
||||||
|
// Space‑toggle state
|
||||||
|
private static double lastDesiredSpeed = 0.1; // remembers the last non‑1.0 scroll speed
|
||||||
|
private static bool isRealTime = true; // true when desiredSpeed == 1.0
|
||||||
|
|
||||||
private static volatile bool running = true;
|
private static volatile bool running = true;
|
||||||
|
|
||||||
public static void Main()
|
public static void Main()
|
||||||
{
|
{
|
||||||
var mode = new VideoMode(new Vector2u(1280, 720));
|
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.SetVerticalSyncEnabled(true);
|
||||||
window.Closed += (_, _) => { running = false; window.Close(); };
|
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.Volume = 70;
|
||||||
soundEngine.Start();
|
soundEngine.Start();
|
||||||
|
|
||||||
double lastAudioTime = 0.0;
|
scenario = new PipeResonatorScenario();
|
||||||
|
scenario.Initialize(SampleRate);
|
||||||
|
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
double lastDrawTime = 0.0;
|
||||||
|
double drawInterval = 1.0 / DrawFrequency;
|
||||||
|
double lastSpeedUpdateTime = stopwatch.Elapsed.TotalSeconds;
|
||||||
|
|
||||||
int warmupSamples = SampleRate / 2;
|
// Resampling buffer
|
||||||
float[] warmup = new float[warmupSamples];
|
List<float> simBuffer = new List<float>(4096);
|
||||||
for (int i = 0; i < warmupSamples; i++)
|
double readIndex = 0.0;
|
||||||
warmup[i] = 0;
|
|
||||||
|
|
||||||
soundEngine.WriteSamples(warmup, warmupSamples);
|
for (int i = 0; i < 4; i++)
|
||||||
lastAudioTime = stopwatch.Elapsed.TotalSeconds;
|
simBuffer.Add(scenario.Process());
|
||||||
|
|
||||||
const int chunkSize = 2048;
|
long totalSimSteps = simBuffer.Count;
|
||||||
float[] buffer = new float[chunkSize];
|
long totalOutputSamples = 0;
|
||||||
|
|
||||||
Simulation.Initialize(SampleRate);
|
double lastRealTime = stopwatch.Elapsed.TotalSeconds;
|
||||||
|
const int outputChunk = 256;
|
||||||
|
float[] outputBuf = new float[outputChunk];
|
||||||
|
|
||||||
while (window.IsOpen)
|
while (window.IsOpen)
|
||||||
{
|
{
|
||||||
window.DispatchEvents();
|
window.DispatchEvents();
|
||||||
|
|
||||||
double currentTime = stopwatch.Elapsed.TotalSeconds;
|
double currentRealTime = stopwatch.Elapsed.TotalSeconds;
|
||||||
double elapsed = currentTime - lastAudioTime;
|
double dtSpeed = currentRealTime - lastSpeedUpdateTime;
|
||||||
int samplesNeeded = (int)(elapsed * SampleRate);
|
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 catch‑up
|
||||||
|
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++)
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastAudioTime = currentTime;
|
int accepted = soundEngine.WriteSamples(outputBuf, toGenerate);
|
||||||
|
totalOutputSamples += accepted;
|
||||||
|
|
||||||
|
if (accepted < toGenerate)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 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.Clear(Color.Black);
|
||||||
|
scenario.Draw(window);
|
||||||
window.Display();
|
window.Display();
|
||||||
|
lastDrawTime = currentRealTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
soundEngine.Dispose();
|
soundEngine.Dispose();
|
||||||
window.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
183
Scenarios/PipeResonatorScenario.cs
Normal file
183
Scenarios/PipeResonatorScenario.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using FluidSim.Components;
|
||||||
|
using FluidSim.Interfaces;
|
||||||
|
using FluidSim.Utils;
|
||||||
|
using SFML.Graphics;
|
||||||
|
using SFML.System;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FluidSim.Core
|
||||||
|
{
|
||||||
|
public class PipeResonatorScenario : Scenario
|
||||||
|
{
|
||||||
|
private Solver solver;
|
||||||
|
private Pipe1D pipe;
|
||||||
|
private int stepCount;
|
||||||
|
private double time;
|
||||||
|
private double dt;
|
||||||
|
private double ambientPressure = 1.0 * Units.atm;
|
||||||
|
private bool enableLogging = true;
|
||||||
|
|
||||||
|
public override void Initialize(int sampleRate)
|
||||||
|
{
|
||||||
|
dt = 1.0 / sampleRate;
|
||||||
|
|
||||||
|
double length = 0.5;
|
||||||
|
double radius = 50 * Units.mm;
|
||||||
|
double area = Units.AreaFromDiameter(radius);
|
||||||
|
|
||||||
|
pipe = new Pipe1D(length, area, sampleRate, forcedCellCount: 80);
|
||||||
|
pipe.SetUniformState(1.225, 0.0, ambientPressure);
|
||||||
|
|
||||||
|
solver = new Solver();
|
||||||
|
solver.SetTimeStep(dt);
|
||||||
|
solver.AddPipe(pipe);
|
||||||
|
solver.SetPipeBoundary(pipe, isLeft: true, BoundaryType.OpenEnd, ambientPressure);
|
||||||
|
solver.SetPipeBoundary(pipe, isLeft: false, BoundaryType.ClosedEnd);
|
||||||
|
|
||||||
|
// Initial pressure pulse
|
||||||
|
int pulseCells = 5;
|
||||||
|
double pulsePressure = 2 * ambientPressure;
|
||||||
|
for (int i = 0; i < pulseCells; i++)
|
||||||
|
pipe.SetCellState(i, 1.225, 0.0, pulsePressure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float Process()
|
||||||
|
{
|
||||||
|
float sample = solver.Step();
|
||||||
|
time += dt;
|
||||||
|
stepCount++;
|
||||||
|
|
||||||
|
double pMid = pipe.GetPressureAtFraction(0.5);
|
||||||
|
sample = (float)((pMid - ambientPressure) / ambientPressure);
|
||||||
|
|
||||||
|
Log(sample);
|
||||||
|
return sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(float sample)
|
||||||
|
{
|
||||||
|
if (!enableLogging) return;
|
||||||
|
if (stepCount % 10 == 0 && stepCount < 1000)
|
||||||
|
{
|
||||||
|
double pMid = pipe.GetPressureAtFraction(0.5);
|
||||||
|
double pOpen = pipe.GetCellPressure(0);
|
||||||
|
double pClosed = pipe.GetCellPressure(pipe.GetCellCount() - 1);
|
||||||
|
Console.WriteLine(
|
||||||
|
$"t = {time * 1e3:F3} ms Step {stepCount:D4}: " +
|
||||||
|
$"sample = {sample:F3}, " +
|
||||||
|
$"P_mid = {pMid:F2} Pa ({pMid / ambientPressure:F4} atm), " +
|
||||||
|
$"P_open = {pOpen:F2} Pa, P_closed = {pClosed:F2} Pa");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw(RenderWindow target)
|
||||||
|
{
|
||||||
|
float winWidth = target.GetView().Size.X;
|
||||||
|
float winHeight = target.GetView().Size.Y;
|
||||||
|
|
||||||
|
float pipeCenterY = winHeight / 2f;
|
||||||
|
float margin = 60f;
|
||||||
|
float pipeStartX = margin;
|
||||||
|
float pipeEndX = winWidth - margin;
|
||||||
|
float pipeLengthPx = pipeEndX - pipeStartX;
|
||||||
|
int n = pipe.GetCellCount();
|
||||||
|
float dx = pipeLengthPx / (n - 1); // spacing between cell centres
|
||||||
|
|
||||||
|
float baseRadius = 25f;
|
||||||
|
float rangeFactor = 1f;
|
||||||
|
float scaleFactor = 5f;
|
||||||
|
|
||||||
|
// ----- smoothstep helper -----
|
||||||
|
static float SmoothStep(float edge0, float edge1, float x)
|
||||||
|
{
|
||||||
|
float t = Math.Clamp((x - edge0) / (edge1 - edge0), 0f, 1f);
|
||||||
|
return t * t * (3f - 2f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Pre‑compute cell positions and radii -----
|
||||||
|
var centers = new float[n];
|
||||||
|
var radii = new float[n];
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
double p = pipe.GetCellPressure(i);
|
||||||
|
float deviation = (float)Math.Tanh((p - ambientPressure) / ambientPressure / rangeFactor);
|
||||||
|
radii[i] = baseRadius * (1f + deviation * scaleFactor);
|
||||||
|
if (radii[i] < 2f) radii[i] = 2f;
|
||||||
|
centers[i] = pipeStartX + i * dx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Build triangle‑strip vertices -----
|
||||||
|
int segmentsPerCell = 8; // smoothness
|
||||||
|
int totalPoints = n + (n - 1) * segmentsPerCell;
|
||||||
|
Vertex[] stripVertices = new Vertex[totalPoints * 2]; // top + bottom for each point
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
// ---- Cell centre ----
|
||||||
|
float x = centers[i];
|
||||||
|
float r = radii[i];
|
||||||
|
double p = pipe.GetCellPressure(i);
|
||||||
|
Color col = PressureColor(p);
|
||||||
|
|
||||||
|
stripVertices[idx++] = new Vertex(new Vector2f(x, pipeCenterY - r), col);
|
||||||
|
stripVertices[idx++] = new Vertex(new Vector2f(x, pipeCenterY + r), col);
|
||||||
|
|
||||||
|
// ---- Intermediate segments after this cell (if not last) ----
|
||||||
|
if (i < n - 1)
|
||||||
|
{
|
||||||
|
for (int s = 1; s <= segmentsPerCell; s++)
|
||||||
|
{
|
||||||
|
float t = s / (float)segmentsPerCell;
|
||||||
|
float st = SmoothStep(0f, 1f, t);
|
||||||
|
float xi = centers[i] + (centers[i + 1] - centers[i]) * t;
|
||||||
|
float ri = radii[i] + (radii[i + 1] - radii[i]) * st;
|
||||||
|
double pi = pipe.GetCellPressure(i) * (1 - t) + pipe.GetCellPressure(i + 1) * t;
|
||||||
|
Color coli = PressureColor(pi);
|
||||||
|
|
||||||
|
stripVertices[idx++] = new Vertex(new Vector2f(xi, pipeCenterY - ri), coli);
|
||||||
|
stripVertices[idx++] = new Vertex(new Vector2f(xi, pipeCenterY + ri), coli);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the pipe as a triangle strip
|
||||||
|
var pipeMesh = new VertexArray(PrimitiveType.TriangleStrip, (uint)stripVertices.Length);
|
||||||
|
for (int i = 0; i < stripVertices.Length; i++)
|
||||||
|
pipeMesh[(uint)i] = stripVertices[i];
|
||||||
|
target.Draw(pipeMesh);
|
||||||
|
|
||||||
|
// ----- Closed end indicator (right) -----
|
||||||
|
float wallThickness = 8f;
|
||||||
|
var wall = new RectangleShape(new Vector2f(wallThickness, winHeight * 0.6f));
|
||||||
|
wall.Position = new Vector2f(pipeEndX, pipeCenterY - winHeight * 0.6f / 2f);
|
||||||
|
wall.FillColor = new Color(180, 180, 180);
|
||||||
|
target.Draw(wall);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Blue (low) → Green (ambient) → Red (high).</summary>
|
||||||
|
private Color PressureColor(double pressure)
|
||||||
|
{
|
||||||
|
double range = ambientPressure * 0.05; // ±5% gives full colour swing
|
||||||
|
double t = (pressure - ambientPressure) / range;
|
||||||
|
t = Math.Clamp(t, -1.0, 1.0);
|
||||||
|
|
||||||
|
byte r, g, b;
|
||||||
|
if (t < 0)
|
||||||
|
{
|
||||||
|
double factor = -t;
|
||||||
|
r = 0;
|
||||||
|
g = (byte)(255 * (1 - factor));
|
||||||
|
b = (byte)(255 * factor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double factor = t;
|
||||||
|
r = (byte)(255 * factor);
|
||||||
|
g = (byte)(255 * (1 - factor));
|
||||||
|
b = 0;
|
||||||
|
}
|
||||||
|
return new Color(r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Scenarios/Scenario.cs
Normal file
23
Scenarios/Scenario.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using SFML.Graphics;
|
||||||
|
|
||||||
|
namespace FluidSim.Core
|
||||||
|
{
|
||||||
|
public abstract class Scenario
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the scenario with a given audio sample rate.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Initialize(int sampleRate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advance one simulation step and return an audio sample.
|
||||||
|
/// The step size is 1 / sampleRate seconds.
|
||||||
|
/// </summary>
|
||||||
|
public abstract float Process();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Draw the current simulation state onto the given SFML render target.
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Draw(RenderWindow target);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user