Skip to content

Commit

Permalink
replaces portaudio with a new backend
Browse files Browse the repository at this point in the history
  • Loading branch information
stakira committed Jul 26, 2024
1 parent 9b44e92 commit f2232f2
Show file tree
Hide file tree
Showing 25 changed files with 765 additions and 9 deletions.
1 change: 0 additions & 1 deletion OpenUtau.Core/Audio/IAudioOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ public class AudioOutputDevice {
public string api;
public int deviceNumber;
public Guid guid;
public object data;

public override string ToString() => $"[{api}] {name}";
}
Expand Down
233 changes: 233 additions & 0 deletions OpenUtau.Core/Audio/MiniAudioOutput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
using OpenUtau.Core.Util;

namespace OpenUtau.Audio {
public class MiniAudioOutput : IAudioOutput, IDisposable {
const int channels = 2;
const int sampleRate = 44100;

public PlaybackState PlaybackState { get; private set; }
public int DeviceNumber { get; private set; }


private ISampleProvider? sampleProvider;
private double currentTimeMs;
private bool eof;

private List<AudioOutputDevice> devices = new List<AudioOutputDevice>();
private IntPtr callbackPtr = IntPtr.Zero;
private IntPtr nativeContext = IntPtr.Zero;
private Guid selectedDevice = Guid.Empty;

public MiniAudioOutput() {
UpdateDeviceList();
unsafe {
var f = (ou_audio_data_callback_t)DataCallback;
GCHandle.Alloc(f);
callbackPtr = Marshal.GetFunctionPointerForDelegate(f);
}
if (Guid.TryParse(Preferences.Default.PlaybackDevice, out var guid)) {
SelectDevice(guid, Preferences.Default.PlaybackDeviceNumber);
} else {
SelectDevice(devices[0].guid, devices[0].deviceNumber);
}
}

private void UpdateDeviceList() {
devices.Clear();
unsafe {
ou_audio_device_info_t* device_infos = stackalloc ou_audio_device_info_t[16];
int count = ou_get_audio_device_infos(device_infos, 16);
if (count == 0) {
throw new Exception("Failed to get any audio device info");
}
if (count > 16) {
ou_free_audio_device_infos(device_infos, 16);
count = ou_get_audio_device_infos(device_infos, count);
}
for (int i = 0; i < count; i++) {
var guidData = new byte[16];
fixed (byte* guidPtr = guidData) {
*(ulong*)guidPtr = device_infos[i].api_id;
*(ulong*)(guidPtr + 8) = device_infos[i].id;
}
devices.Add(new AudioOutputDevice {
name = Marshal.PtrToStringUTF8(device_infos[i].name),
api = Marshal.PtrToStringUTF8(device_infos[i].api),
deviceNumber = i,
guid = new Guid(guidData),
});
}
ou_free_audio_device_infos(device_infos, count);
}
}

public void Init(ISampleProvider sampleProvider) {
PlaybackState = PlaybackState.Stopped;
eof = false;
currentTimeMs = 0;
if (sampleRate != sampleProvider.WaveFormat.SampleRate) {
sampleProvider = new WdlResamplingSampleProvider(sampleProvider, sampleRate);
}
this.sampleProvider = sampleProvider.ToStereo();
}

public void Play() {
if (PlaybackState != PlaybackState.Playing) {
CheckError(ou_audio_device_start(nativeContext));
}
PlaybackState = PlaybackState.Playing;
currentTimeMs = 0;
eof = false;
}

public void Pause() {
if (PlaybackState == PlaybackState.Playing) {
CheckError(ou_audio_device_stop(nativeContext));
}
PlaybackState = PlaybackState.Paused;
}

public void Stop() {
if (PlaybackState == PlaybackState.Playing) {
CheckError(ou_audio_device_stop(nativeContext));
}
PlaybackState = PlaybackState.Stopped;
}

private unsafe void DataCallback(float* buffer, uint channels, uint frame_count) {
var temp = new float[channels * frame_count];
if (sampleProvider != null) {
int n = sampleProvider.Read(temp, 0, temp.Length);
if (n == 0) {
eof = true;
}
}
Marshal.Copy(temp, 0, (IntPtr)buffer, temp.Length);
currentTimeMs += frame_count * 1000.0 / sampleRate;
}

public long GetPosition() {
if (eof && PlaybackState == PlaybackState.Playing) {
Stop();
}
return (long)(Math.Max(0, currentTimeMs) / 1000 * sampleRate * 2 /* 16 bit */ * channels);
}

public void SelectDevice(Guid guid, int deviceNumber) {
if (selectedDevice != Guid.Empty && selectedDevice == guid) {
return;
}
if (nativeContext != IntPtr.Zero) {
CheckError(ou_free_audio_device(nativeContext));
nativeContext = IntPtr.Zero;
selectedDevice = Guid.Empty;
}
for (int i = 0; i < devices.Count; i++) {
if (devices[i].guid == guid) {
deviceNumber = i;
break;
}
if (i == devices.Count - 1) {
guid = devices[0].guid;
deviceNumber = devices[0].deviceNumber;
}
}
uint api_id;
ulong id;
unsafe {
fixed (byte* guidPtr = guid.ToByteArray()) {
api_id = (uint)*(ulong*)guidPtr;
id = *(ulong*)(guidPtr + 8);
}
}
unsafe {
nativeContext = ou_init_audio_device(api_id, id, callbackPtr);
if (nativeContext == IntPtr.Zero) {
throw new Exception("Failed to init audio device");
}
}
selectedDevice = guid;
DeviceNumber = deviceNumber;
if (Preferences.Default.PlaybackDevice != guid.ToString()) {
Preferences.Default.PlaybackDevice = guid.ToString();
Preferences.Default.PlaybackDeviceNumber = deviceNumber;
Preferences.Save();
}
}

public List<AudioOutputDevice> GetOutputDevices() {
return devices;
}

#region binding

[StructLayout(LayoutKind.Sequential)]
private unsafe struct ou_audio_device_info_t {
public IntPtr name;
public ulong id;
public IntPtr api;
public uint api_id;
}

[UnmanagedFunctionPointer(callingConvention: CallingConvention.Cdecl)]
private unsafe delegate void ou_audio_data_callback_t(float* buffer, uint channels, uint frame_count);

[DllImport("worldline")] private static extern unsafe int ou_get_audio_device_infos(ou_audio_device_info_t* device_infos, int max_count);
[DllImport("worldline")] private static extern unsafe void ou_free_audio_device_infos(ou_audio_device_info_t* device_infos, int count);
[DllImport("worldline")] private static extern IntPtr ou_init_audio_device(uint api_id, ulong id, IntPtr callback);
[DllImport("worldline")] private static extern int ou_free_audio_device(IntPtr context);
[DllImport("worldline")] private static extern int ou_audio_device_start(IntPtr context);
[DllImport("worldline")] private static extern int ou_audio_device_stop(IntPtr context);
[DllImport("worldline")] private static extern IntPtr ou_audio_get_error_message(int error_code);

private static void CheckError(int errorCode) {
if (errorCode == 0) {
return;
}
IntPtr ptr = ou_audio_get_error_message(errorCode);
throw new Exception(Marshal.PtrToStringUTF8(ptr));
}

#endregion

#region disposable

private bool disposedValue;

protected virtual void Dispose(bool disposing) {
if (!disposedValue) {
if (disposing) {
// dispose managed state (managed objects)
}

// free unmanaged resources (unmanaged objects) and override finalizer
if (nativeContext != IntPtr.Zero) {
ou_free_audio_device(nativeContext);
nativeContext = IntPtr.Zero;
}

// set large fields to null

disposedValue = true;
}
}

~MiniAudioOutput() {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}

public void Dispose() {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

#endregion
}
}
4 changes: 2 additions & 2 deletions OpenUtau/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ public static void InitAudio() {
Log.Information("Initializing audio.");
if (!OS.IsWindows() || Core.Util.Preferences.Default.PreferPortAudio) {
try {
PlaybackManager.Inst.AudioOutput = new Audio.PortAudioOutput();
PlaybackManager.Inst.AudioOutput = new Audio.MiniAudioOutput();
} catch (Exception e1) {
Log.Error(e1, "Failed to init PortAudio");
Log.Error(e1, "Failed to init MiniAudio");
}
} else {
try {
Expand Down
1 change: 1 addition & 0 deletions OpenUtau/Strings/Strings.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ Warning: this option removes custom presets.</system:String>
<system:String x:Key="prefs.playback.autoscrollmode.stationarycursor">Stationary Cursor</system:String>
<system:String x:Key="prefs.playback.backend">Audio Backend</system:String>
<system:String x:Key="prefs.playback.backend.auto">Automatic</system:String>
<system:String x:Key="prefs.playback.backend.mini">MiniAudio</system:String>
<system:String x:Key="prefs.playback.backend.port">PortAudio</system:String>
<system:String x:Key="prefs.playback.cursorposition">Auto-Scroll Margin</system:String>
<system:String x:Key="prefs.playback.device">Playback Device</system:String>
Expand Down
2 changes: 1 addition & 1 deletion OpenUtau/Views/PreferencesDialog.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
<TextBlock Text="{DynamicResource prefs.playback.backend}" Margin="0,10,0,0"/>
<ComboBox SelectedIndex="{Binding PreferPortAudio}">
<ComboBoxItem Content="{DynamicResource prefs.playback.backend.auto}"/>
<ComboBoxItem Content="{DynamicResource prefs.playback.backend.port}"/>
<ComboBoxItem Content="{DynamicResource prefs.playback.backend.mini}"/>
</ComboBox>
<TextBlock Classes="restart"/>
<TextBlock Text="{DynamicResource prefs.playback.lockstarttime}" Margin="0,10,0,0"/>
Expand Down
13 changes: 12 additions & 1 deletion cpp/.bazelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
build --enable_platform_specific_config
common --enable_platform_specific_config
common --experimental_cc_shared_library
common --enable_bzlmod

run --define absl=1
test --define absl=1

build:linux --cxxopt='-std=c++17'
build:macos --cxxopt='-std=c++17'
build:windows --cxxopt='/std:c++17'

build:ubuntu-aarch64 --cxxopt='-std=c++17'
build:ubuntu-aarch64 --linkopt='-lm'
build:ubuntu-aarch64 --crosstool_top=//toolchain:arm_suite
build:ubuntu-aarch64 --cpu=aarch64
build:ubuntu-aarch64 --compiler=gcc
2 changes: 1 addition & 1 deletion cpp/.bazelversion
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.0.2
6.5.0
6 changes: 4 additions & 2 deletions cpp/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Worldline"""
module(name = "worldline")
"""OpenUtau C++ module."""
module(name = "openutau-cpp")

bazel_dep(name = "abseil-cpp", version = "20240116.1", repo_name = "absl")
bazel_dep(name = "googletest", version = "1.14.0", repo_name = "gtest")

bazel_dep(name = "xxhash", version = "0.8.2")
8 changes: 8 additions & 0 deletions cpp/WORKSPACE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ http_archive(
strip_prefix = "World-f8dd5fb289db6a7f7f704497752bf32b258f9151",
urls = ["https://github.com/mmorise/World/archive/f8dd5fb289db6a7f7f704497752bf32b258f9151.zip"],
)

http_archive(
name = "miniaudio",
build_file = "@//third_party:miniaudio.BUILD",
sha256 = "cbde908871e2619115fd216c74235265348060fe7d340f980cd14342e88d7f72",
strip_prefix = "miniaudio-0.11.21",
urls = ["https://github.com/mackron/miniaudio/archive/refs/tags/0.11.21.zip"],
)
18 changes: 18 additions & 0 deletions cpp/build_linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

setup()
{
sudo apt-get install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi binutils-arm-linux-gnueabi
sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu
}

build()
{
mkdir -p ../runtimes/linux-$1/native
bazel build //worldline:worldline -c opt $2
chmod +w bazel-bin/worldline/libworldline.so
cp bazel-bin/worldline/libworldline.so ../runtimes/linux-$1/native
}

build x64 "--cpu=k8"
build arm64 "--config=ubuntu-aarch64"
16 changes: 16 additions & 0 deletions cpp/build_mac.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

build()
{
bazel build //worldline:worldline -c opt $2
chmod +w bazel-bin/worldline/libworldline.dylib
cp bazel-bin/worldline/libworldline.dylib ../runtimes/osx/native/libworldline-$1.dylib
}

mkdir -p ../runtimes/osx/native

build x64 "--cpu=darwin_x86_64"
build arm64 "--cpu=darwin_arm64"

lipo -create ../runtimes/osx/native/libworldline-x64.dylib ../runtimes/osx/native/libworldline-arm64.dylib -output ../runtimes/osx/native/libworldline.dylib
rm ../runtimes/osx/native/libworldline-x64.dylib ../runtimes/osx/native/libworldline-arm64.dylib
16 changes: 16 additions & 0 deletions cpp/build_win.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@goto :MAIN

:BUILD
@echo Building %~1

if not exist ..\runtimes\%~1\native mkdir ..\runtimes\%~1\native
bazel build //worldline:worldline -c opt --cpu=%~2
attrib -r bazel-bin\worldline\worldline.dll
copy bazel-bin\worldline\worldline.dll ..\runtimes\%~1\native

@EXIT /B

:MAIN
@call :BUILD win-x64 x64_windows
@call :BUILD win-x86 x64_x86_windows
@call :BUILD win-arm64 arm64_windows
7 changes: 7 additions & 0 deletions cpp/third_party/miniaudio.BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package(default_visibility = ["//visibility:public"])

cc_library(
name = "miniaudio",
hdrs = ["miniaudio.h"],
includes = ["."],
)
Loading

0 comments on commit f2232f2

Please sign in to comment.