-
-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* start implementing node graph * update Sekai * remove previous rendering impl * add coordinate providing interfaces * add `Node` * add `Effect` * add material-related interfaces * add `ShaderMaterial` * add `UnlitMaterial` * add `Behavior` * add `Renderable` * add `RenderGroup`s * add `RenderObject`s * add `RenderData` * add `RenderQueue` and `RenderContext` * add `RenderTarget` * add `Renderer` * add `SortedFilteredCollection<T>` * add `Drawable` * add projectors * add `World` * add `Window` node * add executable * ensure material resources are applied * remove `Renderable` * cleanup pass * add `ServiceLocator` * add `Node.Services` * make `Window` override `Services` * refactor `World` * start implementing content manager * add `ContentManager` and `IContentLoader` * add shader and texture loaders * include stbisharp * load content manager * add more constructors to `UnlitMaterial` * start working on audio * add object pool interface * add `AudioStream` * add loader for `.wav` files * add audio controllers and managers * add manager to game --------- Co-authored-by: Ayase Minori <[email protected]>
- Loading branch information
Showing
6 changed files
with
470 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright (c) Cosyne | ||
// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. | ||
|
||
namespace Vignette.Allocation; | ||
|
||
/// <summary> | ||
/// Defines a mechanism for objects that can pool <typeparamref name="T"/>. | ||
/// </summary> | ||
/// <typeparam name="T">The type of object being pooled.</typeparam> | ||
public interface IObjectPool<T> | ||
{ | ||
/// <summary> | ||
/// Gets one <typeparamref name="T"/> from the pool. | ||
/// </summary> | ||
T Get(); | ||
|
||
/// <summary> | ||
/// Returns <typeparamref name="T"/> back to the pool. | ||
/// </summary> | ||
/// <param name="item">The <typeparamref name="T"/> to return.</param> | ||
/// <returns><see langword="true"/> if the item has been returned. Otherwise, <see langword="false"/>.</returns> | ||
bool Return(T item); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
// Copyright (c) Cosyne | ||
// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. | ||
|
||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using Sekai.Audio; | ||
using Vignette.Allocation; | ||
|
||
namespace Vignette.Audio; | ||
|
||
public sealed class AudioManager : IObjectPool<AudioBuffer> | ||
{ | ||
private const int max_buffer_size = 8192; | ||
private const int max_buffer_count = 500; | ||
private readonly AudioDevice device; | ||
private readonly ConcurrentBag<AudioBuffer> bufferPool = new(); | ||
private readonly List<StreamingAudioController> controllers = new(); | ||
|
||
internal AudioManager(AudioDevice device) | ||
{ | ||
this.device = device; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new <see cref="IAudioController"/> for a <see cref="AudioStream"/>. | ||
/// </summary> | ||
/// <param name="stream">The audio stream to attach to the controller.</param> | ||
/// <returns>An audio controller.</returns> | ||
public IAudioController GetController(AudioStream stream) | ||
{ | ||
return new StreamingAudioController(device.CreateSource(), stream, this); | ||
} | ||
|
||
internal void Update() | ||
{ | ||
for (int i = 0; i < controllers.Count; i++) | ||
{ | ||
controllers[i].Update(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Returns an <see cref="IAudioController"/> back to the <see cref="AudioManager"/>. | ||
/// </summary> | ||
/// <param name="controller">The controller to return.</param> | ||
public void Return(IAudioController controller) | ||
{ | ||
if (controller is not StreamingAudioController streaming) | ||
{ | ||
return; | ||
} | ||
|
||
if (!controllers.Remove(streaming)) | ||
{ | ||
return; | ||
} | ||
|
||
streaming.Dispose(); | ||
} | ||
|
||
AudioBuffer IObjectPool<AudioBuffer>.Get() | ||
{ | ||
if (!bufferPool.TryTake(out var buffer)) | ||
{ | ||
buffer = device.CreateBuffer(); | ||
} | ||
|
||
return buffer; | ||
} | ||
|
||
bool IObjectPool<AudioBuffer>.Return(AudioBuffer item) | ||
{ | ||
if (bufferPool.Count >= max_buffer_count) | ||
{ | ||
item.Dispose(); | ||
return false; | ||
} | ||
|
||
bufferPool.Add(item); | ||
return true; | ||
} | ||
|
||
private sealed class StreamingAudioController : IAudioController, IDisposable | ||
{ | ||
public bool Loop { get; set; } | ||
|
||
public TimeSpan Position | ||
{ | ||
get => getTimeFromByteCount((int)stream.Position, stream.Format, stream.SampleRate); | ||
set => seek(getByteCountFromTime(value, stream.Format, stream.SampleRate)); | ||
} | ||
|
||
public TimeSpan Duration => getTimeFromByteCount((int)stream.Length, stream.Format, stream.SampleRate); | ||
|
||
public TimeSpan Buffered => getTimeFromByteCount(buffered, stream.Format, stream.SampleRate); | ||
|
||
public AudioSourceState State => source.State; | ||
|
||
private int buffered; | ||
private bool isDisposed; | ||
private const int max_buffer_stream = 4; | ||
private readonly AudioSource source; | ||
private readonly AudioStream stream; | ||
private readonly IObjectPool<AudioBuffer> bufferPool; | ||
|
||
public StreamingAudioController(AudioSource source, AudioStream stream, IObjectPool<AudioBuffer> bufferPool) | ||
{ | ||
this.source = source; | ||
this.stream = stream; | ||
this.bufferPool = bufferPool; | ||
} | ||
|
||
public void Play() | ||
{ | ||
if (State != AudioSourceState.Paused) | ||
{ | ||
seek(0); | ||
|
||
for (int i = 0; i < max_buffer_stream; i++) | ||
{ | ||
var buffer = bufferPool.Get(); | ||
|
||
if (!allocate(buffer)) | ||
{ | ||
break; | ||
} | ||
|
||
source.Enqueue(buffer); | ||
} | ||
} | ||
|
||
source.Play(); | ||
} | ||
|
||
public void Stop() | ||
{ | ||
seek(0); | ||
} | ||
|
||
public void Pause() | ||
{ | ||
source.Pause(); | ||
} | ||
|
||
public void Update() | ||
{ | ||
while (source.TryDequeue(out var buffer)) | ||
{ | ||
if (!allocate(buffer)) | ||
{ | ||
source.Loop = Loop; | ||
break; | ||
} | ||
|
||
source.Enqueue(buffer); | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
if (isDisposed) | ||
{ | ||
return; | ||
} | ||
|
||
source.Stop(); | ||
|
||
while(source.TryDequeue(out var buffer)) | ||
{ | ||
bufferPool.Return(buffer); | ||
} | ||
|
||
source.Dispose(); | ||
|
||
isDisposed = false; | ||
} | ||
|
||
private void seek(int position) | ||
{ | ||
source.Stop(); | ||
source.Clear(); | ||
stream.Position = buffered = position; | ||
} | ||
|
||
private bool allocate(AudioBuffer buffer) | ||
{ | ||
Span<byte> data = stackalloc byte[max_buffer_size]; | ||
int read = stream.Read(data); | ||
|
||
if (read <= 0) | ||
{ | ||
return false; | ||
} | ||
|
||
buffer.SetData<byte>(data[..read], stream.Format, stream.SampleRate); | ||
buffered += read; | ||
|
||
return true; | ||
} | ||
} | ||
|
||
private static int getChannelCount(AudioFormat format) | ||
{ | ||
return format is AudioFormat.Stereo8 or AudioFormat.Stereo16 ? 2 : 1; | ||
} | ||
|
||
private static int getSamplesCount(AudioFormat format) | ||
{ | ||
return format is AudioFormat.Stereo8 or AudioFormat.Mono8 ? 8 : 16; | ||
} | ||
|
||
private static int getByteCountFromTime(TimeSpan time, AudioFormat format, int sampleRate) | ||
{ | ||
return (int)time.TotalSeconds * sampleRate * getChannelCount(format) * (getSamplesCount(format) / 8); | ||
} | ||
|
||
private static TimeSpan getTimeFromByteCount(int count, AudioFormat format, int sampleRate) | ||
{ | ||
return TimeSpan.FromSeconds(count / (sampleRate * getChannelCount(format) * (getSamplesCount(format) / 8))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright (c) Cosyne | ||
// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. | ||
|
||
using System.IO; | ||
using Sekai.Audio; | ||
|
||
namespace Vignette.Audio; | ||
|
||
/// <summary> | ||
/// A <see cref="Stream"/> containing pulse code modulation (PCM) audio data. | ||
/// </summary> | ||
public class AudioStream : Stream | ||
{ | ||
/// <summary> | ||
/// The audio stream's sample rate | ||
/// </summary> | ||
public int SampleRate { get; } | ||
|
||
/// <summary> | ||
/// The audio stream's format. | ||
/// </summary> | ||
public AudioFormat Format { get; } | ||
|
||
public override bool CanRead => stream.CanRead; | ||
|
||
public override bool CanSeek => stream.CanSeek; | ||
|
||
public override bool CanWrite => stream.CanWrite; | ||
|
||
public override long Length => stream.Length; | ||
|
||
public override long Position | ||
{ | ||
get => stream.Position; | ||
set => stream.Position = value; | ||
} | ||
|
||
private bool isDisposed; | ||
private readonly MemoryStream stream; | ||
|
||
public AudioStream(byte[] buffer, AudioFormat format, int sampleRate) | ||
: this(buffer, true, format, sampleRate) | ||
{ | ||
} | ||
|
||
public AudioStream(byte[] buffer, bool writable, AudioFormat format, int sampleRate) | ||
{ | ||
Format = format; | ||
stream = new MemoryStream(buffer, writable); | ||
SampleRate = sampleRate; | ||
} | ||
|
||
public AudioStream(byte[] buffer, int index, int count, AudioFormat format, int sampleRate) | ||
: this(buffer, index, count, true, format, sampleRate) | ||
{ | ||
} | ||
|
||
public AudioStream(byte[] buffer, int index, int count, bool writable, AudioFormat format, int sampleRate) | ||
{ | ||
Format = format; | ||
stream = new MemoryStream(buffer, index, count, writable); | ||
SampleRate = sampleRate; | ||
} | ||
|
||
public AudioStream(int capacity, AudioFormat format, int sampleRate) | ||
{ | ||
Format = format; | ||
stream = new MemoryStream(capacity); | ||
SampleRate = sampleRate; | ||
} | ||
|
||
public AudioStream(AudioFormat format, int sampleRate) | ||
: this(0, format, sampleRate) | ||
{ | ||
} | ||
|
||
public override void Flush() | ||
{ | ||
stream.Flush(); | ||
} | ||
|
||
public override int Read(byte[] buffer, int offset, int count) | ||
{ | ||
return stream.Read(buffer, offset, count); | ||
} | ||
|
||
public override long Seek(long offset, SeekOrigin origin) | ||
{ | ||
return stream.Seek(offset, origin); | ||
} | ||
|
||
public override void SetLength(long value) | ||
{ | ||
stream.SetLength(value); | ||
} | ||
|
||
public override void Write(byte[] buffer, int offset, int count) | ||
{ | ||
stream.Write(buffer, offset, count); | ||
} | ||
|
||
protected override void Dispose(bool disposing) | ||
{ | ||
if (isDisposed) | ||
{ | ||
return; | ||
} | ||
|
||
stream.Dispose(); | ||
|
||
isDisposed = true; | ||
} | ||
} |
Oops, something went wrong.