summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Glover <j@johnglover.net>2012-06-28 11:55:57 +0100
committerJohn Glover <j@johnglover.net>2012-06-28 11:55:57 +0100
commit7ad9d0fc803e3a778ec3cdcdc1a0537e6a635a02 (patch)
treeb4159e59de0a74d29e4e19b9e5a350daddc42915
parent3a865c69d0823b4255bb73e3c79f60092ad944fb (diff)
downloadsimpl-7ad9d0fc803e3a778ec3cdcdc1a0537e6a635a02.tar.gz
simpl-7ad9d0fc803e3a778ec3cdcdc1a0537e6a635a02.tar.bz2
simpl-7ad9d0fc803e3a778ec3cdcdc1a0537e6a635a02.zip
[base] Add C++ PartialTracking implementation.
-rw-r--r--simpl/base.pyx101
-rw-r--r--src/simpl/base.cpp127
-rw-r--r--src/simpl/base.h61
-rw-r--r--tests/test_base.py39
4 files changed, 301 insertions, 27 deletions
diff --git a/simpl/base.pyx b/simpl/base.pyx
index 3f811a1..bb34e14 100644
--- a/simpl/base.pyx
+++ b/simpl/base.pyx
@@ -35,10 +35,11 @@ cdef extern from "../src/simpl/base.h" namespace "simpl":
void clear()
# partials
- # int num_partials()
- # int max_partials()
- # void max_partials(int new_max_partials)
- # void add_partial(Partial partial)
+ int num_partials()
+ int max_partials()
+ void max_partials(int new_max_partials)
+ c_Peak* partial(int partial_number)
+ void partial(int partial_number, c_Peak* peak)
# audio buffers
int size()
@@ -77,6 +78,20 @@ cdef extern from "../src/simpl/base.h" namespace "simpl":
vector[c_Peak*] find_peaks_in_frame(c_Frame* frame)
vector[c_Frame*] find_peaks(int audio_size, double* audio)
+ cdef cppclass c_PartialTracking "simpl::PartialTracking":
+ c_PartialTracking()
+ void clear()
+ int sampling_rate()
+ void sampling_rate(int new_sampling_rate)
+ int max_partials()
+ void max_partials(int new_max_partials)
+ int min_partial_length()
+ void min_partial_length(int new_min_partial_length)
+ int max_gap()
+ void max_gap(int new_max_gap)
+ vector[c_Peak*] update_partials(c_Frame* frame)
+ vector[c_Frame*] find_partials(vector[c_Frame*] frames)
+
cdef class Peak:
cdef c_Peak* thisptr
@@ -160,6 +175,31 @@ cdef class Frame:
def clear(self):
self.thisptr.clear()
+ # partials
+ property num_partials:
+ def __get__(self): return self.thisptr.num_partials()
+ def __set__(self, int i): raise Exception("Invalid Operation")
+
+ property max_partials:
+ def __get__(self): return self.thisptr.max_partials()
+ def __set__(self, int i): self.thisptr.max_partials(i)
+
+ def partial(self, int i, Peak p=None):
+ cdef c_Peak* c_p
+ if not p:
+ c_p = self.thisptr.partial(i)
+ peak = Peak(False)
+ peak.set_peak(c_p)
+ return peak
+ else:
+ self.thisptr.partial(i, p.thisptr)
+
+ property partials:
+ def __get__(self):
+ return [self.partial(i) for i in range(self.thisptr.num_partials())]
+ def __set__(self, peaks):
+ raise Exception("NotImplemented")
+
# audio buffers
property size:
def __get__(self): return self.thisptr.size()
@@ -262,5 +302,56 @@ cdef class PeakDetection:
def find_peaks(self, np.ndarray[dtype_t, ndim=1] audio):
frames = []
- cdef vector[c_Frame*] c_frames = self.thisptr.find_peaks(len(audio), <double*> audio.data)
+ cdef vector[c_Frame*] output_frames = self.thisptr.find_peaks(len(audio), <double*> audio.data)
+ for i in range(output_frames.size()):
+ f = Frame(output_frames[i].size(), False)
+ f.set_frame(output_frames[i])
+ frames.append(f)
return frames
+
+
+cdef class PartialTracking:
+ cdef c_PartialTracking* thisptr
+
+ def __cinit__(self): self.thisptr = new c_PartialTracking()
+ def __dealloc__(self): del self.thisptr
+
+ def clear(self):
+ self.thisptr.clear()
+
+ property sampling_rate:
+ def __get__(self): return self.thisptr.sampling_rate()
+ def __set__(self, int i): self.thisptr.sampling_rate(i)
+
+ property max_partials:
+ def __get__(self): return self.thisptr.max_partials()
+ def __set__(self, int i): self.thisptr.max_partials(i)
+
+ property min_partial_length:
+ def __get__(self): return self.thisptr.min_partial_length()
+ def __set__(self, int i): self.thisptr.min_partial_length(i)
+
+ property max_gap:
+ def __get__(self): return self.thisptr.max_gap()
+ def __set__(self, int i): self.thisptr.max_gap(i)
+
+ def update_partials(self, Frame frame not None):
+ peaks = []
+ cdef vector[c_Peak*] c_peaks = self.thisptr.update_partials(frame.thisptr)
+ for i in range(c_peaks.size()):
+ peak = Peak(False)
+ peak.set_peak(c_peaks[i])
+ peaks.append(peak)
+ return peaks
+
+ def find_partials(self, frames):
+ partial_frames = []
+ cdef vector[c_Frame*] c_frames
+ for frame in frames:
+ c_frames.push_back((<Frame>frame).thisptr)
+ cdef vector[c_Frame*] output_frames = self.thisptr.find_partials(c_frames)
+ for i in range(output_frames.size()):
+ f = Frame(output_frames[i].size(), False)
+ f.set_frame(output_frames[i])
+ partial_frames.append(f)
+ return partial_frames
diff --git a/src/simpl/base.cpp b/src/simpl/base.cpp
index 9b0c28d..9c70403 100644
--- a/src/simpl/base.cpp
+++ b/src/simpl/base.cpp
@@ -38,9 +38,7 @@ bool Peak::is_free(const string direction) {
}
}
else {
- // Throw(InvalidArgument, "Invalid direction");
- // TODO: fix this
- printf("ERROR: InvalidArgument\n");
+ return false;
}
return true;
@@ -48,6 +46,37 @@ bool Peak::is_free(const string direction) {
// ---------------------------------------------------------------------------
+// Partial
+// ---------------------------------------------------------------------------
+Partial::Partial() {
+ _starting_frame = 0;
+ _partial_number = -1;
+}
+
+Partial::~Partial() {
+ _peaks.clear();
+}
+
+void Partial::add_peak(Peak* peak) {
+}
+
+int Partial::length() {
+ return _peaks.size();
+}
+
+int Partial::first_frame_number() {
+ return _starting_frame;
+}
+
+int Partial::last_frame_number() {
+ return _starting_frame + length();
+}
+
+Peak* Partial::peak(int peak_number) {
+ return _peaks[peak_number];
+}
+
+// ---------------------------------------------------------------------------
// Frame
// ---------------------------------------------------------------------------
Frame::Frame() {
@@ -68,6 +97,7 @@ Frame::~Frame() {
void Frame::init() {
_max_peaks = 100;
_max_partials = 100;
+ _partials.resize(_max_partials);
_audio = NULL;
_synth = NULL;
_residual = NULL;
@@ -110,14 +140,7 @@ Peak* Frame::peak(int peak_number) {
void Frame::clear() {
_peaks.clear();
-}
-
-Peaks::iterator Frame::peaks_begin() {
- return _peaks.begin();
-}
-
-Peaks::iterator Frame::peaks_end() {
- return _peaks.end();
+ _partials.clear();
}
// Frame - partials
@@ -134,17 +157,18 @@ int Frame::max_partials() {
void Frame::max_partials(int new_max_partials) {
_max_partials = new_max_partials;
- // potentially losing data here but the user shouldn't really do this
+ // TODO: potentially losing data here, should prevent or complain
if((int)_partials.size() > _max_partials) {
_partials.resize(_max_partials);
}
}
-void Frame::add_partial(Partial partial) {
+Peak* Frame::partial(int partial_number) {
+ return _partials[partial_number];
}
-Partials::iterator Frame::partials() {
- return _partials.begin();
+void Frame::partial(int partial_number, Peak* peak) {
+ _partials[partial_number] = peak;
}
@@ -213,7 +237,9 @@ PeakDetection::~PeakDetection() {
void PeakDetection::clear() {
for(int i = 0; i < _frames.size(); i++) {
- delete _frames[i];
+ if(_frames[i]) {
+ delete _frames[i];
+ }
}
_frames.clear();
@@ -333,3 +359,72 @@ Frames PeakDetection::find_peaks(int audio_size, sample* audio) {
return _frames;
}
+
+
+// ---------------------------------------------------------------------------
+// PartialTracking
+// ---------------------------------------------------------------------------
+PartialTracking::PartialTracking() {
+ _sampling_rate = 44100;
+ _max_partials = 100;
+ _min_partial_length = 0;
+ _max_gap = 2;
+}
+
+PartialTracking::~PartialTracking() {
+ clear();
+}
+
+void PartialTracking::clear() {
+ _frames.clear();
+}
+
+int PartialTracking::sampling_rate() {
+ return _sampling_rate;
+}
+
+void PartialTracking::sampling_rate(int new_sampling_rate) {
+ _sampling_rate = new_sampling_rate;
+}
+
+int PartialTracking::max_partials() {
+ return _max_partials;
+}
+
+void PartialTracking::max_partials(int new_max_partials) {
+ _max_partials = new_max_partials;
+}
+
+int PartialTracking::min_partial_length() {
+ return _min_partial_length;
+}
+
+void PartialTracking::min_partial_length(int new_min_partial_length) {
+ _min_partial_length = new_min_partial_length;
+}
+
+int PartialTracking::max_gap() {
+ return _max_gap;
+}
+
+void PartialTracking::max_gap(int new_max_gap) {
+ _max_gap = new_max_gap;
+}
+
+// Streamable (real-time) partial-tracking.
+Peaks PartialTracking::update_partials(Frame* frame) {
+ Peaks peaks;
+ return peaks;
+}
+
+// Find partials from the sinusoidal peaks in a list of Frames.
+Frames PartialTracking::find_partials(Frames frames) {
+ for(int i = 0; i < frames.size(); i++) {
+ Peaks peaks = update_partials(frames[i]);
+ for(int j = 0; j < peaks.size(); j++) {
+ frames[i]->partial(j, peaks[j]);
+ }
+ }
+ _frames = frames;
+ return _frames;
+}
diff --git a/src/simpl/base.h b/src/simpl/base.h
index b2d0da9..385e2a9 100644
--- a/src/simpl/base.h
+++ b/src/simpl/base.h
@@ -43,8 +43,25 @@ typedef std::vector<Peak*> Peaks;
// ---------------------------------------------------------------------------
// Partial
+//
+// Represents a sinuoidal partial or track, an ordered sequence of Peaks
// ---------------------------------------------------------------------------
-class Partial {};
+class Partial {
+ private:
+ int _starting_frame;
+ long _partial_number;
+ Peaks _peaks;
+
+ public:
+ Partial();
+ ~Partial();
+
+ void add_peak(Peak* peak);
+ int length();
+ int first_frame_number();
+ int last_frame_number();
+ Peak* peak(int peak_number);
+};
typedef std::vector<Partial*> Partials;
@@ -66,7 +83,7 @@ class Frame {
int _max_peaks;
int _max_partials;
Peaks _peaks;
- Partials _partials;
+ Peaks _partials;
sample* _audio;
sample* _synth;
sample* _residual;
@@ -86,15 +103,13 @@ class Frame {
void add_peaks(Peaks* peaks);
Peak* peak(int peak_number);
void clear();
- Peaks::iterator peaks_begin();
- Peaks::iterator peaks_end();
// partials
int num_partials();
int max_partials();
void max_partials(int new_max_partials);
- void add_partial(Partial partial);
- Partials::iterator partials();
+ Peak* partial(int partial_number);
+ void partial(int partial_number, Peak* peak);
// audio buffers
int size();
@@ -165,6 +180,40 @@ class PeakDetection {
virtual Frames find_peaks(int audio_size, sample* audio);
};
+
+// ---------------------------------------------------------------------------
+// PartialTracking
+//
+// Link spectral peaks from consecutive frames to form partials
+// ---------------------------------------------------------------------------
+
+class PartialTracking {
+ private:
+ int _sampling_rate;
+ int _max_partials;
+ int _min_partial_length;
+ int _max_gap;
+ Frames _frames;
+
+ public:
+ PartialTracking();
+ ~PartialTracking();
+
+ void clear();
+
+ int sampling_rate();
+ void sampling_rate(int new_sampling_rate);
+ int max_partials();
+ void max_partials(int new_max_partials);
+ int min_partial_length();
+ void min_partial_length(int new_min_partial_length);
+ int max_gap();
+ void max_gap(int new_max_gap);
+
+ virtual Peaks update_partials(Frame* frame);
+ virtual Frames find_partials(Frames frames);
+};
+
} // end of namespace Simpl
#endif
diff --git a/tests/test_base.py b/tests/test_base.py
index aade0df..738d530 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -45,6 +45,20 @@ class TestFrame(object):
f.clear()
assert f.num_peaks == 0
+ def test_partials(self):
+ N = 256
+ f = base.Frame(N)
+ f.max_partials = 10
+
+ p = base.Peak()
+ p.amplitude = 0.5
+ p.frequency = 220.0
+ p.phase = 0.0
+
+ f.partial(0, p)
+ assert f.partial(0).amplitude == p.amplitude
+ assert f.partial(0).frequency == p.frequency
+
class TestPeakDetection(object):
float_precision = 5
@@ -66,3 +80,28 @@ class TestPeakDetection(object):
assert len(pd.frames) == len(self.audio) / self.hop_size
assert len(pd.frames[0].peaks) == 0
+
+
+class TestPartialTracking(object):
+ float_precision = 5
+ frame_size = 512
+ hop_size = 512
+ audio_path = os.path.join(
+ os.path.dirname(__file__), 'audio/flute.wav'
+ )
+
+ @classmethod
+ def setup_class(cls):
+ cls.audio = wavfile.read(cls.audio_path)[1]
+ cls.audio = np.asarray(cls.audio, dtype=np.double)
+ cls.audio /= np.max(cls.audio)
+
+ def test_partial_tracking(self):
+ pd = base.PeakDetection()
+ frames = pd.find_peaks(self.audio)
+
+ pt = base.PartialTracking()
+ frames = pt.find_partials(frames)
+
+ assert len(frames) == len(self.audio) / self.hop_size
+ assert len(frames[0].partials) == 100