diff options
author | John Glover <glover.john@gmail.com> | 2010-10-18 17:32:05 +0100 |
---|---|---|
committer | John Glover <glover.john@gmail.com> | 2010-10-18 17:32:05 +0100 |
commit | 30755b92afeae5a5a32860b4f4297180f6d3398d (patch) | |
tree | c9332a65adc6a27016678fccee6ce979d87fed07 /basetypes.py | |
parent | 58a7c36c5a219d8306f276db157097ac30782079 (diff) | |
download | simpl-30755b92afeae5a5a32860b4f4297180f6d3398d.tar.gz simpl-30755b92afeae5a5a32860b4f4297180f6d3398d.tar.bz2 simpl-30755b92afeae5a5a32860b4f4297180f6d3398d.zip |
Moved project over to Git
Diffstat (limited to 'basetypes.py')
-rw-r--r-- | basetypes.py | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/basetypes.py b/basetypes.py new file mode 100644 index 0000000..3a9c87e --- /dev/null +++ b/basetypes.py @@ -0,0 +1,337 @@ +# 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 numpy as np + +class Peak(object): + "A spectral peak" + def __init__(self): + self.amplitude = 0 + self.frequency = 0 + self.phase = 0 + self.next_peak = None + self.previous_peak = None + self.partial_id = None + self.partial_position = None + self.frame_number = None + + def is_start_of_partial(self): + return self.previous_peak is None + + def is_free(self, direction='forwards'): + "Returns true iff this peak is unmatched in the given direction, and has positive amplitude" + if self.amplitude <= 0: + return False + if direction == 'forwards': + if self.next_peak: + return False + elif direction == 'backwards': + if self.previous_peak: + return False + else: + return False + return True + + +def compare_peak_amps(peak_x, peak_y): + """Compares two peaks, and returns 1, 0 or -1 if the first has a greater + amplitude than the second, they have the same amplitude, or the second has + a greater amplitude than the first respectively. + Can be used to sort lists of peaks.""" + if peak_x.amplitude > peak_y.amplitude: + return 1 + elif peak_x.amplitude < peak_y.amplitude: + return -1 + else: + return 0 + +def compare_peak_freqs(peak_x, peak_y): + """Compares two peaks, and returns 1, 0 or -1 if the first has a greater + frequency than the second, they have the same frequency, or the second has + a greater frequency than the first respectively. + Can be used to sort lists of peaks.""" + if peak_x.frequency > peak_y.frequency: + return 1 + elif peak_x.frequency < peak_y.frequency: + return -1 + else: + return 0 + + +class Partial(object): + "Represents a sinuoidal partial or track, an ordered sequence of Peaks" + _num_partials = 0 + + def __init__(self): + "Initialise peaks list and increment partial_id" + self.peaks = [] + self.starting_frame = 0 + self.partial_id = Partial._num_partials + Partial._num_partials += 1 + + def add_peak(self, peak): + "Add peak to this partial, setting its id and partial_id." + partial_position = len(self.peaks) + last_peak = self.get_last_peak() + self.peaks.append(peak) + if last_peak: + last_peak.next_peak = peak + peak.previous_peak = last_peak + peak.partial_position = partial_position + peak.partial_id = self.partial_id + + def get_length(self): + "Return the length of this partial (as a number of frames)" + return len(self.peaks) + + def get_last_frame(self): + "Return the frame number of the last frame in this partial" + return self.starting_frame + self.get_length() + + def get_last_peak(self): + "Return the last peak of this partial" + if self.peaks: + return self.peaks[-1] + return None + + def list_peaks(self): + "A generator that returns the peaks in this partial" + for peak in self.peaks: + yield peak + + +class PeakDetection(object): + "Detect spectral peaks" + + def __init__(self): + self._sampling_rate = 44100 + self._frame_size = 2048 + self._hop_size = 512 + self._max_peaks = 100 + self._window_type = "hamming" + self._window_size = 2048 + self.peaks = [] + + # properties + sampling_rate = property(lambda self: self.get_sampling_rate(), + lambda self, x: self.set_sampling_rate(x)) + frame_size = property(lambda self: self.get_frame_size(), + lambda self, x: self.set_frame_size(x)) + hop_size = property(lambda self: self.get_hop_size(), + lambda self, x: self.set_hop_size(x)) + max_peaks = property(lambda self: self.get_max_peaks(), + lambda self, x: self.set_max_peaks(x)) + window_type = property(lambda self: self.get_window_type(), + lambda self, x: self.set_window_type(x)) + window_size = property(lambda self: self.get_window_size(), + lambda self, x: self.set_window_size(x)) + + def get_sampling_rate(self): + return self._sampling_rate + + def set_sampling_rate(self, sampling_rate): + self._sampling_rate = sampling_rate + + def get_frame_size(self): + return self._frame_size + + def set_frame_size(self, frame_size): + self._frame_size = frame_size + + def get_hop_size(self): + return self._hop_size + + def set_hop_size(self, hop_size): + self._hop_size = hop_size + + def get_max_peaks(self): + return self._max_peaks + + def set_max_peaks(self, max_peaks): + self._max_peaks = max_peaks + + def get_window_type(self): + return self._window_type + + def set_window_type(self, window_type): + self._window_type = window_type + + def get_window_size(self): + return self._window_size + + def set_window_size(self, window_size): + self._window_size = window_size + + def find_peaks_in_frame(self, frame): + "Find and return all spectral peaks in a given frame of audio" + current_peaks = [] + 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 + while pos < len(audio): + # get the next frame + frame = audio[pos:pos+self.frame_size] + # pad if necessary + if len(frame) < self.frame_size: + frame = np.hstack((frame, simpl.zeros(self.frame_size - len(frame)))) + # find peaks + self.peaks.append(self.find_peaks_in_frame(frame)) + pos += self.hop_size + return self.peaks + + +class PartialTracking(object): + "Link spectral peaks from consecutive frames to form partials" + def __init__(self): + self._sampling_rate = 44100 + self._max_partials = 100 + self._min_partial_length = 0 + self._max_gap = 2 + self.partials = [] # list of Partials + + # properties + sampling_rate = property(lambda self: self.get_sampling_rate(), + lambda self, x: self.set_sampling_rate(x)) + max_partials = property(lambda self: self.get_max_partials(), + lambda self, x: self.set_max_partials(x)) + min_partial_length = property(lambda self: self.get_min_partial_length(), + lambda self, x: self.set_min_partial_length(x)) + max_gap = property(lambda self: self.get_max_gap(), + lambda self, x: self.set_max_gap(x)) + + def get_sampling_rate(self): + return self._sampling_rate + + def set_sampling_rate(self, sampling_rate): + self._sampling_rate = sampling_rate + + def get_max_partials(self): + return self._max_partials + + def set_max_partials(self, num_partials): + self._max_partials = num_partials + + def get_min_partial_length(self): + return self._min_partial_length + + def set_min_partial_length(self, length): + self._min_partial_length = length + + def get_max_gap(self): + return self._max_gap + + def set_max_gap(self, gap): + self._max_gap = gap + + def get_partial(self, id): + """Return the partial with partial_id = id. Returns None if no such + partial exists""" + for p in self.partials: + if p.partial_id == id: + return p + return None + + def update_partials(self, frame, frame_number): + "Streamable (real-time) partial-tracking." + frame_partials = [] + return frame_partials + + def find_partials(self, frames): + """Creates tracks from the frames of peaks in self.peak_frames, + stored in self.track_frames""" + for frame_number, frame in enumerate(frames): + self.update_partials(frame, frame_number) + return self.partials + + +class Synthesis(object): + "Synthesise audio from spectral analysis data" + def __init__(self): + self._hop_size = 512 + self._max_partials = 100 + + # properties + hop_size = property(lambda self: self.get_hop_size(), + lambda self, x: self.set_hop_size(x)) + max_partials = property(lambda self: self.get_max_partials(), + lambda self, x: self.set_max_partials(x)) + + def get_hop_size(self): + return self._hop_size + + def set_hop_size(self, hop_size): + self._hop_size = hop_size + + def get_max_partials(self): + return self._max_partials + + def set_max_partials(self, num_partials): + self._max_partials = num_partials + + def synth_frame(self, partials): + "Synthesises a frame of audio, given a list of peaks from tracks" + raise Exception("NotYetImplemented") + + def synth(self, partials): + "Synthesise audio from the given partials" + audio_out = np.array([], dtype=np.float32) + # return an empty frame if there are no partials + if not partials: + return audio_out + current_partials = [] + num_frames = max([partial.get_last_frame() for partial in partials]) + # for each frame of audio + for frame_number in range(num_frames): + # get all partials that start on this frame, append to list of continuing partials + current_partials.extend([partial.list_peaks() for partial in partials if partial.starting_frame == frame_number]) + # get all peaks to be synthesised for this frame + current_peaks = [] + for partial_number, partial in enumerate(current_partials): + try: + current_peaks.append(partial.next()) + except StopIteration: + # End of partial. Set this partial to None, remove it from the list later + current_partials[partial_number] = None + # synth frame + audio_out = np.hstack((audio_out, self.synth_frame(current_peaks))) + # remove any finished partials + current_partials = [partial for partial in current_partials if partial] + return audio_out + + +class Residual(object): + "Calculate a residual signal" + TIME_DOMAIN = 0 + FREQUENCY_DOMAIN = 1 + + def __init__(self): + self.type = Residual.TIME_DOMAIN + self.hop_size = 512 + self.frame_size = 2048 + + def find_residual(self, synth, original): + "Calculate and return the residual signal" + raise Exception("NotYetImplemented") + + def synth(self, synth, original): + "Calculate and return a synthesised residual signal" + raise Exception("NotYetImplemented") |