diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d68a9b9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# For details see http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation +[*.py] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..494b51e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + pull_request: + branches: [master] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.11', '3.12', '3.13', '3.14'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync + + - name: Lint with ruff + run: uv run ruff check music21_tools/ tools/ + + - name: Run tests + run: uv run pytest diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..fb79747 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,383 @@ +[MASTER] + +# Specify a configuration file. +# rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +# init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +## Use multiple processes to speed up Pylint. +## jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" + +# anything changed here, also change in test/testLint.py for now + +disable= + cyclic-import, # we use these inside functions when there's a deep problem. + unnecessary-pass, # not really a problem.. + locally-disabled, # test for this later, but hopefully will know what we're doing + duplicate-code, # needs to ignore strings -- keeps getting doctests... + fixme, # obviously known + superfluous-parens, # nope -- if they make things clearer... + too-many-statements, # someday + too-many-arguments, # definitely! but takes too long to get a fix now... + too-many-public-methods, # maybe... + too-many-branches, # yes, someday + too-many-locals, # no + too-many-lines, # yes, someday + too-many-return-statements, # we'll see + too-many-instance-attributes, # maybe later + too-many-positional-arguments, # try to get down to 6 first... + # no-self-use, # moved to optional extension. + invalid-name, # these are good music21 names; fix the regexp instead... + too-few-public-methods, # never remove or set to 1 + trailing-whitespace, # should ignore blank lines with tabs + missing-docstring, # gets too many well-documented properties + protected-access, # this is an important one, but see below... + unused-argument, + import-self, # fix is either to get rid of it or move away many tests... + wrong-import-position, + no-else-return, + too-many-ancestors, + inconsistent-return-statements, + keyword-arg-before-vararg, + consider-using-get, + chained-comparison, + import-outside-toplevel, + trailing-newlines, # is this really the worst thing in the world? + no-else-continue, # not so bad... + no-else-break, # same... + consider-using-enumerate, # sometimes parallelism is better than enumerate. + consider-using-dict-items, # readability improvement depends on excellent variable names + arguments-differ, # we are not purists. + arguments-renamed, # same + simplifiable-if-statement, # simpler to a computer maybe... + unsubscriptable-object, # too many errors + import-outside-toplevel, # allow for import music21, etc. + not-callable, # false positives, for instance on x.next() + raise-missing-from, # want to do this eventually, but adding 1000 msgs not helpful + consider-using-f-string, # future? + unnecessary-lambda-assignment, # opinionated + consider-using-generator, # generators are less performant for small container sizes, like most of ours + not-an-iterable, # false positives on RecursiveIterator in recent pylint versions. + unpacking-non-sequence, # also getting false positives. +# 'protected-access', # this is an important one, but for now we do a lot of +# # x = copy.deepcopy(self); x._volume = ... which is not a problem... + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text +msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +# comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Required attributes for module, separated by a comma +# required-attributes= + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,baz,toto,tutu,tata,shit,fuck,stuff + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][A-Za-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-zA-Z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-zA-Z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-3 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$|converter\.parse + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=8 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=yes + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=StreamCore + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_|dummy|unused|i$|j$|junk|counter + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +# ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp,setup,run + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# maximum boolean expressions in a line (too-many-boolean-expressions) +max-bool-expr=10 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=20 + +# Maximum number of statements in function / method body +max-statements=100 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=20 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=40 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +# overgeneral-exceptions=Exception diff --git a/LICENSE b/LICENSE index 5a9925a..ed34bbd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2007-2018, Michael Scott Asato Cuthbert and cuthbertLab +Copyright (c) 2004-2026, Michael Scott Asato Cuthbert All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 24dbcde..f9fbd4d 100644 --- a/README.md +++ b/README.md @@ -42,21 +42,22 @@ under `webapps/`. The project is described in: ## Running the tests -This project uses [`uv`](https://docs.astral.sh/uv/) and `pytest`. From the -repository root: - ```sh -# install (creates .venv/ with music21 >= 10 and dev dependencies) -uv sync +uv sync # one-time: install music21 >= 10 + dev deps +uv run pytest # full sweep (doctests + Test classes) +``` -# run the full test + doctest suite -uv run pytest --doctest-modules music21_tools/ +A single module: + +```sh +uv run pytest music21_tools/theoryAnalysis/theoryAnalyzer.py ``` -To test a single module: +`TestExternal` (interactive / display) and `TestSlow` (full-corpus) classes are +skipped by default. Run them one method at a time via `unittest`: ```sh -uv run pytest --doctest-modules music21_tools/theoryAnalysis/theoryAnalyzer.py +uv run python -m unittest music21_tools.chant.chant.TestExternal.testSimpleFile ``` diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..3fc9d16 --- /dev/null +++ b/conftest.py @@ -0,0 +1,28 @@ +''' +Pytest configuration for music21-tools. + +Skip ``TestExternal`` and ``TestSlow`` classes during automated collection. + +By music21 convention: + +* ``TestExternal`` methods open files on the local machine, play sounds, render + with LilyPond, or otherwise need a person at the console. Run them + individually when verifying a behavior, never in CI. +* ``TestSlow`` methods are long-running (full corpus walks, etc.) and are not + meant for the default test sweep. + +Only ``Test`` classes — plain unit tests — survive collection. +''' +from unittest import TestCase + + +def pytest_collection_modifyitems(config, items): + kept = [] + for item in items: + parent = getattr(item, 'parent', None) + cls = getattr(parent, 'cls', None) + if cls is not None and issubclass(cls, TestCase) and cls.__name__ != 'Test': + # filter out TestSlow, TestExternal, etc. + continue + kept.append(item) + items[:] = kept diff --git a/music21_tools/__init__.py b/music21_tools/__init__.py index 766ebd2..7da1ed6 100644 --- a/music21_tools/__init__.py +++ b/music21_tools/__init__.py @@ -1,5 +1,5 @@ -""" +''' music21_tools — demonstration scripts and tools for music21. See README for an overview of subpackages. -""" +''' diff --git a/music21_tools/audioSearchDemos/graphicalInterfaceGame.py b/music21_tools/audioSearchDemos/graphicalInterfaceGame.py index 4fcf832..af7c770 100644 --- a/music21_tools/audioSearchDemos/graphicalInterfaceGame.py +++ b/music21_tools/audioSearchDemos/graphicalInterfaceGame.py @@ -8,21 +8,20 @@ # Copyright: Copyright © 2011-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ -_DOC_IGNORE_MODULE_OR_PACKAGE = True - import math -from . import repetitionGame +import tkinter -import tkinter # @UnresolvedImport @Reimport +from . import repetitionGame +_DOC_IGNORE_MODULE_OR_PACKAGE = True class SFApp(): def __init__(self, master): self.master = master self.frame = tkinter.Frame(master) - #self.frame.pack() - self.master.wm_title("Repetition game") + # self.frame.pack() + self.master.wm_title('Repetition game') self.sizeButton = 11 self.startScreen() @@ -32,13 +31,13 @@ def startScreen(self): master = self.master self.textRules = tkinter.StringVar() self.boxRules = tkinter.Label(master, textvariable=self.textRules) - self.textRules.set("Welcome to the music21 game!\n Rules:\n " + - "Two players: the first one plays a note.\n " + - "The second one has to play the first note and a new one.\n " + - "Continue doing the same until one fails.") + self.textRules.set('Welcome to the music21 game!\n Rules:\n ' + + 'Two players: the first one plays a note.\n ' + + 'The second one has to play the first note and a new one.\n ' + + 'Continue doing the same until one fails.') self.boxRules.grid(row=0, column=0, columnspan=4, rowspan=5, sticky=tkinter.W) - self.buttonAccept = tkinter.Button(master, text="Accept", width=self.sizeButton, + self.buttonAccept = tkinter.Button(master, text='Accept', width=self.sizeButton, command=self.callback, bg='white') self.buttonAccept.grid(row=8, column=1, columnspan=2) @@ -72,7 +71,8 @@ def callback(self): self.boxName4.grid(row=2, column=2) self.textRound = tkinter.StringVar() - self.boxName5 = tkinter.Label(master, width=2*self.sizeButton, textvariable=self.textRound) + self.boxName5 = tkinter.Label(master, width=2 * self.sizeButton, + textvariable=self.textRound) self.textRound.set('Round') self.boxName5.grid(row=2, column=1) @@ -89,30 +89,30 @@ def callback(self): self.canvas2.create_oval(1, 1, 40, 40, fill='red') self.canvas2.grid(row=1, column=2) - self.buttonStart = tkinter.Button(master, text="Start Recording", width=self.sizeButton, + self.buttonStart = tkinter.Button(master, text='Start Recording', width=self.sizeButton, command=self.startGame, bg='green') self.buttonStart.grid(row=3, column=0, columnspan=3) def startGame(self): - #master = self.master + # master = self.master self.good = True - self.textFinal.set('WAIT...') - #self.boxName6.grid(row=4, column=0, columnspan=3) + self.textFinal.set('Wait...') + # self.boxName6.grid(row=4, column=0, columnspan=3) self.rG = repetitionGame.repetitionGame() self.good = True - self.textFinal.set('GO!') + self.textFinal.set('Go!') self.master.after(0, self.mainLoop) def mainLoop(self): - #master = self.master + # master = self.master - if self.good == True: + if self.good: print('rounddddddddddddasdasdadsadad', self.rG.round) self.textRound.set('Round %d' % (self.rG.round + 1)) self.counter = math.pow(-1, self.rG.round) - if self.counter == 1: #player 1 + if self.counter == 1: # player 1 self.canvas1.create_oval(1, 1, 40, 40, fill='green') self.canvas1.grid(row=1, column=0) @@ -131,29 +131,29 @@ def mainLoop(self): self.master.after(10, self.mainLoop) else: if self.counter == -1: - self.textP1Result.set('WINNER!') - #boxName.grid(row=2, column=0) - self.textP2Result.set('LOSER') - #boxName.grid(row=2, column=2) + self.textP1Result.set('Winner!') + # boxName.grid(row=2, column=0) + self.textP2Result.set('Second place') + # boxName.grid(row=2, column=2) self.canvas1.create_oval(1, 1, 40, 40, fill='yellow') self.canvas1.grid(row=1, column=0) self.canvas2.create_oval(1, 1, 40, 40, fill='grey') self.canvas2.grid(row=1, column=2) else: - self.textP1Result.set('LOSER') - self.textP2Result.set('WINNER!') + self.textP1Result.set('Second place') + self.textP2Result.set('Winner!') self.canvas1.create_oval(1, 1, 40, 40, fill='grey') self.canvas1.grid(row=1, column=0) - #self.canvas2.destroy() + # self.canvas2.destroy() self.canvas2.create_oval(1, 1, 40, 40, fill='yellow') self.canvas2.grid(row=1, column=2) self.textFinal.set('Another game?') self.boxName6.grid(row=4, column=0, columnspan=3) -if __name__ == "__main__": +if __name__ == '__main__': root = tkinter.Tk() sfapp = SFApp(root) root.mainloop() diff --git a/music21_tools/audioSearchDemos/graphicalInterfaceSF.py b/music21_tools/audioSearchDemos/graphicalInterfaceSF.py index fe9223c..0bf0bb3 100644 --- a/music21_tools/audioSearchDemos/graphicalInterfaceSF.py +++ b/music21_tools/audioSearchDemos/graphicalInterfaceSF.py @@ -8,16 +8,19 @@ # Copyright: Copyright © 2011-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ -_DOC_IGNORE_MODULE_OR_PACKAGE = True +import math +import queue +import threading +import time +import tkinter from music21 import corpus from music21 import converter +from music21 import environment from music21 import exceptions21 -import threading -import queue -import tkinter -import time -import math +from music21 import scale +from music21.audioSearch import scoreFollower +# from music21.audioSearch import recording _missingImport = [] try: @@ -30,22 +33,18 @@ except ImportError: _missingImport.append('PIL') -from music21 import environment -_MOD = 'audioSearch/graphicalInterfaceSF.py' -environLocal = environment.Environment(_MOD) - -from music21.audioSearch import * #@UnusedWildImport -#from music21.audioSearch import recording -from music21 import scale - try: import AppKit except ImportError: try: import ctypes - except: + except ImportError: pass +_DOC_IGNORE_MODULE_OR_PACKAGE = True +_MOD = 'audioSearch/graphicalInterfaceSF.py' +environLocal = environment.Environment(_MOD) + # TO DO # the same name for the score and the song!!!! @@ -55,21 +54,25 @@ class SFApp(): def __init__(self, master): self.debug = True if 'PIL' in _missingImport: - raise exceptions21.Music21Exception("Need PIL installed to run Score Follower") + raise exceptions21.Music21Exception('Need PIL installed to run Score Follower') self.master = master self.frame = tkinter.Frame(master) - self.master.wm_title("Score follower - music21") + self.master.wm_title('Score follower - music21') self.scoreNameSong = 'scores/d luca gloria_Page_' - #'/Users/cuthbert/Desktop/scores/Saint-Saens-Clarinet-Sonata/Saint-Saens-Clarinet-Sonata_Page_' - #C:\Users\Jordi\Desktop\m21\Saint-Saens-Clarinet-Sonata\Saint-Saens-Clarinet-Sonata\Saint-Saens-Clarinet-Sonata_Page_' + # '/Users/cuthbert/Desktop/scores/Saint-Saens-Clarinet-Sonata/' + # 'Saint-Saens-Clarinet-Sonata_Page_' + # C:\Users\Jordi\Desktop\m21\Saint-Saens-Clarinet-Sonata\ + # Saint-Saens-Clarinet-Sonata\Saint-Saens-Clarinet-Sonata_Page_' #'scores/d luca gloria_Page_' #'scores/d luca gloria_Page_' self.format = 'tiff'#'jpg' self.nameRecordedSong = 'luca/gloria' - #'/Users/cuthbert/Desktop/scores/Saint-Saens-Clarinet-Sonata/saint-saens.xml' - #'C:\Users\Jordi\Desktop\m21\Saint-Saens-Clarinet-Sonata\Saint-Saens-Clarinet-Sonata\saint-saens.xml' - self.pageMeasureNumbers = [] # get directly from score - the last one is the last note of the score + # '/Users/cuthbert/Desktop/scores/Saint-Saens-Clarinet-Sonata/saint-saens.xml' + # 'C:\Users\Jordi\Desktop\m21\Saint-Saens-Clarinet-Sonata\ + # Saint-Saens-Clarinet-Sonata\saint-saens.xml' + # get directly from score — the last entry is the last note + self.pageMeasureNumbers = [] self.totalPagesScore = 1 self.currentLeftPage = 1 self.pagesScore = [] @@ -83,30 +86,33 @@ def __init__(self, master): self.sizeButton = 11 - try: # for windows + try: # for windows unused_user32 = ctypes.windll.user32 # test for error... self.screenResolution = [1024, 600] - #self.screenResolution = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1) - environLocal.printDebug("screen resolution (windows) %d x %d" % (self.screenResolution[0], self.screenResolution[1])) + # self.screenResolution = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1) + environLocal.printDebug('screen resolution (windows) %d x %d' + % (self.screenResolution[0], self.screenResolution[1])) self.resolution = True - except: # mac and linux + except Exception: # mac and linux try: for screen in AppKit.NSScreen.screens(): # @UndefinedVariable - self.screenResolution = [int(screen.frame().size.width), int(screen.frame().size.height)] - environLocal.printDebug("screen resolution (MAC or linux) %d x %d" % (self.screenResolution[0], self.screenResolution[1])) + self.screenResolution = [int(screen.frame().size.width), + int(screen.frame().size.height)] + environLocal.printDebug('screen resolution (MAC or linux) %d x %d' + % (self.screenResolution[0], self.screenResolution[1])) self.resolution = True - except: + except Exception: self.screenResolution = [1024, 600] environLocal.printDebug('screen resolution not detected') self.resolution = False self.y = int(self.screenResolution[1] / 1.25) - self.x = int(self.y / 1.29)# 1.29 = side relation of letter paper - if self.x > self.screenResolution[0] / 2.6: # 2.6 is a factor to scale canvas + self.x = int(self.y / 1.29) # 1.29 = side relation of letter paper + if self.x > self.screenResolution[0] / 2.6: # 2.6 is a factor to scale canvas self.x = int(self.screenResolution[0] / 2.6) self.y = int(self.x * 1.29) - environLocal.printDebug("resized! too big") - environLocal.printDebug("one page score size %d x %d" % (self.x, self.y)) + environLocal.printDebug('resized! too big') + environLocal.printDebug('one page score size %d x %d' % (self.x, self.y)) self.filenameRequest() @@ -117,7 +123,7 @@ def filenameRequest(self): self.textVarName.set(self.scoreNameSong) self.box.grid(row=0, column=3, columnspan=2) - self.buttonSubmit = tkinter.Button(master, text="Submit score name", + self.buttonSubmit = tkinter.Button(master, text='Submit score name', width=2 * self.sizeButton, command=self.startMainCanvas) self.buttonSubmit.grid(row=1, column=3, columnspan=2, padx=0) @@ -152,13 +158,13 @@ def initializeName(self): str(i + 1).zfill(numberLength), self.format) self.listNamePages.append(namePage) - pilPage = PILImage.open(namePage) # @UndefinedVariable - if pilPage.mode != "RGB": - pilPage = pilPage.convert("RGB") + pilPage = PILImage.open(namePage) + if pilPage.mode != 'RGB': + pilPage = pilPage.convert('RGB') pilPage = self.cropBorder(pilPage) - self.pagesScore.append(pilPage.resize(self.newSize, PILImage.ANTIALIAS)) # @UndefinedVariable - self.phimage.append(PILImageTk.PhotoImage(self.pagesScore[i])) # @UndefinedVariable - environLocal.printDebug("initializeName finished") + self.pagesScore.append(pilPage.resize(self.newSize, PILImage.ANTIALIAS)) + self.phimage.append(PILImageTk.PhotoImage(self.pagesScore[i])) + environLocal.printDebug('initializeName finished') def cropBorder(self, img, minColor=240, maxColor=256): @@ -170,9 +176,9 @@ def cropBorder(self, img, minColor=240, maxColor=256): topCut = 0 bottomCut = 0 rightCut = 0 - numberPixels = int(math.sqrt(len(data)/3000000)*4) + numberPixels = int(math.sqrt(len(data) / 3000000) * 4) - #Find top + # Find top for i in range(0, len(data), numberPixels + int(resX / 10)): if (data[i][0] not in colorRange and data[i][1] not in colorRange @@ -180,7 +186,7 @@ def cropBorder(self, img, minColor=240, maxColor=256): topCut = int(i / resX) break - #Find bottom + # Find bottom for i in range(len(data) - 1, 0, -1 * numberPixels - int(resX / 10)): if (data[i][0] not in colorRange and data[i][1] not in colorRange @@ -188,7 +194,7 @@ def cropBorder(self, img, minColor=240, maxColor=256): bottomCut = int((len(data) - i) / resX) break - #Find left + # Find left stop = False for xPos in range(0, resX, numberPixels): # check every 4th pixel for yPos in range(xPos, resY, int(resY / 15)): @@ -203,7 +209,7 @@ def cropBorder(self, img, minColor=240, maxColor=256): break comprovation = False - while comprovation == False: + while not comprovation: bad = False for yPos in range(0, resY, int(resY / resY)): pixelPosition = yPos * resX + leftCut - numberPixels @@ -213,12 +219,12 @@ def cropBorder(self, img, minColor=240, maxColor=256): or data[pixelPosition][1] not in colorRange or data[pixelPosition][2] not in colorRange): bad = True - if bad == False: + if not bad: comprovation = True else: leftCut = leftCut - numberPixels - #Find right + # Find right stop = False for xPos in range(resX - 1, 0, -numberPixels): # check every 4th pixel for yPos in range(resX - xPos, resY, int(resY / 15)): @@ -233,7 +239,7 @@ def cropBorder(self, img, minColor=240, maxColor=256): break comprovation = False - while comprovation == False: + while not comprovation: bad = False for yPos in range(0, resY, int(resY / resY)): pixelPosition = yPos * resX + resX - (rightCut - numberPixels) @@ -243,12 +249,12 @@ def cropBorder(self, img, minColor=240, maxColor=256): and data[pixelPosition][1] not in colorRange and data[pixelPosition][2] not in colorRange): bad = True - if bad == False: + if not bad: comprovation = True else: rightCut = rightCut - numberPixels - margin = int(resX * 0.03) # leave border 3% of the size + margin = int(resX * 0.03) # leave border 3% of the size leftCut = leftCut - margin topCut = topCut - margin @@ -262,7 +268,7 @@ def cropBorder(self, img, minColor=240, maxColor=256): bottomCut = 0 if rightCut < 0: rightCut = 0 - #print (leftCut, topCut, rightCut, bottomCut) + # print(leftCut, topCut, rightCut, bottomCut) img = img.crop((leftCut, topCut, resX - rightCut, resY - bottomCut)) return img @@ -298,9 +304,9 @@ def initializeScore(self): self.middlePages.append(noteCounter) middlePagesCounter += 1 noteCounter += 1 - environLocal.printDebug("beginning of the pages %s" % str(self.beginningPages)) - environLocal.printDebug("middles of the pages %s" % str(self.middlePages)) - environLocal.printDebug("initializeScore finished") + environLocal.printDebug('beginning of the pages %s' % str(self.beginningPages)) + environLocal.printDebug('middles of the pages %s' % str(self.middlePages)) + environLocal.printDebug('initializeScore finished') def initializeGraphicInterface(self): @@ -317,7 +323,7 @@ def initializeGraphicInterface(self): self.sizeCanvasy = self.y self.canvas1 = tkinter.Canvas(master, borderwidth=1, width=self.sizeCanvasx, - height=self.sizeCanvasy, bg="black") + height=self.sizeCanvasy, bg='black') self.canvas1.create_image(self.positionxLeft, self.positionyLeft, image=self.phimage[0], tag='leftImage') @@ -343,37 +349,37 @@ def initializeGraphicInterface(self): self.textVar3.set('That is a good song! :)') - if self.firstTime == True: + if self.firstTime: self.button2 = tkinter.Button(master, - text="START SF", + text='Start SF', width=self.sizeButton, command=self.startScoreFollower, bg='green') self.button2.grid(row=5, column=3) self.button3 = tkinter.Button(master, - text="1st page", + text='1st page', width=self.sizeButton, command=self.goTo1stPage) self.button3.grid(row=4, column=3) - self.button3 = tkinter.Button(master, text="Last page", + self.button3 = tkinter.Button(master, text='Last page', width=self.sizeButton, command=self.goToLastPage) self.button3.grid(row=4, column=4) - self.button4 = tkinter.Button(master, text="Forward", + self.button4 = tkinter.Button(master, text='Forward', width=self.sizeButton, command=self.pageForward) self.button4.grid(row=3, column=4) - self.button7 = tkinter.Button(master, text="Backward", + self.button7 = tkinter.Button(master, text='Backward', width=self.sizeButton, command=self.pageBackward) self.button7.grid(row=3, column=3) - self.button5 = tkinter.Button(master, text="MOVE", + self.button5 = tkinter.Button(master, text='Move', width=self.sizeButton, command=self.moving, bg='beige') self.button5.grid(row=2, column=3) - self.button6 = tkinter.Button(master, text="STOP SF", + self.button6 = tkinter.Button(master, text='Stop SF', width=self.sizeButton, command=self.stopScoreFollower, bg='red') self.button6.grid(row=5, column=4) @@ -384,10 +390,10 @@ def initializeGraphicInterface(self): self.label2.grid(row=7, column=3, sticky=tkinter.E) self.firstTime = False - environLocal.printDebug("initializeGraphicInterface finished") + environLocal.printDebug('initializeGraphicInterface finished') def moving(self): - environLocal.printDebug("moving starting") + environLocal.printDebug('moving starting') if self.currentLeftPage + 1 < self.totalPagesScore: self.ntimes = 0 self.newcoords = self.positionxRight, self.positionyLeft @@ -395,32 +401,32 @@ def moving(self): image=self.phimage[self.currentLeftPage + 1], tag='3rdImage') self.refreshTime = 40 - if self.ScF != None: + if self.ScF is not None: if self.ScF.scoreStream[self.ScF.lastNotePosition].measureNumber < ( self.pageMeasureNumbers[self.currentLeftPage + 1] + self.pageMeasureNumbers[self.currentLeftPage]) / 2.0: self.speed = self.speed = int(3.0 * (self.screenResolution[0] / 1024.0)) environLocal.printDebug( - "moving when last measure was at the first 50% of the right page") + 'moving when last measure was at the first 50% of the right page') elif self.ScF.scoreStream[self.ScF.lastNotePosition].measureNumber < 3 * ( self.pageMeasureNumbers[self.currentLeftPage + 1] + self.pageMeasureNumbers[self.currentLeftPage]) / 4.0: self.speed = self.speed = int(4.0 * (self.screenResolution[0] / 1024.0)) environLocal.printDebug( - "moving when last measure was between the first 50% " + - "and the 75% of the right page") + 'moving when last measure was between the first 50% ' + + 'and the 75% of the right page') else: self.speed = self.speed = int(5.0 * (self.screenResolution[0] / 1024.0)) - environLocal.printDebug("moving when last measure was after " + - "the 75% of the right page") + environLocal.printDebug('moving when last measure was after ' + + 'the 75% of the right page') else: - environLocal.printDebug("moving at default speed") + environLocal.printDebug('moving at default speed') self.speed = 3.0 * (self.screenResolution[0] / 1024.0) self.master.after(500, self.movingRoutine) def movingRoutine(self): - environLocal.printDebug("starting movingRoutine") + environLocal.printDebug('starting movingRoutine') if self.newcoords[0] > self.positionxLeft: self.newcoords = self.positionxRight - self.speed * self.ntimes, self.positionyRight self.canvas1.coords('rightImage', self.newcoords) @@ -431,7 +437,7 @@ def movingRoutine(self): self.master.after(self.refreshTime, self.movingRoutine) else: - environLocal.printDebug("finished main moving routine") + environLocal.printDebug('finished main moving routine') if self.currentLeftPage + 2 <= self.totalPagesScore: self.canvas1.delete('3rdImage') self.pageForward() @@ -460,7 +466,7 @@ def pageForward(self): self.canvas1.grid(row=1, column=0, columnspan=3, rowspan=7) self.currentLeftPage += 1 - environLocal.printDebug("page Forward %d, %d" % (self.currentLeftPage, + environLocal.printDebug('page Forward %d, %d' % (self.currentLeftPage, self.totalPagesScore)) def pageBackward(self): @@ -478,7 +484,8 @@ def pageBackward(self): self.canvas1.grid(row=1, column=0, columnspan=3, rowspan=7) self.currentLeftPage -= 1 - environLocal.printDebug("page Backward %d %d" % (self.currentLeftPage, self.totalPagesScore)) + environLocal.printDebug('page Backward %d %d' + % (self.currentLeftPage, self.totalPagesScore)) def goTo1stPage(self): self.currentLeftPage = 1 @@ -487,7 +494,7 @@ def goTo1stPage(self): image=self.phimage[0], tag='leftImage') self.textVar1.set('Left page: %d/%d' % (1, self.totalPagesScore)) - if self.totalPagesScore > 1: #there is more than 1 page + if self.totalPagesScore > 1: # there is more than 1 page self.canvas1.delete('rightImage') self.canvas1.create_image(self.positionxRight, self.positionyRight, image=self.phimage[1], tag='rightImage') @@ -502,7 +509,7 @@ def goToLastPage(self): image=self.phimage[self.totalPagesScore - 1], tag='leftImage') self.textVar1.set('Left page: %d/%d' % (self.totalPagesScore, self.totalPagesScore)) - if self.totalPagesScore > 1: #there is more than 1 page + if self.totalPagesScore > 1: # there is more than 1 page self.canvas1.delete('rightImage') self.textVar2.set('Right page: -/-') @@ -511,8 +518,8 @@ def goToLastPage(self): def startScoreFollower(self): - environLocal.printDebug("startScoreFollower starting") - self.button2 = tkinter.Button(self.master, text="START SF", width=self.sizeButton, + environLocal.printDebug('startScoreFollower starting') + self.button2 = tkinter.Button(self.master, text='Start SF', width=self.sizeButton, command=self.startScoreFollower, state='disable', bg='green') self.button2.grid(row=5, column=3) scNotes = self.scorePart.flatten().notesAndRests @@ -534,7 +541,7 @@ def startScoreFollower(self): self.textVar3.set('Start playing!') self.scoreFollower = ScF - #parameters for the thread 2 + # parameters for the thread 2 self.dummyQueue = queue.Queue() self.sampleQueue = queue.Queue() self.dummyQueue2 = queue.Queue() @@ -544,19 +551,19 @@ def startScoreFollower(self): def continueScoreFollower(self): - environLocal.printDebug("continueScoreFollower starting") + environLocal.printDebug('continueScoreFollower starting') self.ScF = self.scoreFollower self.timeStart = time.time() - if self.stop == False and (self.firstTimeSF == True or self.rt.resultInThread == False): - environLocal.printDebug("firstTimeSF == True or resultInThread") + if not self.stop and (self.firstTimeSF or not self.rt.resultInThread): + environLocal.printDebug('firstTimeSF == True or resultInThread') self.lastNoteString = 'Note: %d, Measure: %d, Countdown:%d, Page:%d' % ( self.ScF.lastNotePosition, self.ScF.scoreStream[self.ScF.lastNotePosition].measureNumber, self.ScF.countdown, self.currentLeftPage) - if self.firstTimeSF == False: - self.textVarComments.set("1st meas: %d, last meas: %d" % ( + if not self.firstTimeSF: + self.textVarComments.set('1st meas: %d, last meas: %d' % ( self.ScF.scoreStream[self.ScF.firstNotePage].measureNumber, self.ScF.scoreStream[self.ScF.lastNotePage].measureNumber)) self.firstTimeSF = False @@ -564,39 +571,39 @@ def continueScoreFollower(self): self.ScF.firstNotePage = self.beginningPages[self.currentLeftPage - 1] - 1 if self.currentLeftPage + 1 < self.totalPagesScore: self.ScF.lastNotePage = self.middlePages[self.currentLeftPage + 1] - 1 - environLocal.printDebug("3 or more pages remaining %d %d" % (self.firstTimeSF, + environLocal.printDebug('3 or more pages remaining %d %d' % (self.firstTimeSF, self.ScF.lastNotePage)) elif self.currentLeftPage < self.totalPagesScore: self.ScF.lastNotePage = self.beginningPages[self.currentLeftPage + 1] - 1 - environLocal.printDebug("2 pages on the screen %d %d" % (self.firstTimeSF, + environLocal.printDebug('2 pages on the screen %d %d' % (self.firstTimeSF, self.ScF.lastNotePage)) else: self.ScF.lastNotePage = self.beginningPages[self.currentLeftPage] - 1 - environLocal.printDebug("only one page on the screen %d %d" % (self.firstTimeSF, + environLocal.printDebug('only one page on the screen %d %d' % (self.firstTimeSF, self.ScF.lastNotePage)) self.rt = RecordThread(self.dummyQueue, self.sampleQueue, self.ScF) self.rt.daemon = True - environLocal.printDebug("2nd thread about to start") - self.rt.start() # the 2nd thread starts here - self.dummyQueue.put("Start") - environLocal.printDebug("about to put analyzeRecording into master...") + environLocal.printDebug('2nd thread about to start') + self.rt.start() # the 2nd thread starts here + self.dummyQueue.put('Start') + environLocal.printDebug('about to put analyzeRecording into master...') self.master.after(7000, self.analyzeRecording) else: - environLocal.printDebug("stopped...") + environLocal.printDebug('stopped...') self.button2.destroy() - self.button2 = tkinter.Button(self.master, text="START SF", width=self.sizeButton, + self.button2 = tkinter.Button(self.master, text='Start SF', width=self.sizeButton, command=self.startScoreFollower, bg='green') self.button2.grid(row=5, column=3) - self.textVarComments.set('END!! %s' % (self.rt.resultInThread)) + self.textVarComments.set('Done. %s' % (self.rt.resultInThread)) def analyzeRecording(self): self.rt.outQueue.get() self.textVar3.set(self.lastNoteString) self.ScF.firstSlot = self.beginningPages[self.currentLeftPage - 1] - environLocal.printDebug("**** %d %d %d %d" % (self.ScF.lastNotePosition, + environLocal.printDebug('**** %d %d %d %d' % (self.ScF.lastNotePosition, self.beginningPages[self.currentLeftPage - 1], self.currentLeftPage, (self.ScF.lastNotePosition < @@ -608,7 +615,7 @@ def analyzeRecording(self): # case in which the musician plays a note of a not displayed page pageNumber = 0 final = False - while final == False: + while not final: if (pageNumber < self.totalPagesScore and self.ScF.lastNotePosition >= self.beginningPages[pageNumber]): pageNumber += 1 @@ -619,41 +626,41 @@ def analyzeRecording(self): totalPagesToMove = 0 else: totalPagesToMove = pageNumber - self.currentLeftPage - # print "TOTAL PAGES TO MOVE", totalPagesToMove, pageNumber, self.currentLeftPage + # print('Total pages to move', totalPagesToMove, + # pageNumber, self.currentLeftPage) if totalPagesToMove > 0: for i in range(totalPagesToMove): self.pageForward() - # print "has played a note not shown in the score (forward)" + # print('has played a note not shown in the score (forward)') elif totalPagesToMove < 0: for i in range(int(math.fabs(totalPagesToMove))): self.pageBackward() - # print "has played a note not shown in the score (backward)" - + # print('has played a note not shown in the score (backward)') elif (self.ScF.lastNotePosition >= self.middlePages[self.currentLeftPage] - and self.isMoving == False): #50% case + and not self.isMoving): # 50% case self.isMoving = True environLocal.printDebug('moving right page to left') self.moving() - #self.isMoving = False - environLocal.printDebug("playing a note of the second half part of the right page") + # self.isMoving = False + environLocal.printDebug('playing a note of the second half part of the right page') elif (self.ScF.lastNotePosition >= self.beginningPages[self.currentLeftPage] and self.ScF.lastNotePosition < (self.middlePages[self.currentLeftPage] + self.beginningPages[self.currentLeftPage])): self.hits += 1 - environLocal.printDebug("playing a note of the first half part of the right " + - "page: hits=%d" % self.hits) + environLocal.printDebug('playing a note of the first half part of the right ' + + 'page: hits=%d' % self.hits) if self.hits == 2: self.hits = 0 - if self.isMoving == False: + if not self.isMoving: self.isMoving = True environLocal.printDebug('moving for hits') self.moving() - #self.isMoving = False + # self.isMoving = False else: self.hits = 0 - environLocal.printDebug("playing a note of the left page") + environLocal.printDebug('playing a note of the left page') print('------------------last note position', self.ScF.lastNotePosition) @@ -661,13 +668,13 @@ def analyzeRecording(self): def stopScoreFollower(self): self.stop = True - environLocal.printDebug("Stop button pressed!") + environLocal.printDebug('Stop button pressed!') # # -#class External(): +# class External(): # # def __init__(self, newcoords, positionxLeft, positionxRight, speed, positionyRight, # canvas,currentLeftPage,totalPagesScore,newcoords3rd, @@ -720,18 +727,18 @@ def __init__(self, inQueue, outQueue, recordingObject): def run(self): unused_startCommand = self.inQueue.get() - environLocal.printDebug("start command received: recording!") + environLocal.printDebug('start command received: recording!') self.resultInThread = self.object.repeatTranscription() - environLocal.printDebug("repeatTranscription has been run") + environLocal.printDebug('repeatTranscription has been run') self.outQueue.put(1) - environLocal.printDebug("put into outQueue") + environLocal.printDebug('put into outQueue') self.outQueue.task_done() - environLocal.printDebug("task is done") + environLocal.printDebug('task is done') -if __name__ == "__main__": +if __name__ == '__main__': root = tkinter.Tk() sfapp = SFApp(root) root.mainloop() diff --git a/music21_tools/audioSearchDemos/humanVScomputer.py b/music21_tools/audioSearchDemos/humanVScomputer.py index d77f072..9e77d63 100644 --- a/music21_tools/audioSearchDemos/humanVScomputer.py +++ b/music21_tools/audioSearchDemos/humanVScomputer.py @@ -8,13 +8,14 @@ # Copyright: Copyright © 2011-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ -_DOC_IGNORE_MODULE_OR_PACKAGE = True +import time +import random from music21 import scale, note from music21 import audioSearch as base -#from music21.audioSearch import * -import time -import random +# from music21.audioSearch import * + +_DOC_IGNORE_MODULE_OR_PACKAGE = True @@ -24,22 +25,22 @@ def runGame(): good = True gameNotes = [] - print("Welcome to the music21 game!") - print("Rules:") - print("The computer generates a note (and it will play them in the future).") - print("The player has to play all the notes from the beginning.") + print('Welcome to the music21 game!') + print('Rules:') + print('The computer generates a note (and it will play them in the future).') + print('The player has to play all the notes from the beginning.') time.sleep(2) - print("3, 2, 1 GO!") - nameNotes = ["A", "B", "C", "D", "E", "F", "G"] - while(good == True): + print('3, 2, 1 GO!') + nameNotes = ['A', 'B', 'C', 'D', 'E', 'F', 'G'] + while good: randomNumber = random.randint(0, 6) octaveNumber = 4 # I can put a random number here... - fullNameNote = "%s%d" % (nameNotes[randomNumber], octaveNumber) + fullNameNote = '%s%d' % (nameNotes[randomNumber], octaveNumber) gameNotes.append(note.Note(fullNameNote)) roundNumber = roundNumber + 1 - print("ROUND %d" % roundNumber) - print("NOTES UNTIL NOW: (this will not be shown in the final version)") + print('Round %d' % roundNumber) + print('Notes so far (debug-only, will not appear in the final version):') for k in range(len(gameNotes)): print(gameNotes[k].fullName) @@ -47,30 +48,29 @@ def runGame(): freqFromAQList = base.getFrequenciesFromMicrophone(length=seconds, storeWaveFilename=None) detectedPitchesFreq = base.detectPitchFrequencies(freqFromAQList, useScale) detectedPitchesFreq = base.smoothFrequencies(detectedPitchesFreq) - (detectedPitchObjects, unused_listplot) = base.pitchFrequenciesToObjects(detectedPitchesFreq, useScale) - (notesList, unused_durationList) = base.joinConsecutiveIdenticalPitches(detectedPitchObjects) + (detectedPitchObjects, + unused_listplot) = base.pitchFrequenciesToObjects(detectedPitchesFreq, useScale) + (notesList, + unused_durationList) = base.joinConsecutiveIdenticalPitches(detectedPitchObjects) j = 0 i = 0 - while i < len(notesList) and j < len(gameNotes) and good == True: - if notesList[i].name == "rest": + while i < len(notesList) and j < len(gameNotes) and good: + if notesList[i].name == 'rest': i = i + 1 elif notesList[i].name == gameNotes[j].name: i = i + 1 j = j + 1 else: - print("WRONG NOTE! You played", notesList[i].fullName, "and should have been", gameNotes[j].fullName) + print('Wrong note. You played', notesList[i].fullName, + 'and it should have been', gameNotes[j].fullName) good = False - if good == True and j != len(gameNotes): + if good and j != len(gameNotes): good = False - print("YOU ARE VERY SLOW!!! PLAY FASTER NEXT TIME!") + print("Time's up — try a faster pace next round.") - if good == False: - print("GAME OVER! TOTAL ROUNDS: %d" % roundNumber) + if not good: + print('Game over. Total rounds: %d' % roundNumber) -if __name__ == "__main__": +if __name__ == '__main__': runGame() - - -# ----------------------------------------------------------------------------- -# eof diff --git a/music21_tools/audioSearchDemos/omrfollow.py b/music21_tools/audioSearchDemos/omrfollow.py index 0bc5113..5cf4b13 100644 --- a/music21_tools/audioSearchDemos/omrfollow.py +++ b/music21_tools/audioSearchDemos/omrfollow.py @@ -16,7 +16,7 @@ from music21 import stream from music21 import environment -_MOD = "audioSearch.omrfollow" +_MOD = 'audioSearch.omrfollow' environLocal = environment.Environment(_MOD) # given an iterable of pairs return the key corresponding to the greatest value @@ -53,7 +53,7 @@ def recognizeKuhlau(): Only does the first couple of pages ''' pageMeasureNumbers = [1, 73, 151, 217, 268, 313, 366, - 437, 506, 555, 614, 662, 702, 737, 764] #764 is end of document + 437, 506, 555, 614, 662, 702, 737, 764] # 764 is end of document kuhlau = converter.parse('kuhlau_op81_fl2.xml') recognizeScore(kuhlau, pageMeasureNumbers, iterations=3) @@ -74,7 +74,7 @@ def recognizeScore(scorePart, pageMeasureNumbers, iterations=1): for i in range(0, totalNotes, 8): startNote = thisPage[i] startMeasure = startNote.measureNumber - print(" " + str(startMeasure)) + print(' ' + str(startMeasure)) newStream = stream.Stream(thisPage[i:i + 24]) newStream.pageNumber = pgMinus1 + 1 newStream.startMeasure = startMeasure @@ -82,33 +82,33 @@ def recognizeScore(scorePart, pageMeasureNumbers, iterations=1): for loopy in range(iterations): if loopy > 0: - print("\n\nstarting again in 3 seconds") + print('\n\nstarting again in 3 seconds') time.sleep(3) searchScore = audioSearch.transcriber.runTranscribe(show=False, plot=False, seconds=15.0, saveFile=False) - l = search.approximateNoteSearch(searchScore, allStreams) + matches = search.approximateNoteSearch(searchScore, allStreams) scores = [0 for j in range(len(pages))] - for i in range(8): # top 8 searches - topStream = l[i] + for i in range(8): # top 8 searches + topStream = matches[i] scorePage = topStream.pageNumber - 1 - scores[scorePage] += (topStream.matchProbability / (i + 1.5))*10 + scores[scorePage] += (topStream.matchProbability / (i + 1.5)) * 10 - print("\nBest guesses (pg#, starting measure, probability)") - for i,st in enumerate(l): + print('\nBest guesses (pg#, starting measure, probability)') + for i, st in enumerate(matches): print(st.pageNumber, st.startMeasure, st.matchProbability) if i >= 7: break - print("\nWeighed top scores (pg#, score):") + print('\nWeighed top scores (pg#, score):') indexOfMaxScore = argmax_index(scores) for i in range(len(pages)): - print( (i + 1, scores[i]), end="") + print( (i + 1, scores[i]), end='') if i == indexOfMaxScore: - print(" **** ") + print(' **** ') else: - print("") + print('') if __name__ == '__main__': recognizeLuca() diff --git a/music21_tools/audioSearchDemos/repetitionGame.py b/music21_tools/audioSearchDemos/repetitionGame.py index 2fc7475..9258769 100644 --- a/music21_tools/audioSearchDemos/repetitionGame.py +++ b/music21_tools/audioSearchDemos/repetitionGame.py @@ -8,12 +8,11 @@ # Copyright: Copyright © 2011-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ -_DOC_IGNORE_MODULE_OR_PACKAGE = True - - from music21 import scale from music21 import audioSearch as base +_DOC_IGNORE_MODULE_OR_PACKAGE = True + class repetitionGame(): @@ -22,22 +21,22 @@ def __init__(self): self.round = 0 self.good = True self.gameNotes = [] - print("Welcome to the music21 game!") - print("Rules:") - print("Two players: the first one plays a note. \n" - + "The second one has to play the first note and a new one.") - print("Continue doing the same until one fails.") + print('Welcome to the music21 game!') + print('Rules:') + print('Two players: the first one plays a note. \n' + + 'The second one has to play the first note and a new one.') + print('Continue doing the same until one fails.') # time.sleep(2) - print("3, 2, 1 GO!") + print('3, 2, 1 GO!') def game(self): self.round = self.round + 1 - print("self.round %d" % self.round) - # print "NOTES UNTIL NOW: (this will not be shown in the final version)" - # for k in range(len(self.gameNotes)): - # print self.gameNotes[k].fullName + print('self.round %d' % self.round) + # print('Notes until now: (this will not be shown in the final version)') + # for k in range(len(self.gameNotes)): + # print(self.gameNotes[k].fullName) seconds = 2 + self.round freqFromAQList = base.getFrequenciesFromMicrophone(length=seconds, storeWaveFilename=None) @@ -49,41 +48,38 @@ def game(self): unused_durationList) = base.joinConsecutiveIdenticalPitches(detectedPitchObjects) j = 0 i = 0 - while i < len(notesList) and j < len(self.gameNotes) and self.good == True: - if notesList[i].name == "rest": + while i < len(notesList) and j < len(self.gameNotes) and self.good: + if notesList[i].name == 'rest': i = i + 1 elif notesList[i].name == self.gameNotes[j].name: i = i + 1 j = j + 1 else: - print("WRONG NOTE! You played", notesList[i].fullName, - "and should have been", self.gameNotes[j].fullName) + print('Wrong note. You played', notesList[i].fullName, + 'and it should have been', self.gameNotes[j].fullName) self.good = False - if self.good == True and j != len(self.gameNotes): + if self.good and j != len(self.gameNotes): self.good = False - print("YOU ARE VERY SLOW!!! PLAY FASTER NEXT TIME!") + print("Time's up — try a faster pace next round.") - if self.good == False: - print("YOU LOSE!! HAHAHAHA") + if not self.good: + print('Game over.') else: - while i < len(notesList) and notesList[i].name == "rest": + while i < len(notesList) and notesList[i].name == 'rest': i = i + 1 if i < len(notesList): - self.gameNotes.append(notesList[i]) #add a new note - print("WELL DONE!") + self.gameNotes.append(notesList[i]) # add a new note + print('Well done!') else: - print("YOU HAVE NOT ADDED A NEW NOTE! REPEAT AGAIN NOW") + print('No new note added — please add one before repeating.') self.round = self.round - 1 return self.good -if __name__ == "__main__": +if __name__ == '__main__': rG = repetitionGame() good = True - while good == True: + while good: good = rG.game() - -# ----------------------------------------------------------------------------- -# eof diff --git a/music21_tools/bhadley/__init__.py b/music21_tools/bhadley/__init__.py index a4af8a7..e69de29 100644 --- a/music21_tools/bhadley/__init__.py +++ b/music21_tools/bhadley/__init__.py @@ -1,6 +0,0 @@ - - - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/bhadley/harmonyRealizer.py b/music21_tools/bhadley/harmonyRealizer.py index ff4f0bf..7b9948f 100644 --- a/music21_tools/bhadley/harmonyRealizer.py +++ b/music21_tools/bhadley/harmonyRealizer.py @@ -52,7 +52,7 @@ def generateContrapuntalBassLine(harmonyObject, fbRules): fbLine.addElement(o) allSols = fbLine.realize(fbRules) - #print allSols.getNumSolutions() + # print(allSols.getNumSolutions()) return allSols.generateRandomRealizations(1) def generateSmoothBassLine(harmonyObjects): @@ -91,13 +91,13 @@ def generateSmoothBassLine(harmonyObjects): octavePlus = interval.Interval(lastBass, copy.deepcopy(cs.bass())) cs.bass().octave = cs.bass().octave - 2 octaveMinus = interval.Interval(lastBass, copy.deepcopy(cs.bass())) - l = [sameOctave, octavePlus, octaveMinus] + candidates = [sameOctave, octavePlus, octaveMinus] minimum = sameOctave.generic.undirected ret = sameOctave - for i in l: - if i.generic.undirected < minimum: - minimum = i.generic.undirected - ret = i + for candidate in candidates: + if candidate.generic.undirected < minimum: + minimum = candidate.generic.undirected + ret = candidate if ret.noteEnd.octave > 3 or ret.noteEnd.octave < 1: ret.noteEnd.octave = lastBass.octave @@ -118,30 +118,29 @@ def generatePopSongRules(): ''' fbRules = rules.Rules() - #Single Possibility rules - fbRules.forbidIncompletePossibilities = True #True - fbRules.upperPartsMaxSemitoneSeparation = 12 #12 - fbRules.forbidVoiceCrossing = True #True - - #Consecutive Possibility rules - fbRules.forbidParallelFifths = True #True - fbRules.forbidParallelOctaves = True #True - fbRules.forbidHiddenFifths = True #True - fbRules.forbidHiddenOctaves = True #True - fbRules.forbidVoiceOverlap = True #True - fbRules.partMovementLimits = [] #[] - - #Special resolution rules - fbRules.resolveDominantSeventhProperly = False #True - fbRules.resolveDiminishedSeventhProperly = False #True - fbRules.resolveAugmentedSixthProperly = False #True - fbRules.doubledRootInDim7 = False #False - fbRules.applySinglePossibRulesToResolution = False #False - fbRules.applyConsecutivePossibRulesToResolution = False #False - fbRules.restrictDoublingsInItalianA6Resolution = True #True - - fbRules._upperPartsRemainSame = False #False - + # Single Possibility rules + fbRules.forbidIncompletePossibilities = True + fbRules.upperPartsMaxSemitoneSeparation = 12 + fbRules.forbidVoiceCrossing = True + + # Consecutive Possibility rules + fbRules.forbidParallelFifths = True + fbRules.forbidParallelOctaves = True + fbRules.forbidHiddenFifths = True + fbRules.forbidHiddenOctaves = True + fbRules.forbidVoiceOverlap = True + fbRules.partMovementLimits = [] + + # Special resolution rules + fbRules.resolveDominantSeventhProperly = False # def: True + fbRules.resolveDiminishedSeventhProperly = False # def: True + fbRules.resolveAugmentedSixthProperly = False # def: True + fbRules.doubledRootInDim7 = False + fbRules.applySinglePossibRulesToResolution = False + fbRules.applyConsecutivePossibRulesToResolution = False + fbRules.restrictDoublingsInItalianA6Resolution = True + + fbRules._upperPartsRemainSame = False fbRules.partMovementLimits.append((1, 5)) return fbRules @@ -169,15 +168,7 @@ def mergeLeadSheetAndBassLine(leadsheet, bassLine): return s # ------------------------------------------------------------------------------ -class Test(unittest.TestCase): - - def runTest(self): - pass - -class TestExternal(unittest.TestCase): # pragma: no cover - def runTest(self): - pass - +class TestExternal(unittest.TestCase): # pragma: no cover def realizeclercqTemperleyEx(self, testfile): ''' Example realization (using fbRealizer - romanNumerals flavor) of any clercqTemperley file. @@ -187,7 +178,8 @@ def realizeclercqTemperleyEx(self, testfile): testFile1 = s.toScore() testFile = harmony.realizeChordSymbolDurations(testFile1) - smoothBassRN = generateSmoothBassLine(testFile.flatten().getElementsByClass(roman.RomanNumeral)) + smoothBassRN = generateSmoothBassLine( + testFile.flatten().getElementsByClass(roman.RomanNumeral)) output = generateContrapuntalBassLine(smoothBassRN, generateBaroqueRules()) output.insert(metadata.Metadata()) @@ -203,7 +195,8 @@ def testLeadsheetEx1(self): testFile1.insert(metadata.Metadata()) testFile1.metadata.title = 'Jeanie With The Light Brown Hair' testFile = harmony.realizeChordSymbolDurations(testFile1) - smoothBassCS = generateSmoothBassLine(testFile.flatten().getElementsByClass(harmony.ChordSymbol)) + smoothBassCS = generateSmoothBassLine( + testFile.flatten().getElementsByClass(harmony.ChordSymbol)) output = generateContrapuntalBassLine(smoothBassCS, generatePopSongRules()) mergeLeadSheetAndBassLine(testFile1, output).show() @@ -215,23 +208,22 @@ def xtestRealizeLeadsheet(self, music21Stream): ''' testFile = harmony.realizeChordSymbolDurations(music21Stream) - smoothBassCS = generateSmoothBassLine(testFile.flatten().getElementsByClass(harmony.ChordSymbol)) + smoothBassCS = generateSmoothBassLine( + testFile.flatten().getElementsByClass(harmony.ChordSymbol)) output = generateContrapuntalBassLine(smoothBassCS, generatePopSongRules()) mergeLeadSheetAndBassLine(music21Stream, output).show() -if __name__ == "__main__": +if __name__ == '__main__': from music21 import base - base.mainTest(Test, TestExternal) - - #from music21 import corpus - #from music21.demos.bhadley import HarmonyRealizer - #test = HarmonyRealizer.TestExternal() - - #test.leadsheetEx1() - #sc = converter.parse('https://github.com/cuthbertLab/music21/raw/master/music21/corpus/leadSheet/fosterBrownHair.mxl') # Jeannie Light Brown Hair + base.mainTest(TestExternal) + # from music21 import corpus + # from music21.demos.bhadley import HarmonyRealizer + # test = HarmonyRealizer.TestExternal() + # test.leadsheetEx1() + # sc = corpus.parse('leadSheet/fosterBrownHair') # Jeannie Light Brown Hair testfile1 = ''' % Dylan - Blowin' in the Wind @@ -240,7 +232,7 @@ def xtestRealizeLeadsheet(self, music21Stream): Vr: $VP I IV | I | $VP I IV | V | $VP I IV | I | IV V | I IV | IV V | [2/4] I | [4/4] IV V | I IV | IV V | I | S: [D] $In $Vr $Vr $Vr ''' - #test.realizeclercqTemperleyEx(testfile1) + # test.realizeclercqTemperleyEx(testfile1) testfile2 = ''' % Brown-Eyed Girl @@ -253,7 +245,7 @@ def xtestRealizeLeadsheet(self, music21Stream): S: [G] $In $Vr*2 $Ext $Ch $Brk $Vr $Ext $Ch $A ''' - #test.realizeclercqTemperleyEx(testfile2) + # test.realizeclercqTemperleyEx(testfile2) diff --git a/music21_tools/bhadley/nips2011.py b/music21_tools/bhadley/nips2011.py index 5a94cd9..2b3204d 100644 --- a/music21_tools/bhadley/nips2011.py +++ b/music21_tools/bhadley/nips2011.py @@ -27,16 +27,15 @@ orngTree = orange.classification.tree except ImportError: if __name__ == '__main__': - print("Orange must be installed to run this.") + print('Orange must be installed to run this.') exit() orange = None orngTree = None try: - import BeautifulSoup - from BeautifulSoup import * + from BeautifulSoup import BeautifulSoup except ImportError: - pass + BeautifulSoup = None from music21 import common from music21 import features @@ -54,11 +53,11 @@ def nipsBuild(useOurExtractors=True, buildSet=1, evaluationMethod='coarse'): # here are all the pieces by id and their year and billboard chart number (not used) # this data found by Beth Hadley - entries = json.loads('''[{"1024":[2009,48]}, {"1028":[1961,56]}, {"1034":[1986,9]}, {"1054":[1971,12]}, {"1070":[1982,34]}, {"1081":[2006,55]}, {"1099":[1973,11]}, {"1105":[1952,13]}, {"1347":[2003,30]}, {"1384":[1993,27]}, {"1402":[2009,50]}, {"1459":[1969,2]}, {"1722":[1982,2]}, {"1742":[1959,3]}, {"1904":[1964,9]}, {"1905":[1967,18]}, {"1906":[1967,20]}, {"1907":[1964,8]}, {"1912":[1977,44]}, {"1922":[1954,94]}, {"1930":[1999,6]}, {"1934":[1985,10]}, {"1940":[1970,3]}, {"2045":[1961,7]}, {"2049":[1961,12]}, {"2125":[1959,19]}, {"2167":[1965,88]}, {"2170":[1957,68]}, {"2374":[1963,14]}, {"2387":[1950,4]}, {"2405":[1974,2]}, {"2431":[1985,15]}, {"2447":[1960,3]}, {"2448":[1982,28]}, {"2462":[1951,33]}, {"2519":[1965,7]}, {"2521":[1978,23]}, {"2523":[1955,16]}, {"2533":[1976,52]}, {"2546":[1952,50]}, {"2665":[2004,2]}, {"2688":[1973,45]}, {"2709":[2009,5]}, {"2729":[2004,2]}, {"2735":[2009,60]}, {"2742":[1970,98]}, {"2774":[1967,10]}, {"2776":[1952,46]}, {"2777":[1969,47]}, {"2833":[1957,34]}, {"2834":[1957,37]}, {"2838":[1956,8]}, {"2967":[1973,31]}, {"2984":[1987,14]}, {"2985":[1977,41]}, {"2994":[1967,76]}, {"3012":[1963,28]}, {"3153":[1963,5]}, {"3154":[1963,5]}, {"3155":[1965,9]}, {"3157":[1978,36]}, {"3195":[1975,65]}, {"3218":[1992,6]}, {"3226":[1963,58]}, {"3229":[1958,42]}, {"3232":[1979,10]}, {"3241":[1972,3]}, {"3242":[1977,12]}, {"3243":[1970,45]}, {"3275":[1965,8]}, {"3281":[1954,28]}, {"3282":[1991,15]}, {"3285":[1974,33]}, {"3299":[1956,57]}, {"3300":[1953,8]}, {"3301":[1957,47]}, {"3311":[1954,9]}, {"3323":[1954,28]}, {"3324":[1987,14]}, {"3379":[1962,25]}, {"3402":[1961,2]}, {"3409":[1972,12]}, {"3420":[1954,78]}, {"3452":[1971,85]}, {"3466":[1962,46]}, {"3477":[1960,13]}, {"3484":[1983,59]}, {"3532":[1961,15]}, {"3548":[1970,20]}, {"3554":[1981,53]}, {"3558":[1981,62]}, {"3668":[1953,73]}, {"3681":[1954,2]}, {"3687":[1957,11]}, {"3688":[1957,11]}, {"3689":[1962,15]}, {"3690":[1955,17]}, {"3717":[1955,5]}, {"3718":[1962,4]}, {"3719":[1958,48]}, {"3732":[1954,95]}, {"3749":[1953,3]}, {"3755":[1970,93]}, {"3764":[1958,44]}, {"3765":[1974,94]}, {"3772":[1973,71]}, {"3784":[1961,95]}, {"3785":[1950,17]}, {"3786":[1951,15]}, {"3821":[1954,66]}, {"3834":[1963,3]}, {"3845":[1985,65]}, {"3866":[1957,48]}, {"3871":[1950,82]}, {"3911":[1967,23]}, {"3928":[1959,52]}, {"3979":[1989,70]}, {"3980":[1963,10]}, {"4016":[1951,34]}, {"4022":[1956,98]}, {"4044":[1952,54]}, {"4051":[1984,56]}, {"4073":[1980,90]}, {"4117":[1976,1]}, {"4139":[1951,5]}, {"4252":[1950,14]}, {"4286":[1959,12]}, {"4288":[1965,43]}, {"4294":[1961,75]}, {"4318":[1960,10]}, {"4320":[1961,5]}, {"4335":[1966,62]}, {"4341":[1970,41]}, {"4343":[1964,2]}, {"4353":[1961,55]}, {"4354":[1962,21]}, {"4356":[1955,4]}, {"4357":[1966,23]}, {"4400":[1967,52]}, {"4438":[1965,71]}, {"4439":[1951,9]}, {"4462":[1968,42]}, {"4464":[1964,27]}, {"4470":[1987,38]}, {"4492":[1961,31]}, {"4494":[1952,65]}, {"4503":[1953,91]}, {"4504":[1953,35]}, {"4505":[1953,35]}, {"4514":[1970,51]}, {"4537":[1954,21]}, {"4538":[1956,4]}, {"4567":[1962,93]}, {"4575":[1960,19]}, {"4576":[1962,27]}, {"4618":[1998,13]}, {"4621":[1954,67]}, {"4643":[1970,2]}, {"4645":[1966,44]}, {"4649":[1953,40]}, {"4656":[1974,86]}, {"4657":[1984,25]}, {"4679":[1966,33]}, {"4728":[1974,56]}, {"4732":[2005,43]}, {"4740":[1964,33]}, {"4741":[1963,24]}, {"4742":[1970,18]}, {"4750":[1968,2]}, {"4773":[1971,98]}, {"4791":[1966,28]}, {"4795":[1983,96]}, {"4807":[1956,81]}, {"4837":[1950,5]}, {"4889":[1967,84]}, {"4897":[1957,67]}, {"4963":[1991,58]}, {"4978":[1959,51]}, {"4979":[1956,49]}, {"5002":[1956,3]}, {"5023":[1974,60]}, {"5025":[1977,22]}, {"5195":[1954,1]}, {"5228":[1963,65]}, {"5280":[1970,64]}, {"5296":[1955,62]}, {"5300":[1952,43]}, {"5346":[1955,52]}, {"5356":[1981,29]}, {"5367":[1964,54]}, {"5376":[1970,48]}, {"5391":[1982,31]}, {"5394":[1976,30]}, {"5431":[1974,8]}, {"5432":[1962,6]}, {"5480":[1971,49]}, {"5489":[1969,85]}, {"5506":[1960,98]}, {"5522":[1968,50]}, {"5541":[1972,90]}, {"5544":[2001,92]}, {"5547":[1975,70]}, {"5551":[1975,67]}, {"5556":[1999,29]}, {"5567":[1970,80]}, {"5579":[1981,8]}, {"5615":[1972,15]}, {"5631":[1954,81]}, {"5648":[1953,2]}, {"5652":[1950,63]}, {"5653":[1957,98]}, {"5702":[1959,81]}, {"5766":[1956,21]}, {"5768":[1959,47]}, {"5790":[1959,96]}, {"5794":[1957,3]}, {"5798":[1958,94]}, {"5805":[1979,63]}, {"5808":[1958,14]}, {"5810":[2001,5]}, {"5816":[1958,31]}, {"5825":[1959,61]}, {"5837":[1980,5]}, {"5838":[1995,34]}, {"5840":[1974,41]}, {"5841":[1973,35]}, {"5844":[1973,78]}, {"5882":[1954,90]}, {"5883":[1961,63]}, {"5893":[1981,31]}, {"5894":[1981,31]}, {"5936":[1981,30]}, {"5957":[1970,42]}, {"5976":[1992,41]}, {"5983":[1969,21]}, {"5987":[1970,90]}, {"5989":[1975,48]}, {"5990":[1978,37]}, {"6001":[1966,37]}, {"6002":[1969,92]}, {"6022":[1982,77]}, {"6023":[1988,39]}, {"6024":[1977,1]}, {"6042":[1981,2]}, {"6049":[1977,99]}, {"6050":[1956,10]}, {"6052":[1962,77]}, {"6053":[1964,29]}, {"6063":[1957,65]}, {"6065":[1956,15]}, {"6067":[1966,91]}, {"6069":[1987,69]}, {"6073":[1983,53]}, {"6091":[1950,95]}, {"6135":[1962,82]}, {"6151":[1978,18]}, {"6156":[1961,6]}, {"6164":[1954,60]}, {"6169":[1959,54]}, {"6185":[1958,25]}, {"6191":[2005,86]}, {"6239":[1970,24]}, {"6294":[1994,30]}, {"6301":[1952,17]}, {"6354":[2003,3]}, {"6370":[1987,55]}, {"6389":[1961,32]}, {"6398":[1965,15]}, {"6404":[1961,1]}, {"6426":[1992,100]}, {"6430":[1993,32]}, {"6444":[2010,32]}, {"6448":[1958,74]}, {"6476":[1955,12]}, {"6493":[1992,64]}, {"6523":[2007,76]}, {"6528":[1981,18]}, {"6563":[1960,12]}, {"6592":[1998,60]}, {"6689":[1960,20]}, {"6703":[1968,75]}, {"6722":[1955,12]}, {"6773":[1953,100]}, {"6903":[1958,24]}, {"6910":[1962,61]}, {"6961":[1981,35]}, {"6975":[1964,14]}, {"7030":[1958,34]}, {"7038":[1979,26]}, {"7083":[1970,91]}, {"7123":[1957,83]}, {"7228":[1971,2]}, {"7268":[1984,22]}, {"7409":[1994,96]}, {"7418":[1959,98]}, {"7660":[1950,80]}, {"7742":[1999,59]}, {"7790":[1956,14]}, {"7818":[1966,2]}, {"7837":[1972,42]}, {"7838":[1984,15]}, {"7914":[1960,29]}, {"7923":[1950,74]}, {"7925":[1993,87]}, {"7961":[1952,53]}, {"7965":[1959,87]}, {"8006":[1951,4]}, {"8016":[1997,75]}, {"8045":[1987,6]}, {"8103":[2010,74]}, {"8145":[1986,4]}, {"8192":[1970,67]}, {"8216":[1974,35]}, {"8243":[2008,98]}, {"8328":[1976,15]}, {"8364":[1970,65]}, {"8441":[1961,14]}, {"8448":[1962,90]}, {"8557":[1958,48]}, {"8678":[1967,74]}, {"8763":[1955,12]}, {"8855":[1962,74]}, {"8862":[1978,9]}, {"8884":[2009,2]}, {"8887":[2010,18]}, {"8915":[1974,74]}, {"8967":[1974,74]}, {"8999":[1963,3]}, {"9025":[1972,84]}, {"9026":[1971,78]}, {"9177":[1966,50]}, {"9192":[1961,2]}, {"9203":[1960,85]}, {"9224":[1970,69]}, {"9254":[1976,55]}, {"9264":[1973,95]}, {"9266":[1974,31]}, {"9294":[1994,2]}, {"9298":[1969,23]}, {"9322":[1952,81]}, {"9369":[1958,1]}, {"9618":[2007,25]}, {"9652":[2009,63]}, {"9673":[1965,64]}, {"9699":[1987,10]}, {"9883":[1973,67]}, {"9914":[1991,58]}, {"9918":[2010,6]}, {"9945":[1997,32]}, {"9954":[1959,80]}, {"9969":[1966,65]}, {"9983":[1987,14]}, {"9999":[1999,59]}, {"10052":[1969,64]}, {"10075":[1967,49]}, {"10118":[1961,18]}, {"10226":[2009,11]}, {"10234":[1979,14]}, {"10247":[1987,74]}, {"10260":[1970,82]}, {"10265":[1961,7]}, {"10269":[1989,39]}, {"10318":[1978,20]}, {"10320":[1970,35]}, {"10321":[1971,2]}, {"10395":[1968,33]}, {"10466":[1970,12]}, {"10571":[1985,84]}, {"10602":[1963,28]}, {"10604":[1965,25]}, {"10636":[1951,15]}, {"10656":[2010,42]}, {"10663":[1962,1]}, {"10703":[1969,25]}, {"10767":[1959,3]}, {"11007":[1994,31]}, {"11050":[1971,51]}, {"11103":[1986,39]}, {"11150":[1970,36]}, {"11158":[1961,14]}, {"11281":[2009,7]}, {"11284":[1967,41]}, {"11604":[2003,92]}, {"11665":[1992,64]}, {"11667":[1993,18]}, {"11733":[1964,41]}, {"11756":[1951,25]}, {"11767":[1973,66]}, {"11781":[1965,62]}, {"11785":[1979,20]}, {"11807":[1958,26]}, {"11857":[1958,22]}, {"12187":[2009,60]}, {"12275":[1959,98]}, {"12514":[2007,76]}, {"12899":[1958,2]}]''') + entries = json.loads('''[{"1024":[2009,48]}, {"1028":[1961,56]}, {"1034":[1986,9]}, {"1054":[1971,12]}, {"1070":[1982,34]}, {"1081":[2006,55]}, {"1099":[1973,11]}, {"1105":[1952,13]}, {"1347":[2003,30]}, {"1384":[1993,27]}, {"1402":[2009,50]}, {"1459":[1969,2]}, {"1722":[1982,2]}, {"1742":[1959,3]}, {"1904":[1964,9]}, {"1905":[1967,18]}, {"1906":[1967,20]}, {"1907":[1964,8]}, {"1912":[1977,44]}, {"1922":[1954,94]}, {"1930":[1999,6]}, {"1934":[1985,10]}, {"1940":[1970,3]}, {"2045":[1961,7]}, {"2049":[1961,12]}, {"2125":[1959,19]}, {"2167":[1965,88]}, {"2170":[1957,68]}, {"2374":[1963,14]}, {"2387":[1950,4]}, {"2405":[1974,2]}, {"2431":[1985,15]}, {"2447":[1960,3]}, {"2448":[1982,28]}, {"2462":[1951,33]}, {"2519":[1965,7]}, {"2521":[1978,23]}, {"2523":[1955,16]}, {"2533":[1976,52]}, {"2546":[1952,50]}, {"2665":[2004,2]}, {"2688":[1973,45]}, {"2709":[2009,5]}, {"2729":[2004,2]}, {"2735":[2009,60]}, {"2742":[1970,98]}, {"2774":[1967,10]}, {"2776":[1952,46]}, {"2777":[1969,47]}, {"2833":[1957,34]}, {"2834":[1957,37]}, {"2838":[1956,8]}, {"2967":[1973,31]}, {"2984":[1987,14]}, {"2985":[1977,41]}, {"2994":[1967,76]}, {"3012":[1963,28]}, {"3153":[1963,5]}, {"3154":[1963,5]}, {"3155":[1965,9]}, {"3157":[1978,36]}, {"3195":[1975,65]}, {"3218":[1992,6]}, {"3226":[1963,58]}, {"3229":[1958,42]}, {"3232":[1979,10]}, {"3241":[1972,3]}, {"3242":[1977,12]}, {"3243":[1970,45]}, {"3275":[1965,8]}, {"3281":[1954,28]}, {"3282":[1991,15]}, {"3285":[1974,33]}, {"3299":[1956,57]}, {"3300":[1953,8]}, {"3301":[1957,47]}, {"3311":[1954,9]}, {"3323":[1954,28]}, {"3324":[1987,14]}, {"3379":[1962,25]}, {"3402":[1961,2]}, {"3409":[1972,12]}, {"3420":[1954,78]}, {"3452":[1971,85]}, {"3466":[1962,46]}, {"3477":[1960,13]}, {"3484":[1983,59]}, {"3532":[1961,15]}, {"3548":[1970,20]}, {"3554":[1981,53]}, {"3558":[1981,62]}, {"3668":[1953,73]}, {"3681":[1954,2]}, {"3687":[1957,11]}, {"3688":[1957,11]}, {"3689":[1962,15]}, {"3690":[1955,17]}, {"3717":[1955,5]}, {"3718":[1962,4]}, {"3719":[1958,48]}, {"3732":[1954,95]}, {"3749":[1953,3]}, {"3755":[1970,93]}, {"3764":[1958,44]}, {"3765":[1974,94]}, {"3772":[1973,71]}, {"3784":[1961,95]}, {"3785":[1950,17]}, {"3786":[1951,15]}, {"3821":[1954,66]}, {"3834":[1963,3]}, {"3845":[1985,65]}, {"3866":[1957,48]}, {"3871":[1950,82]}, {"3911":[1967,23]}, {"3928":[1959,52]}, {"3979":[1989,70]}, {"3980":[1963,10]}, {"4016":[1951,34]}, {"4022":[1956,98]}, {"4044":[1952,54]}, {"4051":[1984,56]}, {"4073":[1980,90]}, {"4117":[1976,1]}, {"4139":[1951,5]}, {"4252":[1950,14]}, {"4286":[1959,12]}, {"4288":[1965,43]}, {"4294":[1961,75]}, {"4318":[1960,10]}, {"4320":[1961,5]}, {"4335":[1966,62]}, {"4341":[1970,41]}, {"4343":[1964,2]}, {"4353":[1961,55]}, {"4354":[1962,21]}, {"4356":[1955,4]}, {"4357":[1966,23]}, {"4400":[1967,52]}, {"4438":[1965,71]}, {"4439":[1951,9]}, {"4462":[1968,42]}, {"4464":[1964,27]}, {"4470":[1987,38]}, {"4492":[1961,31]}, {"4494":[1952,65]}, {"4503":[1953,91]}, {"4504":[1953,35]}, {"4505":[1953,35]}, {"4514":[1970,51]}, {"4537":[1954,21]}, {"4538":[1956,4]}, {"4567":[1962,93]}, {"4575":[1960,19]}, {"4576":[1962,27]}, {"4618":[1998,13]}, {"4621":[1954,67]}, {"4643":[1970,2]}, {"4645":[1966,44]}, {"4649":[1953,40]}, {"4656":[1974,86]}, {"4657":[1984,25]}, {"4679":[1966,33]}, {"4728":[1974,56]}, {"4732":[2005,43]}, {"4740":[1964,33]}, {"4741":[1963,24]}, {"4742":[1970,18]}, {"4750":[1968,2]}, {"4773":[1971,98]}, {"4791":[1966,28]}, {"4795":[1983,96]}, {"4807":[1956,81]}, {"4837":[1950,5]}, {"4889":[1967,84]}, {"4897":[1957,67]}, {"4963":[1991,58]}, {"4978":[1959,51]}, {"4979":[1956,49]}, {"5002":[1956,3]}, {"5023":[1974,60]}, {"5025":[1977,22]}, {"5195":[1954,1]}, {"5228":[1963,65]}, {"5280":[1970,64]}, {"5296":[1955,62]}, {"5300":[1952,43]}, {"5346":[1955,52]}, {"5356":[1981,29]}, {"5367":[1964,54]}, {"5376":[1970,48]}, {"5391":[1982,31]}, {"5394":[1976,30]}, {"5431":[1974,8]}, {"5432":[1962,6]}, {"5480":[1971,49]}, {"5489":[1969,85]}, {"5506":[1960,98]}, {"5522":[1968,50]}, {"5541":[1972,90]}, {"5544":[2001,92]}, {"5547":[1975,70]}, {"5551":[1975,67]}, {"5556":[1999,29]}, {"5567":[1970,80]}, {"5579":[1981,8]}, {"5615":[1972,15]}, {"5631":[1954,81]}, {"5648":[1953,2]}, {"5652":[1950,63]}, {"5653":[1957,98]}, {"5702":[1959,81]}, {"5766":[1956,21]}, {"5768":[1959,47]}, {"5790":[1959,96]}, {"5794":[1957,3]}, {"5798":[1958,94]}, {"5805":[1979,63]}, {"5808":[1958,14]}, {"5810":[2001,5]}, {"5816":[1958,31]}, {"5825":[1959,61]}, {"5837":[1980,5]}, {"5838":[1995,34]}, {"5840":[1974,41]}, {"5841":[1973,35]}, {"5844":[1973,78]}, {"5882":[1954,90]}, {"5883":[1961,63]}, {"5893":[1981,31]}, {"5894":[1981,31]}, {"5936":[1981,30]}, {"5957":[1970,42]}, {"5976":[1992,41]}, {"5983":[1969,21]}, {"5987":[1970,90]}, {"5989":[1975,48]}, {"5990":[1978,37]}, {"6001":[1966,37]}, {"6002":[1969,92]}, {"6022":[1982,77]}, {"6023":[1988,39]}, {"6024":[1977,1]}, {"6042":[1981,2]}, {"6049":[1977,99]}, {"6050":[1956,10]}, {"6052":[1962,77]}, {"6053":[1964,29]}, {"6063":[1957,65]}, {"6065":[1956,15]}, {"6067":[1966,91]}, {"6069":[1987,69]}, {"6073":[1983,53]}, {"6091":[1950,95]}, {"6135":[1962,82]}, {"6151":[1978,18]}, {"6156":[1961,6]}, {"6164":[1954,60]}, {"6169":[1959,54]}, {"6185":[1958,25]}, {"6191":[2005,86]}, {"6239":[1970,24]}, {"6294":[1994,30]}, {"6301":[1952,17]}, {"6354":[2003,3]}, {"6370":[1987,55]}, {"6389":[1961,32]}, {"6398":[1965,15]}, {"6404":[1961,1]}, {"6426":[1992,100]}, {"6430":[1993,32]}, {"6444":[2010,32]}, {"6448":[1958,74]}, {"6476":[1955,12]}, {"6493":[1992,64]}, {"6523":[2007,76]}, {"6528":[1981,18]}, {"6563":[1960,12]}, {"6592":[1998,60]}, {"6689":[1960,20]}, {"6703":[1968,75]}, {"6722":[1955,12]}, {"6773":[1953,100]}, {"6903":[1958,24]}, {"6910":[1962,61]}, {"6961":[1981,35]}, {"6975":[1964,14]}, {"7030":[1958,34]}, {"7038":[1979,26]}, {"7083":[1970,91]}, {"7123":[1957,83]}, {"7228":[1971,2]}, {"7268":[1984,22]}, {"7409":[1994,96]}, {"7418":[1959,98]}, {"7660":[1950,80]}, {"7742":[1999,59]}, {"7790":[1956,14]}, {"7818":[1966,2]}, {"7837":[1972,42]}, {"7838":[1984,15]}, {"7914":[1960,29]}, {"7923":[1950,74]}, {"7925":[1993,87]}, {"7961":[1952,53]}, {"7965":[1959,87]}, {"8006":[1951,4]}, {"8016":[1997,75]}, {"8045":[1987,6]}, {"8103":[2010,74]}, {"8145":[1986,4]}, {"8192":[1970,67]}, {"8216":[1974,35]}, {"8243":[2008,98]}, {"8328":[1976,15]}, {"8364":[1970,65]}, {"8441":[1961,14]}, {"8448":[1962,90]}, {"8557":[1958,48]}, {"8678":[1967,74]}, {"8763":[1955,12]}, {"8855":[1962,74]}, {"8862":[1978,9]}, {"8884":[2009,2]}, {"8887":[2010,18]}, {"8915":[1974,74]}, {"8967":[1974,74]}, {"8999":[1963,3]}, {"9025":[1972,84]}, {"9026":[1971,78]}, {"9177":[1966,50]}, {"9192":[1961,2]}, {"9203":[1960,85]}, {"9224":[1970,69]}, {"9254":[1976,55]}, {"9264":[1973,95]}, {"9266":[1974,31]}, {"9294":[1994,2]}, {"9298":[1969,23]}, {"9322":[1952,81]}, {"9369":[1958,1]}, {"9618":[2007,25]}, {"9652":[2009,63]}, {"9673":[1965,64]}, {"9699":[1987,10]}, {"9883":[1973,67]}, {"9914":[1991,58]}, {"9918":[2010,6]}, {"9945":[1997,32]}, {"9954":[1959,80]}, {"9969":[1966,65]}, {"9983":[1987,14]}, {"9999":[1999,59]}, {"10052":[1969,64]}, {"10075":[1967,49]}, {"10118":[1961,18]}, {"10226":[2009,11]}, {"10234":[1979,14]}, {"10247":[1987,74]}, {"10260":[1970,82]}, {"10265":[1961,7]}, {"10269":[1989,39]}, {"10318":[1978,20]}, {"10320":[1970,35]}, {"10321":[1971,2]}, {"10395":[1968,33]}, {"10466":[1970,12]}, {"10571":[1985,84]}, {"10602":[1963,28]}, {"10604":[1965,25]}, {"10636":[1951,15]}, {"10656":[2010,42]}, {"10663":[1962,1]}, {"10703":[1969,25]}, {"10767":[1959,3]}, {"11007":[1994,31]}, {"11050":[1971,51]}, {"11103":[1986,39]}, {"11150":[1970,36]}, {"11158":[1961,14]}, {"11281":[2009,7]}, {"11284":[1967,41]}, {"11604":[2003,92]}, {"11665":[1992,64]}, {"11667":[1993,18]}, {"11733":[1964,41]}, {"11756":[1951,25]}, {"11767":[1973,66]}, {"11781":[1965,62]}, {"11785":[1979,20]}, {"11807":[1958,26]}, {"11857":[1958,22]}, {"12187":[2009,60]}, {"12275":[1959,98]}, {"12514":[2007,76]}, {"12899":[1958,2]}]''') # noqa: E501 entryDict = {} for d in entries: - sid = d.keys()[0]; + sid = d.keys()[0] entryDict[int(sid)] = d[sid] @@ -84,20 +83,20 @@ def nipsBuild(useOurExtractors=True, buildSet=1, evaluationMethod='coarse'): for wf in halfOfData: # taking half the data here.... to get the other set do [198:] year = entryDict[wf][0] - ## ignore 1960s and 1970s pieces... + # ignore 1960s and 1970s pieces... if year < 1961 or year >= 1981: fn = leadsheetDir + str(wf) + '.mxl' - print (fn, year, entryDict[wf][1]) + print(fn, year, entryDict[wf][1]) s = converter.parse(fn) title_id = s.metadata.title - #cv = year # if not using coarse but instead using the exact year as the class value + # cv = year # if not using coarse but instead using the exact year as the class value if evaluationMethod == 'coarse': - #coarse evaluation (only "old" or "new") + # coarse evaluation (only "old" or "new") if year < 1961: - cv = "old" + cv = 'old' else: - cv = "new" + cv = 'new' else: # fine grained evaluations cv = year # if not using coarse but instead using the exact year as the class value @@ -149,8 +148,8 @@ def nipsEvalCoarse(): data1 = orange.data.table.Table('d:/docs/research/music21/nips-2011/year5-ourExtractors-1.tab') data2 = orange.data.table.Table('d:/docs/research/music21/nips-2011/year6-ourExtractors-2.tab') - #data1 = orange.ExampleTable('d:/docs/research/music21/nips-2011/year7-midi-only-1.tab') - #data2 = orange.ExampleTable('d:/docs/research/music21/nips-2011/year8-midi-only-2.tab') + # data1 = orange.ExampleTable('d:/docs/research/music21/nips-2011/year7-midi-only-1.tab') + # data2 = orange.ExampleTable('d:/docs/research/music21/nips-2011/year8-midi-only-2.tab') learners = {} @@ -172,16 +171,19 @@ def nipsEvalCoarse(): c = classifier(matchData[i]) if c != matchData[i].getclass(): mismatch += 1 - print('%s %s: misclassified %s/%s of %s' % (cStr, cName, mismatch, len(matchData), matchStr)) + print('%s %s: misclassified %s/%s of %s' + % (cStr, cName, mismatch, len(matchData), matchStr)) def getLeadsheetDatesFromBillboard(): ''' - script to generate a json list matching leadsheet files to their dates. script compiles list of Billboard + script to generate a json list matching leadsheet files to their dates. + Compiles list of Billboard top 100 songs from jamrockentertainment.com, then uses this dictionary to match up titles from leadsheet file. Last updated: 10/6/2011, Beth Hadley - Both the dictionary generated by scraping the website and the json output has been copied into a text file - and saved so this script shouldn't need to be re-run unless a change has been made. See text file. + Both the dictionary generated by scraping the website and the json output + has been copied into a text file and saved so this script shouldn't need to be + re-run unless a change has been made. See text file. ''' import os try: @@ -194,8 +196,10 @@ def getLeadsheetDatesFromBillboard(): # Code below scrapes this website: http://www.jamrockentertainment.com/billboard-music-top-100-songs-listed-by-year.html # for their listing of the Billboard 100 songs from 1950 to 2010. # -# Generates an unordered dictionary with the corresponding data: each key in the dictionary is the year, each value is a list -# of tuples, each tuple includes the song title at index 0 and group/artist at index 1. The list is ordered starting at +# Generates an unordered dictionary with the corresponding data: each key in +# the dictionary is the year, each value is a list of tuples, each tuple +# includes the song title at index 0 and group/artist at index 1. +# The list is ordered starting at # element 0 by rank, starting with number 1. DICT = {} tempList = [] @@ -203,8 +207,10 @@ def getLeadsheetDatesFromBillboard(): UPPERLIMIT = 2011 dates = range(LOWERLIMIT, UPPERLIMIT) for i in dates: - if i !=2008 and i != 2009: - address = 'http://www.jamrockentertainment.com/billboard-music-top-100-songs-listed-by-year/top-100-songs-%s.html' % i + if i != 2008 and i != 2009: + address = ('http://www.jamrockentertainment.com/' + 'billboard-music-top-100-songs-listed-by-year/' + 'top-100-songs-%s.html' % i) url = urlopen(address) html = url.read() soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES) @@ -217,21 +223,23 @@ def getLeadsheetDatesFromBillboard(): words.pop(0) for a in words: a.strip() - pretty = pretty +" "+ a - if song == True: + pretty = pretty + ' ' + a + if song: try: j = float(x) continue - except: + except (ValueError, TypeError): song = False y = pretty continue - if song == False: + if not song: t = y, pretty tempList.append( t ) song = True elif i == 2008: - address = 'http://www.jamrockentertainment.com/billboard-music-top-100-songs-listed-by-year/top-100-songs-%s.html' % i + address = ('http://www.jamrockentertainment.com/' + 'billboard-music-top-100-songs-listed-by-year/' + 'top-100-songs-%s.html' % i) url = urlopen(address) html = url.read() soup = BeautifulSoup(html) @@ -241,11 +249,11 @@ def getLeadsheetDatesFromBillboard(): groups = [] for strong in trs: x = strong.find(text=True) - if " " in str(x): + if ' ' in str(x): x = x.strip(' ') x = x.strip() if cnt < 100: - retstring = "" + retstring = '' loops = 1 for letter in x: if loops > 3: @@ -266,8 +274,10 @@ def getLeadsheetDatesFromBillboard(): group = group.replace('?', 'H') t = title, group tempList.append( t ) - else: #i=2009 - address = 'http://www.jamrockentertainment.com/billboard-music-top-100-songs-lististed-by-year/top-100-songs-%s.html' % i + else: # i=2009 + address = ('http://www.jamrockentertainment.com/' + 'billboard-music-top-100-songs-lististed-by-year/' + 'top-100-songs-%s.html' % i) url = urlopen(address) html = url.read() soup = BeautifulSoup(html, convertEntities=BeautifulSoup.HTML_ENTITIES) @@ -281,7 +291,7 @@ def getLeadsheetDatesFromBillboard(): try: j = float(x) continue - except: + except (ValueError, TypeError): if cnt < 100: groups.append(str(x)) else: @@ -295,16 +305,14 @@ def getLeadsheetDatesFromBillboard(): DICT[str(i)] = copy.deepcopy(tempList) tempList = [] - #print DICT - - + # print(DICT) LOWERLIMIT = 1000 UPPERLIMIT = 12938 indexValues = range(LOWERLIMIT, UPPERLIMIT) matches = 0 rank = 0 - outputjson = '\'[' + outputjson = "'[" for i in indexValues: @@ -312,39 +320,45 @@ def getLeadsheetDatesFromBillboard(): fn = fn.replace(' ', '0') dst = os.path.join(DIR, fn) dates = [] - print (dst) + print(dst) if os.path.exists(dst): piece = converter.parse(dst) for year in DICT: - for title, group in DICT[year]: - if piece.metadata.movementName.lower() == str(title).lower() : #or piece.metadata.composer == str(a): - #print "FOUND by title! Name", piece.metadata.movementName, "Composer", piece.metadata.composer, str(year) - #print dst + for title, _group in DICT[year]: + # or piece.metadata.composer == str(a): + if piece.metadata.movementName.lower() == str(title).lower(): + # print("Found by title! Name", + # piece.metadata.movementName, + # "Composer", piece.metadata.composer, str(year)) + # print(dst) dates.append(int(year)) if len(dates) > 0: - date = min(dates) #date of entry + date = min(dates) # date of entry tempList = DICT[str(date)] for x in tempList: for e in x: if e.lower() == piece.metadata.movementName.lower(): - rank = (tempList.index(x) + 1 ) #position on Billboard 100 + rank = (tempList.index(x) + 1 ) # position on Billboard 100 - #print "Title:", piece.metadata.movementName, " Composer:", piece.metadata.composer, " Date:", date, " Position on Billboard:", rank - #print "Title:", piece.metadata.movementName, " Date:", date + # print('Title:', piece.metadata.movementName, + # ' Composer:', piece.metadata.composer, + # ' Date:', date, + # ' Position on Billboard:', rank) + # print('Title:', piece.metadata.movementName, ' Date:', date) matches = matches + 1 - outputjson = outputjson + '{\"%s":[%s,%s]}, ' % (i, date, rank) + outputjson = outputjson + '{"%s":[%s,%s]}, ' % (i, date, rank) if (i % 500) == 0: j = ((i - 1000) / (11938.00)) * 100.00 - print ('%s %%' %round(j, 2)) - print (outputjson) + print('%s %%' % round(j, 2)) + print(outputjson) foo = len(outputjson) - 2 - outputjson = outputjson[0:foo] + ']\'' - print (outputjson) - print ("FINISHED! Found this many matches: ", matches) + outputjson = outputjson[0:foo] + "]'" + print(outputjson) + print('Finished. Found this many matches: ', matches) def probabilityOfChance(): ''' @@ -380,7 +394,7 @@ def getPFromExperiment(): new = 1 avg = 0.0 for unused_trials in range(10000): - #generating the answerKey + # generating the answerKey answerKey = [] for i in range(NUM_OLD_PIECES): answerKey.append(old) @@ -388,19 +402,19 @@ def getPFromExperiment(): answerKey.append(new) random.shuffle(answerKey) - #generating the answers, then comparing them - #to answerKey and calculating the percentage - #guessed correctly + # generating the answers, then comparing them + # to answerKey and calculating the percentage + # guessed correctly numCorrect = 0.0 guessesOld = [] guessesNew = [] for answer in answerKey: guess = random.randint(0, 1) - #the algorithm is smart - it knows - #there is a maximum number of times it - #can guess old vs. new, and if it - #has reached that maximum, - #it won't guess that value + # the algorithm is smart - it knows + # there is a maximum number of times it + # can guess old vs. new, and if it + # has reached that maximum, + # it won't guess that value if guess == 0: guessesOld.append(guess) else: @@ -410,36 +424,36 @@ def getPFromExperiment(): elif len(guessesNew) > NUM_NEW_PIECES: guess = old if answer == guess: - numCorrect +=1 + numCorrect += 1 avg += numCorrect / TOTAL_TRIALS p = avg / 10000.0 return p - def BinomialCoefficient(n,k): + def BinomialCoefficient(n, k): if n > k: - b = factorial(n) / (factorial(k)*factorial(n-k)) + b = factorial(n) / (factorial(k) * factorial(n - k)) return b - def BinomialProbability(n,k,p,q): - nchoosek = BinomialCoefficient(n,k) - ptothek = pow(p,k) - qtothenminusk = pow(q, (n-k) ) + def BinomialProbability(n, k, p, q): + nchoosek = BinomialCoefficient(n, k) + ptothek = pow(p, k) + qtothenminusk = pow(q, (n - k) ) return nchoosek * ptothek * qtothenminusk - k = 148 #number of times music21 found correct date (successes) - n = TOTAL_TRIALS #total number of trials + k = 148 # number of times music21 found correct date (successes) + n = TOTAL_TRIALS # total number of trials p = getPFromExperiment() - print ('The probability of a successful guess, as calculated by previous experiment', p) - q = 1-p + print('The probability of a successful guess, as calculated by previous experiment', p) + q = 1 - p prob = 0.0 - #Probability of getting 148 or more correct (sum - #the probabilty as k increses from 148 to 214) - for k in range(k,TOTAL_TRIALS): - prob = prob + BinomialProbability(n,k,p,q) + # Probability of getting 148 or more correct (sum + # the probabilty as k increses from 148 to 214) + for ki in range(k, TOTAL_TRIALS): + prob = prob + BinomialProbability(n, ki, p, q) - print ('The probability that the computer would guess correctly 69% or more of the time', prob) + print('The probability that the computer would guess correctly 69% or more of the time', prob) if __name__ == '__main__': - #nipsBuild() + # nipsBuild() nipsEvalCoarse() diff --git a/music21_tools/chant/chant.py b/music21_tools/chant/chant.py index 246d05b..5405b46 100644 --- a/music21_tools/chant/chant.py +++ b/music21_tools/chant/chant.py @@ -8,8 +8,6 @@ # License: BSD, see license.txt # ------------------------------------------------------------------------------ ''' -``ALPHA MODULE``: Not directly supported by cuthbertLab. - Classes and Tools for converting Music21 Streams to Gregorio .gabc Requires the amazing Gregoio library: http://gregorio-project.github.io , which itself @@ -27,23 +25,22 @@ from music21 import stream from music21 import environment -_MOD = "chant.py" +_MOD = 'chant.py' environLocal = environment.Environment(_MOD) - def fromStream(inputStream): if inputStream.metadata is not None: incipit = inputStream.metadata.title else: - incipit = "Benedicamus Domino" + incipit = 'Benedicamus Domino' out = '' out += 'name: ' + incipit + ';\n' - out += "%%\n" + out += '%%\n' return out class GregorianStream(stream.Stream): r''' - + >>> from music21_tools.chant.chant import GregorianStream, GregorianNote >>> s = GregorianStream() >>> s.append(clef.AltoClef()) >>> n = GregorianNote("C4") @@ -54,43 +51,43 @@ class GregorianStream(stream.Stream): >>> s.append(n) >>> s.toGABCText() '(c3) Po(ho)\n' - ''' def toGABCText(self): currentClef = None - outLine = "" + outLine = '' startedSyllable = False for e in self: if hasattr(e, 'isNote') and e.isNote is True: - if e.lyrics and e.lyrics[0] != "": + if e.lyrics and e.lyrics[0] != '': if startedSyllable: - outLine += ")" + outLine += ')' startedSyllable = False if e.lyrics[0].syllabic in ['begin', 'single']: - outLine += " " + outLine += ' ' outLine += e.lyrics[0].text - outLine += "(" + outLine += '(' startedSyllable = True outLine += e.toGABC(useClef=currentClef) elif 'Clef' in e.classes: if startedSyllable: - outLine += ") " + outLine += ') ' startedSyllable = False currentClef = e outLine += self.clefToGABC(e) + ' ' if startedSyllable: - outLine += ")\n" + outLine += ')\n' return outLine def clefToGABC(self, clefIn): ''' + >>> from music21_tools.chant.chant import GregorianStream >>> s = GregorianStream() >>> c = clef.AltoClef() >>> s.clefToGABC(c) '(c3)' ''' - return "(" + clefIn.sign.lower() + str(clefIn.line) + ")" + return '(' + clefIn.sign.lower() + str(clefIn.line) + ')' class GregorianNote(note.Note): @@ -99,13 +96,11 @@ class GregorianNote(note.Note): contains extra attributes which represent the interpretation or graphical representation of the note. - Most of the attributes default to False. Exceptions are noted below. - Example: a very special note. - + >>> from music21_tools.chant.chant import GregorianNote >>> n = GregorianNote("C4") >>> n.liquescent = True >>> n.quilisma = True @@ -149,7 +144,7 @@ def __init__(self, *arguments, **keywords): def toGABC(self, useClef=None, nextNote=None): letter = self.toBasicGABC(useClef) if self.debilis: - letter = "-" + letter + letter = '-' + letter if self.inclinatum: letter = letter.upper() @@ -165,40 +160,40 @@ def toGABC(self, useClef=None, nextNote=None): letter += 'w' elif self.stropha: letter += 's' - if self.liquescent != False: - #if nextNote is not None: - # if nextNote.diatonicNoteNum > self.diatonicNoteNum: - # letter += '<' - # else: - # letter += '>' + if self.liquescent: + # if nextNote is not None: + # if nextNote.diatonicNoteNum > self.diatonicNoteNum: + # letter += '<' + # else: + # letter += '>' if self.liquescent == 'ascending': letter += '<' elif self.liquescent == 'descending': letter += '<' else: letter += '~' - if self.punctumMora != False: + if self.punctumMora: if self.punctumMora == 1: letter += '.' elif self.punctumMora == 2: letter += '..' else: raise ChantException('unable to do punctumMora with more than two notes') - if self.episema != False: + if self.episema: if self.episema == 'vertical': - letter += '\'' + letter += "'" elif self.episema == 'below': letter += '_0' else: letter += '_' - if self.breakNeume != False: - letter += "!" + if self.breakNeume: + letter += '!' - if self.choralSign != False: - letter += "[cs:" + self.choralSign + "]" + if self.choralSign: + letter += '[cs:' + self.choralSign + ']' if self.polyphonic: - letter = "{" + letter + "}" + letter = '{' + letter + '}' return letter @@ -208,6 +203,7 @@ def toBasicGABC(self, useClef=None): see http://home.gna.org/gregorio/gabc/ for more details. 'd' = lowest line + >>> from music21_tools.chant.chant import GregorianNote >>> n = GregorianNote("C4") >>> c = clef.AltoClef() >>> n.toBasicGABC(c) @@ -230,7 +226,7 @@ def toBasicGABC(self, useClef=None): if not hasattr(useClef, 'lowestLine'): raise ChantException( - "useClef has to define the diatonicNoteNum representing the lowest line") + 'useClef has to define the diatonicNoteNum representing the lowest line') stepsAboveLowestLine = inNote.pitch.diatonicNoteNum - useClef.lowestLine asciiNote = stepsAboveLowestLine + asciiD @@ -238,17 +234,17 @@ def toBasicGABC(self, useClef=None): if asciiNote < asciiA: if usedDefaultClef is True: raise ChantException( - "note is too low for the default clef (AltoClef), choose a lower one") + 'note is too low for the default clef (AltoClef), choose a lower one') else: raise ChantException( - "note is too low for the clef (%s), choose a lower one" % str(useClef)) + 'note is too low for the clef (%s), choose a lower one' % str(useClef)) elif asciiNote > asciiM: if usedDefaultClef is True: raise ChantException( - "note is too high for the default clef (AltoClef), choose a higher one") + 'note is too high for the default clef (AltoClef), choose a higher one') else: raise ChantException( - "note is too high for the clef (%s), choose a higher one" % str(useClef)) + 'note is too high for the clef (%s), choose a higher one' % str(useClef)) else: return chr(asciiNote) @@ -258,7 +254,7 @@ def _getFill(self): def _setFill(self, value): if value not in self.fillDic: - raise ChantException("Cannot set fill to value %s." % value) + raise ChantException('Cannot set fill to value %s.' % value) self._fill = value fill = property(_getFill, _setFill, doc='''Sets the @@ -320,30 +316,31 @@ class BaseScoreConverter: def __init__(self): self.environLocal = environLocal self.gregorioConverter = '/usr/local/bin/gregorio' - self.gregorioOptions = "" + self.gregorioOptions = '' self.gregorioCommand = None self.latexConverter = '/usr/texbin/lualatex' self.latexOptions = '--interaction=nonstopmode' - self.score = "" - self.incipit = "" - self.mode = "" + self.score = '' + self.incipit = '' + self.mode = '' self.paperType = None def writeFile(self, text=None): ''' + >>> from music21_tools.chant.chant import BaseScoreConverter >>> bsc = BaseScoreConverter() >>> filePath = bsc.writeFile('hello') - >>> assert(str(filePath).endswith('.gabc')) #_DOCS_HIDE - >>> filePath = '/var/folders/k9/T/music21/tmpekHFCr.gabc' #_DOCS_HIDE + >>> assert(str(filePath).endswith('.gabc')) # _DOCS_HIDE + >>> filePath = '/var/folders/k9/T/music21/tmpekHFCr.gabc' # _DOCS_HIDE >>> filePath '/var/folders/k9/T/music21/tmpekHFCr.gabc' ''' - if text is None or text == "": + if text is None or text == '': raise ChantException('Cannot write file if there is no data') fp = self.environLocal.getTempFile('.gabc') f = open(fp, 'w') @@ -357,11 +354,12 @@ def launchGregorio(self, fp=None): converts a .gabc file to LaTeX using the gregorio converter. Returns the filename with .tex substituted for .gabc + >>> from music21_tools.chant.chant import BaseScoreConverter >>> bsc = BaseScoreConverter() >>> fn = '~cuthbert/Library/Gregorio/examples/Populas.gabc' - >>> #_DOCS_SHOW newFp = bsc.launchGregorio(fn) - >>> #_DOCS_SHOW bsc.gregorioCommand - >>> 'open -a"/usr/local/bin/gregorio" ' + fn #_DOCS_HIDE + >>> # _DOCS_SHOW newFp = bsc.launchGregorio(fn) + >>> # _DOCS_SHOW bsc.gregorioCommand + >>> 'open -a"/usr/local/bin/gregorio" ' + fn # _DOCS_HIDE 'open -a"/usr/local/bin/gregorio" ~cuthbert/Library/Gregorio/examples/Populas.gabc' @@ -433,7 +431,8 @@ def __init__(self): % If you use usual TeX fonts, here is a starting point: \\usepackage{times} -% to change the font to something better, you can install the kpfonts package (if not already installed). To do so +% to change the font to something better, you can install the kpfonts package + % (if not already installed). To do so % go open the "TeX Live Manager" in the Menu Start->All Programs->TeX Live 2010 % here we begin the document @@ -469,7 +468,7 @@ def substituteInfo(self, converter): r''' Puts the correct information into the TeXWrapper for the document - + >>> from music21_tools.chant.chant import DefaultTeXWrapper >>> wrapper = DefaultTeXWrapper() >>> class Converter(): ... score = r'\note{C}' + "\n" + r'\endgregorioscore %' + "\n" + r'\endinput %' @@ -507,7 +506,7 @@ def substituteInfo(self, converter): wrapper = re.sub(r'INCIPITGOESHERE', incipit, wrapper) wrapper = re.sub(r'MODEGOESHERE', mode, wrapper) wrapper = re.sub(r'PAPERTYPEGOESHERE', paperType, wrapper) - wrapper = re.sub(r'\\endinput %', r'\\end{document}' + "\n" + r'\\endinput %', wrapper) + wrapper = re.sub(r'\\endinput %', r'\\end{document}' + '\n' + r'\\endinput %', wrapper) return wrapper @@ -515,45 +514,32 @@ def substituteInfo(self, converter): class ChantException(exceptions21.Music21Exception): pass - - -class Test(unittest.TestCase): - pass - - def runTest(self): - pass - -class TestExternal(unittest.TestCase): # pragma: no cover - pass - - def runTest(self): - pass - +class TestExternal(unittest.TestCase): # pragma: no cover def testSimpleFile(self): s = GregorianStream() s.append(clef.AltoClef()) - n = GregorianNote("C4") - l = note.Lyric("Po") - l.syllabic = "begin" - n.lyrics.append(l) + n = GregorianNote('C4') + lyric = note.Lyric('Po') + lyric.syllabic = 'begin' + n.lyrics.append(lyric) n.oriscus = True s.append(n) - n2 = GregorianNote("D4") + n2 = GregorianNote('D4') s.append(n2) - n3 = GregorianNote("C4") + n3 = GregorianNote('C4') n3.stropha = True s.append(n3) - n4 = GregorianNote("B3") + n4 = GregorianNote('B3') n4.stropha = True s.append(n4) gabcText = s.toGABCText() bsc = BaseScoreConverter() bsc.score = gabcText - bsc.incipit = "Populus" + bsc.incipit = 'Populus' bsc.mode = 'VII' - fn = bsc.writeFile("style: modern;\n\n%%\n" + gabcText) + fn = bsc.writeFile('style: modern;\n\n%%\n' + gabcText) texfn = bsc.launchGregorio(fn) texfh = open(texfn) texcontents = texfh.read() @@ -568,15 +554,6 @@ def testSimpleFile(self): os.system('open %s' % pdffn) - # ------------------------------------------------------------------------------ # define presented order in documentation _DOC_ORDER = [] - - -if __name__ == "__main__": - import music21 - music21.mainTest(Test, 'moduleRelative') - -# ----------------------------------------------------------------------------- -# eof diff --git a/music21_tools/choraleTools/mnum_fixer.py b/music21_tools/choraleTools/mnum_fixer.py index 6069849..63d3793 100644 --- a/music21_tools/choraleTools/mnum_fixer.py +++ b/music21_tools/choraleTools/mnum_fixer.py @@ -9,12 +9,12 @@ # ------------------------------------------------------------------------------ from pathlib import Path -from music21 import * +from music21 import converter, corpus, musicxml p = Path('/Users/Cuthbert/Desktop/Norman_Schmidt_Chorales') pOut = p.parent / 'Out_Chorales' -def run(): +def run(): files = list(p.iterdir()) filenames = [fp.name for fp in files] pos = 0 @@ -29,7 +29,7 @@ def run(): continue pos += 1 runOne(c, cName) - + def runOne(c, cName): pName = p / cName newScore = converter.parse(pName) @@ -48,7 +48,7 @@ def runOne(c, cName): mns = m.measureNumberWithSuffix() ts = m.timeSignature or m.getContextByClass('TimeSignature') if ts is None: - print("No time signature context!", cName, part.id, mns) + print('No time signature context!', cName, part.id, mns) continue barQl = ts.barDuration.quarterLength mQl = m.duration.quarterLength @@ -58,7 +58,7 @@ def runOne(c, cName): truncated = True if not perfect and short < (barQl / 2) else False half = True if not perfect and short == (barQl / 2) else False - if half and priorMeasureWasIncomplete==False: + if half and not priorMeasureWasIncomplete: priorMeasureWasIncomplete = True priorMeasureDuration = mQl priorMeasure = m @@ -77,7 +77,7 @@ def runOne(c, cName): m.number = mn - measureNumberShift m.numberSuffix = ms partNumSuffix.append((mn - measureNumberShift, ms)) - + elif perfect: priorMeasureWasIncomplete = False priorMeasureDuration = mQl @@ -90,7 +90,7 @@ def runOne(c, cName): m.number = 0 measureNumberShift += 1 elif truncated and priorMeasureWasIncomplete and priorMeasureDuration == short: - print("Truncated measure following pickup...", cName, part.id, mn) + print('Truncated measure following pickup...', cName, part.id, mn) priorMeasure.paddingRight = priorMeasure.paddingLeft priorMeasure.paddingLeft = 0 measureNumberShift += 1 @@ -102,7 +102,7 @@ def runOne(c, cName): ms = ms + 'x' m.number = mn - measureNumberShift m.numberSuffix = ms - partNumSuffix.append((mn - measureNumberShift, ms)) + partNumSuffix.append((mn - measureNumberShift, ms)) elif truncated: priorMeasureWasIncomplete = True m.paddingRight = short @@ -111,7 +111,7 @@ def runOne(c, cName): m.number = mn - measureNumberShift partNumSuffix.append((mn - measureNumberShift, ms)) elif pickup and not priorMeasureWasIncomplete: - print("Pickup following complete prior measure", cName, part.id, mn) + print('Pickup following complete prior measure', cName, part.id, mn) priorMeasureWasIncomplete = True m.paddingLeft = short priorMeasure = m @@ -133,7 +133,7 @@ def runOne(c, cName): m.numberSuffix = ms partNumSuffix.append((mn - measureNumberShift, ms)) elif pickup and priorMeasureWasIncomplete and ts is not priorMeasure.timeSignature: - print("Changing TS Pickup", cName, part.id, mn) + print('Changing TS Pickup', cName, part.id, mn) priorMeasureWasIncomplete = True m.paddingLeft = short priorMeasure = m @@ -141,13 +141,13 @@ def runOne(c, cName): measureNumberShift += 1 m.number = mn - measureNumberShift partNumSuffix.append((mn - measureNumberShift, ms)) - + partSuffixesTuple = tuple(partNumSuffix) allSuffixesByPart.add(partSuffixesTuple) - - + + if len(allSuffixesByPart) != 1: - print("Multiple conflicting measures!", cName) + print('Multiple conflicting measures!', cName) print(cName, allSuffixesByPart) try: @@ -156,15 +156,15 @@ def runOne(c, cName): sKOrig = str(kOrig) sKNew = str(kNew) if kOrig.sharps != kNew.sharps: - print("Key changed from", kOrig, kNew) - + print('Key changed from', kOrig, kNew) + if sKNew != sKOrig: kNew.activeSite.replace(kNew, kOrig) analysisKey = newScore.analyze('key') print('Mode would have been changed from ', sKOrig, sKNew) if str(analysisKey) != sKOrig: - print("Key mismatch: ", sKOrig, sKNew, str(analysisKey)) - + print('Key mismatch: ', sKOrig, sKNew, str(analysisKey)) + except IndexError: print('no key in ', cName) @@ -175,21 +175,21 @@ def runOne(c, cName): # for i, pOrig in enumerate(c.parts): # expander = repeat.Expander(pOrig) # if not expander.isExpandable(): -# #print('incoherent repeats', cName) +# # print('incoherent repeats', cName) # try: -# pOrig = expander.process() +# pOrig = expander.process() # except Exception: # pass -# +# # pNew = newScore.parts[i] # expander = repeat.Expander(pNew) # if not expander.isExpandable(): -# #print('incoherent repeats', cName) +# # print('incoherent repeats', cName) # try: # pNew = expander.process() # except Exception: # pass -# +# # origPitches = tuple([p.nameWithOctave for p in pOrig.pitches]) # newPitches = tuple([p.nameWithOctave for p in pNew.pitches]) # if origPitches != newPitches: @@ -202,7 +202,7 @@ def runOne(c, cName): # continue # if thisP != newP: # print(i, thisP, newP) - + if __name__ == '__main__': run() diff --git a/music21_tools/composition/__init__.py b/music21_tools/composition/__init__.py index 8915783..9c58ecf 100644 --- a/music21_tools/composition/__init__.py +++ b/music21_tools/composition/__init__.py @@ -1,16 +1,10 @@ -""" +''' Files in this package relate to aiding in composition -""" +''' __all__ = ['arvo', 'phasing', 'seeger'] # leave off aug30 for now from . import arvo from . import phasing from . import seeger - -# ----------------------------------------------------------------------------- -# eof - - - diff --git a/music21_tools/composition/arvo.py b/music21_tools/composition/arvo.py index 62884c1..f27bf6f 100644 --- a/music21_tools/composition/arvo.py +++ b/music21_tools/composition/arvo.py @@ -52,16 +52,16 @@ def partPari(show=True): else: n.accidental = cminor.accidentalByStep(n.step) if n.offset == (2 - 1) * 4 or n.offset == (74 - 1) * 4: - n.pitch = pitch.Pitch("C3") # exceptions to rule + n.pitch = pitch.Pitch('C3') # exceptions to rule elif n.offset == (73 - 1) * 4: n.tie = None - n.pitch = pitch.Pitch("C3") + n.pitch = pitch.Pitch('C3') top = copy.deepcopy(main.flatten()) main.insert(0, clef.Treble8vbClef()) middle = copy.deepcopy(main.flatten()) - cMinorArpeg = scale.ConcreteScale(pitches=["C2", "E-2", "G2"]) + cMinorArpeg = scale.ConcreteScale(pitches=['C2', 'E-2', 'G2']) # # dummy test on other data # myA = pitch.Pitch("A2") # myA.microtone = -15 diff --git a/music21_tools/composition/aug30.py b/music21_tools/composition/aug30.py index e1ab288..f468764 100644 --- a/music21_tools/composition/aug30.py +++ b/music21_tools/composition/aug30.py @@ -13,14 +13,16 @@ written on August 30, 2008 converted to new system on Dec. 26, 2010 ''' +import copy +import random +import unittest + from music21 import articulations from music21 import duration from music21 import meter from music21 import note from music21 import tempo from music21 import stream -import copy -import random def rhythmLine(baseNote=None, minLength=8.0, maxProbability=0.5): if baseNote is None: @@ -93,9 +95,9 @@ def nextOrPreviousType(baseDuration): def addPart(minLength=80, maxProbability=0.7, instrument=None): s1 = rhythmLine(minLength=minLength, maxProbability=maxProbability) - ts1 = meter.TimeSignature("4/4") + ts1 = meter.TimeSignature('4/4') s1.insert(0, ts1) - s1.insert(0, tempo.MetronomeMark(number=180, text="very fast")) + s1.insert(0, tempo.MetronomeMark(number=180, text='very fast')) if instrument is not None: s1.insert(0, instrument) s1.makeAccidentals() @@ -110,7 +112,7 @@ def addPart(minLength=80, maxProbability=0.7, instrument=None): return s1 -def test(): +def demo(): from music21 import instrument as j sc1 = stream.Score() # instruments = [Piccolo(), Glockenspiel(), 72, 69, 41, 27, 47, 1, 1, 1, 1, 34] @@ -131,9 +133,11 @@ def test(): sc1.insert(0, part) sc1.show() -if __name__ == "__main__": - test() -# ----------------------------------------------------------------------------- -# eof +class TestExternal(unittest.TestCase): # pragma: no cover + def testDemo(self): + demo() + +if __name__ == '__main__': + demo() diff --git a/music21_tools/composition/fadeChord1.py b/music21_tools/composition/fadeChord1.py index 9c628c5..725aa41 100644 --- a/music21_tools/composition/fadeChord1.py +++ b/music21_tools/composition/fadeChord1.py @@ -15,15 +15,15 @@ import math -from music21 import * +from music21 import converter, note, stream def smooth01(steps): # f(x) = arcsin(2x-1)/pi+1/2 out = [] - for i in range(1, steps+1): - frac = i/steps - out.append(math.asin(2*frac-1)/math.pi + 1/2) + for i in range(1, steps + 1): + frac = i / steps + out.append(math.asin(2 * frac - 1) / math.pi + 1 / 2) return out # return [0] + [math.asin(2*(i/steps)+1)/math.pi + 1/2 for i in range(1, steps-1)] + [1] @@ -36,7 +36,6 @@ def main(): basis = cast(stream.Measure, converter.parse("tinynotation: 2/4 c16 d e f g a c' b") .getElementsByClass('Measure').first()) vols = [[1, 0, 1, 0, 1, 0, 1, 0] * reps] - smooths = smooth01(reps) for i, n in enumerate(basis.notes): n.volume.velocityScalar = vols[0][i] notes = basis[note.Note] @@ -51,7 +50,7 @@ def main(): notes[7].groups.append('B') part = stream.Part() - for rep_n in range(reps): + for _ in range(reps): new_measure = deepcopy(basis) part.append(new_measure) @@ -78,16 +77,16 @@ def fade_note( ): reps = end_rep - start_rep smooths = smooth01(reps) - positions = [(1-s) * pos_offset_at_zero for s in smooths] + positions = [(1 - s) * pos_offset_at_zero for s in smooths] if fade_out: - smooths = [1-s for s in smooths] + smooths = [1 - s for s in smooths] m: stream.Measure for i, m in enumerate(part.getElementsByClass('Measure')): if i < start_rep: continue found = m.getElementsByClass(note.Note).getElementsByGroup(group_name) - index_in_smooths = i-start_rep if i < end_rep else end_rep-1-start_rep + index_in_smooths = i - start_rep if i < end_rep else end_rep - 1 - start_rep # print(index_in_smooths, smooths) for n in found: n.volume.velocityScalar = smooths[index_in_smooths] diff --git a/music21_tools/composition/phasing.py b/music21_tools/composition/phasing.py index ed1b126..88c8218 100644 --- a/music21_tools/composition/phasing.py +++ b/music21_tools/composition/phasing.py @@ -33,15 +33,15 @@ def pitchedPhase(cycles=None, show=False): The source code describes how this works. - >>> #_DOCS_SHOW composition.phasing.pitchedPhase(cycles=4, show=True) + >>> # _DOCS_SHOW composition.phasing.pitchedPhase(cycles=4, show=True) .. image:: images/phasingDemo.* :width: 576 ''' - sSrc = converter.parse("""tinynotation: 12/16 E16 F# B c# d F# E c# B F# d c# - E16 F# B c# d F# E c# B F# d c#""", makeNotation=False) + sSrc = converter.parse('''tinynotation: 12/16 E16 F# B c# d F# E c# B F# d c# + E16 F# B c# d F# E c# B F# d c#''', makeNotation=False) sPost = stream.Score() sPost.title = 'phasing experiment' sPost.insert(0, stream.Part()) @@ -112,7 +112,7 @@ def pendulumMusic(show=True, elif ps < 36: active = 3 - jQuant = round(j*8)/8.0 + jQuant = round(j * 8) / 8.0 establishedChords = parts[active].getElementsByOffset(jQuant) if len(establishedChords) == 0: @@ -123,7 +123,7 @@ def pendulumMusic(show=True, c = establishedChords[0] c.append(p) - j += loopLength/(maxNotesPerLoop - totalParts + i) + j += loopLength / (maxNotesPerLoop - totalParts + i) # j += (8+(8-i))/8.0 p = octo.next(p, stepSize=scaleStepSize) @@ -143,21 +143,11 @@ def pendulumMusic(show=True, # ------------------------------------------------------------------------------ class Test(unittest.TestCase): - - def runTest(self): - pass - - def testBasic(self, cycles=4, show=False): # run a reduced version pitchedPhase(cycles=cycles, show=show) class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - - def testBasic(self, cycles=8, show=True): # run a reduced version pitchedPhase(cycles=cycles, show=show) @@ -188,7 +178,7 @@ def xtestPendulumMusic(self, show=True): # define presented order in documentation _DOC_ORDER = [pitchedPhase] -if __name__ == "__main__": +if __name__ == '__main__': if len(sys.argv) == 1: # normal conditions import music21 music21.mainTest(TestExternal) @@ -196,10 +186,3 @@ def xtestPendulumMusic(self, show=True): elif len(sys.argv) > 1: t = Test() t.testBasic(cycles=None, show=True) - - - - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/composition/seeger.py b/music21_tools/composition/seeger.py index b3e9e81..c069e0d 100644 --- a/music21_tools/composition/seeger.py +++ b/music21_tools/composition/seeger.py @@ -40,7 +40,7 @@ def lowerLines(): appendNote = copy.deepcopy(rowNotes[currentNote % 10]) else: # second set of rotations is up a step: appendNote = rowNotes[currentNote % 10].transpose(2) - # if phraseNumber == 8 and addNote == 9: # mistaken transpositions by RCS + # if phraseNumber == 8 and addNote == 9: # mistaken transpositions by RCS # appendNote = appendNote.transpose(-1) # appendNote.lyrics.append(note.Lyric(text="*", number=3)) # @@ -50,20 +50,20 @@ def lowerLines(): if addNote == 0: if phraseNumber != 8: - appendNote.lyrics.append(note.Lyric(text="p" + str(phraseNumber), number=1)) + appendNote.lyrics.append(note.Lyric(text='p' + str(phraseNumber), number=1)) else: - appendNote.lyrics.append(note.Lyric(text="p8*", number=1)) + appendNote.lyrics.append(note.Lyric(text='p8*', number=1)) if (currentNote % 10 == (rotationNumber + 8) % 10) and (currentNote != 0): currentNote += 2 rotationNumber += 1 else: if currentNote % 10 == (rotationNumber + 9) % 10: - appendNote.lyrics.append(note.Lyric(text="r" + str(rotationNumber), number=2)) + appendNote.lyrics.append(note.Lyric(text='r' + str(rotationNumber), number=2)) if rotationNumber in range(13, 22): appendNote.transpose(correctTranspositions[rotationNumber - 13], inPlace=True) appendNote.pitch.simplifyEnharmonic(inPlace=True) - appendNote.lyrics.append(note.Lyric(text="*", number=3)) + appendNote.lyrics.append(note.Lyric(text='*', number=3)) currentNote += 1 if addNote == 20 - phraseNumber: # correct Last Notes @@ -76,7 +76,7 @@ def lowerLines(): # retrograde totalNotes = len(myRow) for i in range(2, totalNotes + 1): # skip last note - el = myRow[totalNotes-i] + el = myRow[totalNotes - i] if 'Note' in el.classes: elNote = el.transpose('A1') elNote.pitch.simplifyEnharmonic(inPlace=True) diff --git a/music21_tools/contour/contour.py b/music21_tools/contour/contour.py index 44dea1d..eb8c300 100644 --- a/music21_tools/contour/contour.py +++ b/music21_tools/contour/contour.py @@ -12,9 +12,8 @@ This module defines the ContourFinder and AggregateContour objects. ''' import random -import unittest -from music21 import base # for _missingImport testing. +from music21 import base # for _missingImport testing. from music21 import repeat from music21 import exceptions21 from music21 import corpus @@ -23,7 +22,7 @@ _MOD = 'contour.py' environLocal = environment.Environment(_MOD) -#--------------------------------------------------- +# --------------------------------------------------- class ContourException(exceptions21.Music21Exception): pass @@ -64,25 +63,31 @@ class ContourFinder: M and n are specified by 'window' and 'slide', which are both 1 by default. + >>> from music21_tools.contour.contour import ContourFinder >>> s = corpus.parse('bwv29.8') - >>> ContourFinder(s).plot('tonality') + >>> tonalContour = ContourFinder(s).getContour('tonality') + >>> isinstance(tonalContour, dict) + True + + To render visually, call ``.plot('tonality')`` instead of ``.getContour(...)``. TODO: image here... ''' def __init__(self, s=None): - self.s = s # a stream.Score object - self.sChords = None #lazy evaluation... + self.s = s # a stream.Score object + self.sChords = None # lazy evaluation... self.key = None - self._contours = { } #A dictionary mapping a contour type to a normalized contour dictionary + # A dictionary mapping a contour type to a normalized contour dictionary + self._contours = {} - #self.metrics contains a dictionary mapping the name of a metric to a tuple (x,y) + # self.metrics contains a dictionary mapping the name of a metric to a tuple (x,y) # where x=metric function and y=needsChordify - self._metrics = {"dissonance": (self.dissonanceMetric, True), - "spacing": (self.spacingMetric, True), - "tonality": (self.tonalDistanceMetric, False) } + self._metrics = {'dissonance': (self.dissonanceMetric, True), + 'spacing': (self.spacingMetric, True), + 'tonality': (self.tonalDistanceMetric, False) } self.isContourFinder = True @@ -90,7 +95,6 @@ def setKey(self, key): ''' Sets the key of ContourFinder's internal stream. If not set manually, self.key will be determined by self.s.analyze('key'). - ''' self.key = key @@ -110,6 +114,7 @@ def getContourValuesForMetric(self, metric, window=1, slide=1, needChordified=Fa measures 3-6: f(measures 3-6), measures 5-8: f( measures5-8), ...} + >>> from music21_tools.contour.contour import ContourFinder >>> metric = lambda s: len(s.measureOffsetMap()) >>> c = corpus.parse('bwv10.7') >>> res = ContourFinder(c).getContourValuesForMetric(metric, 3, 2, False) @@ -122,8 +127,8 @@ def getContourValuesForMetric(self, metric, window=1, slide=1, needChordified=Fa OMIT_FROM_DOCS - >>> #set([1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]).issubset(set(res.keys())) + set([1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]).issubset(set(res.keys())) ''' res = {} @@ -141,19 +146,18 @@ def getContourValuesForMetric(self, metric, window=1, slide=1, needChordified=Fa numMeasures = len(mOffsets) - hasPickup - for i in range(1, numMeasures + 1, slide): #or numMeasures-window + 1 + for i in range(1, numMeasures + 1, slide): # or numMeasures-window + 1 fragment = s.measures(i, i + window - 1) - #TODO: maybe check that i+window-1 is less than numMeasures + window / 2 - + # TODO: maybe check that i+window-1 is less than numMeasures + window / 2 resValue = metric(fragment) res[i] = resValue return res - #TODO: tests that use simple 4-bar pieces that we have to create... - #ALSO: Need pictures or something! Need a clear demonstration! + # TODO: tests that use simple 4-bar pieces that we have to create... + # also: Need pictures or something! Need a clear demonstration! def getContour(self, cType, window=None, slide=None, overwrite=False, metric=None, needsChordify=False, normalized=False): ''' @@ -181,6 +185,7 @@ def getContour(self, cType, window=None, slide=None, overwrite=False, use normalized=False (the default), but to get a contour which evenly divides time between 1.0 and 100.0, use normalized=True + >>> from music21_tools.contour.contour import ContourFinder >>> cf = ContourFinder( corpus.parse('bwv10.7')) >>> mycontour = cf.getContour('dissonance') >>> [mycontour[x] for x in sorted(mycontour.keys())] # doctest: +NORMALIZE_WHITESPACE @@ -193,8 +198,8 @@ def getContour(self, cType, window=None, slide=None, overwrite=False, >>> mycontour = cf.getContour('spacing', metric = lambda x: 2, overwrite=False) Traceback (most recent call last): - OverwriteException: Attempted to overwrite 'spacing' metric but did - not specify overwrite=True + music21_tools.contour.contour.OverwriteException: Attempted to overwrite + 'spacing' metric but did not specify overwrite=True >>> mycontour = cf.getContour('spacing', slide=3, metric = lambda x: 2.0, overwrite=True) >>> [mycontour[x] for x in sorted(mycontour.keys())] @@ -209,14 +214,14 @@ def getContour(self, cType, window=None, slide=None, overwrite=False, if cType in self._contours: if window is not None or slide is not None: raise OverwriteException( - "Attempted to overwrite cached contour of type {0}".format(cType) + - " but did not specify overwrite=True") + 'Attempted to overwrite cached contour of type {0}'.format(cType) + + ' but did not specify overwrite=True') else: return self._contours[cType] elif cType in self._metrics: if metric is not None: raise OverwriteException("Attempted to overwrite '{0}' ".format(cType) + - "metric but did not specify overwrite=True") + 'metric but did not specify overwrite=True') else: metric, needsChordify = self._metrics[cType] else: @@ -226,7 +231,7 @@ def getContour(self, cType, window=None, slide=None, overwrite=False, if cType in self._metrics: metric, needsChordify = self._metrics[cType] else: - raise ContourException("Must provide your own metric for type: %s" % cType) + raise ContourException('Must provide your own metric for type: %s' % cType) if slide is None: @@ -249,6 +254,7 @@ def _normalizeContour(self, contourDict, maxKey): ''' Normalize a contour dictionary so that the values of the keys range from 0.0 to length. + >>> from music21_tools.contour.contour import ContourFinder >>> mycontour = { 0.0: 1.0, 3.0: 0.5, 6.0: 0.8, 9.0: 0.3, 12.0: 0.15, ... 15.0: 0.13, 18.0: 0.4, 21.0: 0.6 } >>> res = ContourFinder()._normalizeContour(mycontour, 100) @@ -273,7 +279,7 @@ def _normalizeContour(self, contourDict, maxKey): myKeys.sort() numKeys = len(myKeys) - spacing = (maxKey)/(numKeys-1.0) + spacing = maxKey / (numKeys - 1.0) res = {} i = 0.0 @@ -284,7 +290,7 @@ def _normalizeContour(self, contourDict, maxKey): return res - #TODO: give same args as getContour, maybe? Also, test this. + # TODO: give same args as getContour, maybe? Also, test this. def plot(self, cType, contourIn=None, regression=True, order=4, title='Contour Plot', fileName=None): (plt, numpy) = _getExtendedModules() @@ -309,12 +315,12 @@ def plot(self, cType, contourIn=None, regression=True, order=4, - plt.plot(t, p(t), 'o-', label='estimate', markersize=1) #probably change label + plt.plot(t, p(t), 'o-', label='estimate', markersize=1) # probably change label plt.xlabel('Time (arbitrary units)') plt.ylabel('Value for %s metric' % cType) - plt.title(title) #say for which piece - #plt.savefig(filename + '.png') + plt.title(title) # say for which piece + # plt.savefig(filename + '.png') if fileName is not None: plt.savefig(fileName + '.png') @@ -329,6 +335,7 @@ def randomize(self, contourDict): Returns a version of contourDict where the keys-to-values mapping is scrambled. + >>> from music21_tools.contour.contour import ContourFinder >>> myDict = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9, 10:10, 11:11, 12:12, 13:13, ... 14:14, 15:15, 16:16, 17:17, 18:18, 19:19, 20:20} >>> res = ContourFinder().randomize(myDict) @@ -353,7 +360,7 @@ def randomize(self, contourDict): return res - #--- code for the metrics + # --- code for the metrics def _calcGenericMetric(self, inpStream, chordMetric): ''' Helper function which, given a metric value for a chord, calculates a metric @@ -363,14 +370,14 @@ def _calcGenericMetric(self, inpStream, chordMetric): ''' score = 0 - n=0 + n = 0 for measure in inpStream: if 'Measure' in measure.classes: for chord in measure: if 'Chord' in chord.classes: dur = chord.duration.quarterLength - score += chordMetric(chord)*dur + score += chordMetric(chord) * dur n += dur if n != 0: @@ -386,6 +393,7 @@ def dissonanceMetric(self, inpStream): To work correctly, input must contain measures and no parts. + >>> from music21_tools.contour.contour import ContourFinder >>> c = corpus.parse('bwv102.7').chordify() >>> ContourFinder().dissonanceMetric( c.measures(1, 1) ) 0.25 @@ -395,16 +403,14 @@ def dissonanceMetric(self, inpStream): True ''' - return self._calcGenericMetric(inpStream, lambda x: 1-x.isConsonant() ) + return self._calcGenericMetric(inpStream, lambda x: 1 - x.isConsonant() ) def spacingMetric(self, inpStream): ''' - Defines a metric which takes a music21 stream containng measures and no parts. + Defines a metric which takes a music21 stream containing measures and no parts. This metric measures how spaced out notes in a piece are. - ''' - - #TODO: FIGURE OUT IF THIS IS REASONABLE! MIGHT WANT TO JUST DO: sqrt( sum(dist^2) ) + # TODO: Figure out if this is reasonable. Might want to just do: sqrt( sum(dist^2) ) def spacingForChord(chord): pitches = [ x.ps for x in chord.pitches ] pitches.sort() @@ -416,26 +422,30 @@ def spacingForChord(chord): return (pitches[1] - pitches[0]) else: res += (pitches[1] - pitches[0]) ** (0.7) - for i in range(1, len(pitches)-1): - res += (pitches[i + 1]-pitches[i]) ** (1.5) + for i in range(1, len(pitches) - 1): + res += (pitches[i + 1] - pitches[i]) ** (1.5) return res return self._calcGenericMetric(inpStream, spacingForChord) - def tonalDistanceMetric(self, inpStream): ''' Returns a number between 0.0 and 1.0 that is a measure of how far away the key of inpStream is from the key of ContourFinder's internal stream. ''' + from music21.analysis.discrete import DiscreteAnalysisException if self.key is None: self.key = self.s.analyze('key') - guessedKey = inpStream.analyze('key') + try: + guessedKey = inpStream.analyze('key') + except DiscreteAnalysisException: + # music21 raises exception when stream has no notes. + return 0.5 - certainty = -2 #should be replaced by a value between -1 and 1 + certainty = -2 # should be replaced by a value between -1 and 1 if guessedKey == self.key: certainty = guessedKey.correlationCoefficient @@ -521,7 +531,7 @@ def addPieceToContour(self, piece, cType, metric=None, window=1, - def getCombinedContour(self, cType): #, metric=None, window=1, slide=1, order=8): + def getCombinedContour(self, cType): # , metric=None, window=1, slide=1, order=8): ''' Returns the combined contour of the type specified by cType. Instead of a dictionary, this contour is just a list of ordered pairs (tuples) with the @@ -565,11 +575,9 @@ def getCombinedContourPoly(self, cType, order=8): def plot(self, cType, showPoints=True, comparisonContour=None, regression=True, order=6): - - #TODO: maybe have an option of specifying a different - # color thing for each individual contour... - - if cType not in self.aggContours: #['dissonance', 'tonality', 'distance']: + # TODO: maybe have an option of specifying a different + # color thing for each individual contour... + if cType not in self.aggContours: # ['dissonance', 'tonality', 'distance']: return None else: contour = self.getCombinedContour(cType) @@ -581,7 +589,7 @@ def plot(self, cType, showPoints=True, comparisonContour=None, regression=True, # else: # contour = self._aggContoursAsList[cType] # - (plt, numpy) = _getExtendedModules() #@UnusedVariable + (plt, numpy) = _getExtendedModules() x, y = zip(*contour) @@ -593,17 +601,17 @@ def plot(self, cType, showPoints=True, comparisonContour=None, regression=True, y = [comparisonContour[i] for i in x] plt.plot(x, y, '.', label='compContour', markersize=8) - #p = numpy.poly1d( numpy.polyfit(x, y, order)) + # p = numpy.poly1d( numpy.polyfit(x, y, order)) p = self.getCombinedContourPoly(cType) if regression: t = numpy.linspace(0, max(x), max(x) + 1) - plt.plot(t, p(t), 'o-', label='estimate', markersize=1) #probably change label + plt.plot(t, p(t), 'o-', label='estimate', markersize=1) # probably change label plt.xlabel('Time (percentage of piece)') - plt.ylabel('Value') #change this - plt.title('title') #say for which piece - #plt.savefig(filename + '.png') + plt.ylabel('Value') # change this + plt.title('title') # say for which piece + # plt.savefig(filename + '.png') plt.show() plt.clf() @@ -651,10 +659,9 @@ def _getOutliers(): def _runExperiment(): - #get chorale iterator, initialize ac - + # get chorale iterator, initialize ac ac = AggregateContour() - #unresolved problem numbers: 88 (repeatFinder fails!) + # unresolved problem numbers: 88 (repeatFinder fails!) goodChorales = ['bach/bwv330', 'bach/bwv245.22', 'bach/bwv431', 'bach/bwv324', 'bach/bwv384', 'bach/bwv379', 'bach/bwv365', @@ -726,19 +733,19 @@ def _runExperiment(): 'bach/bwv343', 'bach/bwv311'] currentNum = 1 - #BCI = corpus.chorales.Iterator(1, 371, returnType='filename') #highest number is 371 - #highestNum = BCI.highestNumber - #currentNum = BCI.currentNumber + # BCI = corpus.chorales.Iterator(1, 371, returnType='filename') # highest number is 371 + # highestNum = BCI.highestNumber + # currentNum = BCI.currentNumber for chorale in goodChorales: print(currentNum) - currentNum +=1 + currentNum += 1 # ''' # if chorale == 'bach/bwv277': -# continue #this chorale here has an added measure -# # container randomly in the middle which breaks things. +# continue # this chorale here has an added measure +# # container randomly in the middle which breaks things. # ''' chorale = corpus.parse(chorale) @@ -759,7 +766,7 @@ def _runExperiment(): # print("too long") # continue # ''' - cf= ContourFinder(chorale) + cf = ContourFinder(chorale) ac.addPieceToContour(cf, 'dissonance') ac.addPieceToContour(cf, 'tonality') ac.addPieceToContour(cf, 'spacing') @@ -770,7 +777,7 @@ def _runExperiment(): for cType in ['spacing', 'tonality', 'dissonance']: - print("considering", cType, ": ") + print('considering', cType, ': ') cf = ContourFinder() totalSuccesses = 0 @@ -790,13 +797,13 @@ def _runExperiment(): if successes > 50: totalSuccesses += 1 - #print "GREAT SUCCESS!" + # print('Great success!') else: totalFailures += 1 - print("failure: chorale " + goodChorales[j]) #index ", str(i) + print('failure: chorale ' + goodChorales[j]) # index ", str(i) - print(cType, ": totalSuccesses =", str(totalSuccesses), - "totalFailures =", str(totalFailures)) + print(cType, ': totalSuccesses =', str(totalSuccesses), + 'totalFailures =', str(totalFailures)) def _plotChoraleContours(): BCI = corpus.chorales.Iterator(1, 75, returnType='filename') @@ -805,23 +812,11 @@ def _plotChoraleContours(): cf = ContourFinder(s) chorale = chorale[5:] print(chorale) - #cf.plot('dissonance', fileName= chorale + 'dissonance', regression=False) + # cf.plot('dissonance', fileName= chorale + 'dissonance', regression=False) try: - cf.plot('tonality', fileName= chorale + 'tonality', regression=False) + cf.plot('tonality', fileName=chorale + 'tonality', regression=False) except exceptions21.Music21Exception: print(chorale) s.show() break pass - - -#------------------------ -class Test(unittest.TestCase): - - def runTest(self): - pass - -if __name__ == "__main__": - import music21 - music21.mainTest(Test, 'moduleRelative') - diff --git a/music21_tools/counterpoint/__init__.py b/music21_tools/counterpoint/__init__.py index c73ef60..645f4a0 100644 --- a/music21_tools/counterpoint/__init__.py +++ b/music21_tools/counterpoint/__init__.py @@ -1,7 +1,4 @@ -__all__ = ["species"] +__all__ = ['species'] from . import species -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/counterpoint/species.py b/music21_tools/counterpoint/species.py index 9537a4a..0a7c585 100644 --- a/music21_tools/counterpoint/species.py +++ b/music21_tools/counterpoint/species.py @@ -32,7 +32,7 @@ from music21 import voiceLeading from music21 import environment -_MOD = "counterpoint/species.py" +_MOD = 'counterpoint/species.py' environLocal = environment.Environment(_MOD) @@ -56,7 +56,8 @@ def findParallelFifths(self, srcStream, cmpStream): any note that has harmonic interval of a fifth and is preceded by a harmonic interval of a fifth. - >>> from music21 import note, stream + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G3') >>> n2 = note.Note('A3') >>> n3 = note.Note('B3') @@ -75,7 +76,7 @@ def findParallelFifths(self, srcStream, cmpStream): 3 >>> n1.editorial.harmonicInterval.name 'P5' - >>> m4.octave = 5 #checking for 12ths as well + >>> m4.octave = 5 # checking for 12ths as well >>> cp.findParallelFifths(cp.stream1, cp.stream2) 3 ''' @@ -88,10 +89,10 @@ def findParallelFifths(self, srcStream, cmpStream): if (note2 is not None and note1.editorial.harmonicInterval is not None and note2.editorial.harmonicInterval is not None - and note1.editorial.harmonicInterval.semiSimpleName == "P5" - and note2.editorial.harmonicInterval.semiSimpleName == "P5"): + and note1.editorial.harmonicInterval.semiSimpleName == 'P5' + and note2.editorial.harmonicInterval.semiSimpleName == 'P5'): numParallelFifths += 1 - note2.editorial["parallelFifth"] = True + note2.editorial['parallelFifth'] = True return numParallelFifths def findHiddenFifths(self, stream1, stream2): @@ -103,7 +104,8 @@ def findHiddenFifths(self, stream1, stream2): where the two streams reach a fifth through parallel motion, but is not a parallel fifth. - >>> from music21 import note, stream + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G3') >>> n2 = note.Note('A3') >>> n3 = note.Note('B3') @@ -125,7 +127,7 @@ def findHiddenFifths(self, stream1, stream2): 2 ''' numHiddenFifths = 0 - for i in range(len(stream1.notes)-1): + for i in range(len(stream1.notes) - 1): note1 = stream1.notes[i] note2 = stream1.getElementAfterElement(note1, [Note]) note3 = stream2.playingWhenAttacked(note1) @@ -134,7 +136,7 @@ def findHiddenFifths(self, stream1, stream2): hidden = self.isHiddenFifth(note1, note2, note3, note4) if hidden: numHiddenFifths += 1 - note2.editorial["hiddenFifth"] = True + note2.editorial['hiddenFifth'] = True return numHiddenFifths def isParallelFifth(self, note11, note12, note21, note22): @@ -143,8 +145,8 @@ def isParallelFifth(self, note11, note12, note21, note22): (i.e. argument order is isParallelFifth(v1n1, v1n2, v2n1, v2n2)), returns True if the two harmonic intervals are P5 and False otherwise. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G3') >>> n2 = note.Note('B-3') >>> m1 = note.Note('D4') @@ -155,7 +157,7 @@ def isParallelFifth(self, note11, note12, note21, note22): >>> cp.isParallelFifth(n1, n2, m1, m2) #(n1, m1) and (n2, m2) are chords True >>> m1.octave = 5 - >>> m2.octave = 5 #test parallel 12ths + >>> m2.octave = 5 # test parallel 12ths >>> cp.isParallelFifth(n1, n2, m1, m2) True @@ -173,8 +175,8 @@ def isHiddenFifth(self, note11, note12, note21, note22): is isHiddenFifth(v1n1, v1n2, v2n1, v2n2)), returns True if there is a hidden fifth and false otherwise. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G3') >>> n2 = note.Note('B-3') >>> m1 = note.Note('E4') @@ -216,8 +218,8 @@ def findParallelOctaves(self, stream1, stream2): any note that has harmonic interval of an octave and is preceded by a harmonic interval of an octave. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G3') >>> n2 = note.Note('A3') >>> n3 = note.Note('B3') @@ -238,7 +240,7 @@ def findParallelOctaves(self, stream1, stream2): >>> cp.findParallelOctaves(cp.stream2, cp.stream1) 3 >>> m3.octave = 5 - >>> m4.octave = 6 #check for parallel 17ths + >>> m4.octave = 6 # check for parallel 17ths >>> cp.findParallelOctaves(cp.stream2, cp.stream1) 3 @@ -250,15 +252,15 @@ def findParallelOctaves(self, stream1, stream2): for note1 in stream1: note2 = stream1.getElementAfterElement(note1, [Note]) if note2 is not None: - if note1.editorial.harmonicInterval.semiSimpleName == "P8": - if note2.editorial.harmonicInterval.semiSimpleName == "P8": + if note1.editorial.harmonicInterval.semiSimpleName == 'P8': + if note2.editorial.harmonicInterval.semiSimpleName == 'P8': numParallelOctaves += 1 note2.editorial.parallelOctave = True for note1 in stream2: note2 = stream2.getElementAfterElement(note1, [Note]) if note2 is not None: - if note1.editorial.harmonicInterval.semiSimpleName == "P8": - if note2.editorial.harmonicInterval.semiSimpleName == "P8": + if note1.editorial.harmonicInterval.semiSimpleName == 'P8': + if note2.editorial.harmonicInterval.semiSimpleName == 'P8': note2.editorial.parallelOctave = True return numParallelOctaves @@ -270,9 +272,8 @@ def findHiddenOctaves(self, stream1, stream2): anything where the two streams reach an octave through parallel motion, but is not a parallel octave. - - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('F3') >>> n2 = note.Note('A3') >>> n3 = note.Note('A3') @@ -300,7 +301,7 @@ def findHiddenOctaves(self, stream1, stream2): ''' numHiddenOctaves = 0 - for i in range(len(stream1.notes)-1): + for i in range(len(stream1.notes) - 1): note1 = stream1.notes[i] note2 = stream1.getElementAfterElement(note1, [Note]) note3 = stream2.playingWhenAttacked(note1) @@ -318,9 +319,8 @@ def isParallelOctave(self, note11, note12, note21, note22): isParallelOctave(v1n1, v1n2, v2n1, v2n2)), returns True if the two harmonic intervals are P8 and False otherwise. - >>> from music21 import note, stream - - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('A3') >>> n2 = note.Note('B-3') >>> m1 = note.Note('A4') @@ -338,12 +338,12 @@ def isParallelOctave(self, note11, note12, note21, note22): ''' vlq = voiceLeading.VoiceLeadingQuartet(note11, note12, note21, note22) return vlq.parallelOctave() -## interval1 = interval.notesToInterval(note11, note21) -## interval2 = interval.notesToInterval(note12, note22) -## if interval1.semiSimpleName == interval2.semiSimpleName == "P8": -## return True -## else: -## return False + # interval1 = interval.notesToInterval(note11, note21) + # interval2 = interval.notesToInterval(note12, note22) + # if interval1.semiSimpleName == interval2.semiSimpleName == "P8": + # return True + # else: + # return False def isHiddenOctave(self, note11, note12, note21, note22): '''Given four notes, assuming the first pair is from one part and @@ -351,26 +351,25 @@ def isHiddenOctave(self, note11, note12, note21, note22): (i.e. argument order is isHiddenOctave(v1n1, v1n2, v2n1, v2n2)) returns True if there is a hidden octave and false otherwise. - - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('A3') >>> n2 = note.Note('B-3') >>> m1 = note.Note('F4') >>> m2 = note.Note('B-4') >>> cp = ModalCounterpoint() - >>> cp.isHiddenOctave(n1, m1, n2, m2) #(n1, n2) and (m1, m2) are chords + >>> cp.isHiddenOctave(n1, m1, n2, m2) # (n1, n2) and (m1, m2) are chords False - >>> cp.isHiddenOctave(n1, n2, m1, m2) #(n1, m1) and (n2, m2) are chords + >>> cp.isHiddenOctave(n1, n2, m1, m2) # (n1, m1) and (n2, m2) are chords True >>> m1.octave = 5 >>> m2.octave = 5 >>> cp.isHiddenOctave(n1, n2, m1, m2) True - ''' vlq = voiceLeading.VoiceLeadingQuartet(note11, note12, note21, note22) return vlq.hiddenOctave() + # interval1 = interval.notesToInterval(note11, note21) # interval2 = interval.notesToInterval(note12, note22) # interval3 = interval.notesToInterval(note11, note12) @@ -393,7 +392,8 @@ def findParallelUnisons(self, stream1, stream2): assigns a flag under note.editorial["parallelUnison"] for any note that has harmonic interval of P1 and is preceded by a P1. - >>> from music21 import note, stream + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G4') >>> n2 = note.Note('A4') >>> n3 = note.Note('B4') @@ -413,7 +413,6 @@ def findParallelUnisons(self, stream1, stream2): True >>> cp.findParallelUnisons(cp.stream2, cp.stream1) 3 - ''' stream1.attachIntervalsBetweenStreams(stream2) stream2.attachIntervalsBetweenStreams(stream1) @@ -422,15 +421,15 @@ def findParallelUnisons(self, stream1, stream2): for note1 in stream1: note2 = stream1.getElementAfterElement(note1, [Note]) if note2 is not None: - if note1.editorial.harmonicInterval.name == "P1": - if note2.editorial.harmonicInterval.name == "P1": + if note1.editorial.harmonicInterval.name == 'P1': + if note2.editorial.harmonicInterval.name == 'P1': numParallelUnisons += 1 note2.editorial.parallelUnison = True for note1 in stream2: note2 = stream2.getElementAfterElement(note1, [Note]) if note2 is not None: - if note1.editorial.harmonicInterval.name == "P1": - if note2.editorial.harmonicInterval.name == "P1": + if note1.editorial.harmonicInterval.name == 'P1': + if note2.editorial.harmonicInterval.name == 'P1': note2.editorial.parallelUnison = True return numParallelUnisons @@ -440,8 +439,8 @@ def isParallelUnison(self, note11, note12, note21, note22): isParallelFifth(v1n1, v1n2, v2n1, v2n2)) returns True if the two harmonic intervals are P1 and False otherwise. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('A3') >>> n2 = note.Note('B-3') >>> m1 = note.Note('A3') @@ -453,26 +452,27 @@ def isParallelUnison(self, note11, note12, note21, note22): True >>> m1.octave = 4 >>> m2.octave = 4 - >>> cp.isParallelUnison(n1, n2, m1, m2) #parallel octaves, not unison + >>> cp.isParallelUnison(n1, n2, m1, m2) # parallel octaves, not unison False - ''' vlq = voiceLeading.VoiceLeadingQuartet(note11, note12, note21, note22) return vlq.parallelUnison() -## interval1 = interval.notesToInterval(note11, note21) -## interval2 = interval.notesToInterval(note12, note22) -## if interval1.name == interval2.name == "P1": -## return True -## else: -## return False + # interval1 = interval.notesToInterval(note11, note21) + # interval2 = interval.notesToInterval(note12, note22) + # if interval1.name == interval2.name == "P1": + # return True + # else: + # return False def isValidHarmony(self, note11, note21): - '''Determines if the harmonic interval between two given notes is + ''' + Determines if the harmonic interval between two given notes is "legal" according to 21M.301 rules of counterpoint. Legal harmonic intervals include 'P1', 'P5', 'P8', 'm3', 'M3', 'm6', and 'M6'. - >>> from music21 import note, stream + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> c = note.Note('C4') >>> d = note.Note('D4') >>> e = note.Note('E4') @@ -499,8 +499,8 @@ def isValidMiddleHarmony(self, note11, note21): 'm6', and 'M6', from before. 'P4' is now included because it is legal for middle harmonies. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> c = note.Note('C4') >>> d = note.Note('D4') >>> e = note.Note('E4') @@ -528,8 +528,8 @@ def allValidHarmony(self, stream1, stream2): include 'P1', 'P5', 'P8', 'm3', 'M3', 'm6', and 'M6'. Also assumes that final interval must be a perfect unison or octave. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G4') >>> n2 = note.Note('A4') >>> n3 = note.Note('D4') @@ -565,12 +565,12 @@ def allValidHarmony(self, stream1, stream2): for note2 in stream2.notes: if note2.editorial.harmonicInterval.semiSimpleName not in self.legalHarmonicIntervals: return False - if stream1.notes[-1].editorial.harmonicInterval.specificName != "Perfect": + if stream1.notes[-1].editorial.harmonicInterval.specificName != 'Perfect': environLocal.printDebug([stream1.notes[-1].editorial.harmonicInterval.specificName + - " ending, yuk!"]) + ' ending, yuk!']) return False if abs(stream1.notes[-1].editorial.harmonicInterval.generic.value) == 5: - environLocal.printDebug(["Ends on a fifth, yuk!"]) + environLocal.printDebug(['Ends on a fifth, yuk!']) return False if (stream1.notes[-1].editorial.harmonicInterval.semiSimpleName == 'P8' and stream1.notes[-2].editorial.harmonicInterval.simpleName == 'M6'): @@ -586,8 +586,8 @@ def allValidHarmonyMiddleVoices(self, stream1, stream2): middle voices, 'P4' is also allowed and the final interval is allowed to be a fifth. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G4') >>> n2 = note.Note('A4') >>> n3 = note.Note('B4') @@ -618,9 +618,9 @@ def allValidHarmonyMiddleVoices(self, stream1, stream2): if (note2.editorial.harmonicInterval.semiSimpleName not in self.legalMiddleHarmonicIntervals): return False - if stream1.notes[-1].editorial.harmonicInterval.specificName != "Perfect": + if stream1.notes[-1].editorial.harmonicInterval.specificName != 'Perfect': environLocal.printDebug([stream1.notes[-1].editorial.harmonicInterval.specificName + - " ending, yuk!"]) + ' ending, yuk!']) return False return True @@ -628,9 +628,8 @@ def countBadHarmonies(self, stream1, stream2): '''Given two simultaneous streams, counts the number of notes (in the first stream given) that create illegal harmonies when attacked. - >>> from music21 import note, stream - - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G4') >>> n2 = note.Note('A4') >>> n3 = note.Note('B4') @@ -664,9 +663,9 @@ def isValidStep(self, note11, note12): include 'P4', 'P5', 'P8', 'm2', 'M2', 'm3', 'M3', and 'm6'. SHOULD BE RENAMED isValidMelody? - - >>> from music21 import note, stream + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> c = note.Note('C4') >>> d = note.Note('D4') >>> e = note.Note('E#4') @@ -692,8 +691,8 @@ def isValidMelody(self, stream1): SHOULD BE RENAMED allValidMelody? - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G-4') >>> n2 = note.Note('A4') >>> n3 = note.Note('B4') @@ -721,13 +720,14 @@ def isValidMelody(self, stream1): return False def countBadSteps(self, stream1): - '''Given a single stream, returns the number of illegal melodic + ''' + Given a single stream, returns the number of illegal melodic intervals. SHOULD BE RENAMED countBadMelodies? - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('G-4') >>> n2 = note.Note('A4') >>> n3 = note.Note('B4') @@ -750,7 +750,7 @@ def countBadSteps(self, stream1): ''' numBadSteps = 0 sn = stream1.notes - for i in range(len(sn)-1): + for i in range(len(sn) - 1): note1 = sn[i] note2 = sn[i + 1] if note2 is not None: @@ -764,8 +764,8 @@ def findAllBadFifths(self, stream1, stream2): and also puts the appropriate tags in note.editorial under `.parallelFifth` and `.hiddenFifth`. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('C4') >>> n2 = note.Note('D4') >>> n3 = note.Note('E4') @@ -787,12 +787,13 @@ def findAllBadFifths(self, stream1, stream2): return parallel + hidden def findAllBadOctaves(self, stream1, stream2): - '''Given two streams, returns the total parallel and hidden octaves, + ''' + Given two streams, returns the total parallel and hidden octaves, and also puts the appropriate tags in note.editorial under `.parallelOctave` and `.hiddenOctave`. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('C4') >>> n2 = note.Note('D4') >>> n3 = note.Note('E4') @@ -808,7 +809,6 @@ def findAllBadOctaves(self, stream1, stream2): >>> cp = ModalCounterpoint(s1, s2) >>> cp.findAllBadOctaves(cp.stream1, cp.stream2) 2 - ''' parallel = self.findParallelOctaves(stream1, stream2) hidden = self.findHiddenOctaves(stream1, stream2) @@ -819,7 +819,8 @@ def tooManyThirds(self, stream1, stream2, limit=3): number of consecutive harmonic thirds exceeds the limit and False otherwise. - >>> from music21 import note, stream + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('E4') >>> n2 = note.Note('F4') >>> n3 = note.Note('G4') @@ -853,42 +854,44 @@ def tooManyThirds(self, stream1, stream2, limit=3): thirds = 0 return False -## def thirdCounter(self, intervalList, numThirds): -## '''Recursive helper function for tooManyThirds that returns the number -## of consecutive thirds in a stream, given a corresponding list of -## interval names. -## -## -## -## >>> cp = ModalCounterpoint() -## >>> iList1 = ['m3', 'M3', 'm6'] -## >>> cp.thirdCounter(iList1, 0) -## 2 -## >>> cp.thirdCounter(iList1, 2) -## 4 -## >>> iList2 = ['m2', 'M3', 'P1'] -## >>> cp.thirdCounter(iList2, 0) -## 0 -## >>> iList3 = [] -## >>> cp.thirdCounter(iList3, 52) -## 52 -## -## ''' -## if len(intervalList) == 0: -## return numThirds -## if intervalList[0] != "M3" and intervalList[0] != "m3": -## return numThirds -## else: -## numThirds += 1 -## newList = intervalList[1:] -## return self.thirdCounter(newList, numThirds) + # def thirdCounter(self, intervalList, numThirds): + # '''Recursive helper function for tooManyThirds that returns the number + # of consecutive thirds in a stream, given a corresponding list of + # interval names. + # + # + # + # >>> cp = ModalCounterpoint() + # >>> iList1 = ['m3', 'M3', 'm6'] + # >>> cp.thirdCounter(iList1, 0) + # 2 + # >>> cp.thirdCounter(iList1, 2) + # 4 + # >>> iList2 = ['m2', 'M3', 'P1'] + # >>> cp.thirdCounter(iList2, 0) + # 0 + # >>> iList3 = [] + # >>> cp.thirdCounter(iList3, 52) + # 52 + # + # ''' + # if len(intervalList) == 0: + # return numThirds + # if intervalList[0] != "M3" and intervalList[0] != "m3": + # return numThirds + # else: + # numThirds += 1 + # newList = intervalList[1:] + # return self.thirdCounter(newList, numThirds) def tooManySixths(self, stream1, stream2, limit=3): - '''Given two consecutive streams and a limit, returns True if the + ''' + Given two consecutive streams and a limit, returns True if the number of consecutive harmonic sixths exceeds the limit and False otherwise. - >>> from music21 import note, stream + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('E4') >>> n2 = note.Note('F4') >>> n3 = note.Note('G4') @@ -923,43 +926,42 @@ def tooManySixths(self, stream1, stream2, limit=3): sixths = 0 return False -## def sixthCounter(self, intervalList, numSixths): -## '''Recursive helper function for tooManySixths that returns the number -## of consecutive thirds in a stream, given a corresponding list of -## interval names. -## -## -## -## >>> cp = ModalCounterpoint() -## >>> iList1 = ['m6', 'M6', 'm3'] -## >>> cp.sixthCounter(iList1, 0) -## 2 -## >>> cp.sixthCounter(iList1, 2) -## 4 -## >>> iList2 = ['m2', 'M6', 'P1'] -## >>> cp.sixthCounter(iList2, 0) -## 0 -## >>> iList3 = [] -## >>> cp.sixthCounter(iList3, 52) -## 52 -## -## ''' -## if len(intervalList) == 0: -## return numSixths -## if intervalList[0] != "M6" and intervalList[0] != "m6": -## return numSixths -## else: -## numSixths += 1 -## newList = intervalList[1:] -## return self.sixthCounter(newList, numSixths) + # def sixthCounter(self, intervalList, numSixths): + # ''' + # Recursive helper function for tooManySixths that returns the number + # of consecutive thirds in a stream, given a corresponding list of + # interval names. + # + # >>> cp = ModalCounterpoint() + # >>> iList1 = ['m6', 'M6', 'm3'] + # >>> cp.sixthCounter(iList1, 0) + # 2 + # >>> cp.sixthCounter(iList1, 2) + # 4 + # >>> iList2 = ['m2', 'M6', 'P1'] + # >>> cp.sixthCounter(iList2, 0) + # 0 + # >>> iList3 = [] + # >>> cp.sixthCounter(iList3, 52) + # 52 + # ''' + # if len(intervalList) == 0: + # return numSixths + # if intervalList[0] != "M6" and intervalList[0] != "m6": + # return numSixths + # else: + # numSixths += 1 + # newList = intervalList[1:] + # return self.sixthCounter(newList, numSixths) def raiseLeadingTone(self, stream1, minorScale): - '''Given a stream of notes and a minor scale object, returns a new + ''' + Given a stream of notes and a minor scale object, returns a new stream that raises all the leading tones of the original stream. Also raises the sixth if applicable to avoid augmented intervals. - >>> from music21 import note, stream - + >>> from music21_tools.counterpoint.species import ModalCounterpoint + >>> from music21 import * >>> n1 = note.Note('C4') >>> n2 = note.Note('G4') >>> n3 = note.Note('A4') @@ -997,15 +999,15 @@ def raiseLeadingTone(self, stream1, minorScale): maxNote = len(s1notes) for i in range(maxNote): note1 = s1notes[i] - if (note1.name == sixth and i < maxNote - 2): + if note1.name == sixth and i < maxNote - 2: note2 = s1notes[i + 1] note3 = s1notes[i + 2] - if (note2.name == seventh and note3.name == tonic): - note1 = note1.transpose("A1") - elif (note1.name == seventh and i < maxNote - 1): + if note2.name == seventh and note3.name == tonic: + note1 = note1.transpose('A1') + elif note1.name == seventh and i < maxNote - 1: note2 = s1notes[i + 1] if note2.name == tonic: - note1 = note1.transpose("A1") + note1 = note1.transpose('A1') stream2.append(copy.deepcopy(note1)) return stream2 @@ -1020,10 +1022,10 @@ def generateFirstSpecies(self, cantusFirmus, minorScale, choice='random'): thirdsGood = False sixthsGood = False - while (not goodHarmony or not goodMelody or not thirdsGood or not sixthsGood): + while not goodHarmony or not goodMelody or not thirdsGood or not sixthsGood: environLocal.printDebug(['']) environLocal.printDebug(['-------------------------------------']) - environLocal.printDebug(['STARTING OVER NOW']) + environLocal.printDebug(['starting over']) try: top = self.getValidSecondVoice(cantusFirmus, minorScale, 'random') top = self.raiseLeadingTone(top, minorScale) @@ -1043,17 +1045,17 @@ def generateFirstSpecies(self, cantusFirmus, minorScale, choice='random'): environLocal.printDebug([note1.name + str(note1.octave) for note1 in cantusFirmus.notes]) if not goodHarmony: - environLocal.printDebug(["bad harmony"]) + environLocal.printDebug(['bad harmony']) else: - environLocal.printDebug(["harmony good"]) + environLocal.printDebug(['harmony good']) if not goodMelody: - environLocal.printDebug(["bad melody"]) + environLocal.printDebug(['bad melody']) else: - environLocal.printDebug(["melody good"]) + environLocal.printDebug(['melody good']) if not thirdsGood: - environLocal.printDebug(["too many thirds"]) + environLocal.printDebug(['too many thirds']) if not sixthsGood: - environLocal.printDebug(["too many sixths"]) + environLocal.printDebug(['too many sixths']) except ModalCounterpointException: pass @@ -1078,8 +1080,8 @@ def getValidSecondVoice(self, stream1, minorScale, choice='random'): # interval.transposeNote(firstNote, "P5"), # interval.transposeNote(firstNote, "P8")] choices = [copy.deepcopy(firstNote), - firstNote.transpose("P5"), - firstNote.transpose("P8")] + firstNote.transpose('P5'), + firstNote.transpose('P8')] if choice == 'random': note1 = random.choice(choices) elif choice == 'first': @@ -1096,7 +1098,7 @@ def getValidSecondVoice(self, stream1, minorScale, choice='random'): choices = self.generateValidNotes(prevFirmus, currFirmus, prevNote, afterLeap, minorScale) if not choices: - raise ModalCounterpointException("Sorry, please try again") + raise ModalCounterpointException('Sorry, please try again') if choice == 'random': newNote = random.choice(choices) elif choice == 'first': @@ -1104,7 +1106,7 @@ def getValidSecondVoice(self, stream1, minorScale, choice='random'): elif choice == 'last': newNote = choices[-1] else: - newNote = random.choice(choices) # if choice flag not recognized, go with random + newNote = random.choice(choices) # if choice flag not recognized, go with random newNote.duration = currFirmus.duration stream2.append(newNote) int1 = interval.notesToInterval(prevNote, newNote) @@ -1129,37 +1131,37 @@ def generateValidNotes(self, prevFirmus, currFirmus, prevNote, afterLeap, minorS possibleNotes = [] - n1 = interval.transposeNote(prevNote, "m2") - n2 = interval.transposeNote(prevNote, "M2") - n3 = interval.transposeNote(prevNote, "m3") - n4 = interval.transposeNote(prevNote, "M3") + n1 = interval.transposeNote(prevNote, 'm2') + n2 = interval.transposeNote(prevNote, 'M2') + n3 = interval.transposeNote(prevNote, 'm3') + n4 = interval.transposeNote(prevNote, 'M3') if afterLeap: goingUp = [n1, n2, n3, n4] else: - n5 = interval.transposeNote(prevNote, "P4") - n6 = interval.transposeNote(prevNote, "P5") + n5 = interval.transposeNote(prevNote, 'P4') + n6 = interval.transposeNote(prevNote, 'P5') goingUp = [n1, n2, n3, n4, n5, n6] - n7 = interval.transposeNote(prevNote, "m-2") - n8 = interval.transposeNote(prevNote, "M-2") - n9 = interval.transposeNote(prevNote, "m-3") - n10 = interval.transposeNote(prevNote, "M-3") + n7 = interval.transposeNote(prevNote, 'm-2') + n8 = interval.transposeNote(prevNote, 'M-2') + n9 = interval.transposeNote(prevNote, 'm-3') + n10 = interval.transposeNote(prevNote, 'M-3') if afterLeap: goingDown = [n7, n8, n9, n10] else: - n11 = interval.transposeNote(prevNote, "P-4") - n12 = interval.transposeNote(prevNote, "P-5") + n11 = interval.transposeNote(prevNote, 'P-4') + n12 = interval.transposeNote(prevNote, 'P-5') goingDown = [n7, n8, n9, n10, n11, n12] possibleNotes.extend(goingUp) possibleNotes.extend(goingDown) - #favor contrary motion + # favor contrary motion if bottomInt.direction < 0: possibleNotes.extend(goingUp) else: possibleNotes.extend(goingDown) - environLocal.printDebug(["possible: ", [note1.name for note1 in possibleNotes]]) + environLocal.printDebug(['possible: ', [note1.name for note1 in possibleNotes]]) goodNotes = minorScale.getPitches('C2', 'C6') goodNames = [note2.name for note2 in goodNotes] @@ -1187,7 +1189,7 @@ def generateValidNotes(self, prevFirmus, currFirmus, prevNote, afterLeap, minorS continue if interval.Interval(currFirmus, note1).generic.value > 10: continue - environLocal.printDebug(["adding: ", note1.name, note1.octave]) + environLocal.printDebug(['adding: ', note1.name, note1.octave]) valid.append(note1) return valid @@ -1206,42 +1208,42 @@ def generateValidLastNotes(self, prevFirmus, currFirmus, prevNote, afterLeap, mi possibleNotes = [] - n1 = interval.transposeNote(prevNote, "m2") - n2 = interval.transposeNote(prevNote, "M2") + n1 = interval.transposeNote(prevNote, 'm2') + n2 = interval.transposeNote(prevNote, 'M2') if not topVoice: - n3 = interval.transposeNote(prevNote, "m3") - n4 = interval.transposeNote(prevNote, "M3") + n3 = interval.transposeNote(prevNote, 'm3') + n4 = interval.transposeNote(prevNote, 'M3') if afterLeap: goingUp = [n1, n2, n3, n4] else: - n5 = interval.transposeNote(prevNote, "P4") - n6 = interval.transposeNote(prevNote, "P5") + n5 = interval.transposeNote(prevNote, 'P4') + n6 = interval.transposeNote(prevNote, 'P5') goingUp = [n1, n2, n3, n4, n5, n6] else: goingUp = [n1, n2] - n7 = interval.transposeNote(prevNote, "m-2") - n8 = interval.transposeNote(prevNote, "M-2") + n7 = interval.transposeNote(prevNote, 'm-2') + n8 = interval.transposeNote(prevNote, 'M-2') if not topVoice: - n9 = interval.transposeNote(prevNote, "m-3") - n10 = interval.transposeNote(prevNote, "M-3") + n9 = interval.transposeNote(prevNote, 'm-3') + n10 = interval.transposeNote(prevNote, 'M-3') if afterLeap: goingDown = [n7, n8, n9, n10] else: - n11 = interval.transposeNote(prevNote, "P-4") - n12 = interval.transposeNote(prevNote, "P-5") + n11 = interval.transposeNote(prevNote, 'P-4') + n12 = interval.transposeNote(prevNote, 'P-5') goingDown = [n7, n8, n9, n10, n11, n12] else: goingDown = [n7, n8] possibleNotes.extend(goingUp) possibleNotes.extend(goingDown) - #favor contrary motion + # favor contrary motion if bottomInt.direction < 0: possibleNotes.extend(goingUp) else: possibleNotes.extend(goingDown) - environLocal.printDebug(["possible: ", [note1.name for note1 in possibleNotes]]) + environLocal.printDebug(['possible: ', [note1.name for note1 in possibleNotes]]) goodNotes = minorScale.ascending() goodNames = [note2.name for note2 in goodNotes] @@ -1272,7 +1274,7 @@ def generateValidLastNotes(self, prevFirmus, currFirmus, prevNote, afterLeap, mi if (interval.Interval(currFirmus, note1).simpleName == 1 or interval.Interval(currFirmus, note1).simpleName == 5): - environLocal.printDebug(["adding: ", note1.name, note1.octave]) + environLocal.printDebug(['adding: ', note1.name, note1.octave]) valid.append(note1) return valid @@ -1315,8 +1317,8 @@ class Test(unittest.TestCase): # # counterpoint1 = ModalCounterpoint(stream1, stream2) # -# # GGBC -# # CDFF +# # GGBC +# # CDFF # findPar5 = counterpoint1.findParallelFifths(stream1, stream2) # print(findPar5) # @@ -1635,8 +1637,6 @@ def getRandomCF(mode=None): representing the name of the related scale's tonic, which can be made into a note and a scale object. - - >>> cf = getRandomCF() >>> sorted(list(cf.keys())) ['mode', 'notes'] @@ -1644,7 +1644,6 @@ def getRandomCF(mode=None): True >>> isinstance(cf['mode'], str) True - ''' if mode is not None: raise Exception('Cantus firmus selection by mode does not yet exist') @@ -1652,18 +1651,12 @@ def getRandomCF(mode=None): # ------------------------------------------------------------------------------ -class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - - +class TestExternal(unittest.TestCase): # pragma: no cover def testGenerateFirstSpecies(self): ''' A First Species Counterpoint Generator by Jackie Rogoff (MIT 2010) written as part of an UROP (Undergraduate Research Opportunities Program) project at M.I.T. 2008. ''' - counterpoint1 = ModalCounterpoint() cf = getRandomCF() @@ -1679,47 +1672,35 @@ def testGenerateFirstSpecies(self): score.insert(0, meter.TimeSignature('4/4')) score.insert(0, top) score.insert(0, cantusFirmus) -# score.show('text') -# score.show('musicxml') + # score.show('text') + # score.show('musicxml') score.show('midi') score.show('lily.png') def xtestGenerateFirstSpeciesThreeVoices(self): ''' A First Species, Three-Voice Counterpoint Generator by Jackie Rogoff (MIT 2010) - written as continuation of - a UROP (Undergraduate Research Opportunities Program) project at M.I.T. summer 2010. + written as continuation of a UROP (Undergraduate Research + Opportunities Program) project at M.I.T. summer 2010. ''' - counterpoint1 = ModalCounterpoint() - - cf = cantusFirmi[0]# getRandomCF() + cf = cantusFirmi[0] # getRandomCF() environLocal.printDebug(['Using: ', cf['notes']]) - cantusFirmus = stream.Part(converter.parse(cf['notes'], "4/4").notes) - + cantusFirmus = stream.Part(converter.parse('tinynotation: 4/4 ' + cf['notes']).notes) baseNote = Note(cf['mode']) thisScale = scale.MinorScale(baseNote) - (middleVoice, topVoice) = counterpoint1.generateFirstSpeciesThreeVoices( cantusFirmus, thisScale, 'random') - score = stream.Score() score.insert(0, meter.TimeSignature('4/4')) score.insert(0, topVoice) score.insert(0, middleVoice) score.insert(0, cantusFirmus) -# score.show('text') -# score.show('musicxml') + # score.show('text') + # score.show('musicxml') score.show('midi') score.show('lily.png') - - -if __name__ == "__main__": +if __name__ == '__main__': import music21 music21.mainTest(Test, 'moduleRelative') - - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/featureExtraction/ismir2011.py b/music21_tools/featureExtraction/ismir2011.py index e5f53c0..8505e38 100644 --- a/music21_tools/featureExtraction/ismir2011.py +++ b/music21_tools/featureExtraction/ismir2011.py @@ -17,13 +17,13 @@ # def example2(): # handel = corpus.parse('hwv56/movement3-05.md') # fe = features.jSymbolic.TripleMeterFeature(handel) -# print (fe.extract().vector) +# print(fe.extract().vector) # # # no longer works... # soft = converter.parse("https://github.com/cuthbertLab/music21/raw/master/music21/" + # "corpus/leadSheet/fosterBrownHair.mxl") # fe.setData(soft) -# print (fe.extract().vector) +# print(fe.extract().vector) class MusicaFictaFeature(features.FeatureExtractor): name = 'Musica Ficta' @@ -34,21 +34,21 @@ def _process(self): allPitches = self.data['flat.pitches'] fictaPitches = 0 for p in allPitches: - if p.name == "B-": + if p.name == 'B-': continue elif p.accidental is not None and p.accidental.name != 'natural': fictaPitches += 1 self._feature.vector[0] = fictaPitches / len(allPitches) -def testFictaFeature(): +def demoFictaFeature(): luca = corpus.parse('luca/gloria.mxl') fe = MusicaFictaFeature(luca) - print (fe.extract().vector) + print(fe.extract().vector) mv = corpus.parse('monteverdi/madrigal.3.1.xml') fe.setData(mv) - print (fe.extract().vector) + print(fe.extract().vector) -def testDataSet(): +def demoDataSet(): fes = features.extractorsById(['ql1', 'ql2', 'ql3']) ds = features.DataSet(classLabel='Composer') ds.addFeatureExtractors(fes) @@ -61,11 +61,11 @@ def testDataSet(): classValue='Handel') ds.addData('http://www.midiworld.com/midis/other/handel/gfh-jm01.mid') ds.process() - print (ds.getAttributeLabels()) + print(ds.getAttributeLabels()) ds.write('d:/desktop/baroqueQLs.csv') fList = ds.getFeaturesAsList() - print (fList[0]) - print (features.outputFormats.OutputTabOrange(ds).getString()) + print(fList[0]) + print(features.outputFormats.OutputTabOrange(ds).getString()) for i in range(len(fList)): # display scores as pngs generated by Lilypond # if the most common note is an eighth note (0.5) @@ -81,7 +81,7 @@ def prepareChinaEurope1(): featureExtractors = features.extractorsById(['r31', 'r32', 'r33', 'r34', 'r35', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15', 'p16', 'p19', 'p20', 'p21']) - #featureExtractors = features.extractorsById('all') + # featureExtractors = features.extractorsById('all') oChina1 = corpus.parse('essenFolksong/han1') oCEurope1 = corpus.parse('essenFolksong/boehme10') @@ -103,7 +103,10 @@ def prepareChinaEurope1(): def prepareChinaEurope2(): - featureExtractors = features.extractorsById(['r31', 'r32', 'r33', 'r34', 'r35', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15', 'p16', 'p19', 'p20', 'p21']) + featureExtractors = features.extractorsById( + ['r31', 'r32', 'r33', 'r34', 'r35', + 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'p10', + 'p11', 'p12', 'p13', 'p14', 'p15', 'p16', 'p19', 'p20', 'p21']) oChina2 = corpus.parse('essenFolksong/han2') oCEurope2 = corpus.parse('essenFolksong/boehme20') @@ -123,8 +126,9 @@ def prepareChinaEurope2(): ds2.process() ds2.write('d:/desktop/folkTest.tab') -def testChinaEuropeFull(): - import orange, orngTree +def demoChinaEuropeFull(): + import orange + import orngTree data1 = orange.ExampleTable('d:/desktop/1.tab') data2 = orange.ExampleTable('d:/desktop/2.tab') @@ -147,12 +151,13 @@ def testChinaEuropeFull(): c = classifier(matchData[i]) if c != matchData[i].getclass(): mismatch += 1 - print('%s %s: misclassified %s/%s of %s' % (cStr, cName, mismatch, len(matchData), matchStr)) + print('%s %s: misclassified %s/%s of %s' + % (cStr, cName, mismatch, len(matchData), matchStr)) # this test requires orange and related files def xtestChinaEuropeSimpler(): - import orange, orngTree # @UnusedImport @UnresolvedImport + import orange trainData = orange.ExampleTable('ismir2011_fb_folkTrain.tab') testData = orange.ExampleTable('ismir2011_fb_folkTest.tab') @@ -173,7 +178,7 @@ def xtestChinaEuropeSimpler(): knnWrong += 1 total = float(len(testData)) - print (majWrong/total, knnWrong/total) + print(majWrong / total, knnWrong / total) def prepareTrecentoCadences(): @@ -201,7 +206,7 @@ def prepareTrecentoCadences(): ds.addData(s, classValue = thisBallata.composer, id=str(i)) else: ds2.addData(s, classValue = thisBallata.composer, id=str(i)) - print (i, thisBallata.title, thisBallata.composer) + print(i, thisBallata.title, thisBallata.composer) ds.process() ds2.process() @@ -209,8 +214,8 @@ def prepareTrecentoCadences(): ds2.write('d:/desktop/trecento2.tab') -def testTrecentoSimpler(): - import orange, orngTree # @UnusedImport @UnresolvedImport +def demoTrecentoSimpler(): + import orange trainData = orange.ExampleTable('d:/desktop/trecento2.tab') testData = orange.ExampleTable('d:/desktop/trecento1.tab') @@ -231,7 +236,7 @@ def testTrecentoSimpler(): knnWrong += 1 total = float(len(testData)) - print (majWrong/total, knnWrong/total) + print(majWrong / total, knnWrong / total) def wekaCommands(): ''' @@ -290,18 +295,16 @@ def wekaCommands(): pass - -### FIGURED BASS PAPER ### - +# FIGURED BASS PAPER def tinyNotationBass(): bass1 = converter.parse('tinyNotation: 4/4 C4 D8_6 E8_6 F4 G4_7 c1', makeNotation=False) - #bass1.show('lily.png') + # bass1.show('lily.png') fbLine1 = figuredBass.realizer.figuredBassFromStream(bass1) fbLine1.showAllRealizations() def figuredBassScale(): - fbScale1 = figuredBass.realizerScale.FiguredBassScale("D", "major") - print (fbScale1.getSamplePitches("E3", "6")) + fbScale1 = figuredBass.realizerScale.FiguredBassScale('D', 'major') + print(fbScale1.getSamplePitches('E3', '6')) def exampleD(): @@ -313,7 +316,7 @@ def exampleD(): def fbFeatureExtraction(): exampleFB = converter.parse('ismir2011_fb_example1b.xml') fe1 = features.jSymbolic.PitchClassDistributionFeature(exampleFB) - print (fe1.extract().vector) + print(fe1.extract().vector) # [0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.6666666666666666, 0.0, 0.0, 1.0, 0.0, 0.0] n1 = exampleFB.parts[0][1][5] n1.expressions.append(expressions.Turn()) @@ -328,7 +331,7 @@ def fbFeatureExtraction(): exampleFB.parts[0][2].append(y) fb1 = figuredBass.realizer.figuredBassFromStream(exampleFB.parts[1]) - #realization = fb1.realize() + # realization = fb1.realize() sol1 = fb1.generateRandomRealization() exampleFBOut = stream.Score() @@ -337,23 +340,19 @@ def fbFeatureExtraction(): exampleFBOut.insert(0, sol1.parts[1]) fe1.setData(exampleFBOut) - print (fe1.extract().vector) - #[0.0, 0.5, 1.0, 0.0, 0.6000000000000001, 0.0, 0.4, 0.2, 0.0, 0.7000000000000001, 0.0, 0.1] + print(fe1.extract().vector) + # [0.0, 0.5, 1.0, 0.0, 0.6000000000000001, 0.0, 0.4, 0.2, 0.0, 0.7000000000000001, 0.0, 0.1] # exampleFBOut.show() if __name__ == '__main__': pass - #testTrecentoSimpler() - #prepareTrecentoCadences() - #figuredBassScale() - #fbFeatureExtraction() - #testChinaEuropeSimpler() - - #prepareChinaEurope2() - #testDataSet() - #testFictaFeature() - #example2() - - -# ----------------------------------------------------------------------------- -# eof + # demoTrecentoSimpler() + # prepareTrecentoCadences() + # figuredBassScale() + # fbFeatureExtraction() + # testChinaEuropeSimpler() + + # prepareChinaEurope2() + # demoDataSet() + # demoFictaFeature() + # example2() diff --git a/music21_tools/josquin/label_intervals.py b/music21_tools/josquin/label_intervals.py index 284c46b..e43e593 100644 --- a/music21_tools/josquin/label_intervals.py +++ b/music21_tools/josquin/label_intervals.py @@ -11,13 +11,13 @@ from music21 import converter, interval def displayIntervals(file): - """ + ''' displayIntervals reads a music file and then displays it with an enumeration of the intervals above the lowest notes in the score at each sonority (after chordification). - """ + ''' sJosquinPiece = converter.parse(file) - #dissonant_intervals = ['m2', 'M2', 'M7', 'd5', 'm7', 'A4', 'P4'] + # dissonant_intervals = ['m2', 'M2', 'M7', 'd5', 'm7', 'A4', 'P4'] rJosquinPiece = sJosquinPiece.chordify() for c in rJosquinPiece.flatten().getElementsByClass('Chord'): if len(c.pitches) == 1: @@ -35,19 +35,18 @@ def displayIntervals(file): sJosquinPiece.insert(0, rJosquinPiece) sJosquinPiece.show() -##################################################################################### - -if __name__ == "__main__": +# ----------------------------------------------- +if __name__ == '__main__': import os import sys - basedir = "" # "/Users/Victoria/Desktop/" - filename = "1202a-Missa_Sine_nomine-Kyrie.xml" #argv[1] + basedir = '' # "/Users/Victoria/Desktop/" + filename = '1202a-Missa_Sine_nomine-Kyrie.xml' # argv[1] if os.path.isfile(filename): displayIntervals(filename) elif os.path.isfile(basedir + filename): displayIntervals(basedir + filename) else: - print("Cannot find file ", sys.argv[1]) - print("Usage: " + sys.argv[0] + " filename") + print('Cannot find file ', sys.argv[1]) + print('Usage: ' + sys.argv[0] + ' filename') diff --git a/music21_tools/midi/build_melody.py b/music21_tools/midi/build_melody.py index 3974a27..b4ba06a 100644 --- a/music21_tools/midi/build_melody.py +++ b/music21_tools/midi/build_melody.py @@ -28,7 +28,7 @@ def populate_midi_track_from_data(mt, data): mt.events.append(dt) me = midi.MidiEvent(mt) - me.type = "NOTE_ON" + me.type = 'NOTE_ON' me.channel = 1 me.time = None # d me.pitch = p @@ -42,7 +42,7 @@ def populate_midi_track_from_data(mt, data): mt.events.append(dt) me = midi.MidiEvent(mt) - me.type = "NOTE_OFF" + me.type = 'NOTE_OFF' me.channel = 1 me.time = None # d me.pitch = p @@ -58,7 +58,7 @@ def populate_midi_track_from_data(mt, data): mt.events.append(dt) me = midi.MidiEvent(mt) - me.type = "END_OF_TRACK" + me.type = 'END_OF_TRACK' me.channel = 1 me.data = '' # must set data to empty string mt.events.append(me) @@ -69,7 +69,7 @@ def main(): mt = midi.MidiTrack(1) # duration, pitch, velocity - data = [] # one start note + data = [] # one start note beats_per_measure = 4 measures = 16 @@ -91,7 +91,7 @@ def main(): env = environment.Environment() temp_filename = env.getTempFile('.mid') - print("Saving file to: %s" % temp_filename) + print('Saving file to: %s' % temp_filename) mf.open(temp_filename, 'wb') mf.write() mf.close() diff --git a/music21_tools/misc/eschbeg.py b/music21_tools/misc/eschbeg.py index ed45291..16dfe3d 100644 --- a/music21_tools/misc/eschbeg.py +++ b/music21_tools/misc/eschbeg.py @@ -22,16 +22,19 @@ eschbeg = '30ET47' def letterToNumber(letter): - if letter == "E": number = 11 - elif letter == "T": number = 10 - else: number = int(letter) + if letter == 'E': + number = 11 + elif letter == 'T': + number = 10 + else: + number = int(letter) return number def numberToLetter(number): if number == 11: - letter = "E" + letter = 'E' elif number == 10: - letter = "T" + letter = 'T' else: letter = str(number) return letter @@ -40,7 +43,7 @@ def numberToLetter(number): def setupTranspositions(): _eschbegTransposed = [] for i in range(12): - thisTransposition = "" + thisTransposition = '' for letter in eschbeg: number = letterToNumber(letter) newnum = (number + i) % 12 @@ -55,15 +58,15 @@ def generateToneRows(numberToGenerate=1000, cardinality=12): ''' generates a list of random 12-tone rows. - >>> #_DOCS_SHOW generateToneRows(4) + >>> # _DOCS_SHOW generateToneRows(4) ['T310762985E4', '9E036T472158', '5879E3T12064', '417T26E95038'] generate random 3-note sets: - >>> #_DOCS_SHOW generateToneRows(4, 3) + >>> # _DOCS_SHOW generateToneRows(4, 3) ['840', 'T61', 'T10', '173'] ''' - allNotes = "0123456789TE" + allNotes = '0123456789TE' firstRow = list(allNotes) returnRows = [] for i in range(numberToGenerate): @@ -76,13 +79,13 @@ def generateRandomRows(numberToGenerate=1000): generates random rows which might have the same note twice, but never twice in a row. - >>> #_DOCS_SHOW generateRandomRows(4) + >>> # _DOCS_SHOW generateRandomRows(4) ['67051534121', '05874071696', 'E082T6569674', '4E8383E4E395'] ''' returnRows = [] for i in range(numberToGenerate): myRow = [] - lastLetter = "" + lastLetter = '' for j in range(12): newLetter = numberToLetter(random.randint(0, 11)) if newLetter != lastLetter: @@ -199,14 +202,14 @@ def findEmbeddedChords(testSet='0234589', cardinality=3, skipInverse=False): ''' eschbegSplit12 = [letterToNumber(x) for x in testSet] - ret = "" + ret = '' for myTrichord in chord.tables.FORTE[cardinality]: if myTrichord is None: continue myPitches = myTrichord[0] myPitchString = ''.join([str(p) for p in myPitches]) - ret += "\n[" + myPitchString + "]: " + ret += '\n[' + myPitchString + ']: ' for i in range(12): notFound = False transPitches = [(p + i) % 12 for p in myPitches] @@ -215,19 +218,19 @@ def findEmbeddedChords(testSet='0234589', cardinality=3, skipInverse=False): notFound = True if notFound is False: transPitchesString = ''.join([str(p) for p in transPitches]) - ret += "(" + transPitchesString + ") " + ret += '(' + transPitchesString + ') ' if skipInverse is False: myInverse = [] for myPitch in myPitches: myInverse.append(11 - myPitch) myInverseMin = min(myInverse) - for i,p in enumerate(myInverse): + for i, p in enumerate(myInverse): myInverse[i] = p - myInverseMin myInverse = sorted(myInverse) myInverseString = ''.join([str(p) for p in myInverse]) - if myInverseString != myPitchString: # some are symmetric - ret += "\n[" + myInverseString + "]: " + if myInverseString != myPitchString: # some are symmetric + ret += '\n[' + myInverseString + ']: ' for i in range(12): notFound = False transInverse = [(p + i) % 12 for p in myInverse] @@ -236,7 +239,7 @@ def findEmbeddedChords(testSet='0234589', cardinality=3, skipInverse=False): notFound = True if notFound is False: transInverseString = ''.join([str(p) for p in transInverse]) - ret += "(" + transInverseString + ") " + ret += '(' + transInverseString + ') ' return ret.lstrip() def uniquenessOfEschbeg(cardinality=7, searchCardinality=3, skipInverse=False, showMatching=True): @@ -308,7 +311,7 @@ def uniquenessOfEschbeg(cardinality=7, searchCardinality=3, skipInverse=False, s for i in range(1, len(allHeptachords)): thisHeptachord = allHeptachords[i][0] thisHeptachordString = ''.join([numberToLetter(p) for p in thisHeptachord]) - #print thisHeptachordString + # print(thisHeptachordString) foundTrichords = findEmbeddedChords(thisHeptachordString, cardinality = searchCardinality, skipInverse = skipInverse) @@ -318,19 +321,11 @@ def uniquenessOfEschbeg(cardinality=7, searchCardinality=3, skipInverse=False, s if '(' not in trichordInfo: noneMissing = False break - if noneMissing == showMatching: # default True + if noneMissing == showMatching: # default True allHeptachordList.append(thisHeptachordString) return allHeptachordList -if __name__ == "__main__": +if __name__ == '__main__': import music21 music21.mainTest('moduleRelative') - - #print findEmbeddedChords(cardinality=5) - #p, t = priorProbability(100000, enforce12Tone=False) - #print p - #print t - -#------------------------------ -#eof diff --git a/music21_tools/misc/gatherAccidentals.py b/music21_tools/misc/gatherAccidentals.py index c9de494..3d436aa 100644 --- a/music21_tools/misc/gatherAccidentals.py +++ b/music21_tools/misc/gatherAccidentals.py @@ -110,7 +110,7 @@ def getAccidentalCount(score, includeNonAccidentals=False, excludeZeros=True): ''' # check for non-streams if not score.isStream: - raise GatherAccidentalsException("Input score must be a music21.stream.Stream object") + raise GatherAccidentalsException('Input score must be a music21.stream.Stream object') notes = score.flatten().notes tally = _initializeTally() for obj in notes: @@ -122,7 +122,7 @@ def getAccidentalCount(score, includeNonAccidentals=False, excludeZeros=True): accidental = p.accidental if accidental is None: if includeNonAccidentals: - tally['natural'] +=1 + tally['natural'] += 1 continue tally[accidental.name] += 1 return _deleteZeros(tally, excludeZeros) @@ -152,7 +152,7 @@ def getAccidentalCountSum(scores, includeNonAccidentals=False, excludeZeros=True tally = _initializeTally() for score in scores: if not score.isStream: - raise exceptions21.Music21Exception("score must be a stream object") + raise exceptions21.Music21Exception('score must be a stream object') scoreTally = getAccidentalCount(score, includeNonAccidentals, False) # dict.update() won't suffice; list() for Python v3 for k in list(scoreTally.keys()): @@ -204,7 +204,7 @@ def _deleteZeros(tally, excludeZeros): ''' if excludeZeros: for k in list(tally.keys()): - if tally[k] is 0: + if tally[k] == 0: del tally[k] return tally @@ -216,24 +216,20 @@ class GatherAccidentalsException(exceptions21.Music21Exception): # ------------------------------------------------------- class Test(unittest.TestCase): - - def runTest(self): - pass - def testGetAccidentalCountBasic(self): s = stream.Stream() - self.assertEqual(len(s.flatten().notes), 0) # the stream should be empty + self.assertEqual(len(s.flatten().notes), 0) # the stream should be empty self.assertEqual(getAccidentalCount(s), {}) def testGetAccidentalCountIntermediate(self): s = stream.Stream() - s.append(note.Note("C4")) # no accidental - s.append(note.Note("C#4")) # sharp - s.append(note.Note("D-4")) # flat + s.append(note.Note('C4')) # no accidental + s.append(note.Note('C#4')) # sharp + s.append(note.Note('D-4')) # flat self.assertEqual(getAccidentalCount(s), {'flat': 1, 'sharp': 1}) self.assertEqual(getAccidentalCount(s, True), {'flat': 1, 'sharp': 1, 'natural': 1}) - note4 = note.Note("C4") + note4 = note.Note('C4') self.assertIsNone(note4.pitch.accidental) note4.pitch.accidental = pitch.Accidental('natural') # add a natural accidental s.append(note4) @@ -269,10 +265,6 @@ def testGetAccidentalCountSumAdvanced(self): class TestSlow(unittest.TestCase): - - def runTest(self): - pass - def testAccidentalCountBachChorales(self): # the total number of accidentals in the Bach Chorales chorales = list( corpus.chorales.Iterator() ) @@ -280,7 +272,8 @@ def testAccidentalCountBachChorales(self): {'double-sharp': 4, 'flat': 7886, 'natural': 79869, 'sharp': 14940}) -if __name__ == "__main__": +if __name__ == '__main__': import music21 - music21.mainTest(Test, 'moduleRelative') # replace 'Test' with 'TestSlow' to test it on all 371 Bach Chorales. + # replace 'Test' with 'TestSlow' to test it on all 371 Bach Chorales. + music21.mainTest(Test, 'moduleRelative') diff --git a/music21_tools/misc/monteverdi.py b/music21_tools/misc/monteverdi.py index 98af791..fde8675 100644 --- a/music21_tools/misc/monteverdi.py +++ b/music21_tools/misc/monteverdi.py @@ -21,11 +21,11 @@ def spliceAnalysis(book=3, madrigal=1): ''' splice an analysis of the madrigal under the analysis itself ''' - #mad = corpus.parse('monteverdi/madrigal.%s.%s.xml' % (book, madrigal)) + # mad = corpus.parse('monteverdi/madrigal.%s.%s.xml' % (book, madrigal)) analysis = corpus.parse('monteverdi/madrigal.%s.%s.rntxt' % (book, madrigal)) # these are multiple parts in a score stream - #excerpt = mad.measures(1, 20) + # excerpt = mad.measures(1, 20) # get from first part aMeasures = analysis.parts[0].measures(1, 20) @@ -33,40 +33,41 @@ def spliceAnalysis(book=3, madrigal=1): for myN in aMeasures.flatten().notesAndRests: myN.style.hideObjectOnPrint = True x = aMeasures.write() - print (x) - #excerpt.insert(0, aMeasures) - #excerpt.show() + print(x) + # excerpt.insert(0, aMeasures) + # excerpt.show() def showAnalysis(book=3, madrigal= 3): - #analysis = converter.parse('d:/docs/research/music21/dmitri_analyses/Mozart Piano Sonatas/k331.rntxt') + # analysis = converter.parse( + # 'd:/docs/research/music21/dmitri_analyses/Mozart Piano Sonatas/k331.rntxt') filename = 'monteverdi/madrigal.%s.%s.rntxt' % (book, madrigal) analysis = corpus.parse(filename) - #analysis.show() + # analysis.show() (major, minor) = iqSemitonesAndPercentage(analysis) - print (major) - print (minor) + print(major) + print(minor) def analyzeBooks(books=(3,), start=1, end=20, show=False, strict=False): - majorFig = "" - minorFig = "" - majorSt = "" - minorSt = "" - majorRoot = "" - minorRoot = "" + majorFig = '' + minorFig = '' + majorSt = '' + minorSt = '' + majorRoot = '' + minorRoot = '' for book in books: for i in range(start, end + 1): filename = 'monteverdi/madrigal.%s.%s.rntxt' % (book, i) - if strict == True: + if strict: analysis = corpus.parse(filename) - print(book,i) + print(book, i) else: try: analysis = corpus.parse(filename) - print(book,i) + print(book, i) except Exception: - print("Cannot parse %s, maybe it does not exist..." % (filename)) + print('Cannot parse %s, maybe it does not exist...' % (filename)) continue - if show == True: + if show: analysis.show() (MF, mF) = iqChordsAndPercentage(analysis) (MSt, mSt) = iqSemitonesAndPercentage(analysis) @@ -94,17 +95,17 @@ def iqChordsAndPercentage(analysisStream): ''' totalDuration = analysisStream.duration.quarterLength romMerged = analysisStream.flatten().stripTies() - major = "" - minor = "" + major = '' + minor = '' active = 'minor' for element in romMerged: - if "RomanNumeral" in element.classes: + if 'RomanNumeral' in element.classes: fig = element.figure fig = fig.replace('[no5]', '') fig = fig.replace('[no3]', '') fig = fig.replace('[no1]', '') - longString = fig + " (" + str(int( - element.duration.quarterLength * 10000 / totalDuration) / 100) + ") " + longString = fig + ' (' + str(int( + element.duration.quarterLength * 10000 / totalDuration) / 100) + ') ' if active == 'major': major += longString else: @@ -112,25 +113,25 @@ def iqChordsAndPercentage(analysisStream): elif hasattr(element, 'tonic'): if element.mode == 'major': active = 'major' - major += "\n" + element.tonic + " " + element.mode + " " + major += '\n' + element.tonic + ' ' + element.mode + ' ' else: active = 'minor' - minor += "\n" + element.tonic + " " + element.mode + " " + minor += '\n' + element.tonic + ' ' + element.mode + ' ' return (major, minor) def iqSemitonesAndPercentage(analysisStream): totalDuration = analysisStream.duration.quarterLength romMerged = analysisStream.flatten().stripTies() - major = "" - minor = "" + major = '' + minor = '' active = 'minor' for element in romMerged: - if "RomanNumeral" in element.classes: + if 'RomanNumeral' in element.classes: distanceToTonicInSemis = int((element.root().ps - pitch.Pitch(element.scale.tonic).ps) % 12) - longString = str(distanceToTonicInSemis) + " (" + str(int( + longString = str(distanceToTonicInSemis) + ' (' + str(int( element.duration.quarterLength * 10000 / totalDuration) - / 100) + ") " + / 100) + ') ' if active == 'major': major += longString else: @@ -138,25 +139,25 @@ def iqSemitonesAndPercentage(analysisStream): elif hasattr(element, 'tonic'): if element.mode == 'major': active = 'major' - major += "\n" + element.tonic + " " + element.mode + " " + major += '\n' + element.tonic + ' ' + element.mode + ' ' else: active = 'minor' - minor += "\n" + element.tonic + " " + element.mode + " " + minor += '\n' + element.tonic + ' ' + element.mode + ' ' return (major, minor) def iqRootsAndPercentage(analysisStream): totalDuration = analysisStream.duration.quarterLength romMerged = analysisStream.flatten().stripTies() - major = "" - minor = "" + major = '' + minor = '' active = 'minor' for element in romMerged: - if "RomanNumeral" in element.classes: - #distanceToTonicInSemis = int((element.root().ps - + if 'RomanNumeral' in element.classes: + # distanceToTonicInSemis = int((element.root().ps - # pitch.Pitch(element.scale.tonic).ps) % 12) elementLetter = str(element.root().name) - ## leave El + # # leave El if element.quality == 'minor' or element.quality == 'diminished': elementLetter = elementLetter.lower() elif element.quality == 'other': @@ -168,20 +169,20 @@ def iqRootsAndPercentage(analysisStream): elementLetter = elementLetter.lower() else: pass - longString = elementLetter + " (" + str(int( + longString = elementLetter + ' (' + str(int( element.duration.quarterLength * 10000 / totalDuration) - / 100) + ") " + / 100) + ') ' if active == 'major': major += longString else: minor += longString - elif "Key" in element.classes: + elif 'Key' in element.classes: if element.mode == 'major': active = 'major' - major += "\n" + element.tonic + " " + element.mode + " " + major += '\n' + element.tonic + ' ' + element.mode + ' ' else: active = 'minor' - minor += "\n" + element.tonic + " " + element.mode + " " + minor += '\n' + element.tonic + ' ' + element.mode + ' ' return (major, minor) def monteverdiParallels(books=(3,), start=1, end=20, show=True, strict=False): @@ -191,19 +192,19 @@ def monteverdiParallels(books=(3,), start=1, end=20, show=True, strict=False): for book in books: for i in range(start, end + 1): filename = 'monteverdi/madrigal.%s.%s.xml' % (book, i) - if strict == True: + if strict: c = corpus.parse(filename) - print (book,i) + print(book, i) else: try: c = corpus.parse(filename) - print (book,i) - except: - print ("Cannot parse %s, maybe it does not exist..." % (filename)) + print(book, i) + except Exception: + print(f'Cannot parse {filename}, maybe it does not exist...') continue displayMe = False for i in range(len(c.parts) - 1): - #iName = c.parts[i].id + # iName = c.parts[i].id ifn = c.parts[i].flatten().notesAndRests.stream() omi = ifn.offsetMap() for j in range(i + 1, len(c.parts)): @@ -251,8 +252,10 @@ def findPhraseBoundaries(book=4, madrigal=12): for p in sc.parts: partNotes = p.flatten().stripTies(matchByPitch=True).notesAndRests - #thisPartPhraseScores = [] # keeps track of the likelihood that a phrase boundary is after note i - for i in range(2, len(partNotes) - 2): # start on the third note and stop searching on the third to last note... + # thisPartPhraseScores = [] # keeps track of the likelihood that + # a phrase boundary is after note i + # start on the third note and stop searching on the third-to-last note + for i in range(2, len(partNotes) - 2): thisScore = 0 twoNotesBack = partNotes[i - 2] previousNote = partNotes[i - 1] @@ -267,15 +270,15 @@ def findPhraseBoundaries(book=4, madrigal=12): phraseScoresByOffset[phraseOffset] = 0 existingScore = 0 - if thisNote.isRest == True: + if thisNote.isRest: continue - if nextNote.isRest == True: + if nextNote.isRest: thisScore = thisScore + 10 else: intervalToNextNote = interval.notesToInterval(thisNote.pitches[0], nextNote.pitches[0]) - if intervalToNextNote.chromatic.undirected >= 6: # a tritone or bigger + if intervalToNextNote.chromatic.undirected >= 6: # a tritone or bigger thisScore = thisScore + 10 if ((thisNote.quarterLength > previousNote.quarterLength) and (thisNote.quarterLength > nextNote.quarterLength)): @@ -304,10 +307,10 @@ def findPhraseBoundaries(book=4, madrigal=12): for thisOffset in sorted(phraseScoresByOffset.keys()): psbo = phraseScoresByOffset[thisOffset] if psbo > 0: - print (thisOffset, psbo) + print(thisOffset, psbo) relevantNote = flattenedBass.getElementAtOrBefore(thisOffset - 0.1) if hasattr(relevantNote, 'score'): - print ("adjusting score from %d to %d for note in measure %d" % ( + print('adjusting score from %d to %d for note in measure %d' % ( relevantNote.score, relevantNote.score + psbo, relevantNote.measureNumber)) relevantNote.score += psbo else: @@ -320,8 +323,8 @@ def findPhraseBoundaries(book=4, madrigal=12): if __name__ == '__main__': - #spliceAnalysis() - #analyzeBooks(books=[3, 4, 5]) - #analyzeBooks(books=[4], start=10, end=10, show=True, strict=True) + # spliceAnalysis() + # analyzeBooks(books=[3, 4, 5]) + # analyzeBooks(books=[4], start=10, end=10, show=True, strict=True) findPhraseBoundaries(book=4, madrigal=12) - #monteverdiParallels(books=[3], start=1, end=1, show=True, strict=True) + # monteverdiParallels(books=[3], start=1, end=1, show=True, strict=True) diff --git a/music21_tools/theory/mgtaPart1.py b/music21_tools/theory/mgtaPart1.py index 2652561..e866b74 100644 --- a/music21_tools/theory/mgtaPart1.py +++ b/music21_tools/theory/mgtaPart1.py @@ -46,7 +46,7 @@ def ch1_basic_I_A(show=True, *arguments, **keywords): # get direction of enharmonic move? # a move upward goes from f to g-, then a--- - #n.pitch.getEnharmonic(1) + # n.pitch.getEnharmonic(1) found.append(None) if show: for i in range(len(pitches)): @@ -62,16 +62,16 @@ def ch1_basic_I_B(show=True, *arguments, **keywords): ''' pitches = [('a#', 'b'), ('b-', 'c#'), ('g-', 'a'), ('d-', 'c##'), ('f', 'e'), ('f#', 'e')] - for i,j in pitches: + for i, j in pitches: n1 = note.Note(i) n2 = note.Note(j) i1 = interval.notesToInterval(n1, n2) - if i1.intervalClass == 1: # by interval class - unused_mark = "H" + if i1.intervalClass == 1: # by interval class + unused_mark = 'H' elif i1.intervalClass == 2: - unused_mark = "W" + unused_mark = 'W' else: - unused_mark = "N" + unused_mark = 'N' # no keyboard diagram yet! # k1 = keyboard.Diagram() @@ -158,8 +158,8 @@ def ch1_basic_II_A_1(show=True, *arguments, **keywords): *- ''' exercise = converter.parseData(humdata) - #exercise = music21.parseData("ch1_basic_II_A_1.xml") - for n in exercise.flatten().notes: # have to use flat here + # exercise = music21.parseData("ch1_basic_II_A_1.xml") + for n in exercise.flatten().notes: # have to use flat here n.lyric = n.nameWithOctave if show: exercise.show() @@ -188,10 +188,10 @@ def ch1_basic_II_A_2(show=True, *arguments, **keywords): *- ''' exercise = converter.parseData(humdata) - for n in exercise.flatten().notes: # have to use flat here + for n in exercise.flatten().notes: # have to use flat here n.lyric = n.nameWithOctave exercise.insert(0, clef.BassClef()) - exercise = exercise.sorted # need sorted to get clef + exercise = exercise.sorted # need sorted to get clef if show: exercise.show() @@ -200,7 +200,9 @@ def ch1_basic_II_A_2(show=True, *arguments, **keywords): def ch1_basic_II_B_1(show=True, *arguments, **keywords): ''' p4. - For each of the five trebleclef pitches on the left, write the alto-clef equivalent on the right. Then label each pitch with the correct name and octave designation. + For each of the five trebleclef pitches on the left, write the alto-clef + equivalent on the right. Then label each pitch with the correct name and + octave designation. ''' humdata = ''' **kern @@ -212,7 +214,7 @@ def ch1_basic_II_B_1(show=True, *arguments, **keywords): *- ''' exercise = converter.parseData(humdata) - for n in exercise.flatten().notes: # have to use flat here + for n in exercise.flatten().notes: # have to use flat here n.lyric = n.nameWithOctave exercise.insert(0, clef.AltoClef()) if show: @@ -222,11 +224,13 @@ def ch1_basic_II_B_1(show=True, *arguments, **keywords): def ch1_basic_II_B_2(show=True, *arguments, **keywords): ''' p4. - For each of the five bass clef pitches on the left, write the tenor-clef equivalent on the right. Then label each pitch with the correct name and octave designation. + For each of the five bass clef pitches on the left, write the tenor-clef + equivalent on the right. Then label each pitch with the correct name and + octave designation. ''' humdata = '**kern\n1F#1e-\n1B\n1D-\n1c\n*-' exercise = converter.parseData(humdata) - for n in exercise.flatten().notes: # have to use flat here + for n in exercise.flatten().notes: # have to use flat here n.lyric = n.nameWithOctave exercise.insert(0, clef.TenorClef()) if show: @@ -245,7 +249,7 @@ def ch1_basic_II_C(data, intervalShift): n1 = note.Note(e) n1.quarterLength = 4 n2 = n1.transpose(intervalShift) - m.append(chord.Chord([n1, n2])) # chord to show both + m.append(chord.Chord([n1, n2])) # chord to show both else: m.append(e) m.timeSignature = m.bestTimeSignature() @@ -331,7 +335,7 @@ def ch1_writing_I_A_1(show=True, *arguments, **keywords): ''' ex = converter.parseData(humdata) ex = ex.transpose('p8') - ex.insert(0, clef.BassClef()) # maintain clef + ex.insert(0, clef.BassClef()) # maintain clef if show: ex.show() @@ -354,7 +358,7 @@ def ch1_writing_I_A_2(show=True, *arguments, **keywords): # this notation excerpt is incomplete ex = converter.parseData(humdata) ex = ex.transpose('p8') - ex.insert(0, clef.TrebleClef()) # maintain clef + ex.insert(0, clef.TrebleClef()) # maintain clef if show: ex.show() @@ -365,8 +369,8 @@ def ch1_writing_I_B_1(show=True, *arguments, **keywords): Transcribe these melodies into the clef specified without changing octaves. ''' # camptown races - ex = converter.parse("tinynotation: 2/4 g8 g e g", makeNotation=False) - ex.insert(0, clef.AltoClef()) # maintain clef + ex = converter.parse('tinynotation: 2/4 g8 g e g', makeNotation=False) + ex.insert(0, clef.AltoClef()) # maintain clef if show: ex.show() @@ -456,7 +460,7 @@ def ch1_writing_II_A(show=True, *arguments, **keywords): import random from music21 import stream, expressions, pitch - dirWeight = [-1, 1] # start with an even distribution + dirWeight = [-1, 1] # start with an even distribution s = stream.Stream() nStart = note.Note('g4') @@ -471,7 +475,7 @@ def ch1_writing_II_A(show=True, *arguments, **keywords): if len(s) > 4 and n.pitch.pitchClass == nStart.pitch.pitchClass: n.expressions.append(expressions.Fermata()) break - if len(s) > 30: # emergency break in case the piece is too long + if len(s) > 30: # emergency break in case the piece is too long break direction = random.choice(dirWeight) if direction == 1: @@ -484,9 +488,9 @@ def ch1_writing_II_A(show=True, *arguments, **keywords): break # end b/c our transposition have exceeded accidental range iSpread = interval.notesToInterval(nStart, n) - if iSpread.direction == -1: # we are below our target, favor upward + if iSpread.direction == -1: # we are below our target, favor upward dirWeight = [-1, 1, 1] - if iSpread.direction == 1: # we are above our target, favor down + if iSpread.direction == 1: # we are above our target, favor down dirWeight = [-1, -1, 1] if show: @@ -550,10 +554,10 @@ def ch2_basic_I_A_1(show=True, *arguments, **keywords): ex = stream.Stream() ex.insert(key.KeySignature(1)) - data = [[('d6',1.5)], - [('d6',1.5)], - [('d6',.5),('c6',.5),('b5',.5)], - [('rest',.5),('b5',.25),('c6',.25),('d6',.5)], + data = [[('d6', 1.5)], + [('d6', 1.5)], + [('d6', .5), ('c6', .5), ('b5', .5)], + [('rest', .5), ('b5', .25), ('c6', .25), ('d6', .5)], ] for mData in data: @@ -608,13 +612,16 @@ def ch2_basic_I_B(show=True, *arguments, **keywords): (Given meter type and one of meter, beat unit, beat division, full bar divisions, provide the other data) ''' - # a brute force way to do this might have a function in meter.py that returns a number of candidates for a given meter type and some other parameter (bar duration). then, these values can be tested for match. + # a brute force way to do this might have a function in meter.py that returns + # a number of candidates for a given meter type and some other parameter + # (bar duration). then, these values can be tested for match. pass def ch2_basic_I_C(show=True, *arguments, **keywords): '''p. 13 - Complete the chart below. (For a given meter, provide meter type, beat unit, beat division, and beat subdivision.) + Complete the chart below. (For a given meter, provide meter type, beat unit, + beat division, and beat subdivision.) ''' from music21 import meter, stream @@ -711,7 +718,7 @@ def ch2_writing_I_A(tsStr, barGroups): for b in barGroups: summation = 0 for ql in b: - if ql > 0: # quick encoding: negative values for rests + if ql > 0: # quick encoding: negative values for rests n = note.Note() else: n = note.Rest() @@ -729,7 +736,8 @@ def ch2_writing_I_A(tsStr, barGroups): def ch2_writing_I_A_1(show=True, *arguments, **keywords): '''p. 14 - Complete the rhythms below by adding one note value that completes any measure with too few beats. + Complete the rhythms below by adding one note value that completes any + measure with too few beats. ''' barGroups = ([.5], [.75, .25], [.5, .75], [.25, .25, .25, .25], [.5], []) ex = ch2_writing_I_A('3/8', barGroups) @@ -742,7 +750,9 @@ def ch2_writing_I_A_1(show=True, *arguments, **keywords): def ch2_writing_I_A_2(show=True, *arguments, **keywords): '''p. 14 ''' - barGroups = ([.75, .25, .25, .25, .25, .25, 1.5], [.25, .25, .5, -.5, .5], [2, .25, .25, .25, .25, .25, .25], [3]) + barGroups = ([.75, .25, .25, .25, .25, .25, 1.5], + [.25, .25, .5, -.5, .5], + [2, .25, .25, .25, .25, .25, .25], [3]) ex = ch2_writing_I_A('4/4', barGroups) if show: ex.show() @@ -752,7 +762,8 @@ def ch2_writing_I_A_2(show=True, *arguments, **keywords): def ch2_writing_I_A_3(show=True, *arguments, **keywords): '''p. 14 ''' - barGroups = ([2, 1.5, .5, .5, .5, .5], [-.5, .5, .5, .5, 1, 1, 1], [4, 1.5], [.5, .5, .5, .5, .5, 1, .5]) + barGroups = ([2, 1.5, .5, .5, .5, .5], [-.5, .5, .5, .5, 1, 1, 1], + [4, 1.5], [.5, .5, .5, .5, .5, 1, .5]) ex = ch2_writing_I_A('3/2', barGroups) if show: ex.show() @@ -815,7 +826,9 @@ def ch2_writing_I_B_5(show=True, *arguments, **keywords): def ch2_writing_II_A(show=True, *arguments, **keywords): '''p. 15 - Each of these pieces begins with an anacrusis. What note value (or note value plus rest) could the composer use to fill the last measure of the compositions correctly. + Each of these pieces begins with an anacrusis. What note value (or note value + plus rest) could the composer use to fill the last measure of the compositions + correctly. ''' pass @@ -918,20 +931,20 @@ def ch2_writing_III_B(src): # note that ending ties are not given with tiny notation group = [] for n in s2.notesAndRests: - #environLocal.printDebug([n, n.tie]) - if n.tie != None or len(group) > 0: - if n.tie != None and n.tie.type != 'stop': + # environLocal.printDebug([n, n.tie]) + if n.tie is not None or len(group) > 0: + if n.tie is not None and n.tie.type != 'stop': group.append(n) - else: # end of tied notes + else: # end of tied notes group.append(n) - ql= sum([x.quarterLength for x in group]) + ql = sum([x.quarterLength for x in group]) for i in range(len(group)): - if i == 0: # keep first, extend dur + if i == 0: # keep first, extend dur group[i].quarterLength = ql group[i].tie = None # remove tie - else: # remove from source + else: # remove from source s2.remove(group[i]) - group = [] # reset + group = [] # reset environLocal.printDebug(['post editing']) @@ -943,7 +956,7 @@ def ch2_writing_III_B_1(show=True, *arguments, **keywords): Renotate the following rhythms without ties. ''' - ex = converter.parse("tinynotation: 3/8 c8~ c16 c16 c16 c16 c8~ c8 c c16 c~ c c c8 c4~ c8") + ex = converter.parse('tinynotation: 3/8 c8~ c16 c16 c16 c16 c8~ c8 c c16 c~ c c c8 c4~ c8') ex = ch2_writing_III_B(ex) if show: @@ -955,7 +968,8 @@ def ch2_writing_III_B_1(show=True, *arguments, **keywords): def ch2_writing_III_B_2(show=True, *arguments, **keywords): '''p. 17 ''' - ex = converter.parse("tinynotation: 4/4 c4~ c8 c16 c c8 c~ c c c2~ c4 c8 c8 c8~ c16 c c8~ c16 c c2") + ex = converter.parse( + 'tinynotation: 4/4 c4~ c8 c16 c c8 c~ c c c2~ c4 c8 c8 c8~ c16 c c8~ c16 c c2') ex = ch2_writing_III_B(ex) if show: @@ -967,7 +981,7 @@ def ch2_writing_III_B_2(show=True, *arguments, **keywords): def ch2_writing_III_B_3(show=True, *arguments, **keywords): '''p. 17 ''' - ex = converter.parse("tinynotation: 3/2 c2~ c4 c c~ c8 c c4 c~ c c c2 c2 c2~ c4 c c1~ c2") + ex = converter.parse('tinynotation: 3/2 c2~ c4 c c~ c8 c c4 c~ c c c2 c2 c2~ c4 c c1~ c2') ex = ch2_writing_III_B(ex) if show: @@ -993,7 +1007,9 @@ def ch2_writing_IV_A(show=True, *arguments, **keywords): def ch2_writing_IV_B(show=True, *arguments, **keywords): '''p. 18 ''' - ex = converter.parse("tinynotation: 2/4 c8. c16 e8 g c'4. r8 a8. a16 c'8 a g4. e16 f g8 g8 e16 e g16 g a8 g8 e4 d8 e16 f e d8 d16 c4.") + ex = converter.parse( + "tinynotation: 2/4 c8. c16 e8 g c'4. r8 a8. a16 c'8 a g4. e16 f g8 g8" + ' e16 e g16 g a8 g8 e4 d8 e16 f e d8 d16 c4.') ex = ex.makeMeasures() ex.makeBeams(inPlace=True) @@ -1015,7 +1031,9 @@ def ch2_writing_V_A(show=True, *arguments, **keywords): from music21 import key # note: tiny is not encoding C#s for c'#4 properly (it seems) - ex = converter.parse("tinynotation: 3/2 g#1 f#4 g#4 a1 g#2 f#1 g#4. en8 g#2 f#4 r4 f#4 d#8 B8 e2 r4 e4 a4. a8 a2 g#4 g# b4. e8 a2~ a4 a4 d'n4. d'8 d'n2 c'#4 c'# c'# c'#") + ex = converter.parse( + 'tinynotation: 3/2 g#1 f#4 g#4 a1 g#2 f#1 g#4. en8 g#2 f#4 r4 f#4 d#8 B8' + " e2 r4 e4 a4. a8 a2 g#4 g# b4. e8 a2~ a4 a4 d'n4. d'8 d'n2 c'#4 c'# c'# c'#") ex.insert(0, key.KeySignature(4)) # presently, this only works if makeAccidentals is called before make measures @@ -1295,7 +1313,9 @@ def ch3_analysis_II_B(show=True, *arguments, **keywords): def ch4_basic_I_A(show=True, *arguments, **keywords): '''p. 33 - For each major key, write out the scale. Circle degree 6. Write out a new scale that begins ont he pitch class you circled. Write the name of this relative-minor scale on the line indicated. + For each major key, write out the scale. Circle degree 6. Write out a new + scale that begins ont he pitch class you circled. Write the name of this + relative-minor scale on the line indicated. ''' pass @@ -1589,13 +1609,13 @@ def ch5_writing_IV_A(show=True, *arguments, **keywords): ex.insert(meter.TimeSignature('6/4')) ex.insert(key.KeySignature(3)) - for p, d in [(None,1), ('f#3',1),('g#3',1),('a3',4), - ('g#3',.5),('a#3',.5),('b3',2), - ('a#3',.5),('g#3',.5),('a#3',.5),('b#3',.5), - ('c#4',1.5),('b3',.5),('a3',.5),('c#4',.5),('b3',.5),('a3',.5), - ('g#3',2),('f#3',3), (None,.5), ('c#4',.5),('c#4',.5), - ('b3',.5),('b3',.5),('a#3',.5)]: - if p == None: + for p, d in [(None, 1), ('f#3', 1), ('g#3', 1), ('a3', 4), + ('g#3', .5), ('a#3', .5), ('b3', 2), + ('a#3', .5), ('g#3', .5), ('a#3', .5), ('b#3', .5), + ('c#4', 1.5), ('b3', .5), ('a3', .5), ('c#4', .5), ('b3', .5), ('a3', .5), + ('g#3', 2), ('f#3', 3), (None, .5), ('c#4', .5), ('c#4', .5), + ('b3', .5), ('b3', .5), ('a#3', .5)]: + if p is None: n = note.Rest() else: n = note.Note(p) @@ -1817,19 +1837,13 @@ def ch5_analysis_C(show=True, *arguments, **keywords): ] -class TestExternal(unittest.TestCase): # pragma: no cover - def runTest(self): - pass - +class TestExternal(unittest.TestCase): # pragma: no cover def testBasic(self): for func in FUNCTIONS: func(show=True, play=False) class Test(unittest.TestCase): - def runTest(self): - pass - def testBasic(self): for func in FUNCTIONS: func(show=False, play=False) @@ -1838,44 +1852,40 @@ def testBasic(self): # ------------------------------------------------------------------------------ -if __name__ == "__main__": - #ch2_writing_III_A_1(show=True) +if __name__ == '__main__': + # ch2_writing_III_A_1(show=True) if len(sys.argv) == 1: music21.mainTest(Test) else: pass - #b = TestExternal() - #b.testBasic() - - #ch1_writing_I_B_1(show=True) - #ch1_writing_I_B_2(show=True) - #ch1_writing_I_B_3(show=True) - #ch1_basic_II_C_2(show=True) - - #ch5_writing_IV_A(show=True) + # b = TestExternal() + # b.testBasic() + # ch1_writing_I_B_1(show=True) + # ch1_writing_I_B_2(show=True) + # ch1_writing_I_B_3(show=True) + # ch1_basic_II_C_2(show=True) - #ch2_basic_I_A_1(show=True) + # ch5_writing_IV_A(show=True) - #ch2_writing_III_A_1(show=True) - #ch2_writing_III_A_2(show=True) - #ch2_writing_III_A_3(show=True) + # ch2_basic_I_A_1(show=True) - #ch2_writing_III_B_3(show=True) + # ch2_writing_III_A_1(show=True) + # ch2_writing_III_A_2(show=True) + # ch2_writing_III_A_3(show=True) - #ch2_writing_I_A_1(show=True) - #ch2_writing_I_A_2(show=True) - #ch2_writing_I_A_3(show=True) - #ch2_writing_I_A_4(show=True) - #ch2_writing_I_A_5(show=True) + # ch2_writing_III_B_3(show=True) - #ch2_writing_IV_B(show=True) - #ch2_writing_V_A(show=True) - #ch2_writing_III_B_1() + # ch2_writing_I_A_1(show=True) + # ch2_writing_I_A_2(show=True) + # ch2_writing_I_A_3(show=True) + # ch2_writing_I_A_4(show=True) + # ch2_writing_I_A_5(show=True) -# ----------------------------------------------------------------------------- -# eof + # ch2_writing_IV_B(show=True) + # ch2_writing_V_A(show=True) + # ch2_writing_III_B_1() diff --git a/music21_tools/theory/mgtaPart2.py b/music21_tools/theory/mgtaPart2.py index f8d2326..2f077e1 100755 --- a/music21_tools/theory/mgtaPart2.py +++ b/music21_tools/theory/mgtaPart2.py @@ -23,32 +23,23 @@ # ------------------------------------------------------------------------------ # all are tests class Test(unittest.TestCase): - - def runTest(self): - pass - -# ------------------------------------------------------------------------------ -# CHAPTER 6 -# ------------------------------------------------------------------------------ -# Basic Elements -# ------------------------------------------------------------------------------ -# I. Writing generic intervals (melodic) - - + # ------------------------------------------------------------------------------ + # CHAPTER 6 + # ------------------------------------------------------------------------------ + # Basic Elements + # ------------------------------------------------------------------------------ + # I. Writing generic intervals (melodic) def xtest_Ch6_basic_I_A(self, *arguments, **keywords): '''p55 Write a whole note on the specified generic interval. Do not add sharps or flats. - MSC: 2013 Oct -- No longer works now that convertGenericToSemitone() [buggy] has been removed. + MSC: 2013 Oct -- No longer works now that convertGenericToSemitone() + [buggy] has been removed. ''' pass - - - -# ------------------------------------------------------------------------------ -# II. Writing major and perfect pitch intervals - + # ------------------------------------------------------------------------------ + # II. Writing major and perfect pitch intervals def test_Ch6_basic_II_A(self, *arguments, **keywords): '''p. 55 Write the specified melodic interval above the given note. @@ -71,7 +62,7 @@ def test_Ch6_basic_II_A(self, *arguments, **keywords): if i == len(pitches1): s.append(clef.BassClef()) - p = pitch.Pitch(p) # convert string to obj + p = pitch.Pitch(p) # convert string to obj iObj = interval.Interval(intervals[i]) c = chord.Chord([p, p.transpose(iObj)], type='whole') s.append(c) @@ -86,128 +77,14 @@ def test_Ch6_basic_II_A(self, *arguments, **keywords): self.assertEqual(match, ['C5', 'C#5', 'C5', 'C#5', 'B-4', 'E3', 'B-2', 'B-2', 'A-3', 'B-2']) - def test_Ch6_basic_II_B(self, *arguments, **keywords): - pass - -# ------------------------------------------------------------------------------ -# III. Writing major, minor, and perfect pitch intervals - - -# ------------------------------------------------------------------------------ -# IV. Writing diminished and augmented pitch intervals - -# ------------------------------------------------------------------------------ -# V. Enharmonically equivalent intervals - - -# ------------------------------------------------------------------------------ -# VI. Interval inversion - - -# ------------------------------------------------------------------------------ -# VII. Interval class - - - -# ------------------------------------------------------------------------------ -# VII. Interval class - - - -# ------------------------------------------------------------------------------ -# Writing Exercises -# ------------------------------------------------------------------------------ -# Writing intervals - - -# ------------------------------------------------------------------------------ -# Analysis -# ------------------------------------------------------------------------------ -# I. Harmonic and melodic intervals - - - -# ------------------------------------------------------------------------------ -# II. Melodic intervals, compound intervals, and interval class - - - - - -# ------------------------------------------------------------------------------ -# CHAPTER 7: Triads and Seventh Chords -# ------------------------------------------------------------------------------ -# Basic Elements -# ------------------------------------------------------------------------------ -# I. Building triads above a scale - - - -# ------------------------------------------------------------------------------ -# II. Spelling isolated triads - - -# ------------------------------------------------------------------------------ -# III. Scale-degree triads - - -# ------------------------------------------------------------------------------ -# IV. Building seventh chords above a scale - - -# ------------------------------------------------------------------------------ -# V. Spelling isolated seventh chords - - - -# ------------------------------------------------------------------------------ -# VI. Scale-degree triads and seventh chords in inversion - - -# ------------------------------------------------------------------------------ -# Analysis -# ------------------------------------------------------------------------------ -# I. Brief analysis - - -# ------------------------------------------------------------------------------ -# II. Examining a lead sheet - - - - - - - -# ------------------------------------------------------------------------------ -# CHAPTER 8: Intervals in Action -# ------------------------------------------------------------------------------ -# Basic Elements -# ------------------------------------------------------------------------------ -# I. Opening and closing patterns in note-against-note counterpoint - - - - - - - - - - - - -if __name__ == "__main__": +if __name__ == '__main__': import music21 import sys - if len(sys.argv) == 1: # normal conditions + if len(sys.argv) == 1: # normal conditions music21.mainTest(Test) elif len(sys.argv) > 1: t = Test() - #t.test_Ch6_basic_II_A(show=True) - - - + # t.test_Ch6_basic_II_A(show=True) diff --git a/music21_tools/theoryAnalysis/__init__.py b/music21_tools/theoryAnalysis/__init__.py index ee3f05b..24b2308 100644 --- a/music21_tools/theoryAnalysis/__init__.py +++ b/music21_tools/theoryAnalysis/__init__.py @@ -1,6 +1,2 @@ -__all__ = ["theoryAnalyzer", "theoryResult", "wwnortonMGTA"] - - -# ----------------------------------------------------------------------------- -# eof +__all__ = ['theoryAnalyzer', 'theoryResult', 'wwnortonMGTA'] diff --git a/music21_tools/theoryAnalysis/theoryAnalyzer.py b/music21_tools/theoryAnalysis/theoryAnalyzer.py index b58c235..456877a 100644 --- a/music21_tools/theoryAnalysis/theoryAnalyzer.py +++ b/music21_tools/theoryAnalysis/theoryAnalyzer.py @@ -46,9 +46,10 @@ * :meth:`~Analyzer.getMelodicIntervals` You can then iterate through these objects and access the attributes directly. Here is an example -of this that will analyze the root motion in a score: - +of this that will analyze the root motion in a score:: + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer + >>> from music21 import harmony >>> p = corpus.parse('leadsheet').flatten().getElementsByClass('Harmony').stream() >>> p = harmony.realizeChordSymbolDurations(p) >>> averageMotion = 0 @@ -58,9 +59,9 @@ >>> # gets a list of tuples, adjacent chord symbol objects in the score >>> for x in l: ... averageMotion += abs(x.rootInterval().intervalClass) - >>> #rootInterval() returns the interval between the roots of the first chordSymbol and second + >>> # rootInterval() returns the interval between the roots of the first chordSymbol and second >>> averageMotion = averageMotion // len(l) - >>> averageMotion #average intervalClass in this piece is about 4 + >>> averageMotion # average intervalClass in this piece is about 4 4 **get only interesting music theory voiceLeading objects from a score** @@ -122,19 +123,18 @@ 7. if all checks are true, the passingTone or neighborTone is removed from the score (because the whole point of parsing the score into voiceLeadingObjects was to maintain a direct pointer to the original object in the score.) -8. the gap created by the deletion is filled in by extending the duration of the previous note - +8. the gap created by the deletion is filled in by extending the duration of the previous note:: ->>> p = corpus.parse('bwv6.6').measures(0, 20) ->>> #_DOCS_SHOW p.show() - .. image:: images/completebach.* - :width: 500 + >>> p = corpus.parse('bwv6.6').measures(0, 20) + >>> #_DOCS_SHOW p.show() + .. image:: images/completebach.* + :width: 500 ->>> ads.removePassingTones(p) ->>> ads.removeNeighborTones(p) ->>> #_DOCS_SHOW p.show() - .. image:: images/bachnononharm.* - :width: 500 + >>> ads.removePassingTones(p) + >>> ads.removeNeighborTones(p) + >>> #_DOCS_SHOW p.show() + .. image:: images/bachnononharm.* + :width: 500 =========================================== Detailed Method Documentation @@ -161,10 +161,8 @@ from music21 import corpus from music21 import duration from music21 import exceptions21 -from music21 import harmony from music21 import interval from music21 import key -from music21 import meter from music21 import note from music21 import roman from music21 import stream @@ -178,29 +176,29 @@ class Analyzer: _DOC_ORDER = ['getVerticalities', 'getVLQs', 'getThreeNoteLinearSegments', - 'getLinearSegments', 'getVerticalityNTuplets', 'getHarmonicIntervals', - 'getMelodicIntervals', 'getParallelFifths', 'getPassingTones', - 'getNeighborTones', 'getParallelOctaves', 'identifyParallelFifths', - 'identifyParallelOctaves', 'identifyParallelUnisons', - 'identifyHiddenFifths', 'identifyHiddenOctaves', 'identifyImproperResolutions', - 'identifyLeapNotSetWithStep', 'identifyOpensIncorrectly', - 'identifyClosesIncorrectly', - 'identifyPassingTones', 'removePassingTones', 'identifyNeighborTones', - 'removeNeighborTones', 'identifyDissonantHarmonicIntervals', - 'identifyImproperDissonantIntervals', - 'identifyDissonantMelodicIntervals', 'identifyObliqueMotion', - 'identifySimilarMotion', - 'identifyParallelMotion', - 'identifyContraryMotion', 'identifyOutwardContraryMotion', - 'identifyInwardContraryMotion', 'identifyAntiParallelMotion', - 'identifyTonicAndDominant', 'identifyHarmonicIntervals', - 'identifyScaleDegrees', 'identifyMotionType', - 'identifyCommonPracticeErrors', - 'addAnalysisData', 'removeFromAnalysisData', 'setKeyMeasureMap', - 'getKeyMeasureMap', 'getKeyAtMeasure', - 'getResultsString', 'colorResults', 'getHTMLResultsString', - 'getAllPartNumPairs', 'getNotes' - ] + 'getLinearSegments', 'getVerticalityNTuplets', 'getHarmonicIntervals', + 'getMelodicIntervals', 'getParallelFifths', 'getPassingTones', + 'getNeighborTones', 'getParallelOctaves', 'identifyParallelFifths', + 'identifyParallelOctaves', 'identifyParallelUnisons', + 'identifyHiddenFifths', 'identifyHiddenOctaves', 'identifyImproperResolutions', + 'identifyLeapNotSetWithStep', 'identifyOpensIncorrectly', + 'identifyClosesIncorrectly', + 'identifyPassingTones', 'removePassingTones', 'identifyNeighborTones', + 'removeNeighborTones', 'identifyDissonantHarmonicIntervals', + 'identifyImproperDissonantIntervals', + 'identifyDissonantMelodicIntervals', 'identifyObliqueMotion', + 'identifySimilarMotion', + 'identifyParallelMotion', + 'identifyContraryMotion', 'identifyOutwardContraryMotion', + 'identifyInwardContraryMotion', 'identifyAntiParallelMotion', + 'identifyTonicAndDominant', 'identifyHarmonicIntervals', + 'identifyScaleDegrees', 'identifyMotionType', + 'identifyCommonPracticeErrors', + 'addAnalysisData', 'removeFromAnalysisData', 'setKeyMeasureMap', + 'getKeyMeasureMap', 'getKeyAtMeasure', + 'getResultsString', 'colorResults', 'getHTMLResultsString', + 'getAllPartNumPairs', 'getNotes', + ] def __init__(self): @@ -212,6 +210,7 @@ def addAnalysisData(self, score): also adds to any embedded Streams... + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> p = stream.Part() >>> s = stream.Score() >>> s.insert(0, p) @@ -234,8 +233,6 @@ def addAnalysisData(self, score): dd['ResultDict'] = defaultdict(dict) self.store[p.id] = dd - - # -------------------------------------------------------------------------------------- # Methods to split the score up into little pieces for analysis # The little pieces are all from voiceLeading.py, such as @@ -248,6 +245,7 @@ def getVerticalities(self, score, classFilterList=('Note', 'Chord', 'Harmony', ' to determine what vertical slices to take. Default is to return only objects of type Note, Chord, Harmony, and Rest. + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> n1 = note.Note('c5') >>> n1.quarterLength = 4 >>> n2 = note.Note('f4') @@ -285,6 +283,7 @@ def getVerticalities(self, score, classFilterList=('Note', 'Chord', 'Harmony', ' , {0: []})>] + >>> from music21 import harmony >>> sc3 = stream.Score() >>> p1 = stream.Part() >>> p1.append(harmony.ChordSymbol('C', quarterLength = 1)) @@ -304,7 +303,7 @@ def getVerticalities(self, score, classFilterList=('Note', 'Chord', 'Harmony', ' sid = score.id self.addAnalysisData(score) if ('Verticalities' in self.store[sid] - and self.store[sid]['Verticalities'] != None): + and self.store[sid]['Verticalities'] is not None): return self.store[sid]['Verticalities'] # if elements exist at same offset, return both @@ -324,16 +323,16 @@ def getVerticalities(self, score, classFilterList=('Note', 'Chord', 'Harmony', ' classList=classFilterList) # el = part.flatten().getElementAtOrBefore(c.offset,classList=[ # 'Note', 'Rest', 'Chord', 'Harmony']) - for el in elementStream.elements: + for el in elementStream: contentDict[partNum].append(el) - partNum +=1 + partNum += 1 else: elementStream = score.flatten().getElementsByOffset(c.offset, mustBeginInSpan=False, classList=classFilterList) # el = part.flatten().getElementAtOrBefore(c.offset, # classList=['Note', 'Rest', 'Chord', 'Harmony']) - for el in elementStream.elements: + for el in elementStream: contentDict[partNum].append(el) partNum += 1 @@ -349,6 +348,7 @@ def getVLQs(self, score, partNum1, partNum2): extracts and returns a list of the :class:`~music21.voiceLeading.VoiceLeadingQuartet` objects present between partNum1 and partNum2 in the score + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> part0.append(note.Note('c4')) @@ -383,7 +383,7 @@ def getVLQs(self, score, partNum1, partNum2): defaultKey = score.analyze('key') newKey = defaultKey vlq.key = newKey - #vlq.key = getKeyAtMeasure(score, vlq.v1n1.measureNumber) + # vlq.key = getKeyAtMeasure(score, vlq.v1n1.measureNumber) allVLQs.extend(vlqs) return allVLQs @@ -393,6 +393,7 @@ def getThreeNoteLinearSegments(self, score, partNum): objects present in partNum in the score (three note linear segments are made up of ONLY three notes) + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> part0.append(note.Note('c4')) @@ -440,6 +441,7 @@ def getLinearSegments(self, score, partNum, lengthLinearSegment, classFilterList + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> part0.append(note.Note('c4')) @@ -479,6 +481,7 @@ def getLinearSegments(self, score, partNum, lengthLinearSegment, classFilterList + >>> from music21 import * >>> sc3 = stream.Score() >>> part2 = stream.Part() >>> part2.append(harmony.ChordSymbol('D-', quarterLength=1)) @@ -495,14 +498,14 @@ def getLinearSegments(self, score, partNum, lengthLinearSegment, classFilterList ''' linearSegments = [] - #no caching here - possibly implement later on... + # no caching here - possibly implement later on... verticalities = self.getVerticalities(score) for i in range(len(verticalities) - lengthLinearSegment + 1): objects = [] for n in range(lengthLinearSegment): objects.append(verticalities[i + n].getObjectsByPart(partNum, classFilterList)) - #print objects + # print objects if (lengthLinearSegment == 3 and 'Note' in self._getTypeOfAllObjects(objects)): tnls = voiceLeading.ThreeNoteLinearSegment(objects[0], objects[1], objects[2]) @@ -521,8 +524,8 @@ def getLinearSegments(self, score, partNum, lengthLinearSegment, classFilterList def _getTypeOfAllObjects(self, objectList): setList = [] for obj in objectList: - if obj != None: - setList.append(set (obj.classes) ) + if obj is not None: + setList.append(set(obj.classes) ) if setList: lastSet = setList[0] @@ -541,6 +544,7 @@ def getVerticalityNTuplets(self, score, ntupletNum): :class:`~music21.voiceLeading.VerticalityNTuplet` or the corresponding subclass (currently only supports triplets) + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> part1 = stream.Part() @@ -558,9 +562,11 @@ def getVerticalityNTuplets(self, score, ntupletNum): >>> ads = Analyzer() >>> len(ads.getVerticalityNTuplets(sc, 3)) 2 - >>> ads.getVerticalityNTuplets(sc, 3)[1] - , - , ]> + >>> triplet = ads.getVerticalityNTuplets(sc, 3)[1] + >>> triplet.__class__.__name__ + 'VerticalityTriplet' + >>> len(triplet.verticalities) + 3 ''' verticalityNTuplets = [] sid = score.id @@ -594,6 +600,7 @@ def getHarmonicIntervals(self, score, partNum1, partNum2): returns a list of all the harmonic intervals (:class:`~music21.interval.Interval` ) occurring between the two specified parts. + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> part0.append(note.Note('e4')) @@ -633,6 +640,7 @@ def getMelodicIntervals(self, score, partNum): returns a list of all the melodic intervals (:class:`~music21.interval.Interval`) in the specified part. + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> part0.append(note.Note('c4')) @@ -650,7 +658,7 @@ def getMelodicIntervals(self, score, partNum): ''' mInvList = [] noteList = score.parts[partNum].flatten().getElementsByClass(['Note', 'Rest']) - for (i,n1) in enumerate(noteList[:-1]): + for (i, n1) in enumerate(noteList[:-1]): n2 = noteList[i + 1] if 'Note' in n1.classes and 'Note' in n2.classes: @@ -667,6 +675,7 @@ def getNotes(self, score, partNum): returns a list of notes present in the score. If Rests are present, appends None to the list + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> p = stream.Part() >>> p.repeatAppend(note.Note('C'), 3) @@ -698,6 +707,7 @@ def getAllPartNumPairs(self, score): Gets a list of all possible pairs of partNumbers: tuples (partNum1, partNum2) where 0 <= partNum1 < partnum2 < numParts + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> part0.append(note.Note('c5')) @@ -774,19 +784,19 @@ def _identifyBasedOnVLQ(self, score, partNum1, partNum2, dictKey, else: vlqList = self.getVLQs(score, partNum1, partNum2) - if endIndex is None and startIndex >=0: + if endIndex is None and startIndex >= 0: endIndex = len(vlqList) for vlq in vlqList[startIndex:endIndex]: - if testFunction(vlq) is not False: # True or value + if testFunction(vlq) is not False: # True or value tr = theoryResult.VLQTheoryResult(vlq) tr.value = testFunction(vlq) if textFunction is None: tr.text = tr.value else: tr.text = textFunction(vlq, partNum1, partNum2) - if editorialDictKey != None: + if editorialDictKey is not None: tr.markNoteEditorial(editorialDictKey, editorialValue, editorialMarkList) if color is not None: tr.color(color) @@ -807,7 +817,7 @@ def _identifyBasedOnHarmonicInterval(self, score, partNum1, partNum2, color, dic hIntvList = self.getHarmonicIntervals(score, partNum1, partNum2) for hIntv in hIntvList: - if testFunction(hIntv) is not False: # True or value + if testFunction(hIntv) is not False: # True or value tr = theoryResult.IntervalTheoryResult(hIntv) tr.value = valueFunction(hIntv) @@ -828,7 +838,7 @@ def _identifyBasedOnMelodicInterval(self, score, partNum, mIntvList = self.getMelodicIntervals(score, partNum) for mIntv in mIntvList: - if testFunction(mIntv) is not False: # True or value + if testFunction(mIntv) is not False: # True or value tr = theoryResult.IntervalTheoryResult(mIntv) tr.value = testFunction(mIntv) tr.text = textFunction(mIntv, partNum) @@ -848,7 +858,7 @@ def _identifyBasedOnNote(self, score, partNum, color, dictKey, testFunction, tex nList = self.getNotes(score, partNum) for n in nList: - if testFunction(score, n) is not False: # True or value + if testFunction(score, n) is not False: # True or value tr = theoryResult.NoteTheoryResult(n) tr.value = testFunction(score, n) @@ -900,7 +910,7 @@ def _identifyBasedOnVerticalityNTuplet(self, score, partNumToIdentify, dictKey, for vsnt in self.getVerticalityNTuplets(score, nTupletNum): if testFunction(vsnt, partNumToIdentify) is not False: tr = theoryResult.VerticalityNTupletTheoryResult(vsnt, partNumToIdentify) - if editorialDictKey != None: + if editorialDictKey is not None: tr.markNoteEditorial(editorialDictKey, editorialValue, editorialMarkDict) if textFunction is not None: tr.text = textFunction(vsnt, partNumToIdentify) @@ -949,6 +959,7 @@ def identifyParallelFifths(self, score, partNum1=None, partNum2=None, color=None + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -975,15 +986,15 @@ def identifyParallelFifths(self, score, partNum1=None, partNum2=None, color=None 'Parallel fifth in measure 1: Part 1 moves from D to E while part 2 moves from G to A' ''' testFunction = lambda vlq: vlq.parallelFifth() - textFunction = lambda vlq, pn1, pn2: ("Parallel fifth in measure " + textFunction = lambda vlq, pn1, pn2: ('Parallel fifth in measure ' + str(vlq.v1n1.measureNumber) - + ": " - + "Part " + str(pn1 + 1) - + " moves from " + vlq.v1n1.name - + " to " + vlq.v1n2.name + " " - + "while part " + str(pn2 + 1) - + " moves from " + vlq.v2n1.name - + " to " + vlq.v2n2.name) + + ': ' + + 'Part ' + str(pn1 + 1) + + ' moves from ' + vlq.v1n1.name + + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + + ' moves from ' + vlq.v2n1.name + + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -995,6 +1006,7 @@ def getParallelFifths(self, score, partNum1=None, partNum2=None): + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1043,6 +1055,7 @@ def identifyParallelOctaves(self, score, partNum1=None, partNum2=None, + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1066,14 +1079,14 @@ def identifyParallelOctaves(self, score, partNum1=None, partNum2=None, ''' testFunction = lambda vlq: vlq.parallelOctave() - textFunction = lambda vlq, pn1, pn2: ("Parallel octave in measure " - + str(vlq.v1n1.measureNumber) + ": " - + "Part " + str(pn1 + 1) - + " moves from " + vlq.v1n1.name - + " to " + vlq.v1n2.name - + " while part " + str(pn2 + 1) - + " moves from " + vlq.v2n1.name - + " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Parallel octave in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + + ' moves from ' + vlq.v1n1.name + + ' to ' + vlq.v1n2.name + + ' while part ' + str(pn2 + 1) + + ' moves from ' + vlq.v2n1.name + + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -1085,6 +1098,7 @@ def getParallelOctaves(self, score, partNum1=None, partNum2=None): + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1125,6 +1139,7 @@ def identifyParallelUnisons(self, score, partNum1=None, partNum2=None, color=Non + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1153,14 +1168,14 @@ def identifyParallelUnisons(self, score, partNum1=None, partNum2=None, color=Non ''' testFunction = lambda vlq: vlq.parallelUnison() - textFunction = lambda vlq, pn1, pn2: ("Parallel unison in measure " - + str(vlq.v1n1.measureNumber) + ": " - + "Part " + str(pn1 + 1) - + " moves from " + vlq.v1n1.name - + " to " + vlq.v1n2.name - + " while part " + str(pn2 + 1) - + " moves from " + vlq.v2n1.name - + " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Parallel unison in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + + ' moves from ' + vlq.v1n1.name + + ' to ' + vlq.v1n2.name + + ' while part ' + str(pn2 + 1) + + ' moves from ' + vlq.v2n1.name + + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -1177,6 +1192,7 @@ def identifyHiddenFifths(self, score, partNum1=None, partNum2=None, + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1200,14 +1216,14 @@ def identifyHiddenFifths(self, score, partNum1=None, partNum2=None, ''' testFunction = lambda vlq: vlq.hiddenFifth() - textFunction = lambda vlq, pn1, pn2: ("Hidden fifth in measure " - + str(vlq.v1n1.measureNumber) +": " - + "Part " + str(pn1 + 1) - + " moves from " + vlq.v1n1.name - + " to " + vlq.v1n2.name - + " while part " + str(pn2 + 1) - + " moves from " + vlq.v2n1.name - + " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Hidden fifth in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + + ' moves from ' + vlq.v1n1.name + + ' to ' + vlq.v1n2.name + + ' while part ' + str(pn2 + 1) + + ' moves from ' + vlq.v2n1.name + + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -1224,6 +1240,7 @@ def identifyHiddenOctaves(self, score, partNum1=None, partNum2=None, color=None, + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1247,14 +1264,14 @@ def identifyHiddenOctaves(self, score, partNum1=None, partNum2=None, color=None, ''' testFunction = lambda vlq: vlq.hiddenOctave() - textFunction = lambda vlq, pn1, pn2: ("Hidden octave in measure " - + str(vlq.v1n1.measureNumber) + ": " - + "Part " + str(pn1 + 1) - + " moves from " + vlq.v1n1.name - + " to " + vlq.v1n2.name - + " while part " + str(pn2 + 1) - + " moves from " + vlq.v2n1.name - + " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Hidden octave in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + + ' moves from ' + vlq.v1n1.name + + ' to ' + vlq.v1n2.name + + ' while part ' + str(pn2 + 1) + + ' moves from ' + vlq.v2n1.name + + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -1271,6 +1288,7 @@ def identifyImproperResolutions(self, score, partNum1=None, partNum2=None, color + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1293,16 +1311,16 @@ def identifyImproperResolutions(self, score, partNum1=None, partNum2=None, color ''' if editorialMarkList is None: editorialMarkList = [] - #TODO: incorporate Jose's resolution rules into this method (italian6, etc.) + # TODO: incorporate Jose's resolution rules into this method (italian6, etc.) testFunction = lambda vlq: not vlq.isProperResolution() - textFunction = lambda vlq, pn1, pn2: ("Improper resolution of " + + textFunction = lambda vlq, pn1, pn2: ('Improper resolution of ' + vlq.vIntervals[0].simpleNiceName + - " in measure " + str(vlq.v1n1.measureNumber) + ": " + - "Part " + str(pn1 + 1) + " moves from " + + ' in measure ' + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + vlq.v1n1.name + - " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name + " to " + vlq.v2n2.name) + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color, editorialDictKey='isImproperResolution', editorialValue=True, editorialMarkList=editorialMarkList) @@ -1320,6 +1338,7 @@ def identifyLeapNotSetWithStep(self, score, partNum1=None, partNum2=None, + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1344,14 +1363,14 @@ def identifyLeapNotSetWithStep(self, score, partNum1=None, partNum2=None, ''' testFunction = lambda vlq: vlq.leapNotSetWithStep() - textFunction = lambda vlq, pn1, pn2: ("Leap not set with step in measure " - + str(vlq.v1n1.measureNumber) + ": " - + "Part " + str(pn1 + 1) - + " moves from " + vlq.v1n1.name - + " to " + vlq.v1n2.name - + " while part " + str(pn2 + 1) - + " moves from " + vlq.v2n1.name - + " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Leap not set with step in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + + ' moves from ' + vlq.v1n1.name + + ' to ' + vlq.v1n2.name + + ' while part ' + str(pn2 + 1) + + ' moves from ' + vlq.v2n1.name + + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -1363,6 +1382,7 @@ def identifyOpensIncorrectly(self, score, partNum1=None, partNum2=None, + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1387,7 +1407,7 @@ def identifyOpensIncorrectly(self, score, partNum1=None, partNum2=None, ''' testFunction = lambda vlq: vlq.opensIncorrectly() - textFunction = lambda vlq, pn1, pn2: "Opening harmony is not in style" + textFunction = lambda vlq, pn1, pn2: 'Opening harmony is not in style' self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color, startIndex = 0, endIndex = 1) @@ -1399,6 +1419,7 @@ def identifyClosesIncorrectly(self, score, partNum1=None, partNum2=None, color=N + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1425,7 +1446,7 @@ def identifyClosesIncorrectly(self, score, partNum1=None, partNum2=None, color=N ''' testFunction = lambda vlq: vlq.closesIncorrectly() - textFunction = lambda vlq, pn1, pn2: "Closing harmony is not in style" + textFunction = lambda vlq, pn1, pn2: 'Closing harmony is not in style' self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color, startIndex=-1) @@ -1445,6 +1466,8 @@ def identifyPassingTones(self, score, partNumToIdentify=None, color=None, dictKe :class:`~music21.editorial.NoteEditorial` value of editorialValue at ``note.editorial.[editorialDictKey]`` + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer + >>> from music21 import * >>> sc = stream.Score() >>> sc.insert(0, meter.TimeSignature('2/4')) >>> part0 = stream.Part() @@ -1485,8 +1508,8 @@ def getPassingTones(self, score, dictKey=None, partNumToIdentify=None, unaccente returns a list of all passing tones present in the score, as identified by :meth:`~music21.voiceLeading.VerticalityTriplet.hasPassingTone` - - + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer + >>> from music21 import * >>> sc = stream.Score() >>> sc.insert(0, meter.TimeSignature('2/4')) >>> part0 = stream.Part() @@ -1526,8 +1549,8 @@ def getNeighborTones(self, score, dictKey=None, partNumToIdentify=None, unaccent returns a list of all passing tones present in the score, as identified by :meth:`~music21.voiceLeading.VerticalityTriplet.hasNeighborTone` - - + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer + >>> from music21 import * >>> sc = stream.Score() >>> sc.insert(0, meter.TimeSignature('2/4')) >>> part0 = stream.Part() @@ -1568,7 +1591,8 @@ def removePassingTones(self, score, dictKey='unaccentedPassingTones'): extending note duraitons (method under development) - + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer + >>> from music21 import * >>> sc = stream.Score() >>> sc.insert(0, meter.TimeSignature('2/4')) >>> part0 = stream.Part() @@ -1597,7 +1621,7 @@ def removePassingTones(self, score, dictKey='unaccentedPassingTones'): sid = score.id self.getPassingTones(score, dictKey=dictKey) for tr in self.store[sid]['ResultDict'][dictKey]: - a = tr.vsnt.tnlsDict[tr.partNumIdentified] #identifiedThreeNoteLinearSegment + a = tr.vsnt.tnlsDict[tr.partNumIdentified] # identifiedThreeNoteLinearSegment durationNewTone = a.n1.duration.quarterLength + a.n2.duration.quarterLength for obj in score.recurse(): if obj.id == a.n2.id: @@ -1613,6 +1637,8 @@ def removeNeighborTones(self, score, dictKey='unaccentedNeighborTones'): extending note duraitons (method under development) + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer + >>> from music21 import * >>> sc = stream.Score() >>> sc.insert(0, meter.TimeSignature('2/4')) >>> part0 = stream.Part() @@ -1641,7 +1667,7 @@ def removeNeighborTones(self, score, dictKey='unaccentedNeighborTones'): sid = score.id self.getNeighborTones(score, dictKey=dictKey) for tr in self.store[sid]['ResultDict'][dictKey]: - a = tr.vsnt.tnlsDict[tr.partNumIdentified] #identifiedThreeNoteLinearSegment + a = tr.vsnt.tnlsDict[tr.partNumIdentified] # identifiedThreeNoteLinearSegment durationNewTone = a.n1.duration.quarterLength + a.n2.duration.quarterLength for obj in score.recurse(): if obj.id == a.n2.id: @@ -1649,7 +1675,7 @@ def removeNeighborTones(self, score, dictKey='unaccentedNeighborTones'): break a.n1.duration = duration.Duration(durationNewTone) score.stripTies(inPlace=True, matchByPitch=True) - #a.n1.color = 'red' + # a.n1.color = 'red' self.store[sid]['Verticalities'] = None def identifyNeighborTones(self, score, partNumToIdentify=None, color=None, dictKey=None, @@ -1662,8 +1688,8 @@ def identifyNeighborTones(self, score, partNumToIdentify=None, color=None, dictK on weak beats). unaccentedOnly by default set to True - - + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer + >>> from music21 import * >>> sc = stream.Score() >>> sc.insert(0, meter.TimeSignature('2/4')) >>> part0 = stream.Part() @@ -1697,7 +1723,7 @@ def identifyNeighborTones(self, score, partNumToIdentify=None, color=None, dictK ' identified as a neighbor tone in part ' + str(pn + 1)) self._identifyBasedOnVerticalityNTuplet(score, partNumToIdentify, dictKey, testFunction, textFunction, color, editorialDictKey, editorialValue, - editorialMarkDict={1:[partNumToIdentify]}, nTupletNum=3) + editorialMarkDict={1: [partNumToIdentify]}, nTupletNum=3) def identifyDissonantHarmonicIntervals(self, score, partNum1=None, partNum2=None, color=None, dictKey='dissonantHarmonicIntervals'): @@ -1713,6 +1739,7 @@ def identifyDissonantHarmonicIntervals(self, score, partNum1=None, partNum2=None + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1740,13 +1767,13 @@ def identifyDissonantHarmonicIntervals(self, score, partNum1=None, partNum2=None from F to B between part 1 and part 2' ''' testFunction = lambda hIntv: hIntv is not None and not hIntv.isConsonant() - textFunction = lambda hIntv, pn1, pn2: ("Dissonant harmonic interval in measure " - + str(hIntv.noteStart.measureNumber) + ": " - + str(hIntv.simpleNiceName) + " from " - + str(hIntv.noteStart.name) + " to " + textFunction = lambda hIntv, pn1, pn2: ('Dissonant harmonic interval in measure ' + + str(hIntv.noteStart.measureNumber) + ': ' + + str(hIntv.simpleNiceName) + ' from ' + + str(hIntv.noteStart.name) + ' to ' + str(hIntv.noteEnd.name) - + " between part " + str(pn1 + 1) - + " and part " + str(pn2 + 1)) + + ' between part ' + str(pn1 + 1) + + ' and part ' + str(pn2 + 1)) self._identifyBasedOnHarmonicInterval(score, partNum1, partNum2, color, dictKey, testFunction, textFunction) @@ -1760,6 +1787,7 @@ def identifyImproperDissonantIntervals(self, score, partNum1=None, partNum2=None + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1783,7 +1811,8 @@ def identifyImproperDissonantIntervals(self, score, partNum1=None, partNum2=None >>> len(ads.store[sc.id]['ResultDict']['improperDissonantIntervals']) 1 >>> ads.store[sc.id]['ResultDict']['improperDissonantIntervals'][0].text - 'Improper dissonant harmonic interval in measure 1: Perfect Fourth from B to E between part 1 and part 2' + 'Improper dissonant harmonic interval in measure 1: + Perfect Fourth from B to E between part 1 and part 2' ''' sid = score.id @@ -1812,13 +1841,13 @@ def identifyImproperDissonantIntervals(self, score, partNum1=None, partNum2=None else: intv = resultTheoryObject.intv tr = theoryResult.IntervalTheoryResult(intv) - #tr.value = valueFunction(hIntv) - tr.text = ("Improper dissonant harmonic interval in measure " + - str(intv.noteStart.measureNumber) +": " + - str(intv.niceName) + " from " + str(intv.noteStart.name) + - " to " + str(intv.noteEnd.name) + - " between part " + str(partNum1 + 1) + - " and part " + str(partNum2 + 1)) + # tr.value = valueFunction(hIntv) + tr.text = ('Improper dissonant harmonic interval in measure ' + + str(intv.noteStart.measureNumber) + ': ' + + str(intv.niceName) + ' from ' + str(intv.noteStart.name) + + ' to ' + str(intv.noteEnd.name) + + ' between part ' + str(partNum1 + 1) + + ' and part ' + str(partNum2 + 1)) if color is not None: tr.color(color) self._updateScoreResultDict(score, dictKey, tr) @@ -1836,6 +1865,7 @@ def identifyDissonantMelodicIntervals(self, score, partNum=None, color=None, + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -1861,11 +1891,11 @@ def identifyDissonantMelodicIntervals(self, score, partNum=None, color=None, ''' testFunction = lambda mIntv: mIntv is not None and mIntv.simpleName in [ - "A2", "A4", "d5", "m7", "M7"] - textFunction = lambda mIntv, pn: ("Dissonant melodic interval in part " + str(pn + 1) + - " measure " + str(mIntv.noteStart.measureNumber) +": " + - str(mIntv.simpleNiceName) + " from " + - str(mIntv.noteStart.name) + " to " + + 'A2', 'A4', 'd5', 'm7', 'M7'] + textFunction = lambda mIntv, pn: ('Dissonant melodic interval in part ' + str(pn + 1) + + ' measure ' + str(mIntv.noteStart.measureNumber) + ': ' + + str(mIntv.simpleNiceName) + ' from ' + + str(mIntv.noteStart.name) + ' to ' + str(mIntv.noteEnd.name)) self._identifyBasedOnMelodicInterval(score, partNum, color, dictKey, testFunction, textFunction) @@ -1879,85 +1909,85 @@ def identifyDissonantMelodicIntervals(self, score, partNum=None, color=None, def identifyObliqueMotion(self, score, partNum1=None, partNum2=None, color=None): dictKey = 'obliqueMotion' testFunction = lambda vlq: vlq.obliqueMotion() - textFunction = lambda vlq, pn1, pn2: ("Oblique motion in measure " + - str(vlq.v1n1.measureNumber) +": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name + " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Oblique motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) def identifySimilarMotion(self, score, partNum1=None, partNum2=None, color=None): dictKey = 'similarMotion' testFunction = lambda vlq: vlq.similarMotion() - textFunction = lambda vlq, pn1, pn2: ("Similar motion in measure " + - str(vlq.v1n1.measureNumber) +": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name+ " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Similar motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) def identifyParallelMotion(self, score, partNum1=None, partNum2=None, color= None): dictKey = 'parallelMotion' testFunction = lambda vlq: vlq.parallelMotion() - textFunction = lambda vlq, pn1, pn2: ("Parallel motion in measure " + - str(vlq.v1n1.measureNumber) +": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name+ " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Parallel motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) def identifyContraryMotion(self, score, partNum1=None, partNum2=None, color=None): dictKey = 'contraryMotion' testFunction = lambda vlq: vlq.contraryMotion() - textFunction = lambda vlq, pn1, pn2: ("Contrary motion in measure " + - str(vlq.v1n1.measureNumber) + ": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name+ " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Contrary motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) def identifyOutwardContraryMotion(self, score, partNum1=None, partNum2=None, color=None): dictKey = 'outwardContraryMotion' testFunction = lambda vlq: vlq.outwardContraryMotion() - textFunction = lambda vlq, pn1, pn2: ("Outward contrary motion in measure " + - str(vlq.v1n1.measureNumber) + ": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + - vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name+ " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Outward contrary motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) def identifyInwardContraryMotion(self, score, partNum1=None, partNum2=None, color=None): dictKey = 'inwardContraryMotion' testFunction = lambda vlq: vlq.inwardContraryMotion() - textFunction = lambda vlq, pn1, pn2: ("Inward contrary motion in measure " + - str(vlq.v1n1.measureNumber) + ": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name+ " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Inward contrary motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, color, dictKey, testFunction, textFunction) def identifyAntiParallelMotion(self, score, partNum1=None, partNum2=None, color=None): dictKey = 'antiParallelMotion' testFunction = lambda vlq: vlq.antiParallelMotion() - textFunction = lambda vlq, pn1, pn2: ("Anti-parallel motion in measure " + - str(vlq.v1n1.measureNumber) +": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name+ " to " + vlq.v2n2.name) + textFunction = lambda vlq, pn1, pn2: ('Anti-parallel motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -1976,6 +2006,7 @@ def identifyTonicAndDominant(self, score, color=None, slice at offset 0, 6, and 7 in the piece, pass ``responseOffsetMap=[0, 6, 7]`` + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -2006,7 +2037,7 @@ def identifyTonicAndDominant(self, score, color=None, def testFunction(vs, score): noteList = vs.getObjectsByClass('Note') - if not None in noteList: + if None not in noteList: inChord = chord.Chord(noteList) inKey = self.getKeyAtMeasure(score, noteList[0].measureNumber) chordBass = noteList[-1] @@ -2020,7 +2051,7 @@ def textFunction(vs, rn): for n in vs.getObjectsByClass('Note'): notes += n.name + ',' notes = notes[:-1] - return "Roman Numeral of " + notes + ' is ' + rn + return 'Roman Numeral of ' + notes + ' is ' + rn self._identifyBasedOnVerticality(score, color, dictKey, testFunction, textFunction, responseOffsetMap=responseOffsetMap) @@ -2031,7 +2062,7 @@ def textFunction(vs, rn): # homework assignments # - #def identifyRomanNumerals(self, score, color=None, + # def identifyRomanNumerals(self, score, color=None, # dictKey='romanNumerals', responseOffsetMap = []): # ''' # Identifies the roman numerals in the piece by iterating through @@ -2079,6 +2110,7 @@ def identifyHarmonicIntervals(self, score, partNum1=None, partNum2=None, created with ``.value`` set to the string most commonly used to identify the interval (0 through 9, with A4 and d5) + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -2108,7 +2140,7 @@ def identifyHarmonicIntervals(self, score, partNum1=None, partNum2=None, ''' testFunction = lambda hIntv: hIntv.generic.undirected if hIntv is not None else False - textFunction = lambda hIntv, pn1, pn2: ("harmonic interval between " + textFunction = lambda hIntv, pn1, pn2: ('harmonic interval between ' + hIntv.noteStart.name + ' and ' + hIntv.noteEnd.name + ' between parts ' + str(pn1 + 1) + ' and ' + str(pn2 + 1) + ' is a ' + str(hIntv.niceName)) @@ -2128,6 +2160,7 @@ def identifyScaleDegrees(self, score, partNum=None, color=None, dictKey='scaleDe ''' identify all the scale degrees in the score in partNum, or if not specified ALL partNums + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -2160,7 +2193,7 @@ def identifyScaleDegrees(self, score, partNum=None, color=None, dictKey='scaleDe testFunction = lambda sc, n: (str(self.getKeyAtMeasure(sc, n.measureNumber).getScale().getScaleDegreeFromPitch( n.pitch)) ) if n is not None else False - textFunction = lambda n, pn, scaleDegree: ("scale degree of " + n.name + ' in part ' + + textFunction = lambda n, pn, scaleDegree: ('scale degree of ' + n.name + ' in part ' + str(pn + 1) + ' is ' + str(scaleDegree)) self._identifyBasedOnNote(score, partNum, color, dictKey, testFunction, textFunction) @@ -2178,6 +2211,7 @@ def identifyMotionType(self, score, partNum1=None, partNum2=None, + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -2207,13 +2241,13 @@ def identifyMotionType(self, score, partNum1=None, partNum2=None, ''' testFunction = lambda vlq: vlq.motionType().value - textFunction = lambda vlq, pn1, pn2: (vlq.motionType().value + ' Motion in measure '+ - str(vlq.v1n1.measureNumber) + ": " + - "Part " + str(pn1 + 1) + " moves from " + - vlq.v1n1.name + " to " + vlq.v1n2.name + " " + - "while part " + str(pn2 + 1) + " moves from " + - vlq.v2n1.name+ " to " + vlq.v2n2.name) if ( - vlq.motionType() != "No Motion") else 'No motion' + textFunction = lambda vlq, pn1, pn2: (vlq.motionType().value + ' Motion in measure ' + + str(vlq.v1n1.measureNumber) + ': ' + + 'Part ' + str(pn1 + 1) + ' moves from ' + + vlq.v1n1.name + ' to ' + vlq.v1n2.name + ' ' + + 'while part ' + str(pn2 + 1) + ' moves from ' + + vlq.v2n1.name + ' to ' + vlq.v2n2.name) if ( + vlq.motionType() != 'No Motion') else 'No motion' self._identifyBasedOnVLQ(score, partNum1, partNum2, dictKey, testFunction, textFunction, color) @@ -2238,10 +2272,10 @@ def identifyCommonPracticeErrors(self, score, partNum1=None, partNum2=None, self.identifyHiddenOctaves(score, partNum1, partNum2, 'green', dictKey) self.identifyParallelUnisons(score, partNum1, partNum2, 'blue', dictKey) self.identifyImproperResolutions(score, partNum1, partNum2, 'purple', dictKey) - #self.identifyLeapNotSetWithStep(score, partNum1, partNum2, 'white', dictKey) + # self.identifyLeapNotSetWithStep(score, partNum1, partNum2, 'white', dictKey) self.identifyImproperDissonantIntervals(score, partNum1, partNum2, 'white', dictKey, unaccentedOnly = True) - self.identifyDissonantMelodicIntervals(score, partNum1,'cyan', dictKey) + self.identifyDissonantMelodicIntervals(score, partNum1, 'cyan', dictKey) self.identifyOpensIncorrectly(score, partNum1, partNum2, 'brown', dictKey) self.identifyClosesIncorrectly(score, partNum1, partNum2, 'gray', dictKey) @@ -2254,6 +2288,7 @@ def getResultsString(self, score, typeList=None): returns string of all results found by calling all identify methods on the TheoryAnalyzer score + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> part0 = stream.Part() >>> p0measure1 = stream.Measure(number=1) @@ -2281,17 +2316,17 @@ def getResultsString(self, score, typeList=None): Hidden fifth in measure 1: Part 1 moves from C to D while part 2 moves from C to G Closing harmony is not in style ''' - resultStr = "" + resultStr = '' sid = score.id self.addAnalysisData(score) for resultType in self.store[sid]['ResultDict']: if typeList is None or resultType in typeList: - resultStr += resultType + ": \n" + resultStr += resultType + ': \n' for result in self.store[sid]['ResultDict'][resultType]: resultStr += result.text - resultStr += "\n" - resultStr = resultStr[0:-1] #remove final new line character + resultStr += '\n' + resultStr = resultStr[0:-1] # remove final new line character return resultStr def getHTMLResultsString(self, score, typeList=None): @@ -2299,18 +2334,18 @@ def getHTMLResultsString(self, score, typeList=None): returns string of all results found by calling all identify methods on the TheoryAnalyzer score ''' - resultStr = "" + resultStr = '' sid = score.id self.addAnalysisData(score) for resultType in self.store[sid]['ResultDict']: if typeList is None or resultType in typeList: - resultStr += "" + resultType + ":
    " + resultStr += '' + resultType + ':
      ' for result in self.store[sid]['ResultDict'][resultType]: resultStr += ("
    • " + - result.text.sub(':',":") + - "
    • ") - resultStr += "

    " + result.text.sub(':', ":") + + '') + resultStr += '

' return resultStr @@ -2334,6 +2369,7 @@ def removeFromAnalysisData(self, score, dictKeys): you'd like removed. Pass in a list of dictKeys or just a single dictionary key. + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> sc = stream.Score() >>> ads = Analyzer() >>> ads.addAnalysisData(sc) @@ -2341,7 +2377,7 @@ def removeFromAnalysisData(self, score, dictKeys): ... 'h1':'another sample response', '5':'third sample response'} >>> ads.removeFromAnalysisData(sc, 'sampleDictKey') >>> for k in sorted(list(ads.store[sc.id]['ResultDict'].keys())): - ... print("{0}\t{1}".format(k, ads.store[sc.id]['ResultDict'][k])) + ... print("{0} {1}".format(k, ads.store[sc.id]['ResultDict'][k])) 5 third sample response h1 another sample response @@ -2359,14 +2395,14 @@ def removeFromAnalysisData(self, score, dictKeys): del self.store[sid]['ResultDict'][dictKey] except Exception: pass - #raise TheoryAnalyzerException('got a dictKey to remove from + # raise TheoryAnalyzerException('got a dictKey to remove from # resultDictionary that wasnt in the dictionary: %s', dictKey) else: try: del self.store[sid]['ResultDict'][dictKeys] except Exception: pass - #raise TheoryAnalyzerException('got a dictKey to remove from resultDictionary + # raise TheoryAnalyzerException('got a dictKey to remove from resultDictionary # that wasn''t in the dictionary: %s', dictKeys) # def getKeyMeasureMap(self, score): @@ -2390,6 +2426,7 @@ def setKeyMeasureMap(self, score, keyMeasureMap): for analysis purposes only - no key object is actually added to the score. Check the music xml to verify measure numbers; pickup measures are usually 0. + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> n1 = note.Note('c5') >>> n1.quarterLength = 4 >>> n2 = note.Note('f4') @@ -2422,6 +2459,7 @@ def getKeyAtMeasure(self, score, measureNumber): uses keyMeasureMap to return music21 key object. If keyMeasureMap not specified, returns key analysis of theory score as a whole. + >>> from music21_tools.theoryAnalysis.theoryAnalyzer import Analyzer >>> s = stream.Score() >>> ads = Analyzer() @@ -2443,7 +2481,7 @@ def getKeyAtMeasure(self, score, measureNumber): return key.Key(kName) else: return keyMeasureMap[dictKey] - if measureNumber == 0: #just in case of a pickup measure + if measureNumber == 0: # just in case of a pickup measure if 1 in keyMeasureMap: return key.Key(key.convertKeyStringToMusic21KeyString(keyMeasureMap[1])) else: @@ -2459,19 +2497,18 @@ class TheoryAnalyzerException(exceptions21.Music21Exception): class Test(unittest.TestCase): def testChordMotionExample(self): - pass # in doctest -# from music21 import harmony, theoryAnalysis -# p = corpus.parse('leadsheet').flatten().getElementsByClass('Harmony') -# harmony.realizeChordSymbolDurations(p) -# averageMotion = 0 -# l = ads.getLinearSegments(p, 0, 2, ['Harmony']) -# for x in l: -# averageMotion += abs(x.rootInterval().intervalClass) -# averageMotion = averageMotion // len(l) -# self.assertEqual(averageMotion, 4) -# + pass # in doctest + # from music21 import harmony, theoryAnalysis + # p = corpus.parse('leadsheet').flatten().getElementsByClass('Harmony') + # harmony.realizeChordSymbolDurations(p) + # averageMotion = 0 + # l = ads.getLinearSegments(p, 0, 2, ['Harmony']) + # for x in l: + # averageMotion += abs(x.rootInterval().intervalClass) + # averageMotion = averageMotion // len(l) + # self.assertEqual(averageMotion, 4) + def testFastVerticalityCheck(self): - from music21 import stream sc = stream.Score() part0 = stream.Part() p0measure1 = stream.Measure(number=1) @@ -2497,28 +2534,23 @@ def testFastVerticalityCheck(self): 'Roman Numeral of B-,G is I') -class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - +class TestExternal(unittest.TestCase): # pragma: no cover def demo(self): from music21 import converter sc = converter.parse( '/Users/bhadley/Dropbox/Music21Theory/TestFiles/TheoryAnalyzer/TATest.xml') ads = Analyzer() ads.identifyCommonPracticeErrors(sc) + # sc.show() - #sc.show() def removeNHTones(self): p = corpus.parse('bwv6.6').measures(0, 20) p.show() - ads = Analyzer() ads.removePassingTones(p) ads.removeNeighborTones(p) p.show() -if __name__ == "__main__": +if __name__ == '__main__': import music21 - music21.mainTest(Test, 'moduleRelative') #, runTest='testFastVerticalityCheck') + music21.mainTest(Test, 'moduleRelative') diff --git a/music21_tools/theoryAnalysis/theoryResult.py b/music21_tools/theoryAnalysis/theoryResult.py index 0d653e8..b3e0ad8 100644 --- a/music21_tools/theoryAnalysis/theoryResult.py +++ b/music21_tools/theoryAnalysis/theoryResult.py @@ -9,8 +9,6 @@ # Copyright: Copyright © 2009-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ -import unittest - class TheoryResult: ''' A TheoryResult object is used to store information about the results @@ -26,11 +24,11 @@ class TheoryResult: 'currentColor': 'The color of the entire theory result object', } def __init__(self): - self.text = "" - self.value = "" - self.currentColor = "" + self.text = '' + self.value = '' + self.currentColor = '' - def color(self,color): + def color(self, color): ''' Bass-class method to color the individual elements in the theory result object. Polymorphically colors the theory result objects based on the type of object. @@ -246,7 +244,7 @@ class VerticalityNTupletTheoryResult(TheoryResult): def __init__(self, vsnt, partNumIdentified=None): super().__init__() - self.vsnt = vsnt #vertical slice ntuplet + self.vsnt = vsnt # vertical slice ntuplet self.partNumIdentified = partNumIdentified def color(self, color ='red', partNum=None, noteList=None): @@ -255,12 +253,12 @@ def color(self, color ='red', partNum=None, noteList=None): ''' if noteList is None: noteList = [] - if partNum != None: + if partNum is not None: print('color...', partNum, self.partNumIdentified, self.vsnt.nTupletNum, self.vsnt.tnlsDict.keys()) if self.vsnt.nTupletNum == 3: self.vsnt.tnlsDict[partNum].color(color) - elif self.partNumIdentified !=None: + elif self.partNumIdentified is not None: if self.vsnt.nTupletNum == 3: self.vsnt.tnlsDict[self.partNumIdentified].color(color, [2] ) @@ -273,30 +271,8 @@ def markNoteEditorial(self, editorialDictKey, editorialValue, editorialMarkDict= Default editorialMarkDict = {2:[1]} ''' if editorialMarkDict is None: - editorialMarkDict = {2:[1]} + editorialMarkDict = {2: [1]} for vsNum, partNumList in editorialMarkDict.items(): for unused_counter_partNum in partNumList: self.vsnt.verticalities[vsNum].getObjectsByPart(0, classFilterList=['Note']).editorial[editorialDictKey] = editorialValue - - -class Test(unittest.TestCase): - - def runTest(self): - pass - -class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - - def demo(self): - pass - - -if __name__ == "__main__": - import music21 - music21.mainTest(Test, 'moduleRelative') - - #te = TestExternal() - #te.demo() diff --git a/music21_tools/theoryAnalysis/wwnortonMGTA.py b/music21_tools/theoryAnalysis/wwnortonMGTA.py index 4406dd8..03aad5e 100644 --- a/music21_tools/theoryAnalysis/wwnortonMGTA.py +++ b/music21_tools/theoryAnalysis/wwnortonMGTA.py @@ -9,19 +9,15 @@ # Copyright: Copyright © 2009-2026 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ -_DOC_IGNORE_MODULE_OR_PACKAGE = True - import copy -import unittest - -import music21 - from music21 import converter from music21 import stream from music21 import instrument from music21 import note from . import theoryAnalyzer +_DOC_IGNORE_MODULE_OR_PACKAGE = True + class wwnortonExercise: ''' @@ -34,13 +30,13 @@ class wwnortonExercise: ''' def __init__(self): - self.xmlFileDirectory = "C:/Users/bhadley/Dropbox/Music21Theory/TestFiles/Exercises/" - #/xmlfiles/" - self.xmlFilename = "" + self.xmlFileDirectory = 'C:/Users/bhadley/Dropbox/Music21Theory/TestFiles/Exercises/' + # /xmlfiles/" + self.xmlFilename = '' self.originalExercise = stream.Stream() self.modifiedExercise = stream.Stream() self.studentExercise = stream.Stream() - self.textResultString = "" + self.textResultString = '' self.pn = {} self.ads = theoryAnalyzer.Analyzer() @@ -55,12 +51,12 @@ def loadOriginalExercise(self): self.modifiedExercise = copy.deepcopy(self.originalExercise) self.addAuxillaryParts() - def manuallyLoadOriginalExercise(self,sc): + def manuallyLoadOriginalExercise(self, sc): self.originalExercise = sc self.modifiedExercise = copy.deepcopy(self.originalExercise) self.addAuxillaryParts() - def loadStudentExercise(self,sc): + def loadStudentExercise(self, sc): for n in sc.flatten().getElementsByClass('Note'): n.color = 'black' self.studentExercise = sc @@ -69,35 +65,35 @@ def getBlankExercise(self): return self.blankExercise def _partOffsetsToPartIndecies(self): - for (i,el) in enumerate(self.modifiedExercise.elements): + for (i, el) in enumerate(self.modifiedExercise.elements): el.offset = i - #self.modifiedExercise.show('text') + # self.modifiedExercise.show('text') def _partOffsetsToZero(self): - for (i,el) in enumerate(self.modifiedExercise.elements): + for (i, el) in enumerate(self.modifiedExercise.elements): el.offset = 0 - #self.modifiedExercise.show('text') + # self.modifiedExercise.show('text') def _updatepn(self, newPartNum, direction): for partName in self.pn: existingPartNum = self.pn[partName] if existingPartNum > newPartNum: shiftedPartNum = existingPartNum + 1 - elif existingPartNum == newPartNum and direction =="above": + elif existingPartNum == newPartNum and direction == 'above': shiftedPartNum = existingPartNum + 1 else: shiftedPartNum = existingPartNum self.pn[partName] = shiftedPartNum - def addMarkerPartFromExisting(self, existingPartName, newPartName, newPartTitle="", - direction="below", rhythmType="copy", color=None): + def addMarkerPartFromExisting(self, existingPartName, newPartName, newPartTitle='', + direction='below', rhythmType='copy', color=None): partNum = self.pn[existingPartName] self._partOffsetsToPartIndecies() existingPart = self.modifiedExercise.parts[partNum] existingPartOffset = existingPart.offset - if rhythmType == "chordify": + if rhythmType == 'chordify': existingPart = self.originalExercise.chordify() newPart = copy.deepcopy(existingPart.getElementsByClass('Measure')) firstNote = True @@ -107,14 +103,14 @@ def addMarkerPartFromExisting(self, existingPartName, newPartName, newPartTitle= previousNotRestContents = copy.deepcopy(m.getElementsByClass('NotRest')) measureDuration = m.duration.quarterLength - m.removeByClass(['GeneralNote']) # includes rests + m.removeByClass(['GeneralNote']) # includes rests m.removeByClass(['Dynamic']) - m.removeByClass(['Stream']) # get voices or sub-streams + m.removeByClass(['Stream']) # get voices or sub-streams m.removeByClass(['Dynamic']) m.removeByClass(['Expression']) m.removeByClass(['KeySignature']) - if rhythmType == "quarterNotes": + if rhythmType == 'quarterNotes': for i in range(int(measureDuration)): markerNote = note.Note('c4') markerNote.notehead = 'x' @@ -142,17 +138,17 @@ def addMarkerPartFromExisting(self, existingPartName, newPartName, newPartTitle= for ks in newPart.flatten().getElementsByClass('KeySignature'): ks.sharps = 0 for c in newPart.flatten().getElementsByClass('Clef'): - c.sign = "C" + c.sign = 'C' c.line = 3 - self._updatepn(partNum,direction=direction) - if direction == "above": + self._updatepn(partNum, direction=direction) + if direction == 'above': insertLoc = existingPartOffset - 0.5 self.pn[newPartName] = partNum - elif direction == "below" or direction is None: + elif direction == 'below' or direction is None: insertLoc = existingPartOffset + 0.5 self.pn[newPartName] = partNum + 1 - self.modifiedExercise.insert(insertLoc,newPart) - #self.modifiedExercise.show('text') + self.modifiedExercise.insert(insertLoc, newPart) + # self.modifiedExercise.show('text') # Somehow needed for sorting... self.modifiedExercise._reprText() self._partOffsetsToZero() @@ -165,10 +161,10 @@ def compareMarkerLyricAnswer(self, score, taKey, markerPartName, offsetFunc, lyr correctLyric = lyricFunc(resultObj) markerNote = markerPart.flatten().getElementAtOrBefore(offset, classList=['Note']) if markerNote is None or markerNote.offset != offset: - print("No Marker") + print('No Marker') continue if markerNote.lyric != str(correctLyric): - #markerNote.lyric = markerNote.lyric + "->" + str(correctLyric) + # markerNote.lyric = markerNote.lyric + "->" + str(correctLyric) markerNote.color = 'red' def showStudentExercise(self): @@ -194,27 +190,27 @@ def __init__(self): def addAuxillaryParts(self): self.addMarkerPartFromExisting('part1', newPartName='p1ScaleDegrees', - newPartTitle="1. Scale Degrees", - direction="above") + newPartTitle='1. Scale Degrees', + direction='above') self.addMarkerPartFromExisting('part1', newPartName='motionType', - newPartTitle="2. Motion Type", - rhythmType="quarterNotes", - direction="above") + newPartTitle='2. Motion Type', + rhythmType='quarterNotes', + direction='above') self.addMarkerPartFromExisting('part1', newPartName='harmIntervals', - newPartTitle="3. Harmonic Intervals", + newPartTitle='3. Harmonic Intervals', rhythmType='chordify', - direction="below") + direction='below') def checkExercise(self): - self.ads.setKeyMeasureMap(self.studentExercise,{0:'F'}) + self.ads.setKeyMeasureMap(self.studentExercise, {0: 'F'}) self.ads.identifyMotionType(self.studentExercise, self.pn['part1'], - self.pn['part2'],dictKey='motionType') + self.pn['part2'], dictKey='motionType') self.ads.identifyScaleDegrees(self.studentExercise, self.pn['part1'], dictKey='p1ScaleDegrees') self.ads.identifyHarmonicIntervals(self.studentExercise, self.pn['part1'], - self.pn['part2'],dictKey='harmIntervals') + self.pn['part2'], dictKey='harmIntervals') scaleDegreeOffsetFunc = lambda resultObj: resultObj.n.offset scaleDegreeLyricTextFunc = lambda resultObj: resultObj.value @@ -258,11 +254,11 @@ def __init__(self): def addAuxillaryParts(self): self.addMarkerPartFromExisting('part2', newPartName='harmIntervals', - newPartTitle="1. Harmonic Intervals", - direction = "below") + newPartTitle='1. Harmonic Intervals', + direction = 'below') def checkExercise(self): - self.ads.setKeyMeasureMap(self.studentExercise, {0:'G', 5:'D', 8:'F'}) + self.ads.setKeyMeasureMap(self.studentExercise, {0: 'G', 5: 'D', 8: 'F'}) self.ads.identifyHarmonicIntervals(self.studentExercise, self.pn['part1'], self.pn['part2'], dictKey='harmIntervals') self.ads.identifyCommonPracticeErrors(self.studentExercise, self.pn['part1'], @@ -280,32 +276,3 @@ def checkExercise(self): offsetFunc=harmonicIntervalOffsetFunc, lyricFunc=harmonicIntervalTextFunc) - - -# ------------------------------------------------------------ - -class Test(unittest.TestCase): - - def runTest(self): - pass - -class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - - def demo(self): - ex = ex11_1_I() - sc = converter.parse('C:/Users/bhadley/Dropbox/Music21Theory/TestFiles/' + - 'SampleStudentResponses/S11_1_IA_completed.xml') - ex.loadStudentExercise(sc) - ex.checkExercise() - ex.showStudentExercise() - - -if __name__ == "__main__": - music21.mainTest(Test) - - #te = TestExternal() - #te.demo() - diff --git a/music21_tools/trecento/__init__.py b/music21_tools/trecento/__init__.py index c22b22c..143a0df 100644 --- a/music21_tools/trecento/__init__.py +++ b/music21_tools/trecento/__init__.py @@ -1,16 +1,8 @@ -__all__ = ['cadencebook', 'capua', 'findTrecentoFragments', 'notation', 'tonality'] - -# this is necessary to get these names available with a -# from music21 import * import statement -import sys +__all__ = ['cadencebook', 'capua', 'findTrecentoFragments', 'medren', 'notation', 'tonality'] from . import cadencebook from . import capua from . import findTrecentoFragments +from . import medren from . import notation from . import tonality - -#from music21.trecento import * - -# ----------------------------------------------------------------------------- -# eof diff --git a/music21_tools/trecento/_base.py b/music21_tools/trecento/_base.py new file mode 100644 index 0000000..099a9ec --- /dev/null +++ b/music21_tools/trecento/_base.py @@ -0,0 +1,21 @@ +# ------------------------------------------------------------------------------ +# Name: music21_tools/trecento/_base.py +# Purpose: Shared base classes for the trecento subpackage +# +# Authors: Michael Scott Asato Cuthbert +# +# Copyright: Copyright © 2026 Michael Scott Asato Cuthbert +# License: BSD, see license.txt +# ------------------------------------------------------------------------------ +''' +Shared ancestors used elsewhere in the trecento subpackage. Avoids circular imports. +''' +from music21 import meter + + +class MedievalMeter(meter.TimeSignature): + ''' + Common base class for medieval/Renaissance Meter markers + (:class:`~music21_tools.trecento.medren.Mensuration` and + :class:`~music21_tools.trecento.notation.Divisione`), for `getContextByClass` + ''' diff --git a/music21_tools/trecento/cadenceProbabilities.py b/music21_tools/trecento/cadenceProbabilities.py index 51d83f9..05373da 100644 --- a/music21_tools/trecento/cadenceProbabilities.py +++ b/music21_tools/trecento/cadenceProbabilities.py @@ -22,16 +22,16 @@ def getLandiniRandomStart(i): '''according to distribution of starting tenor notes of Landini cadences''' if i < 16: - return "A" + return 'A' if i < 35: - return "C" + return 'C' if i < 67: - return "D" + return 'D' if i < 69: - return "E" + return 'E' if i < 85: - return "F" - return "G" + return 'F' + return 'G' # def findsame(total=1000): # '''find out how often the first note and second note should be the same @@ -58,8 +58,8 @@ def getLandiniRandomStart(i): def countCadencePercentages(): ballatas = cadencebook.BallataSheet() totalPieces = 0.0 - firstNoteTotal = defaultdict(lambda:0) - lastNoteTotal = defaultdict(lambda:0) + firstNoteTotal = defaultdict(lambda: 0) + lastNoteTotal = defaultdict(lambda: 0) for thisWork in ballatas: incipit = thisWork.incipit @@ -90,19 +90,15 @@ def countCadencePercentages(): firstNoteTotal[firstNote.name] += 1 lastNoteTotal[lastNote.name] += 1 - print("First note distribution:") + print('First note distribution:') for thisName in firstNoteTotal: print(thisName, firstNoteTotal[thisName] / totalPieces) - print("Last note distribution:") + print('Last note distribution:') for thisName in lastNoteTotal: print(thisName, lastNoteTotal[thisName] / totalPieces) -if __name__ == "__main__": +if __name__ == '__main__': countCadencePercentages() - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/cadencebook.py b/music21_tools/trecento/cadencebook.py index a0c5a80..85b5a9a 100644 --- a/music21_tools/trecento/cadencebook.py +++ b/music21_tools/trecento/cadencebook.py @@ -21,8 +21,6 @@ import xlrd -_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) - from music21 import duration from music21 import expressions from music21 import metadata @@ -31,6 +29,8 @@ from . import trecentoCadence from . import polyphonicSnippet +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + class TrecentoSheet: ''' A TrecentoSheet represents a single worksheet of an Excel spreadsheet @@ -40,6 +40,7 @@ class TrecentoSheet: See the specialized subclasses below, esp. BallataSheet for more details. + >>> from music21_tools.trecento.cadencebook import TrecentoSheet >>> kyrieSheet = TrecentoSheet(sheetname='kyrie') >>> for thisKyrie in kyrieSheet: ... print(thisKyrie.title) @@ -48,13 +49,13 @@ class TrecentoSheet: Kyrie rondello ''' - filename = "cadences.xls" - sheetname = "fischer_caccia" + filename = 'cadences.xls' + sheetname = 'fischer_caccia' def __init__(self, **keywords): self.iterIndex = 2 - if "filename" in keywords: - self.filename = keywords["filename"] + if 'filename' in keywords: + self.filename = keywords['filename'] if self.filename: try: xbook = xlrd.open_workbook(str(self.filename)) @@ -62,8 +63,8 @@ def __init__(self, **keywords): xbook = xlrd.open_workbook(os.path.join(_THIS_DIR, self.filename)) - if "sheetname" in keywords: - self.sheetname = keywords["sheetname"] + if 'sheetname' in keywords: + self.sheetname = keywords['sheetname'] self.sheet = xbook.sheet_by_name(self.sheetname) self.totalRows = self.sheet.nrows @@ -113,7 +114,7 @@ def makeWork(self, rownumber=2): Row 1 is a header, so makeWork(2) gives the first piece. - + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> ballataSheet = BallataSheet() >>> b = ballataSheet.makeWork(3) >>> print(b.title) @@ -126,7 +127,7 @@ def workByTitle(self, title): ''' return the first work with TITLE in the work's title. Case insensitive - + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> ballataSheet = BallataSheet() >>> farina = ballataSheet.workByTitle('farina') >>> print(farina.title) @@ -163,45 +164,36 @@ class CacciaSheet(TrecentoSheet): ''' shortcut to a worksheet containing all the caccia cadences encoded - 2011-May: none encoded. - - + >>> from music21_tools.trecento.cadencebook import CacciaSheet >>> cacciaSheet = CacciaSheet() ''' - - sheetname = "fischer_caccia" + sheetname = 'fischer_caccia' class MadrigalSheet(TrecentoSheet): ''' shortcut to a worksheet containing all the madrigal cadences encoded - 2011-May: none encoded. - - ''' - sheetname = "fischer_madr" + sheetname = 'fischer_madr' class BallataSheet(TrecentoSheet): ''' shortcut to a worksheet containing all the ballata cadences encoded. - 2011-May: over 400 of 460 encoded; unencoded pieces are mostly fragmentary. - ''' - - sheetname = "fischer_ballata" + sheetname = 'fischer_ballata' def makeWork(self, rownumber=1): rowvalues = self.sheet.row_values(rownumber - 1) return Ballata(rowvalues, self.rowDescriptions) class KyrieSheet(TrecentoSheet): - sheetname = "kyrie" + sheetname = 'kyrie' class GloriaSheet(TrecentoSheet): @@ -210,9 +202,9 @@ class GloriaSheet(TrecentoSheet): French, Spanish, and Italian Gloria's openings of the Et in Terra, Dominus Deus, Qui Tollis, encoded along with the ends of the Cum Sancto and Amen. - 2011-August: all encoded except some very fragmentary pieces. + >>> from music21_tools.trecento.cadencebook import GloriaSheet >>> cadenceSpreadSheet = GloriaSheet() >>> gloriaNo20 = cadenceSpreadSheet.makeWork(20) >>> incipit = gloriaNo20.incipit @@ -249,21 +241,19 @@ class GloriaSheet(TrecentoSheet): {16.0} {0.0} {2.0} - ''' - - sheetname = "gloria" + sheetname = 'gloria' def makeWork(self, rownumber=1): rowvalues = self.sheet.row_values(rownumber - 1) return Gloria(rowvalues, self.rowDescriptions) class CredoSheet(TrecentoSheet): - sheetname = "credo" + sheetname = 'credo' class SanctusSheet(TrecentoSheet): - sheetname = "sanctus" + sheetname = 'sanctus' class AgnusDeiSheet(TrecentoSheet): - sheetname = "agnus" + sheetname = 'agnus' class TrecentoCadenceWork: ''' @@ -298,7 +288,7 @@ class TrecentoCadenceWork: test just creating an empty TrecentoCadenceWork: - + >>> from music21_tools.trecento.cadencebook import TrecentoCadenceWork >>> tcw = TrecentoCadenceWork() ''' beginSnippetPositions = [8] @@ -306,12 +296,12 @@ class TrecentoCadenceWork: def __init__(self, rowvalues=None, rowDescriptions=None): if rowvalues is None: - rowvalues = ["", "", "", "", "", "", "", "", "", "", "", "", ""] + rowvalues = ['', '', '', '', '', '', '', '', '', '', '', '', ''] if rowDescriptions is None: - rowDescriptions = ["Catalog Number", "Title", "Composer", "EncodedVoices", - "PMFC/CMM Vol.", "PMFC Page Start", "PMFC Page End", - "Time Signature Beginning", "Incipit C", "Incipit T", - "Incipit Ct", "Incipit Type", "Notes"] + rowDescriptions = ['Catalog Number', 'Title', 'Composer', 'EncodedVoices', + 'PMFC/CMM Vol.', 'PMFC Page Start', 'PMFC Page End', + 'Time Signature Beginning', 'Incipit C', 'Incipit T', + 'Incipit Ct', 'Incipit Type', 'Notes'] self.rowvalues = rowvalues self.rowDescriptions = rowDescriptions self.fischerNum = rowvalues[0] @@ -348,7 +338,7 @@ def __init__(self, rowvalues=None, rowDescriptions=None): else: self.totalPmfcPage = None - if self.composer == ".": + if self.composer == '.': self.isAnonymous = True else: self.isAnonymous = False @@ -365,13 +355,14 @@ def asOpus(self): ''' returns all snippets as a :class:`~music21.stream.Opus` object + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> deduto = BallataSheet().workByTitle('deduto') >>> deduto.title 'Deduto sey a quel' >>> dedutoScore = deduto.asOpus() >>> dedutoScore - >>> #_DOCS_SHOW dedutoScore.show('lily.png') + >>> # _DOCS_SHOW dedutoScore.show('lily.png') ''' o = stream.Opus() md = metadata.Metadata() @@ -393,7 +384,7 @@ def asOpus(self): for partNumber, snippetPart in enumerate( thisSnippet.getElementsByClass('TrecentoCadenceStream')): - if thisSnippet.snippetName != "" and partNumber == self.totalVoices - 1: + if thisSnippet.snippetName != '' and partNumber == self.totalVoices - 1: textEx = expressions.TextExpression(thisSnippet.snippetName) textEx.style.absoluteY = 'below' if 'FrontPaddedSnippet' in thisSnippet.classes: @@ -426,7 +417,7 @@ def asScore(self): ''' returns all snippets as a score chunk - + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> deduto = BallataSheet().workByTitle('deduto') >>> deduto.title 'Deduto sey a quel' @@ -437,7 +428,7 @@ def asScore(self): Changes made to a snippet are reflected in the asScore() score object: - >>> deduto.snippets[0].parts[0].flatten().notes[0].name = "C###" + >>> deduto.snippets[0].parts[0].flatten().notes[0].name = 'C###' >>> deduto.asScore().parts[0].flatten().notes[0].name 'C###' ''' @@ -458,7 +449,7 @@ def asScore(self): thisSnippet.contratenor is None): continue for partNumber, snippetPart in enumerate(thisSnippet.getElementsByClass('Stream')): - if thisSnippet.snippetName != "" and partNumber == self.totalVoices - 1: + if thisSnippet.snippetName != '' and partNumber == self.totalVoices - 1: textEx = expressions.TextExpression(thisSnippet.snippetName) textEx.style.absoluteY = 'below' if 'FrontPaddedSnippet' in thisSnippet.classes: @@ -497,15 +488,16 @@ def incipit(self): Returns None if the piece or timeSignature is undefined + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> bs = BallataSheet() >>> accur = bs.makeWork(2) >>> accurIncipit = accur.incipit >>> print(accurIncipit) - + ''' rowBlock = self.rowvalues[8:12] rowBlock.append(self.rowvalues[7]) - if rowBlock[0] == "" or self.timeSigBegin == "": + if rowBlock[0] == '' or self.timeSigBegin == '': return None else: blockOut = self.convertBlockToStreams(rowBlock) @@ -516,33 +508,32 @@ def getOtherSnippets(self): returns a list of bits of music notation that are not the actual incipits of the piece. - - + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> bs = BallataSheet() >>> accur = bs.makeWork(2) >>> accurSnippets = accur.getOtherSnippets() >>> for thisSnip in accurSnippets: ... print(thisSnip) - - + + ''' beginSnippetPositions = self.beginSnippetPositions endSnippetPositions = self.endSnippetPositions # overridden in class Ballata if not endSnippetPositions: - endSnippetPositions = range(12, len(self.rowvalues)-1, 5) + endSnippetPositions = range(12, len(self.rowvalues) - 1, 5) returnSnips = [] for i in beginSnippetPositions: if i == beginSnippetPositions[0]: continue - thisSnippet = self.getSnippetAtPosition(i, snippetType="begin") + thisSnippet = self.getSnippetAtPosition(i, snippetType='begin') if thisSnippet is not None: thisSnippet.snippetName = self.rowDescriptions[i] thisSnippet.snippetName = re.sub(r'cad\b', 'cadence', thisSnippet.snippetName) thisSnippet.snippetName = re.sub(r'\s*C$', '', thisSnippet.snippetName) returnSnips.append(thisSnippet) for i in endSnippetPositions: - thisSnippet = self.getSnippetAtPosition(i, snippetType="end") + thisSnippet = self.getSnippetAtPosition(i, snippetType='end') if thisSnippet is not None: thisSnippet.snippetName = self.rowDescriptions[i] thisSnippet.snippetName = re.sub('cad ', 'cadence ', thisSnippet.snippetName) @@ -550,22 +541,22 @@ def getOtherSnippets(self): returnSnips.append(thisSnippet) return returnSnips - def getSnippetAtPosition(self, snippetPosition, snippetType="end"): + def getSnippetAtPosition(self, snippetPosition, snippetType='end'): ''' gets a "snippet" which is a collection of up to 3 lines of music, a timeSignature and a description of the cadence. - + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> bs = BallataSheet() >>> accur = bs.makeWork(2) >>> print(accur.getSnippetAtPosition(12)) - + ''' - if self.rowvalues[snippetPosition].strip() != "": + if self.rowvalues[snippetPosition].strip() != '': thisBlock = self.rowvalues[snippetPosition:snippetPosition + 5] - if thisBlock[4].strip() == "": - if self.timeSigBegin == "": + if thisBlock[4].strip() == '': + if self.timeSigBegin == '': return None # need a timesig thisBlock[4] = self.timeSigBegin blockOut = self.convertBlockToStreams(thisBlock) @@ -580,8 +571,7 @@ def convertBlockToStreams(self, thisBlock): :class:`~trecentoCadence.TrecentoCadenceStream` notation) and returns a list of Streams and other information - - + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> block1 = ['e4 f g a', 'g4 a b cc', '', 'no-cadence', '2/4'] >>> bs = BallataSheet() >>> dummyPiece = bs.makeWork(2) @@ -615,10 +605,10 @@ def convertBlockToStreams(self, thisBlock): thisVoice = thisVoice.strip() if thisVoice: try: - returnBlock[i] = trecentoCadence.CadenceConverter(currentTimeSig + " " + + returnBlock[i] = trecentoCadence.CadenceConverter(currentTimeSig + ' ' + thisVoice).parse().stream except duration.DurationException as value: - raise duration.DurationException("Problems in line %s: specifically %s" % + raise duration.DurationException('Problems in line %s: specifically %s' % (thisVoice, value)) # except Exception, (value): # raise Exception("Unknown Problems in line %s: specifically %s" % @@ -646,7 +636,7 @@ def cadenceA(self): except IndexError: return None if fc is not None: - fc.snippetName = "A section cadence" + fc.snippetName = 'A section cadence' return fc @property @@ -660,7 +650,7 @@ def cadenceB(self): except IndexError: return None if fc is not None: - fc.snippetName = "B section cadence (1st or only ending)" + fc.snippetName = 'B section cadence (1st or only ending)' return fc @property @@ -673,7 +663,7 @@ def cadenceBClos(self): except IndexError: return None if fc is not None: - fc.snippetName = "B section cadence (2nd ending)" + fc.snippetName = 'B section cadence (2nd ending)' return fc def getAllStreams(self): @@ -681,7 +671,7 @@ def getAllStreams(self): Get all streams in the work as a List, losing association with the other polyphonic units. - + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> b = BallataSheet().makeWork(20) >>> sList = b.getAllStreams() >>> sList @@ -702,6 +692,7 @@ def pmfcPageRange(self): returns a nicely formatted string giving the page numbers in PMFC where the piece can be found + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> bs = BallataSheet() >>> altroCheSospirar = bs.makeWork(4) >>> altroCheSospirar.title @@ -713,9 +704,9 @@ def pmfcPageRange(self): ''' if self.pmfcPageStart != self.pmfcPageEnd: - return str("pp. " + str(self.pmfcPageStart) + "-" + str(self.pmfcPageEnd)) + return str('pp. ' + str(self.pmfcPageStart) + '-' + str(self.pmfcPageEnd)) else: - return str("p. " + str(self.pmfcPageStart)) + return str('p. ' + str(self.pmfcPageStart)) class Ballata(TrecentoCadenceWork): @@ -739,9 +730,6 @@ class Gloria(TrecentoCadenceWork): class Test(unittest.TestCase): - def runTest(self): - pass - def testTrecentoCadenceWorkCopying(self): w = TrecentoCadenceWork() _w1 = copy.copy(w) @@ -769,14 +757,10 @@ def testConvertBlockToStreams(self): class TestExternal(unittest.TestCase): # pragma: no cover - def runTest(self): - pass - def testCredo(self): ''' testing a Credo in and Lilypond out ''' - cs1 = CredoSheet() # filename = r'd:\docs\trecento\fischer\cadences.xls') # cs1 = BallataSheet() credo1 = cs1.makeWork(2) @@ -812,7 +796,7 @@ def xtestVirelais(self): ''' virelaisSheet = TrecentoSheet(sheetname='virelais') thisVirelai = virelaisSheet.makeWork(54) - if thisVirelai.title != "": + if thisVirelai.title != '': print(thisVirelai.title) thisVirelai.incipit.show('musicxml') @@ -822,7 +806,7 @@ def xtestRondeaux(self): ''' rondeauxSheet = TrecentoSheet(sheetname='rondeaux') thisRondeaux = rondeauxSheet.makeWork(41) - if thisRondeaux.title != "": + if thisRondeaux.title != '': print(thisRondeaux.title) thisRondeaux.incipit.show('musicxml') @@ -832,7 +816,7 @@ def xtestGloria(self): ''' gloriaS = GloriaSheet() thisGloria = gloriaS.makeWork(20) - if thisGloria.title != "": + if thisGloria.title != '': thisGloria.asScore().show() def xtestAsScore(self): @@ -845,11 +829,6 @@ def xtestAsScore(self): pass -if __name__ == "__main__": +if __name__ == '__main__': import music21 music21.mainTest(Test, 'importPlusRelative') # , TestExternal) - - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/capua.py b/music21_tools/trecento/capua.py index 3356580..16df3f1 100644 --- a/music21_tools/trecento/capua.py +++ b/music21_tools/trecento/capua.py @@ -59,9 +59,11 @@ def applyCapuaToCadencebookWork(thisWork): :class:`~music21.alpha.trecento.polyphonicSnippet.PolyphonicSnippet` objects (a Score subclass) + >>> from music21_tools.trecento.capua import applyCapuaToCadencebookWork, capuaFictaToAccidental + >>> from music21_tools.trecento.cadencebook import BallataSheet >>> import copy - >>> b = cadencebook.BallataSheet().makeWork(331) # Francesco, Non Creder Donna + >>> b = BallataSheet().makeWork(331) # Francesco, Non Creder Donna >>> bOrig = copy.deepcopy(b) >>> applyCapuaToCadencebookWork(b) >>> bFN = b.asScore().flatten().notes @@ -573,8 +575,10 @@ def compareThreeFictas(srcStream1, srcStream2): srcStream1 and srcStream2 should be .flatten().notesAndRests - >>> b = cadencebook.BallataSheet().makeWork(331).asScore() - >>> #_DOCS_SHOW b.show() + >>> from music21_tools.trecento.capua import applyCapuaToStream, compareThreeFictas + >>> from music21_tools.trecento.cadencebook import BallataSheet + >>> b = BallataSheet().makeWork(331).asScore() + >>> # _DOCS_SHOW b.show() >>> b0n = b.parts[0].flatten().notesAndRests.stream() >>> b1n = b.parts[1].flatten().notesAndRests.stream() >>> applyCapuaToStream(b0n) @@ -821,9 +825,9 @@ def findCorrections(correctionType='Maj3', startPiece=2, endPiece=459): # 'pmfcAlt': 4, 'pmfcNotCapua': 1, 'totalNotes': 82} # >>> foundPieceOpus.show('lily.pdf') -# >>> #_DOCS_SHOW (totalDict, foundPieceOpus) = correctedMin6() -# >>> totalDict = {'potentialChange': 82, 'capuaAlt': 30, 'pmfcAndCapua': 3, #_DOCS_HIDE -# ... 'capuaNotPmfc': 27, 'pmfcAlt': 4, 'pmfcNotCapua': 1, 'totalNotes': 82} #_DOCS_HIDE +# >>> # _DOCS_SHOW (totalDict, foundPieceOpus) = correctedMin6() +# >>> totalDict = {'potentialChange': 82, 'capuaAlt': 30, 'pmfcAndCapua': 3, # _DOCS_HIDE +# ... 'capuaNotPmfc': 27, 'pmfcAlt': 4, 'pmfcNotCapua': 1, 'totalNotes': 82} # _DOCS_HIDE # >>> pp(totalDict) # {'alterAll': 82, 'capuaAlt': 30, 'pmfcAndCapua': 3, 'capuaNotPmfc': 27, # 'pmfcAlt': 4, 'pmfcNotCapua': 1, 'totalNotes': 82} @@ -933,9 +937,9 @@ def improvedHarmony(startPiece=2, endPiece=459): Returns a dict showing the results - >>> #_DOCS_SHOW improvedHarmony() - >>> print("{'imperfCapua': 22, 'imperfIgnored': 155, " + #_DOCS_HIDE - ... "'perfCapua': 194, 'perfIgnored': 4057}") #_DOCS_HIDE + >>> # _DOCS_SHOW improvedHarmony() + >>> print("{'imperfCapua': 22, 'imperfIgnored': 155, " + # _DOCS_HIDE + ... "'perfCapua': 194, 'perfIgnored': 4057}") # _DOCS_HIDE {'imperfCapua': 22, 'imperfIgnored': 155, 'perfCapua': 194, 'perfIgnored': 4057} ''' @@ -963,7 +967,8 @@ def improvedHarmony(startPiece=2, endPiece=459): if len(thisSnippetParts) < 2: continue srcStream1 = thisSnippetParts['C'].flatten().notesAndRests - srcStream2 = thisSnippetParts['T'].flatten().notesAndRests # ignore 3rd voice for now... + # ignore 3rd voice for now... + srcStream2 = thisSnippetParts['T'].flatten().notesAndRests srcStream1.attachIntervalsBetweenStreams(srcStream2) # srcStream2.attachIntervalsBetweenStreams(srcStream1) applyCapuaToStream(srcStream1) @@ -1038,48 +1043,19 @@ def ruleFrequency(startNumber=2, endNumber=459): class Test(unittest.TestCase): - - def runTest(self): - pass - def testRunNonCrederDonna(self): - pieceNum = 331 # Francesco, PMFC 4 6-7: Non creder, donna - ballataObj = cadencebook.BallataSheet() - pieceObj = ballataObj.makeWork(pieceNum) - - applyCapuaToCadencebookWork(pieceObj) - srcStream = pieceObj.snippets[0].parts[0].flatten().notesAndRests.stream() - cmpStream = pieceObj.snippets[0].parts[1].flatten().notesAndRests.stream() - # ignore 3rd voice for now... - srcStream.attachIntervalsBetweenStreams(cmpStream) - cmpStream.attachIntervalsBetweenStreams(srcStream) - - # colorCapuaFicta(srcStream, cmpStream, 'both') - - outList = [] - for n in srcStream: - if n.editorial.harmonicInterval is not None: - outSublist = [n.name, n.editorial.harmonicInterval.simpleName] - if 'capuaFicta' in n.editorial: - outSublist.append(repr(n.editorial.capuaFicta)) - else: - outSublist.append(None) - outList.append(outSublist) - + _pieceObj, outList = _analyzeNonCrederDonna() self.assertEqual(outList, [ ['A', 'P5', None], ['A', 'M6', None], ['G', 'P5', None], ['G', 'm6', None], - ['A', 'm7', None], ['F', 'd5', ''], + ['A', 'm7', None], ['F', 'd5', ''], ['G', 'm6', None], ['A', 'P1', None], ['B', 'M6', None], ['A', 'P5', None], ['G', 'm7', None], ['G', 'm6', None], ['F', 'd5', None], ['E', 'M3', None], ['D', 'P1', None] ]) - # pieceObj.asOpus().show('lily.pdf') - - return pieceObj def testRun1(self): ballataSht = cadencebook.BallataSheet() @@ -1132,19 +1108,47 @@ def testColorCapuaFicta(self): # assert n13.style.color == 'green' -class TestExternal(unittest.TestCase): # pragma: no cover - def runTest(self): - pass +def _analyzeNonCrederDonna(): + ''' + Run Capua on Francesco, "Non creder, donna" (PMFC 4 6-7, BallataSheet + row 331). Returns ``(pieceObj, outList)`` so both ``Test`` (which only + asserts on ``outList``) and ``TestExternal`` (which renders ``pieceObj`` + with LilyPond) can share the analysis without a test method returning a + value (deprecated in unittest). + ''' + pieceNum = 331 # Francesco, PMFC 4 6-7: Non creder, donna + ballataObj = cadencebook.BallataSheet() + pieceObj = ballataObj.makeWork(pieceNum) + applyCapuaToCadencebookWork(pieceObj) + srcStream = pieceObj.snippets[0].parts[0].flatten().notesAndRests.stream() + cmpStream = pieceObj.snippets[0].parts[1].flatten().notesAndRests.stream() + # ignore 3rd voice for now... + srcStream.attachIntervalsBetweenStreams(cmpStream) + cmpStream.attachIntervalsBetweenStreams(srcStream) + + outList = [] + for n in srcStream: + if n.editorial.harmonicInterval is not None: + outSublist = [n.name, n.editorial.harmonicInterval.simpleName] + if 'capuaFicta' in n.editorial: + outSublist.append(repr(n.editorial.capuaFicta)) + else: + outSublist.append(None) + outList.append(outSublist) + + return pieceObj, outList + + +class TestExternal(unittest.TestCase): # pragma: no cover def testRunNonCrederDonna(self): - t = Test() - pObj = t.testRunNonCrederDonna() + pObj, _outList = _analyzeNonCrederDonna() pObj.asOpus().show('lily.png') def testShowFourA(self): ballataObj = cadencebook.BallataSheet() showStream = stream.Opus() - for i in range(2, 45): # 459): # all ballate + for i in range(2, 45): # 459): # all ballate pieceObj = ballataObj.makeWork(i) # N.B. -- we now use Excel column numbers theseSnippets = pieceObj.snippets for thisSnippet in theseSnippets: @@ -1162,10 +1166,6 @@ def testShowFourA(self): class TestSlow(unittest.TestCase): - - def runTest(self): - pass - def testCompare1(self): ballataObj = cadencebook.BallataSheet() totalDict = { @@ -1219,7 +1219,7 @@ def testRuleFrequency(self): # runPiece(267) # (totalDict, foundPieceOpus) = findCorrections(correctionType='min6', # startPiece=21, endPiece=22) - # print totalDict + # print(totalDict) # if len(foundPieceOpus) > 0: # foundPieceOpus.show('lily.png') import music21 diff --git a/music21_tools/trecento/findSevs.py b/music21_tools/trecento/findSevs.py index a55571e..81f4431 100644 --- a/music21_tools/trecento/findSevs.py +++ b/music21_tools/trecento/findSevs.py @@ -46,9 +46,5 @@ def findInWork(work, searchInterval=7): # interval.note2.style.color = "blue" return containsInterval -if __name__ == "__main__": +if __name__ == '__main__': find(2, 459, ) - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/findTrecentoFragments.py b/music21_tools/trecento/findTrecentoFragments.py index 78effe2..cd54295 100644 --- a/music21_tools/trecento/findTrecentoFragments.py +++ b/music21_tools/trecento/findTrecentoFragments.py @@ -48,7 +48,7 @@ def compareToStream(self, cmpStream): else: for colorNote in range(i, i + self.intervalLength): # does not exactly work because of rests, oh well - cmpStream.notesAndRests[colorNote].style.color = "blue" + cmpStream.notesAndRests[colorNote].style.color = 'blue' return True return False @@ -74,7 +74,7 @@ def compareToStream(self, cmpStream): break else: # success! for colorNote in range(i, self.noteLength): - sN[colorNote].style.color = "blue" + sN[colorNote].style.color = 'blue' return True return False @@ -90,7 +90,7 @@ def searchForNotes(notesStr): noteObjArr = [] for tN in notesArr: tNName = tN[0] - if tNName.lower() != "r": + if tNName.lower() != 'r': tNObj = note.Note() tNObj.name = tN[0] tNObj.octave = int(tN[1]) @@ -112,19 +112,19 @@ def searchForNotes(notesStr): def colorFound(searcher1, thisCadence, streamOpus, thisWork, i): if searcher1.compareToStream(thisCadence.parts[i].flatten()) is True: - notesStr = "" + notesStr = '' for thisNote in thisCadence.parts[i].flatten().notesAndRests: # thisNote.style.color = "blue" if thisNote.isRest is False: - notesStr += thisNote.nameWithOctave + " " + notesStr += thisNote.nameWithOctave + ' ' else: - notesStr += "r " + notesStr += 'r ' streamOpus.insert(0, thisCadence) # streamLily += ("\\score {" + # "<< \\time " + str(thisCadence.timeSig) + # "\n \\new Staff {" + str(thisCadence.parts[i].lily) + "} >>" + # thisCadence.header() + "\n}\n") - print("In piece %r found in stream %d: %s" % (thisWork.title, i, notesStr)) + print('In piece %r found in stream %d: %s' % (thisWork.title, i, notesStr)) def searchForIntervals(notesStr): ''' @@ -163,14 +163,14 @@ def searchForIntervals(notesStr): streamOpus.show('lily.png') def findRandomVerona(): - searchForNotes("A4 F4 G4 E4 F4 G4") # p. 4 cadence 1 - searchForNotes("F4 G4 A4 G4 A4 F4 G4 A4") # p. 4 incipit 2 + searchForNotes('A4 F4 G4 E4 F4 G4') # p. 4 cadence 1 + searchForNotes('F4 G4 A4 G4 A4 F4 G4 A4') # p. 4 incipit 2 def findCasanatense522(): - searchForIntervals("D4 E4 D4 C4 D4 E4 F4") + searchForIntervals('D4 E4 D4 C4 D4 E4 F4') def findRavi3ORegina(): - searchForNotes("G16 G16 F8 E16") # should be cadence A, cantus + searchForNotes('G16 G16 F8 E16') # should be cadence A, cantus def searchForVat1969(): @@ -230,7 +230,7 @@ def audioVirelaiSearch(): virelaiCantuses = [] for i in range(2, 54): thisVirelai = virelaisSheet.makeWork(i) - if thisVirelai.title != "": + if thisVirelai.title != '': try: vc = thisVirelai.incipit.getElementsByClass('Part')[0] vc.insert(0, metadata.Metadata(title=thisVirelai.title)) @@ -241,10 +241,10 @@ def audioVirelaiSearch(): # from music21 import converter # searchScore = converter.parse("c'4 a8 a4 g8 b4. d'4. c8 b a g f4", '6/8') # searchScore.show() - l = search.approximateNoteSearch(searchScore, virelaiCantuses) - for i in l: - print(i.metadata.title, i.matchProbability) - l[0].show() + matches = search.approximateNoteSearch(searchScore, virelaiCantuses) + for match in matches: + print(match.metadata.title, match.matchProbability) + matches[0].show() def findSimilarGloriaParts(): ''' @@ -263,8 +263,8 @@ def savedSearches(): # searchForNotes("G3 D3 R D3 D3 E3 F3") # Donna si to fallito TEST - last note = F# # searchForIntervals("F3 C3 C3 F3 G3") # Bologna Archivio: Per seguirla TEST # searchForNotes("F3 E3 F3 G3 F3 E3") # Mons archive fragment -- see FB Aetas Aurea post - searchForNotes("F4 G4 F4 B4 G4 A4 G4 F4 E4") # or B-4. Paris 25406 -- - # Dominique Gatte pen tests + searchForNotes('F4 G4 F4 B4 G4 A4 G4 F4 E4') # or B-4. Paris 25406 -- + # Dominique Gatte pen tests # searchForNotes("D4 D4 C4 D4") # Fortuna Rira Seville 25 TEST! CANNOT FIND # searchForNotes("D4 C4 B3 A3 G3") # Tenor de monaco so tucto Seville 25 @@ -275,24 +275,18 @@ def savedSearches(): # searchForIntervals("C4 B3 A3 A3 G3 G3 A3") # London 29987 88v T # searchForNotes("G3 E3 F3 G3 F3 E3 D3 C3") # probable French piece, # # Nuremberg 9, but worth a check - # searchForIntervals("A4 A4 G4 G4 F4 E4") # Nuremberg 9a, staff 6 probable French Piece + # searchForIntervals("A4 A4 G4 G4 F4 E4") # Nuremberg 9a, staff 6 probable French Piece # findCasanatense522() # findRandomVerona() # findRavi3ORegina() # searchForIntervals("D4 B4 D4 C4 D4") # cadence formula from 15th c. that # Lisa Colton was searching for in earlier sources -- none found - # searchForIntervals("G4 A4 G4 F4 E4 F4 G4 E4") # Prague XVII.J.17-14_1r piece 1 + # searchForIntervals("G4 A4 G4 F4 E4 F4 G4 E4") # Prague XVII.J.17-14_1r piece 1 # -- possible contrafact -- no correct matches - # searchForIntervals("G4 A4 B4 G4 F4 G4 F4 E4") # Prague XVII.J.17-14_1r piece 2 + # searchForIntervals("G4 A4 B4 G4 F4 G4 F4 E4") # Prague XVII.J.17-14_1r piece 2 # -- possible contrafact -- no matches - # searchForIntervals("F4 A4 F4 G4 F4 G4 A4") # Duke white notation manuscript + # searchForIntervals("F4 A4 F4 G4 F4 G4 A4") # Duke white notation manuscript -if __name__ == "__main__": +if __name__ == '__main__': savedSearches() # audioVirelaiSearch() - - - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/find_vatican1790.py b/music21_tools/trecento/find_vatican1790.py index e722e2f..e3abcf7 100644 --- a/music21_tools/trecento/find_vatican1790.py +++ b/music21_tools/trecento/find_vatican1790.py @@ -22,16 +22,12 @@ def find(): for ballata in ballatas: if i > 10: break - if ballata.timeSigBegin == "6/8" or ballata.timeSigBegin == "9/8": + if ballata.timeSigBegin == '6/8' or ballata.timeSigBegin == '9/8': incipit = ballata.incipit if incipit is not None: i += 1 opus.insert(0, incipit) opus.show('lily.pdf') -if __name__ == "__main__": +if __name__ == '__main__': find() - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/largestAmbitus.py b/music21_tools/trecento/largestAmbitus.py index a05a814..1de0cc8 100644 --- a/music21_tools/trecento/largestAmbitus.py +++ b/music21_tools/trecento/largestAmbitus.py @@ -31,7 +31,7 @@ def main(): ambi = p.analyze('ambitus') distance = ambi.diatonic.generic.undirected if distance >= 15: - print("************ GOT ONE!: {0} ************".format(ambi)) + print('************ GOT ONE!: {0} ************'.format(ambi)) elif distance >= 9: print(ambi) else: @@ -42,9 +42,5 @@ def main(): _DOC_ORDER = [] -if __name__ == "__main__": +if __name__ == '__main__': main() - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/medren.py b/music21_tools/trecento/medren.py index c6b513b..fc4f29a 100644 --- a/music21_tools/trecento/medren.py +++ b/music21_tools/trecento/medren.py @@ -14,6 +14,7 @@ ''' import copy import unittest +from typing import Literal from music21 import bar from music21 import base @@ -32,6 +33,7 @@ from music21.duration import Duration from . import notation +from ._base import MedievalMeter environLocal = environment.Environment('medren') @@ -68,7 +70,7 @@ (4, False)], } -_validMensuralTypes = [None,'maxima', 'longa', 'brevis', 'semibrevis', 'minima', 'semiminima'] +_validMensuralTypes = [None, 'maxima', 'longa', 'brevis', 'semibrevis', 'minima', 'semiminima'] _validMensuralAbbr = [None, 'Mx', 'L', 'B', 'SB', 'M', 'SM'] @@ -77,6 +79,7 @@ class MensuralClef(clef.Clef): ''' An object representing a mensural clef found in medieval and Renaissance music. + >>> from music21_tools.trecento.medren import MensuralClef >>> fclef = MensuralClef('F') >>> fclef.line 3 @@ -118,7 +121,7 @@ def fontString(self): return self._fontString -class Mensuration(meter.TimeSignature): +class Mensuration(MedievalMeter): ''' An object representing a mensuration sign found in French notation. Takes four optional arguments: tempus, prolation, mode, and maximode. @@ -127,6 +130,7 @@ class Mensuration(meter.TimeSignature): Valid values for tempus and mode are 'perfect' and 'imperfect'. Valid values for prolation and maximode are 'major' and 'minor'. + >>> from music21_tools.trecento.medren import Mensuration >>> ODot = Mensuration(tempus='perfect', prolation='major') >>> ODot.standardSymbol 'O-dot' @@ -203,6 +207,7 @@ def fontString(self): TODO: Convert to SMuFL + >>> from music21_tools.trecento.medren import Mensuration >>> O = Mensuration('imperfect', 'major') >>> O.fontString '0x4f' @@ -261,8 +266,9 @@ def __eq__(self, other): Essentially the same as music21.base.Music21Object.__eq__, but equality of mensural type is tested rather than equality of duration + >>> from music21_tools.trecento.medren import GeneralMensuralNote >>> from music21 import stream - + >>> m = GeneralMensuralNote('minima') >>> n = GeneralMensuralNote('brevis') >>> m == n @@ -278,7 +284,6 @@ def __eq__(self, other): >>> m == n True ''' - eq = hasattr(other, 'mensuralType') if eq: eq = eq and (self.mensuralType == other.mensuralType) @@ -292,10 +297,28 @@ def __eq__(self, other): eq = eq and (self.offset == other.offset) return eq - def _getMensuralType(self): + @property + def mensuralType(self): + ''' + Name of the mensural length of the general mensural note + (brevis, longa, etc.): + + >>> from music21_tools.trecento.medren import GeneralMensuralNote + >>> gmn = GeneralMensuralNote('maxima') + >>> gmn.mensuralType + 'maxima' + >>> gmn_1 = GeneralMensuralNote('SB') + >>> gmn_1.mensuralType + 'semibrevis' + >>> gmn_2 = GeneralMensuralNote('blah') + Traceback (most recent call last): + music21_tools.trecento.medren.MedRenException: blah is not a valid + mensural type or abbreviation + ''' return self._mensuralType - def _setMensuralType(self, mensuralTypeOrAbbr): + @mensuralType.setter + def mensuralType(self, mensuralTypeOrAbbr): if mensuralTypeOrAbbr in _validMensuralTypes: self._mensuralType = mensuralTypeOrAbbr elif mensuralTypeOrAbbr in _validMensuralAbbr: @@ -304,22 +327,6 @@ def _setMensuralType(self, mensuralTypeOrAbbr): raise MedRenException('%s is not a valid mensural type or abbreviation' % mensuralTypeOrAbbr) - mensuralType = property(_getMensuralType, _setMensuralType, - doc='''Name of the mensural length of the general mensural note - (brevis, longa, etc.): - - >>> gmn = GeneralMensuralNote('maxima') - >>> gmn.mensuralType - 'maxima' - >>> gmn_1 = GeneralMensuralNote('SB') - >>> gmn_1.mensuralType - 'semibrevis' - >>> gmn_2 = GeneralMensuralNote('blah') - Traceback (most recent call last): - MedRenException: blah is not a valid - mensural type or abbreviation - ''') - def updateDurationFromMensuration(self, mensuration=None, surroundingStream=None): ''' The duration of a :class:`GeneralMensuralNote` object can be accessed @@ -337,6 +344,9 @@ def updateDurationFromMensuration(self, mensuration=None, surroundingStream=None Every time a duration is changed, the method :meth:`GeneralMensuralNote.updateDurationFromMensuration`` should be called. + >>> from music21_tools.trecento.notation import Divisione, Punctus + >>> from music21_tools.trecento import notation + >>> from music21_tools.trecento.medren import GeneralMensuralNote, MensuralNote >>> mn = GeneralMensuralNote('B') >>> mn.duration.quarterLength 0.0 @@ -347,16 +357,14 @@ def updateDurationFromMensuration(self, mensuration=None, surroundingStream=None However, if subclass is given, context (a stream) is given, and a mensuration or divisione is given, duration can be determined. - >>> from music21_tools import trecento - >>> s = stream.Stream() - >>> s.append(notation.Divisione('.p.')) + >>> s.append(Divisione('.p.')) >>> for i in range(3): ... s.append(MensuralNote('A', 'SB')) - >>> s.append(trecento.notation.Punctus()) + >>> s.append(Punctus()) >>> s.append(MensuralNote('B', 'SB')) >>> s.append(MensuralNote('B', 'SB')) - >>> s.append(trecento.notation.Punctus()) + >>> s.append(Punctus()) >>> s.append(MensuralNote('A', 'B')) >>> for mn in s: ... if isinstance(mn, GeneralMensuralNote): @@ -381,6 +389,11 @@ def updateDurationFromMensuration(self, mensuration=None, surroundingStream=None else: mOrD = mensuration + if mOrD is None: + # No mensuration or divisione in context — duration cannot be inferred. + self.duration = duration.Duration(0.0) + return + index = self._getTranslator(mensurationOrDivisione=mOrD, surroundingStream=surroundingStream) @@ -406,7 +419,7 @@ def _getTranslator(self, mensurationOrDivisione=None, surroundingStream=None): activeSite=surroundingStream) self._gettingDuration = True - if measure and 'Divisione' in mOrD.classes: + if measure and mOrD is not None and isinstance(mOrD, notation.Divisione): if index == 0: self.lenList = notation.BrevisLengthTranslator( mOrD, measure).getKnownLengths() @@ -430,8 +443,9 @@ def _determineMensurationOrDivisione(self): If no context is present, returns None. - >>> from music21_tools import trecento - + >>> from music21_tools.trecento.medren import Mensuration + >>> from music21_tools.trecento.notation import Divisione + >>> from music21_tools.trecento.medren import GeneralMensuralNote >>> gmn = GeneralMensuralNote('longa') >>> gmn._determineMensurationOrDivisione() >>> @@ -439,27 +453,26 @@ def _determineMensurationOrDivisione(self): >>> s_2 = stream.Stream() >>> s_3 = stream.Stream() >>> s_3.insert(3, gmn) - >>> s_2.insert(1, trecento.notation.Divisione('.q.')) + >>> s_2.insert(1, Divisione('.q.')) >>> s_1.insert(2, Mensuration('perfect', 'major')) >>> s_2.insert(2, s_3) >>> s_1.insert(3, s_2) >>> gmn._determineMensurationOrDivisione() ''' - # mOrD = _getTargetBeforeOrAtObj(self, - # [Mensuration, trecento.notation.Divisione]) - searchClasses = (Mensuration, notation.Divisione) - mOrD = self.getContextByClass(searchClasses) + # In music21 v10, getContextByClass takes a single class only, so we + # search for the common parent of Mensuration and Divisione. + mOrD = self.getContextByClass(MedievalMeter) if mOrD is not None: return mOrD else: return None -# if len(mOrD)> 0: -# mOrD = mOrD[0] #Gets most recent M or D -# else: -# mOrD = None #TODO: try to determine mensuration for French Notation -# -# return mOrD + # if len(mOrD)> 0: + # mOrD = mOrD[0] # Gets most recent M or D + # else: + # mOrD = None # TODO: try to determine mensuration for French Notation + # + # return mOrD def _getSurroundingMeasure(self, mensurationOrDivisione=None, activeSite=None): ''' @@ -469,10 +482,10 @@ def _getSurroundingMeasure(self, mensurationOrDivisione=None, activeSite=None): If the general mensural note has more than one context, only the surrounding measure of the first context is returned. - >>> from music21_tools import trecento - + >>> from music21_tools.trecento.medren import GeneralMensuralNote, Ligature, MensuralNote + >>> from music21_tools.trecento.notation import Divisione, Punctus >>> s_1 = stream.Stream() - >>> s_1.append(trecento.notation.Divisione('.p.')) + >>> s_1.append(Divisione('.p.')) >>> l = MensuralNote('A', 'longa') >>> s_1.append(l) >>> for i in range(4): @@ -480,7 +493,7 @@ def _getSurroundingMeasure(self, mensurationOrDivisione=None, activeSite=None): >>> gmn_1 = GeneralMensuralNote('minima') >>> s_1.append(gmn_1) >>> s_1.append(MensuralNote('C', 'minima')) - >>> s_1.append(trecento.notation.Punctus()) + >>> s_1.append(Punctus()) >>> gmn_1._getSurroundingMeasure(activeSite=s_1) ([, , @@ -490,8 +503,8 @@ def _getSurroundingMeasure(self, mensurationOrDivisione=None, activeSite=None): ], 4) >>> s_2 = stream.Stream() - >>> s_2.append(trecento.notation.Divisione('.p.')) - >>> s_2.append(trecento.notation.Punctus()) + >>> s_2.append(Divisione('.p.')) + >>> s_2.append(Punctus()) >>> s_2.append(MensuralNote('A', 'semibrevis')) >>> s_2.append(MensuralNote('B', 'semibrevis')) >>> gmn_2 = GeneralMensuralNote('semibrevis') @@ -502,7 +515,6 @@ def _getSurroundingMeasure(self, mensurationOrDivisione=None, activeSite=None): , ], 2) ''' - mOrD = mensurationOrDivisione if mOrD is None: mOrD = self._determineMensurationOrDivisione() @@ -532,14 +544,15 @@ def _getSurroundingMeasure(self, mensurationOrDivisione=None, activeSite=None): for i in range(currentIndex - 1, -1, -1): # Punctus and ligature marks indicate a new measure - if (('Punctus' in tempList[i].classes) or - ('Ligature' in tempList[i].classes)): + if ((isinstance(tempList[i], notation.Punctus)) or + (isinstance(tempList[i], Ligature))): indOffset = i + 1 break - elif 'GeneralMensuralNote' in tempList[i].classes: + elif isinstance(tempList[i], GeneralMensuralNote): # In Italian notation, brevis, longa, and maxima indicate a new measure - if (('Divisione' in mOrD.classes) and - (tempList[i].mensuralType in ['brevis', 'longa', 'maxima'])): + if (mOrD is not None + and isinstance(mOrD, notation.Divisione) + and tempList[i].mensuralType in ['brevis', 'longa', 'maxima']): indOffset = i + 1 break else: @@ -550,12 +563,13 @@ def _getSurroundingMeasure(self, mensurationOrDivisione=None, activeSite=None): mList.reverse() mList.insert(currentIndex, self) for j in range(currentIndex + 1, len(tempList), 1): - if (('Punctus' in tempList[j].classes) or - ('Ligature' in tempList[j].classes)): + if ((isinstance(tempList[j], notation.Punctus)) or + (isinstance(tempList[j], Ligature))): break - if 'GeneralMensuralNote' in tempList[j].classes: - if (('Divisione' in mOrD.classes) and - (tempList[j].mensuralType in ['brevis', 'longa', 'maxima'])): + if isinstance(tempList[j], GeneralMensuralNote): + if (mOrD is not None + and isinstance(mOrD, notation.Divisione) + and tempList[j].mensuralType in ['brevis', 'longa', 'maxima']): break else: mList.insert(j, tempList[j]) @@ -573,21 +587,13 @@ class MensuralRest(GeneralMensuralNote, note.Rest): :class:`music21.GeneralMensuralNote`. ''' - # scaling? - def __init__(self, *arguments, **keywords): - note.Rest.__init__(self, *arguments, **keywords) # do not replace with super - GeneralMensuralNote.__init__(self) # due to different keywords... - self._gettingDuration = False - self._mensuralType = 'brevis' - - if arguments: - tOrA = arguments[0] - if tOrA in _validMensuralTypes: - self._mensuralType = tOrA - elif tOrA in _validMensuralAbbr: - self._mensuralType = _validMensuralTypes[_validMensuralAbbr.index(tOrA)] - else: - raise MedRenException('%s is not a valid mensural type or abbreviation' % tOrA) + def __init__(self, mensuralTypeOrAbbr: str = 'brevis', **keywords): + # Do not forward the mensural-type string to note.Rest: in music21 v10 + # the first positional arg is `length` (a Duration type or quarterLength), + # and a mensural abbreviation like 'SB' would raise DurationException. + note.Rest.__init__(self, **keywords) # do not replace with super + # GeneralMensuralNote owns the mensural-type parsing and validation: + GeneralMensuralNote.__init__(self, mensuralTypeOrAbbr) self._duration = None self._fontString = '' @@ -622,6 +628,7 @@ def fontString(self): TODO: Replace w/ SMuFL + >>> from music21_tools.trecento.medren import MensuralRest >>> mr = MensuralRest('SB') >>> mr.fontString '0x32' @@ -656,25 +663,14 @@ class MensuralNote(GeneralMensuralNote, note.Note): :class:`music21.GeneralMensuralNote`. ''' - # scaling? - def __init__(self, *arguments, **keywords): - if arguments: - note.Note.__init__(self, arguments[0], **keywords) # do not replace with super + def __init__(self, pitch=None, mensuralTypeOrAbbr: str = 'brevis', **keywords): + if pitch is not None: + note.Note.__init__(self, pitch, **keywords) # do not replace with super else: note.Note.__init__(self, **keywords) # do not replace with super - - GeneralMensuralNote.__init__(self) # due to different arguments, keywords - self._gettingDuration = False - self._mensuralType = 'brevis' - - if len(arguments) > 1: - tOrA = arguments[1] - if tOrA in _validMensuralTypes: - self._mensuralType = tOrA - elif tOrA in _validMensuralAbbr: - self._mensuralType = _validMensuralTypes[_validMensuralAbbr.index(tOrA)] - else: - raise MedRenException('%s is not a valid mensural type or abbreviation' % tOrA) + + # GeneralMensuralNote owns the mensural-type parsing and validation: + GeneralMensuralNote.__init__(self, mensuralTypeOrAbbr) if self.mensuralType in ['minima', 'semiminima']: self.stems = ['up'] @@ -700,6 +696,10 @@ def __eq__(self, other): Only pitch is shown as a test. For other cases, please see the docs for :meth:``music21.GeneralMensuralNote.__eq__`` + >>> from music21_tools import trecento + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import Divisione + >>> from music21_tools.trecento import notation >>> m = MensuralNote('A', 'minima') >>> n = MensuralNote('B', 'minima') >>> m == n @@ -739,6 +739,7 @@ def fontString(self): TODO: Replace with SMuFL + >>> from music21_tools.trecento.medren import MensuralNote >>> mn = MensuralNote('A', 'M') >>> mn.setStem('down') >>> mn.fontString @@ -799,9 +800,9 @@ def fontString(self): return self._fontString - - def _setMensuralType(self, mensuralTypeOrAbbr): - GeneralMensuralNote._setMensuralType(self, mensuralTypeOrAbbr) + @GeneralMensuralNote.mensuralType.setter + def mensuralType(self, mensuralTypeOrAbbr): + GeneralMensuralNote.mensuralType.fset(self, mensuralTypeOrAbbr) if self.mensuralType in ['minima', 'semiminima']: self.stems = ['up'] @@ -812,10 +813,6 @@ def _setMensuralType(self, mensuralTypeOrAbbr): if self._mensuralType == 'semiminima': self.flags['up'] = 'right' - mensuralType = property(GeneralMensuralNote._getMensuralType, _setMensuralType, - doc=''' See documentation in `music21.GeneralMensuralType`''') - - def getNumDots(self): ''' Used for French notation. Not yet implemented @@ -841,10 +838,12 @@ def setStem(self, direction): have a side stem, and vice versa). Setting stem direction to None removes all but the default number of stems. + >>> from music21_tools.trecento.medren import MensuralNote >>> r_1 = MensuralNote('A', 'brevis') >>> r_1.setStem('down') Traceback (most recent call last): - MedRenException: A note of type brevis cannot be equipped with a stem + music21_tools.trecento.medren.MedRenException: A note of type brevis + cannot be equipped with a stem >>> r_2 = MensuralNote('A', 'semibrevis') >>> r_2.setStem('down') @@ -858,7 +857,8 @@ def setStem(self, direction): >>> r_3.setStem('down') Traceback (most recent call last): - MedRenException: This note already has the maximum number of stems + music21_tools.trecento.medren.MedRenException: This note already has + the maximum number of stems >>> r_3.setStem(None) >>> r_3.getStems() @@ -923,10 +923,11 @@ def setFlag(self, stemDirection, orientation): but may be set to 'left'. Any note with a downstem may also have a flag on that stem. + >>> from music21_tools.trecento.medren import MensuralNote >>> r_1 = MensuralNote('A', 'minima') >>> r_1.setFlag('up', 'right') Traceback (most recent call last): - MedRenException: a flag may not be added + music21_tools.trecento.medren.MedRenException: a flag may not be added to an upstem of note type minima >>> r_1.setStem('down') @@ -945,9 +946,9 @@ def setFlag(self, stemDirection, orientation): >>> r_3.setStem('side') >>> r_3.setFlag('side', 'left') Traceback (most recent call last): - MedRenException: a flag cannot be added to a stem with direction side + music21_tools.trecento.medren.MedRenException: a flag cannot be added + to a stem with direction side ''' - if stemDirection == 'up': if self.mensuralType != 'semiminima': raise MedRenException('a flag may not be added to an upstem of note type %s' % @@ -1042,6 +1043,7 @@ class Ligature(base.Music21Object): The ligatures outlined in blue would be constructed as follows: + >>> from music21_tools.trecento.medren import Ligature >>> l1 = Ligature(['A4', 'F4', 'G4', 'A4', 'B-4']) >>> l1.makeOblique(0) >>> l1.setStem(0, 'down', 'left') @@ -1057,7 +1059,6 @@ class Ligature(base.Music21Object): Note that ligatures cannot be displayed yet. ''' - def __init__(self, pitches=None, color='black', filled='yes'): super().__init__() self.noteheadShape = None @@ -1098,6 +1099,7 @@ def notes(self): ''' Returns the ligature as a list of mensural notes + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'B4']) >>> print([n.mensuralType for n in l.notes]) ['brevis', 'brevis'] @@ -1187,6 +1189,7 @@ def setColor(self, value, index=None): Sets the color of note at index to value. If no index is specified, or index is set to None, every note in the ligature is given value as a color. + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'C5', 'B4']) >>> l.setColor('red') >>> l.getColor() @@ -1240,6 +1243,7 @@ def setFillStatus(self, value, index=None): To set a notehead as filled, value should be 'yes' or 'filled'. To set a notehead as empty, value should be 'no' or 'empty' . + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'C5', 'B4']) >>> l.setFillStatus('filled') >>> l.getFillStatus() @@ -1292,6 +1296,7 @@ def makeOblique(self, startIndex): Note that an oblique notehead cannot start on the last note of a ligature. Also, a note that is a maxima cannot be the start or end of an oblique notehead. + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'C5', 'B4', 'A4']) >>> l.makeOblique(1) >>> l.getNoteheadShape(1) @@ -1300,15 +1305,15 @@ def makeOblique(self, startIndex): 'oblique' >>> l.makeOblique(0) Traceback (most recent call last): - MedRenException: cannot start oblique notehead at index 0 + music21_tools.trecento.medren.MedRenException: cannot start oblique notehead at index 0 >>> l.makeOblique(2) Traceback (most recent call last): - MedRenException: cannot start oblique notehead at index 2 + music21_tools.trecento.medren.MedRenException: cannot start oblique notehead at index 2 >>> l.makeOblique(3) Traceback (most recent call last): - MedRenException: no note exists at index 4 + music21_tools.trecento.medren.MedRenException: no note exists at index 4 ''' if startIndex < self._ligatureLength() - 1: currentShape = self.noteheadShape[startIndex] @@ -1332,6 +1337,7 @@ def makeSquare(self, index): If the note at index is part of an oblique notehead, all other notes that are part of that notehead are also set to have square noteheads. + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'C5', 'B4', 'A4']) >>> l.makeOblique(1) >>> l.makeSquare(2) @@ -1375,6 +1381,8 @@ def setMaxima(self, index, value): A note cannot be a maxima if that note has a stem. A note cannot be a maxima if the previous note has an up-stem. + >>> from music21_tools import trecento + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'C5', 'B4']) >>> l.setStem(0, 'up', 'left') >>> l.setMaxima(2, True) @@ -1383,11 +1391,11 @@ def setMaxima(self, index, value): >>> l.setMaxima(1, True) Traceback (most recent call last): - MedRenException: cannot make note at index 1 a maxima + music21_tools.trecento.medren.MedRenException: cannot make note at index 1 a maxima >>> l.setMaxima(0, True) Traceback (most recent call last): - MedRenException: cannot make note at index 0 a maxima + music21_tools.trecento.medren.MedRenException: cannot make note at index 0 a maxima >>> l.setMaxima(2, False) >>> l.isMaxima(2) @@ -1442,10 +1450,11 @@ def setStem(self, index, direction, orientation): Finally, a stem cannot be set on a note that is a maxima. An up-stem cannot be set on a note preceding a maxima. + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'C5', 'B4', 'A4', 'B4']) >>> l.setStem(0, 'none', 'left') Traceback (most recent call last): - MedRenException: direction "None" and orientation "left" + music21_tools.trecento.medren.MedRenException: direction "None" and orientation "left" not supported for ligatures >>> l.setStem(1, 'up', 'left') @@ -1454,16 +1463,18 @@ def setStem(self, index, direction, orientation): >>> l.setStem(2, 'down', 'right') Traceback (most recent call last): - MedRenException: a stem with direction "down" not permitted at index 2 + music21_tools.trecento.medren.MedRenException: a stem with direction + "down" not permitted at index 2 >>> l.setMaxima(4, True) >>> l.setStem(4, 'up', 'left') Traceback (most recent call last): - MedRenException: cannot place stem at index 4 + music21_tools.trecento.medren.MedRenException: cannot place stem at index 4 >>> l.setStem(3, 'up', 'left') Traceback (most recent call last): - MedRenException: a stem with direction "up" not permitted at index 3 + music21_tools.trecento.medren.MedRenException: a stem with direction + "up" not permitted at index 3 ''' if direction == 'None' or direction == 'none': direction = None @@ -1479,11 +1490,11 @@ def setStem(self, index, direction, orientation): self.stems[index] = (direction, orientation) elif orientation in ['left', 'right']: if index == 0: - prevStem = (None,None) + prevStem = (None, None) nextStem = self.getStem(1) elif index == self._ligatureLength() - 1: prevStem = self.getStem(self._ligatureLength() - 2) - nextStem = (None,None) + nextStem = (None, None) else: prevStem = self.getStem(index - 1) nextStem = self.getStem(index + 1) @@ -1514,11 +1525,11 @@ def setStem(self, index, direction, orientation): else: raise MedRenException( 'a stem with orientation "%s" not permitted at index %d' % - (orientation,index)) + (orientation, index)) else: raise MedRenException( 'direction "%s" and orientation "%s" not supported for ligatures' % - (direction,orientation)) + (direction, orientation)) self._notes = [] def isReversed(self, index): @@ -1547,6 +1558,7 @@ def setReverse(self, endIndex, value): A reversed note is displayed directly on top of the preceeding note in the ligature. + >>> from music21_tools.trecento.medren import Ligature >>> l = Ligature(['A4', 'C5', 'F5', 'F#5']) >>> l.setStem(1, 'down', 'left') >>> l.setStem(2, 'down', 'left') @@ -1557,12 +1569,12 @@ def setReverse(self, endIndex, value): >>> l.setReverse(2, True) Traceback (most recent call last): - MedRenException: the note at index 2 + music21_tools.trecento.medren.MedRenException: the note at index 2 cannot be given reverse value True >>> l.setReverse(3, True) Traceback (most recent call last): - MedRenException: the note at index 3 + music21_tools.trecento.medren.MedRenException: the note at index 3 cannot be given reverse value True ''' if value == 'True' or value == 'true': @@ -1700,6 +1712,9 @@ def breakMensuralStreamIntoBrevisLengths(inpStream, inpMOrD=None, printUpdates=F Otherwise, this causes a inconsistency when converting the stream. + >>> from music21_tools.trecento.medren import ( + ... GeneralMensuralNote, MensuralNote, breakMensuralStreamIntoBrevisLengths) + >>> from music21_tools.trecento.notation import Divisione, Punctus >>> s = stream.Score() >>> p = stream.Part() >>> m = stream.Measure() @@ -1707,58 +1722,60 @@ def breakMensuralStreamIntoBrevisLengths(inpStream, inpMOrD=None, printUpdates=F >>> s.append(GeneralMensuralNote('B')) >>> breakMensuralStreamIntoBrevisLengths(s) Traceback (most recent call last): - MedRenException: cannot combine objects - of type , - within stream + music21_tools.trecento.medren.MedRenException: + cannot combine objects of type , + within stream >>> s = stream.Score() >>> p.append(s) >>> breakMensuralStreamIntoBrevisLengths(p) Traceback (most recent call last): - MedRenException: Hierarchy of - violated by + music21_tools.trecento.medren.MedRenException: + Hierarchy of + violated by + + The normal case: a single flat ``Part`` carrying a Divisione at its head, + followed by mensural notes and puncti. The function returns a ``Part`` + of ``Measure`` objects grouped by brevis-length, with the Divisione placed + as the first element of the first Measure. - >>> from music21_tools.trecento import notation >>> p = stream.Part() - >>> m.append(MensuralNote('G', 'B')) - >>> p.append(notation.Divisione('.q.')) - >>> p.repeatAppend(MensuralNote('A', 'SB'),2) - >>> p.append(notation.Punctus()) - >>> p.repeatAppend(MensuralNote('B', 'M'),4) - >>> p.append(notation.Punctus()) + >>> p.append(Divisione('.q.')) + >>> p.repeatAppend(MensuralNote('A', 'SB'), 2) + >>> p.append(Punctus()) + >>> p.repeatAppend(MensuralNote('B', 'M'), 4) + >>> p.append(Punctus()) >>> p.append(MensuralNote('C', 'B')) - >>> s.append(notation.Divisione('.p.')) - >>> s.append(p) - >>> s.append(m) - >>> breakMensuralStreamIntoBrevisLengths(s, printUpdates = True) - Traceback (most recent call last): - MedRenException: Mensuration or divisione - <...Divisione .q.> not consistent within hierarchy - - >>> s = stream.Stream() - >>> s.append(notation.Divisione('.q.')) - >>> s.append(p) - >>> s.append(m) - >>> t = breakMensuralStreamIntoBrevisLengths(s, printUpdates = True) + >>> t = breakMensuralStreamIntoBrevisLengths(p, printUpdates=True) Getting measure 0... ... >>> t.show('text') - {0.0} <...Divisione .q.> - {0.0} - {0.0} - {0.0} <...MensuralNote semibrevis A> - {0.0} <...MensuralNote semibrevis A> - {0.0} - {0.0} <...MensuralNote minima B> - {0.0} <...MensuralNote minima B> - {0.0} <...MensuralNote minima B> - {0.0} <...MensuralNote minima B> - {0.0} - {0.0} <...MensuralNote brevis C> - {0.0} - {0.0} <...MensuralNote brevis G> + {0.0} + {0.0} + {0.0} <...MensuralNote semibrevis A> + {0.0} <...MensuralNote semibrevis A> + {0.0} + {0.0} <...MensuralNote minima B> + {0.0} <...MensuralNote minima B> + {0.0} <...MensuralNote minima B> + {0.0} <...MensuralNote minima B> + {0.0} + {0.0} <...MensuralNote brevis C> + + >>> first = t.getElementsByClass(stream.Measure).first() + >>> isinstance(first[0], Divisione) + True + + Now insert a second, conflicting Divisione mid-Part — the function refuses + because trecento divisione changes are only allowed at the highest stream + level, not within a single voice. + + >>> p.insert(5.0, Divisione('.p.')) + >>> breakMensuralStreamIntoBrevisLengths(p) + Traceback (most recent call last): + music21_tools.trecento.medren.MedRenException: Mensuration or + divisione not consistent within hierarchy ''' - mOrD = inpMOrD mOrDInAsNone = True if mOrD is not None: @@ -1767,30 +1784,29 @@ def breakMensuralStreamIntoBrevisLengths(inpStream, inpMOrD=None, printUpdates=F inpStream_copy = copy.deepcopy(inpStream) # Preserve your input newStream = inpStream.__class__() - def isHigherInhierarchy(l, u): + def isHigherInHierarchy(lower, upper): hierarchy = ['Stream', 'Score', 'Part', 'Measure'] - uclass0 = None - for tryClass in u.classes: + upperClass = None + for tryClass in upper.classes: if tryClass in hierarchy: - uclass0 = tryClass + upperClass = tryClass break - lclass0 = None - for tryClass in l.classes: + lowerClass = None + for tryClass in lower.classes: if tryClass in hierarchy: - lclass0 = tryClass + lowerClass = tryClass break + if upperClass is None: + raise MedRenException('Cannot find class in our hierarchy of streams: %s' % (upper)) + if lowerClass is None: + raise MedRenException('Cannot find class in our hierarchy of streams: %s' % (lower)) - if uclass0 is None: - raise MedRenException("Cannot find class in our hierarchy of streams: %s" % (u)) - if lclass0 is None: - raise MedRenException("Cannot find class in our hierarchy of streams: %s" % (l)) - - if hierarchy.index(uclass0) == 0: + if hierarchy.index(upperClass) == 0: return False else: - return hierarchy.index(lclass0) <= hierarchy.index(uclass0) + return hierarchy.index(lowerClass) <= hierarchy.index(upperClass) tempStream_1, tempStream_2 = inpStream_copy.splitByClass(None, lambda x: x.isStream) if tempStream_1: @@ -1802,7 +1818,7 @@ def isHigherInhierarchy(l, u): for item in tempStream_2: newStream.append(item) - if ('Mensuration' in item.classes) or ('Divisione' in item.classes): + if isinstance(item, MedievalMeter): if mOrDInAsNone: # If first case or changed mOrD mOrD = item elif mOrD.standardSymbol != item.standardSymbol: @@ -1812,7 +1828,7 @@ def isHigherInhierarchy(l, u): tempStream_1_1, tempStream_1_2 = tempStream_1.splitByClass( None, - lambda x: isHigherInhierarchy(x, tempStream_1)) + lambda x: isHigherInHierarchy(x, tempStream_1)) if tempStream_1_1: raise MedRenException('Hierarchy of %s violated by %s' % (tempStream_1.__class__, tempStream_1_1[0].__class__)) @@ -1828,36 +1844,55 @@ def isHigherInhierarchy(l, u): else: measureNum = 0 mensuralMeasure = [] + # Hold clef/divisione/mensuration until the first Measure is built, then + # prepend them inside it — that matches the natural music21 layout + # (TimeSignature/Clef live inside their first Measure, not as siblings + # of it). + pendingFirstMeasureItems = [] for e in inpStream_copy: - if 'MensuralClef' in e.classes: - newStream.append(e) - elif ('Mensuration' in e.classes) or ('Divisione' in e.classes): - if mOrDInAsNone: # If first case or changed mOrD + if isinstance(e, MensuralClef): + pendingFirstMeasureItems.append(e) + elif isinstance(e, MedievalMeter): + if mOrD is None: mOrD = e - newStream.append(e) - elif mOrD.standardSymbol != e.standardSymbol: # If higher, different mOrD found + pendingFirstMeasureItems.append(e) + elif mOrD.standardSymbol != e.standardSymbol: + # A second, different divisione in the same flat stream — + # not allowed (divisione changes must happen at a higher + # hierarchy level). raise MedRenException( - 'Mensuration or divisione %s not consistent within hierarchy' % e) - elif 'Ligature' in e.classes: + f'Mensuration or divisione {e} not consistent within hierarchy') + # else: same divisione repeated, ignore silently + elif isinstance(e, Ligature): tempStream = stream.Stream() for mn in e.notes: tempStream.append(mn) for m in breakMensuralStreamIntoBrevisLengths(tempStream, printUpdates): newStream.append(m) - elif ('GeneralMensuralNote' in e.classes) and (e not in mensuralMeasure): + elif (isinstance(e, GeneralMensuralNote)) and (e not in mensuralMeasure): m = stream.Measure(number=measureNum) if printUpdates is True: - print('Getting measure %s...' % measureNum) + print(f'Getting measure {measureNum}...') mensuralMeasure = e._getSurroundingMeasure(mOrD, inpStream_copy)[0] if printUpdates is True: - print('mensuralMeasure %s' % mensuralMeasure) + print(f'mensuralMeasure {mensuralMeasure}') + # Place clef/divisione (collected so far) at the front of the + # very first Measure; subsequent Measures get only their notes. + for item in pendingFirstMeasureItems: + m.append(item) + pendingFirstMeasureItems = [] for item in mensuralMeasure: m.append(item) newStream.append(m) measureNum += 1 + # If the input was just metadata (clef/divisione) with no notes, place + # those items at the top of newStream so they aren't lost. + for item in pendingFirstMeasureItems: + newStream.append(item) + return newStream # ----------------------------------------------------------- @@ -1872,11 +1907,11 @@ def setBarlineStyle(score, newStyle, oldStyle='regular', *, inPlace=False): returns the Score object. ''' - if inPlace is False: + if not inPlace: score = copy.deepcopy(score) oldStyle = oldStyle.lower() - for m in score.semiFlat: + for m in score.flatten(retainContainers=True): if isinstance(m, stream.Measure): barline = m.rightBarline if barline is None: @@ -1886,13 +1921,13 @@ def setBarlineStyle(score, newStyle, oldStyle='regular', *, inPlace=False): barline.style = newStyle return score -def scaleDurations(score, scalingNum=1, *, inPlace=False, scaleUnlinked=True): +def scaleDurations(score, scalingNum: int|float = 1, *, inPlace=False, scaleUnlinked=True): ''' scale all notes and TimeSignatures by the scaling amount. returns the Score object ''' - if inPlace is False: + if not inPlace: score = copy.deepcopy(score) for el in score.recurse(): @@ -1903,7 +1938,7 @@ def scaleDurations(score, scalingNum=1, *, inPlace=False, scaleUnlinked=True): el.duration.linked is False and scaleUnlinked is True): raise MedRenException('scale unlinked is not yet supported') - if isinstance(el, tempo.MetronomeMark): + if isinstance(el, tempo.MetronomeMark) and el.number is not None: el.number = el.number * scalingNum elif isinstance(el, meter.TimeSignature): newNum = el.numerator @@ -1917,7 +1952,7 @@ def scaleDurations(score, scalingNum=1, *, inPlace=False, scaleUnlinked=True): raise MedRenException( 'cannot create a scaling of the TimeSignature for this ratio') newDem = int(newDem) - el.loadRatio(newNum, newDem) + el.load(f'{newNum}/{newDem}') for p in score.parts: p.makeBeams(inPlace=True) @@ -1957,7 +1992,7 @@ def transferTies(score, *, inPlace=False): if tupAct != 0: # error... tempTuplet = duration.Tuplet(tupAct, tupNorm, copy.deepcopy(tempDuration.components[0])) - tempTuplet.tupletActualShow = "none" + tempTuplet.tupletActualShow = 'none' tempTuplet.bracket = False tieBeneficiary.duration = tempDuration tieBeneficiary.duration.tuplets = (tempTuplet,) @@ -1967,19 +2002,19 @@ def transferTies(score, *, inPlace=False): tiedEl.style.hideObjectOnPrint = True tiedNotes = [] - if inPlace is False: + if not inPlace: return score -def convertHouseStyle(score, durationScale=2, barlineStyle='tick', - tieTransfer=True, inPlace=False): +def convertHouseStyle(score, + durationScale: int|float|Literal[False] = 2, + barlineStyle: str|Literal[False] = 'tick', + tieTransfer: bool = True, + inPlace: bool = False): ''' The method :meth:`convertHouseStyle` takes a score, durationScale, barlineStyle, tieTransfer, and inPlace as arguments. Of these, only score is not optional. - Default values for durationScale, barlineStyle, tieTransfer, and inPlace are - 2, 'tick', True, and False respectively. - Changing :attr:`convertHouseStyle.barlineStyle` changes how the barlines are displayed within the piece. @@ -1996,30 +2031,28 @@ def convertHouseStyle(score, durationScale=2, barlineStyle='tick', and with the barline style set to 'tick'. The circled area shows a space left blank due to tieTransfer being True. - + >>> from music21_tools.trecento.medren import convertHouseStyle >>> from music21 import corpus >>> gloria = corpus.parse('luca/gloria') - >>> #_DOCS_HIDE gloria.show() - + >>> # _DOCS_HIDE gloria.show() .. image:: images/medren_convertHouseStyle_1.* :width: 600 >>> newGloria = convertHouseStyle(gloria, durationScale=2, ... barlineStyle='tick', tieTransfer=True) - >>> #_DOCS_HIDE newGloria.show() + >>> # _DOCS_HIDE newGloria.show() .. image:: images/medren_convertHouseStyle_2.* :width: 600 ''' - - if inPlace is False: + if not inPlace: score = copy.deepcopy(score) if durationScale is not False: scaleDurations(score, durationScale, inPlace=True) if barlineStyle is not False: setBarlineStyle(score, barlineStyle, inPlace=True) - if tieTransfer is not False: + if tieTransfer: transferTies(score, inPlace=True) return score @@ -2037,7 +2070,7 @@ def cummingSchubertStrettoFuga(score): thisGeneric = thisInt.generic.directed for strettoType in [8, -8, 5, -5, 4, -4]: strettoAllowed = [x[0] for x in allowableStrettoIntervals[strettoType]] - # inefficent but who cares + # inefficient repeatAllowed = [x[1] for x in allowableStrettoIntervals[strettoType]] for j in range(len(strettoAllowed)): thisStrettoAllowed = strettoAllowed[j] @@ -2051,23 +2084,15 @@ def cummingSchubertStrettoFuga(score): if score.title: print(score.title) - print("intv.\tcount\tpercent") - for l in sorted(strettoKeys): - print("%2d\t%3d\t%2d%%" % (l, strettoKeys[l], strettoKeys[l] * 100 / len(sn) - 1)) - print("\n") + print('intv.\tcount\tpercent') + for intv in sorted(strettoKeys): + print('%2d\t%3d\t%2d%%' % (intv, strettoKeys[intv], strettoKeys[intv] * 100 / len(sn) - 1)) + print('\n') class MedRenException(exceptions21.Music21Exception): pass -class Test(unittest.TestCase): - - def runTest(self): - pass - class TestExternal(unittest.TestCase): # pragma: no cover - def runTest(self): - pass - def xtestBarlineConvert(self): from music21 import corpus testPiece = corpus.parse('luca/gloria') @@ -2096,7 +2121,7 @@ def xtestUnlinked(self): n1.duration.quarterLength = 2.0 s.append([n1, n2]) - def xtestPythagSharps(self): + def xtestPythagoreanSharps(self): from music21 import corpus, midi gloria = corpus.parse('luca/gloria') p = gloria.parts[0].flatten() @@ -2117,22 +2142,15 @@ def testHouseStyle(self): gloriaNew = convertHouseStyle(gloria) gloriaNew.show() -def testStretto(): +def demoStretto(): from music21 import converter - salve = converter.parse("A4 A G A D A G F E F G F E D", '4/4') # salveRegina liber p. 276 - adTe = converter.parse("D4 F A G G F A E G F E D G C D E D G F E D", '4/4') # ad te clamamus - etJesum = converter.parse("D4 AA C D D D E E D D C G F E D G F E D C D", '4/4') - salve.title = "Salve Regina (opening)LU p. 276" - adTe.title = "...ad te clamamus" - etJesum.title = "...et Jesum" + # salveRegina liber p. 276 + salve = converter.parse('tinyNotation: 4/4 A4 A G A D A G F E F G F E D') + # ad te clamamus + adTe = converter.parse('tinyNotation: 4/4 D4 F A G G F A E G F E D G C D E D G F E D') + etJesum = converter.parse('tinyNotation: 4/4 D4 AA C D D D E E D D C G F E D G F E D C D') + salve.title = 'Salve Regina (opening)LU p. 276' + adTe.title = '...ad te clamamus' + etJesum.title = '...et Jesum' for piece in [salve, adTe, etJesum]: cummingSchubertStrettoFuga(piece) - -if __name__ == '__main__': - import music21 - music21.mainTest(Test, 'importPlusRelative') # TestExternal) - # music21.testConvertMensuralMeasure() - # almaRedemptoris = converter.parse("C4 E F G A G G G A B c G", '4/4') #liber 277 (pdf401) - # puer = converter.parse('G4 d d d e d c c d c e d d', '4/4') # puer natus est 408 (pdf 554) - # almaRedemptoris.title = "Alma Redemptoris Mater LU p. 277" - # puer.title = "Puer Natus Est Nobis" diff --git a/music21_tools/trecento/notation.py b/music21_tools/trecento/notation.py index 199d1b3..b62c718 100644 --- a/music21_tools/trecento/notation.py +++ b/music21_tools/trecento/notation.py @@ -26,16 +26,21 @@ from music21 import common from music21 import duration from music21 import exceptions21 +from music21 import metadata from music21 import meter +from music21 import text + +from music21 import environment from music21 import note from music21 import stream from music21 import tie from music21 import tinyNotation -from music21 import environment -_MOD = "trecento/notation.py" -environLocal = environment.Environment(_MOD) from . import medren +from ._base import MedievalMeter + +_MOD = 'trecento/notation.py' +environLocal = environment.Environment(_MOD) _validDivisiones = { (None, None): 0, @@ -118,8 +123,8 @@ def postParse(self, n): class FlagsModifier(tinyNotation.Modifier): def postParse(self, m21Obj): - direction = {'': None, 'S': 'side', 'D': 'down', 'U':'up'} - orientation = {'': None,'L': 'left', 'R': 'right'} + direction = {'': None, 'S': 'side', 'D': 'down', 'U': 'up'} + orientation = {'': None, 'L': 'left', 'R': 'right'} for flag in self.modifierData.split('/'): if len(flag) > 1 and (flag[0] in direction) and (flag[1] in orientation): m21Obj.setFlag(direction[flag[0]], orientation[flag[1]]) @@ -129,7 +134,7 @@ def postParse(self, m21Obj): class StemsModifier(tinyNotation.Modifier): def postParse(self, m21Obj): - direction = {'': None, 'S': 'side', 'D': 'down', 'U':'up'} + direction = {'': None, 'S': 'side', 'D': 'down', 'U': 'up'} orientation = {'L': 'left', 'R': 'right'} if '/' in self.modifierData or len(self.modifierData) == 1: for stem in self.modifierData.split('/'): @@ -241,6 +246,8 @@ class TrecentoTinyConverter(tinyNotation.Converter): If no mensural type is specified, it is assumed to be the same as the previous note. I.e., c(SB) B c d is a string of semibreves. + >>> from music21_tools.trecento.medren import Ligature + >>> from music21_tools.trecento.notation import TrecentoTinyConverter >>> tTNN = TrecentoTinyConverter('a(M)').parse().stream.flatten().notes[0] >>> tTNN.pitch @@ -256,11 +263,11 @@ class TrecentoTinyConverter(tinyNotation.Converter): :meth:`music21.medren.MensuralNote.setStem()`. >>> tTNN = TrecentoTinyConverter( - ... 'a(SB)[S]').parse().stream.flatten().notes[0] + ... 'a(SB)[S]').parse().stream.recurse().notes.first() >>> tTNN.getStems() ['side'] - >>> tTNN = TrecentoTinyConverter('a(M)[D]').parse().stream.flatten().notes[0] + >>> tTNN = TrecentoTinyConverter('a(M)[D]').parse().stream.recurse().notes.first() >>> tTNN.getStems() ['up', 'down'] @@ -272,7 +279,7 @@ class TrecentoTinyConverter(tinyNotation.Converter): follow the rules outlined in :meth:`music21.medren.MensuralNote.setFlag()`. >>> tTNN = TrecentoTinyConverter( - ... 'a(SM)_UL').parse().stream.flatten().notes[0] + ... 'a(SM)_UL').parse().stream.recurse().notes.first() >>> tTNN.getStems() ['up'] @@ -286,7 +293,7 @@ class TrecentoTinyConverter(tinyNotation.Converter): direction-orientation pairs, as shown in the following complex example: >>> tTNN = TrecentoTinyConverter( - ... 'a(SM)[D]_UL/DR').parse().stream.flatten().notes[0] + ... 'a(SM)[D]_UL/DR').parse().stream.recurse().notes.first() >>> tTNN.pitch >>> tTNN.getStems() @@ -310,11 +317,11 @@ class TrecentoTinyConverter(tinyNotation.Converter): {0.0} {0.0} {0.0} - {0.0} + {0.0} {0.0} - >>> tTNN = ts.flatten().getElementsByClass('Ligature')[0] + >>> tTNN = ts[Ligature].first() >>> tTNN - + >>> [str(p) for p in tTNN.pitches] ['F4', 'G4', 'A4', 'G4', 'F4', 'C4'] @@ -331,7 +338,7 @@ class TrecentoTinyConverter(tinyNotation.Converter): Examples: >>> ts = TrecentoTinyConverter(r'lig{f a[DL]=R}').parse().stream - >>> tTNN = ts.flatten().getElementsByClass('Ligature')[0] + >>> tTNN = ts[Ligature].first() >>> tTNN.getStem(1) ('down', 'left') @@ -340,7 +347,7 @@ class TrecentoTinyConverter(tinyNotation.Converter): >>> tTNN = TrecentoTinyConverter( - ... 'lig{f g a[UR] g f(Mx)}').parse().stream.flatten().getElementsByClass('Ligature')[0] + ... 'lig{f g a[UR] g f(Mx)}').parse().stream[Ligature].first() >>> print([n.mensuralType for n in tTNN.notes]) ['longa', 'brevis', 'semibrevis', 'semibrevis', 'maxima'] @@ -361,7 +368,7 @@ class TrecentoTinyConverter(tinyNotation.Converter): ... '$C3 .p. c(SB) d e p d(B) lig{e d c}').parse().stream.flatten() >>> tTNS.show('text') {0.0} - {0.0} + {0.0} <...MensuralClef> {0.0} <...Divisione .p.> {0.0} <...medren.MensuralNote semibrevis C> {0.0} <...medren.MensuralNote semibrevis D> @@ -375,7 +382,8 @@ class TrecentoTinyConverter(tinyNotation.Converter): 'quad': tinyNotation.QuadrupletState, 'lig': LigatureState } - def __init__(self, stringRep=""): + + def __init__(self, stringRep=''): super().__init__(stringRep) self.tokenMap = [ (r'(\$[A-Z]\d?)', ClefToken), @@ -388,12 +396,12 @@ def __init__(self, stringRep=""): self.modifierUnderscore = FlagsModifier self.modifierAngle = LigatureNoteheadModifier self.modifierParens = MensuralTypeModifier - self.modifierSquare = StemsModifier # or LigatureStemsModifier -# self.modifierMap = [ -# (r'\[([A-Z]?(\/[A-Z])*)\]', StemsModifier), -# (r'\[([A-Z][A-Z])\]', LigatureStemsModifier), -# (r'(\/R)', LigatureReverseModifier), -# ] + self.modifierSquare = StemsModifier # or LigatureStemsModifier + # self.modifierMap = [ + # (r'\[([A-Z]?(\/[A-Z])*)\]', StemsModifier), + # (r'\[([A-Z][A-Z])\]', LigatureStemsModifier), + # (r'(\/R)', LigatureReverseModifier), + # ] self.stateDict['lastDuration'] = 0.0 self.stateDict['previousMensuralType'] = None @@ -404,6 +412,7 @@ def parse(self, parent=None): if parent: r.mensuralType = parent.stateDict['previousMensuralType'] + class TrecentoNoteToken(tinyNotation.NoteToken): ''' For documentation please see :class:`music21.TrecentoTinyConverter`. @@ -430,7 +439,7 @@ def fontString(self): '''The utf-8 code corresponding the punctus in Cicionia font''' return self._fontString -class Divisione(meter.TimeSignature): +class Divisione(MedievalMeter): ''' An object representing a divisione found in Trecento Notation. Takes one argument, nameOrSymbol. This is the name of the divisione, or @@ -443,6 +452,7 @@ class Divisione(meter.TimeSignature): The corresponding symbols are '.q.', '.i.', '.p.', '.n.', '.o.', and '.d.'. + >>> from music21_tools.trecento.notation import Divisione >>> d = Divisione('senaria imperfecta') >>> d.standardSymbol '.i.' @@ -454,7 +464,6 @@ class Divisione(meter.TimeSignature): >>> d = Divisione('q') >>> d.standardSymbol '.q.' - ''' def __init__(self, nameOrSymbol='.p.'): self.name = None @@ -537,6 +546,8 @@ def convertTrecentoStream(inpStream, inpDiv=None): Anonymous, Se per dureça. Padua, Biblioteca Universitaria, MS 1115. Folio Ar. + >>> from music21 import stream + >>> from music21_tools.trecento.notation import TrecentoTinyConverter, convertTrecentoStream >>> upperString = ".p. $C1 g(B) g(M) f e g f e p g(SB) f(SM) e d e(M) f p " >>> upperString += "e(SB) e(SM) f e d(M) c p " >>> upperString += "d(SB) r e p f(M) e d e d c p d(SB) c(M) d c d p e(SB) r(M) " @@ -578,49 +589,44 @@ def convertTrecentoStream(inpStream, inpDiv=None): :width: 600 ''' - div = inpDiv - offset = 0 - # hierarchy = ['measure', 'part', 'score'] - - convertedStream = None - if 'measure' in inpStream.classes: - convertedStream = stream.Measure() - elif 'part' in inpStream.classes: + # Find a divisione (recursively if needed); fail loudly if none exists. + div = inpDiv or inpStream[Divisione].first() + if div is None: + raise TrecentoNotationException( + f'No divisione found in {inpStream}; cannot convert to modern notation.') + + # Mirror the type of the input where possible (Measure/Part/Score), + # otherwise produce a plain Stream. + if isinstance(inpStream, stream.Measure): + convertedStream: stream.Stream = stream.Measure() + elif isinstance(inpStream, stream.Part): convertedStream = stream.Part() - elif 'score' in inpStream.classes: - convertedStream = stream.Measure() + elif isinstance(inpStream, stream.Score): + convertedStream = stream.Score() else: convertedStream = stream.Stream() - measuredStream = medren.breakMensuralStreamIntoBrevisLengths(inpStream, inpDiv) + measuredStream = medren.breakMensuralStreamIntoBrevisLengths(inpStream, div) print('') - for e in measuredStream: - - if ('Metadata' in e.classes) or ('TextBox' in e.classes): #Formatting - convertedStream.append(e) - - elif 'MensuralClef' in e.classes: - pass - - elif 'Divisione' in e.classes: - div = e - - elif e.isMeasure: - print(' Converting measure %s' % e.number) - measureList = convertBrevisLength(e, convertedStream, - inpDiv=div, measureNumOffset=offset) - for m in measureList: - convertedStream.append(m) - - elif e.isStream: - print('Converting stream %s' % e) - convertedPart = convertTrecentoStream(e, inpDiv = div) - convertedStream.insert(0, convertedPart) - - else: - raise medren.MedRenException( - 'Object %s cannot be processed as part of a trecento stream' % e) + # Preserve formatting elements. + for fmt_el in measuredStream[metadata.Metadata]: + convertedStream.append(fmt_el) + for fmt_el in measuredStream[text.TextBox]: + convertedStream.append(fmt_el) + + # Convert measures (one or more modern Measures per mensural brevis-length). + for measureEl in measuredStream.getElementsByClass(stream.Measure): + print(f' Converting measure {measureEl.number}') + for m in convertBrevisLength(measureEl, convertedStream, + inpDiv=div, measureNumOffset=0): + convertedStream.append(m) + + # Recurse on nested non-Measure streams (Parts, sub-Scores, etc). + for subStream in measuredStream: + if isinstance(subStream, stream.Stream) and not isinstance(subStream, stream.Measure): + print(f'Converting stream {subStream}') + convertedStream.insert(0, convertTrecentoStream(subStream, inpDiv=div)) return convertedStream @@ -643,21 +649,29 @@ def convertBrevisLength(brevisLength, convertedStream, inpDiv=None, measureNumOf rem = None measureList = [] - mList = list(brevisLength.recurse())[1:] + # .recurse() does not include the host stream (skipSelf default flipped + # to True in music21 v5.5), so a `[1:]` here would silently drop a real + # element (typically the first note). Pull only mensural objects — the + # surrounding Clef/Divisione/Punctus ride along inside the Measure but + # contribute no note-length and would confuse BrevisLengthTranslator. + mList = [el for el in brevisLength.recurse() + if isinstance(el, medren.GeneralMensuralNote)] + + # Resolve the Divisione for this measure. Only one source is allowed: + # either the enclosing context (inpDiv) OR an in-measure Divisione, not both. + divs = list(brevisLength[Divisione]) + if len(divs) > 1 or (divs and inpDiv is not None): + offending = divs[0] if inpDiv is None else inpDiv + raise TrecentoNotationException( + f'divisione {offending} not consistent within hierarchy') + # Should already be caught by medren.breakMensuralStreamIntoBrevisLengths, + # but just in case... + if div is None and divs: + div = divs[0] tempTBL = BrevisLengthTranslator(div, mList) lenList = tempTBL.getKnownLengths() - - for item in mList: - if 'Divisione' in item.classes: - if div is None: - div = item - else: - raise TrecentoNotationException( - 'divisione %s not consistent within heirarchy' % item) - # Should already be caught by medren.breakMensuralStreamIntoBrevisLengths, - # but just in case... mDur = 0 if div is not None: rem = div.barDuration.quarterLength @@ -673,14 +687,19 @@ def convertBrevisLength(brevisLength, convertedStream, inpDiv=None, measureNumOf else: raise TrecentoNotationException('Cannot find or determine or divisione') - if lenList[0] > div.minimaPerBrevis: #Longa, Maxima + if not lenList: + # Empty measure (e.g. one that contained only a Divisione/Clef and no + # mensural notes); nothing to convert. + return measureList + + if lenList[0] > div.minimaPerBrevis: # Longa, Maxima startNote = note.Note(mList[0].pitch) startNote.duration = div.barDuration startNote.tie = tie.Tie('start') m.append(startNote) measureList.append(m) - for dummy in range(int(lenList[0]/div.minimaPerBrevis) - 2): + for dummy in range(int(lenList[0] / div.minimaPerBrevis) - 2): measureNumOffset += 1 tempMeasure = stream.Measure(number=brevisLength.number + measureNumOffset) @@ -700,18 +719,18 @@ def convertBrevisLength(brevisLength, convertedStream, inpDiv=None, measureNumOf else: for i in range(len(mList)): - if 'MensuralRest' in mList[i].classes: + if isinstance(mList[i], medren.MensuralRest): n = note.Rest() - elif 'MensuralNote' in mList[i].classes: + elif isinstance(mList[i], medren.MensuralNote): n = note.Note(mList[i].pitch) - dur = lenList[i]*mDur + dur = lenList[i] * mDur - if (rem - dur) > -0.0001: #Fits w/i measure up to rounding error + if (rem - dur) > -0.0001: # Fits w/i measure up to rounding error n.duration = duration.Duration(dur) m.append(n) rem -= dur - else: #Syncopated across barline + else: # Syncopated across barline n.duration = duration.Duration(rem) n.tie = tie.Tie('start') m.append(n) @@ -745,16 +764,16 @@ class BrevisLengthTranslator: This acts a helper class to improve the efficiency of :class:`music21.convertBrevisLength`. -# >>> names = ['SM', 'SM', 'SM', 'SM', 'SB', 'SB', 'SB', 'SB', 'SB', 'SM', 'SM', 'SM'] -# >>> BL = [medren.MensuralNote('A', n) for n in names] -# >>> BL[3] = medren.MensuralRest('SM') -# >>> for mn in BL[-3:]: -# ... mn.setFlag('up', 'left') -# >>> for mn in BL[4:9]: -# ... mn.setStem('down') -# >>> TBL = BrevisLengthTranslator(div, BL) -# >>> TBL.getKnownLengths() -# [0.5, 0.5, 0.5, 0.5, 4.0, 4.0, 4.0, 4.0, 4.0, 0.666..., 0.666..., 0.666...] + # >>> names = ['SM', 'SM', 'SM', 'SM', 'SB', 'SB', 'SB', 'SB', 'SB', 'SM', 'SM', 'SM'] + # >>> BL = [medren.MensuralNote('A', n) for n in names] + # >>> BL[3] = medren.MensuralRest('SM') + # >>> for mn in BL[-3:]: + # ... mn.setFlag('up', 'left') + # >>> for mn in BL[4:9]: + # ... mn.setStem('down') + # >>> TBL = BrevisLengthTranslator(div, BL) + # >>> TBL.getKnownLengths() + # [0.5, 0.5, 0.5, 0.5, 4.0, 4.0, 4.0, 4.0, 4.0, 0.666..., 0.666..., 0.666...] ''' def __init__(self, divisione=None, BL=None, pDS=False): @@ -764,11 +783,11 @@ def __init__(self, divisione=None, BL=None, pDS=False): self.brevisLength = BL self.unchangeableNoteLengthsList = [] - self.unknownLengthsDict = {'semibrevis':[], - 'semibrevis_downstem':[], - 'semiminima_right_flag':[], - 'seminima_left_flag':[], - 'semiminima_rest':[] + self.unknownLengthsDict = {'semibrevis': [], + 'semibrevis_downstem': [], + 'semiminima_right_flag': [], + 'seminima_left_flag': [], + 'semiminima_rest': [] } self.knownLengthsList = [] @@ -786,7 +805,7 @@ def __init__(self, divisione=None, BL=None, pDS=False): self.hasLastSB = None def getKnownLengths(self): - if 'Divisione' in self.div.classes: + if isinstance(self.div, Divisione): self.knownLengthsList = self.translate() else: raise TrecentoNotationException('%s not recognized as divisione' % self.div) @@ -805,34 +824,35 @@ def getBreveStrength(self, lengths): and see that the second is more logical. Note that the strength itself is meaningless except when compared to other possible lengths for the same notes. + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> div = Divisione('.n.') >>> names = ['SB', 'M', 'M', 'M', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> TBL.getBreveStrength([2.0, 1.0, 1.0, 1.0, 4.0]) 2.0555... >>> TBL.getBreveStrength([3.0, 1.0, 1.0, 1.0, 3.0]) 2.8333... - ''' div = self.div BL = self.brevisLength - typeStrength = {'semibrevis': 1.0, 'minima': 0.5, 'semiminima':0.25} + typeStrength = {'semibrevis': 1.0, 'minima': 0.5, 'semiminima': 0.25} beatStrength = 0 strength = 0 curBeat = 0 for i in range(len(lengths)): - if math.isclose(curBeat - round(curBeat), 0): #Rounding error + if math.isclose(curBeat - round(curBeat), 0): # Rounding error curBeat = round(curBeat) if div.standardSymbol in ['.i.', '.n.']: if math.isclose(curBeat % 3, 0): beatStrength = 1.0 elif curBeat % 3 - 1 or i % 3 == 2: - beatStrength = 1/3 + beatStrength = 1 / 3 else: - beatStrength = 1/9 + beatStrength = 1 / 9 elif div.standardSymbol in ['.q.', '.o.', '.d.']: if curBeat % 4 == 0: beatStrength = 1.0 @@ -866,7 +886,7 @@ def getBreveStrength(self, lengths): for i, item in enumerate(self.brevisLength): if (isinstance(item, medren.MensuralNote) and - (not 'down' in item.getStems()) and + ('down' not in item.getStems()) and (lengths[i] > lastSBLen)): strength = 0 @@ -895,14 +915,18 @@ def determineStrongestMeasureLengths(self, :param shrinkable_indices: tuple of indices of elements able to take up slack (i.e. ending SB, or downstem SB) + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> div = Divisione('.n.') >>> names = ['SB', 'M', 'M', 'M', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) - >>> TBL.determineStrongestMeasureLengths([2.0, 1.0, 1.0, 1.0, 4.0], - ... ([0],), (1,), (1.0,), 0.0, shrinkable_indices=(-1,)) + >>> TBL.determineStrongestMeasureLengths( + ... [2.0, 1.0, 1.0, 1.0, 4.0], + ... ([0],), (1,), (1.0,), + ... 0.0, + ... shrinkable_indices=(-1,)) ([3.0, 1.0, 1.0, 1.0, 3.0], 0.0) - ''' if not (len(change_tup) == len(num_tup)) and (len(change_tup) == len(diff_tup)): raise Exception( @@ -925,8 +949,8 @@ def determineStrongestMeasureLengths(self, remain = lenRem lenRem_final = lenRem - for l in _allCombinations(change, change_num): - for i in l: + for combo in _allCombinations(change, change_num): + for i in combo: lengths_changeable[i] += diff if release is not None: lengths_changeable[release] -= diff @@ -951,7 +975,6 @@ def determineStrongestMeasureLengths(self, remain = lenRem return lengths, lenRem_final - def getUnchangeableNoteLengths(self): ''' takes the music in self.brevisLength and returns a list where element i in this list @@ -959,70 +982,74 @@ def getUnchangeableNoteLengths(self): cannot be determined without taking into account the context (e.g., semiminims, semibreves) then None is placed in that list. + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> div = Divisione('.i.') >>> names = ['SB', 'M', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> TBL.getUnchangeableNoteLengths() [None, 1.0, None] >>> names = ['B'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> TBL.getUnchangeableNoteLengths() [6.0] >>> names = ['L'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> TBL.getUnchangeableNoteLengths() [12.0] >>> names = ['Mx'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> TBL.getUnchangeableNoteLengths() [24.0] - ''' unchangeableNoteLengthsList = [] for obj in self.brevisLength: minimaLength = None - #If its duration is set, doesn't need to be determined + # Non-mensural items (Clef, Divisione, Punctus, …) can legitimately + # sit inside the Measure but contribute no note-length. Skip them + # by entering None to keep the list aligned with self.brevisLength. + mensuralType = getattr(obj, 'mensuralType', None) - #Gets rid of everything known - if obj.mensuralType == 'maxima': + # Gets rid of everything known + if mensuralType == 'maxima': minimaLength = 4.0 * self.div.minimaPerBrevis - elif obj.mensuralType == 'longa': + elif mensuralType == 'longa': minimaLength = 2.0 * self.div.minimaPerBrevis - elif obj.mensuralType == 'brevis': + elif mensuralType == 'brevis': minimaLength = float(self.div.minimaPerBrevis) else: - objC = obj.classes - if 'GeneralMensuralNote' not in objC: + if not isinstance(obj, medren.GeneralMensuralNote): + unchangeableNoteLengthsList.append(None) continue - #Dep on div - if obj.mensuralType == 'semibrevis': - if 'MensuralRest' in obj.classes: + # Dep on div + if mensuralType == 'semibrevis': + if isinstance(obj, medren.MensuralRest): if self.div.standardSymbol in ['.q.', '.i.']: minimaLength = self.div.minimaPerBrevis / 2.0 elif self.div.standardSymbol in ['.p.', '.n.']: minimaLength = self.div.minimaPerBrevis / 3.0 - else: # we don't know it... + else: # we don't know it... pass else: - if 'side' in obj.getStems(): # oblique-stemmed semibreve + if 'side' in obj.getStems(): # oblique-stemmed semibreve minimaLength = 3.0 - else: # WHO THe heck knows a semibreve's length!!! :-) + else: # Who the heck knows a semibreve's length!!! :-) pass - elif obj.mensuralType == 'minima': - if 'MensuralNote' in obj.classes and 'down' in obj.stems: + elif mensuralType == 'minima': + if isinstance(obj, medren.MensuralNote) and 'down' in obj.stems: raise TrecentoNotationException('Dragmas currently not supported') - elif 'MensuralNote' in obj.classes and 'side' in obj.stems: + elif isinstance(obj, medren.MensuralNote) and 'side' in obj.stems: minimaLength = 1.5 else: minimaLength = 1.0 - elif obj.mensuralType == 'semiminima': + elif mensuralType == 'semiminima': pass unchangeableNoteLengthsList.append(minimaLength) return unchangeableNoteLengthsList @@ -1034,9 +1061,11 @@ def classifyUnknownNotesByType(self, unchangeableNoteLengthsList): and the values are a list of indices in self.brevisLength (and unchangeableNoteLengthsList) which are those types... + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> div = Divisione('.n.') >>> names = ['SB', 'M', 'M', 'M', 'SB', 'M'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> unchangeableNoteLengths = TBL.getUnchangeableNoteLengths() >>> kldict = TBL.classifyUnknownNotesByType(unchangeableNoteLengths) @@ -1053,8 +1082,8 @@ def classifyUnknownNotesByType(self, unchangeableNoteLengthsList): semiminima_left_flag_list = [] semiminima_rest_list = [] - #Don't need these yet - #=================================================================== + # Don't need these yet + # =================================================================== # dragmas_no_flag = [] # dragmas_RNo_flag = [] # dragmas_LNo_flag = [] @@ -1063,21 +1092,25 @@ def classifyUnknownNotesByType(self, unchangeableNoteLengthsList): # dragmas_RL_flag = [] # dragmas_LR_flag = [] # dragmas_LL_flag = [] - #=================================================================== + # =================================================================== for i in range(len(self.brevisLength)): obj = self.brevisLength[i] knownLength = unchangeableNoteLengths[i] if knownLength is not None: continue + # Skip non-mensural items (Clef, Divisione, Punctus, ...) that + # ride along inside the Measure. + if not isinstance(obj, medren.GeneralMensuralNote): + continue if obj.mensuralType == 'semibrevis': - if 'MensuralRest' in obj.classes: + if isinstance(obj, medren.MensuralRest): if self.div.standardSymbol not in ['.q.', '.i.', '.p.', '.n.']: semibrevis_list.append(i) else: if 'side' in obj.getStems(): - pass # shouldnt happen since length is known... + pass # shouldnt happen since length is known... elif 'down' in obj.getStems(): semibrevis_downstem.append(i) else: @@ -1085,21 +1118,21 @@ def classifyUnknownNotesByType(self, unchangeableNoteLengthsList): elif obj.mensuralType == 'minima': pass elif obj.mensuralType == 'semiminima': - if 'MensuralNote' in obj.classes: + if isinstance(obj, medren.MensuralNote): if 'down' in obj.getStems(): raise TrecentoNotationException('Dragmas currently not supported') elif obj.getFlags()['up'] == 'right': semiminima_right_flag_list.append(i) elif obj.getFlags()['up'] == 'left': semiminima_left_flag_list.append(i) - if 'MensuralRest' in obj.classes: + if isinstance(obj, medren.MensuralRest): semiminima_rest_list.append(i) - retDict = {'semibrevis':semibrevis_list, - 'semibrevis_downstem':semibrevis_downstem, - 'semiminima_right_flag':semiminima_right_flag_list, - 'semiminima_left_flag':semiminima_left_flag_list, - 'semiminima_rest':semiminima_rest_list, + retDict = {'semibrevis': semibrevis_list, + 'semibrevis_downstem': semibrevis_downstem, + 'semiminima_right_flag': semiminima_right_flag_list, + 'semiminima_left_flag': semiminima_left_flag_list, + 'semiminima_rest': semiminima_rest_list, } self.numberOfSemibreves = len(retDict['semibrevis']) @@ -1126,7 +1159,7 @@ def translate(self): self.minimaRemaining -= kl if None in self.unchangeableNoteLengthsList: - #Process everything else + # Process everything else if self.div.standardSymbol == '.i.': knownLengthsList = self.translateDivI() elif self.div.standardSymbol == '.n.': @@ -1144,8 +1177,8 @@ def translate(self): newDiv = Divisione(self.div.standardSymbol) newDiv.minimaPerBrevis = 2 * self.div.minimaPerBrevis return self.brevisLength -# tempTBL = BrevisLengthTranslator(newDiv, self.brevisLength[:]) -# knownLengthsList = tempTBL.getKnownLengths() + # tempTBL = BrevisLengthTranslator(newDiv, self.brevisLength[:]) + # knownLengthsList = tempTBL.getKnownLengths() for i in range(len(knownLengthsList)): # Float errors ml = knownLengthsList[i] @@ -1155,13 +1188,15 @@ def translate(self): except TypeError: raise TypeError('ml is screwed up! %s' % ml) - return [float(l) for l in knownLengthsList] + return [float(length) for length in knownLengthsList] def translateDivI(self, unchangeableNoteLengthsList=None, unknownLengthsDict=None, minRem=None): ''' + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> div = Divisione('.i.') >>> names = ['SB', 'M', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unchlist @@ -1188,13 +1223,13 @@ def translateDivI(self, unchangeableNoteLengthsList=None, unknownLengthsDict=Non knownLengthsList = unchangeableNoteLengthsList[:] if self.numberOfSemibreves > 0: - avgSBLength = minRem/self.numberOfSemibreves + avgSBLength = minRem / self.numberOfSemibreves for ind in semibrevis_list: if avgSBLength == 2: knownLengthsList[ind] = 2.0 minRem -= 2.0 elif (2 < avgSBLength) and (avgSBLength < 3): - if (ind < (len(self.brevisLength)-1) and + if (ind < (len(self.brevisLength) - 1) and self.brevisLength[ind + 1].mensuralType == 'minima'): knownLengthsList[ind] = 2.0 minRem -= 2.0 @@ -1215,9 +1250,11 @@ def translateDivN(self, unchangeableNoteLengthsList=None, unknownLengthsDict=Non ''' Translate the Novanaria (9) Divisio; returns the number of minims for each note. + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> div = Divisione('.n.') >>> names = ['SB', 'M', 'M', 'M', 'SB', 'M'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1231,16 +1268,15 @@ def translateDivN(self, unchangeableNoteLengthsList=None, unknownLengthsDict=Non [2.0, 1.0, 1.0, 1.0, 3.0, 1.0] >>> names = ['SB', 'M', 'M', 'M', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) >>> TBL.translateDivN(unchlist, unkldict, 6.0) [3.0, 1.0, 1.0, 1.0, 3.0] - >>> names = ['SB', 'M', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1253,9 +1289,7 @@ def translateDivN(self, unchangeableNoteLengthsList=None, unknownLengthsDict=Non >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) >>> TBL.translateDivN(unchlist, unkldict, 8.0) [5.0, 1.0, 3.0] - ''' - if unchangeableNoteLengthsList is None: unchangeableNoteLengthsList = self.unchangeableNoteLengthsList else: @@ -1274,10 +1308,10 @@ def translateDivN(self, unchangeableNoteLengthsList=None, unknownLengthsDict=Non semibrevis_downstem_index = None if self.numberOfDownstems > 0: - semibrevis_downstem_index = semibrevis_downstem[0] #Only room for one downstem + semibrevis_downstem_index = semibrevis_downstem[0] # Only room for one downstem knownLengthsList = unchangeableNoteLengthsList[:] - extend_list = [] #brevises able to be lengthened + extend_list = [] # brevises able to be lengthened extend_num = 0 if self.numberOfSemibreves > 0: @@ -1287,7 +1321,7 @@ def translateDivN(self, unchangeableNoteLengthsList=None, unknownLengthsDict=Non knownLengthsList[ind] = 2.0 minRem -= 2.0 extend_list.append(ind) - else: # if not followed by minima -- make 3.0 + else: # if not followed by minima -- make 3.0 knownLengthsList[ind] = 3.0 minRem -= 3.0 @@ -1310,17 +1344,17 @@ def translateDivN(self, unchangeableNoteLengthsList=None, unknownLengthsDict=Non extend_num = min(knownLengthsList[semibrevis_downstem_index] - 3, len(extend_list)) shrink_tup += (semibrevis_downstem_index,) - else: #no downstems + else: # no downstems if self.hasLastSB: knownLengthsList[semibrevis_list[-1]] = max(minRem, 3.0) minRem -= knownLengthsList[-1] - extend_num = min(knownLengthsList[-1]- 3, len(extend_list)) + extend_num = min(knownLengthsList[-1] - 3, len(extend_list)) shrink_tup += -1, - elif self.numberOfSemibreves > 0: #SBs, but no last SB - if (self.brevisLength[semibrevis_list[-1] + 1].mensuralType == 'minima'): + elif self.numberOfSemibreves > 0: # SBs, but no last SB + if self.brevisLength[semibrevis_list[-1] + 1].mensuralType == 'minima': knownLengthsList[semibrevis_list[-1]] = 2.0 minRem -= 2.0 extend_list.append(semibrevis_list[-1]) @@ -1354,9 +1388,11 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, ''' Translates P and Q (6 and 4) + >>> from music21_tools.trecento.medren import MensuralNote, MensuralRest + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> div = Divisione('.q.') >>> names = ['SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1364,8 +1400,8 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, [2.0, 2.0] >>> names = ['M', 'SM', 'SM', 'SM', 'SM', 'SM'] - >>> BL = [medren.MensuralNote('A', n) for n in names] - >>> BL[4] = medren.MensuralRest('SM') + >>> BL = [MensuralNote('A', n) for n in names] + >>> BL[4] = MensuralRest('SM') >>> BL[1].setFlag('up', 'left') >>> BL[2].setFlag('up', 'left') >>> TBL = BrevisLengthTranslator(div, BL) @@ -1376,7 +1412,7 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, >>> div = Divisione('.p.') >>> names = ['SB', 'SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(div, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1384,7 +1420,7 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, [2.0, 2.0, 2.0] >>> names = ['M', 'SB', 'SM', 'SM'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> BL[1].setStem('down') >>> TBL = BrevisLengthTranslator(div, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() @@ -1393,8 +1429,8 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, [1.0, 4.0, 0.5, 0.5] >>> names = ['SM', 'SM', 'SM', 'SM', 'SM', 'SM', 'SM', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] - >>> BL[3] = medren.MensuralRest('SM') + >>> BL = [MensuralNote('A', n) for n in names] + >>> BL[3] = MensuralRest('SM') >>> for mn in BL[:3]: ... mn.setFlag('up', 'left') >>> TBL = BrevisLengthTranslator(div, BL) @@ -1402,9 +1438,7 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) >>> TBL.translateDivPQ(unchlist, unkldict, 6.0) [0.666..., 0.666..., 0.666..., 0.5, 0.5, 0.5, 0.5, 2.0] - ''' - if unchangeableNoteLengthsList is None: unchangeableNoteLengthsList = self.unchangeableNoteLengthsList else: @@ -1430,7 +1464,7 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, extend_num = 0 semibrevis_downstem_index = None - if self.numberOfDownstems > 0: #Only room for one downstem per brevis length + if self.numberOfDownstems > 0: # Only room for one downstem per brevis length semibrevis_downstem_index = semibrevis_downstem[0] for ind in semibrevis_list[:-1]: @@ -1468,7 +1502,7 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, diff_tup = () shrink_tup = () - lengths = [(0.5, 0.5), (2/3, 0.5), (0.5, 2/3), (2/3, 2/3)] + lengths = [(0.5, 0.5), (2 / 3, 0.5), (0.5, 2 / 3), (2 / 3, 2 / 3)] for (left_length, right_length) in lengths: for ind in semiminima_left_flag_list: @@ -1494,18 +1528,18 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, minRem_changeable) minRem_changeable -= knownLengthsList_changeable[semibrevis_downstem_index] - else: #no downstems + else: # no downstems if self.hasLastSB: knownLengthsList_changeable[semibrevis_list[-1]] = max(2.0, minRem_changeable) minRem_changeable -= knownLengthsList_changeable[semibrevis_list[-1]] - elif self.numberOfSemibreves > 0: #semibreves, but no ending SB + elif self.numberOfSemibreves > 0: # semibreves, but no ending SB knownLengthsList_changeable[semibrevis_list[-1]] = 2.0 minRem_changeable -= 2.0 - else: #left_length != right_length + else: # left_length != right_length master_list = (semiminima_left_flag_list + semiminima_right_flag_list + @@ -1541,7 +1575,7 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, knownLengthsList_changeable[ind] = right_length minRem_changeable -= right_length - #Otherwise, we don't know. Append SM Rest to extend list. + # Otherwise, we don't know. Append SM Rest to extend list. else: knownLengthsList_changeable[ind] = 0.5 extend_list.append(ind) @@ -1556,29 +1590,29 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, knownLengthsList_changeable[semibrevis_downstem_index] = max( minRem_changeable, 2.0) - extend_num = min(6*minRem_changeable - 15.0, len(extend_list)) + extend_num = min(6 * minRem_changeable - 15.0, len(extend_list)) minRem_changeable -= knownLengthsList_changeable[semibrevis_downstem_index] shrink_tup += (semibrevis_downstem_index,) - else: #No downstems + else: # No downstems if self.hasLastSB: knownLengthsList_changeable[semibrevis_list[-1]] = max( minRem_changeable, 2.0) - extend_num = min(6*minRem_changeable - 12.0, len(extend_list)) + extend_num = min(6 * minRem_changeable - 12.0, len(extend_list)) minRem_changeable -= max(minRem_changeable, 2.0) shrink_tup += -1, - elif self.numberOfSemibreves > 0: #SBs, but no last SB + elif self.numberOfSemibreves > 0: # SBs, but no last SB knownLengthsList_changeable[semibrevis_list[-1]] = 2.0 minRem_changeable -= 2.0 extend_num = len(extend_list) change_tup += (extend_list,) num_tup += (extend_num,) - diff_tup += (1/6,) + diff_tup += (1 / 6,) if minRem_changeable > -0.0001: knownLengthsList_changeable, minRem_changeable = ( @@ -1593,7 +1627,7 @@ def translateDivPQ(self, unchangeableNoteLengthsList=None, self.minimaRemaining = minRem_changeable if (tempStrength > strength) and (minRem_changeable > -0.0001): - #Technically, >= 0, but rounding error occurs. + # Technically, >= 0, but rounding error occurs. knownLengthsList = knownLengthsList_changeable[:] minRem = minRem_changeable strength = tempStrength @@ -1612,9 +1646,11 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, ''' Translates the octonaria and duodenaria divisions + >>> from music21_tools.trecento.medren import MensuralNote + >>> from music21_tools.trecento.notation import BrevisLengthTranslator, Divisione >>> divO = Divisione('.o.') >>> names = ['SB', 'SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> BL[1].setStem('down') >>> TBL = BrevisLengthTranslator(divO, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() @@ -1623,7 +1659,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, [2.0, 4.0, 2.0] >>> names = ['SM', 'SM', 'SM', 'SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(divO, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1632,7 +1668,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, >>> divD = Divisione('.d.') >>> names = ['SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(divD, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1640,7 +1676,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, [4.0, 8.0] >>> names = ['SB', 'SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(divD, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1648,7 +1684,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, [4.0, 4.0, 4.0] >>> names = ['SB', 'SB', 'SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(divD, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1666,7 +1702,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, [2.0, 4.0, 4.0, 2.0] >>> names = ['SB', 'SB', 'SM', 'SM', 'SM', 'SM', 'SB', 'SB'] - >>> BL = [medren.MensuralNote('A', n) for n in names] + >>> BL = [MensuralNote('A', n) for n in names] >>> TBL = BrevisLengthTranslator(divD, BL) >>> unchlist = TBL.getUnchangeableNoteLengths() >>> unkldict = TBL.classifyUnknownNotesByType(unchlist) @@ -1731,7 +1767,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, else: - lengths = [(0.5, 0.5), (2/3, 0.5), (0.5, 2/3), (2/3, 2/3)] + lengths = [(0.5, 0.5), (2 / 3, 0.5), (0.5, 2 / 3), (2 / 3, 2 / 3)] strength = 0 for (left_length, right_length) in lengths: @@ -1754,7 +1790,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, knownLengthsList_changeable[ind] = left_length minRem_changeable -= left_length - else: #left_length != right_length + else: # left_length != right_length master_list = (semiminima_left_flag_list + semiminima_right_flag_list + @@ -1783,8 +1819,8 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, else: knownLengthsList_changeable[ind] = 0.5 - #extend_list.append(ind) ### BUG: extend_list does not exist - #extend_list = _removeRepeatedElements(extend_list_2) + # extend_list.append(ind) # BUG: extend_list does not exist + # extend_list = _removeRepeatedElements(extend_list_2) if self.numberOfDownstems > 0: @@ -1808,7 +1844,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, if extend_list_2: shrink_tup += (semibrevis_downstem_index,) - else: #downstems >= 2 + else: # downstems >= 2 newMensuralBL = [medren.MensuralNote('A', 'SB') for i in range(len(semibrevis_downstem))] @@ -1821,9 +1857,9 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, for i, ind in enumerate(semibrevis_downstem): knownLengthsList_changeable[ind] = dSLengthList[i] - #Don't need shrink_tup. There is no room to extend anything. + # Don't need shrink_tup. There is no room to extend anything. - elif self.numberOfSemibreves > 0: # no downstems + elif self.numberOfSemibreves > 0: # no downstems if self.hasLastSB: maxVal = max(minRem_changeable, 2.0) knownLengthsList_changeable[semibrevis_list[-1]] = maxVal @@ -1845,7 +1881,7 @@ def translateDivOD(self, unchangeableNoteLengthsList=None, change_tup += extend_list_1, extend_list_2 num_tup += extend_num_1, extend_num_2 - diff_tup += (2.0, 1/6) + diff_tup += (2.0, 1 / 6) if minRem_changeable > -0.0001: knownLengthsList_changeable, minRem_changeable = ( @@ -1880,20 +1916,18 @@ def _allCombinations(combinationList, num): >>> _allCombinations(['a', 'b', 'c'], 2) [[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'c'], ['a']] - ''' - combs = [] if num > 0: for i in range(len(combinationList)): comb = [combinationList[i]] - for c in _allCombinations(combinationList[(i + 1):], num-1): + for c in _allCombinations(combinationList[(i + 1):], num - 1): combs.append(comb + c) combs.reverse() combs.insert(0, []) return combs -def _removeRepeatedElements(listOrTup): #So I don't have to use set +def _removeRepeatedElements(listOrTup): # So I don't have to use set newListOrTup = [] for item in listOrTup: @@ -1910,7 +1944,7 @@ class TrecentoNotationException(exceptions21.Music21Exception): pass # ------------------------------------------------------------------------------------- -class TestExternal(unittest.TestCase): # pragma: no cover +class TestExternal(unittest.TestCase): # pragma: no cover def testTinyTrecentoStream(self): from music21 import text @@ -2040,32 +2074,32 @@ def processStream(mStream, pitches, lengths, downStems=None): SePerDureca.append(upper) SePerDureca.append(lower) - upperString = (".p. $C1 g(B) g(M) f e g f e p g(SB) f(SM) e d e(M) f p " + - "e(SB) e(SM) f e d(M) c p d(SB) r e p f(M) e d e d c p " + - "d(SB) c(M) d c d p e(SB) r(M) g f e p g(SB) a(SM) g f e(M) " + - "d p e(SB) f(SM) e d c(M) d e(L) a(SB) a p b(M) a g a g f p g " + - "f e f e f p g(SB) g(SM) a g f e d p e(SB) r(M) f(SM) e d e(M) " + - "p d(SB) r e(M) f p g(SB) d r(SM) e p f e d e d c p d(SB) d(M) " + + upperString = ('.p. $C1 g(B) g(M) f e g f e p g(SB) f(SM) e d e(M) f p ' + + 'e(SB) e(SM) f e d(M) c p d(SB) r e p f(M) e d e d c p ' + + 'd(SB) c(M) d c d p e(SB) r(M) g f e p g(SB) a(SM) g f e(M) ' + + 'd p e(SB) f(SM) e d c(M) d e(L) a(SB) a p b(M) a g a g f p g ' + + 'f e f e f p g(SB) g(SM) a g f e d p e(SB) r(M) f(SM) e d e(M) ' + + 'p d(SB) r e(M) f p g(SB) d r(SM) e p f e d e d c p d(SB) d(M) ' + "e(SB) c(M) p d(SB) c(M) d c B c(L) c'(SB)[D] c' p c'(SM) b a " + "b(M) c' b c' p b(SM) a g a(M) b a b p g(SB) g(SM) a g f e d p " + - "e(SB) r e(M) f p g f e f e f p g(SB) r g(M) f p g(SB) f(SM) e " + + 'e(SB) r e(M) f p g f e f e f p g(SB) r g(M) f p g(SB) f(SM) e ' + "d e(M) f e(L) a(M) b a b g(SB) p c'(M) b a c' b a p b c' b a " + "g a p b(SB) c'(SM) b a g(M) f p a(SB) a(M) g(SB) f(M) p e(SB) r " + - "g(M) f p g f e f e d p c(SB) d r p a g(SM) a g f e d p e(SB) r " + - "f(SM) e d e(SB) d(Mx)") - lowerString = (".p. $C3 c(L) G(B) A(SB) B c p d c r p A B c p d c B p " + - "lig{A[DL] G} A c B A(L) A(SB) A p G A B p c c(M) B(SB) " + - "A(M) p G(SB) G p A B c p d A r p G[D] A p B B(M) c(SB) c(M) " + - "p d(SB) d(M) A(SB) A(M) p G(SB) A B C(L) c(SB)[D] c e(B) d c(SB) " + - "c d p e d r p c c(M) d(SB) d(M) p c(SB) r r p c d c(M) d " + - "e(L) d(SB)[D] e p c[D] d p e e(M) d(SB) c(M) p B(SB) A B(M) " + - "c p d(SB) d(M) c(SB) d(M) p e(SB) d r p c c c(M) A(SB) B(M) p " + - "c(SB) B B p A B[D] p A B c d(Mx)") + 'g(M) f p g f e f e d p c(SB) d r p a g(SM) a g f e d p e(SB) r ' + + 'f(SM) e d e(SB) d(Mx)') + lowerString = ('.p. $C3 c(L) G(B) A(SB) B c p d c r p A B c p d c B p ' + + 'lig{A[DL] G} A c B A(L) A(SB) A p G A B p c c(M) B(SB) ' + + 'A(M) p G(SB) G p A B c p d A r p G[D] A p B B(M) c(SB) c(M) ' + + 'p d(SB) d(M) A(SB) A(M) p G(SB) A B C(L) c(SB)[D] c e(B) d c(SB) ' + + 'c d p e d r p c c(M) d(SB) d(M) p c(SB) r r p c d c(M) d ' + + 'e(L) d(SB)[D] e p c[D] d p e e(M) d(SB) c(M) p B(SB) A B(M) ' + + 'c p d(SB) d(M) c(SB) d(M) p e(SB) d r p c c c(M) A(SB) B(M) p ' + + 'c(SB) B B p A B[D] p A B c d(Mx)') upperConverted = TrecentoTinyConverter(upperString).parse( - ).stream.flatten().getElementsNotOfClass('Barline') + ).stream.flatten().getElementsNotOfClass('Barline').stream() lowerConverted = TrecentoTinyConverter(lowerString).parse( - ).stream.flatten().getElementsNotOfClass('Barline') + ).stream.flatten().getElementsNotOfClass('Barline').stream() TinySePerDureca.append(upperConverted) TinySePerDureca.append(lowerConverted) @@ -2084,14 +2118,8 @@ def processStream(mStream, pitches, lengths, downStems=None): else: print('norm only: %s' % SePerDureca[i + 1][j]) print('') - - #TinySePerDureca.show('text') - -class Test(unittest.TestCase): - - def runTest(self): - pass + # TinySePerDureca.show('text') if __name__ == '__main__': import music21 - music21.mainTest(Test, TestExternal, 'importPlusRelative') + music21.mainTest(TestExternal, 'importPlusRelative') diff --git a/music21_tools/trecento/polyphonicSnippet.py b/music21_tools/trecento/polyphonicSnippet.py index f134ffd..9b8d6ea 100644 --- a/music21_tools/trecento/polyphonicSnippet.py +++ b/music21_tools/trecento/polyphonicSnippet.py @@ -27,14 +27,15 @@ class PolyphonicSnippet(stream.Score): The fourth is the cadence type (optional), the fifth is the time signature if not the same as the time signature of the parentPiece. - >>> from . import trecentoCadence - >>> cantus = trecentoCadence.CadenceConverter( + >>> from music21_tools.trecento.cadencebook import BallataSheet + >>> from music21_tools.trecento.polyphonicSnippet import PolyphonicSnippet + >>> from music21_tools.trecento.trecentoCadence import CadenceConverter + >>> cantus = CadenceConverter( ... "6/8 c'2. d'8 c'4 a8 f4 f8 a4 c'4 c'8").parse().stream - >>> tenor = trecentoCadence.CadenceConverter("6/8 F1. f2. e4. d").parse().stream - >>> from . import cadencebook + >>> tenor = CadenceConverter("6/8 F1. f2. e4. d").parse().stream >>> ps = PolyphonicSnippet( ... [cantus, tenor, None, "8-8", "6/8"], - ... parentPiece=cadencebook.BallataSheet().makeWork(3)) + ... parentPiece=BallataSheet().makeWork(3)) >>> ps.elements (, , ) @@ -54,21 +55,22 @@ class PolyphonicSnippet(stream.Score): >>> dumClass = dummy.__class__ >>> dumClass - + >>> dumdum = dumClass() >>> dumdum.__class__ - + >>> ps2 = ps.__class__() >>> ps2.elements () + >>> from music21_tools.trecento.polyphonicSnippet import Incipit >>> dummy2 = Incipit() >>> dummy2.elements () ''' - snippetName = "" + snippetName = '' def __init__(self, fiveExcelCells=None, parentPiece=None): super().__init__() @@ -76,7 +78,7 @@ def __init__(self, fiveExcelCells=None, parentPiece=None): fiveExcelCells = [] if fiveExcelCells != []: if len(fiveExcelCells) != 5: - raise Exception("Need five Excel Cells to make a PolyphonicSnippet object") + raise Exception('Need five Excel Cells to make a PolyphonicSnippet object') self.cadenceType = fiveExcelCells[3] self.timeSig = meter.TimeSignature(fiveExcelCells[4]) @@ -87,16 +89,16 @@ def __init__(self, fiveExcelCells=None, parentPiece=None): self.longestLineLength = 0 - if self.contratenor == "" or self.contratenor is None: + if self.contratenor == '' or self.contratenor is None: self.contratenor = None else: self.contratenor.id = 'Ct' - if self.tenor == "" or self.tenor is None: + if self.tenor == '' or self.tenor is None: self.tenor = None else: self.tenor.id = 'T' - if self.cantus == "" or self.cantus is None: + if self.cantus == '' or self.cantus is None: self.cantus = None else: self.cantus.id = 'C' @@ -136,23 +138,23 @@ def _padParts(self): def header(self): '''returns a string that prints an appropriate header for this cadence''' - if self.snippetName == "": - if (self.parentPiece is not None): - headOut = "" + if self.snippetName == '': + if self.parentPiece is not None: + headOut = '' parentPiece = self.parentPiece - if (parentPiece.fischerNum): - headOut += str(parentPiece.fischerNum) + ". " + if parentPiece.fischerNum: + headOut += str(parentPiece.fischerNum) + '. ' if parentPiece.title: headOut += parentPiece.title if (parentPiece.pmfcVol and parentPiece.pmfcPageRange()): - headOut += " PMFC " + str(parentPiece.pmfcVol) + " " + headOut += ' PMFC ' + str(parentPiece.pmfcVol) + ' ' headOut += parentPiece.pmfcPageRange() return headOut else: - return "" + return '' else: - if (self.parentPiece is not None): - headOut = self.parentPiece.title + " -- " + self.snippetName + if self.parentPiece is not None: + headOut = self.parentPiece.title + ' -- ' + self.snippetName else: return self.snippetName @@ -161,7 +163,7 @@ def findLongestCadence(self): returns the length. (in quarterLengths) for the longest line in the parts - + >>> from music21_tools.trecento.polyphonicSnippet import PolyphonicSnippet >>> s1 = stream.Part([note.Note(type='whole')]) >>> s2 = stream.Part([note.Note(type='half')]) >>> s3 = stream.Part([note.Note(type='quarter')]) @@ -169,7 +171,6 @@ def findLongestCadence(self): >>> ps = PolyphonicSnippet(fiveExcelRows) >>> ps.findLongestCadence() 4.0 - ''' longestLineLength = 0 for thisStream in self.parts: @@ -185,6 +186,7 @@ def measuresShort(self, thisStream): ''' returns the number of measures short that each stream is compared to the longest stream. + >>> from music21_tools.trecento.polyphonicSnippet import PolyphonicSnippet >>> s1 = stream.Part([note.Note(type='whole')]) >>> s2 = stream.Part([note.Note(type='half')]) >>> s3 = stream.Part([note.Note(type='quarter')]) @@ -199,25 +201,22 @@ def measuresShort(self, thisStream): >>> ps.measuresShort(s1) 0.0 ''' - - timeSigLength = self.timeSig.barDuration.quarterLength thisStreamLength = thisStream.duration.quarterLength shortness = self.findLongestCadence() - thisStreamLength - shortmeasures = shortness/timeSigLength + shortmeasures = shortness / timeSigLength return shortmeasures - class Incipit(PolyphonicSnippet): - snippetName = "" + snippetName = '' def backPadLine(self, thisStream): ''' Pads a Stream with a bunch of rests at the end to make it the same length as the longest line - + >>> from music21_tools.trecento.polyphonicSnippet import Incipit >>> ts = meter.TimeSignature('1/4') >>> s1 = stream.Part([ts]) >>> s1.repeatAppend(note.Note(type='quarter'), 4) @@ -240,11 +239,10 @@ def backPadLine(self, thisStream): {3.0} {0.0} {1.0} - ''' shortMeasures = int(self.measuresShort(thisStream)) - if (shortMeasures > 0): + if shortMeasures: shortDuration = self.timeSig.barDuration hasMeasures = thisStream.hasMeasures() if hasMeasures: @@ -275,15 +273,15 @@ def backPadLine(self, thisStream): lastMeasure.rightBarline = oldRightBarline - class FrontPaddedSnippet(PolyphonicSnippet): - snippetName = "" + snippetName = '' def frontPadLine(self, thisStream): - '''Pads a line with a bunch of rests at the + ''' + Pads a line with a bunch of rests at the front to make it the same length as the longest line - + >>> from music21_tools.trecento.polyphonicSnippet import FrontPaddedSnippet >>> ts = meter.TimeSignature('1/4') >>> s1 = stream.Part([ts]) >>> s1.repeatAppend(note.Note(type='quarter'), 4) @@ -306,11 +304,10 @@ def frontPadLine(self, thisStream): {3.0} {0.0} {1.0} - ''' shortMeasures = int(self.measuresShort(thisStream)) - if (shortMeasures > 0): + if shortMeasures: shortDuration = self.timeSig.barDuration offsetShift = shortDuration.quarterLength * shortMeasures hasMeasures = thisStream.hasMeasures() @@ -326,7 +323,6 @@ def frontPadLine(self, thisStream): for thisNote in thisStream.iter.notesAndRests: thisNote.setOffsetBySite(thisStream, thisStream.elementOffset(m) + offsetShift) - for i in range(shortMeasures): newRest = note.Rest() newRest.duration = copy.deepcopy(shortDuration) @@ -355,18 +351,10 @@ def frontPadLine(self, thisStream): newFirstM.insert(nOffset, n) - - - - class Test(unittest.TestCase): - pass - - def runTest(self): - pass - def testCopyAndDeepcopy(self): - '''Test copying all objects defined in this module + ''' + Test copying all objects defined in this module ''' import sys for part in sys.modules[self.__module__].__dict__: @@ -375,7 +363,7 @@ def testCopyAndDeepcopy(self): elif part in ['Test', 'TestExternal']: continue elif callable(part): - #environLocal.printDebug(['testing copying on', part]) + # environLocal.printDebug(['testing copying on', part]) obj = getattr([self.__module__, part])() a = copy.copy(obj) b = copy.deepcopy(obj) @@ -383,24 +371,16 @@ def testCopyAndDeepcopy(self): self.assertNotEqual(b, obj) -class TestExternal(unittest.TestCase): # pragma: no cover - pass - - def runTest(self): - pass +class TestExternal(unittest.TestCase): # pragma: no cover def testLily(self): from . import trecentoCadence, cadencebook cantus = trecentoCadence.CadenceConverter( "6/8 c'2. d'8 c'4 a8 f4 f8 a4 c'4 c'8").parse().stream - tenor = trecentoCadence.CadenceConverter("6/8 F1. f2. e4. d").parse().stream - ps = PolyphonicSnippet([cantus, tenor, None, "8-8", "6/8"], + tenor = trecentoCadence.CadenceConverter('6/8 F1. f2. e4. d').parse().stream + ps = PolyphonicSnippet([cantus, tenor, None, '8-8', '6/8'], parentPiece=cadencebook.BallataSheet().makeWork(3)) ps.show('musicxml.png') -if __name__ == "__main__": +if __name__ == '__main__': import music21 music21.mainTest(Test, TestExternal, 'importPlusRelative') - -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/quodJactatur.py b/music21_tools/trecento/quodJactatur.py index dc09ebe..a07f8ab 100644 --- a/music21_tools/trecento/quodJactatur.py +++ b/music21_tools/trecento/quodJactatur.py @@ -39,13 +39,9 @@ without any problems. Working on this problem also gave a great test of music21's ability to manipulate diatonic Streams. ''' - import copy -import unittest - from music21 import clef -#from music21 import common from music21 import corpus from music21 import exceptions21 from music21 import instrument @@ -193,9 +189,9 @@ def getQJ(): ''' - qj = corpus.parse("ciconia/quod_jactatur") + qj = corpus.parse('ciconia/quod_jactatur') qjPart = qj.getElementsByClass(stream.Part)[0] - qjPart.transpose("P-8", inPlace=True) + qjPart.transpose('P-8', inPlace=True) qjPart.replace(qjPart.flatten().getElementsByClass(clef.Clef)[0], clef.BassClef()) cachedParts['1-0-False-False'] = copy.deepcopy(qjPart) return qjPart @@ -231,10 +227,10 @@ def findRetrogradeVoices(show=True): thisScore = strength else: int1 = interval.Interval(n.pitches[0], n.pitches[1]) - #print int1.generic.simpleUndirected + # print(int1.generic.simpleUndirected) if int1.generic.simpleUndirected in [1, 3, 4, 5]: thisScore = strength - elif int1.generic.simpleUndirected == 6: # less good + elif int1.generic.simpleUndirected == 6: # less good thisScore = strength / 2.0 else: thisScore = -2 * strength @@ -246,7 +242,7 @@ def findRetrogradeVoices(show=True): totIntervals += 1 n.lyric = str(thisScore) - finalScore = int(100*(consScore + 0.0)/totIntervals) + finalScore = int(100 * (consScore + 0.0) / totIntervals) qj.insert(0, qjChords.flatten()) qj2.flatten().notesAndRests[0].addLyric('Trans: ' + str(transpose)) qj2.flatten().notesAndRests[0].addLyric('Invert: ' + str(invert)) @@ -256,20 +252,20 @@ def findRetrogradeVoices(show=True): qj.show() else: if invert: - invStr = "Invert" + invStr = 'Invert' else: - invStr = " " - print(str(transpose) + " " + invStr + " " + str(finalScore)) + invStr = ' ' + print(str(transpose) + ' ' + invStr + ' ' + str(finalScore)) def prepareSolution(triplumTup, ctTup, tenorTup): qjSolved = stream.Score() for transpose, delay, invert, retro in [triplumTup, ctTup, tenorTup]: - idString = "%d-%d-%s-%s" % (transpose, delay, invert, retro) + idString = '%d-%d-%s-%s' % (transpose, delay, invert, retro) if idString in cachedParts: qjPart = copy.deepcopy(cachedParts[idString]) else: - qjPart = copy.deepcopy(cachedParts["1-0-False-False"]) + qjPart = copy.deepcopy(cachedParts['1-0-False-False']) if retro is True: qjPart = reverse(qjPart, makeNotation = False) if invert is True: @@ -281,8 +277,8 @@ def prepareSolution(triplumTup, ctTup, tenorTup): cachedParts[idString] = copy.deepcopy(qjPart) qjSolved.insert(0, qjPart.flatten()) - #DOESN'T WORK -- am I doing something wrong? - #for tp in qjSolved.parts: + # DOESN'T WORK -- am I doing something wrong? + # for tp in qjSolved.parts: # tp.makeMeasures(inPlace=True) qjChords = qjSolved.chordify() @@ -292,7 +288,7 @@ def prepareSolution(triplumTup, ctTup, tenorTup): startCounting = False for n in qjChords.flatten().notes: - if not 'Chord' in n.classes: + if 'Chord' not in n.classes: continue if (startCounting is False or n.offset >= 70) and len(n.pitches) < 2: continue @@ -309,7 +305,7 @@ def prepareSolution(triplumTup, ctTup, tenorTup): totIntervals += 1 n.lyric = str(thisScore) - return (qjChords, (consScore/(totIntervals + 0.0)), qjSolved) + return (qjChords, (consScore / (totIntervals + 0.0)), qjSolved) def getStrengthForNote(n): ''' @@ -322,9 +318,9 @@ def getStrengthForNote(n): solutions should use n.beat ''' - if (n.offset / 2.0) == int(n.offset / 2.0): # downbeat + if (n.offset / 2.0) == int(n.offset / 2.0): # downbeat strength = 4 - elif (n.offset) == int(n.offset): # strong beat + elif (n.offset) == int(n.offset): # strong beat strength = 2 else: strength = 0.5 @@ -362,7 +358,7 @@ def possibleSolution(): # qjSolved.insert(0, stream.Part()) # qjSolved.insert(0, qjChords) qjSolved.show('musicxml') - #qjChords.show('musicxml') + # qjChords.show('musicxml') def multipleSolve(): # this is ridiculous... @@ -429,7 +425,7 @@ def multipleSolve(): continue # same as lowestInvert == True and # middleInvert == False - if (transLowest == 1 and transMiddle == 1): + if transLowest == 1 and transMiddle == 1: continue # if transHighest == 4 then it's the same # as (-4, -4, 1) except for a few tritones @@ -448,7 +444,7 @@ def multipleSolve(): unused_fullScore) = prepareSolution(triplum, ct, tenor) - #writeLine = (tripT, ctT, tenT, tripDelay, + # writeLine = (tripT, ctT, tenT, tripDelay, # ctDelay, tenDelay, tripInvert, ctInvert, # tenInvert, tripRetro, ctRetro, tenRetro, # avgScore) @@ -466,32 +462,14 @@ def multipleSolve(): lowestRetro, avgScore) if avgScore > 0: - print("") + print('') if avgScore > maxScore: maxScore = avgScore - print("***** ", end="") + print('***** ', end='') print(writeLine) else: - print(str(i) + " ", end="") + print(str(i) + ' ', end='') csvFile.writerow(writeLine) class QuodJactaturException(exceptions21.Music21Exception): pass - -class Test(unittest.TestCase): - - def runTest(self): - pass - - - -if __name__ == "__main__": - import music21 - music21.mainTest('importPlusRelative') -# bentWolfSolution() -# possibleSolution() -# findRetrogradeVoices() - pass -# ----------------------------------------------------------------------------- -# eof - diff --git a/music21_tools/trecento/runTrecentoCadence.py b/music21_tools/trecento/runTrecentoCadence.py index c74ffff..a56aaf8 100644 --- a/music21_tools/trecento/runTrecentoCadence.py +++ b/music21_tools/trecento/runTrecentoCadence.py @@ -10,8 +10,7 @@ ''' Python script to find out certain statistics about the trecento cadences ''' - -import unittest +import music21 from . import cadencebook def countTimeSig(): @@ -25,19 +24,19 @@ def countTimeSig(): for trecentoWork in ballataObj: thisTime = trecentoWork.timeSigBegin - thisTime = thisTime.strip() # remove leading and trailing whitespace - if (thisTime == ""): + thisTime = thisTime.strip() # remove leading and trailing whitespace + if thisTime == '': pass else: totalPieces += 1 - if (thisTime in timeSigCounter): + if thisTime in timeSigCounter: timeSigCounter[thisTime] += 1 else: timeSigCounter[thisTime] = 1 for thisKey in sorted(timeSigCounter): - print(thisKey, ":", timeSigCounter[thisKey], - str(int(timeSigCounter[thisKey] * 100 / totalPieces)) + "%") + print(thisKey, ':', timeSigCounter[thisKey], + str(int(timeSigCounter[thisKey] * 100 / totalPieces)) + '%') def sortByPMFC(work): ''' @@ -74,7 +73,7 @@ def makePDFfromPieces(start=1, finish=2): ballataObj = cadencebook.BallataSheet() retrievedPieces = [] - for i in range(start, finish): ## some random pieces + for i in range(start, finish): # some random pieces randomPiece = ballataObj.makeWork( i ) # if randomPiece.incipit is not None: retrievedPieces.append(randomPiece) @@ -82,7 +81,7 @@ def makePDFfromPieces(start=1, finish=2): opus = stream.Opus() retrievedPieces.sort(key=sortByPMFC) for randomPiece in retrievedPieces: - #print(randomPiece.title.encode('utf-8')) + # print(randomPiece.title.encode('utf-8')) randomOpus = randomPiece.asOpus() for s in randomOpus.scores: opus.insert(0, s) @@ -93,7 +92,7 @@ def makePDFfromPieces(start=1, finish=2): # randomIncipit = randomPiece.incipitClass() # lilyString += randomIncipit.lily() # randomCadA = randomPiece.cadenceAClass() -## randomCadA.header = randomIncipit.header ## use its header however +# randomCadA.header = randomIncipit.header # use its header however # lilyString += randomCadA.lily() # randomCadB1 = randomPiece.cadenceB1Class() # if randomCadB1 is not None: @@ -114,13 +113,13 @@ def makePDFfromPiecesWithCapua(start=2, finish=3): ballataObj = cadencebook.BallataSheet() retrievedPieces = [] - for i in range(start, finish): ## some random pieces + for i in range(start, finish): # some random pieces try: randomPiece = ballataObj.makeWork( i ) # if randomPiece.incipit: retrievedPieces.append(randomPiece) - except: - raise Exception("Ugg " + str(i)) + except Exception as exc: + raise Exception(f'Could not retrieve random piece at index {i}') from exc # lilyString = "" # retrievedPieces.sort(key=sortByPMFC) @@ -133,7 +132,7 @@ def makePDFfromPiecesWithCapua(start=2, finish=3): # lilyString += randomIncipit.lily() # # randomCadA = randomPiece.cadenceAClass() -## randomCadA.header = randomIncipit.header ## use its header however +# randomCadA.header = randomIncipit.header # use its header however # for thisStream in randomCadA.streams: # capua.runRulesOnStream(thisStream) # @@ -158,34 +157,9 @@ def makePDFfromPiecesWithCapua(start=2, finish=3): def checkValidity(): ballataObj = cadencebook.BallataSheet() - for i in range(1,378): - randomPiece = ballataObj.makeWork(i) #random.randint(231, 312) + for i in range(1, 378): + randomPiece = ballataObj.makeWork(i) # random.randint(231, 312) try: unused_incipitStreams = randomPiece.incipitStreams() except music21.tinyNotation.TinyNotationException as inst: - raise Exception(randomPiece.title + " had problem " + inst.args) - - -# temporarily commenting out for adding standard test approach -# if (__name__ == "__main__"): -# # countTimeSig() -# makePDFfromPiecesWithCapua() - - -# ------------------------------------------------------------------------------ -class Test(unittest.TestCase): - - def runTest(self): - pass - - def testA(self): - self.assertEqual(5, 5) ## something really wrong!?? - -if __name__ == "__main__": - #makePDFfromPiecesWithCapua() - import music21 - music21.mainTest(Test, 'moduleRelative') - - -# ----------------------------------------------------------------------------- -# eof + raise Exception(randomPiece.title + ' had problem ' + inst.args) diff --git a/music21_tools/trecento/tonality.py b/music21_tools/trecento/tonality.py index e31e8ea..8112ba3 100644 --- a/music21_tools/trecento/tonality.py +++ b/music21_tools/trecento/tonality.py @@ -28,15 +28,13 @@ class TonalityCounter: ''' The TonalityCounter object takes a list of Trecento Works - (defined in music21.trecento.cadencebook) and when run() + (defined in music21_tools.trecento.cadencebook) and when run() is called, stores a set of information about the cadence tonalities of the works. - streamName can be "C" (cantus), "T" (tenor, default), or "Ct" (contratenor), or very rarely "4" (fourth voice). - cadenceName can be "A" or "B" (which by default uses the second ending of cadence B if there are two endings) or an integer specifying which cadence to consult (-1 being @@ -44,15 +42,14 @@ class TonalityCounter: want the Amen no matter how many internal cadences there are). - This example takes three ballata and how that all three of them cadence on a different note than they began on. All three cadence on D despite beginning on C, A, and B (or B - flat) repsectively. - + flat) respectively. - >>> from music21_tools.trecento import cadencebook - >>> threeBallata = cadencebook.BallataSheet()[15:18] + >>> from music21_tools.trecento.cadencebook import BallataSheet + >>> from music21_tools.trecento.tonality import TonalityCounter + >>> threeBallata = BallataSheet()[15:18] >>> tc1 = TonalityCounter(threeBallata) >>> tc1.run() >>> print(tc1.output) @@ -74,33 +71,33 @@ class TonalityCounter: ''' - def __init__(self, worksList, streamName="T", cadenceName="A"): + def __init__(self, worksList, streamName='T', cadenceName='A'): self.worksList = worksList self.streamName = streamName self.cadenceName = cadenceName - self.output = "" + self.output = '' self.displayStream = None self.storedDict = None def run(self): from music21 import stream - output = "" + output = '' streamName = self.streamName allScores = stream.Opus() - myDict = {'A': defaultdict(lambda:False), 'B': defaultdict(lambda:False), - 'C': defaultdict(lambda:False), 'D': defaultdict(lambda:False), - 'E': defaultdict(lambda:False), 'F': defaultdict(lambda:False), - 'G': defaultdict(lambda:False)} + myDict = {'A': defaultdict(lambda: False), 'B': defaultdict(lambda: False), + 'C': defaultdict(lambda: False), 'D': defaultdict(lambda: False), + 'E': defaultdict(lambda: False), 'F': defaultdict(lambda: False), + 'G': defaultdict(lambda: False)} for thisWork in self.worksList: incip = thisWork.incipit - if self.cadenceName == "A": + if self.cadenceName == 'A': cadence = thisWork.cadenceA - elif self.cadenceName == "B": + elif self.cadenceName == 'B': cadence = thisWork.cadenceBclos try: - if (cadence is None or cadence.parts[streamName] is None): + if cadence is None or cadence.parts[streamName] is None: cadence = thisWork.cadenceB except KeyError: cadence = thisWork.cadenceB @@ -110,7 +107,7 @@ def run(self): except IndexError: continue else: - raise Exception("Cannot deal with cadence type %s" % self.cadenceName) + raise Exception('Cannot deal with cadence type %s' % self.cadenceName) if incip is None or cadence is None: continue @@ -124,14 +121,14 @@ def run(self): firstNote = incipSN.pitches[0] cadenceNote = cadenceSN.pitches[-1] except IndexError: - output += thisWork.title + "\n" + output += thisWork.title + '\n' continue myDict[firstNote.step][cadenceNote.step] += 1 if firstNote.step == cadenceNote.step: allScores.insert(0, incip) allScores.insert(0, cadence) - output += "%30s %4s %4s\n" % (thisWork.title[0:30], firstNote.name, cadenceNote.name) + output += '%30s %4s %4s\n' % (thisWork.title[0:30], firstNote.name, cadenceNote.name) bigTotalSame = 0 bigTotalDiff = 0 @@ -139,17 +136,17 @@ def run(self): outKeyDiffTotal = 0 for inKey in sorted(myDict[outKey]): if outKey == inKey: - output += "**** " + output += '**** ' bigTotalSame += myDict[outKey][inKey] else: - output += " " + output += ' ' outKeyDiffTotal += myDict[outKey][inKey] bigTotalDiff += myDict[outKey][inKey] - output += "%4s %4s %4d\n" % (outKey, inKey, myDict[outKey][inKey]) - output += " %4s diff %4d\n" % (outKey, outKeyDiffTotal) - output += "Total Same %4d %3.1f%%\n" % (bigTotalSame, + output += '%4s %4s %4d\n' % (outKey, inKey, myDict[outKey][inKey]) + output += ' %4s diff %4d\n' % (outKey, outKeyDiffTotal) + output += 'Total Same %4d %3.1f%%\n' % (bigTotalSame, (bigTotalSame * 100.0) / (bigTotalSame + bigTotalDiff)) - output += "Total Diff %4d %3.1f%%\n" % (bigTotalDiff, + output += 'Total Diff %4d %3.1f%%\n' % (bigTotalDiff, (bigTotalDiff * 100.0) / (bigTotalSame + bigTotalDiff)) self.storedDict = myDict self.displayStream = allScores @@ -160,17 +157,15 @@ def landiniTonality(show=True): generates information about the tonality of Landini's ballate using the tenor (streamName = "T") and the A cadence (which we would believe would end the piece) - ''' - - ballataObj = cadencebook.BallataSheet() + ballataObj = cadencebook.BallataSheet() worksList = [] for thisWork in ballataObj: - if thisWork.composer == "Landini": + if thisWork.composer == 'Landini': worksList.append(thisWork) - tCounter = TonalityCounter(worksList, streamName = "T", cadenceName = "A") + tCounter = TonalityCounter(worksList, streamName='T', cadenceName='A') tCounter.run() - if show is True: + if show: print(tCounter.output) def nonLandiniTonality(show=True): @@ -181,6 +176,7 @@ def nonLandiniTonality(show=True): would end the piece) + >>> from music21_tools import trecento >>> #_DOCS_SHOW trecento.tonality.nonLandiniTonality(show=True) @@ -271,9 +267,9 @@ def nonLandiniTonality(show=True): for thisWork in ballataObj: if show: print(thisWork.title) - if thisWork.composer != "Landini" and thisWork.composer != ".": + if thisWork.composer != 'Landini' and thisWork.composer != '.': worksList.append(thisWork) - tCounter = TonalityCounter(worksList, streamName = "T", cadenceName = "A") + tCounter = TonalityCounter(worksList, streamName = 'T', cadenceName = 'A') tCounter.run() if show is True: print(tCounter.output) @@ -289,15 +285,15 @@ def anonBallataTonality(show=True): ballataObj = cadencebook.BallataSheet() worksList = [] for thisWork in ballataObj: - if thisWork.composer == ".": + if thisWork.composer == '.': worksList.append(thisWork) - tCounter = TonalityCounter(worksList, streamName = "T", cadenceName = "A") + tCounter = TonalityCounter(worksList, streamName = 'T', cadenceName = 'A') tCounter.run() if show is True: print(tCounter.output) - print("Generating Lilypond PNG of all pieces where the first note of " + - "the tenor is the same pitchclass as the last note of Cadence A") - print("It might take a while, esp. on the first Lilypond run...") + print('Generating Lilypond PNG of all pieces where the first note of ' + + 'the tenor is the same pitchclass as the last note of Cadence A') + print('It might take a while, esp. on the first Lilypond run...') tCounter.displayStream.show('lily.png') def sacredTonality(show=True): @@ -321,13 +317,13 @@ def sacredTonality(show=True): sanctusObj.makeWork(2), agnusObj.makeWork(2) ] - tCounter = TonalityCounter(worksList, streamName = "T", cadenceName = -1) + tCounter = TonalityCounter(worksList, streamName = 'T', cadenceName = -1) tCounter.run() if show is True: print(tCounter.output) tCounter.displayStream.show('lily.png') -def testAll(show=True, fast=False): +def runAll(show=True, fast=False): sacredTonality(show) if fast is False: nonLandiniTonality(show) @@ -335,22 +331,13 @@ def testAll(show=True, fast=False): landiniTonality(show) class Test(unittest.TestCase): - pass - def runTest(self): - testAll(show=False, fast=True) - -class TestExternal(unittest.TestCase): # pragma: no cover - pass + runAll(show=False, fast=True) +class TestExternal(unittest.TestCase): # pragma: no cover def runTest(self): - testAll(show=True, fast=False) + runAll(show=True, fast=False) -if __name__ == "__main__": +if __name__ == '__main__': import music21 - music21.mainTest(Test, 'importPlusRelative') #External) - - -# ----------------------------------------------------------------------------- -# eof - + music21.mainTest(Test, 'importPlusRelative') diff --git a/music21_tools/trecento/trecentoCadence.py b/music21_tools/trecento/trecentoCadence.py index 429b5f5..cf3e3ea 100644 --- a/music21_tools/trecento/trecentoCadence.py +++ b/music21_tools/trecento/trecentoCadence.py @@ -49,7 +49,7 @@ class CadenceConverter(tinyNotation.Converter): ''' Subclass of Tiny Notation that calls these tokens instead of the defaults - + >>> from music21_tools.trecento.trecentoCadence import CadenceConverter >>> dLucaGloriaIncipit = CadenceConverter( ... "6/8 c'2. d'8 c'4 a8 f4 f8 a4 c'4 c'8").parse().stream >>> dLucaGloriaIncipit.rightBarline = 'final' @@ -58,7 +58,7 @@ class CadenceConverter(tinyNotation.Converter): , ) ''' - def __init__(self, stringRep=""): + def __init__(self, stringRep=''): super().__init__(stringRep) self.tokenMap = [ (r'(\d+\/\d+)', tinyNotation.TimeSignatureToken), @@ -66,13 +66,8 @@ def __init__(self, stringRep=""): (r'(\S*)', CadenceNoteToken), # last ] -###### test routines class Test(unittest.TestCase): - - def runTest(self): - pass - def testCopyAndDeepcopy(self): '''Test copying all objects defined in this module ''' @@ -90,25 +85,21 @@ def testCopyAndDeepcopy(self): self.assertNotEqual(a, obj) self.assertNotEqual(b, obj) - def testDotGroups(self): cn = CadenceConverter('c#2..') cn.parse() - a = cn.stream.flatten().notes[0] # returns the stored music21 note. + a = cn.stream.flatten().notes[0] # returns the stored music21 note. self.assertEqual(a.name, 'C#') self.assertEqual(a.duration.type, 'half') self.assertEqual(a.duration.dotGroups, (1, 1)) self.assertEqual(a.duration.quarterLength, 4.5) -class TestExternal(unittest.TestCase): # pragma: no cover +class TestExternal(unittest.TestCase): # pragma: no cover ''' These objects generate PNGs, etc. ''' - def runTest(self): - pass - def testTrecentoLine(self): ''' should display a 6 beat long line with some triplets @@ -117,10 +108,6 @@ def testTrecentoLine(self): self.assertAlmostEqual(st.duration.quarterLength, 6.0) st.show() -if __name__ == "__main__": +if __name__ == '__main__': import music21 music21.mainTest(Test, 'importPlusRelative') - -# ----------------------------------------------------------------------------- -# eof - diff --git a/publications/icmc2010.py b/publications/icmc2010.py index f49a2bc..9a77029 100644 --- a/publications/icmc2010.py +++ b/publications/icmc2010.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # Name: icmc2010.py # Purpose: icmc2010.py @@ -157,10 +156,6 @@ def oldAccent(show=True): class Test(unittest.TestCase): - - def runTest(self): - pass - def testBasic(self): '''icmc2010: Test non-showing functions ''' @@ -168,10 +163,6 @@ def testBasic(self): func(show=False) class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - def testBasic(self): '''Test showing functions ''' @@ -185,6 +176,3 @@ def testBasic(self): # bergEx01() music21.mainTest(Test) -# ----------------------------------------------------------------------------- -# eof - diff --git a/publications/icmc2011.py b/publications/icmc2011.py index 9aae61a..25b1d69 100644 --- a/publications/icmc2011.py +++ b/publications/icmc2011.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # Name: icmc2011.py # Purpose: Demonstrations for the ICMC 2011 poster session @@ -23,11 +22,6 @@ # ------------------------------------------------------------------------------ class Test(unittest.TestCase): - - def runTest(self): - pass - - def testStreams01(self): ''' Basic stream issues @@ -241,7 +235,7 @@ def testStreams01(self): # Non-Hierarchical Object Associations # oldIds = [] # for idKey in n1.sites.siteDict: - # print (idKey, n1.sites.siteDict[idKey].isDead) + # print(idKey, n1.sites.siteDict[idKey].isDead) # oldIds.append(idKey) # print("-------") @@ -253,7 +247,7 @@ def testStreams01(self): # print(id(sp1), id(sp1.spannerStorage), n1.sites.siteDict[id(sp1.spannerStorage)].isDead) # if id(sp1.spannerStorage) in oldIds: - # print ("******!!!!!!!!!*******") + # print("******!!!!!!!!!*******") # Elements can report on what Spanner they belong to ss1 = n1.getSpannerSites() @@ -552,10 +546,10 @@ def testScalesPy06(self): # def testScalesPy10(self): # # look through s = corpus.parse('bwv1080/06') - # #part = corpus.parse('bwv1080/03').measures(24, 29).parts[0] - # #part = corpus.parse('bwv1080/03').parts[0] + # # part = corpus.parse('bwv1080/03').measures(24, 29).parts[0] + # # part = corpus.parse('bwv1080/03').parts[0] # - # #from music21 import corpus, scale, note + # # from music21 import corpus, scale, note # from music21 import analysis # # scDMelodicMinor = scale.MelodicMinorScale('d4') @@ -620,7 +614,7 @@ def testEx01(self): sc2 = scale.WholeToneScale('f#') # get pitches from any range of this scale - # print str(sc2.getPitches('g2', 'c4')) + # print(str(sc2.getPitches('g2', 'c4'))) self.assertEqual(common.pitchList(sc2.getPitches('g2', 'c4')), '[A-2, B-2, C3, D3, F-3, G-3, A-3, B-3, C4]') @@ -644,7 +638,7 @@ def testEx01(self): # # Labeling a vocal part based on scale degrees derived # # from key signature and from a specified target key. # - # s = corpus.parse('hwv56/movement3-03.md')#.measures(1, 7) + # s = corpus.parse('hwv56/movement3-03.md') # .measures(1, 7) # basso = s.parts['basso'] # s.remove(basso) # diff --git a/publications/ismir2010.py b/publications/ismir2010.py index 8c0aae0..e1a6a52 100644 --- a/publications/ismir2010.py +++ b/publications/ismir2010.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # Name: ismir2010.py # Purpose: Examples for ISMIR 2010 paper @@ -91,8 +90,8 @@ def newDomSev(show=True): firstNote = thisMeasure.notesAndRests[0] firstNote.lyric = primeForm - # Thus we append the chord in closed position and then - # the measure containing the chord. + # Thus we append the chord in closed position and then + # the measure containing the chord. chordMeasure = stream.Measure() chordMeasure.append( @@ -326,7 +325,7 @@ def demoBasic(): len(soprano.getElementsByClass('Measure')) _mRange = soprano.measures(14, 16) # mRange.show() - # mRange.sorted.show('text') # here we can see this + # mRange.sorted.show('text') # here we can see this @@ -513,7 +512,7 @@ def demoBachSearch(): import random fpList = list(corpus.search('bach').search('.xml')) - + random.shuffle(fpList) results = stream.Stream() @@ -607,10 +606,6 @@ def demoBachSearchBrief(): class Test(unittest.TestCase): - - def runTest(self): - pass - def testBasic(self): '''ismir2010: Test non-showing functions @@ -620,10 +615,6 @@ def testBasic(self): func(show=False) class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - def testBasic(self): for func in funcList: func(show=True) @@ -656,6 +647,3 @@ def testBasic(self): # demoGraphMessiaenBrief() # demoGraphMessiaen() -# ----------------------------------------------------------------------------- -# eof - diff --git a/publications/misc2010.py b/publications/misc2010.py index 4102199..d27efbb 100644 --- a/publications/misc2010.py +++ b/publications/misc2010.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # Name: misc2010.py # Purpose: demos from 2010 @@ -108,7 +107,7 @@ def towersOfHanoi(show=False, numParts=6, transpose=False): descendingPitches = [medPitch, lowPitch, highPitch] ascendingPitches = [lowPitch, medPitch, highPitch] - if (numParts/2.0) == int(numParts/2.0): + if (numParts / 2.0) == numParts // 2: oddPitches = descendingPitches evenPitches = ascendingPitches else: @@ -120,7 +119,7 @@ def towersOfHanoi(show=False, numParts=6, transpose=False): firstNote = note.Note("E5") firstNote.quarterLength = baseQuarterLength - if (i / 2.0) == int(i / 2.0): + if (i / 2.0) == i // 2: pitchCycle = copy.deepcopy(evenPitches) else: pitchCycle = copy.deepcopy(oddPitches) diff --git a/publications/seaverOct2009.py b/publications/seaverOct2009.py index 66c8d09..a41aaf6 100644 --- a/publications/seaverOct2009.py +++ b/publications/seaverOct2009.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from music21 import stream from music21 import clef from music21 import converter, corpus, instrument, graph, note, meter, humdrum @@ -44,7 +43,7 @@ def lsort(keyname): defaultPitch = music21.pitch.Pitch("C3") - # semiFlat lets me get all Measures no matter where they reside in the tree structure + # semiFlat lets me get all Measures no matter where they reside in the tree structure measureStream = converter.parse(humdrum.testFiles.mazurka6 ).flatten(retainContainers=True).getElementsByClass('Measure') rhythmicHash = defaultdict(list) @@ -93,7 +92,7 @@ def lsort(keyname): def displayChopinRhythms(): defaultPitch = music21.pitch.Pitch("C3") - # semiFlat lets me get all Measures no matter where they reside in the tree structure + # semiFlat lets me get all Measures no matter where they reside in the tree structure measureStream = converter.parse(humdrum.testFiles.mazurka6 ).flatten(retainContainers=True).getElementsByClass('Measure') rhythmicHash = {} @@ -394,7 +393,7 @@ def januaryThankYou(): # single labeled chord can be difficult. The first and second should be # extremely easy. The third and fifth should be easy to construct in # such a way that it doesn't miss anything but it might give some -# results that are "false positives". #4 will be the hardest to +# results that are "false positives". No. 4 will be the hardest to # construct, since a half cadence is something that's not determined so # much by the cadence chord and the chords directly preceding but more # from a larger sense of tonal listening which is of course harder to @@ -493,10 +492,6 @@ def js_q5(): tests = [simple4a, simple4b, simple4c, simple4e, simple4f] class TestExternal(unittest.TestCase): # pragma: no cover - - def runTest(self): - pass - def testBasic(self): '''Test showing functions ''' @@ -510,26 +505,13 @@ def testSimple3(self): class Test(unittest.TestCase): - - def runTest(self): - pass - - def xtestBasic(self): '''seaverOct2009: Test non-showing functions ''' for func in tests: func(show=False) - if __name__ == "__main__": # js_q1() music21.mainTest(Test) # music21.mainTest(TestExternal) - - - - -# ----------------------------------------------------------------------------- -# eof - diff --git a/publications/seaver_presentation_2008.py b/publications/seaver_presentation_2008.py index 5624f0f..b3913b1 100644 --- a/publications/seaver_presentation_2008.py +++ b/publications/seaver_presentation_2008.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # Name: demos/seaver_presentation_2008.py # Purpose: Demonstrations for the Seaver 2008 demo @@ -23,8 +22,8 @@ def createEasyScale(): s1 = converter.parse("tinynotation: 3/4 d8 e f g a b") -# s1.timeSignature = time1 -# s1.showTimeSignature = True + # s1.timeSignature = time1 + # s1.showTimeSignature = True s1.show('lily.png') def createSleep(): diff --git a/publications/smt2010.py b/publications/smt2010.py index b8ae710..da20146 100644 --- a/publications/smt2010.py +++ b/publications/smt2010.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # Name: smt2010.py # Purpose: Demonstrations for the SMT 2010 poster session @@ -9,16 +8,12 @@ # Copyright: Copyright © 2009-14 Michael Scott Asato Cuthbert # License: BSD, see license.txt # ------------------------------------------------------------------------------ - - import unittest from music21 import corpus, converter, note, chord, stream, environment, graph, interval, meter _MOD = 'demo/smt2010.py' environLocal = environment.Environment(_MOD) - - def ex01(show=True, *arguments, **keywords): ''' This example extracts first a part, then a measure from a complete score. @@ -184,8 +179,8 @@ def ex1_revised(show=True, *arguments, **keywords): else: beethovenScore = corpus.parse('opus133.xml') # load a MusicXML file - violin2 = beethovenScore[1] # most programming languages start counting from 0, - # so part 0 = violin 1, part 1 = violin 2, etc. + violin2 = beethovenScore[1] # most programming languages start counting from 0, + # so part 0 = violin 1, part 1 = violin 2, etc. display = stream.Stream() # an empty container for filling with found notes for thisMeasure in violin2.getElementsByClass('Measure'): notes = thisMeasure.findConsecutiveNotes(skipUnisons=True, @@ -294,8 +289,8 @@ def corpusMelodicIntervalSearch(show=True): msg.append( 'locale: %s: found %s percent melodic sevenths, out of %s intervals in %s works' % ( name, pcentSevenths, intervalCount, workCount)) -# for key in sorted(intervalDict.keys()): -# print intervalDict[key] + # for key in sorted(intervalDict.keys()): + # print(intervalDict[key]) for sub in msg: if show: @@ -445,10 +440,6 @@ def chordifyAnalysisBrief(): # ------------------------------------------------------------------------------ class Test(unittest.TestCase): - - def runTest(self): - pass - def testBasic(self): ''' smt2010: Test non-showing functions @@ -460,7 +451,6 @@ def testBasic(self): # for func in [findPotentialPassingTones]: for func in [findHighestNotes, demoJesse, corpusMelodicIntervalSearchBrief, findPotentialPassingTones]: - # func(show=False, op133=sStream) func(show=False) @@ -489,7 +479,3 @@ def testBasic(self): # chordifyAnalysisBrief() # corpusMelodicIntervalSearchBrief() chordifyAnalysisBrief() - -# ----------------------------------------------------------------------------- -# eof - diff --git a/publications/smt2011.py b/publications/smt2011.py index 880296c..0876c99 100644 --- a/publications/smt2011.py +++ b/publications/smt2011.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # Name: smt2011.py # Purpose: Demonstrations for the SMT 2011 demo @@ -147,7 +146,7 @@ def demoMakeChords(): c = src.flattenParts().makeChords(minimumWindowSize=3) print(c.write()) c.show() - + src = corpus.parse('opus18no1/movement3.xml').measures(0, 10) src.chordify().show() diff --git a/pyproject.toml b/pyproject.toml index 585c27d..3202f1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "music21-tools" -version = "10.1.2" +version = "10.2.0" description = "Demonstrations and tools for music21." readme = "README.md" license = "BSD-3-Clause" @@ -52,7 +52,41 @@ Homepage = "https://github.com/cuthbertLab/music21-tools" default-groups = ["dev"] [tool.hatch.build.targets.wheel] -packages = ["music21_tools"] +packages = ['music21_tools'] [tool.pytest.ini_options] -doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS" +# `uv run pytest` is equivalent to: +# pytest --doctest-modules -o python_files='*.py' music21_tools/ +addopts = ['--doctest-modules'] +doctest_optionflags = ['NORMALIZE_WHITESPACE', 'ELLIPSIS'] +python_files = ['*.py'] +testpaths = ['music21_tools'] + +[tool.ruff] +line-length = 100 + +[tool.ruff.lint] +# Flake8, pycodestyle, flake8-quotes + pyflakes (mirrors music21's house style) +select = ['E', 'W', 'Q', 'F', 'B'] +dummy-variable-rgx = '^(_.*|unused.*|i|j|counter|junk|dummy)' +ignore = [ + 'E227', # space around | -- nice to omit in int|str + 'E301', # 0 blank lines + 'E302', # blank lines -- expected 2 blank lines + 'E303', # too many blank lines + 'E731', # assignment to lambda + 'B904', # raise-from + 'B905', # zip without strict= +] + +[tool.ruff.lint.flake8-quotes] +inline-quotes = 'single' +multiline-quotes = 'single' +docstring-quotes = 'single' + +[tool.ruff.lint.per-file-ignores] +# clercqTemperley example contains a real chord progression on a single line +# (musical content; not wrappable). +'music21_tools/bhadley/harmonyRealizer.py' = ['E501'] +# Gregorio .gabc → LaTeX template strings; line breaks would break the template. +'music21_tools/chant/chant.py' = ['E501'] diff --git a/tools/exceldiff.py b/tools/exceldiff.py index e478992..9a6c03f 100755 --- a/tools/exceldiff.py +++ b/tools/exceldiff.py @@ -23,14 +23,14 @@ def main(argv: list[str]) -> None: if len(argv) != 3: - raise SystemExit("Usage: exceldiff.py file1.xls:sheet1 file2.xls:sheet2") + raise SystemExit('Usage: exceldiff.py file1.xls:sheet1 file2.xls:sheet2') if argv[1].count(':') == 1: (book1name, sheetname1) = argv[1].split(':') if book1name.count('.xls') == 0: - book1name += ".xls" + book1name += '.xls' else: - raise SystemExit("First name must be in form filename:sheetname") + raise SystemExit('First name must be in form filename:sheetname') if argv[2].count(':') == 1: (book2name, sheetname2) = argv[2].split(':') @@ -38,7 +38,7 @@ def main(argv: list[str]) -> None: (book2name, sheetname2) = (argv[2], sheetname1) if book2name.count('.xls') == 0: - book2name += ".xls" + book2name += '.xls' book1 = xlrd.open_workbook(book1name) book2 = xlrd.open_workbook(book2name) @@ -81,26 +81,26 @@ def main(argv: list[str]) -> None: minCells = totalCells1 # doesnt matter which for j in range(minCells): if rowvalues1[j] != rowvalues2[j]: - print("%3d,%2s--%34s : %34s" % (i + 1, xlrd.colname(j), + print('%3d,%2s--%34s : %34s' % (i + 1, xlrd.colname(j), (rowvalues1[j]).encode('utf-8')[:34], (rowvalues2[j]).encode('utf-8')[:34])) if extraCells > 0: - print("%3d extra cells in row %3d in" % (extraCells, i + 1), end='') + print('%3d extra cells in row %3d in' % (extraCells, i + 1), end='') if longrow == 1: - print(book1name + ":" + sheetname1) + print(book1name + ':' + sheetname1) elif longrow == 2: - print(book2name + ":" + sheetname2) + print(book2name + ':' + sheetname2) else: - raise Exception("What? longrow was not set!") + raise Exception('What? longrow was not set!') if extraRows > 0: - print("%3d extra rows in" % extraRows,) + print('%3d extra rows in' % extraRows,) if longsheet == 1: - print(book1name + ":" + sheetname1) + print(book1name + ':' + sheetname1) elif longsheet == 2: - print(book2name + ":" + sheetname2) + print(book2name + ':' + sheetname2) else: - raise Exception("What? longsheet was not set!") + raise Exception('What? longsheet was not set!') if __name__ == '__main__': diff --git a/uv.lock b/uv.lock index 214cc53..be76ce6 100644 --- a/uv.lock +++ b/uv.lock @@ -522,7 +522,7 @@ wheels = [ [[package]] name = "music21-tools" -version = "10.1.2" +version = "10.2.0" source = { editable = "." } dependencies = [ { name = "music21" },