Add a base record class to handle common functions (avoid duplicate code)
This commit is contained in:
1 parent
9054f529a0
commit
3bc8fd516f
6 files changed
+277
-310
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using PortAudioSharp;
|
using PortAudioSharp;
|
||||||
using Vocaluxe.Base;
|
using Vocaluxe.Base;
|
||||||
@@ -8,40 +9,53 @@ namespace Vocaluxe.Lib.Sound
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// PortAudio can be used for record and playback
|
/// PortAudio can be used for record and playback
|
||||||
/// So do some common stuff here and make sure those 2 do not interfere
|
/// So do some common stuff here and make sure those 2 do not interfere
|
||||||
|
/// Basic lifetime: On Init() get a new handle, close/dispose it in your close/dispose
|
||||||
///
|
///
|
||||||
/// DO NEVER use following Pa_* functions other than the ones from this class:
|
/// DO NEVER use following Pa_* functions other than the ones from this class:
|
||||||
/// Initialize, Terminate, OpenStream, CloseStream
|
/// Initialize, Terminate, OpenStream, CloseStream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static class CPortAudioCommon
|
class CPortAudioHandle : IDisposable
|
||||||
{
|
{
|
||||||
private static int _RefCount;
|
private static int _RefCount;
|
||||||
private static readonly object _Mutex = new object();
|
private static readonly object _Mutex = new object();
|
||||||
|
|
||||||
|
private bool _Disposed;
|
||||||
|
private readonly List<IntPtr> _Streams = new List<IntPtr>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Safe method to init PortAudio (adds a reference)
|
/// Initializes PortAudio library (if required)
|
||||||
/// MUST call CloseDriver when done
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True on success, falso if not initialized (log written)</returns>
|
public CPortAudioHandle()
|
||||||
public static bool InitDriver()
|
|
||||||
{
|
{
|
||||||
lock (_Mutex)
|
lock (_Mutex)
|
||||||
{
|
{
|
||||||
if (_RefCount == 0)
|
if (_RefCount == 0)
|
||||||
{
|
{
|
||||||
if (CheckError("Initialize", PortAudio.Pa_Initialize()))
|
if (CheckError("Initialize", PortAudio.Pa_Initialize()))
|
||||||
return false;
|
throw new Exception();
|
||||||
}
|
}
|
||||||
_RefCount++;
|
_RefCount++;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
~CPortAudioHandle()
|
||||||
/// Safe method to close PortAudio
|
|
||||||
/// MUST be called _exactly_ once after successfull InitDriver call
|
|
||||||
/// </summary>
|
|
||||||
public static void CloseDriver()
|
|
||||||
{
|
{
|
||||||
|
_Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_Disposed)
|
||||||
|
return;
|
||||||
|
if (!disposing)
|
||||||
|
CLog.LogDebug("Did not close CPortAudioHandle");
|
||||||
|
//Make sure we do not leek any streams as we may keep PA open
|
||||||
|
if (_Streams.Count > 0)
|
||||||
|
{
|
||||||
|
CLog.LogDebug("Did not close " + _Streams.Count + "PortAudio-Stream(s)");
|
||||||
|
while (_Streams.Count > 0)
|
||||||
|
CloseStream(_Streams[0]);
|
||||||
|
}
|
||||||
lock (_Mutex)
|
lock (_Mutex)
|
||||||
{
|
{
|
||||||
Debug.Assert(_RefCount > 0);
|
Debug.Assert(_RefCount > 0);
|
||||||
@@ -49,15 +63,37 @@ namespace Vocaluxe.Lib.Sound
|
|||||||
if (_RefCount == 0)
|
if (_RefCount == 0)
|
||||||
PortAudio.Pa_Terminate();
|
PortAudio.Pa_Terminate();
|
||||||
}
|
}
|
||||||
|
_Disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PortAudio.PaError OpenStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters, ref PortAudio.PaStreamParameters? outputParameters,
|
public void Dispose()
|
||||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
|
||||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
|
||||||
{
|
{
|
||||||
|
_Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close the PortAudio handle once you are done
|
||||||
|
/// </summary>
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PortAudio.PaError OpenStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters, ref PortAudio.PaStreamParameters? outputParameters,
|
||||||
|
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||||
|
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||||
|
{
|
||||||
|
if (_Disposed)
|
||||||
|
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||||
|
|
||||||
lock (_Mutex)
|
lock (_Mutex)
|
||||||
{
|
{
|
||||||
return PortAudio.Pa_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData);
|
PortAudio.PaError res = PortAudio.Pa_OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback,
|
||||||
|
userData);
|
||||||
|
if (res == PortAudio.PaError.paNoError)
|
||||||
|
_Streams.Add(stream);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,9 +108,9 @@ namespace Vocaluxe.Lib.Sound
|
|||||||
/// <param name="streamCallback"></param>
|
/// <param name="streamCallback"></param>
|
||||||
/// <param name="userData"></param>
|
/// <param name="userData"></param>
|
||||||
/// <returns>True on success</returns>
|
/// <returns>True on success</returns>
|
||||||
public static bool OpenInputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters,
|
public bool OpenInputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? inputParameters,
|
||||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||||
{
|
{
|
||||||
PortAudio.PaStreamParameters? outputParameters = null;
|
PortAudio.PaStreamParameters? outputParameters = null;
|
||||||
return
|
return
|
||||||
@@ -93,9 +129,9 @@ namespace Vocaluxe.Lib.Sound
|
|||||||
/// <param name="streamCallback"></param>
|
/// <param name="streamCallback"></param>
|
||||||
/// <param name="userData"></param>
|
/// <param name="userData"></param>
|
||||||
/// <returns>True on success</returns>
|
/// <returns>True on success</returns>
|
||||||
public static bool OpenOutputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? outputParameters,
|
public bool OpenOutputStream(out IntPtr stream, ref PortAudio.PaStreamParameters? outputParameters,
|
||||||
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
double sampleRate, uint framesPerBuffer, PortAudio.PaStreamFlags streamFlags,
|
||||||
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
PortAudio.PaStreamCallbackDelegate streamCallback, IntPtr userData)
|
||||||
{
|
{
|
||||||
PortAudio.PaStreamParameters? inputParameters = null;
|
PortAudio.PaStreamParameters? inputParameters = null;
|
||||||
return
|
return
|
||||||
@@ -103,11 +139,15 @@ namespace Vocaluxe.Lib.Sound
|
|||||||
OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
OpenStream(out stream, ref inputParameters, ref outputParameters, sampleRate, framesPerBuffer, streamFlags, streamCallback, userData));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CloseStream(IntPtr stream)
|
public void CloseStream(IntPtr stream)
|
||||||
{
|
{
|
||||||
|
if (_Disposed)
|
||||||
|
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||||
|
|
||||||
lock (_Mutex)
|
lock (_Mutex)
|
||||||
{
|
{
|
||||||
PortAudio.Pa_CloseStream(stream);
|
PortAudio.Pa_CloseStream(stream);
|
||||||
|
_Streams.Remove(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,8 +158,11 @@ namespace Vocaluxe.Lib.Sound
|
|||||||
/// <param name="action">Action identifier (E.g. openStream)</param>
|
/// <param name="action">Action identifier (E.g. openStream)</param>
|
||||||
/// <param name="errorCode">Result returned by Pa_* call</param>
|
/// <param name="errorCode">Result returned by Pa_* call</param>
|
||||||
/// <returns>True on error</returns>
|
/// <returns>True on error</returns>
|
||||||
public static bool CheckError(String action, PortAudio.PaError errorCode)
|
public bool CheckError(String action, PortAudio.PaError errorCode)
|
||||||
{
|
{
|
||||||
|
if (_Disposed)
|
||||||
|
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||||
|
|
||||||
if (errorCode != PortAudio.PaError.paNoError)
|
if (errorCode != PortAudio.PaError.paNoError)
|
||||||
{
|
{
|
||||||
CLog.LogError(action + " error: " + PortAudio.Pa_GetErrorText(errorCode));
|
CLog.LogError(action + " error: " + PortAudio.Pa_GetErrorText(errorCode));
|
||||||
@@ -140,11 +183,10 @@ namespace Vocaluxe.Lib.Sound
|
|||||||
/// Selects the most appropriate host api
|
/// Selects the most appropriate host api
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The most appropriate host api</returns>
|
/// <returns>The most appropriate host api</returns>
|
||||||
public static int GetHostApi()
|
public int GetHostApi()
|
||||||
{
|
{
|
||||||
//Caller has to hold a reference anyway so no locking needed
|
if (_Disposed)
|
||||||
if (_RefCount < 1)
|
throw new ObjectDisposedException("PortAudioHandle already disposed");
|
||||||
return -1;
|
|
||||||
|
|
||||||
int selectedHostApi = PortAudio.Pa_GetDefaultHostApi();
|
int selectedHostApi = PortAudio.Pa_GetDefaultHostApi();
|
||||||
int apiCount = PortAudio.Pa_GetHostApiCount();
|
int apiCount = PortAudio.Pa_GetHostApiCount();
|
||||||
@@ -15,6 +15,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
|
|
||||||
public readonly int ID;
|
public readonly int ID;
|
||||||
private readonly Closeproc _Closeproc;
|
private readonly Closeproc _Closeproc;
|
||||||
|
private CPortAudioHandle _PaHandle;
|
||||||
|
|
||||||
private readonly CSyncTimer _SyncTimer;
|
private readonly CSyncTimer _SyncTimer;
|
||||||
private int _ByteCount = 4;
|
private int _ByteCount = 4;
|
||||||
@@ -187,12 +188,12 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
{
|
{
|
||||||
Paused = false;
|
Paused = false;
|
||||||
_AfterFadeAction = EStreamAction.Nothing;
|
_AfterFadeAction = EStreamAction.Nothing;
|
||||||
CPortAudioCommon.CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream));
|
_PaHandle.CheckError("StartStream", PortAudioSharp.PortAudio.Pa_StartStream(_Stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
CPortAudioCommon.CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream));
|
_PaHandle.CheckError("StopStream (playback)", PortAudioSharp.PortAudio.Pa_StopStream(_Stream));
|
||||||
Skip(0f);
|
Skip(0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,14 +211,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
if (!File.Exists(fileName))
|
if (!File.Exists(fileName))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool driverInitialized = false;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!CPortAudioCommon.InitDriver())
|
_PaHandle = new CPortAudioHandle();
|
||||||
return false;
|
|
||||||
driverInitialized = true;
|
|
||||||
|
|
||||||
int hostApi = CPortAudioCommon.GetHostApi();
|
int hostApi = _PaHandle.GetHostApi();
|
||||||
_ApiInfo = PortAudioSharp.PortAudio.Pa_GetHostApiInfo(hostApi);
|
_ApiInfo = PortAudioSharp.PortAudio.Pa_GetHostApiInfo(hostApi);
|
||||||
_OutputDeviceInfo = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_ApiInfo.defaultOutputDevice);
|
_OutputDeviceInfo = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_ApiInfo.defaultOutputDevice);
|
||||||
if (_OutputDeviceInfo.defaultLowOutputLatency < 0.1)
|
if (_OutputDeviceInfo.defaultLowOutputLatency < 0.1)
|
||||||
@@ -227,8 +225,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
if (driverInitialized)
|
if (_PaHandle != null)
|
||||||
CPortAudioCommon.CloseDriver();
|
{
|
||||||
|
_PaHandle.Close();
|
||||||
|
_PaHandle = null;
|
||||||
|
}
|
||||||
CLog.LogError("Error Init PortAudio Playback");
|
CLog.LogError("Error Init PortAudio Playback");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -240,7 +241,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
SFormatInfo format = _Decoder.GetFormatInfo();
|
SFormatInfo format = _Decoder.GetFormatInfo();
|
||||||
if (format.SamplesPerSecond == 0)
|
if (format.SamplesPerSecond == 0)
|
||||||
{
|
{
|
||||||
CPortAudioCommon.CloseDriver();
|
_PaHandle.Close();
|
||||||
|
_PaHandle = null;
|
||||||
_Decoder.Close();
|
_Decoder.Close();
|
||||||
_Decoder = null;
|
_Decoder = null;
|
||||||
CLog.LogError("Error Init PortAudio Playback (samples=0)");
|
CLog.LogError("Error Init PortAudio Playback (samples=0)");
|
||||||
@@ -262,7 +264,7 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
hostApiSpecificStreamInfo = IntPtr.Zero
|
hostApiSpecificStreamInfo = IntPtr.Zero
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!CPortAudioCommon.OpenOutputStream(
|
if (!_PaHandle.OpenOutputStream(
|
||||||
out _Stream,
|
out _Stream,
|
||||||
ref outputParams,
|
ref outputParams,
|
||||||
format.SamplesPerSecond,
|
format.SamplesPerSecond,
|
||||||
@@ -271,7 +273,8 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
_PaStreamCallback,
|
_PaStreamCallback,
|
||||||
IntPtr.Zero) || _Stream == IntPtr.Zero)
|
IntPtr.Zero) || _Stream == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
CPortAudioCommon.CloseDriver();
|
_PaHandle.Close();
|
||||||
|
_PaHandle = null;
|
||||||
_Decoder.Close();
|
_Decoder.Close();
|
||||||
_Decoder = null;
|
_Decoder = null;
|
||||||
return false;
|
return false;
|
||||||
@@ -410,9 +413,11 @@ namespace Vocaluxe.Lib.Sound.Playback.PortAudio
|
|||||||
|
|
||||||
private void _DoFree()
|
private void _DoFree()
|
||||||
{
|
{
|
||||||
CPortAudioCommon.CloseStream(_Stream);
|
_PaHandle.CloseStream(_Stream);
|
||||||
CPortAudioCommon.CloseDriver();
|
_PaHandle.Close();
|
||||||
|
_PaHandle = null;
|
||||||
_Decoder.Close();
|
_Decoder.Close();
|
||||||
|
_Decoder = null;
|
||||||
|
|
||||||
_Closeproc(ID);
|
_Closeproc(ID);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Vocaluxe.Base;
|
||||||
|
|
||||||
|
namespace Vocaluxe.Lib.Sound.Record
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class to implement common functionality for Record classes
|
||||||
|
/// </summary>
|
||||||
|
abstract class CRecordBase
|
||||||
|
{
|
||||||
|
private bool _Initialized;
|
||||||
|
protected List<CRecordDevice> _Devices;
|
||||||
|
protected CBuffer[] _Buffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Init recording
|
||||||
|
/// Child class should overwrite this to list devices
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if success</returns>
|
||||||
|
public virtual bool Init()
|
||||||
|
{
|
||||||
|
if (_Initialized)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_Devices = new List<CRecordDevice>();
|
||||||
|
_Buffer = new CBuffer[CSettings.MaxNumPlayer];
|
||||||
|
for (int i = 0; i < _Buffer.Length; i++)
|
||||||
|
_Buffer[i] = new CBuffer();
|
||||||
|
|
||||||
|
_Initialized = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop all voice capturing streams and terminates recording freeing resources
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Close()
|
||||||
|
{
|
||||||
|
if (!_Initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_Devices = null;
|
||||||
|
if (_Buffer != null)
|
||||||
|
{
|
||||||
|
foreach (CBuffer buffer in _Buffer)
|
||||||
|
buffer.Dispose();
|
||||||
|
_Buffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Procedure to be called by descendent classes with data that got recorded
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="device">Device from which the data was recorded</param>
|
||||||
|
/// <param name="data">Recorded samples, assume Int16 and interleaved for multi-channels</param>
|
||||||
|
protected void _HandleData(CRecordDevice device, byte[] data)
|
||||||
|
{
|
||||||
|
byte[] leftBuffer;
|
||||||
|
byte[] rightBuffer;
|
||||||
|
|
||||||
|
if (device.Channels == 2)
|
||||||
|
{
|
||||||
|
leftBuffer = new byte[data.Length / 2];
|
||||||
|
rightBuffer = new byte[data.Length / 2];
|
||||||
|
//[]: Sample, L: Left channel R: Right channel
|
||||||
|
//[LR][LR][LR][LR][LR][LR]
|
||||||
|
//The data is interleaved and needs to be demultiplexed
|
||||||
|
for (int i = 0; i < data.Length / 4; i++)
|
||||||
|
{
|
||||||
|
leftBuffer[i * 2] = data[i * 4];
|
||||||
|
leftBuffer[i * 2 + 1] = data[i * 4 + 1];
|
||||||
|
rightBuffer[i * 2] = data[i * 4 + 2];
|
||||||
|
rightBuffer[i * 2 + 1] = data[i * 4 + 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
leftBuffer = rightBuffer = data;
|
||||||
|
|
||||||
|
if (device.PlayerChannel1 > 0)
|
||||||
|
_Buffer[device.PlayerChannel1 - 1].ProcessNewBuffer(leftBuffer);
|
||||||
|
|
||||||
|
if (device.PlayerChannel2 > 0)
|
||||||
|
_Buffer[device.PlayerChannel2 - 1].ProcessNewBuffer(rightBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detect Pitch and Volume of the newest voice buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="player"></param>
|
||||||
|
public void AnalyzeBuffer(int player)
|
||||||
|
{
|
||||||
|
if (!_Initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_Buffer[player].AnalyzeBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetToneAbs(int player)
|
||||||
|
{
|
||||||
|
if (!_Initialized)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return _Buffer[player].ToneAbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetTone(int player)
|
||||||
|
{
|
||||||
|
return _Initialized ? _Buffer[player].Tone : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTone(int player, int tone)
|
||||||
|
{
|
||||||
|
if (!_Initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_Buffer[player].Tone = tone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetMaxVolume(int player)
|
||||||
|
{
|
||||||
|
return _Initialized ? _Buffer[player].MaxVolume : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ToneValid(int player)
|
||||||
|
{
|
||||||
|
return _Initialized && _Buffer[player].ToneValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int NumHalfTones()
|
||||||
|
{
|
||||||
|
return CBuffer.NumHalfTones;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] ToneWeigth(int player)
|
||||||
|
{
|
||||||
|
return _Initialized ? _Buffer[player].ToneWeigth : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<CRecordDevice> RecordDevices()
|
||||||
|
{
|
||||||
|
return (_Initialized && _Devices.Count > 0) ? _Devices.AsReadOnly() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,39 +23,28 @@ using Vocaluxe.Base;
|
|||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
||||||
{
|
{
|
||||||
class CDirectSoundRecord : IRecord
|
class CDirectSoundRecord : CRecordBase, IRecord
|
||||||
{
|
{
|
||||||
private bool _Initialized;
|
private bool _Initialized;
|
||||||
private List<CRecordDevice> _Devices;
|
|
||||||
private List<CSoundCardSource> _Sources;
|
private List<CSoundCardSource> _Sources;
|
||||||
|
|
||||||
private readonly CBuffer[] _Buffer;
|
public override bool Init()
|
||||||
|
|
||||||
public CDirectSoundRecord()
|
|
||||||
{
|
{
|
||||||
_Buffer = new CBuffer[CSettings.MaxNumPlayer];
|
if (!base.Init())
|
||||||
for (int i = 0; i < _Buffer.Length; i++)
|
return false;
|
||||||
_Buffer[i] = new CBuffer();
|
|
||||||
|
|
||||||
Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Init()
|
|
||||||
{
|
|
||||||
DeviceCollection devices = DirectSoundCapture.GetDevices();
|
|
||||||
_Devices = new List<CRecordDevice>();
|
|
||||||
_Sources = new List<CSoundCardSource>();
|
_Sources = new List<CSoundCardSource>();
|
||||||
|
|
||||||
int id = 0;
|
DeviceCollection devices = DirectSoundCapture.GetDevices();
|
||||||
|
|
||||||
foreach (DeviceInformation dev in devices)
|
foreach (DeviceInformation dev in devices)
|
||||||
{
|
{
|
||||||
using (var ds = new DirectSoundCapture(dev.DriverGuid))
|
using (var ds = new DirectSoundCapture(dev.DriverGuid))
|
||||||
{
|
{
|
||||||
var device = new CRecordDevice(id, dev.DriverGuid.ToString(), dev.Description, ds.Capabilities.Channels);
|
var device = new CRecordDevice(_Devices.Count, dev.DriverGuid.ToString(), dev.Description, ds.Capabilities.Channels);
|
||||||
|
|
||||||
_Devices.Add(device);
|
_Devices.Add(device);
|
||||||
|
|
||||||
id++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,14 +53,11 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public override void Close()
|
||||||
{
|
{
|
||||||
if (_Initialized)
|
Stop();
|
||||||
{
|
_Initialized = false;
|
||||||
Stop();
|
base.Close();
|
||||||
_Initialized = false;
|
|
||||||
}
|
|
||||||
//System.IO.File.WriteAllBytes("test0.raw", _Buffer[0].Buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Start()
|
public bool Start()
|
||||||
@@ -112,81 +98,6 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AnalyzeBuffer(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_Buffer[player].AnalyzeBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetToneAbs(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return _Buffer[player].ToneAbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetTone(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return _Buffer[player].Tone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTone(int player, int tone)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_Buffer[player].Tone = tone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float GetMaxVolume(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0f;
|
|
||||||
|
|
||||||
return _Buffer[player].MaxVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ToneValid(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return _Buffer[player].ToneValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyCollection<CRecordDevice> RecordDevices()
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (_Devices.Count == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return _Devices.AsReadOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int NumHalfTones()
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return CBuffer.NumHalfTones;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] ToneWeigth(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return _Buffer[player].ToneWeigth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _OnDataReady(object sender, CSampleDataEventArgs e)
|
private void _OnDataReady(object sender, CSampleDataEventArgs e)
|
||||||
{
|
{
|
||||||
if (!_Initialized)
|
if (!_Initialized)
|
||||||
@@ -194,32 +105,7 @@ namespace Vocaluxe.Lib.Sound.Record.DirectSound
|
|||||||
CRecordDevice dev = _Devices.FirstOrDefault(device => device.Driver == e.Guid);
|
CRecordDevice dev = _Devices.FirstOrDefault(device => device.Driver == e.Guid);
|
||||||
if (dev == null)
|
if (dev == null)
|
||||||
return;
|
return;
|
||||||
byte[] leftBuffer;
|
_HandleData(dev, e.Data);
|
||||||
byte[] rightBuffer;
|
|
||||||
|
|
||||||
if (dev.Channels == 2)
|
|
||||||
{
|
|
||||||
leftBuffer = new byte[e.Data.Length / 2];
|
|
||||||
rightBuffer = new byte[e.Data.Length / 2];
|
|
||||||
//[]: Sample, L: Left channel R: Right channel
|
|
||||||
//[LR][LR][LR][LR][LR][LR]
|
|
||||||
//The data is interleaved and needs to be demultiplexed
|
|
||||||
for (int i = 0; i < e.Data.Length / 4; i++)
|
|
||||||
{
|
|
||||||
leftBuffer[i * 2] = e.Data[i * 4];
|
|
||||||
leftBuffer[i * 2 + 1] = e.Data[i * 4 + 1];
|
|
||||||
rightBuffer[i * 2] = e.Data[i * 4 + 2];
|
|
||||||
rightBuffer[i * 2 + 1] = e.Data[i * 4 + 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
leftBuffer = rightBuffer = e.Data;
|
|
||||||
|
|
||||||
if (dev.PlayerChannel1 > 0)
|
|
||||||
_Buffer[dev.PlayerChannel1 - 1].ProcessNewBuffer(leftBuffer);
|
|
||||||
|
|
||||||
if (dev.PlayerChannel2 > 0)
|
|
||||||
_Buffer[dev.PlayerChannel2 - 1].ProcessNewBuffer(rightBuffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,47 +16,33 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Vocaluxe.Base;
|
using Vocaluxe.Base;
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
||||||
{
|
{
|
||||||
class CPortAudioRecord : IRecord
|
class CPortAudioRecord : CRecordBase, IRecord
|
||||||
{
|
{
|
||||||
private bool _Initialized;
|
private bool _Initialized;
|
||||||
private readonly List<CRecordDevice> _Devices = new List<CRecordDevice>();
|
private CPortAudioHandle _PaHandle;
|
||||||
|
|
||||||
private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _MyRecProc;
|
private PortAudioSharp.PortAudio.PaStreamCallbackDelegate _MyRecProc;
|
||||||
private IntPtr[] _RecHandle;
|
private IntPtr[] _RecHandle;
|
||||||
|
|
||||||
private readonly CBuffer[] _Buffer;
|
|
||||||
|
|
||||||
public CPortAudioRecord()
|
|
||||||
{
|
|
||||||
_Buffer = new CBuffer[CSettings.MaxNumPlayer];
|
|
||||||
for (int i = 0; i < _Buffer.Length; i++)
|
|
||||||
_Buffer[i] = new CBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Init PortAudio and list record devices
|
/// Init PortAudio and list record devices
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>true if success</returns>
|
/// <returns>true if success</returns>
|
||||||
public bool Init()
|
public override bool Init()
|
||||||
{
|
{
|
||||||
if (_Initialized)
|
if (!base.Init())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!CPortAudioCommon.InitDriver())
|
_PaHandle = new CPortAudioHandle();
|
||||||
return false;
|
|
||||||
|
|
||||||
_Devices.Clear();
|
int hostAPI = _PaHandle.GetHostApi();
|
||||||
int hostAPI = CPortAudioCommon.GetHostApi();
|
|
||||||
int numDevices = PortAudioSharp.PortAudio.Pa_GetDeviceCount();
|
int numDevices = PortAudioSharp.PortAudio.Pa_GetDeviceCount();
|
||||||
for (int i = 0; i < numDevices; i++)
|
for (int i = 0; i < numDevices; i++)
|
||||||
{
|
{
|
||||||
@@ -69,9 +55,6 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (CBuffer buffer in _Buffer)
|
|
||||||
buffer.Reset();
|
|
||||||
|
|
||||||
_RecHandle = new IntPtr[_Devices.Count];
|
_RecHandle = new IntPtr[_Devices.Count];
|
||||||
_MyRecProc = _MyPaStreamCallback;
|
_MyRecProc = _MyPaStreamCallback;
|
||||||
_Initialized = true;
|
_Initialized = true;
|
||||||
@@ -80,6 +63,7 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
|||||||
{
|
{
|
||||||
_Initialized = false;
|
_Initialized = false;
|
||||||
CLog.LogError("Error initializing PortAudio: " + e.Message);
|
CLog.LogError("Error initializing PortAudio: " + e.Message);
|
||||||
|
Close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +79,6 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
|||||||
if (!_Initialized)
|
if (!_Initialized)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (_RecHandle == null || _RecHandle.Length == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
foreach (IntPtr handle in _RecHandle)
|
foreach (IntPtr handle in _RecHandle)
|
||||||
@@ -128,7 +109,7 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
|||||||
suggestedLatency = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_Devices[dev].ID).defaultLowInputLatency,
|
suggestedLatency = PortAudioSharp.PortAudio.Pa_GetDeviceInfo(_Devices[dev].ID).defaultLowInputLatency,
|
||||||
hostApiSpecificStreamInfo = IntPtr.Zero
|
hostApiSpecificStreamInfo = IntPtr.Zero
|
||||||
};
|
};
|
||||||
if (!CPortAudioCommon.OpenInputStream(
|
if (!_PaHandle.OpenInputStream(
|
||||||
out _RecHandle[dev],
|
out _RecHandle[dev],
|
||||||
ref inputParams,
|
ref inputParams,
|
||||||
44100,
|
44100,
|
||||||
@@ -138,7 +119,7 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
|||||||
new IntPtr(dev)))
|
new IntPtr(dev)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (CPortAudioCommon.CheckError("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
|
if (_PaHandle.CheckError("Start Stream (rec)", PortAudioSharp.PortAudio.Pa_StartStream(_RecHandle[dev])))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,97 +143,25 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop all voice capturing streams and terminate PortAudio
|
/// Stop all voice capturing streams and terminate PortAudio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Close()
|
public override void Close()
|
||||||
{
|
{
|
||||||
if (!_Initialized)
|
if (_RecHandle != null)
|
||||||
return;
|
{
|
||||||
|
foreach (IntPtr handle in _RecHandle)
|
||||||
foreach (IntPtr handle in _RecHandle)
|
{
|
||||||
CPortAudioCommon.CloseStream(handle);
|
if (handle != IntPtr.Zero)
|
||||||
CPortAudioCommon.CloseDriver();
|
_PaHandle.CloseStream(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_PaHandle != null)
|
||||||
|
{
|
||||||
|
_PaHandle.Close();
|
||||||
|
_PaHandle = null;
|
||||||
|
}
|
||||||
|
|
||||||
_Initialized = false;
|
_Initialized = false;
|
||||||
|
|
||||||
//System.IO.File.WriteAllBytes("test0.raw", _Buffer[0].Buffer);
|
base.Close();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Detect Pitch and Volume of the newest voice buffer
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="player"></param>
|
|
||||||
public void AnalyzeBuffer(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_Buffer[player].AnalyzeBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetToneAbs(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return _Buffer[player].ToneAbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetTone(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return _Buffer[player].Tone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTone(int player, int tone)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_Buffer[player].Tone = tone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float GetMaxVolume(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0f;
|
|
||||||
|
|
||||||
return _Buffer[player].MaxVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ToneValid(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return _Buffer[player].ToneValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int NumHalfTones()
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return CBuffer.NumHalfTones;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] ToneWeigth(int player)
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return _Buffer[player].ToneWeigth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyCollection<CRecordDevice> RecordDevices()
|
|
||||||
{
|
|
||||||
if (!_Initialized)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (_Devices.Count == 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return _Devices.AsReadOnly();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PortAudioSharp.PortAudio.PaStreamCallbackResult _MyPaStreamCallback(
|
private PortAudioSharp.PortAudio.PaStreamCallbackResult _MyPaStreamCallback(
|
||||||
@@ -275,34 +184,10 @@ namespace Vocaluxe.Lib.Sound.Record.PortAudio
|
|||||||
numBytes = frameCount * 2;
|
numBytes = frameCount * 2;
|
||||||
|
|
||||||
byte[] recbuffer = new byte[numBytes];
|
byte[] recbuffer = new byte[numBytes];
|
||||||
byte[] leftBuffer;
|
|
||||||
byte[] rightBuffer;
|
|
||||||
|
|
||||||
// copy from managed to unmanaged memory
|
// copy from managed to unmanaged memory
|
||||||
Marshal.Copy(input, recbuffer, 0, (int)numBytes);
|
Marshal.Copy(input, recbuffer, 0, (int)numBytes);
|
||||||
if (dev.Channels == 2)
|
_HandleData(dev, recbuffer);
|
||||||
{
|
|
||||||
leftBuffer = new byte[numBytes / 2];
|
|
||||||
rightBuffer = new byte[numBytes / 2];
|
|
||||||
//[]: Sample, L: Left channel R: Right channel
|
|
||||||
//[LR][LR][LR][LR][LR][LR]
|
|
||||||
//The data is interleaved and needs to be demultiplexed
|
|
||||||
for (int i = 0; i < frameCount; i++)
|
|
||||||
{
|
|
||||||
leftBuffer[i * 2] = recbuffer[i * 4];
|
|
||||||
leftBuffer[i * 2 + 1] = recbuffer[i * 4 + 1];
|
|
||||||
rightBuffer[i * 2] = recbuffer[i * 4 + 2];
|
|
||||||
rightBuffer[i * 2 + 1] = recbuffer[i * 4 + 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
leftBuffer = rightBuffer = recbuffer;
|
|
||||||
|
|
||||||
if (dev.PlayerChannel1 > 0)
|
|
||||||
_Buffer[dev.PlayerChannel1 - 1].ProcessNewBuffer(leftBuffer);
|
|
||||||
|
|
||||||
if (dev.PlayerChannel2 > 0)
|
|
||||||
_Buffer[dev.PlayerChannel2 - 1].ProcessNewBuffer(rightBuffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@@ -222,7 +222,7 @@
|
|||||||
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudioStream.cs" />
|
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudioStream.cs" />
|
||||||
<Compile Include="Lib\Sound\Playback\OpenAL\COpenAlStream.cs" />
|
<Compile Include="Lib\Sound\Playback\OpenAL\COpenAlStream.cs" />
|
||||||
<Compile Include="Lib\Sound\Playback\PortAudio\CPortAudioStream.cs" />
|
<Compile Include="Lib\Sound\Playback\PortAudio\CPortAudioStream.cs" />
|
||||||
<Compile Include="Lib\Sound\CPortAudioCommon.cs" />
|
<Compile Include="Lib\Sound\CPortAudioHandle.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\CBuffer.cs" />
|
<Compile Include="Lib\Sound\Record\CBuffer.cs" />
|
||||||
<Compile Include="Base\CConfig.cs" />
|
<Compile Include="Base\CConfig.cs" />
|
||||||
<Compile Include="Base\CCover.cs" />
|
<Compile Include="Base\CCover.cs" />
|
||||||
@@ -264,6 +264,7 @@
|
|||||||
<Compile Include="GameModes\CGameModes.cs" />
|
<Compile Include="GameModes\CGameModes.cs" />
|
||||||
<Compile Include="Lib\Input\CControllerFramework.cs" />
|
<Compile Include="Lib\Input\CControllerFramework.cs" />
|
||||||
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudio.cs" />
|
<Compile Include="Lib\Sound\Playback\GstreamerSharp\CGstreamerSharpAudio.cs" />
|
||||||
|
<Compile Include="Lib\Sound\Record\CRecordBase.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\DirectSound\CSampleDataEventArgs.cs" />
|
<Compile Include="Lib\Sound\Record\DirectSound\CSampleDataEventArgs.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\DirectSound\CSoundCardSource.cs" />
|
<Compile Include="Lib\Sound\Record\DirectSound\CSoundCardSource.cs" />
|
||||||
<Compile Include="Lib\Video\Acinerella\CDecoder.cs" />
|
<Compile Include="Lib\Video\Acinerella\CDecoder.cs" />
|
||||||
|
|||||||
Reference in new issue
Block a user