Browse Source

Simplify sound stream code and improve the SoundSynthesis sample. Closes #257.

Lasse Öörni 11 years ago
parent
commit
a06e4238b3

+ 40 - 67
Source/Engine/Audio/SoundSource.cpp

@@ -102,6 +102,8 @@ static const char* typeNames[] =
 
 static const float AUTOREMOVE_DELAY = 0.25f;
 
+static const int STREAM_SAFETY_SAMPLES = 4;
+
 extern const char* AUDIO_CATEGORY;
 
 SoundSource::SoundSource(Context* context) :
@@ -116,8 +118,7 @@ SoundSource::SoundSource(Context* context) :
     position_(0),
     fractPosition_(0),
     timePosition_(0.0f),
-    streamWritePosition_(0),
-    streamStopped_(false)
+    unusedStreamSize_(0)
 {
     audio_ = GetSubsystem<Audio>();
 
@@ -323,66 +324,32 @@ void SoundSource::Mix(int* dest, unsigned samples, int mixRate, bool stereo, boo
     if (!position_ || (!sound_ && !soundStream_) || !IsEnabledEffective())
         return;
     
+    int streamFilledSize, outBytes;
+    
     if (soundStream_ && streamBuffer_)
     {
-        unsigned streamBufferSize = streamBuffer_->GetDataSize();
+        int streamBufferSize = streamBuffer_->GetDataSize();
+        // Calculate how many bytes of stream sound data is needed
+        int neededSize = (int)((float)samples * frequency_ / (float)mixRate);
+        // Add a little safety buffer. Subtract previous unused data
+        neededSize += STREAM_SAFETY_SAMPLES;
+        neededSize *= soundStream_->GetSampleSize();
+        neededSize -= unusedStreamSize_;
+        neededSize = Clamp(neededSize, 0, streamBufferSize - unusedStreamSize_);
         
-        // Decode new audio in stream mode. If stream experienced an underrun, try restarting
-        if (streamStopped_)
-        {
-            // If stream should stop at end, do not loop after current buffer is played
-            if (soundStream_->GetStopAtEnd())
-                streamBuffer_->SetLooped(false);
-            else
-            {
-                unsigned outBytes = soundStream_->GetData(streamBuffer_->GetStart(), streamBufferSize);
-                if (outBytes)
-                {
-                    // If did not get a full buffer, fill the rest with zero
-                    if (outBytes < streamBufferSize)
-                        memset(streamBuffer_->GetStart() + outBytes, 0, streamBufferSize - outBytes);
-                    streamStopped_ = false;
-                    // Start playback from beginning of stream buffer again so that there is minimal latency
-                    position_ = streamBuffer_->GetStart();
-                    fractPosition_ = 0;
-                    streamWritePosition_ = 0;
-                }
-            }
-        }
-        else
-        {
-            unsigned currentPos = position_ - streamBuffer_->GetStart();
-            unsigned totalBytes;
-            
-            // Handle possible wraparound
-            if (currentPos >= streamWritePosition_)
-                totalBytes = currentPos - streamWritePosition_;
-            else
-                totalBytes = streamBuffer_->GetDataSize() - streamWritePosition_ + currentPos;
-            
-            while (totalBytes)
-            {
-                // Calculate size of current stream data request (may need to do in two parts if wrapping)
-                unsigned bytes = streamBuffer_->GetDataSize() - streamWritePosition_;
-                if (bytes > totalBytes)
-                    bytes = totalBytes;
-                
-                unsigned outBytes = soundStream_->GetData(streamBuffer_->GetStart() + streamWritePosition_, bytes);
-                // If got less than the requested amount, stream reached end or experienced underrun. Fill rest with zero
-                if (outBytes < bytes)
-                {
-                    streamStopped_ = true;
-                    memset(streamBuffer_->GetStart() + streamWritePosition_ + outBytes, 0, bytes - outBytes);
-                }
-                
-                streamWritePosition_ += bytes;
-                streamWritePosition_ %= streamBuffer_->GetDataSize();
-                totalBytes -= bytes;
-            }
-        }
+        // Always start play position at the beginning of the stream buffer
+        position_ = streamBuffer_->GetStart();
+        
+        // Request new data from the stream
+        signed char* dest = streamBuffer_->GetStart() + unusedStreamSize_;
+        outBytes = neededSize ? soundStream_->GetData(dest, neededSize) : 0;
+        dest += outBytes;
+        // Zero-fill rest if stream did not produce enough data
+        if (outBytes < neededSize)
+            memset(dest, 0, neededSize - outBytes);
         
-        // Correct interpolation of the stream buffer
-        streamBuffer_->FixInterpolation();
+        // Calculate amount of total bytes of data in stream buffer now, to know how much went unused after mixing
+        streamFilledSize = neededSize + unusedStreamSize_;
     }
 
     // If streaming, play the stream buffer. Otherwise play the original sound
@@ -426,9 +393,22 @@ void SoundSource::Mix(int* dest, unsigned samples, int mixRate, bool stereo, boo
         }
     }
 
-    // Update the time position
+    // Update the time position. In stream mode, copy unused data back to the beginning of the stream buffer
     if (soundStream_)
+    {
         timePosition_ += ((float)samples / (float)mixRate) * frequency_ / soundStream_->GetFrequency();
+        
+        unusedStreamSize_ = Max(streamFilledSize - (int)(size_t)(position_ - streamBuffer_->GetStart()), 0);
+        if (unusedStreamSize_)
+            memcpy(streamBuffer_->GetStart(), (const void*)position_, unusedStreamSize_);
+        
+        // If stream did not produce any data, stop if applicable
+        if (!outBytes && soundStream_->GetStopAtEnd())
+        {
+            position_ = 0;
+            return;
+        }
+    }
     else if (sound_)
         timePosition_ = ((float)(int)(size_t)(position_ - sound_->GetStart())) / (sound_->GetSampleSize() * sound_->GetFrequency());
 }
@@ -529,14 +509,8 @@ void SoundSource::PlayLockless(SharedPtr<SoundStream> stream)
         streamBuffer_->SetFormat(stream->GetIntFrequency(), stream->IsSixteenBit(), stream->IsStereo());
         streamBuffer_->SetLooped(true);
         
-        // Fill stream buffer with initial data
-        unsigned outBytes = stream->GetData(streamBuffer_->GetStart(), streamBufferSize);
-        if (outBytes < streamBufferSize)
-            memset(streamBuffer_->GetStart() + outBytes, 0, streamBufferSize - outBytes);
-        
         soundStream_ = stream;
-        streamWritePosition_ = 0;
-        streamStopped_ = false;
+        unusedStreamSize_ = 0;
         position_ = streamBuffer_->GetStart();
         fractPosition_ = 0;
         return;
@@ -550,7 +524,6 @@ void SoundSource::StopLockless()
 {
     position_ = 0;
     timePosition_ = 0.0f;
-    streamStopped_ = true;
     
     // Free the sound stream and decode buffer if a stream was playing
     soundStream_.Reset();

+ 2 - 4
Source/Engine/Audio/SoundSource.h

@@ -172,10 +172,8 @@ private:
     volatile float timePosition_;
     /// Decode buffer.
     SharedPtr<Sound> streamBuffer_;
-    /// Position in stream buffer the next audio data from the stream will be written to.
-    unsigned streamWritePosition_;
-    /// Stream underrun flag.
-    bool streamStopped_;
+    /// Unused stream bytes from previous frame.
+    int unusedStreamSize_;
 };
 
 }

+ 12 - 8
Source/Samples/29_SoundSynthesis/SoundSynthesis.cpp

@@ -43,7 +43,7 @@ SoundSynthesis::SoundSynthesis(Context* context) :
     filter_(0.5f),
     accumulator_(0.0f),
     osc1_(0.0f),
-    osc2_(0.0f)
+    osc2_(180.0f)
 {
 }
 
@@ -95,7 +95,7 @@ void SoundSynthesis::UpdateSound()
         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);
+        float newValue = Clamp((Sin(osc1_) + Sin(osc2_)) * 100000.0f, -32767.0f, 32767.0f);
         accumulator_ = Lerp(accumulator_, newValue, filter_);
         newData[i] = (int)accumulator_;
     }
@@ -109,14 +109,15 @@ void SoundSynthesis::CreateInstructions()
     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);
+    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);
+    instructionText_->SetTextAlignment(HA_CENTER);
+    instructionText_->SetHorizontalAlignment(HA_CENTER);
+    instructionText_->SetVerticalAlignment(VA_CENTER);
+    instructionText_->SetPosition(0, ui->GetRoot()->GetHeight() / 4);
 }
 
 void SoundSynthesis::SubscribeToEvents()
@@ -140,5 +141,8 @@ void SoundSynthesis::HandleUpdate(StringHash eventType, VariantMap& eventData)
         filter_ -= timeStep * 0.5f;
     filter_ = Clamp(filter_, 0.01f, 1.0f);
     
+    instructionText_->SetText("Use cursor up and down to control sound filtering\n"
+        "Coefficient: " + String(filter_));
+    
     UpdateSound();
 }

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

@@ -62,6 +62,8 @@ private:
     SharedPtr<Node> node_;
     /// Sound stream that we update.
     SharedPtr<BufferedSoundStream> soundStream_;
+    /// Instruction text.
+    SharedPtr<Text> instructionText_;
     /// Filter coefficient for the sound.
     float filter_;
     /// Synthesis accumulator.