summaryrefslogtreecommitdiff
path: root/sms.py
diff options
context:
space:
mode:
authorJohn Glover <glover.john@gmail.com>2010-10-18 17:32:05 +0100
committerJohn Glover <glover.john@gmail.com>2010-10-18 17:32:05 +0100
commit30755b92afeae5a5a32860b4f4297180f6d3398d (patch)
treec9332a65adc6a27016678fccee6ce979d87fed07 /sms.py
parent58a7c36c5a219d8306f276db157097ac30782079 (diff)
downloadsimpl-30755b92afeae5a5a32860b4f4297180f6d3398d.tar.gz
simpl-30755b92afeae5a5a32860b4f4297180f6d3398d.tar.bz2
simpl-30755b92afeae5a5a32860b4f4297180f6d3398d.zip
Moved project over to Git
Diffstat (limited to 'sms.py')
-rw-r--r--sms.py406
1 files changed, 406 insertions, 0 deletions
diff --git a/sms.py b/sms.py
new file mode 100644
index 0000000..de4277e
--- /dev/null
+++ b/sms.py
@@ -0,0 +1,406 @@
+# Copyright (c) 2009 John Glover, National University of Ireland, Maynooth
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import simpl
+import pysms
+import numpy as np
+
+class SMSPeakDetection(simpl.PeakDetection):
+ "Sinusoidal peak detection using SMS"
+ _instances = 0
+
+ def __init__(self):
+ # limit this to only 1 instance at a time as calls to libsms are not independent,
+ # some static C variables are used. These should really be addressed in libsms.
+ # todo: silently treat this as a Singleton object rather than raising an exception?
+ SMSPeakDetection._instances += 1
+ if SMSPeakDetection._instances > 1:
+ raise Exception("Currently only 1 instance of each SMS analysis/synthesis object can exist at once")
+ simpl.PeakDetection.__init__(self)
+ pysms.sms_init()
+ # analysis parameters
+ self._analysis_params = pysms.SMS_AnalParams()
+ self._analysis_params.iSamplingRate = self.sampling_rate
+ self._analysis_params.iWindowType = pysms.SMS_WIN_HAMMING
+ self._analysis_params.fHighestFreq = 20000
+ #self._analysis_params.fLowestFundamental = 50
+ #self._analysis_params.fDefaultFundamental = 100
+ self._analysis_params.iMaxDelayFrames = 4
+ self._analysis_params.analDelay = 0
+ self._analysis_params.minGoodFrames = 1
+ self._analysis_params.iFormat = pysms.SMS_FORMAT_HP
+ pysms.sms_initAnalysis(self._analysis_params)
+ self._peaks = pysms.SMS_SpectralPeaks(self.max_peaks)
+ # By default, SMS will change the size of the frames being read depending on the
+ # detected fundamental frequency (if any) of the input sound. To prevent this
+ # behaviour (useful when comparing different analysis algorithms), set the
+ # static_frame_size variable to True
+ self.static_frame_size = False
+ #self.static_frame_size = True
+ # set default hop and frame sizes to match those in the parent class
+ self._analysis_params.iFrameRate = self.sampling_rate / self._hop_size
+ pysms.sms_changeHopSize(self._hop_size, self._analysis_params)
+ self._analysis_params.fDefaultFundamental = float(self.sampling_rate)/self.frame_size
+ self._analysis_params.fLowestFundamental = self._analysis_params.fDefaultFundamental
+
+
+ def __del__(self):
+ pysms.sms_freeAnalysis(self._analysis_params)
+ pysms.sms_free()
+ SMSPeakDetection._instances -= 1
+
+ # properties
+ max_frequency = property(lambda self: self.get_max_frequency(),
+ lambda self, x: self.set_max_frequency(x))
+ default_fundamental = property(lambda self: self.get_default_fundamental(),
+ lambda self, x: self.set_default_fundamental(x))
+ max_frame_delay = property(lambda self: self.get_max_frame_delay(),
+ lambda self, x: self.set_max_frame_delay(x))
+ analysis_delay = property(lambda self: self.get_analysis_delay(),
+ lambda self, x: self.set_analysis_delay(x))
+ min_good_frames = property(lambda self: self.get_min_good_frames(),
+ lambda self, x: self.set_min_good_frames(x))
+ min_frequency = property(lambda self: self.get_min_frequency(),
+ lambda self, x: self.set_min_frequency(x))
+ min_peak_amp = property(lambda self: self.get_min_peak_amp(),
+ lambda self, x: self.set_min_peak_amp(x))
+
+ def get_max_frequency(self):
+ return self._analysis_params.fHighestFreq
+
+ def set_max_frequency(self, max_frequency):
+ self._analysis_params.fHighestFreq = max_frequency
+ self._analysis_params.peakParams.fHighestFreq = max_frequency
+
+ def get_default_fundamental(self):
+ return self._analysis_params.fDefaultFundamental
+
+ def set_default_fundamental(self, default_fundamental):
+ self._analysis_params.fDefaultFundamental = default_fundamental
+
+ def get_max_frame_delay(self):
+ return self._analysis_params.iMaxDelayFrames
+
+ def set_max_frame_delay(self, max_frame_delay):
+ self._analysis_params.iMaxDelayFrames = max_frame_delay
+
+ def get_analysis_delay(self):
+ return self._analysis_params.analDelay
+
+ def set_analysis_delay(self, analysis_delay):
+ self._analysis_params.analDelay = analysis_delay
+
+ def get_min_good_frames(self):
+ return self._analysis_params.minGoodFrames
+
+ def set_min_good_frames(self, min_good_frames):
+ self._analysis_params.minGoodFrames = min_good_frames
+
+ def get_min_frequency(self):
+ return self._analysis_params.fLowestFundamental
+
+ def set_min_frequency(self, min_frequency):
+ self._analysis_params.fLowestFundamental = min_frequency
+ self._analysis_params.peakParams.fLowestFreq = min_frequency
+
+ def get_min_peak_amp(self):
+ return self._analysis_params.fMinPeakMag
+
+ def set_min_peak_amp(self, min_peak_amp):
+ self._analysis_params.fMinPeakMag = min_peak_amp
+ self._analysis_params.peakParams.fMinPeakMag = min_peak_amp
+
+ def get_hop_size(self):
+ return self._analysis_params.sizeHop
+
+ def set_hop_size(self, hop_size):
+ self._analysis_params.iFrameRate = self.sampling_rate / hop_size
+ pysms.sms_changeHopSize(hop_size, self._analysis_params)
+
+ def set_max_peaks(self, max_peaks):
+ # todo: compare to SMS_MAX_NPEAKS?
+ self._max_peaks = max_peaks
+ self._analysis_params.peakParams.iMaxPeaks = max_peaks
+ self._peaks = pysms.SMS_SpectralPeaks(max_peaks)
+
+ def set_sampling_rate(self, sampling_rate):
+ self._sampling_rate = sampling_rate
+ self._analysis_params.iSamplingRate = sampling_rate
+
+ def set_window_size(self, window_size):
+ self._window_size = window_size
+ self._analysis_params.iDefaultSizeWindow = window_size
+
+ def find_peaks_in_frame(self, frame):
+ "Find and return all spectral peaks in a given frame of audio"
+ current_peaks = []
+ num_peaks = pysms.sms_findPeaks(frame,
+ self._analysis_params,
+ self._peaks)
+ if num_peaks > 0:
+ amps = simpl.zeros(num_peaks)
+ freqs = simpl.zeros(num_peaks)
+ phases = simpl.zeros(num_peaks)
+ self._peaks.getFreq(freqs)
+ self._peaks.getMag(amps)
+ self._peaks.getPhase(phases)
+ for i in range(num_peaks):
+ p = simpl.Peak()
+ p.amplitude = amps[i]
+ p.frequency = freqs[i]
+ p.phase = phases[i]
+ current_peaks.append(p)
+ return current_peaks
+
+ def find_peaks(self, audio):
+ """Find and return all spectral peaks in a given audio signal.
+ If the signal contains more than 1 frame worth of audio, it will be broken
+ up into separate frames, with a list of peaks returned for each frame."""
+ self.peaks = []
+ pos = 0
+ self._analysis_params.iSizeSound = len(audio)
+ while pos < len(audio):
+ print pos, self.frame_size, self._analysis_params.sizeNextRead, len(audio)
+ # change frame size based on sizeNextRead
+ if not self.static_frame_size:
+ if pos + self.frame_size < len(audio):
+ self.frame_size = self._analysis_params.sizeNextRead
+ else:
+ self.frame_size = len(audio) - pos
+ # get the next frame
+ frame = audio[pos:pos+self.frame_size]
+ # find peaks
+ self.peaks.append(self.find_peaks_in_frame(frame))
+ pos += self.hop_size
+ return self.peaks
+
+
+class SMSPartialTracking(simpl.PartialTracking):
+ "Partial tracking using SMS"
+ _instances = 0
+
+ def __init__(self):
+ # limit this to only 1 instance at a time as calls to libsms are not independent,
+ # some static C variables are used. These should really be addressed in libsms.
+ SMSPartialTracking._instances += 1
+ if SMSPartialTracking._instances > 1:
+ raise Exception("Currently only 1 instance of each SMS analysis/synthesis object can exist at once")
+ simpl.PartialTracking.__init__(self)
+ pysms.sms_init()
+ self._analysis_params = pysms.SMS_AnalParams()
+ self._analysis_params.iSamplingRate = self.sampling_rate
+ self._analysis_params.fHighestFreq = 20000
+ self._analysis_params.fLowestFundamental = 50
+ self._analysis_params.fDefaultFundamental = 100
+ self._analysis_params.iMaxDelayFrames = 3 # minimum frame delay with libsms
+ self._analysis_params.analDelay = 0
+ self._analysis_params.minGoodFrames = 1
+ self._analysis_params.iFormat = pysms.SMS_FORMAT_HP
+ self._analysis_params.nTracks = self.max_partials
+ self._analysis_params.nGuides = self.max_partials
+ pysms.sms_initAnalysis(self._analysis_params)
+ self._analysis_frame = pysms.SMS_Data()
+ self.live_partials = [None for i in range(self.max_partials)]
+
+ def __del__(self):
+ pysms.sms_freeAnalysis(self._analysis_params)
+ pysms.sms_free()
+ SMSPartialTracking._instances -= 1
+
+ def set_max_partials(self, max_partials):
+ self._max_partials = max_partials
+ self._analysis_params.nTracks = max_partials
+ self._analysis_params.nGuides = max_partials
+
+ def update_partials(self, frame, frame_number):
+ "Streamable (real-time) partial-tracking."
+ frame_partials = []
+ # load Peak amplitudes, frequencies and phases into arrays
+ num_peaks = len(frame)
+ amps = simpl.zeros(num_peaks)
+ freqs = simpl.zeros(num_peaks)
+ phases = simpl.zeros(num_peaks)
+ for i in range(num_peaks):
+ peak = frame[i]
+ amps[i] = peak.amplitude
+ freqs[i] = peak.frequency
+ phases[i] = peak.phase
+ # set peaks in SMS_AnalParams structure
+ pysms.sms_setPeaks(self._analysis_params, amps, freqs, phases)
+ # SMS partial tracking
+ pysms.sms_findPartials(self._analysis_frame, self._analysis_params)
+ # read values back into amps, freqs, phases
+ num_partials = self._analysis_frame.nTracks
+ amps = simpl.zeros(num_partials)
+ freqs = simpl.zeros(num_partials)
+ phases = simpl.zeros(num_partials)
+ self._analysis_frame.getSinAmp(amps)
+ self._analysis_frame.getSinFreq(freqs)
+ self._analysis_frame.getSinPhase(phases)
+ # form simpl Partial objects
+ for i in range(num_partials):
+ # for each partial, if the mag is > 0, this partial is alive
+ if amps[i] > 0:
+ # create a peak object
+ p = simpl.Peak()
+ p.amplitude = amps[i]
+ p.frequency = freqs[i]
+ p.phase = phases[i]
+ # add this peak to the appropriate partial
+ if not self.live_partials[i]:
+ self.live_partials[i] = simpl.Partial()
+ self.live_partials[i].starting_frame = frame_number
+ self.partials.append(self.live_partials[i])
+ self.live_partials[i].add_peak(p)
+ # if the mag is 0 and this partial was alive, kill it
+ else:
+ if self.live_partials[i]:
+ self.live_partials[i] = None
+ return frame_partials
+
+
+class SMSSynthesis(simpl.Synthesis):
+ "Sinusoidal resynthesis using SMS"
+ _instances = 0
+
+ def __init__(self):
+ SMSSynthesis._instances += 1
+ if SMSSynthesis._instances > 1:
+ raise Exception("Currently only 1 instance of each SMS analysis/synthesis object can exist at once")
+ simpl.Synthesis.__init__(self)
+ pysms.sms_init()
+ self._synth_params = pysms.SMS_SynthParams()
+ self._synth_params.iDetSynthType = pysms.SMS_DET_SIN
+ pysms.sms_initSynth(self._synth_params)
+ self._current_frame = simpl.zeros(self.hop_size)
+ self._analysis_frame = pysms.SMS_Data()
+ pysms.sms_allocFrame(self._analysis_frame, self.max_partials,
+ self.num_stochastic_coeffs, 1, self.stochastic_type, 0)
+
+ def __del__(self):
+ pysms.sms_freeFrame(self._analysis_frame)
+ pysms.sms_freeSynth(self._synth_params)
+ pysms.sms_free()
+ SMSSynthesis._instances -= 1
+
+ # properties
+ sampling_rate = property(lambda self: self.get_sampling_rate(),
+ lambda self, x: self.set_sampling_rate(x))
+ synthesis_type = property(lambda self: self.get_synthesis_type(),
+ lambda self, x: self.set_synthesis_type(x))
+ num_stochastic_coeffs = property(lambda self: self.get_num_stochastic_coeffs(),
+ lambda self, x: self.set_num_stochastic_coeffs(x))
+ stochastic_type = property(lambda self: self.get_stochastic_type(),
+ lambda self, x: self.set_stochastic_type(x))
+ original_sampling_rate = property(lambda self: self.get_original_sampling_rate(),
+ lambda self, x: self.set_original_sampling_rate(x))
+ original_hop_size = property(lambda self: self.get_original_hop_size(),
+ lambda self, x: self.set_original_hop_size(x))
+
+ def get_hop_size(self):
+ return self._synth_params.sizeHop
+
+ def set_hop_size(self, hop_size):
+ self._synth_params.sizeHop = hop_size
+ self._current_frame = simpl.zeros(self.hop_size)
+
+ def get_max_partials(self):
+ return self._synth_params.nTracks
+
+ def set_max_partials(self, max_partials):
+ self._synth_params.nTracks = max_partials
+ pysms.sms_allocFrame(self._analysis_frame, max_partials,
+ self.num_stochastic_coeffs, 1, self.stochastic_type, 0)
+
+ def get_sampling_rate(self):
+ return self._synth_params.iSamplingRate
+
+ def set_sampling_rate(self, sampling_rate):
+ self._synth_params.iSamplingRate = sampling_rate
+
+ def get_synthesis_type(self):
+ return self._synth_params.iSynthesisType
+
+ def set_synthesis_type(self, synthesis_type):
+ self._synth_params.iSynthesisType = synthesis_type
+
+ def get_num_stochastic_coeffs(self):
+ return self._synth_params.nStochasticCoeff
+
+ def set_num_stochastic_coeffs(self, num_stochastic_coeffs):
+ self._synth_params.nStochasticCoeff = num_stochastic_coeffs
+ pysms.sms_allocFrame(self._analysis_frame, self.max_partials,
+ num_stochastic_coeffs, 1, self.stochastic_type, 0)
+
+ def get_stochastic_type(self):
+ return self._synth_params.iStochasticType
+
+ def set_stochastic_type(self, stochastic_type):
+ self._synth_params.iStochasticType = stochastic_type
+ pysms.sms_allocFrame(self._analysis_frame, self.max_partials,
+ self.num_stochastic_coeffs, 1, stochastic_type, 0)
+
+ def get_original_sampling_rate(self):
+ return self._synth_params.iOriginalSRate
+
+ def set_original_sampling_rate(self, sampling_rate):
+ self._synth_params.iOriginalSRate = sampling_rate
+
+ def get_original_hop_size(self):
+ return self._synth_params.origSizeHop
+
+ def set_original_hop_size(self, hop_size):
+ self._synth_params.origSizeHop = hop_size
+
+ def synth_frame(self, peaks):
+ "Synthesises a frame of audio, given a list of peaks from tracks"
+ amps = simpl.zeros(self.max_partials)
+ freqs = simpl.zeros(self.max_partials)
+ phases = simpl.zeros(self.max_partials)
+ for i in range(self.max_partials):
+ if i < len(peaks):
+ amps[i] = peaks[i].amplitude
+ freqs[i] = peaks[i].frequency
+ phases[i] = peaks[i].phase
+ self._analysis_frame.setSinAmp(amps)
+ self._analysis_frame.setSinFreq(freqs)
+ self._analysis_frame.setSinPha(phases)
+ pysms.sms_synthesize(self._analysis_frame, self._current_frame, self._synth_params)
+ return self._current_frame
+
+
+class SMSResidual(simpl.Residual):
+ _instances = 0
+
+ def __init__(self):
+ SMSResidual._instances += 1
+ if SMSResidual._instances > 1:
+ raise Exception("Currently only 1 instance of each SMS analysis/synthesis object can exist at once")
+ simpl.Residual.__init__(self)
+ pysms.sms_init()
+ self._analysis_params = pysms.SMS_AnalParams()
+ pysms.sms_initAnalysis(self._analysis_params)
+
+ def __del__(self):
+ pysms.sms_free()
+ SMSSynthesis._instances -= 1
+
+ def find_residual(self, synth, original):
+ "Calculate and return the residual signal"
+ residual = simpl.zeros(synth.size)
+ if pysms.sms_findResidual(synth, original, residual, self._analysis_params) == -1:
+ raise Exception("Residual error: Synthesised audio and original audio have different lengths")
+ return residual