From 9d5c3da8942305461d42627d686abdeac7fba30c Mon Sep 17 00:00:00 2001 From: John Glover Date: Mon, 2 Jul 2012 23:45:01 +0100 Subject: [synthesis] Add C++ implemention of SMSSynthesis. --- simpl/synthesis.pxd | 7 ++++ simpl/synthesis.pyx | 35 +++++++++++++++- src/simpl/synthesis.cpp | 78 ++++++++++++++++++++++++++++++++++ src/simpl/synthesis.h | 31 ++++++++++++-- src/sms/sms.h | 2 +- tests/create_libsms_test_data.py | 90 ++++++++++++++++++++++++++++++++++++++++ tests/test_synthesis.py | 89 ++++++++++++++++++++++++++++++++++++--- 7 files changed, 320 insertions(+), 12 deletions(-) diff --git a/simpl/synthesis.pxd b/simpl/synthesis.pxd index 6c08897..fe49c7b 100644 --- a/simpl/synthesis.pxd +++ b/simpl/synthesis.pxd @@ -24,3 +24,10 @@ cdef extern from "../src/simpl/synthesis.h" namespace "simpl": void max_partials(int new_max_partials) void synth_frame(c_Frame* frame) vector[c_Frame*] synth(vector[c_Frame*] frames) + + cdef cppclass c_SMSSynthesis "simpl::SMSSynthesis"(c_Synthesis): + c_SMSSynthesis() + int num_stochastic_coeffs() + int stochastic_type() + int det_synthesis_type() + void det_synthesis_type(int new_det_synthesis_type) diff --git a/simpl/synthesis.pyx b/simpl/synthesis.pyx index 180d575..ad23fa8 100644 --- a/simpl/synthesis.pyx +++ b/simpl/synthesis.pyx @@ -12,8 +12,12 @@ from base cimport c_Frame cdef class Synthesis: cdef c_Synthesis* thisptr - def __cinit__(self): self.thisptr = new c_Synthesis() - def __dealloc__(self): del self.thisptr + def __cinit__(self): + self.thisptr = new c_Synthesis() + + def __dealloc__(self): + if self.thisptr: + del self.thisptr property sampling_rate: def __get__(self): return self.thisptr.sampling_rate() @@ -49,3 +53,30 @@ cdef class Synthesis: frame_audio = np.PyArray_SimpleNewFromData(1, shape, np.NPY_DOUBLE, output_frames[i].synth()) output[i * self.thisptr.hop_size():(i + 1) * self.thisptr.hop_size()] = frame_audio return output + + +cdef class SMSSynthesis(Synthesis): + SMS_DET_IFFT = 0 + SMS_DET_SIN = 1 + + def __cinit__(self): + if self.thisptr: + del self.thisptr + self.thisptr = new c_SMSSynthesis() + + def __dealloc__(self): + if self.thisptr: + del self.thisptr + self.thisptr = 0 + + property num_stochastic_coeffs: + def __get__(self): return (self.thisptr).num_stochastic_coeffs() + def __set__(self, int i): raise Exception("NotImplemented") + + property stochastic_type: + def __get__(self): return (self.thisptr).stochastic_type() + def __set__(self, int i): raise Exception("NotImplemented") + + property det_synthesis_type: + def __get__(self): return (self.thisptr).det_synthesis_type() + def __set__(self, int i): (self.thisptr).det_synthesis_type(i) diff --git a/src/simpl/synthesis.cpp b/src/simpl/synthesis.cpp index 168749b..9dd1131 100644 --- a/src/simpl/synthesis.cpp +++ b/src/simpl/synthesis.cpp @@ -51,7 +51,85 @@ void Synthesis::synth_frame(Frame* frame) { Frames Synthesis::synth(Frames frames) { for(int i = 0; i < frames.size(); i++) { + sample* synth_audio = new sample[_hop_size]; + memset(synth_audio, 0.0, sizeof(sample) * _hop_size); + frames[i]->synth(synth_audio); synth_frame(frames[i]); } return frames; } + + +// --------------------------------------------------------------------------- +// SMSSynthesis +// --------------------------------------------------------------------------- + +SMSSynthesis::SMSSynthesis() { + sms_init(); + + sms_initSynthParams(&_synth_params); + _synth_params.iSamplingRate = _sampling_rate; + _synth_params.iDetSynthType = SMS_DET_SIN; + _synth_params.iSynthesisType = SMS_STYPE_DET; + _synth_params.iStochasticType = SMS_STOC_NONE; + _synth_params.sizeHop = _hop_size; + _synth_params.nTracks = _max_partials; + _synth_params.deEmphasis = 0; + sms_initSynth(&_synth_params); + + sms_allocFrame(&_data, _max_partials, + num_stochastic_coeffs(), 1, + stochastic_type(), 0); +} + +SMSSynthesis::~SMSSynthesis() { + sms_freeSynth(&_synth_params); + sms_freeFrame(&_data); + sms_free(); +} + +void SMSSynthesis::hop_size(int new_hop_size) { + _hop_size = new_hop_size; + + sms_freeSynth(&_synth_params); + _synth_params.sizeHop = _hop_size; + sms_initSynth(&_synth_params); +} + +void SMSSynthesis::max_partials(int new_max_partials) { + _max_partials = new_max_partials; + + sms_freeSynth(&_synth_params); + sms_freeFrame(&_data); + _synth_params.nTracks = _max_partials; + sms_initSynth(&_synth_params); + sms_allocFrame(&_data, _max_partials, + num_stochastic_coeffs(), 1, + stochastic_type(), 0); +} + +int SMSSynthesis::num_stochastic_coeffs() { + return _synth_params.nStochasticCoeff; +} + +int SMSSynthesis::stochastic_type() { + return _synth_params.iStochasticType; +} + +int SMSSynthesis::det_synthesis_type() { + return _synth_params.iDetSynthType; +} + +void SMSSynthesis::det_synthesis_type(int new_det_synthesis_type) { + _synth_params.iDetSynthType = new_det_synthesis_type; +} + +void SMSSynthesis::synth_frame(Frame* frame) { + for(int i = 0; i < _data.nTracks; i++) { + _data.pFSinAmp[i] = frame->peak(i)->amplitude; + _data.pFSinFreq[i] = frame->peak(i)->frequency; + _data.pFSinPha[i] = frame->peak(i)->phase; + } + + sms_synthesize(&_data, frame->synth(), &_synth_params); +} diff --git a/src/simpl/synthesis.h b/src/simpl/synthesis.h index 43b45d0..b3efd9c 100644 --- a/src/simpl/synthesis.h +++ b/src/simpl/synthesis.h @@ -3,6 +3,10 @@ #include "base.h" +extern "C" { + #include "sms.h" +} + using namespace std; namespace simpl @@ -16,7 +20,7 @@ namespace simpl // --------------------------------------------------------------------------- class Synthesis { - private: + protected: int _frame_size; int _hop_size; int _max_partials; @@ -27,9 +31,9 @@ class Synthesis { int frame_size(); void frame_size(int new_frame_size); int hop_size(); - void hop_size(int new_hop_size); + virtual void hop_size(int new_hop_size); int max_partials(); - void max_partials(int new_max_partials); + virtual void max_partials(int new_max_partials); int sampling_rate(); void sampling_rate(int new_sampling_rate); @@ -38,6 +42,27 @@ class Synthesis { }; +// --------------------------------------------------------------------------- +// SMSSynthesis +// --------------------------------------------------------------------------- +class SMSSynthesis : public Synthesis { + private: + SMSSynthParams _synth_params; + SMSData _data; + + public: + SMSSynthesis(); + ~SMSSynthesis(); + void hop_size(int new_hop_size); + void max_partials(int new_max_partials); + int num_stochastic_coeffs(); + int stochastic_type(); + int det_synthesis_type(); + void det_synthesis_type(int new_det_synthesis_type); + void synth_frame(Frame* frame); +}; + + } // end of namespace Simpl #endif diff --git a/src/sms/sms.h b/src/sms/sms.h index 14399fc..7372a8a 100644 --- a/src/sms/sms.h +++ b/src/sms/sms.h @@ -320,7 +320,7 @@ typedef struct * synthesized frame. * */ -typedef struct +typedef struct SMSSynthParams { int iStochasticType; /*!< type of stochastic model defined by SMS_StocSynthType \see SMS_StocSynthType */ diff --git a/tests/create_libsms_test_data.py b/tests/create_libsms_test_data.py index 4adf6f3..0fedbc0 100644 --- a/tests/create_libsms_test_data.py +++ b/tests/create_libsms_test_data.py @@ -1,6 +1,7 @@ import os import json import numpy as np +import scipy.io.wavfile as wav import pysms import simpl @@ -38,6 +39,18 @@ def _pysms_analysis_params(sampling_rate): return analysis_params +def _pysms_synthesis_params(sampling_rate): + synth_params = pysms.SMS_SynthParams() + pysms.sms_initSynthParams(synth_params) + synth_params.iSamplingRate = sampling_rate + synth_params.iSynthesisType = pysms.SMS_STYPE_DET + synth_params.iStochasticType = pysms.SMS_STOC_NONE + synth_params.sizeHop = hop_size + synth_params.nTracks = max_peaks + synth_params.deEmphasis = 0 + return synth_params + + def _size_next_read(): pysms.sms_init() snd_header = pysms.SMS_SndHeader() @@ -165,9 +178,83 @@ def _partial_tracking(): return sms_frames +def _harmonic_synthesis(): + pysms.sms_init() + snd_header = pysms.SMS_SndHeader() + + if(pysms.sms_openSF(audio_path, snd_header)): + raise NameError(pysms.sms_errorString()) + + analysis_params = _pysms_analysis_params(sampling_rate) + if pysms.sms_initAnalysis(analysis_params, snd_header) != 0: + raise Exception("Error allocating memory for analysis_params") + analysis_params.iSizeSound = num_samples + analysis_params.nFrames = num_frames + sms_header = pysms.SMS_Header() + pysms.sms_fillHeader(sms_header, analysis_params, "pysms") + + sample_offset = 0 + size_new_data = 0 + current_frame = 0 + analysis_frames = [] + do_analysis = True + + while do_analysis and (current_frame < num_frames): + sample_offset += size_new_data + size_new_data = analysis_params.sizeNextRead + + frame_audio = audio[sample_offset:sample_offset + size_new_data] + frame_audio = np.array(frame_audio, dtype=np.float32) + if len(frame_audio) < size_new_data: + frame_audio = np.hstack(( + frame_audio, np.zeros(size_new_data - len(frame_audio), + dtype=np.float32) + )) + + analysis_data = pysms.SMS_Data() + pysms.sms_allocFrameH(sms_header, analysis_data) + status = pysms.sms_analyze(frame_audio, analysis_data, + analysis_params) + + if status == 1: + current_frame += 1 + analysis_frames.append(analysis_data) + elif status == 0: + pysms.sms_freeFrame(analysis_data) + if status == -1: + do_analysis = False + pysms.sms_freeFrame(analysis_data) + + # blank_frame = analysis_frames[0] + # analysis_frames = analysis_frames[1:] + # pysms.sms_freeFrame(blank_frame) + + synth_params = _pysms_synthesis_params(sampling_rate) + pysms.sms_initSynth(sms_header, synth_params) + + synth_frame = np.zeros(synth_params.sizeHop, dtype=np.float32) + synth_audio = np.array([], dtype=np.float32) + + for i in range(len(analysis_frames)): + pysms.sms_synthesize(analysis_frames[i], synth_frame, synth_params) + synth_audio = np.hstack((synth_audio, synth_frame)) + + synth_audio = np.asarray(synth_audio * 32768, np.int16) + + for frame in analysis_frames: + pysms.sms_freeFrame(frame) + pysms.sms_freeAnalysis(analysis_params) + pysms.sms_closeSF() + pysms.sms_freeSynth(synth_params) + pysms.sms_free() + + return synth_audio + + if __name__ == '__main__': size_next_read = _size_next_read() partial_tracking = _partial_tracking() + harmonic_synthesis = _harmonic_synthesis() test_data = {'size_next_read': size_next_read, 'peak_detection': partial_tracking, @@ -176,3 +263,6 @@ if __name__ == '__main__': test_data = json.dumps(test_data) with open('libsms_test_data.json', 'w') as f: f.write(test_data) + + wav.write('libsms_harmonic_synthesis.wav', sampling_rate, + harmonic_synthesis) diff --git a/tests/test_synthesis.py b/tests/test_synthesis.py index 794b047..8c592a5 100644 --- a/tests/test_synthesis.py +++ b/tests/test_synthesis.py @@ -1,4 +1,5 @@ import os +import json import numpy as np from nose.tools import assert_almost_equals import simpl @@ -6,27 +7,103 @@ import simpl.peak_detection as peak_detection import simpl.partial_tracking as partial_tracking import simpl.synthesis as synthesis -float_precision = 5 +float_precision = 2 frame_size = 512 hop_size = 512 +max_peaks = 10 +max_partials = 10 +num_frames = 30 +num_samples = num_frames * hop_size audio_path = os.path.join( os.path.dirname(__file__), 'audio/flute.wav' ) +libsms_test_data_path = os.path.join( + os.path.dirname(__file__), 'libsms_test_data.json' +) +libsms_harmonic_synthesis_path = os.path.join( + os.path.dirname(__file__), 'libsms_harmonic_synthesis.wav' +) + +PeakDetection = peak_detection.PeakDetection +SMSPeakDetection = peak_detection.SMSPeakDetection +PartialTracking = partial_tracking.PartialTracking +SMSPartialTracking = partial_tracking.SMSPartialTracking +Synthesis = synthesis.Synthesis +SMSSynthesis = synthesis.SMSSynthesis + + +def _load_libsms_test_data(): + test_data = None + with open(libsms_test_data_path, 'r') as f: + test_data = json.loads(f.read()) + return test_data class TestSynthesis(object): @classmethod def setup_class(cls): cls.audio = simpl.read_wav(audio_path)[0] + cls.audio = cls.audio[0:num_samples] + + def test_basic(self): + pd = PeakDetection() + pd.hop_size = hop_size + frames = pd.find_peaks(self.audio) + + pt = PartialTracking() + pt.max_partials = max_partials + frames = pt.find_partials(frames) + + s = Synthesis() + s.hop_size = hop_size + synth_audio = s.synth(frames) + + assert len(synth_audio) == len(self.audio) + + +class TestSMSSynthesis(object): + @classmethod + def setup_class(cls): + cls.audio = simpl.read_wav(audio_path)[0] + cls.audio = cls.audio[0:num_samples] + cls.test_data = _load_libsms_test_data() - def test_synthesis(self): - pd = peak_detection.PeakDetection() + def test_basic(self): + pd = SMSPeakDetection() + pd.hop_size = hop_size frames = pd.find_peaks(self.audio) - pt = partial_tracking.PartialTracking() + pt = SMSPartialTracking() + pt.max_partials = max_partials frames = pt.find_partials(frames) - s = synthesis.Synthesis() + s = SMSSynthesis() + s.hop_size = hop_size synth_audio = s.synth(frames) - assert len(synth_audio) + assert len(synth_audio) == len(self.audio) + + def test_harmonic_synthesis(self): + pd = SMSPeakDetection() + pd.hop_size = hop_size + frames = pd.find_peaks(self.audio) + + pt = SMSPartialTracking() + pt.max_partials = max_partials + frames = pt.find_partials(frames) + + synth = SMSSynthesis() + synth.hop_size = hop_size + synth.det_synthesis_type = SMSSynthesis.SMS_DET_IFFT + synth_audio = synth.synth(frames) + + assert len(synth_audio) == len(self.audio) + + sms_audio, sampling_rate = simpl.read_wav( + libsms_harmonic_synthesis_path + ) + + assert len(synth_audio) == len(sms_audio) + + for i in range(len(synth_audio)): + assert_almost_equals(synth_audio[i], sms_audio[i], float_precision) -- cgit v1.2.3