Skip to content

Commit

Permalink
Pause audio while in a TAS for cassettes
Browse files Browse the repository at this point in the history
  • Loading branch information
psyGamer committed Mar 6, 2024
1 parent c7d70a7 commit 4b91f24
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 7 deletions.
80 changes: 73 additions & 7 deletions Source/Audio/Audio.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Celeste64.TAS;
using FMOD.Studio;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;

Expand All @@ -9,7 +9,7 @@ public static class Audio
{
private class Module : Foster.Framework.Module
{
public override void Update()
public override void Update()
=> Audio.Update();

public override void Shutdown()
Expand All @@ -21,6 +21,9 @@ public override void Shutdown()
private static readonly Dictionary<string, FMOD.GUID> events = [];
private static readonly Dictionary<string, FMOD.GUID> buses = [];

private static FMOD.ChannelGroup masterChannelGroup;
internal static FMOD.DSP dsp;

public static void Init()
{
// live upate allows FMOD UI to interact with sounds in-game in real time
Expand Down Expand Up @@ -50,9 +53,72 @@ public static void Init()
// Initialize FMOD
Check(system.initialize(1024, studioFlags, flags, IntPtr.Zero));

var desc = default(FMOD.DSP_DESCRIPTION);
desc.version = 0x00010000;
desc.numinputbuffers = 1;
desc.numoutputbuffers = 1;
desc.read = BlockCallback;

core.getMasterChannelGroup(out masterChannelGroup);
core.createDSP(ref desc, out dsp);
masterChannelGroup.addDSP(0, dsp);
dsp.setBypass(true);

App.Register<Module>();
}

// Theoretical perfect amount of samples per frame
private const int TargetRecordedSamples = 48000 / 60;
// Blocks the FMOD thread.
internal static bool blockEnabled = false;
// The accumulated overhead, since audio chunks contain more data than 1 frame.
private static int totalRecodedSamplesError = 0;
// Actual amount of samples which were blocked
private static int recordedSamples = 0;

private static unsafe FMOD.RESULT BlockCallback(ref FMOD.DSP_STATE dspState, IntPtr inBuffer, IntPtr outBuffer, uint samples, int inChannels, ref int outChannels) {
float* src = (float*) inBuffer;
float* dst = (float*) outBuffer;

if (inChannels == outChannels) {
NativeMemory.Copy(src, dst, (nuint) (inChannels * samples * Marshal.SizeOf<float>()));
} else if (inChannels > outChannels) {
// Cut the remaining channels off
for (int sample = 0; sample < samples; sample++) {
NativeMemory.Copy(src + sample * inChannels, dst + sample * outChannels, (nuint) (outChannels * Marshal.SizeOf<float>()));
}
} else {
// Repeat the last channel to fill the remaining ones
for (int sample = 0; sample < samples; sample++) {
NativeMemory.Copy(src + sample * inChannels, dst + sample * outChannels, (nuint) (inChannels * Marshal.SizeOf<float>()));
for (int channel = inChannels; channel < outChannels; channel++) {
dst![sample * outChannels + channel] = src![sample * inChannels + inChannels - 1];
}
}
}

recordedSamples += (int) samples;

// Skip a frame to let the video catch up again
if (totalRecodedSamplesError >= TargetRecordedSamples) {
totalRecodedSamplesError -= TargetRecordedSamples;
recordedSamples = 0;
return FMOD.RESULT.OK;
}

if (recordedSamples >= TargetRecordedSamples)
blockEnabled = true;

// Block until the TAS allows resuming. (also blocks the main FMOD thread which is good, since it avoid artifacts)
while (blockEnabled && Manager.Running) { }

// Account for overshooting
totalRecodedSamplesError += recordedSamples - TargetRecordedSamples;
recordedSamples = 0;

return FMOD.RESULT.OK;
}

private static bool isResolverSet = false;

private static void LoadDynamicLibraries()
Expand All @@ -61,11 +127,11 @@ private static void LoadDynamicLibraries()
return;
isResolverSet = true;

var path
= Path.GetDirectoryName(AppContext.BaseDirectory)
var path
= Path.GetDirectoryName(AppContext.BaseDirectory)
?? Directory.GetCurrentDirectory();

NativeLibrary.SetDllImportResolver(typeof(FMOD.Studio.System).Assembly,
NativeLibrary.SetDllImportResolver(typeof(FMOD.Studio.System).Assembly,
(name, assembly, dllImportSearchPath) =>
{
name = Path.GetFileNameWithoutExtension(name);
Expand Down Expand Up @@ -210,7 +276,7 @@ private static AudioHandle Create(in EventDescription desc)
Log.Warning($"Failed to create Audio Event Instance: {result}");
return new AudioHandle();
}

return new AudioHandle(instance);
}

Expand Down Expand Up @@ -309,4 +375,4 @@ public static ulong ParameterIDToU64(PARAMETER_ID id)
return ((ulong)id.data1 << 32) | (id.data2);
}

}
}
3 changes: 3 additions & 0 deletions Source/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ public override void Update()
return;
}

// Allow for next audio frame
Audio.blockEnabled = false;

// update top scene
if (scenes.TryPeek(out var scene))
{
Expand Down
4 changes: 4 additions & 0 deletions Source/TAS/Manager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public static void EnableRun()
Controller.RefreshInputs();

TASLevelRecord.ID = string.Empty;

Audio.dsp.setBypass(false);
}

public static void DisableRun()
Expand All @@ -63,6 +65,8 @@ public static void DisableRun()
NextState = State.Disabled;
AttributeUtils.Invoke<DisableRunAttribute>();
Controller.Stop();

Audio.dsp.setBypass(true);
}

public static void Update()
Expand Down

0 comments on commit 4b91f24

Please sign in to comment.