Fix bugs in AKF detection

Add CWavFile.cs for reading wav files
Add more test cases
This commit is contained in:
Flamefire committed 2014-04-09 18:28:35 +02:00
1 parent 614a7e0ed2
commit d584206a7f
9 files changed
+524 -17

No files matched your search

+2 -2
View File
@@ -4,7 +4,7 @@
#include <cmath> #include <cmath>
float* short2FloatArray(short* in, size_t len){ 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* result = static_cast<float*>(malloc(len * sizeof(float)));
float* curOut = result; float* curOut = result;
@@ -17,7 +17,7 @@ float* short2FloatArray(short* in, size_t len){
} }
double* short2DoubleArray(short* in, size_t len, bool scale = true){ 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* result = static_cast<double*>(malloc(len * sizeof(double)));
double* curOut = result; double* curOut = result;
+5 -1
View File
@@ -75,7 +75,11 @@ DllExport void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt){
if(sampleCt <= 0 || !analyzer) if(sampleCt <= 0 || !analyzer)
return; return;
short* dataShort = static_cast<short*>(static_cast<void*>(data)); 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){ DllExport int PtAKF_GetNote(PtAKF* analyzer, double* maxVolume, float* weights){
Binary file not shown.
+87 -6
View File
@@ -24,9 +24,9 @@ float* PtAKF::_SamplesPerPeriodPerTone = NULL;
PtAKF::PtAKF(unsigned step){ PtAKF::PtAKF(unsigned step){
_InitCount++; _InitCount++;
if(_InitCount == 1){ if(_InitCount == 1){
_SamplesPerPeriodPerTone = new float[_MaxHalfTone + 1]; _SamplesPerPeriodPerTone = new float[_MaxHalfTone + 1 + _HalfTonesAdd];
//Init Array to avoid costly calculations //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); double freq = BaseToneFrequency * pow(HalftoneBase, toneIndex);
_SamplesPerPeriodPerTone[toneIndex] = static_cast<float>(44100.0 / freq); // samples in one period _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) if(vol > maxVolumeL)
maxVolumeL = vol; maxVolumeL = vol;
} }
maxVolumeL /= 32767.; //maxVolumeL /= 32767.;
*maxVolume = maxVolumeL; *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 maxWeight = -1;
float minWeight = 1; float minWeight = 1;
int maxTone = -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++) for (int toneIndex = _MinHalfTone; toneIndex <= _MaxHalfTone; toneIndex++)
{ {
float curWeight = _AnalyzeToneFunc(samples, toneIndex); float curWeight = _AnalyzeToneFunc(samples, toneIndex);
@@ -81,18 +92,61 @@ int PtAKF::_GetNote(float* samples, double* maxVolume, float* weights){
minWeight = curWeight; minWeight = curWeight;
weights[toneIndex - _MinHalfTone] = 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; return maxTone;
}else return -1; }else return -1;
} }
float PtAKF::_AnalyzeToneFunc(float* samples, int toneIndex){ 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 float samplesPerPeriodD = _SamplesPerPeriodPerTone[toneIndex]; // samples in one period
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD); int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
int maxSampleCt = static_cast<int>(_SamplesPerPeriodPerTone[0]);
float fHigh = samplesPerPeriodD - samplesPerPeriod; float fHigh = samplesPerPeriodD - samplesPerPeriod;
float fLow = 1.0f - fHigh; float fLow = 1.0f - fHigh;
@@ -114,7 +168,34 @@ float PtAKF::_AnalyzeToneFunc(float* samples, int toneIndex){
accumDistAKF /= _SampleCt; accumDistAKF /= _SampleCt;
accumDistAMDF /= sampleIndex; 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;
} }
/* /*
+6 -3
View File
@@ -7,7 +7,8 @@ public:
PtAKF(unsigned step); PtAKF(unsigned step);
~PtAKF(); ~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); _AnalysisBuf.insert(begin, end);
} }
@@ -17,12 +18,14 @@ private:
static int _InitCount; static int _InitCount;
static float* _SamplesPerPeriodPerTone; static float* _SamplesPerPeriodPerTone;
static constexpr int _MinHalfTone = 0; //C2 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; RingBuffer<_SampleCt * 2> _AnalysisBuf;
unsigned _Step; unsigned _Step;
static int _GetNote(float* samples, double* maxVolume, float* weights); static int _GetNote(float* samples, double* maxVolume, float* weights);
static float _AnalyzeToneFunc(float* samples, int toneIndex); static float _AnalyzeToneFunc(float* samples, int toneIndex);
static float _FastAKF(float* samples, float samplesPerPeriodD);
}; };
+3
View File
@@ -164,6 +164,9 @@ namespace Vocaluxe.Base
public static void LogDebug(string text) public static void LogDebug(string text)
{ {
_DebugLog.Add(String.Format("{0:HH:mm:ss.ffff}", DateTime.Now) + ":" + 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) public static void LogSongInfo(string text)
+125 -5
View File
@@ -19,9 +19,12 @@
using System; using System;
#if TEST_PITCH #if TEST_PITCH
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using System.Diagnostics; using System.Diagnostics;
using VocaluxeLib; using VocaluxeLib;
using VocaluxeLib.Utils;
#endif #endif
@@ -30,11 +33,12 @@ namespace Vocaluxe.Lib.Sound.Record
class CBuffer : IDisposable class CBuffer : IDisposable
{ {
#if TEST_PITCH #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 _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) private const double _HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
#endif #endif
private CPtAKF _Analyzer = new CPtAKF(); private CPtAKF _Analyzer = new CPtAKF();
private CAnalyzer _Analyzer2 = new CAnalyzer();
private double _MaxVolume; private double _MaxVolume;
@@ -94,11 +98,12 @@ namespace Vocaluxe.Lib.Sound.Record
//if (assigned(fVoiceStream)) then //if (assigned(fVoiceStream)) then
//fVoiceStream.WriteData(Buffer, BufferSize); //fVoiceStream.WriteData(Buffer, BufferSize);
_Analyzer.Input(buffer); _Analyzer.Input(buffer);
_Analyzer2.Input(buffer);
} }
public void AnalyzeBuffer() public void AnalyzeBuffer()
{ {
//_Analyzer.Process(); _Analyzer2.Process();
//_MaxVolume = _Analyzer.GetPeak() / 43 + 1; //_MaxVolume = _Analyzer.GetPeak() / 43 + 1;
int tone = _Analyzer.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); // int tone = _Analyzer.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); //
if (tone >= 0 && _MaxVolume >= MinVolume) if (tone >= 0 && _MaxVolume >= MinVolume)
@@ -132,7 +137,7 @@ namespace Vocaluxe.Lib.Sound.Record
private static string _ToneToNote(int tone, bool withOctave = true) private static string _ToneToNote(int tone, bool withOctave = true)
{ {
tone += 24; 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]; string result = notes[tone % 12];
if (withOctave) if (withOctave)
result += (tone / 12); result += (tone / 12);
@@ -145,11 +150,12 @@ namespace Vocaluxe.Lib.Sound.Record
return; return;
_PitchTestRun = true; _PitchTestRun = true;
int toneFrom = 0; int toneFrom = 0;
const int toneTo = 47; //B5 const int toneTo = -1; // 47; //B5
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo)); Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
byte[] data; byte[] data;
byte[] data2 = new byte[2048]; byte[] data2 = new byte[2048];
int ok = 0; int ok = 0;
int tests = 0;
double angle = 0; double angle = 0;
for (int distort = 0; distort < 10; distort++) for (int distort = 0; distort < 10; distort++)
{ {
@@ -167,11 +173,30 @@ namespace Vocaluxe.Lib.Sound.Record
if (!ToneValid) if (!ToneValid)
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " not detected (Distortion: " + distort + ")"); CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " not detected (Distortion: " + distort + ")");
else if (Tone != tone % 12) else if (Tone != tone % 12)
{
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " wrongly detected as " + _ToneToNote(Tone, false) + " (Distortion: " + distort + ")"); 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 else
ok++; 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); _GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, ref angle, out data);
Stopwatch sw = new Stopwatch(); Stopwatch sw = new Stopwatch();
sw.Start(); sw.Start();
@@ -182,13 +207,108 @@ namespace Vocaluxe.Lib.Sound.Record
AnalyzeBuffer(); AnalyzeBuffer();
} }
sw.Stop(); 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"; (int)(repeats / (sw.ElapsedMilliseconds / 1000.0)) + " buffers/s";
Console.WriteLine(msg); Console.WriteLine(msg);
MessageBox.Show(msg); MessageBox.Show(msg);
Reset(); 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) private static short[] _GetDistort(int len, int tone, int type)
{ {
short[] sdata = new short[len]; short[] sdata = new short[len];
+295
View File
@@ -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;
}
}
}
+1
View File
@@ -188,6 +188,7 @@
<Compile Include="PartyModes\CScreenPartyDummy.cs" /> <Compile Include="PartyModes\CScreenPartyDummy.cs" />
<Compile Include="PartyModes\IPartyMode.cs" /> <Compile Include="PartyModes\IPartyMode.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\CWavFile.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>