Initial sound impl

main
Talon 2023-08-28 18:38:06 +02:00
parent 06c521a981
commit f4f88f711a
17 changed files with 372 additions and 10 deletions

3
.vscode/settings.json vendored 100644
View File

@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "Ryuclaw.Mac.sln"
}

View File

@ -0,0 +1,30 @@
using System;
using System.Numerics;
using Ryuclaw.Shared.Audio;
namespace Ryuclaw.Audio.Desktop
{
public abstract class AbstractAudioSource : IAudioSource
{
private Vector3 _position { get; set; }
public AbstractAudioSource()
{
}
public abstract void Play();
public void SetPosition(Vector3 position)
{
_position = position;
}
public abstract void Stop();
public void Update(float dt)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using Ryuclaw.Shared.Audio;
using Ryuclaw.Audio.Shared;
namespace Ryuclaw.Audio.Desktop
{
public class AudioCache : IAudioCache
{
private Dictionary<string, WaveReader> _cache;
public AudioCache()
{
_cache = new();
}
public WaveReader Get(string name)
{
if (_cache.TryGetValue(name, out var reader))
{
return reader;
} else
{
var newReader = new WaveReader();
newReader.Load(name);
_cache[name] = newReader;
return newReader;
}
}
public void Set(string name, IAudioSource source)
{
throw new NotImplementedException();
}
IAudioSource IAudioCache.Get(string name)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using Ryuclaw.Shared.Audio;
using OpenAL;
using System.Numerics;
namespace Ryuclaw.Audio.Desktop;
public class AudioEngine : IAudioEngine
{
private readonly object _lock = new object();
private IntPtr _context;
private IntPtr _device;
private AudioCache _cache;
public AudioEngine()
{
_device = ALC10.alcOpenDevice(null);
_context = ALC10.alcCreateContext(_device, null);
_cache = new AudioCache();
}
public IAudioSource CreateSource(string name)
{
throw new NotImplementedException();
}
public void SetListenerOrientation(Vector3 forward, Vector3 up)
{
throw new NotImplementedException();
}
public void SetListenerPosition(Vector3 position)
{
throw new NotImplementedException();
}
public bool Start()
{
ALC10.alcMakeContextCurrent(_context);
return true;
}
public void Stop()
{
throw new NotImplementedException();
}
public void Update(float dt)
{
throw new NotImplementedException();
}
}

View File

@ -1,7 +0,0 @@
namespace Ryuclaw.Audio.Desktop;
public class Class1
{
}

View File

@ -0,0 +1,28 @@
using System;
using Ryuclaw.Audio.Shared;
namespace Ryuclaw.Audio.Desktop
{
public class MemoryAudioSource : AbstractAudioSource
{
private WaveReader _reader;
public MemoryAudioSource(string name)
{
_reader = new WaveReader();
_reader.Load(name);
Console.WriteLine(_reader);
}
public override void Play()
{
throw new NotImplementedException();
}
public override void Stop()
{
throw new NotImplementedException();
}
}
}

View File

@ -8,5 +8,11 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryuclaw.Shared\Ryuclaw.Shared.csproj" /> <ProjectReference Include="..\Ryuclaw.Shared\Ryuclaw.Shared.csproj" />
<ProjectReference Include="..\Ryuclaw.Audio.Shared\Ryuclaw.Audio.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="OpenAL-CS">
<HintPath>..\..\..\code\OpenAL-CS\bin\Debug\netstandard2.0\OpenAL-CS.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<dllmap dll="soft_oal.dll" os="windows" target="soft_oal.dll"/>
<dllmap dll="soft_oal.dll" os="osx" target="libopenal.1.23.1.dylib"/>
<dllmap dll="soft_oal.dll" os="linux" target="libopenal.so.1"/>
<dllmap dll="soft_oal.dll" os="ios" target="__internal"/>
</configuration>

View File

@ -0,0 +1,7 @@
namespace Ryuclaw.Audio.Shared;
public class Class1
{
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,24 @@
using System;
namespace Ryuclaw.Audio.Shared
{
public class WaveHeader
{
public int SampleRate { get; private set; }
public int ChunkSize { get; private set; }
public int ByteRate { get; private set; }
public short BitsPerSample { get; private set; }
public short NumChannels { get; private set; }
public short BlockAlign { get; private set; }
public WaveHeader(int sampleRate = 48000, int chunkSize = 16, int byteRate = 16, short bitsPerSample = 16, short numChannels = 1, short blockAlign = 8)
{
SampleRate = sampleRate;
ChunkSize = chunkSize;
ByteRate = byteRate;
BitsPerSample = bitsPerSample;
NumChannels = numChannels;
BlockAlign = blockAlign;
}
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.IO;
namespace Ryuclaw.Audio.Shared
{
public class WaveReader
{
private WaveHeader _header;
public byte[] AudioData { get; private set; }
public WaveReader()
{
_header = new WaveHeader();
AudioData = new byte[0];
}
public void Load(string filePath)
{
using (var stream = new FileStream(filePath, FileMode.Open))
using (var reader = new BinaryReader(stream))
{
// RIFF header
string chunkID = new string(reader.ReadChars(4));
if (chunkID != "RIFF") throw new Exception("Not a valid WAV file.");
var ChunkSize = reader.ReadInt32(); // Chunk size
string format = new string(reader.ReadChars(4));
if (format != "WAVE") throw new Exception("Not a valid WAV file.");
// fmt sub-chunk
string subchunk1ID = new string(reader.ReadChars(4));
if (subchunk1ID != "fmt ") throw new Exception("Expected fmt chunk.");
int subChunk1Size = reader.ReadInt32();
short audioFormat = reader.ReadInt16();
var NumChannels = reader.ReadInt16();
var SampleRate = reader.ReadInt32();
var ByteRate = reader.ReadInt32(); // Byte rate
var BlockAlign = reader.ReadInt16(); // Block align
var BitsPerSample = reader.ReadInt16();
_header = new WaveHeader(SampleRate, ChunkSize, ByteRate, BitsPerSample, NumChannels, BlockAlign);
if (audioFormat != 1 || subChunk1Size != 16)
{
throw new Exception("Only PCM format WAV files are supported.");
}
// data sub-chunk
string subchunk2ID = new string(reader.ReadChars(4));
if (subchunk2ID != "data") throw new Exception("Expected data chunk.");
int subChunk2Size = reader.ReadInt32();
AudioData = reader.ReadBytes(subChunk2Size);
}
}
}
}

View File

@ -0,0 +1,79 @@
using System;
using System.IO;
namespace Ryuclaw.Audio.Shared
{
public class WaveStreamer : IDisposable
{
private readonly FileStream _fileStream;
private readonly BinaryReader _reader;
private long _dataPosition; // This will store the position where audio data begins in the file.
private int _dataLength; // This will store the length of the audio data.
private WaveHeader _header;
public WaveStreamer(string filePath)
{
_header = new WaveHeader();
_fileStream = new FileStream(filePath, FileMode.Open);
_reader = new BinaryReader(_fileStream);
LoadHeader();
}
private void LoadHeader()
{
// RIFF header
string chunkID = new string(_reader.ReadChars(4));
if (chunkID != "RIFF") throw new Exception("Not a valid WAV file.");
var ChunkSize = _reader.ReadInt32(); // Chunk size
string format = new string(_reader.ReadChars(4));
if (format != "WAVE") throw new Exception("Not a valid WAV file.");
// fmt sub-chunk
string subchunk1ID = new string(_reader.ReadChars(4));
if (subchunk1ID != "fmt ") throw new Exception("Expected fmt chunk.");
int subChunk1Size = _reader.ReadInt32();
short audioFormat = _reader.ReadInt16();
var NumChannels = _reader.ReadInt16();
var SampleRate = _reader.ReadInt32();
var ByteRate = _reader.ReadInt32(); // Byte rate
var BlockAlign = _reader.ReadInt16(); // Block align
var BitsPerSample = _reader.ReadInt16();
_header = new WaveHeader(SampleRate, ChunkSize, ByteRate, BitsPerSample, NumChannels, BlockAlign);
if (audioFormat != 1 || subChunk1Size != 16)
{
throw new Exception("Only PCM format WAV files are supported.");
}
// data sub-chunk
string subchunk2ID = new string(_reader.ReadChars(4));
if (subchunk2ID != "data") throw new Exception("Expected data chunk.");
_dataLength = _reader.ReadInt32();
_dataPosition = _fileStream.Position; // Store the position where audio data begins.
}
public byte[] ReadData(int byteCount)
{
_fileStream.Seek(_dataPosition, SeekOrigin.Begin);
byte[] buffer = _reader.ReadBytes(byteCount); // Read a chunk of audio data.
_dataPosition = _fileStream.Position; // Update the position to start from next time.
return buffer;
}
public bool HasDataLeft()
{
return _fileStream.Position < _dataPosition + _dataLength;
}
public void Dispose()
{
_reader.Dispose();
_fileStream.Dispose();
}
}
}

View File

@ -9,13 +9,26 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<CreatePackage>false</CreatePackage> <CreatePackage>false</CreatePackage>
<UseSGen>false</UseSGen> <UseSGen>false</UseSGen>
<WarningLevel>4</WarningLevel>
<LinkMode>None</LinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<CreatePackage>false</CreatePackage> <CreatePackage>false</CreatePackage>
<UseSGen>false</UseSGen> <UseSGen>false</UseSGen>
<LinkMode>None</LinkMode>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryuclaw.Shared\Ryuclaw.Shared.csproj" /> <ProjectReference Include="..\Ryuclaw.Shared\Ryuclaw.Shared.csproj" />
<ProjectReference Include="..\Ryuclaw.Speech.MacOS\Ryuclaw.Speech.MacOS.csproj" /> <ProjectReference Include="..\Ryuclaw.Speech.MacOS\Ryuclaw.Speech.MacOS.csproj" />
<ProjectReference Include="..\Ryuclaw.Audio.Desktop\Ryuclaw.Audio.Desktop.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="dylibs/soft_oal.dll.dylib" Link="soft_oal.dll.dylib">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="dylibs\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,10 +1,13 @@
using ObjCRuntime; using ObjCRuntime;
using Ryuclaw.Speech.MacOS; using Ryuclaw.Speech.MacOS;
using Ryuclaw.Audio.Desktop;
namespace Ryuclaw.Client.Mac; namespace Ryuclaw.Client.Mac;
public partial class ViewController : NSViewController { public partial class ViewController : NSViewController {
private TTS synth; private TTS synth = new TTS();
private AudioEngine engine = new AudioEngine();
protected ViewController (NativeHandle handle) : base (handle) protected ViewController (NativeHandle handle) : base (handle)
{ {
// This constructor is required if the view controller is loaded from a xib or a storyboard. // This constructor is required if the view controller is loaded from a xib or a storyboard.
@ -16,8 +19,10 @@ public partial class ViewController : NSViewController {
base.ViewDidLoad (); base.ViewDidLoad ();
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
synth = new TTS(); synth.SetVoice("Alex");
synth.Speak("Rawr, world!"); synth.Speak("Rawr, rawr!");
engine.Start();
} }
public override NSObject RepresentedObject { public override NSObject RepresentedObject {

Binary file not shown.

View File

@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryuclaw.Audio.Desktop", "Ry
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryuclaw.Client.Mac", "Ryuclaw.Client.Mac\Ryuclaw.Client.Mac.csproj", "{0C6AE29F-C44B-4876-ABE9-D36C79158BBD}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryuclaw.Client.Mac", "Ryuclaw.Client.Mac\Ryuclaw.Client.Mac.csproj", "{0C6AE29F-C44B-4876-ABE9-D36C79158BBD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryuclaw.Audio.Shared", "Ryuclaw.Audio.Shared\Ryuclaw.Audio.Shared.csproj", "{B03FE841-5714-43BA-9084-7259FFC78837}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -39,6 +41,10 @@ Global
{0C6AE29F-C44B-4876-ABE9-D36C79158BBD}.Debug|Any CPU.Build.0 = Debug|Any CPU {0C6AE29F-C44B-4876-ABE9-D36C79158BBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C6AE29F-C44B-4876-ABE9-D36C79158BBD}.Release|Any CPU.ActiveCfg = Release|Any CPU {0C6AE29F-C44B-4876-ABE9-D36C79158BBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C6AE29F-C44B-4876-ABE9-D36C79158BBD}.Release|Any CPU.Build.0 = Release|Any CPU {0C6AE29F-C44B-4876-ABE9-D36C79158BBD}.Release|Any CPU.Build.0 = Release|Any CPU
{B03FE841-5714-43BA-9084-7259FFC78837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B03FE841-5714-43BA-9084-7259FFC78837}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B03FE841-5714-43BA-9084-7259FFC78837}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B03FE841-5714-43BA-9084-7259FFC78837}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE