Source code for music21.derivation
# -----------------------------------------------------------------------------
# Name: derivation.py
# Purpose: Class for storing and managing Stream-based derivations
#
# Authors: Christopher Ariza
# Joséphine Wolf Oberholtzer
# Michael Scott Asato Cuthbert
#
# Copyright: Copyright © 2011-2014 Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# ----------------------------------------------------------------------------
'''
This module defines objects for tracking the derivation of one
:class:`~music21.stream.Stream` from another.
'''
from __future__ import annotations
import weakref
from collections.abc import Generator
import functools
import typing as t
import unittest
from music21 import common
from music21.common.objects import SlottedObjectMixin
from music21 import environment
if t.TYPE_CHECKING:
from music21 import base
environLocal = environment.Environment('derivation')
[docs]
def derivationMethod(function):
'''
This decorator can be used for creating a function that returns a new
derivation. But is currently unused, since it does not take into account `inPlace=True`.
`Stream.cloneEmpty(derivationMethod='derivationMethod')` is preferred for
Streams.
>>> from copy import deepcopy
>>> @derivation.derivationMethod
... def allGreen(n):
... n2 = deepcopy(n)
... n2.style.color = 'green'
... return n2
>>> n = note.Note('C#')
>>> n2 = allGreen(n)
>>> n2.style.color
'green'
>>> n2.name = 'D-'
>>> n2.derivation
<Derivation of <music21.note.Note D-> from <music21.note.Note C#> via 'allGreen'>
'''
@functools.wraps(function)
def wrapper(self, *arguments, **keywords):
result = function(self, *arguments, **keywords)
result.derivation.origin = self
result.derivation.method = function.__name__
return result
return wrapper
[docs]
class Derivation(SlottedObjectMixin):
'''
A Derivation object keeps track of which Streams (or perhaps other Music21Objects)
a Stream or other music21 object has come from and how.
Derivation is automatically updated by many methods:
>>> import copy
>>> sOrig = stream.Stream(id='orig')
>>> sNew = copy.deepcopy(sOrig)
>>> sNew.id = 'copy'
>>> sNew.derivation
<Derivation of <music21.stream.Stream copy>
from <music21.stream.Stream orig> via '__deepcopy__'>
>>> sNew.derivation.client
<music21.stream.Stream copy>
>>> sNew.derivation.client is sNew
True
>>> sNew.derivation.origin
<music21.stream.Stream orig>
>>> sNew.derivation.method
'__deepcopy__'
>>> s1 = stream.Stream()
>>> s1.id = 'DerivedStream'
>>> d1 = derivation.Derivation(s1)
>>> s2 = stream.Stream()
>>> s2.id = 'OriginalStream'
>>> d1.method = 'manual'
>>> d1.origin = s2
>>> d1
<Derivation of <music21.stream.Stream DerivedStream> from
<music21.stream.Stream OriginalStream> via 'manual'>
>>> d1.origin is s2
True
>>> d1.client is s1
True
>>> import copy
>>> d2 = copy.deepcopy(d1)
>>> d2.origin is s2
True
>>> d1.method = 'measure'
>>> d1.method
'measure'
Deleting the origin stream does not change the Derivation, since origin is held by strong ref:
>>> import gc # Garbage collection
>>> del s2
>>> unused = gc.collect() # ensure Garbage collection is run
>>> d1
<Derivation of <music21.stream.Stream DerivedStream>
from <music21.stream.Stream OriginalStream> via 'measure'>
But deleting the client stream changes the Derivation, since client is held by weak ref,
and will also delete the origin (so long as client was ever set).
>>> del s1
>>> unused = gc.collect() # ensure Garbage collection is run
>>> d1
<Derivation of None from None via 'measure'>
'''
# CLASS VARIABLES #
__slots__ = (
'_client',
'_clientId',
'_method',
'_origin',
'_originId',
)
# INITIALIZER #
def __init__(self, client: base.Music21Object|None = None):
# store a reference to the Music21Object that has this Derivation object as a property
self._client: weakref.ReferenceType|None = None
self._clientId: int|None = None # store python-id to optimize w/o unwrapping
self._method: str|None = None
# origin could be stored as a weak ref -- the place where the client was derived from.
self._origin: base.Music21Object|None = None
self._originId: int|None = None # store id to optimize w/o unwrapping
# set client; can handle None
self.client = client
# SPECIAL METHODS #
def __deepcopy__(self, memo=None):
'''
Manage deepcopying by creating a new reference to the same object. If
the origin no longer exists, then origin is set to None.
'''
new = type(self)()
new.client = self.client
new.origin = self.origin
return new
def __repr__(self):
'''
Representation of the Derivation.
'''
klass = self.__class__.__name__
via = f' via {self.method!r}' if self.method else ''
return f'<{klass} of {self.client} from {self.origin}{via}>'
def __getstate__(self):
# unwrap weakref for pickling
self._client = common.unwrapWeakref(self._client)
return SlottedObjectMixin.__getstate__(self)
def __setstate__(self, state):
SlottedObjectMixin.__setstate__(self, state)
self._client = common.wrapWeakref(self._client)
# PUBLIC METHODS #
# PUBLIC PROPERTIES #
@property
def client(self) -> base.Music21Object|None:
c = common.unwrapWeakref(self._client)
if c is None and self._clientId is not None:
self._clientId = None
self._client = None
self._origin = None
self._originId = None
return c
@client.setter
def client(self, client: base.Music21Object|None):
# client is the Stream that this derivation lives on
if client is None:
self._clientId = None
self._client = None
else:
self._clientId = id(client)
self._client = common.wrapWeakref(client) # type: ignore
[docs]
def chain(self) -> Generator[base.Music21Object, None, None]:
'''
Iterator/Generator
Yields the Streams which this Derivation's client Stream was derived
from. This provides a way to obtain all Streams that the client passed
through, such as those created by
:meth:`~music21.stream.Stream.getElementsByClass` or
:meth:`~music21.stream.Stream.flatten`.
>>> s1 = stream.Stream()
>>> s1.id = 's1'
>>> s1.repeatAppend(note.Note(), 10)
>>> s1.repeatAppend(note.Rest(), 10)
>>> s2 = s1.notesAndRests.stream()
>>> s2.id = 's2'
>>> s3 = s2.getElementsByClass(note.Note).stream()
>>> s3.id = 's3'
>>> for y in s3.derivation.chain():
... print(y)
<music21.stream.Stream s2>
<music21.stream.Stream s1>
>>> list(s3.derivation.chain()) == [s2, s1]
True
'''
orig: base.Music21Object | None = self.origin
while orig is not None:
yield orig
orig = orig.derivation.origin # pylint: disable=no-member
@property
def method(self) -> str|None:
'''
Returns or sets the string of the method that was used to generate this
Stream.
>>> s = stream.Stream()
>>> s.derivation.method is None
True
>>> sNotes = s.notes.stream()
>>> sNotes.derivation.method
'notes'
Some examples are 'getElementsByClass' etc.
>>> s = stream.Stream()
>>> s.id = 'lonelyStream'
>>> s.append(clef.TrebleClef())
>>> s.append(note.Note())
>>> sNotes = s.notes.stream()
>>> sNotes.derivation
<Derivation of <music21.stream.Stream lonelyStream>
from <music21.stream.Stream lonelyStream> via 'notes'>
>>> derived = sNotes.derivation
>>> derived.method
'notes'
>>> derived.method = 'blah'
>>> derived.method
'blah'
>>> derived is sNotes.derivation
True
>>> sNotes.derivation.method
'blah'
'''
return self._method
@method.setter
def method(self, method: str|None):
self._method = method
@property
def origin(self) -> base.Music21Object|None:
return self._origin
@origin.setter
def origin(self, origin: base.Music21Object|None):
# for now, origin is not a weak ref
if origin is None:
self._originId = None
self._origin = None
else:
self._originId = id(origin)
self._origin = origin
# self._origin = common.wrapWeakref(origin)
@property
def originId(self) -> int|None:
'''
Return the Python id (=memory location) of the origin.
(Same as id(derivation.origin). Not the same as derivation.origin.ind)
'''
return self._originId
@property
def rootDerivation(self) -> base.Music21Object|None:
r'''
Return a reference to the oldest source of this Stream; that is, chain
calls to :attr:`~music21.stream.Stream.derivesFrom` until we get to a
Stream that cannot be further derived.
>>> s1 = stream.Stream()
>>> s1.repeatAppend(note.Note(), 10)
>>> s1.repeatAppend(note.Rest(), 10)
>>> s2 = s1.notesAndRests.stream()
>>> s3 = s2.getElementsByClass(note.Note).stream()
>>> s3.derivation.rootDerivation is s1
True
'''
derivationChain = list(self.chain())
if derivationChain:
return derivationChain[-1]
else:
return None
# -----------------------------------------------------------------------------
class Test(unittest.TestCase):
pass
# -----------------------------------------------------------------------------
# define presented order in documentation
_DOC_ORDER = [Derivation]
if __name__ == '__main__':
# sys.arg test options will be used in mainTest()
import music21
music21.mainTest(Test)