def load_pairwise_alignment(self, performance_ref, performance_des): if performance_ref not in self.performances: raise MSMDDBError('Piece {0} in collection {1} does' ' not have a performance with name {2}.' ' Available performances: {3}' ''.format(self.name, self.collection_root, performance_ref, self.available_performances)) if performance_des not in self.performances: raise MSMDDBError('Piece {0} in collection {1} does' ' not have a performance with name {2}.' ' Available performances: {3}' ''.format(self.name, self.collection_root, performance_des, self.available_performances)) score = self.load_score(self.available_scores[0]) mungos = score.load_mungos() aln = list() for cur_mung in mungos: onset_key_ref = performance_ref + '_onset_seconds' onset_key_des = performance_des + '_onset_seconds' # check if this note has an alignment associated to it if onset_key_ref not in cur_mung.data.keys(): continue if onset_key_des not in cur_mung.data.keys(): continue cur_note_onset_ref = cur_mung.data[performance_ref + '_onset_seconds'] cur_note_onset_des = cur_mung.data[performance_des + '_onset_seconds'] aln.append((cur_note_onset_ref, cur_note_onset_des)) # sort by appearance in MIDI matrix aln.sort(key=itemgetter(1)) return aln
def add_performance(self, name, audio_file=None, midi_file=None, overwrite=False): """Creates a new performance in the piece from existing audio and optionally MIDI files. :param name: Name of the new performance. The performance folder will have this name, and the performance audio (and midi) file names will be derived from this name by simply copying the format suffix from the ``audio_file`` and ``midi_file`` arguments. :param audio_file: The audio file for the performance. Will be copied into the newly created performance directory, with the filename derived as the `name`` plus the format suffix. :param midi_file: The performance MIDI. Optional. Will be copied into the newly created performance directory. Same name convention as for ``audio_file``. :param overwrite: If true, if a performance with the given ``name`` exists, will delete it. """ if (audio_file is None) and (midi_file is None): raise ValueError('At least one of audio and midi files' ' has to be supplied to create a performance.') if name in self.performances: if overwrite: logging.info('Piece {0}: performance {1} already exists,' ' overwriting!'.format(self.name, name)) time.sleep(5) self.remove_performance(name) else: raise MSMDDBError('Piece {0}: performance {1} already' ' exists!'.format(self.name, name)) new_performance_dir = os.path.join(self.performance_dir, name) # This part should be refactored as performance.build_performance() os.mkdir(new_performance_dir) audio_fmt = os.path.splitext(audio_file)[-1] performance_audio_filename = os.path.join(new_performance_dir, name + audio_fmt) shutil.copyfile(audio_file, performance_audio_filename) if midi_file: midi_fmt = os.path.splitext(midi_file)[-1] performance_midi_filename = os.path.join(new_performance_dir, name + midi_fmt) shutil.copyfile(midi_file, performance_midi_filename) self.update() # Test-load the Performance. Ensures folder structure initialization. _ = self.load_performance(name, require_audio=(audio_file is not None), require_midi=(midi_file is not None))
def clear_view(self, view_name): """Removes the given view.""" self.update() if view_name not in self.views: raise MSMDDBError('Score {0}: requested clearing view' ' {1}, but this view does not exist!' ' (Available views: {2})' ''.format(self.name, view_name, self.views.keys())) shutil.rmtree(self.views[view_name]) self.update()
def load_score(self, score_name): self.update() if score_name not in self.scores: raise MSMDDBError('Piece {0} in collection {1} does' ' not have a score with name {2}.' ' Available scores: {3}' ''.format(self.name, self.collection_root, score_name, self.available_scores)) score_dir = self.scores[score_name] score = Score(folder=score_dir, piece_name=self.name) return score
def _load_feature_by_suffix(self, suffix): """Utility function for loading features by suffix naming conventions.""" self.update_features() candidate_feature_names = [ f for f in self.features if f.endswith(suffix) ] if len(candidate_feature_names) == 0: raise MSMDDBError('Performance {0}: Feature {1}' ' not available! Availble feature' ' names: {2}'.format(self.name, suffix, self.features.keys())) if len(candidate_feature_names) > 1: raise MSMDDBError('Performance {0}: More than one feature' ' conforms to the suffix {1}: {2}' ''.format(self.name, suffix, candidate_feature_names)) feature_name = candidate_feature_names[0] return self.load_feature(feature_name)
def view_files(self, view_name): """Return a list of the paths to all (non-hidden) files in the view.""" self.update() if view_name not in self.views: raise MSMDDBError('Score {0}: requested view {1}' ' not available!' ''.format(self.name, view_name)) view_dir = self.views[view_name] return [ os.path.join(view_dir, f) for f in sorted(os.listdir(view_dir)) if (not f.startswith('.')) and ( os.path.isfile(os.path.join(view_dir, f))) ]
def load_performance(self, performance_name, **perf_kwargs): """Creates a ``Performance`` object for the given performance and returns it. You can pass Performance initialization kwargs.""" self.update() if performance_name not in self.performances: raise MSMDDBError('Piece {0} in collection {1} does' ' not have a performance with name {2}.' ' Available performances: {3}' ''.format(self.name, self.collection_root, performance_name, self.available_performances)) performance_dir = self.performances[performance_name] performance = Performance(folder=performance_dir, piece_name=self.name, **perf_kwargs) return performance
def _set_authority(self, authority_format): """Sets the authority to the selected format. Don't do this unless you are sure what you are doing. If you really need to derive something in the piece from different authority encodings, consider initializing another ``Piece`` instance.""" if authority_format not in self.AVAILABLE_AUTHORITIES: raise ValueError('Authority format not supported: {0}' ''.format(authority_format)) if authority_format not in self.encodings: raise MSMDDBError('Piece {0} in collection {1} does' ' not have the requested authority' ' encoding {2}. (Available encodings:' ' {3}'.format(self.name, self.collection_root, authority_format, self.encodings.values())) self.authority_format = authority_format self.authority = self.encodings[authority_format]
def load_feature(self, feature_name): """Loads the feature with the given name, if available in self.features. Raises a ValueError otherwise.""" self.collect_features() if feature_name not in self.features: raise ValueError('Performance {0}: feature {1} not available!' ' Available feature names: {2}' ''.format(self.name, feature_name, self.features.keys())) if not os.path.isfile(self.features[feature_name]): raise MSMDDBError('Performance {0}: feature {1} is' ' available, but the file {2} does not' ' exist...?' ''.format(self.name, feature_name, self.features[feature_name])) feature = numpy.load(self.features[feature_name]) return feature
def load_mungos(self, classes=None, by_page=False): """Loads all the available MuNG objects as a list. You need to make sure the objids don't clash across pages!""" self.update() if 'mung' not in self.views: raise MSMDDBError('Score {0}: mung view not available!' ''.format(self.name)) mung_files = self.view_files('mung') mungos = [] for f in mung_files: ms = parse_cropobject_list(f) if by_page: mungos.append(ms) else: mungos.extend(ms) if classes is not None: mungos = [m for m in mungos if m.clsname in classes] return mungos
def __init__(self, folder, piece_name, audio_fmt='flac', require_audio=True, require_midi=True): """Initialize Performance. :param audio_fmt: The audio of the performance is expected to have this format. """ super(Performance, self).__init__() if not os.path.isdir(folder): raise MSMDDBError('Performance initialized with' ' non-existent directory: {0}' ''.format(folder)) self.folder = folder name = path2name(folder) self.name = name self.piece_name = piece_name self.metadata = self.load_metadata() if audio_fmt.startswith('.'): audio_fmt = audio_fmt[1:] self.audio_fmt = audio_fmt self.audio = self.discover_audio(required=require_audio) self.audio_name = None if self.audio: self.audio_name = path2name(self.audio) self.midi = self.discover_midi(required=require_midi) self.features_dir = os.path.join(self.folder, 'features') self._ensure_features_dir() self.features = self.collect_features()
def get_ordered_notes(self, filter_tied=False, reverse_columns=False, return_columns=False): """Returns the MuNG objects corresponding to notes in the canonical ordering: by page, system, left-to-right, and top-down within simultaneities (e.g. chords). :param reverse_columns: If set, will order the columns bottom-up instead of top-down. Use this for events alignment, not for score inference. """ self.update() if 'mung' not in self.views: raise MSMDDBError('Score {0}: mung view not available!' ''.format(self.name)) mung_files = self.view_files('mung') # Algorithm: # - Create hard ordering constraints: # - pages (already done: mungos_per_page) # - systems notes_per_page = [] for f in mung_files: mungos = parse_cropobject_list(f) mgraph = NotationGraph(mungos) _CONST = InferenceEngineConstants() note_mungos = [c for c in mungos if 'midi_pitch_code' in c.data] system_mungos = [c for c in mungos if c.clsname == 'staff'] system_mungos = sorted(system_mungos, key=lambda m: m.top) notes_per_system = [] for s in system_mungos: system_notes = mgraph.ancestors( s, classes=_CONST.NOTEHEAD_CLSNAMES) for c in system_notes: if 'midi_pitch_code' not in c.data: print('Notehead without pitch: {0}' ''.format(str(c))) continue if c.data['midi_pitch_code'] is None: print('Notehead with pitch=None: {0}' ''.format(str(c))) system_notes = [ c for c in system_notes if 'midi_pitch_code' in c.data ] # print('Ancestors of system {0}: {1}'.format(s, system_notes)) # Process simultaneities. We use a very small overlap ratio, # because we want to catch also chords that have noteheads # on both sides of the stem. Sorted top-down. # Remove all tied notes. if filter_tied: system_notes = [ m for m in system_notes if ('tied' not in m.data) or ( ('tied' in m.data) and (m.data['tied'] != 1)) ] system_note_columns = group_mungos_by_column( system_notes, MIN_OVERLAP_RATIO=0.05, reverse_columns=reverse_columns) # print('System {0}: n_columns = {1}' # ''.format(s.objid, len(system_note_columns))) ltr_sorted_columns = sorted(system_note_columns.items(), key=lambda kv: kv[0]) # print('ltr_sorted_columns[0] = {0}'.format(ltr_sorted_columns[0])) system_ordered_simultaneities = [ c[1] for c in ltr_sorted_columns ] # print('system_ordered_sims[0] = {0}'.format(system_ordered_simultaneities[0])) notes_per_system.append(system_ordered_simultaneities) # print('Total entries in notes_per_system = {0}'.format(len(notes_per_system))) notes_per_page.append(notes_per_system) # Data structure # -------------- # notes_per_page = [ # notes_per_system_1 = [ # ordered_simultaneities = [ # simultaneity1 = [ a'', f'', c'', a' ], # simultaneity2 = [ g'', e'', c'', bes' ], # ... # ] # ], # notes_per_system_2 = [ # simultaneity1 = [ ... ] # ... # ] # ] # Unroll simultaneities notes according to this data structure # DEBUG # print('notes_per_page: {0}'.format(pprint.pformat(notes_per_page))) ordered_simultaneities = [] for page in notes_per_page: for system in page: ordered_simultaneities.extend(system) if return_columns: return ordered_simultaneities ordered_notes = [] for sim in ordered_simultaneities: ordered_notes.extend(list(reversed(sim))) return ordered_notes