-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
79487d9
commit 33f6d0e
Showing
12 changed files
with
545 additions
and
7 deletions.
There are no files selected for viewing
121 changes: 121 additions & 0 deletions
121
Osu.Patcher.Hook/Patches/Mods/AudioPreview/FixUpdatePlaybackRate.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Reflection.Emit; | ||
using HarmonyLib; | ||
using JetBrains.Annotations; | ||
using Osu.Stubs.Audio; | ||
using Osu.Utils.Extensions; | ||
using static System.Reflection.Emit.OpCodes; | ||
|
||
// ReSharper disable InconsistentNaming | ||
|
||
namespace Osu.Patcher.Hook.Patches.Mods.AudioPreview; | ||
|
||
/// <summary> | ||
/// Apply various fixes to <c>AudioTrackBass</c> to make <c>BASS_ATTRIB_TEMPO</c> work again on | ||
/// "Preview" audio streams. We use this in combination with <see cref="ModSelectAudioPreview" /> | ||
/// to reapply the constant pitch speed modifier (for DoubleTime). | ||
/// </summary> | ||
[OsuPatch] | ||
[HarmonyPatch] | ||
[UsedImplicitly] | ||
internal class FixUpdatePlaybackRate | ||
{ | ||
[UsedImplicitly] | ||
[HarmonyTargetMethod] | ||
private static MethodBase Target() => AudioTrackBass.Constructor.Reference; | ||
|
||
[UsedImplicitly] | ||
[HarmonyTranspiler] | ||
private static IEnumerable<CodeInstruction> Transpiler( | ||
IEnumerable<CodeInstruction> instructions, | ||
ILGenerator generator) | ||
{ | ||
// Remove the conditional & block that checks for this.Preview and always do the full initialization | ||
// Otherwise, the "quick" initialization never uses BASS_FX_TempoCreate, which makes setting | ||
// BASSAttribute.BASS_ATTRIB_TEMPO impossible (what's used for DoubleTime). | ||
// instructions = instructions.NoopSignature( | ||
instructions = instructions.NoopSignature( | ||
// if (Preview) { audioStream = audioStreamForwards = audioStreamPrefilter; } | ||
[ | ||
Ldarg_0, | ||
Callvirt, | ||
Brfalse_S, | ||
Ldarg_0, | ||
Ldarg_0, | ||
Ldarg_0, | ||
Ldfld, | ||
Dup, | ||
Stloc_1, | ||
Stfld, | ||
Ldloc_1, | ||
Call, | ||
Br_S, | ||
] | ||
); | ||
|
||
// Change this "BASSFlag flags = Preview ? 0 : (BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_STREAM_PRESCAN);" | ||
// into "BASSFlag flags = Preview ? BASSFlag.BASS_STREAM_DECODE : (BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_STREAM_PRESCAN);" | ||
// This is so BASS_FX_TempoCreate still works when called later | ||
var foundFlags = false; | ||
const int BASS_STREAM_DECODE = 0x200000; | ||
const int BASS_STREAM_PRESCAN = 0x20000; | ||
instructions = instructions.Manipulator( | ||
inst => foundFlags || inst.Is(Ldc_I4, BASS_STREAM_DECODE | BASS_STREAM_PRESCAN), | ||
inst => | ||
{ | ||
if (inst.OperandIs(BASS_STREAM_DECODE | BASS_STREAM_PRESCAN)) | ||
{ | ||
foundFlags = true; | ||
return; | ||
} | ||
|
||
// This is the "? 0" | ||
if (inst.opcode != Ldc_I4_0) | ||
return; | ||
|
||
inst.opcode = Ldc_I4; | ||
inst.operand = BASS_STREAM_DECODE; | ||
foundFlags = false; | ||
} | ||
); | ||
|
||
// Load speed optimization: disable BASS_FX_ReverseCreate when the "quick" parameter is true (aka. Preview) | ||
var found = false; | ||
instructions = instructions.Reverse().ManipulatorReplace( | ||
inst => found || inst.Is(Stfld, AudioTrackBass.AudioStreamBackwardsHandle.Reference), | ||
inst => | ||
{ | ||
// Only targeting the Call instruction before Stfld | ||
if (inst.opcode != Call) | ||
{ | ||
found = true; | ||
return [inst]; | ||
} | ||
|
||
found = false; | ||
var labelTrue = generator.DefineLabel(); | ||
var labelFalse = generator.DefineLabel(); | ||
|
||
return new[] | ||
{ | ||
new(Ldarg_2), // "bool quick" | ||
new(Brfalse_S, labelFalse), | ||
|
||
// Clean up the 3 values to the Call on stack | ||
new(Pop), | ||
new(Pop), | ||
new(Pop), | ||
new(Ldc_I4_0), // Load a "0" to be used for Stfld AudioStreamBackwardsHandle | ||
new(Br_S, labelTrue), | ||
|
||
inst.WithLabels([labelFalse]), | ||
new(Nop) { labels = [labelTrue] }, | ||
}.Reverse(); | ||
} | ||
).Reverse(); | ||
|
||
return instructions; | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
Osu.Patcher.Hook/Patches/Mods/AudioPreview/ModAudioEffects.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using System; | ||
using Osu.Stubs.Audio; | ||
using Osu.Stubs.Scoring; | ||
using static Osu.Stubs.Other.Mods; | ||
|
||
namespace Osu.Patcher.Hook.Patches.Mods.AudioPreview; | ||
|
||
/// <summary> | ||
/// Handles applying and resetting the audio effects on the AudioEngine. | ||
/// </summary> | ||
internal static class ModAudioEffects | ||
{ | ||
/// <summary> | ||
/// Applies the audio changes to the AudioEngine based on the current global mods. | ||
/// </summary> | ||
internal static void ApplyModEffects() | ||
{ | ||
ResetChanges(); | ||
|
||
var mods = ModManager.ModStatus.Get(); | ||
|
||
// NC always comes with DT | ||
if ((mods & Nightcore) > None) | ||
mods &= ~DoubleTime; | ||
|
||
switch (mods & (DoubleTime | Nightcore | HalfTime)) | ||
{ | ||
case DoubleTime: | ||
UpdateAudioRate(rate => rate * 1.5); | ||
break; | ||
case Nightcore: | ||
AudioEngine.Nightcore.Set(true); | ||
UpdateAudioRate(rate => rate * 1.5); | ||
break; | ||
case HalfTime: | ||
UpdateAudioRate(rate => rate * 0.75); | ||
break; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Resets the audio stream effects back to default. | ||
/// </summary> | ||
private static void ResetChanges() | ||
{ | ||
AudioEngine.Nightcore.Set(false); | ||
UpdateAudioRate(_ => 100); | ||
} | ||
|
||
/// <summary> | ||
/// Gets, modifies, and writes back the CurrentPlaybackRate on the AudioEngine. | ||
/// </summary> | ||
/// <param name="onModify">Rate transformer.</param> | ||
private static void UpdateAudioRate(Func<double, double> onModify) | ||
{ | ||
var currentRate = AudioEngine.GetCurrentPlaybackRate.Invoke(); | ||
var newRate = onModify.Invoke(currentRate); | ||
|
||
AudioEngine.SetCurrentPlaybackRate.Invoke(parameters: [newRate]); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
Osu.Patcher.Hook/Patches/Mods/AudioPreview/ModSelectAudioPreview.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using HarmonyLib; | ||
using JetBrains.Annotations; | ||
using Osu.Stubs.SongSelect; | ||
|
||
namespace Osu.Patcher.Hook.Patches.Mods.AudioPreview; | ||
|
||
/// <summary> | ||
/// Hooks the place where ModButtons get updates in the mod selection menu to apply audio effects. | ||
/// </summary> | ||
[OsuPatch] | ||
[HarmonyPatch] | ||
[UsedImplicitly] | ||
internal class ModSelectAudioPreview | ||
{ | ||
[UsedImplicitly] | ||
[HarmonyTargetMethod] | ||
private static MethodBase Target() => ModSelection.UpdateMods.Reference; | ||
|
||
[UsedImplicitly] | ||
[HarmonyPostfix] | ||
private static void After() => Task.Run(ModAudioEffects.ApplyModEffects); | ||
} |
24 changes: 24 additions & 0 deletions
24
Osu.Patcher.Hook/Patches/Mods/AudioPreview/TrackUpdatePreviewMusic.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using HarmonyLib; | ||
using JetBrains.Annotations; | ||
using Osu.Stubs.Audio; | ||
|
||
namespace Osu.Patcher.Hook.Patches.Mods.AudioPreview; | ||
|
||
/// <summary> | ||
/// Hooks the place where preview audio gets loaded to apply our mod audio effects. | ||
/// </summary> | ||
[OsuPatch] | ||
[HarmonyPatch] | ||
[UsedImplicitly] | ||
public class TrackUpdatePreviewMusic | ||
{ | ||
[UsedImplicitly] | ||
[HarmonyTargetMethod] | ||
private static MethodBase Target() => AudioEngine.LoadAudioForPreview.Reference; | ||
|
||
[HarmonyPostfix] | ||
[UsedImplicitly] | ||
private static void After() => Task.Run(ModAudioEffects.ApplyModEffects); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
using System.Linq; | ||
using System.Reflection; | ||
using JetBrains.Annotations; | ||
using Osu.Utils.IL; | ||
using Osu.Utils.Lazy; | ||
using static System.Reflection.Emit.OpCodes; | ||
|
||
namespace Osu.Stubs.Audio; | ||
|
||
/// <summary> | ||
/// Original: <c>osu.Audio.AudioEngine</c> | ||
/// b20240123: <c></c> | ||
/// </summary> | ||
[PublicAPI] | ||
public class AudioEngine | ||
{ | ||
/// <summary> | ||
/// Original: <c>LoadAudioForPreview(Beatmap beatmap, bool continuePlayback, bool previewPoint, bool quick)</c> | ||
/// b20240123: <c></c> | ||
/// </summary> | ||
[Stub] | ||
public static readonly LazyMethod<bool> LoadAudioForPreview = LazyMethod<bool>.ByPartialSignature( | ||
"osu.Audio.AudioEngine::LoadAudioForPreview(Beatmap, bool, bool, bool)", | ||
[ | ||
Call, | ||
Ldc_I4_0, | ||
Ceq, | ||
Stloc_0, | ||
Leave_S, | ||
Pop, | ||
Ldc_I4, | ||
Call, | ||
] | ||
); | ||
|
||
/// <summary> | ||
/// Original: <c>get_CurrentPlaybackRate()</c> (property getter) | ||
/// b20240123: <c></c> | ||
/// </summary> | ||
[Stub] | ||
public static readonly LazyMethod<double> GetCurrentPlaybackRate = LazyMethod<double>.BySignature( | ||
"osu.Audio.AudioEngine::get_CurrentPlaybackRate()", | ||
[ | ||
Ldsfld, | ||
Dup, | ||
Brtrue_S, | ||
Pop, | ||
Ldc_R8, | ||
Ret, | ||
Callvirt, | ||
Ret, | ||
] | ||
); | ||
|
||
/// <summary> | ||
/// Original: <c>set_CurrentPlaybackRate()</c> (property setter) | ||
/// b20240123: <c></c> | ||
/// </summary> | ||
[Stub] | ||
public static readonly LazyMethod SetCurrentPlaybackRate = LazyMethod.ByPartialSignature( | ||
"osu.Audio.AudioEngine::set_CurrentPlaybackRate()", | ||
[ | ||
Ldsfld, // Reference to AudioEngine::Nightcore | ||
Ldc_I4_0, | ||
Ceq, | ||
Callvirt, | ||
Ldloc_0, | ||
Ldarg_0, | ||
Callvirt, | ||
Ret, | ||
] | ||
); | ||
|
||
/// <summary> | ||
/// Original: <c>Nightcore</c> | ||
/// b20240123: <c></c> | ||
/// </summary> | ||
[Stub] | ||
public static readonly LazyField<bool> Nightcore = new( | ||
"osu.Audio.AudioEngine::Nightcore", | ||
() => | ||
{ | ||
// Last Ldsfld in get_CurrentPlaybackRate() is a reference to AudioEngine::Nightcore | ||
var instruction = MethodReader | ||
.GetInstructions(SetCurrentPlaybackRate.Reference) | ||
.Reverse() | ||
.First(inst => inst.Opcode == Ldsfld); | ||
|
||
return (FieldInfo)instruction.Operand; | ||
} | ||
); | ||
} |
Oops, something went wrong.