-
Notifications
You must be signed in to change notification settings - Fork 422
Commit
Co-authored-by: smallketchup82 <[email protected]>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using System.Buffers; | ||
using System.IO; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using ManagedBass; | ||
using ManagedBass.Loud; | ||
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Code Quality
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Code Quality
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Build only (iOS)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Build only (iOS)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Debug, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Debug, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (macOS, macos-latest, Debug, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (macOS, macos-latest, Debug, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Debug, SingleThread)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Debug, SingleThread)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (macOS, macos-latest, Debug, SingleThread)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (macOS, macos-latest, Debug, SingleThread)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Release, SingleThread)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Release, SingleThread)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Release, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Linux, ubuntu-latest, Release, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Windows, windows-latest, Debug, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Windows, windows-latest, Debug, MultiThreaded)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Build only (Android)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Build only (Android)
Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs GitHub Actions / Test (Windows, windows-latest, Debug, SingleThread)
|
||
using osu.Framework.Audio.Callbacks; | ||
using osu.Framework.Extensions; | ||
using osu.Framework.Logging; | ||
|
||
namespace osu.Framework.Audio.Track | ||
{ | ||
/// <summary> | ||
/// Measures loudness of audio samples. | ||
/// </summary> | ||
public class TrackLoudness : IDisposable | ||
{ | ||
private Stream? data; | ||
|
||
private readonly Task readTask; | ||
|
||
private readonly CancellationTokenSource cancelSource = new CancellationTokenSource(); | ||
|
||
private float? integratedLoudness; | ||
|
||
/// <summary> | ||
/// Measures loudness from provided audio data. | ||
/// </summary> | ||
/// <param name="data"> | ||
/// The sample data stream. | ||
/// The <see cref="TrackLoudness"/> will take ownership of this stream and dispose it when done reading track data. | ||
/// If null, loudness won't get calculated. | ||
/// </param> | ||
public TrackLoudness(Stream? data) | ||
{ | ||
this.data = data; | ||
|
||
readTask = Task.Run(() => | ||
{ | ||
if (data == null) | ||
return; | ||
|
||
if (Bass.CurrentDevice < 0) | ||
{ | ||
Logger.Log("Failed to measure loudness as no bass device is available."); | ||
return; | ||
} | ||
|
||
FileCallbacks fileCallbacks = new FileCallbacks(new DataStreamFileProcedures(data)); | ||
|
||
int decodeStream = Bass.CreateStream(StreamSystem.NoBuffer, BassFlags.Decode | BassFlags.Float, fileCallbacks.Callbacks, fileCallbacks.Handle); | ||
|
||
if (decodeStream == 0) | ||
{ | ||
Logger.Log($"Bass failed to create a stream while trying to measure loudness: {Bass.LastError}"); | ||
fileCallbacks.Dispose(); | ||
return; | ||
} | ||
|
||
byte[] buffer = ArrayPool<byte>.Shared.Rent(20000); | ||
|
||
try | ||
{ | ||
int loudHandle = BassLoud.Start(decodeStream, BassFlags.BassLoudnessIntegrated | BassFlags.BassLoudnessAutofree, 0); | ||
|
||
if (loudHandle == 0) | ||
{ | ||
Logger.Log($"Failed to start BassLoud: {Bass.LastError}"); | ||
return; | ||
} | ||
|
||
while (Bass.ChannelGetData(decodeStream, buffer, buffer.Length) >= 0) | ||
{ | ||
} | ||
|
||
float integratedLoudness = 1; | ||
bool gotLevel = BassLoud.GetLevel(loudHandle, BassFlags.BassLoudnessIntegrated, ref integratedLoudness); | ||
|
||
if (!gotLevel) | ||
{ | ||
Logger.Log($"Failed to get loudness level: {Bass.LastError}"); | ||
return; | ||
} | ||
|
||
if (integratedLoudness < 0) | ||
this.integratedLoudness = integratedLoudness; | ||
} | ||
finally | ||
{ | ||
Bass.StreamFree(decodeStream); | ||
fileCallbacks.Dispose(); | ||
data.Dispose(); | ||
|
||
ArrayPool<byte>.Shared.Return(buffer); | ||
} | ||
}, cancelSource.Token); | ||
} | ||
|
||
/// <summary> | ||
/// Returns integrated loudness. | ||
/// </summary> | ||
public async Task<float?> GetIntegratedLoudnessAsync() | ||
{ | ||
await readTask.ConfigureAwait(false); | ||
|
||
return integratedLoudness; | ||
} | ||
|
||
/// <summary> | ||
/// Returns integrated loudness. | ||
/// </summary> | ||
public float? GetIntegratedLoudness() => GetIntegratedLoudnessAsync().GetResultSafely(); | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (!disposedValue) | ||
{ | ||
cancelSource.Cancel(); | ||
cancelSource.Dispose(); | ||
integratedLoudness = null; | ||
|
||
data?.Dispose(); | ||
data = null; | ||
|
||
disposedValue = true; | ||
} | ||
} | ||
|
||
private bool disposedValue; | ||
|
||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
} | ||
} |