SHA256
84 lines
3.4 KiB
C#
84 lines
3.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace ADOFAIRenderer.Util.Audio.Clip;
|
|
|
|
/// <summary>
|
|
/// Forward-only <see cref="IPCMReader"/> over PCM held in memory as a list of blocks.
|
|
/// Block b holds frames [b * FramesPerBlock, (b + 1) * FramesPerBlock) interleaved; the last block may be shorter.
|
|
/// Each block is released (passed to the release callback and dropped) as soon as the read position passes its end,
|
|
/// and all remaining blocks are released on <see cref="Dispose"/>.
|
|
/// </summary>
|
|
internal sealed class MemoryPCMReader : IPCMReader
|
|
{
|
|
private readonly float[]?[] _blocks;
|
|
private readonly Action<float[]>? _onBlockReleased;
|
|
private int _releasedCount; // Blocks [0, _releasedCount) are released
|
|
|
|
public int Channels { get; }
|
|
public int SampleRate { get; }
|
|
public long TotalFrames { get; }
|
|
public bool IsForwardOnly => true;
|
|
public long Position { get; private set; }
|
|
|
|
/// <summary>Frames per block (all blocks except the last).</summary>
|
|
public int FramesPerBlock { get; }
|
|
|
|
public MemoryPCMReader(IReadOnlyList<float[]> blocks, int framesPerBlock, int channels, int sampleRate, long totalFrames,
|
|
Action<float[]>? onBlockReleased)
|
|
{
|
|
if (channels <= 0) throw new ArgumentOutOfRangeException(nameof(channels));
|
|
if (framesPerBlock <= 0) throw new ArgumentOutOfRangeException(nameof(framesPerBlock));
|
|
if (totalFrames > (long)blocks.Count * framesPerBlock) throw new ArgumentException("Blocks hold fewer frames than totalFrames.", nameof(blocks));
|
|
|
|
_blocks = new float[blocks.Count][];
|
|
for (var i = 0; i < blocks.Count; i++) _blocks[i] = blocks[i];
|
|
_onBlockReleased = onBlockReleased;
|
|
FramesPerBlock = framesPerBlock;
|
|
Channels = channels;
|
|
SampleRate = sampleRate;
|
|
TotalFrames = totalFrames;
|
|
}
|
|
|
|
public void Seek(long frame)
|
|
{
|
|
if (frame < 0 || frame > TotalFrames) throw new ArgumentOutOfRangeException(nameof(frame));
|
|
if (frame < (long)_releasedCount * FramesPerBlock)
|
|
throw new InvalidOperationException($"Frame {frame} was already released by the forward-only reader.");
|
|
Position = frame;
|
|
}
|
|
|
|
public int Read(float[] buffer, int frameCount)
|
|
{
|
|
var total = (int)Math.Min(frameCount, TotalFrames - Position);
|
|
if (total <= 0) return 0;
|
|
|
|
var written = 0;
|
|
while (written < total)
|
|
{
|
|
var blockIndex = (int)(Position / FramesPerBlock);
|
|
var block = _blocks[blockIndex] ?? throw new InvalidOperationException($"Block {blockIndex} was already released.");
|
|
var offset = (int)(Position - (long)blockIndex * FramesPerBlock);
|
|
var count = Math.Min(total - written, FramesPerBlock - offset);
|
|
Array.Copy(block, offset * Channels, buffer, written * Channels, count * Channels);
|
|
written += count;
|
|
Position += count;
|
|
ReleaseBlocksBefore((int)(Position / FramesPerBlock));
|
|
}
|
|
return written;
|
|
}
|
|
|
|
private void ReleaseBlocksBefore(int blockIndex)
|
|
{
|
|
while (_releasedCount < blockIndex && _releasedCount < _blocks.Length)
|
|
{
|
|
var block = _blocks[_releasedCount];
|
|
_blocks[_releasedCount] = null;
|
|
_releasedCount++;
|
|
if (block != null) _onBlockReleased?.Invoke(block);
|
|
}
|
|
}
|
|
|
|
public void Dispose() => ReleaseBlocksBefore(_blocks.Length);
|
|
}
|