Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TrackLoudness (BASSloud) #6281

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions osu.Framework/Audio/Track/TrackLoudness.cs
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

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Code Quality

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Code Quality

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, SingleThread)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)

Check failure on line 10 in osu.Framework/Audio/Track/TrackLoudness.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, MultiThreaded)

The type or namespace name 'Loud' does not exist in the namespace 'ManagedBass' (are you missing an assembly reference?)
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.", level: LogLevel.Error);
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}", level: LogLevel.Error);
fileCallbacks.Dispose();
return;
}

byte[] buffer = ArrayPool<byte>.Shared.Rent(20000);

try
{
int loudHandle = BassLoud.Start(decodeStream, BassFlags.BassLoudnessIntegrated | BassFlags.BassLoudnessAutofree, 0);

if (loudHandle == 0)
throw new InvalidOperationException("Failed to start BassLoud");

while (Bass.ChannelGetData(decodeStream, buffer, buffer.Length) >= 0)
{
}

float bassIntegratedLoudness = 1;
bool gotLevel = BassLoud.GetLevel(loudHandle, BassFlags.BassLoudnessIntegrated, ref bassIntegratedLoudness);

if (!gotLevel)
throw new InvalidOperationException("Failed to get loudness level");

if (bassIntegratedLoudness < 0)
integratedLoudness = bassIntegratedLoudness;
}
catch (Exception e)
{
Logger.Error(e, $"{e.Message}: {Bass.LastError}");
}
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();

public static double ConvertToVolumeOffset(int targetLevel, float integratedLoudness) => Math.Pow(10, (targetLevel - (double)integratedLoudness) / 20);

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);
}
}
}
Loading