/* * DSFML - SFML Library wrapper for the D programming language. * Copyright (C) 2008 Julien Dagorn (sirjulio13@gmail.com) * Copyright (C) 2010 Andreas Hollandt * * 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.soundstream; import dsfml.system.alloc; import dsfml.system.common; import dsfml.system.vector3; import dsfml.system.linkedlist; import dsfml.system.lock; import core.thread; import core.sync.mutex; import dsfml.audio.sound; import dsfml.audio.soundsource; /** * SoundStream is a streamed sound, ie samples are acquired * while the sound is playing. Use it for big sounds that would * require hundreds of MB in memory, or for streaming sound from the network. * * SoundStream is a base class and cannot be instanciated directly. * * $(B onGetData override will be called by a different thread, take care of synchronization issues.) onStart is called by the thread which called .play(). * * ------------------------ * class MySoundStream : SoundStream * { * this() * { * super(2, 44100); // you need to initialize the base class before any operation. * } * protected bool onGetData(out short[] data) * { * //You need to fill data array with some samples * * return true; //or false if you want to stop playback * } * * protected bool onStart() * { * return true; * } * } * ------------------------ */ abstract class SoundStream : SoundSource!("sfSoundStream") { override void dispose() { stop(); sfSoundStream_Destroy(m_ptr); s_instances.remove(m_id); } /** * Start playing the stream */ void play() { m_flag = true; sfSoundStream_Play(m_ptr); if (status != SoundStatus.Paused) { m_t = new Thread(&threadPoll); m_t.start(); } } /** * Pause the stream */ void pause() { sfSoundStream_Pause(m_ptr); } /** * Stop the stream */ void stop() { m_flag = false; sfSoundStream_Stop(m_ptr); m_t.join(); if (m_dummy !is null) delete m_dummy; } /** * Get number of channels of the stream * * Returns: * number of channels */ uint getChannelsCount() { return m_channelsCount; } /** * Get the sample rate of the stream * * Returns: * sample rate */ uint getSampleRate() { return m_sampleRate; } /** * Get the current playing offset of the stream * * Returns: * current playing offset, in seconds. */ float getPlayingOffset() { return sfSoundStream_GetPlayingOffset(m_ptr); } /** * Set the current playing position of a music * * Params: * timeOffset = New playing position, expressed in seconds */ void setPlayingOffset(float timeOffset) { sfSoundStream_SetPlayingOffset(m_ptr, timeOffset); } /** * Tell whether or not the stream is looping * * Returns: * True if the music is looping, false otherwise */ bool getLoop() { if (m_ptr !is null) return cast(bool)sfSoundStream_GetLoop(m_ptr); return false; } /** * Set the stream loop state. * * Disabled by default. * * Params: * loop = true to play in loop, false to play once */ void setLoop(bool loop) { if (m_ptr !is null) sfSoundStream_SetLoop(m_ptr, loop); } protected: /** * Protected constructor * * Params: * channelsCount = number of channel * sampleRate = sample rate of the stream * */ this(uint channelsCount, uint sampleRate) { m_channelsCount = channelsCount; m_sampleRate = sampleRate; super(sfSoundStream_Create(&externalOnGetData, &externalOnSeek, channelsCount, sampleRate, &m_id)); m_mutex = new Mutex(); m_samples = new LinkedList!(Data); m_t = new Thread(&this.threadPoll); m_id = ++s_seed; s_instances[m_id] = this; } /** * Called each time the stream is seeked */ abstract void onSeek(); /** * Called each time the stream needs new data. * This method will be call by an other thread, take care of possible synchronisation issues. * * Params: * data = array of samples to stream * * Returns: * true to continue streaming, false to stop */ abstract bool onGetData(out short[] data); private: // Called sync when user calling play() // TODO: check if it's correct that way extern(C) static void externalOnSeek(float t, void* user) { int id; if ((id = *cast(int*) user) in s_instances) { SoundStream temp = s_instances[id]; return (temp.onSeek()); } } // C Thread callback (no allocation can be done) extern (C) static int externalOnGetData(sfSoundStreamChunk* data, void* user) { int id, flag = false; // Get calling soundStream if ((id = *cast(int*) user) in s_instances) { SoundStream temp = s_instances[id]; //if no samples are available but streaming is not stopped, we sleep the thread while (temp.m_samples.empty && temp.m_flag) Thread.sleep(10_000_0); // 10ms scope Lock l = new Lock(temp.m_mutex); if (!temp.m_samples.empty) { if (temp.m_dummy !is null) delete temp.m_dummy; temp.m_dummy = temp.m_samples.dequeue; if ((flag = temp.m_dummy.Flag) == true) { data.Samples = temp.m_dummy.Samples.ptr; data.NbSamples = temp.m_dummy.Samples.length; } else { data.Samples = null; data.NbSamples = 0; } } } return flag; } // Managed thread loop void threadPoll() { short[] data; bool ret = true; // while streaming is active ... while (ret && m_flag) { { scope Lock l = new Lock(m_mutex); // see how many samples are available (keep always 2 samples ready) if (m_samples.getCount < 2) { // if we need new samples, lock and call derived class ret = onGetData(data); m_samples.enqueue(new Data(data, ret)); } } Thread.sleep(100_000_0); // 100ms } } private class Data { short[] Samples; bool Flag; mixin Alloc; this (short[] samples, bool flag) { this.Samples = samples; this.Flag = flag; } } Thread m_t; Mutex m_mutex; LinkedList!(Data) m_samples; Data m_dummy; bool m_flag; uint m_channelsCount; uint m_sampleRate; int m_id; static SoundStream[int] s_instances; static int s_seed = 0; // External ==================================================================== extern (C) { struct sfSoundStreamChunk{ short* Samples; uint NbSamples; } alias void function(float, void*) sfSoundStreamSeekCallback; alias int function (sfSoundStreamChunk*, void*) sfSoundStreamGetDataCallback; alias SFMLClass function(sfSoundStreamGetDataCallback, sfSoundStreamSeekCallback, uint, uint, void*) pf_sfSoundStream_Create; alias void function(SFMLClass) pf_sfSoundStream_Destroy; alias void function(SFMLClass) pf_sfSoundStream_Play; alias void function(SFMLClass) pf_sfSoundStream_Pause; alias void function(SFMLClass) pf_sfSoundStream_Stop; alias uint function(SFMLClass) pf_sfSoundStream_GetChannelsCount; alias uint function(SFMLClass) pf_sfSoundStream_GetSampleRate; alias float function(SFMLClass) pf_sfSoundStream_GetPlayingOffset; alias void function(SFMLClass, float) pf_sfSoundStream_SetPlayingOffset; alias int function(SFMLClass) pf_sfSoundStream_GetLoop; alias void function(SFMLClass, int) pf_sfSoundStream_SetLoop; static pf_sfSoundStream_Create sfSoundStream_Create; static pf_sfSoundStream_Destroy sfSoundStream_Destroy; static pf_sfSoundStream_Play sfSoundStream_Play; static pf_sfSoundStream_Pause sfSoundStream_Pause; static pf_sfSoundStream_Stop sfSoundStream_Stop; static pf_sfSoundStream_GetChannelsCount sfSoundStream_GetChannelsCount; static pf_sfSoundStream_GetSampleRate sfSoundStream_GetSampleRate; static pf_sfSoundStream_GetPlayingOffset sfSoundStream_GetPlayingOffset; static pf_sfSoundStream_SetPlayingOffset sfSoundStream_SetPlayingOffset; static pf_sfSoundStream_GetLoop sfSoundStream_GetLoop; static pf_sfSoundStream_SetLoop sfSoundStream_SetLoop; } static this() { debug DllLoader dll = DllLoader.load("csfml-audio-d"); else DllLoader dll = DllLoader.load("csfml-audio"); sfSoundStream_Create = cast(pf_sfSoundStream_Create)dll.getSymbol("sfSoundStream_Create"); sfSoundStream_Destroy = cast(pf_sfSoundStream_Destroy)dll.getSymbol("sfSoundStream_Destroy"); sfSoundStream_Play = cast(pf_sfSoundStream_Play)dll.getSymbol("sfSoundStream_Play"); sfSoundStream_Pause = cast(pf_sfSoundStream_Pause)dll.getSymbol("sfSoundStream_Pause"); sfSoundStream_Stop = cast(pf_sfSoundStream_Stop)dll.getSymbol("sfSoundStream_Stop"); sfSoundStream_GetChannelsCount = cast(pf_sfSoundStream_GetChannelsCount)dll.getSymbol("sfSoundStream_GetChannelsCount"); sfSoundStream_GetSampleRate = cast(pf_sfSoundStream_GetSampleRate)dll.getSymbol("sfSoundStream_GetSampleRate"); sfSoundStream_GetPlayingOffset = cast(pf_sfSoundStream_GetPlayingOffset)dll.getSymbol("sfSoundStream_GetPlayingOffset"); sfSoundStream_SetPlayingOffset = cast(pf_sfSoundStream_SetPlayingOffset)dll.getSymbol("sfSoundStream_SetPlayingOffset"); sfSoundStream_GetLoop = cast(pf_sfSoundStream_GetLoop)dll.getSymbol("sfSoundStream_GetLoop"); sfSoundStream_SetLoop = cast(pf_sfSoundStream_SetLoop)dll.getSymbol("sfSoundStream_SetLoop"); } }