Source code for vis.analyzers.indexers.over_bass

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -------------------------------------------------------------------- #
# Program Name:           vis
# Program Description:    Helps analyze music with computers.
#
# Filename:               analyzers/indexers/over_bass.py
# Purpose:                Over Bass indexer
#
# Copyright (C) 2016 Marina Borsodi-Benson, and Alexander Morgan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public 
# License along with this program. If not, see 
# <http://www.gnu.org/licenses/>.
# -------------------------------------------------------------------- #
"""
.. codeauthor:: Marina Borsodi-Benson <marinaborsodibenson@gmail.com>
.. codeauthor:: Alexander Morgan

"""

from vis.analyzers import indexer
import pandas


[docs]class OverBassIndexer(indexer.Indexer): """ Using horizontal events and vertical intervals, this finds the intervals over the bass motion. Call this indexer via the ``get_data()`` method of either an ``indexed_piece`` object or an ``aggregated_pieces`` object (see examples below). The DataFrames passed in the 'score' argument should be concatenated :keyword 'horizontal': The horizontal voice you wish to use as a bass. If not indicated, this is automatically assigned to the lowest voice. :type 'horizontal': int :keyword 'type': The type of horizontal event you wish to index. The default is 'notes', which should be used if you are passing in the results of the ``NoteRestIndexer``. If you are passing in the results of the ``HorizontalIntervalIndexer``. :type 'type': str **Example:** >>> from vis.models.indexed_piece import Importer >>> ip = Importer('path_to_piece.xml') This example provides note names for the tracked voice (usually the bass voice): >>> input_dfs = [ip.get_data('noterest'), ip.get_data('vertical_interval')] >>> ob_setts = {'type': 'notes'} >>> ip.get_data('over_bass', data=input_dfs, settings=ob_setts) To use the OverBassIndexer with the melodic intervals of the tracked voice instead, do this: >>> input_dfs = [ip.get_data('horizontal_interval'), ip.get_data('vertical_interval')] >>> ob_setts = {'type': 'intervals'} >>> ip.get_data('over_bass', data=input_dfs, settings=ob_setts) """ required_score_type = 'pandas.DataFrame' possible_settings = ['horizontal', 'type'] _WRONG_HORIZ = ('horizontal setting must be a voice present in' + ' the piece') _WRONG_TYPE = 'Type given is not found' def __init__(self, score, settings=None): """ :param score: The intervals and horizontal events to be used to find the intervals over the bass. :type score: :class:`pandas.DataFrame` :param settings: There are 2 possible settings but no required settings :type settings: dict or NoneType :raises: :exc:`RuntimeError` if the optional setting ``type`` does not match the ``score`` input. :raises: :exc:`RuntimeError` if the optional setting ``horizontal`` indicates a voice that does not exist """ self._score = pandas.concat(score, axis=1) self._settings = {'type': 'notes'} if (settings is not None): self._settings.update(settings) types = {'intervals': 'interval.HorizontalIntervalIndexer', 'notes': 'noterest.NoteRestIndexer'} if (self._settings['type'] in types and types[self._settings['type']] in self._score): self.horiz_score = self._score[types[self._settings['type']]] else: raise RuntimeError(self._WRONG_TYPE) if ('horizontal' not in self._settings): self._settings['horizontal'] = len(self.horiz_score.columns) - 1 elif self._settings['horizontal'] > len(self.horiz_score.columns) - 1: raise RuntimeError(self._WRONG_HORIZ) self.horizontal_voice = self._settings['horizontal'] self.vert_score = self._score['interval.IntervalIndexer'] super(OverBassIndexer, self).__init__(score, None)
[docs] def run(self): """ Make a new index of the intervals over the bass motion. :returns: A :class:`DataFrame` of the intervals over the bass. :rtype: :class:`pandas.DataFrame` """ pairs = [] intervals = [] results = self.horiz_score[str(self.horizontal_voice)] intervals.append(results.tolist()) for pair in list(self.vert_score.columns.values): if str(self.horizontal_voice) in pair: pairs.append(pair) for pair in pairs: intervals.append(self.vert_score[pair].tolist()) intervals = zip(*intervals) pairs = str(self.horizontal_voice) + ' ' + ' '.join(pairs) result = pandas.DataFrame({pairs: pandas.Series([str(intvl) for intvl in intervals], index=self.horiz_score.index)}) return self.make_return(result.columns.values, [result[name] for name in result.columns])