Skip to content

Commit

Permalink
Merge pull request #691 from b-editor/wave-support
Browse files Browse the repository at this point in the history
Waveファイルを読み込めるようにした
  • Loading branch information
yuto-trd authored Sep 3, 2023
2 parents 62e30ce + d766ff2 commit 2583f2e
Show file tree
Hide file tree
Showing 11 changed files with 646 additions and 14 deletions.
10 changes: 10 additions & 0 deletions src/Beutl.Core/Rational.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,16 @@ public Rational Simplify()
return new Rational(left.Numerator * right.Numerator, left.Denominator * right.Denominator);
}

public static Rational operator *(Rational left, int right)
{
return new Rational(left.Numerator * right, left.Denominator);
}

public static Rational operator *(Rational left, long right)
{
return new Rational(left.Numerator * right, left.Denominator);
}

public static Rational operator /(Rational left, Rational right)
{
if (right.Numerator == 0)
Expand Down
25 changes: 19 additions & 6 deletions src/Beutl.Engine/Media/Decoding/DecoderRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
namespace Beutl.Media.Decoding;
using Beutl.Media.Wave;

namespace Beutl.Media.Decoding;

public static class DecoderRegistry
{
private static readonly List<IDecoderInfo> _registered = new();
private static readonly List<IDecoderInfo> s_registered = new()
{
new WaveDecoderInfo()
};

public static IEnumerable<IDecoderInfo> EnumerateDecoder()
{
return _registered;
return s_registered;
}

public static MediaReader? OpenMediaFile(string file, MediaOptions options)
{
return GuessDecoder(file).FirstOrDefault()?.Open(file, options);
foreach (IDecoderInfo decoder in GuessDecoder(file))
{
if (decoder.Open(file, options) is { } reader)
{
return reader;
}
}

return null;
}

public static IDecoderInfo[] GuessDecoder(string file)
{
return _registered.Where(i => i.IsSupported(file)).ToArray();
return s_registered.Where(i => i.IsSupported(file)).ToArray();
}

public static void Register(IDecoderInfo decoder)
{
_registered.Add(decoder);
s_registered.Add(decoder);
}
}
13 changes: 9 additions & 4 deletions src/Beutl.Engine/Media/Music/Pcm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,22 @@ public Pcm<T> Resamples(int frequency)
if (SampleRate == frequency) return Clone();

// 比率
float ratio = SampleRate / (float)frequency;
double ratio = SampleRate / (double)frequency;

// 1チャンネルのサイズ
int size = (int)(frequency * DurationRational.ToSingle());
int bits = sizeof(T) * 8;
int size = (int)(frequency * bits * DurationRational.ToDouble() / bits);

T* tmp = (T*)NativeMemory.AllocZeroed((nuint)(sizeof(T) * size));
float index = 0f;
double index = 0f;
for (int i = 0; i < size; i++)
{
index += ratio;
tmp[i] = DataSpan[(int)Math.Floor(index)];
int indexFloor = (int)Math.Floor(index - 1);
if (0 <= indexFloor && indexFloor < DataSpan.Length)
{
tmp[i] = DataSpan[indexFloor];
}
}

var result = new Pcm<T>(frequency, size);
Expand Down
140 changes: 140 additions & 0 deletions src/Beutl.Engine/Media/Wave/WaveAnalysis.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using static Beutl.Media.Wave.WaveBitConverter;

namespace Beutl.Media.Wave;

public sealed class WaveAnalysis
{
public WaveAnalysis(Stream stream)
{
string errormsg = "It is not a WAVE file.";

// RIFF
Span<byte> intBytes = stackalloc byte[4];
stream.ReadExactly(intBytes);
if (!intBytes.SequenceEqual("RIFF"u8))
{
throw new Exception(errormsg);
}

stream.Position += 4;

// WAVE
stream.ReadExactly(intBytes);
if (!intBytes.SequenceEqual("WAVE"u8))
{
throw new Exception(errormsg);
}

// fmt
int len;
short wFormatTag, nChannels, nBlockAlign, wBitsPerSample;
int nSamplesPerSec, nAvgBytesPerSec;
while (true)
{
stream.ReadExactly(intBytes);
if (intBytes.SequenceEqual("fmt "u8))
{
stream.ReadExactly(intBytes);
len = ToInt32(intBytes);

Span<byte> shortBytes = stackalloc byte[2];

// 種類(1:リニアPCM)
stream.ReadExactly(shortBytes);
wFormatTag = ToInt16(shortBytes);

// チャンネル数(1:モノラル 2:ステレオ)
stream.ReadExactly(shortBytes);
nChannels = ToInt16(shortBytes);

// サンプリングレート(44100=44.1kHzなど)
stream.ReadExactly(intBytes);
nSamplesPerSec = ToInt32(intBytes);

// 平均データ転送レート(byte/sec)
// ※PCMの場合はnSamplesPerSec * nBlockAlign
stream.ReadExactly(intBytes);
nAvgBytesPerSec = ToInt32(intBytes);

// ブロックサイズ
// ※PCMの場合はwBitsPerSample * nChannels / 8
stream.ReadExactly(shortBytes);
nBlockAlign = ToInt16(shortBytes);

// サンプルあたりのビット数 (bit/sample)
// ※PCMの場合は8bit=8, 16bit =16
stream.ReadExactly(shortBytes);
wBitsPerSample = ToInt16(shortBytes);

// WaveFomatExなどの対策
stream.Position = stream.Position + len - 16;

break;
}
else
{
stream.ReadExactly(intBytes);
len = ToInt32(intBytes);
stream.Position += len;
}

if (stream.Position >= stream.Length)
{
throw new Exception(errormsg);
}
}

// data
byte[] raw;
while (true)
{
stream.ReadExactly(intBytes);
if (intBytes.SequenceEqual("data"u8))
{
stream.ReadExactly(intBytes);
len = ToInt32(intBytes);

raw = new byte[len];
stream.ReadExactly(raw);

break;
}
else
{
stream.ReadExactly(intBytes);
len = ToInt32(intBytes);
stream.Position += len;
}

if (stream.Position >= stream.Length)
{
throw new Exception(errormsg);
}
}

// WaveFomat構造体(アクセス用)
WaveFomat = new WaveFormat
{
FormatTag = (WaveFormatTag)wFormatTag,
Channels = nChannels,
SamplesPerSec = nSamplesPerSec,
AvgBytesPerSec = nAvgBytesPerSec,
BlockAlign = nBlockAlign,
BitsPerSample = wBitsPerSample
};
// 波形データ
Raw = raw;
// 再生時間
Duration = new Rational(len, nAvgBytesPerSec);
// ビットレート (bps)
Bitrate = nSamplesPerSec * wBitsPerSample * nChannels;
}

public WaveFormat WaveFomat { get; }

public byte[] Raw { get; }

public Rational Duration { get; }

public int Bitrate { get; }
}
86 changes: 86 additions & 0 deletions src/Beutl.Engine/Media/Wave/WaveBitConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
namespace Beutl.Media.Wave;

internal static class WaveBitConverter
{
public static short ToInt16(ReadOnlySpan<byte> value)
{
return (short)(value[1] << 8 | value[0]);
}

public static int ToInt24(ReadOnlySpan<byte> value)
{
return value[2] << 16 | value[1] << 8 | value[0];
}

public static uint ToUInt24(ReadOnlySpan<byte> value)
{
return (uint)(value[2] << 16 | value[1] << 8 | value[0]);
}

public static int ToInt32(ReadOnlySpan<byte> value)
{
return value[3] << 24 | value[2] << 16 | value[1] << 8 | value[0];
}

public static float ToSingle(ReadOnlySpan<byte> value)
{
return BitConverter.ToSingle(value);
}

public static ushort ToUInt16(ReadOnlySpan<byte> value)
{
return (ushort)(value[1] << 8 | value[0]);
}

// Uint32からInt32へ
// ※0 ~ 4294967296 から -2147483648 ~ 2147483647へ
public static int ShiftInt32(uint x)
{
if (2147483648 <= x)
{
return (int)-(4294967296 - x);
}
else
{
return (int)x;
}
}

public static sbyte ShiftInt8(byte x)
{
if (128 == x) return 0;

if (129 >= x)
{
return (sbyte)(x - 128);
}
else
{
return (sbyte)-(128 - x);
}
}

public static short ShiftInt16(ushort x)
{
if (32768 <= x)
{
return (short)-(65536 - x);
}
else
{
return (short)x;
}
}

public static int ShiftInt24(uint x)
{
if (8388608 <= x)
{
return (int)-(16777216 - x);
}
else
{
return (int)x;
}
}
}
31 changes: 31 additions & 0 deletions src/Beutl.Engine/Media/Wave/WaveDecoderInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Beutl.Media.Decoding;

namespace Beutl.Media.Wave;

public sealed class WaveDecoderInfo : IDecoderInfo
{
public string Name => "Wave Reader (unsigned 8bit[PCM], signed 16bit[PCM], signed 24bit[PCM], signed 24bit[PCM], signed 32bit[PCM], 32bit float[IEEE Float])";

public IEnumerable<string> AudioExtensions()
{
yield return ".wav";
yield return ".wave";
}

public MediaReader? Open(string file, MediaOptions options)
{
try
{
return new WaveReader(file, options);
}
catch
{
return null;
}
}

public IEnumerable<string> VideoExtensions()
{
yield break;
}
}
16 changes: 16 additions & 0 deletions src/Beutl.Engine/Media/Wave/WaveFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Beutl.Media.Wave;

public record WaveFormat
{
public required WaveFormatTag FormatTag { get; init; }

public required short Channels { get; init; }

public required int SamplesPerSec { get; init; }

public required int AvgBytesPerSec { get; init; }

public required short BlockAlign { get; init; }

public required short BitsPerSample { get; init; }
}
9 changes: 9 additions & 0 deletions src/Beutl.Engine/Media/Wave/WaveFormatTag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Beutl.Media.Wave;

// https://github.com/TakeshiOkamoto/WAVE.wasm.js/tree/master
public enum WaveFormatTag : short
{
Pcm = 1,
IeeeFloat = 3,
MuLaw = 7
}
Loading

0 comments on commit 2583f2e

Please sign in to comment.