333 lines
9.1 KiB
D
333 lines
9.1 KiB
D
|
/*
|
||
|
* DSFML - SFML Library binding in D language.
|
||
|
* Copyright (C) 2008 Julien Dagorn (sirjulio13@gmail.com)
|
||
|
*
|
||
|
* This software is provided 'as-is', without any express or
|
||
|
* implied warranty. In no event will the authors be held
|
||
|
* liable for any damages arising from the use of this software.
|
||
|
*
|
||
|
* Permission is granted to anyone to use this software for any purpose,
|
||
|
* including commercial applications, and to alter it and redistribute
|
||
|
* it freely, subject to the following restrictions:
|
||
|
*
|
||
|
* 1. The origin of this software must not be misrepresented;
|
||
|
* you must not claim that you wrote the original software.
|
||
|
* If you use this software in a product, an acknowledgment
|
||
|
* in the product documentation would be appreciated but
|
||
|
* is not required.
|
||
|
*
|
||
|
* 2. Altered source versions must be plainly marked as such,
|
||
|
* and must not be misrepresented as being the original software.
|
||
|
*
|
||
|
* 3. This notice may not be removed or altered from any
|
||
|
* source distribution.
|
||
|
*/
|
||
|
|
||
|
module dsfml.audio.soundrecorder;
|
||
|
|
||
|
import dsfml.audio.soundbuffer;
|
||
|
|
||
|
import dsfml.system.alloc;
|
||
|
import dsfml.system.common;
|
||
|
import dsfml.system.sleep;
|
||
|
import dsfml.system.linkedlist;
|
||
|
import dsfml.system.mutex;
|
||
|
import dsfml.system.lock;
|
||
|
import dsfml.system.thread;
|
||
|
|
||
|
/**
|
||
|
* SoundRecorder is an interface for capturing sound data.
|
||
|
*
|
||
|
* $(B onProcessSamples and onStop will be called by a different thread, take care of synchronization issues.)
|
||
|
*
|
||
|
* Examples:
|
||
|
* -------
|
||
|
* class MySoundRecorder : SoundRecorder
|
||
|
* {
|
||
|
* this()
|
||
|
* {
|
||
|
*
|
||
|
* }
|
||
|
*
|
||
|
* protected bool onStart()
|
||
|
* {
|
||
|
* return true;
|
||
|
* }
|
||
|
*
|
||
|
* protected void onStop()
|
||
|
* {
|
||
|
*
|
||
|
* }
|
||
|
*
|
||
|
* protected bool onProcessSamples(out short[])
|
||
|
* {
|
||
|
* // Process data here
|
||
|
*
|
||
|
* return true; //return true to continue capture, else return false
|
||
|
* }
|
||
|
* }
|
||
|
* -------
|
||
|
*/
|
||
|
abstract class SoundRecorder : DSFMLObject
|
||
|
{
|
||
|
override void dispose()
|
||
|
{
|
||
|
if (m_flag)
|
||
|
stop();
|
||
|
|
||
|
m_instances.remove(m_id);
|
||
|
sfSoundRecorder_Destroy(m_ptr);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Start the capture.
|
||
|
*
|
||
|
* Only one capture can happen at the same time
|
||
|
*
|
||
|
* Params:
|
||
|
* sampleRate : Sound frequency (the more samples, the higher the quality)
|
||
|
* (44100 by default = CD quality)
|
||
|
*/
|
||
|
void start(uint sampleRate = 44100)
|
||
|
{
|
||
|
sfSoundRecorder_Start(m_ptr, sampleRate);
|
||
|
m_t = new Thread(&threadPoll);
|
||
|
m_t.launch();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stop the capture
|
||
|
*/
|
||
|
void stop()
|
||
|
{
|
||
|
sfSoundRecorder_Stop(m_ptr);
|
||
|
m_flag = false;
|
||
|
m_t.wait();
|
||
|
m_t = null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the sample rate
|
||
|
*
|
||
|
* Returns:
|
||
|
* Frequency, in samples per second
|
||
|
*/
|
||
|
uint getSampleRate()
|
||
|
{
|
||
|
return sfSoundRecorder_GetSampleRate(m_ptr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Tell if the system supports sound capture.
|
||
|
* If not, this class won't be usable
|
||
|
*
|
||
|
* Returns:
|
||
|
* True if audio capture is supported
|
||
|
*
|
||
|
*/
|
||
|
static bool canCapture()
|
||
|
{
|
||
|
return cast(bool)sfSoundRecorder_CanCapture();
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
/**
|
||
|
* Protected constructor
|
||
|
*/
|
||
|
this()
|
||
|
{
|
||
|
m_id = ++seed;
|
||
|
m_instances[m_id] = this;
|
||
|
super(sfSoundRecorder_Create(&internalOnStart, &internalCallback, &internalOnStop, &m_id));
|
||
|
|
||
|
init(true);
|
||
|
}
|
||
|
|
||
|
this(void* ptr)
|
||
|
{
|
||
|
super(ptr);
|
||
|
|
||
|
init(false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Start recording audio data
|
||
|
*
|
||
|
* Returns:
|
||
|
* False to abort recording audio data, true to start
|
||
|
*/
|
||
|
abstract bool onStart();
|
||
|
|
||
|
/**
|
||
|
* Stop recording audio data
|
||
|
*/
|
||
|
abstract void onStop();
|
||
|
|
||
|
/**
|
||
|
* callback function
|
||
|
*
|
||
|
* Parameters:
|
||
|
* samples = Array of samples
|
||
|
*
|
||
|
* Returns:
|
||
|
* true to continue recording, false to stop.
|
||
|
*/
|
||
|
abstract bool onProcessSamples(short[] samples);
|
||
|
|
||
|
bool m_disposed;
|
||
|
private:
|
||
|
/*
|
||
|
* an init function to initialize id of the object.
|
||
|
*/
|
||
|
void init(bool flag)
|
||
|
{
|
||
|
if (flag)
|
||
|
{
|
||
|
m_list = new LinkedList!(Samples)();
|
||
|
|
||
|
m_flag = true;
|
||
|
m_continue = true;
|
||
|
|
||
|
m_mutex = new Mutex();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void* m_userData;
|
||
|
int m_id;
|
||
|
|
||
|
static int seed = 0;
|
||
|
static SoundRecorder[int] m_instances;
|
||
|
|
||
|
/*
|
||
|
* Extern C callback function
|
||
|
*
|
||
|
* This function must be static for C interop. To retrieve the current
|
||
|
* instance, we retrieve id of the sender in the user data, and search associated instance
|
||
|
* in the associative array.
|
||
|
*
|
||
|
* We don't call delegate or derived class on that thread because GC is not aware of this thread
|
||
|
* instead we enqueue data informations in a queue and poll this queue with a managed thread.
|
||
|
*/
|
||
|
extern(C) static int internalCallback(short* s, size_t size, void* user)
|
||
|
{
|
||
|
int id;
|
||
|
// retrieve instance
|
||
|
if ((id = *cast(int*)(user)) in m_instances)
|
||
|
{
|
||
|
SoundRecorder temp = m_instances[id];
|
||
|
scope Lock l = new Lock(temp.m_mutex);
|
||
|
if (temp.m_continue)
|
||
|
// this new is allowed because Samples is an custom alloc class.
|
||
|
temp.m_list.enqueue(new Samples(s, size));
|
||
|
return temp.m_continue;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
extern(C) static int internalOnStart(void* user)
|
||
|
{
|
||
|
int id;
|
||
|
bool ret = false;
|
||
|
if ((id = *cast(int*)(user)) in m_instances)
|
||
|
{
|
||
|
SoundRecorder temp = m_instances[id];
|
||
|
ret = temp.onStart();
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
extern(C) static void internalOnStop(void* user)
|
||
|
{
|
||
|
// Nothing to do
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Managed thread loop
|
||
|
*/
|
||
|
void threadPoll(void* user)
|
||
|
{
|
||
|
while (m_flag)
|
||
|
{
|
||
|
sleep(0.05f);
|
||
|
// if samples are available
|
||
|
if (!m_list.empty)
|
||
|
{
|
||
|
// Lock ressources
|
||
|
scope Lock l = new Lock(m_mutex);
|
||
|
|
||
|
Samples s = m_list.dequeue;
|
||
|
m_continue = this.onProcessSamples(s.data[0..s.length].dup);
|
||
|
|
||
|
delete s;
|
||
|
|
||
|
if (!m_continue)
|
||
|
{
|
||
|
// delete all samples left
|
||
|
foreach(Samples dummy; m_list)
|
||
|
delete dummy;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onStop();
|
||
|
}
|
||
|
|
||
|
Mutex m_mutex;
|
||
|
|
||
|
bool m_flag;
|
||
|
bool m_continue = true;
|
||
|
LinkedList!(Samples) m_list;
|
||
|
Thread m_t;
|
||
|
|
||
|
// External ====================================================================
|
||
|
|
||
|
extern (C)
|
||
|
{
|
||
|
typedef void* function(int function(void*), int function(short*, size_t, void*), void function(void*), void*) pf_sfSoundRecorder_Create;
|
||
|
typedef void function(void*) pf_sfSoundRecorder_Destroy;
|
||
|
typedef void function(void*, uint SampleRate) pf_sfSoundRecorder_Start;
|
||
|
typedef void function(void*) pf_sfSoundRecorder_Stop;
|
||
|
typedef uint function(void*) pf_sfSoundRecorder_GetSampleRate;
|
||
|
typedef int function() pf_sfSoundRecorder_CanCapture;
|
||
|
|
||
|
static pf_sfSoundRecorder_Create sfSoundRecorder_Create;
|
||
|
static pf_sfSoundRecorder_Destroy sfSoundRecorder_Destroy;
|
||
|
static pf_sfSoundRecorder_Start sfSoundRecorder_Start;
|
||
|
static pf_sfSoundRecorder_Stop sfSoundRecorder_Stop;
|
||
|
static pf_sfSoundRecorder_GetSampleRate sfSoundRecorder_GetSampleRate;
|
||
|
static pf_sfSoundRecorder_CanCapture sfSoundRecorder_CanCapture;
|
||
|
}
|
||
|
|
||
|
static this()
|
||
|
{
|
||
|
DllLoader dll = DllLoader.load("csfml-audio");
|
||
|
|
||
|
sfSoundRecorder_Create = cast(pf_sfSoundRecorder_Create)dll.getSymbol("sfSoundRecorder_Create");
|
||
|
sfSoundRecorder_Destroy = cast(pf_sfSoundRecorder_Destroy)dll.getSymbol("sfSoundRecorder_Destroy");
|
||
|
sfSoundRecorder_Start = cast(pf_sfSoundRecorder_Start)dll.getSymbol("sfSoundRecorder_Start");
|
||
|
sfSoundRecorder_Stop = cast(pf_sfSoundRecorder_Stop)dll.getSymbol("sfSoundRecorder_Stop");
|
||
|
sfSoundRecorder_GetSampleRate = cast(pf_sfSoundRecorder_GetSampleRate)dll.getSymbol("sfSoundRecorder_GetSampleRate");
|
||
|
sfSoundRecorder_CanCapture = cast(pf_sfSoundRecorder_CanCapture)dll.getSymbol("sfSoundRecorder_CanCapture");
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Use explicit alloc to allow instaciation by C thread
|
||
|
private class Samples
|
||
|
{
|
||
|
mixin Alloc;
|
||
|
|
||
|
this(short* data, size_t length)
|
||
|
{
|
||
|
this.data = data;
|
||
|
this.length = length;
|
||
|
}
|
||
|
|
||
|
public short* data;
|
||
|
public size_t length;
|
||
|
}
|
||
|
|