Fix bugs in AKF detection
Add CWavFile.cs for reading wav files Add more test cases
This commit is contained in:
1 parent
614a7e0ed2
commit
d584206a7f
9 files changed
+524
-17
No files matched your search
@@ -4,7 +4,7 @@
|
||||
#include <cmath>
|
||||
|
||||
float* short2FloatArray(short* in, size_t len){
|
||||
const float maxShort = 32768.0; //maximum abs value of a short
|
||||
const float maxShort = 32767.0;
|
||||
|
||||
float* result = static_cast<float*>(malloc(len * sizeof(float)));
|
||||
float* curOut = result;
|
||||
@@ -17,7 +17,7 @@ float* short2FloatArray(short* in, size_t len){
|
||||
}
|
||||
|
||||
double* short2DoubleArray(short* in, size_t len, bool scale = true){
|
||||
const double maxShort = 32768.0; //maximum abs value of a short
|
||||
const double maxShort = 32767.0;
|
||||
|
||||
double* result = static_cast<double*>(malloc(len * sizeof(double)));
|
||||
double* curOut = result;
|
||||
|
||||
@@ -75,7 +75,11 @@ DllExport void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt){
|
||||
if(sampleCt <= 0 || !analyzer)
|
||||
return;
|
||||
short* dataShort = static_cast<short*>(static_cast<void*>(data));
|
||||
analyzer->input(dataShort, dataShort + sampleCt);
|
||||
float* dataFloat = short2FloatArray(static_cast<short*>(static_cast<void*>(data)), sampleCt);
|
||||
analyzer->input(dataFloat, dataFloat + sampleCt);
|
||||
freeFloatArray(dataFloat);
|
||||
|
||||
//analyzer->input(dataShort, dataShort + sampleCt);
|
||||
}
|
||||
|
||||
DllExport int PtAKF_GetNote(PtAKF* analyzer, double* maxVolume, float* weights){
|
||||
|
||||
Binary file not shown.
+87
-6
@@ -24,9 +24,9 @@ float* PtAKF::_SamplesPerPeriodPerTone = NULL;
|
||||
PtAKF::PtAKF(unsigned step){
|
||||
_InitCount++;
|
||||
if(_InitCount == 1){
|
||||
_SamplesPerPeriodPerTone = new float[_MaxHalfTone + 1];
|
||||
_SamplesPerPeriodPerTone = new float[_MaxHalfTone + 1 + _HalfTonesAdd];
|
||||
//Init Array to avoid costly calculations
|
||||
for (int toneIndex = 0; toneIndex <= _MaxHalfTone; toneIndex++)
|
||||
for (int toneIndex = 0; toneIndex <= _MaxHalfTone + _HalfTonesAdd; toneIndex++)
|
||||
{
|
||||
double freq = BaseToneFrequency * pow(HalftoneBase, toneIndex);
|
||||
_SamplesPerPeriodPerTone[toneIndex] = static_cast<float>(44100.0 / freq); // samples in one period
|
||||
@@ -61,12 +61,23 @@ int PtAKF::_GetNote(float* samples, double* maxVolume, float* weights){
|
||||
if(vol > maxVolumeL)
|
||||
maxVolumeL = vol;
|
||||
}
|
||||
maxVolumeL /= 32767.;
|
||||
//maxVolumeL /= 32767.;
|
||||
*maxVolume = maxVolumeL;
|
||||
|
||||
//Scale the samples to have a peak of 1 to better decide between voiced/unvoiced
|
||||
//float scaleVal = 1 / maxVolumeL;
|
||||
//for(int i = 0; i < _SampleCt; i++){
|
||||
// samples[i] *= scaleVal;
|
||||
//}
|
||||
|
||||
float maxWeight = -1;
|
||||
float minWeight = 1;
|
||||
int maxTone = -1;
|
||||
|
||||
//Attention: We have a peak at lag 0 that might stretch that far, that we detect a wrong "peak" at _MaxHalfTone
|
||||
//Because of that we filter out all tones that are past the last zero crossing from below but keep tones with decreasing weights (going towards zero crossing from above)
|
||||
int lastValidTone = 0;
|
||||
float lastWeight = 1.f;
|
||||
for (int toneIndex = _MinHalfTone; toneIndex <= _MaxHalfTone; toneIndex++)
|
||||
{
|
||||
float curWeight = _AnalyzeToneFunc(samples, toneIndex);
|
||||
@@ -81,18 +92,61 @@ int PtAKF::_GetNote(float* samples, double* maxVolume, float* weights){
|
||||
minWeight = curWeight;
|
||||
|
||||
weights[toneIndex - _MinHalfTone] = curWeight;
|
||||
|
||||
//Filter:
|
||||
if(lastWeight > curWeight || (lastWeight > 0.f && curWeight <= 0.f))
|
||||
lastValidTone = toneIndex - 1;
|
||||
lastWeight = curWeight;
|
||||
}
|
||||
|
||||
if(maxWeight - minWeight > 0.01){
|
||||
if(maxTone==_MaxHalfTone){
|
||||
//We might have caught the lag 0 peak so go a bit further to check for other zero crossings (or we won't be able to detect _MaxHalfTone)
|
||||
float lastWeight = _FastAKF(samples, _SamplesPerPeriodPerTone[_MaxHalfTone]);
|
||||
for(int toneIndex = _MaxHalfTone+1; toneIndex <= _MaxHalfTone + _HalfTonesAdd; toneIndex++){
|
||||
float curWeight = _FastAKF(samples, _SamplesPerPeriodPerTone[toneIndex]);
|
||||
if(lastWeight > curWeight || (lastWeight > 0.f && curWeight <= 0.f)){
|
||||
lastValidTone = toneIndex - 1;
|
||||
break;
|
||||
}
|
||||
lastWeight = curWeight;
|
||||
}
|
||||
if(lastValidTone < maxTone){
|
||||
maxWeight = -1.f;
|
||||
minWeight = 1.f;
|
||||
for(int toneIndex = _MinHalfTone; toneIndex <= lastValidTone; toneIndex++){
|
||||
float curWeight = weights[toneIndex-_MinHalfTone];
|
||||
if (curWeight > maxWeight)
|
||||
{
|
||||
maxWeight = curWeight;
|
||||
maxTone = toneIndex;
|
||||
}
|
||||
|
||||
if (curWeight < minWeight)
|
||||
minWeight = curWeight;
|
||||
}
|
||||
//Set all invalid weights to 0
|
||||
for(int toneIndex = lastValidTone + 1;toneIndex < _MaxHalfTone; toneIndex++){
|
||||
weights[toneIndex - _MinHalfTone]=0.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float energy = _FastAKF(samples, 0);
|
||||
float maxAKF = _FastAKF(samples, _SamplesPerPeriodPerTone[maxTone]);
|
||||
//printf("%g %g\n",energy, maxAKF);
|
||||
|
||||
//if(maxWeight - minWeight > 0.025){
|
||||
if(maxAKF >= 0.3 * energy){
|
||||
return maxTone;
|
||||
}else return -1;
|
||||
}
|
||||
|
||||
float PtAKF::_AnalyzeToneFunc(float* samples, int toneIndex){
|
||||
// Use method by Kobayashi and Shimamura (1995): Combine AKF and AMDF to a new f(z)=AKF(z)/(AMDF(z)+k) with k=1
|
||||
// Use method by Kobayashi and Shimamura (2001): Combine AKF and AMDF to a new f(z)=AKF(z)/(AMDF(z)+k) with k=1
|
||||
|
||||
float samplesPerPeriodD = _SamplesPerPeriodPerTone[toneIndex]; // samples in one period
|
||||
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||
int maxSampleCt = static_cast<int>(_SamplesPerPeriodPerTone[0]);
|
||||
float fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||
float fLow = 1.0f - fHigh;
|
||||
|
||||
@@ -114,7 +168,34 @@ float PtAKF::_AnalyzeToneFunc(float* samples, int toneIndex){
|
||||
accumDistAKF /= _SampleCt;
|
||||
accumDistAMDF /= sampleIndex;
|
||||
|
||||
return accumDistAKF / (accumDistAMDF * 32767.f + 32767.f); // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||
//return accumDistAKF / (accumDistAMDF * 32767.f + 32767.f * 32767.f); // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||
float result = accumDistAKF / (accumDistAMDF + 1.f);
|
||||
return result; // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||
//{toneIndex}: {accumDistAKF} ; {accumDistAMDF}; {result}
|
||||
}
|
||||
|
||||
float PtAKF::_FastAKF(float* samples, float samplesPerPeriodD){
|
||||
// Do a fast, non-scaled AKF (used for Lag 0 peak removal)
|
||||
|
||||
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||
int maxSampleCt = static_cast<int>(_SamplesPerPeriodPerTone[0]);
|
||||
float fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||
float fLow = 1.0f - fHigh;
|
||||
|
||||
float accumDistAKF = 0; // accumulated distances
|
||||
|
||||
// compare correlating samples
|
||||
int sampleIndex = 0; // index of sample to analyze
|
||||
// Start value= index of sample one period ahead
|
||||
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex + 1 < _SampleCt; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance to corresponding sample in next period
|
||||
float xn = samples[sampleIndex];
|
||||
float xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh;
|
||||
accumDistAKF += xn * xnt;
|
||||
}
|
||||
|
||||
return accumDistAKF;
|
||||
}
|
||||
/*
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ public:
|
||||
PtAKF(unsigned step);
|
||||
~PtAKF();
|
||||
|
||||
void input(short* begin, short* end){
|
||||
/** Add input data to buffer. This is thread-safe (against other functions). **/
|
||||
template <typename InIt> void input(InIt begin, InIt end) {
|
||||
_AnalysisBuf.insert(begin, end);
|
||||
}
|
||||
|
||||
@@ -17,12 +18,14 @@ private:
|
||||
static int _InitCount;
|
||||
static float* _SamplesPerPeriodPerTone;
|
||||
static constexpr int _MinHalfTone = 0; //C2
|
||||
static constexpr int _MaxHalfTone = 47; //B5
|
||||
static constexpr int _MaxHalfTone = 55;//47; //B5
|
||||
static constexpr int _HalfTonesAdd = 4; //Additonal half tones to analyze to remove the peak at lag 0
|
||||
|
||||
constexpr static size_t _SampleCt = 4096;
|
||||
constexpr static size_t _SampleCt = 2048;
|
||||
RingBuffer<_SampleCt * 2> _AnalysisBuf;
|
||||
unsigned _Step;
|
||||
|
||||
static int _GetNote(float* samples, double* maxVolume, float* weights);
|
||||
static float _AnalyzeToneFunc(float* samples, int toneIndex);
|
||||
static float _FastAKF(float* samples, float samplesPerPeriodD);
|
||||
};
|
||||
@@ -164,6 +164,9 @@ namespace Vocaluxe.Base
|
||||
public static void LogDebug(string text)
|
||||
{
|
||||
_DebugLog.Add(String.Format("{0:HH:mm:ss.ffff}", DateTime.Now) + ":" + text);
|
||||
#if DEBUG
|
||||
Console.WriteLine(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogSongInfo(string text)
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
|
||||
using System;
|
||||
#if TEST_PITCH
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using System.Diagnostics;
|
||||
using VocaluxeLib;
|
||||
using VocaluxeLib.Utils;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,11 +33,12 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
class CBuffer : IDisposable
|
||||
{
|
||||
#if TEST_PITCH
|
||||
//Half tones: C C♯ D D# E F F♯ G G♯ A A# B
|
||||
//Half tones: C C# D D# E F F# G G# A A# B
|
||||
private const double _BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz)
|
||||
private const double _HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
#endif
|
||||
private CPtAKF _Analyzer = new CPtAKF();
|
||||
private CAnalyzer _Analyzer2 = new CAnalyzer();
|
||||
|
||||
private double _MaxVolume;
|
||||
|
||||
@@ -94,11 +98,12 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
//if (assigned(fVoiceStream)) then
|
||||
//fVoiceStream.WriteData(Buffer, BufferSize);
|
||||
_Analyzer.Input(buffer);
|
||||
_Analyzer2.Input(buffer);
|
||||
}
|
||||
|
||||
public void AnalyzeBuffer()
|
||||
{
|
||||
//_Analyzer.Process();
|
||||
_Analyzer2.Process();
|
||||
//_MaxVolume = _Analyzer.GetPeak() / 43 + 1;
|
||||
int tone = _Analyzer.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); //
|
||||
if (tone >= 0 && _MaxVolume >= MinVolume)
|
||||
@@ -132,7 +137,7 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
private static string _ToneToNote(int tone, bool withOctave = true)
|
||||
{
|
||||
tone += 24;
|
||||
string[] notes = {"C", "C♯", "D", "D#", "E", "F", "F♯", "G", "G♯", "A", "A#", "B"};
|
||||
string[] notes = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||
string result = notes[tone % 12];
|
||||
if (withOctave)
|
||||
result += (tone / 12);
|
||||
@@ -145,11 +150,12 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
return;
|
||||
_PitchTestRun = true;
|
||||
int toneFrom = 0;
|
||||
const int toneTo = 47; //B5
|
||||
const int toneTo = -1; // 47; //B5
|
||||
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
|
||||
byte[] data;
|
||||
byte[] data2 = new byte[2048];
|
||||
int ok = 0;
|
||||
int tests = 0;
|
||||
double angle = 0;
|
||||
for (int distort = 0; distort < 10; distort++)
|
||||
{
|
||||
@@ -167,11 +173,30 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
if (!ToneValid)
|
||||
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " not detected (Distortion: " + distort + ")");
|
||||
else if (Tone != tone % 12)
|
||||
{
|
||||
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " wrongly detected as " + _ToneToNote(Tone, false) + " (Distortion: " + distort + ")");
|
||||
CWavFile file = new CWavFile();
|
||||
file.Create(tone + "-" + distort + ".wav", 1, 44100, 16);
|
||||
file.Write16BitSamples(data, 1);
|
||||
file.Close();
|
||||
}
|
||||
else
|
||||
ok++;
|
||||
tests++;
|
||||
}
|
||||
}
|
||||
if (_TestFile("toneG3.wav", 19))
|
||||
ok++;
|
||||
tests++;
|
||||
if (_TestFile("toneG3Miss.wav", 19))
|
||||
ok++;
|
||||
tests++;
|
||||
if (_TestFile("toneG4.wav", 31))
|
||||
ok++;
|
||||
tests++;
|
||||
_TestFile("whistling3.wav", "whistling3.txt", ref tests, ref ok);
|
||||
_TestFile("sClausVoc.wav", "sClausVoc.txt", ref tests, ref ok);
|
||||
|
||||
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, ref angle, out data);
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
@@ -182,13 +207,108 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
AnalyzeBuffer();
|
||||
}
|
||||
sw.Stop();
|
||||
string msg = "Analyser: Errors: " + ((toneTo - toneFrom + 1) * 10 - ok) + "; Speed: " +
|
||||
string msg = "Analyser: Errors: " + (tests - ok) + "/" + tests + "; Speed: " +
|
||||
(int)(repeats / (sw.ElapsedMilliseconds / 1000.0)) + " buffers/s";
|
||||
Console.WriteLine(msg);
|
||||
MessageBox.Show(msg);
|
||||
Reset();
|
||||
}
|
||||
|
||||
private struct STimedNote
|
||||
{
|
||||
public int Time, Note;
|
||||
}
|
||||
|
||||
private void _TestFile(string fileName, string testFileName, ref int ct, ref int ok)
|
||||
{
|
||||
if (!File.Exists(testFileName))
|
||||
{
|
||||
ok = ct = 0;
|
||||
return;
|
||||
}
|
||||
List<STimedNote> tones = new List<STimedNote>();
|
||||
using (StreamReader reader = new StreamReader(testFileName))
|
||||
{
|
||||
String line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
int p = line.IndexOf(' ');
|
||||
if (p < 0)
|
||||
continue;
|
||||
STimedNote note;
|
||||
note.Time = int.Parse(line.Substring(0, p));
|
||||
note.Note = int.Parse(line.Substring(p + 1));
|
||||
tones.Add(note);
|
||||
}
|
||||
}
|
||||
_TestFile(fileName, tones, ref ct, ref ok);
|
||||
}
|
||||
|
||||
private bool _TestFile(string fileName, int tone)
|
||||
{
|
||||
STimedNote note;
|
||||
note.Note = tone;
|
||||
note.Time = 46;
|
||||
List<STimedNote> tones = new List<STimedNote> {note};
|
||||
int ct = 0;
|
||||
int ok = 0;
|
||||
_TestFile(fileName, tones, ref ct, ref ok);
|
||||
return ok >= ct - 4;
|
||||
}
|
||||
|
||||
private void _TestFile(string fileName, IList<STimedNote> tones, ref int ct, ref int ok)
|
||||
{
|
||||
CWavFile wavFile = new CWavFile();
|
||||
try
|
||||
{
|
||||
if (!wavFile.Open(fileName))
|
||||
return;
|
||||
if (wavFile.BitsPerSample != 16)
|
||||
{
|
||||
wavFile.Close();
|
||||
return;
|
||||
}
|
||||
int samplesRead = 0;
|
||||
int curTimeIndex = -1;
|
||||
int curNote = -1;
|
||||
const int maxSamplesPerBatch = 512;
|
||||
while (wavFile.NumSamplesLeft / wavFile.NumChannels > maxSamplesPerBatch)
|
||||
{
|
||||
byte[] samples = wavFile.GetNextSamples16BitAsBytes(maxSamplesPerBatch, 1);
|
||||
samplesRead += samples.Length / 2;
|
||||
int time = samplesRead * 1000 / wavFile.SampleRate;
|
||||
ProcessNewBuffer(samples);
|
||||
AnalyzeBuffer();
|
||||
while (curTimeIndex + 1 < tones.Count && time >= tones[curTimeIndex + 1].Time)
|
||||
{
|
||||
curTimeIndex++;
|
||||
curNote = tones[curTimeIndex].Note;
|
||||
}
|
||||
if (curNote < 0)
|
||||
continue;
|
||||
ct++;
|
||||
if (!ToneValid)
|
||||
CBase.Log.LogDebug("Note " + _ToneToNote(curNote) + " at " + time + "ms not detected");
|
||||
else if (ToneAbs % 12 != curNote % 12)
|
||||
CBase.Log.LogDebug("Note " + _ToneToNote(curNote) + " at " + time + "ms wrongly detected as " + _ToneToNote(ToneAbs));
|
||||
else
|
||||
ok++;
|
||||
int note2 = (int)Math.Round(_Analyzer2.FindNote());
|
||||
if (note2 < 0)
|
||||
CBase.Log.LogDebug("Note2 " + _ToneToNote(curNote) + " at " + time + "ms not detected");
|
||||
else if (note2 % 12 != curNote % 12)
|
||||
CBase.Log.LogDebug("Note2 " + _ToneToNote(curNote) + " at " + time + "ms wrongly detected as " + _ToneToNote(note2));
|
||||
else
|
||||
ok++;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Error on file " + fileName + ": " + e);
|
||||
}
|
||||
wavFile.Close();
|
||||
}
|
||||
|
||||
private static short[] _GetDistort(int len, int tone, int type)
|
||||
{
|
||||
short[] sdata = new short[len];
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace VocaluxeLib.Utils
|
||||
{
|
||||
//Simple class to read and write WAV files
|
||||
public class CWavFile
|
||||
{
|
||||
public short NumChannels { get; private set; }
|
||||
public int SampleRate { get; private set; }
|
||||
public short BitsPerSample { get; private set; }
|
||||
public string FileName { get; private set; }
|
||||
//Unless this is true, all fields are to be considered as invalid
|
||||
public int DataSize { get; private set; }
|
||||
//Number of samples (=SampesPerChannel*NumChannels)
|
||||
public int NumSamples
|
||||
{
|
||||
get { return (DataSize / (BitsPerSample / 8)); }
|
||||
}
|
||||
public int NumSamplesLeft { get; private set; }
|
||||
public bool IsOpen { get; private set; }
|
||||
private int _DataPos;
|
||||
private FileStream _File;
|
||||
private bool _IsWritable;
|
||||
|
||||
public CWavFile()
|
||||
{
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
~CWavFile()
|
||||
{
|
||||
Debug.Assert(!IsOpen);
|
||||
Close();
|
||||
}
|
||||
|
||||
public void Create(string fileName, short numChannels, int sampleRate, short bitsPerSample)
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
_File = new FileStream(fileName, FileMode.Create);
|
||||
FileName = fileName;
|
||||
NumChannels = numChannels;
|
||||
SampleRate = sampleRate;
|
||||
BitsPerSample = bitsPerSample;
|
||||
DataSize = 0;
|
||||
NumSamplesLeft = 0;
|
||||
IsOpen = true;
|
||||
_IsWritable = true;
|
||||
_WriteHeader();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!IsOpen)
|
||||
return;
|
||||
if (_IsWritable)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
writer.Seek(4, SeekOrigin.Begin);
|
||||
writer.Write((int)_File.Length - 8);
|
||||
writer.Seek(_DataPos - 4, SeekOrigin.Begin);
|
||||
writer.Write(DataSize);
|
||||
}
|
||||
_File.Dispose();
|
||||
_File = null;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public bool Open(string fileName)
|
||||
{
|
||||
if (IsOpen)
|
||||
Close();
|
||||
if (!File.Exists(fileName))
|
||||
return false;
|
||||
FileName = fileName;
|
||||
_File = new FileStream(FileName, FileMode.Open);
|
||||
try
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(_File);
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
|
||||
return false;
|
||||
reader.ReadInt32(); //Chunksize (file)
|
||||
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
|
||||
return false;
|
||||
long curPos = _File.Position;
|
||||
if (_SeekTo(reader, "fmt ") < 0)
|
||||
return false;
|
||||
short audioFormat = reader.ReadInt16();
|
||||
if (audioFormat != 1) //PCM
|
||||
return false;
|
||||
NumChannels = reader.ReadInt16();
|
||||
SampleRate = reader.ReadInt32();
|
||||
int bytesPerSec = reader.ReadInt32();
|
||||
short blockAlign = reader.ReadInt16();
|
||||
BitsPerSample = reader.ReadInt16();
|
||||
if (BitsPerSample != 8 && BitsPerSample != 16)
|
||||
return false;
|
||||
if (blockAlign != NumChannels * (BitsPerSample / 8))
|
||||
return false;
|
||||
if (bytesPerSec != blockAlign * SampleRate)
|
||||
return false;
|
||||
_File.Position = curPos;
|
||||
DataSize = _SeekTo(reader, "data");
|
||||
if (DataSize <= 0)
|
||||
return false;
|
||||
_DataPos = (int)_File.Position;
|
||||
DataSize = Math.Min(DataSize, (int)_File.Length - _DataPos);
|
||||
NumSamplesLeft = NumSamples;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_File.Dispose();
|
||||
throw;
|
||||
}
|
||||
IsOpen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of numSamples samples per channel for the selected channel(s)
|
||||
/// </summary>
|
||||
/// <param name="numSamples">Samples per channel</param>
|
||||
/// <param name="channel">Channel for which to get the samples (0=all)</param>
|
||||
/// <returns></returns>
|
||||
public byte[] GetNextSamples8Bit(int numSamples, int channel = 0)
|
||||
{
|
||||
if (channel < 0 || channel > NumChannels || BitsPerSample != 8)
|
||||
return null;
|
||||
int samplesPerBlock = (channel == 0 ? NumChannels : 1);
|
||||
if (NumSamplesLeft < numSamples * samplesPerBlock)
|
||||
return null;
|
||||
byte[] result;
|
||||
BinaryReader reader = new BinaryReader(_File);
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
result = reader.ReadBytes(numSamples * samplesPerBlock);
|
||||
else
|
||||
{
|
||||
result = new byte[numSamples];
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
{
|
||||
_File.Seek(channel - 1, SeekOrigin.Current);
|
||||
result[i] = reader.ReadByte();
|
||||
_File.Seek(NumChannels - channel, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
NumSamplesLeft -= numSamples * samplesPerBlock;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of numSamples samples per channel for the selected channel(s)
|
||||
/// </summary>
|
||||
/// <param name="numSamples">Samples per channel</param>
|
||||
/// <param name="channel">Channel for which to get the samples (0=all)</param>
|
||||
/// <returns></returns>
|
||||
public short[] GetNextSamples16Bit(int numSamples, int channel = 0)
|
||||
{
|
||||
if (channel < 0 || channel > NumChannels || BitsPerSample != 16)
|
||||
return null;
|
||||
int samplesPerBlock = (channel == 0 ? NumChannels : 1);
|
||||
if (NumSamplesLeft < numSamples * samplesPerBlock)
|
||||
return null;
|
||||
short[] result;
|
||||
BinaryReader reader = new BinaryReader(_File);
|
||||
try
|
||||
{
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
{
|
||||
result = new short[numSamples * NumChannels];
|
||||
for (int i = 0; i < numSamples * NumChannels; i++)
|
||||
result[i] = reader.ReadInt16();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new short[numSamples];
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
{
|
||||
_File.Seek((channel - 1) * 2, SeekOrigin.Current);
|
||||
result[i] = reader.ReadInt16();
|
||||
_File.Seek((NumChannels - channel) * 2, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
NumSamplesLeft -= numSamples * samplesPerBlock;
|
||||
}
|
||||
catch (EndOfStreamException e)
|
||||
{
|
||||
Console.WriteLine("Error: " + e);
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] GetNextSamples16BitAsBytes(int numSamples, int channel = 0)
|
||||
{
|
||||
short[] samples = GetNextSamples16Bit(numSamples, channel);
|
||||
if (samples == null)
|
||||
return null;
|
||||
byte[] samplesByte = new byte[samples.Length * 2];
|
||||
Buffer.BlockCopy(samples, 0, samplesByte, 0, samplesByte.Length);
|
||||
return samplesByte;
|
||||
}
|
||||
|
||||
public bool WriteSamples(byte[] samples, int channel = 0)
|
||||
{
|
||||
if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 8)
|
||||
return false;
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
writer.Write(samples);
|
||||
else
|
||||
{
|
||||
foreach (byte sample in samples)
|
||||
{
|
||||
_File.Seek(channel - 1, SeekOrigin.Current);
|
||||
writer.Write(sample);
|
||||
_File.Seek(NumChannels - channel, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
DataSize += samples.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Write16BitSamples(short[] samples, int channel = 0)
|
||||
{
|
||||
if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 16)
|
||||
return false;
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
if (channel == 0 || NumChannels == 1)
|
||||
{
|
||||
foreach (short sample in samples)
|
||||
writer.Write(sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (short sample in samples)
|
||||
{
|
||||
_File.Seek((channel - 1) * 2, SeekOrigin.Current);
|
||||
writer.Write(sample);
|
||||
_File.Seek((NumChannels - channel) * 2, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
DataSize += samples.Length * 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Write16BitSamples(byte[] samples, int channel = 0)
|
||||
{
|
||||
if (samples.Length == 0)
|
||||
return true;
|
||||
short[] samplesShort = new short[samples.Length / 2];
|
||||
Buffer.BlockCopy(samples, 0, samplesShort, 0, samples.Length);
|
||||
return Write16BitSamples(samplesShort, channel);
|
||||
}
|
||||
|
||||
private void _WriteHeader()
|
||||
{
|
||||
if (!IsOpen || !_IsWritable)
|
||||
return;
|
||||
BinaryWriter writer = new BinaryWriter(_File);
|
||||
writer.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||
writer.Write(0);
|
||||
writer.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||
writer.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||
writer.Write(16);
|
||||
writer.Write((short)1); // PCM
|
||||
writer.Write(NumChannels); // Channels
|
||||
writer.Write(SampleRate); // Sample rate
|
||||
writer.Write(SampleRate * NumChannels * (BitsPerSample / 8)); // Average bytes per second
|
||||
writer.Write((short)((BitsPerSample / 8) * NumChannels)); // block align
|
||||
writer.Write(BitsPerSample); // bits per sample
|
||||
writer.Write(Encoding.ASCII.GetBytes("data"));
|
||||
writer.Write(DataSize);
|
||||
_DataPos = (int)_File.Position;
|
||||
writer.Seek(4, SeekOrigin.Begin);
|
||||
writer.Write((int)writer.BaseStream.Length);
|
||||
writer.Seek(_DataPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
private static int _SeekTo(BinaryReader reader, string chunkId)
|
||||
{
|
||||
do
|
||||
{
|
||||
string curChunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
|
||||
int chunkSize = reader.ReadInt32();
|
||||
if (curChunkId == chunkId)
|
||||
return chunkSize;
|
||||
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current);
|
||||
} while (reader.BaseStream.Position < reader.BaseStream.Length);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,6 +188,7 @@
|
||||
<Compile Include="PartyModes\CScreenPartyDummy.cs" />
|
||||
<Compile Include="PartyModes\IPartyMode.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Utils\CWavFile.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
||||
Reference in new issue
Block a user