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
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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()