changeset 310:8c59cc68eabd tonioni

Initial implementation of clip note mix
author Chris Cannam
date Tue, 07 Jan 2014 17:11:14 +0000
parents 71050ffd0141
children 86fb9160d122
files audioio/ClipMixer.cpp audioio/ClipMixer.h
diffstat 2 files changed, 130 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/audioio/ClipMixer.cpp	Tue Jan 07 15:52:47 2014 +0000
+++ b/audioio/ClipMixer.cpp	Tue Jan 07 17:11:14 2014 +0000
@@ -16,6 +16,7 @@
 #include "ClipMixer.h"
 
 #include <sndfile.h>
+#include <cmath>
 
 #include "base/Debug.h"
 
@@ -89,12 +90,29 @@
     m_clipLength = info.frames;
     m_clipF0 = f0;
     m_clipRate = info.samplerate;
+
+    return true;
 }
 
 void
 ClipMixer::reset()
 {
-    //!!!
+    m_playing.clear();
+}
+
+float
+ClipMixer::getResampleRatioFor(float frequency)
+{
+    if (!m_clipData) return 1.0;
+    float pitchRatio = frequency / m_clipF0;
+    float resampleRatio = m_sampleRate / m_clipRate;
+    return pitchRatio * resampleRatio;
+}
+
+int
+ClipMixer::getResampledClipDuration(float frequency)
+{
+    return int(ceil(m_clipLength * getResampleRatioFor(frequency)));
 }
 
 void
@@ -103,6 +121,102 @@
                std::vector<NoteStart> newNotes, 
                std::vector<NoteEnd> endingNotes)
 {
-    //!!! do this!
+    foreach (NoteStart note, newNotes) {
+        m_playing.push_back(note);
+    }
+
+    std::vector<NoteStart> remaining;
+
+    float *levels = new float[m_channels];
+
+    foreach (NoteStart note, m_playing) {
+
+        for (int c = 0; c < m_channels; ++c) {
+            levels[c] = gain;
+        }
+        if (note.pan != 0.0 && m_channels == 2) {
+            levels[0] *= 1.0 - note.pan;
+            levels[1] *= note.pan + 1.0;
+        }
+
+        int start = note.frameOffset;
+        int durationHere = m_blockSize;
+        if (start > 0) durationHere = m_blockSize - start;
+
+        bool ending = false;
+
+        foreach (NoteEnd end, endingNotes) {
+            if (end.frequency == note.frequency && 
+                end.frameOffset >= start &&
+                end.frameOffset <= m_blockSize) {
+                ending = true;
+                durationHere = end.frameOffset;
+                if (start > 0) durationHere = end.frameOffset - start;
+                break;
+            }
+        }
+
+        int clipDuration = getResampledClipDuration(note.frequency);
+        if (start + clipDuration > 0) {
+            if (start < 0 && start + clipDuration < durationHere) {
+                durationHere = clipDuration - start;
+            }
+            if (durationHere > 0) {
+                mixNote(toBuffers,
+                        levels,
+                        note.frequency,
+                        start < 0 ? -start : 0,
+                        start > 0 ?  start : 0,
+                        durationHere);
+            }
+        }
+
+        if (!ending) {
+            NoteStart adjusted = note;
+            adjusted.frameOffset -= m_blockSize;
+            remaining.push_back(adjusted);
+        }
+    }
+
+    m_playing = remaining;
 }
 
+void
+ClipMixer::mixNote(float **toBuffers,
+                   float *levels,
+                   float frequency,
+                   int sourceOffset,
+                   int targetOffset,
+                   int sampleCount)
+{
+    if (!m_clipData) return;
+
+    float ratio = getResampleRatioFor(frequency);
+    
+    //!!! todo: release time
+
+    for (int i = 0; i < sampleCount; ++i) {
+
+        int s = sourceOffset + i;
+
+        float os = s / ratio;
+        int osi = int(floor(os));
+
+        //!!! just linear interpolation for now (same as SV's sample
+        //!!! player). a small sinc kernel would be better and
+        //!!! probably "good enough"
+        float value = 0.f;
+        if (osi < m_clipLength) {
+            value += m_clipData[osi];
+        }
+        if (osi + 1 < m_clipLength) {
+            value += (m_clipData[osi + 1] - m_clipData[osi]) * (os - osi);
+        }
+        
+        for (int c = 0; c < m_channels; ++c) {
+            toBuffers[c][targetOffset + i] = levels[c] * value;
+        }
+    }
+}
+
+
--- a/audioio/ClipMixer.h	Tue Jan 07 15:52:47 2014 +0000
+++ b/audioio/ClipMixer.h	Tue Jan 07 17:11:14 2014 +0000
@@ -40,15 +40,15 @@
     //!!! AudioGenerator's NoteOff?
 
     struct NoteStart {
-	int frameOffset; // in current processing block
+	int frameOffset; // within current processing block
 	float frequency; // Hz
 	float level; // volume in range (0,1]
 	float pan; // range [-1,1]
     };
 
     struct NoteEnd {
+	int frameOffset; // in current processing block
         float frequency; // matching note start
-	int frameOffset; // in current processing block
     };
 
     void mix(float **toBuffers, 
@@ -67,6 +67,18 @@
     int m_clipLength;
     float m_clipF0;
     float m_clipRate;
+
+    std::vector<NoteStart> m_playing;
+
+    float getResampleRatioFor(float frequency);
+    int getResampledClipDuration(float frequency);
+
+    void mixNote(float **toBuffers, 
+                 float *levels,
+                 float frequency,
+                 int sourceOffset, // within resampled note
+                 int targetOffset, // within target buffer
+                 int sampleCount);
 };