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>
|
||||
<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>
|
||||
</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' ">
|
||||
<CreatePackage>false</CreatePackage>
|
||||
<UseSGen>false</UseSGen>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LinkMode>None</LinkMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<CreatePackage>false</CreatePackage>
|
||||
<UseSGen>false</UseSGen>
|
||||
<LinkMode>None</LinkMode>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryuclaw.Shared\Ryuclaw.Shared.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>
|
||||
</Project>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
using ObjCRuntime;
|
||||
using Ryuclaw.Speech.MacOS;
|
||||
using Ryuclaw.Audio.Desktop;
|
||||
|
||||
namespace Ryuclaw.Client.Mac;
|
||||
|
||||
public partial class ViewController : NSViewController {
|
||||
private TTS synth;
|
||||
private TTS synth = new TTS();
|
||||
private AudioEngine engine = new AudioEngine();
|
||||
|
||||
protected ViewController (NativeHandle handle) : base (handle)
|
||||
{
|
||||
// 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 ();
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
synth = new TTS();
|
||||
synth.Speak("Rawr, world!");
|
||||
synth.SetVoice("Alex");
|
||||
synth.Speak("Rawr, rawr!");
|
||||
|
||||
engine.Start();
|
||||
}
|
||||
|
||||
public override NSObject RepresentedObject {
|
||||
|
|
Binary file not shown.
|
@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryuclaw.Audio.Desktop", "Ry
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryuclaw.Client.Mac", "Ryuclaw.Client.Mac\Ryuclaw.Client.Mac.csproj", "{0C6AE29F-C44B-4876-ABE9-D36C79158BBD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryuclaw.Audio.Shared", "Ryuclaw.Audio.Shared\Ryuclaw.Audio.Shared.csproj", "{B03FE841-5714-43BA-9084-7259FFC78837}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
Loading…
Reference in New Issue