Source code for vis.analyzers.indexers.cadence
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -------------------------------------------------------------------- #
# Program Name: vis
# Program Description: Helps analyze music with computers.
#
# Filename: analyzers/indexers/cadence.py
# Purpose: Cadence Indexer
#
# Copyright (C) 2016 Marina Borsodi-Benson, M. Ryan Bannon, 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>
"""
from vis.analyzers import indexer
import pandas
[docs]class CadenceIndexer(indexer.Indexer):
"""
Using ``OverBassIndexer`` and ``FermataIndexer`` results, finds
cadences as lists of events in the approach to a fermata.
Call this indexer via the ``get_data()`` method of either an
``indexed_piece`` object or an ``aggregated_pieces`` object (see
example below).
:keyword 'length': The length of the cadence, or how many events
happen before a fermata.
:type 'length': int
:keyword 'voice': The voice in which you want to look for
fermatas. The default value for this is 'all'.
:type 'voice': str or int
**Example:**
Prepare an indexed piece and import pandas:
>>> from vis.models.indexed_piece import Importer
>>> import pandas
>>> ip = Importer('path_to_piece.xml')
Prepare ``OverBassIndexer`` and ``FermataIndexer`` results. For more
specific advice on how to do this, please see the documentation of
those two indexers. These two DataFrames should be passed as a list.
For simplicity, including the ``FermataIndexer`` results is
optional, and this example shows how to use the ``CadenceIndexer``
without explicitly providing the ``FermataIndexer`` results, so the
'data' argument is a singleton list.
>>> overbass_input_dfs = [ip.get_data('noterest'),
ip.get_data('vertical_interval')]
>>> ob_setts = {
'type': 'notes'
}
>>> overbass = ip.get_data('over_bass', data=overbass_input_dfs,
settings=ob_setts)
Get the ``CadenceIndexer`` results with specified settings:
>>> ca_setts = {'length': 3}
>>> ip.get_data('cadence', data=[overbass], settings=ca_setts)
"""
required_score_type = 'pandas.DataFrame'
possible_settings = ['length', 'voice']
_MISSING_LENGTH = 'CadenceIndexer requires "length" setting.'
_LOW_LENGTH = 'Setting "length" must have a value of at least 1.'
_BAD_VOICE = 'voice setting must be a voice present in the piece'
def __init__(self, score, settings=None):
"""
:param score: The OverBassIndexer results and FermataIndexer
results to be used to find cadences.
:type score: :class:`pandas.DataFrame`
:param settings: The setting 'length' is required.
:type settings: dict
:raises: :exc:`RuntimeError` if the required setting 'length' is
notgiven.
:raises: :exc:`RuntimeError` if the value of 'length' is below 1
:raises: :exc:`RuntimeError` if the given voice is not a voice
found in the piece.
"""
self._score = pandas.concat(score, axis=1)
self.fig = self._score['over_bass.OverBassIndexer']
self.ferm = self._score['fermata.FermataIndexer']
if settings is None or 'length' not in settings:
raise RuntimeError(self._MISSING_LENGTH)
elif settings['length'] < 1:
raise RuntimeError(self._LOW_LENGTH)
elif 'voice' not in settings:
self._settings = settings
self._settings['voice'] = 'all'
elif(type(settings['voice']) is int
and settings['voice'] >= len(self.ferm.columns)):
raise RuntimeError(self._BAD_VOICE)
else:
self._settings = settings
super(CadenceIndexer, self).__init__(score, None)
[docs] def run(self):
"""
Makes a new index of the cadences in the piece.
:returns: A :class:`DataFrame` of the cadences.
:rtype: :class:`pandas.DataFrame`
"""
endings = []
if self._settings['voice'] is 'all':
for part in self.ferm.columns:
endings.extend(self.ferm[
self.ferm[part].notnull()].index.tolist())
else:
endings.extend(self.ferm[self.ferm[
str(self._settings['voice'])].notnull()].index.tolist())
endings = list(set(endings))
beginnings = []
indices = self.ferm.index.tolist()
for ind in endings:
beginnings.append(indices[
indices.index(ind)-self._settings['length']+1])
beginnings.sort()
endings.sort()
locations = list(zip(beginnings, endings))
cadences = []
for x in range(len(beginnings)):
my_cadence = []
for place in self.fig.loc[locations[x][0]:locations[x][1]].index.tolist():
my_cadence.extend(self.fig.loc[place].tolist())
cadences.append(my_cadence)
result = pandas.DataFrame(
{'Cadences': pandas.Series(cadences, index=beginnings)})
return self.make_return(result.columns.values,
[result[name] for name in result.columns])