diff options
-rw-r--r-- | simpl/base.pyx | 101 | ||||
-rw-r--r-- | src/simpl/base.cpp | 127 | ||||
-rw-r--r-- | src/simpl/base.h | 61 | ||||
-rw-r--r-- | tests/test_base.py | 39 |
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 |