def __init__(self, pieces=None, metafile=None): """ :param pieces: The IndexedPieces to collect. :type pieces: list of :class:`~vis.models.indexed_piece.IndexedPiece` """ def init_metadata(): """ Initialize valid metadata fields with a zero-length string. """ field_list = ['composers', 'dates', 'date_range', 'titles', 'locales', 'pathnames'] for field in field_list: self._metadata[field] = None super(AggregatedPieces, self).__init__() self._pieces = pieces if pieces is not None else [] self._metafile = metafile if metafile is not None else [] self._metadata = {} init_metadata() # Multi-key dictionary for combined_experimenter calls to get_data() self._mkd = mkd({# Experimenters that can combine results from multiple pieces: ('aggregator', 'aggregator.ColumnAggregator', aggregator.ColumnAggregator): aggregator.ColumnAggregator, ('bar_chart', 'barchart.RBarChart', barchart.RBarChart): barchart.RBarChart, ('frequency', 'frequency.FrequencyExperimenter', frequency.FrequencyExperimenter): frequency.FrequencyExperimenter}) # Only include dendrogram experimenter if scipy and matplotlib were installed try: self._mkd[('dendrogram', 'dendrogram.HierarchicalClusterer', dendrogram.HierarchicalClusterer)] = self._get_dendrogram except NameError: pass
def __init__(self, pieces=None, metafile=None): """ :param pieces: The IndexedPieces to collect. :type pieces: list of :class:`~vis.models.indexed_piece.IndexedPiece` """ def init_metadata(): """ Initialize valid metadata fields with a zero-length string. """ field_list = [ 'composers', 'dates', 'date_range', 'titles', 'locales', 'pathnames' ] for field in field_list: self._metadata[field] = None super(AggregatedPieces, self).__init__() self._pieces = pieces if pieces is not None else [] self._metafile = metafile if metafile is not None else [] self._metadata = {} init_metadata() # Multi-key dictionary for combined_experimenter calls to get_data() self._mkd = mkd({ # Experimenters that can combine results from multiple pieces: ('aggregator', 'aggregator.ColumnAggregator', aggregator.ColumnAggregator): aggregator.ColumnAggregator, ('bar_chart', 'barchart.RBarChart', barchart.RBarChart): barchart.RBarChart, ('frequency', 'frequency.FrequencyExperimenter', frequency.FrequencyExperimenter): frequency.FrequencyExperimenter }) # Only include dendrogram experimenter if scipy and matplotlib were installed try: self._mkd[( 'dendrogram', 'dendrogram.HierarchicalClusterer', dendrogram.HierarchicalClusterer)] = self._get_dendrogram except NameError: pass
def __init__(self, pathname='', opus_id=None, score=None, metafile=None, username=None, password=None): """ :param str pathname: Pathname to the file music21 will import for this :class:`IndexedPiece`. :param opus_id: The index of the :class:`Score` for this :class:`IndexedPiece`, if the file imports as a :class:`music21.stream.Opus`. :returns: A new :class:`IndexedPiece`. :rtype: :class:`IndexedPiece` """ def init_metadata(): """ Initialize valid metadata fields with a zero-length string. """ field_list = ['opusNumber', 'movementName', 'composer', 'number', 'anacrusis', 'movementNumber', 'date', 'composers', 'alternativeTitle', 'title', 'localeOfComposition', 'parts'] for field in field_list: self._metadata[field] = '' self._metadata['pathname'] = pathname super(IndexedPiece, self).__init__() self._imported = False self._analyses = {} self._score = score self._pathname = pathname self._metadata = {} self._known_opus = False self._opus_id = opus_id # if the file imports as an Opus, this is the index of the Score self._username = username self._password = password # Multi-key dictionary for calls to get_data() self._mkd = mkd({ # Indexers (in alphabetical order of their long-format strings): ('active_voices', 'active_voices.ActiveVoicesIndexer', active_voices.ActiveVoicesIndexer): self._get_active_voices, ('cadence', 'cadence.CadenceIndexer', cadence.CadenceIndexer): self._get_cadence, ('contour', 'contour.ContourIndexer', contour.ContourIndexer): contour.ContourIndexer, ('dissonance', 'dissonance.DissonanceIndexer', dissonance.DissonanceIndexer): self._get_dissonance, ('fermata', 'fermata.FermataIndexer', fermata.FermataIndexer): self._get_fermata, ('horizontal_interval', 'interval.HorizontalIntervalIndexer', interval.HorizontalIntervalIndexer): self._get_horizontal_interval, ('vertical_interval', 'interval.IntervalIndexer', interval.IntervalIndexer): self._get_vertical_interval, ('duration', 'meter.DurationIndexer', meter.DurationIndexer): self._get_duration, ('measure', 'meter.MeasureIndexer', meter.MeasureIndexer): self._get_measure, ('beat_strength', 'meter.NoteBeatStrengthIndexer', meter.NoteBeatStrengthIndexer): self._get_beat_strength, ('ngram', 'ngram.NGramIndexer', ngram.NGramIndexer): self._get_ngram, ('multistop', 'noterest.MultiStopIndexer', noterest.MultiStopIndexer): self._get_multistop, ('noterest', 'noterest.NoteRestIndexer', noterest.NoteRestIndexer): self._get_noterest, ('offset', 'offset.FilterByOffsetIndexer', offset.FilterByOffsetIndexer): offset.FilterByOffsetIndexer, ('over_bass', 'over_bass.OverBassIndexer', over_bass.OverBassIndexer): over_bass.OverBassIndexer, ('repeat', 'repeat.FilterByRepeatIndexer', repeat.FilterByRepeatIndexer): repeat.FilterByRepeatIndexer, ('windexer', 'windexer.Windexer', windexer.Windexer): windexer.Windexer, # Experimenters (in alphabetical order of their long-format strings): ('aggregator', 'aggregator.ColumnAggregator', aggregator.ColumnAggregator): aggregator.ColumnAggregator, ('bar_chart', 'barchart.RBarChart', barchart.RBarChart): barchart.RBarChart, # The dendrogram experimenter should only be used by an AggregatedPieces object ('frequency', 'frequency.FrequencyExperimenter', frequency.FrequencyExperimenter): frequency.FrequencyExperimenter }) init_metadata() if metafile is not None: self._metafile = metafile self._open_file() self._opus_id = opus_id # if the file imports as an Opus, this is the index of the Score
def _dynamic_run(self): """ Replacement run method for when the ``quarterLength`` setting is set to 'dynamic'. It assigns context-dependent offset values based on the dissonance types detected in the piece, and its attack density. This setting should only be used for the analysis of Renaissance music with duple divisions in the metric level of the contrapuntal rhythm. For more on contrapuntal rhythm, see Ruth DeFord, 2015. :returns: A :class:`DataFrame` with offset-indexed values for all inputted parts. The pandas indices (holding music21 offsets) start at the first offset at which there is an event in any of the inputted parts. An offset appears at durational intervals equal to the contrapuntal rhythm at that moment in the piece. The value of this contrapuntal rhythm duration is dynamic and can therefore change throughout the course of a piece. :rtype: :class:`pandas.DataFrame` """ dom_data = self._settings['dom_data'] #Remove the upper level of the columnar multi-index. dds = dom_data[0].copy() dds.columns = range(len(dds.columns)) ddr = dom_data[1].copy() ddr.columns = dds.columns bbs = dom_data[2].copy() bbs.columns = dds.columns nnr = dom_data[3].copy() nnr.columns = dds.columns ts = dom_data[4] w = 6 # Remove weak dissonances weaks = ('R', 'D', 'L', 'U', 'E', 'C', 'A') indx, cols = numpy.where(dom_data[0].isin(weaks)) for x in reversed(range(len(indx))): spot = ddr.iloc[:indx[x], cols[x]].last_valid_index() # Add the weak dissonance duration to the note that immediately precedes it. ddr.at[spot, cols[x]] += ddr.iat[indx[x], cols[x]] # Remove strong dissonances other than suspensions strongs = ('Q', 'H') indx, cols = numpy.where(dom_data[0].isin(strongs)) for x in reversed(range(len(indx))): spot = ddr.iloc[indx[x] + 1:, cols[x]].first_valid_index() # Add the strong dissonance duration to the note that immediately follows it. ddr.iat[indx[x], cols[x]] += ddr.at[spot, cols[x]] ddr.at[spot, cols[x]] = float('nan') nnr.iat[indx[x], cols[x]] = nnr.at[spot, cols[x]] nnr.at[spot, cols[x]] = float('nan') # Delete the duration entries of weak dissonances ddr[dds.isin(weaks)] = float('nan') nnr[dds.isin(weaks)] = float('nan') # Delete the duration entries of strong dissonances other than suspensions ddr[dds.isin(strongs)] = float('nan') # Delete duration entries for rests ddr = ddr[nnr != 'Rest'] ddr.dropna(how='all', inplace=True) # Attack-density analysis without most dissonances for the whole piece. combined = pandas.Series(ddr.index[1:] - ddr.index[:-1], index=ddr.index[:-1]) comb_roll = combined.rolling(w).mean() # Broadcast any bs value to all columns of a df. cbs = pandas.concat([dom_data[2].T.bfill().iloc[0]] * len(bbs.columns), axis=1, ignore_index=True) diss_levs = mkd({ ('2/1w', '4/2w'): { .0625: 1, .125: 2, .25: 4, .5: 8, 1: 8 }, #NB: things that happen on beats 1 and 3 are treated the same way. ('2/1s', '4/2s'): { .0625: .25, .125: .5, .25: 1, .5: 2, 1: 2 }, ('2/2w', '4/4w'): { .0625: .5, .125: 1, .25: 2, .5: 4, 1: 4 }, #NB: things that happen on beats 1 and 3 are treated the same way. ('2/2s', '4/4s'): { .0625: .125, .125: .25, .25: .5, .5: 1, 1: 1 }, }) # Get the beatstrength of the dissonances diss_cr = bbs[dds.isin(weaks)] time_sig = ts.iloc[0, 0] diss_cr.replace(diss_levs[time_sig + 'w'], inplace=True) swsus = ('S', 'Q', 'H') sbs = cbs[dds.isin(swsus)] sbs.replace(diss_levs[time_sig + 's'], inplace=True) # CR analysis based on dissonance types alone. diss_cr.update(sbs) cr = comb_roll.copy() ccr = cr.copy() # Snap the readings to a reasonable note-value grid. # The CR reading can only be an eighth, quarter, half, or whole note. mlt = 1.25 # top threshhold above which to round CR reading up. ccr[cr < .5 * mlt] = .5 ccr[cr > .5 * mlt] = 1 ccr[cr > 1 * mlt] = 2 ccr[cr > 2 * mlt] = 4 for i, val in enumerate(ccr): counts = diss_cr.iloc[i:i + w].stack().value_counts() lvi = ccr.iloc[:i].last_valid_index() if len(counts) > 0 and counts.index[ 0] == val: # If the most common diss in the window corresponds to the attack-density reading, leave the current val continue elif lvi is not None and ccr.at[ lvi] == val: # There is no dissonance in this window, but the IR analysis has not changed, so the reading is valid continue else: # The new level has not been confirmed by a corresponding dissonance ccr.iat[i] = float('nan') ccr.ffill(inplace=True) ccr.bfill(inplace=True) ccr = ccr.loc[ccr.shift() != ccr] # Remove consecutive duplicates # Make the new index end_time = int(dom_data[5]) # "highest time" of first part. spots = list(ccr.index) spots.append( end_time ) # Add the index value of the last moment of the piece which usually has no event at it. new_index = [] for i, spot in enumerate(spots[:-1]): if spot % ccr.iat[i] != 0: spot -= (spot % ccr.iat[i]) post = list( numpy.arange(spot, spots[i + 1], ccr.iat[i]) ) # you can't use range() because range can't handle floats if bool(new_index) and bool(post) and post[0] in new_index: del post[0] new_index.extend(post) if isinstance(self._score, list): self._score = pandas.concat(self._score, axis=1) return self._score.reindex(index=pandas.Index(new_index)).ffill()
def __init__(self, pathname='', opus_id=None, score=None, metafile=None, username=None, password=None): """ :param str pathname: Pathname to the file music21 will import for this :class:`IndexedPiece`. :param opus_id: The index of the :class:`Score` for this :class:`IndexedPiece`, if the file imports as a :class:`music21.stream.Opus`. :returns: A new :class:`IndexedPiece`. :rtype: :class:`IndexedPiece` """ def init_metadata(): """ Initialize valid metadata fields with a zero-length string. """ field_list = ['opusNumber', 'movementName', 'composer', 'number', 'anacrusis', 'movementNumber', 'date', 'composers', 'alternativeTitle', 'title', 'localeOfComposition', 'parts'] for field in field_list: self._metadata[field] = '' self._metadata['pathname'] = pathname super(IndexedPiece, self).__init__() self._imported = False self._analyses = {} self._score = score self._pathname = pathname self._metadata = {} self._known_opus = False self._opus_id = opus_id # if the file imports as an Opus, this is the index of the Score self._username = username self._password = password # Multi-key dictionary for calls to get_data() self._mkd = mkd({ # Indexers (in alphabetical order of their long-format strings): ('active_voices', 'active_voices.ActiveVoicesIndexer', active_voices.ActiveVoicesIndexer): self._get_active_voices, ('approach', 'approach.ApproachIndexer', approach.ApproachIndexer): self._get_approach, ('contour', 'contour.ContourIndexer', contour.ContourIndexer): contour.ContourIndexer, ('dissonance', 'dissonance.DissonanceIndexer', dissonance.DissonanceIndexer): self._get_dissonance, ('fermata', 'fermata.FermataIndexer', fermata.FermataIndexer): self._get_fermata, ('horizontal_interval', 'interval.HorizontalIntervalIndexer', interval.HorizontalIntervalIndexer): self._get_horizontal_interval, ('vertical_interval', 'interval.IntervalIndexer', interval.IntervalIndexer): self._get_vertical_interval, ('duration', 'meter.DurationIndexer', meter.DurationIndexer): self._get_duration, ('measure', 'meter.MeasureIndexer', meter.MeasureIndexer): self._get_measure, ('beat_strength', 'meter.NoteBeatStrengthIndexer', meter.NoteBeatStrengthIndexer): self._get_beat_strength, ('ngram', 'ngram.NGramIndexer', ngram.NGramIndexer): self._get_ngram, ('multistop', 'noterest.MultiStopIndexer', noterest.MultiStopIndexer): self._get_multistop, ('noterest', 'noterest.NoteRestIndexer', noterest.NoteRestIndexer): self._get_noterest, ('offset', 'offset.FilterByOffsetIndexer', offset.FilterByOffsetIndexer): self._get_offset, ('over_bass', 'over_bass.OverBassIndexer', over_bass.OverBassIndexer): over_bass.OverBassIndexer, ('repeat', 'repeat.FilterByRepeatIndexer', repeat.FilterByRepeatIndexer): repeat.FilterByRepeatIndexer, ('windexer', 'windexer.Windexer', windexer.Windexer): windexer.Windexer, # Experimenters (in alphabetical order of their long-format strings): ('aggregator', 'aggregator.ColumnAggregator', aggregator.ColumnAggregator): aggregator.ColumnAggregator, ('bar_chart', 'barchart.RBarChart', barchart.RBarChart): barchart.RBarChart, # The dendrogram experimenter should only be used by an AggregatedPieces object ('frequency', 'frequency.FrequencyExperimenter', frequency.FrequencyExperimenter): frequency.FrequencyExperimenter }) init_metadata() if metafile is not None: self._metafile = metafile self._open_file() self._opus_id = opus_id # if the file imports as an Opus, this is the index of the Score
def _dynamic_run(self): """ Replacement run method for when the ``quarterLength`` setting is set to 'dynamic'. It assigns context-dependent offset values based on the dissonance types detected in the piece, and its attack density. This setting should only be used for the analysis of Renaissance music with duple divisions in the metric level of the contrapuntal rhythm. For more on contrapuntal rhythm, see Ruth DeFord, 2015. :returns: A :class:`DataFrame` with offset-indexed values for all inputted parts. The pandas indices (holding music21 offsets) start at the first offset at which there is an event in any of the inputted parts. An offset appears at durational intervals equal to the contrapuntal rhythm at that moment in the piece. The value of this contrapuntal rhythm duration is dynamic and can therefore change throughout the course of a piece. :rtype: :class:`pandas.DataFrame` """ dom_data = self._settings['dom_data'] #Remove the upper level of the columnar multi-index. dds = dom_data[0].copy() dds.columns = range(len(dds.columns)) ddr = dom_data[1].copy() ddr.columns = dds.columns bbs = dom_data[2].copy() bbs.columns = dds.columns nnr = dom_data[3].copy() nnr.columns = dds.columns ts = dom_data[4] w = 6 # Remove weak dissonances weaks = ('R', 'D', 'L', 'U', 'E', 'C', 'A') indx, cols = numpy.where(dom_data[0].isin(weaks)) for x in reversed(range(len(indx))): spot = ddr.iloc[:indx[x], cols[x]].last_valid_index() # Add the weak dissonance duration to the note that immediately precedes it. ddr.at[spot, cols[x]] += ddr.iat[indx[x], cols[x]] # Remove strong dissonances other than suspensions strongs = ('Q', 'H') indx, cols = numpy.where(dom_data[0].isin(strongs)) for x in reversed(range(len(indx))): spot = ddr.iloc[indx[x]+1:, cols[x]].first_valid_index() # Add the strong dissonance duration to the note that immediately follows it. ddr.iat[indx[x], cols[x]] += ddr.at[spot, cols[x]] ddr.at[spot, cols[x]] = float('nan') nnr.iat[indx[x], cols[x]] = nnr.at[spot, cols[x]] nnr.at[spot, cols[x]] = float('nan') # Delete the duration entries of weak dissonances ddr[dds.isin(weaks)] = float('nan') nnr[dds.isin(weaks)] = float('nan') # Delete the duration entries of strong dissonances other than suspensions ddr[dds.isin(strongs)] = float('nan') # Delete duration entries for rests ddr = ddr[nnr != 'Rest'] ddr.dropna(how='all', inplace=True) # Attack-density analysis without most dissonances for the whole piece. combined = pandas.Series(ddr.index[1:] - ddr.index[:-1], index=ddr.index[:-1]) comb_roll = combined.rolling(w).mean() # Broadcast any bs value to all columns of a df. cbs = pandas.concat([dom_data[2].T.bfill().iloc[0]]*len(bbs.columns), axis=1, ignore_index=True) diss_levs = mkd({('2/1w', '4/2w'): {.0625: 1, .125: 2, .25: 4, .5: 8, 1: 8}, #NB: things that happen on beats 1 and 3 are treated the same way. ('2/1s', '4/2s'): {.0625: .25, .125: .5, .25: 1, .5: 2, 1: 2}, ('2/2w', '4/4w'): {.0625: .5, .125: 1, .25: 2, .5: 4, 1: 4}, #NB: things that happen on beats 1 and 3 are treated the same way. ('2/2s', '4/4s'): {.0625: .125, .125: .25, .25: .5, .5: 1, 1: 1}, }) # Get the beatstrength of the dissonances diss_cr = bbs[dds.isin(weaks)] time_sig = ts.iloc[0, 0] diss_cr.replace(diss_levs[time_sig + 'w'], inplace=True) swsus = ('S', 'Q', 'H') sbs = cbs[dds.isin(swsus)] sbs.replace(diss_levs[time_sig + 's'], inplace=True) # CR analysis based on dissonance types alone. diss_cr.update(sbs) cr = comb_roll.copy() ccr = cr.copy() # Snap the readings to a reasonable note-value grid. # The CR reading can only be an eighth, quarter, half, or whole note. mlt = 1.25 # top threshhold above which to round CR reading up. ccr[cr < .5*mlt] = .5 ccr[cr > .5*mlt] = 1 ccr[cr > 1*mlt] = 2 ccr[cr > 2*mlt] = 4 for i, val in enumerate(ccr): counts = diss_cr.iloc[i:i+w].stack().value_counts() lvi = ccr.iloc[:i].last_valid_index() if len(counts) > 0 and counts.index[0] == val: # If the most common diss in the window corresponds to the attack-density reading, leave the current val continue elif lvi is not None and ccr.at[lvi] == val: # There is no dissonance in this window, but the IR analysis has not changed, so the reading is valid continue else: # The new level has not been confirmed by a corresponding dissonance ccr.iat[i] = float('nan') ccr.ffill(inplace=True) ccr.bfill(inplace=True) ccr = ccr.loc[ccr.shift() != ccr] # Remove consecutive duplicates # Make the new index end_time = int(dom_data[5]) # "highest time" of first part. spots = list(ccr.index) spots.append(end_time) # Add the index value of the last moment of the piece which usually has no event at it. new_index = [] for i, spot in enumerate(spots[:-1]): if spot % ccr.iat[i] != 0: spot -= (spot % ccr.iat[i]) post = list(numpy.arange(spot, spots[i+1], ccr.iat[i])) # you can't use range() because range can't handle floats if bool(new_index) and bool(post) and post[0] in new_index: del post[0] new_index.extend(post) if isinstance(self._score, list): self._score = pandas.concat(self._score, axis=1) return self._score.reindex(index=pandas.Index(new_index)).ffill()