Initial sound impl
parent
06c521a981
commit
f4f88f711a
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"dotnet.defaultSolution": "Ryuclaw.Mac.sln"
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Ryuclaw.Audio.Desktop;
|
|
||||||
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Ryuclaw.Audio.Shared;
|
||||||
|
|
||||||
|
public class Class1
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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.
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue