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>
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;
+5 -1
View File
@@ -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
View File
@@ -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;
}
/*
+6 -3
View File
@@ -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);
};
+3
View File
@@ -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)
+125 -5
View File
@@ -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];
+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\IPartyMode.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\CWavFile.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>