Browse Source

Added BufferedSoundStream class and sound synthesis C++ example.
Removed SharedArrayPtr DynamicCast() as it made no sense; replaced with ReinterpretCast().

Lasse Öörni 11 years ago
parent
commit
1cc8f2b82b

+ 131 - 0
Source/Engine/Audio/BufferedSoundStream.cpp

@@ -0,0 +1,131 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "BufferedSoundStream.h"
+
+#include <cstring>
+
+#include "DebugNew.h"
+
+namespace Urho3D
+{
+
+BufferedSoundStream::BufferedSoundStream() :
+    position_(0)
+{
+}
+
+BufferedSoundStream::~BufferedSoundStream()
+{
+}
+
+unsigned BufferedSoundStream::GetData(signed char* dest, unsigned numBytes)
+{
+    MutexLock lock(bufferMutex_);
+    
+    unsigned outBytes = 0;
+    
+    while (numBytes && buffers_.Size())
+    {
+        // Copy as much from the front buffer as possible, then discard it and move to the next
+        List<Pair<SharedArrayPtr<signed char>, unsigned> >::Iterator front = buffers_.Begin();
+        
+        unsigned copySize = front->second_ - position_;
+        if (copySize > numBytes)
+            copySize = numBytes;
+        
+        memcpy(dest, front->first_.Get() + position_, copySize);
+        position_ += copySize;
+        if (position_ >= front->second_)
+        {
+            buffers_.PopFront();
+            position_ = 0;
+        }
+        
+        dest += copySize;
+        outBytes += copySize;
+        numBytes -= copySize;
+    }
+    
+    return outBytes;
+}
+
+void BufferedSoundStream::AddData(void* data, unsigned numBytes)
+{
+    if (data && numBytes)
+    {
+        MutexLock lock(bufferMutex_);
+        
+        SharedArrayPtr<signed char> newBuffer(new signed char[numBytes]);
+        memcpy(newBuffer.Get(), data, numBytes);
+        buffers_.Push(MakePair(newBuffer, numBytes));
+    }
+}
+
+void BufferedSoundStream::AddData(SharedArrayPtr<signed char> data, unsigned numBytes)
+{
+    if (data && numBytes)
+    {
+        MutexLock lock(bufferMutex_);
+        
+        buffers_.Push(MakePair(data, numBytes));
+    }
+}
+
+void BufferedSoundStream::AddData(SharedArrayPtr<signed short> data, unsigned numBytes)
+{
+    if (data && numBytes)
+    {
+        MutexLock lock(bufferMutex_);
+        
+        buffers_.Push(MakePair(ReinterpretCast<signed char>(data), numBytes));
+    }
+}
+
+void BufferedSoundStream::Clear()
+{
+    MutexLock lock(bufferMutex_);
+    
+    buffers_.Clear();
+    position_ = 0;
+}
+
+unsigned BufferedSoundStream::GetBufferNumBytes() const
+{
+    MutexLock lock(bufferMutex_);
+    
+    unsigned ret = 0;
+    for (List<Pair<SharedArrayPtr<signed char>, unsigned> >::ConstIterator i = buffers_.Begin(); i != buffers_.End(); ++i)
+        ret += i->second_;
+    // Subtract amount of sound data played from the front buffer
+    ret -= position_;
+    
+    return ret;
+}
+
+float BufferedSoundStream::GetBufferLength() const
+{
+    return (float)GetBufferNumBytes() / (GetFrequency() * (float)GetSampleSize());
+}
+
+}

+ 69 - 0
Source/Engine/Audio/BufferedSoundStream.h

@@ -0,0 +1,69 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "ArrayPtr.h"
+#include "List.h"
+#include "Mutex.h"
+#include "Pair.h"
+#include "SoundStream.h"
+
+namespace Urho3D
+{
+
+/// %Sound stream that supports manual buffering of data from the main thread.
+class BufferedSoundStream : public SoundStream
+{
+public:
+    /// Construct.
+    BufferedSoundStream();
+    /// Destruct.
+    ~BufferedSoundStream();
+    
+    /// Produce sound data into destination. Return number of bytes produced. Called by SoundSource from the mixing thread.
+    virtual unsigned GetData(signed char* dest, unsigned numBytes);
+    
+    /// Buffer sound data. Makes a copy of it.
+    void AddData(void* data, unsigned numBytes);
+    /// Buffer sound data by taking ownership of it.
+    void AddData(SharedArrayPtr<signed char> data, unsigned numBytes);
+    /// Buffer sound data by taking ownership of it.
+    void AddData(SharedArrayPtr<signed short> data, unsigned numBytes);
+    /// Remove all buffered audio data.
+    void Clear();
+    
+    /// Return amount of buffered (unplayed) sound data in bytes.
+    unsigned GetBufferNumBytes() const;
+    /// Return length of buffered (unplayed) sound data in seconds.
+    float GetBufferLength() const;
+    
+private:
+    /// Buffers and their sizes.
+    List<Pair<SharedArrayPtr<signed char>, unsigned> > buffers_;
+    /// Byte position in the frontmost buffer.
+    unsigned position_;
+    /// Mutex for buffer data.
+    mutable Mutex bufferMutex_;
+};
+
+}

+ 7 - 4
Source/Engine/Audio/OggVorbisSoundStream.cpp

@@ -67,15 +67,18 @@ unsigned OggVorbisSoundStream::GetData(signed char* dest, unsigned numBytes)
     
     
     unsigned channels = stereo_ ? 2 : 1;
     unsigned channels = stereo_ ? 2 : 1;
     unsigned outSamples = stb_vorbis_get_samples_short_interleaved(vorbis, channels, (short*)dest, numBytes >> 1);
     unsigned outSamples = stb_vorbis_get_samples_short_interleaved(vorbis, channels, (short*)dest, numBytes >> 1);
+    unsigned outBytes = (outSamples * channels) << 1;
     
     
-    // Rewind and retry if produced no output and should loop
-    if (!outSamples && !stopAtEnd_)
+    // Rewind and retry if is looping and produced less output than should have
+    if (outBytes < numBytes && !stopAtEnd_)
     {
     {
+        numBytes -= outBytes;
         stb_vorbis_seek_start(vorbis);
         stb_vorbis_seek_start(vorbis);
-        outSamples = stb_vorbis_get_samples_short_interleaved(vorbis, channels, (short*)dest, numBytes >> 1);
+        outSamples = stb_vorbis_get_samples_short_interleaved(vorbis, channels, (short*)(dest + outBytes), numBytes >> 1);
+        outBytes += (outSamples * channels) << 1;
     }
     }
     
     
-    return (outSamples * channels) << 1;
+    return outBytes;
 }
 }
 
 
 }
 }

+ 3 - 3
Source/Engine/Audio/SoundStream.h

@@ -41,15 +41,15 @@ public:
     
     
     /// Set sound data format.
     /// Set sound data format.
     void SetFormat(unsigned frequency, bool sixteenBit, bool stereo);
     void SetFormat(unsigned frequency, bool sixteenBit, bool stereo);
-    /// Set whether playback should stop when no more data (GetData() returns 0 bytes.) Default false.
+    /// Set whether playback should stop when no more data. Default false.
     void SetStopAtEnd(bool enable);
     void SetStopAtEnd(bool enable);
     
     
     /// Return sample size.
     /// Return sample size.
     unsigned GetSampleSize() const;
     unsigned GetSampleSize() const;
     /// Return default frequency as a float.
     /// Return default frequency as a float.
-    float GetFrequency() { return (float)frequency_; }
+    float GetFrequency() const { return (float)frequency_; }
     /// Return default frequency as an integer.
     /// Return default frequency as an integer.
-    unsigned GetIntFrequency() { return frequency_; }
+    unsigned GetIntFrequency() const { return frequency_; }
     /// Return whether playback should stop when no more data.
     /// Return whether playback should stop when no more data.
     bool GetStopAtEnd() const { return stopAtEnd_; }
     bool GetStopAtEnd() const { return stopAtEnd_; }
     /// Return whether data is sixteen bit.
     /// Return whether data is sixteen bit.

+ 11 - 17
Source/Engine/Container/ArrayPtr.h

@@ -122,19 +122,13 @@ public:
         AddRef();
         AddRef();
     }
     }
     
     
-    /// Perform a dynamic cast from a shared array pointer of another type.
-    template <class U> void DynamicCast(const SharedArrayPtr<U>& rhs)
+   /// Perform a reinterpret cast from a shared array pointer of another type.
+    template <class U> void ReinterpretCast(const SharedArrayPtr<U>& rhs)
     {
     {
         ReleaseRef();
         ReleaseRef();
-        ptr_ = dynamic_cast<T*>(rhs.Get());
-        
-        if (ptr_)
-        {
-            refCount_ = rhs.RefCountPtr();
-            AddRef();
-        }
-        else
-            refCount_ = 0;
+        ptr_ = reinterpret_cast<T*>(rhs.Get());
+        refCount_ = rhs.RefCountPtr();
+        AddRef();
     }
     }
     
     
     /// Check if the pointer is null.
     /// Check if the pointer is null.
@@ -201,11 +195,11 @@ template <class T, class U> SharedArrayPtr<T> StaticCast(const SharedArrayPtr<U>
     return ret;
     return ret;
 }
 }
 
 
-/// Perform a dynamic cast from one shared array pointer type to another.
-template <class T, class U> SharedArrayPtr<T> DynamicCast(const SharedArrayPtr<U>& ptr)
+/// Perform a reinterpret cast from one shared array pointer type to another.
+template <class T, class U> SharedArrayPtr<T> ReinterpretCast(const SharedArrayPtr<U>& ptr)
 {
 {
     SharedArrayPtr<T> ret;
     SharedArrayPtr<T> ret;
-    ret.DynamicCast(ptr);
+    ret.ReinterpretCast(ptr);
     return ret;
     return ret;
 }
 }
 
 
@@ -409,11 +403,11 @@ template <class T, class U> WeakArrayPtr<T> StaticCast(const WeakArrayPtr<U>& pt
     return ret;
     return ret;
 }
 }
 
 
-/// Perform a dynamic cast from one weak pointer type to another.
-template <class T, class U> WeakArrayPtr<T> DynamicCast(const WeakArrayPtr<U>& ptr)
+/// Perform a reinterpret cast from one weak pointer type to another.
+template <class T, class U> WeakArrayPtr<T> ReinterpretCast(const WeakArrayPtr<U>& ptr)
 {
 {
     WeakArrayPtr<T> ret;
     WeakArrayPtr<T> ret;
-    ret.DynamicCast(ptr);
+    ret.ReinterpretCast(ptr);
     return ret;
     return ret;
 }
 }
 
 

+ 33 - 0
Source/Samples/29_SoundSynthesis/CMakeLists.txt

@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2008-2014 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME 29_SoundSynthesis)
+
+# Define source files
+define_source_files (EXTRA_H_FILES ${COMMON_SAMPLE_H_FILES})
+
+# Setup target with resource copying
+setup_main_executable ()
+
+# Setup test cases
+add_test (NAME ${TARGET_NAME} COMMAND ${TARGET_NAME} -timeout ${TEST_TIME_OUT})

+ 144 - 0
Source/Samples/29_SoundSynthesis/SoundSynthesis.cpp

@@ -0,0 +1,144 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "BufferedSoundStream.h"
+#include "CoreEvents.h"
+#include "Engine.h"
+#include "Font.h"
+#include "Input.h"
+#include "Log.h"
+#include "Node.h"
+#include "SoundSource.h"
+#include "Text.h"
+#include "UI.h"
+
+#include "SoundSynthesis.h"
+
+#include "DebugNew.h"
+
+// Expands to this example's entry-point
+DEFINE_APPLICATION_MAIN(SoundSynthesis)
+
+SoundSynthesis::SoundSynthesis(Context* context) :
+    Sample(context),
+    filter_(0.5f),
+    accumulator_(0.0f),
+    osc1_(0.0f),
+    osc2_(0.0f)
+{
+}
+
+void SoundSynthesis::Start()
+{
+    // Execute base class startup
+    Sample::Start();
+
+    // Create the sound stream & start playback
+    CreateSound();
+    
+    // Create the UI content
+    CreateInstructions();
+    
+    // Hook up to the frame update events
+    SubscribeToEvents();
+}
+
+void SoundSynthesis::CreateSound()
+{
+    // Sound source needs a node so that it is considered enabled
+    node_ = new Node(context_);
+    SoundSource* source = node_->CreateComponent<SoundSource>();
+    
+    soundStream_ = new BufferedSoundStream();
+    // Set format: 44100 kHz, sixteen bit, mono
+    soundStream_->SetFormat(44100, true, false);
+    
+    // Start playback. We don't have data in the stream yet, but the SoundSource will wait until there is data,
+    // as the stream is by default in the "don't stop at end" mode
+    source->Play(soundStream_);
+}
+
+void SoundSynthesis::UpdateSound()
+{
+    // Try to keep 1/10 seconds of sound in the buffer, to avoid both dropouts and unnecessary latency
+    float targetLength = 1.0f / 10.0f;
+    float requiredLength = targetLength - soundStream_->GetBufferLength();
+    if (requiredLength < 0.0f)
+        return;
+    
+    unsigned numSamples = (unsigned)(soundStream_->GetFrequency() * requiredLength);
+    if (!numSamples)
+        return;
+    
+    SharedArrayPtr<signed short> newData(new signed short[numSamples]);
+    for (unsigned i = 0; i < numSamples; ++i)
+    {
+        osc1_ = fmodf(osc1_ + 1.0f, 360.0f);
+        osc2_ = fmodf(osc2_ + 1.002f, 360.0f);
+        
+        float newValue = Clamp((Sin(osc1_) + Sin(osc2_)) * 200000.0f, -32767.0f, 32767.0f);
+        accumulator_ = Lerp(accumulator_, newValue, filter_);
+        newData[i] = (int)accumulator_;
+    }
+    
+    soundStream_->AddData(newData, numSamples * sizeof(signed short));
+}
+
+void SoundSynthesis::CreateInstructions()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    UI* ui = GetSubsystem<UI>();
+    
+    // Construct new Text object, set string to display and font to use
+    Text* instructionText = ui->GetRoot()->CreateChild<Text>();
+    instructionText->SetText("Use cursor up and down to control sound filtering");
+    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
+    
+    // Position the text relative to the screen center
+    instructionText->SetHorizontalAlignment(HA_CENTER);
+    instructionText->SetVerticalAlignment(VA_CENTER);
+    instructionText->SetPosition(0, ui->GetRoot()->GetHeight() / 4);
+}
+
+void SoundSynthesis::SubscribeToEvents()
+{
+    // Subscribe HandleUpdate() function for processing update events
+    SubscribeToEvent(E_UPDATE, HANDLER(SoundSynthesis, HandleUpdate));
+}
+
+void SoundSynthesis::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace Update;
+
+    // Take the frame time step, which is stored as a float
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+    
+    // Use keys to control the filter constant
+    Input* input = GetSubsystem<Input>();
+    if (input->GetKeyDown(KEY_UP))
+        filter_ += timeStep * 0.5f;
+    if (input->GetKeyDown(KEY_DOWN))
+        filter_ -= timeStep * 0.5f;
+    filter_ = Clamp(filter_, 0.01f, 1.0f);
+    
+    UpdateSound();
+}

+ 73 - 0
Source/Samples/29_SoundSynthesis/SoundSynthesis.h

@@ -0,0 +1,73 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Sample.h"
+
+namespace Urho3D
+{
+
+class Node;
+class BufferedSoundStream;
+
+}
+
+/// Sound synthesis example.
+/// This sample demonstrates:
+///     - Playing back a sound stream produced in realtime in code
+class SoundSynthesis : public Sample
+{
+    OBJECT(SoundSynthesis);
+
+public:
+    /// Construct.
+    SoundSynthesis(Context* context);
+
+    /// Setup after engine initialization and before running the main loop.
+    virtual void Start();
+
+private:
+    /// Construct the sound stream and start playback.
+    void CreateSound();
+    /// Buffer more sound data.
+    void UpdateSound();
+    /// Construct an instruction text to the UI.
+    void CreateInstructions();
+    /// Subscribe to application-wide logic update events.
+    void SubscribeToEvents();
+    /// Handle the logic update event.
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+    
+    /// Scene node for the sound component.
+    SharedPtr<Node> node_;
+    /// Sound stream that we update.
+    SharedPtr<BufferedSoundStream> soundStream_;
+    /// Filter coefficient for the sound.
+    float filter_;
+    /// Synthesis accumulator.
+    float accumulator_;
+    /// First oscillator.
+    float osc1_;
+    /// Second oscillator.
+    float osc2_;
+};

+ 1 - 0
Source/Samples/CMakeLists.txt

@@ -66,3 +66,4 @@ add_subdirectory (25_Urho2DParticle)
 add_subdirectory (26_ConsoleInput)
 add_subdirectory (26_ConsoleInput)
 add_subdirectory (27_Urho2DPhysics)
 add_subdirectory (27_Urho2DPhysics)
 add_subdirectory (28_Urho2DPhysicsRope)
 add_subdirectory (28_Urho2DPhysicsRope)
+add_subdirectory (29_SoundSynthesis)