Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import and export of figured bass elements from MEI and musicxml file formats #1539

Closed
wants to merge 226 commits into from
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
226 commits
Select commit Hold shift + click to select a range
267bb07
Change full measure rest (+Aloha!)
mscuthbert Jan 7, 2023
9f3f066
Improve existing code
mscuthbert Jan 13, 2023
c472631
NOTE: tie.style is a string -- change!
mscuthbert Jan 21, 2023
48d216d
added figuredBass tag to mei import
Feb 26, 2023
b3249a9
fixed import and moved figuredBassIndication to harmony
Feb 26, 2023
59d54c0
Merge pull request #2 from mxordn/mei-import-fb-support
mxordn Feb 26, 2023
afcde73
added meterSig to mei import
Feb 26, 2023
7ca3834
first working musicxml output for FiguredBassIndication Objects
Feb 28, 2023
79ab796
improved figuredbass mei and musicxml import/export deals now with ex…
Feb 28, 2023
8fe69ed
Merge branch 'cuthbertLab:master' into mei-import-fb-support
mxordn Feb 28, 2023
fe3aaf5
stable import and export of figured base. Some unicode characters mig…
Mar 4, 2023
7708338
cleanup mei import and musicxml export
Mar 4, 2023
0793349
Merge pull request #3 from mxordn/mei-import-fb-support
mxordn Mar 4, 2023
82995d7
xml import and export fixed problems with more than two figures per note
Mar 7, 2023
60653c1
Merge pull request #4 from cuthbertLab/master
mxordn Mar 21, 2023
0cabb7d
fixed wrong offsets for multiple figures
Mar 21, 2023
dc812a4
First attempt at adding accidentals to Turns, Trills, and Mordents.
gregchapman-dev Mar 22, 2023
fd1626d
fixed export of multiple figures, where an already exsiting duration …
Mar 22, 2023
874b959
cleaned upcomments and stuff
Mar 22, 2023
7c62238
Merge pull request #5 from mxordn/musicxml-fb-import
mxordn Mar 22, 2023
a85aeeb
added support for prefix tags and solved problems with empty number tags
Mar 22, 2023
582a367
Add the ability to set size for Mordent/Trill, but only if the Morden…
gregchapman-dev Mar 22, 2023
90f38cf
Fix interval calculation.
gregchapman-dev Mar 22, 2023
ea8d3e8
Fix some bugs to get tests passing.
gregchapman-dev Mar 22, 2023
ffc6190
figured bass is now checked
Mar 22, 2023
9acd4a6
updated mei/base.py
Mar 23, 2023
1c11c71
mei/base.py recent update to upstream
Mar 23, 2023
5114ede
Enhance Trill/Mordent/Turn name to include any accidental(s).
gregchapman-dev Mar 23, 2023
13d0d59
full merge of mei/base.py
Mar 23, 2023
f065d4c
edit in mei/test_base.py to pass new fb element in part list
Mar 23, 2023
2e92bfb
Mordent/Turn/Trill.get{Upper,Lower,}Size now takes pitch instead of n…
gregchapman-dev Mar 24, 2023
98533df
Minimize gaps produced by quantization algorithm
jacobtylerwalls Mar 25, 2023
57b5f01
Write ornament accidentals as <accidental-mark> in m21ToXml.py.
gregchapman-dev Mar 25, 2023
52a3280
Get rid of accidental-mark TODOs. They're done.
gregchapman-dev Mar 25, 2023
f64ea43
fixup! Minimize
jacobtylerwalls Mar 25, 2023
6f75a9b
Be a little more accurate about what has been done with accidental-mark.
gregchapman-dev Mar 25, 2023
835910c
Fix notations32a.xml "turn+acc.(ab.+bel./rel to turn)", which had bot…
gregchapman-dev Mar 25, 2023
1c88d57
Fix some tests.
gregchapman-dev Mar 25, 2023
517f046
fixup! Minimize
jacobtylerwalls Mar 25, 2023
fe5186a
fixup! Minimize
jacobtylerwalls Mar 26, 2023
d4ebe55
Add regression test for insert(ignoreSort=True)
jacobtylerwalls Mar 26, 2023
a7135d3
fixup! Add regression test
jacobtylerwalls Mar 26, 2023
1ea8840
fixup! Minimize
jacobtylerwalls Mar 26, 2023
c634196
Remove Expander._srcNotMeasureStream
jacobtylerwalls Mar 26, 2023
f558b8e
Allow accid to be set (not just __init__ed), but only if size has not…
gregchapman-dev Mar 26, 2023
0def6e3
Make the turn accidentals in notation32a be ones that Finale can actu…
gregchapman-dev Mar 26, 2023
b83121b
Parse 'accidental-mark' (Ornament) from MusicXML.
gregchapman-dev Mar 26, 2023
88333d5
Test turn accidentals (upper and lower).
gregchapman-dev Mar 26, 2023
256d013
mypy notes that I was setting accid on ornaments that didn't have 'em.
gregchapman-dev Mar 26, 2023
389bb82
suggestions from review added, smaller formatting improvements
Mar 27, 2023
2fb6285
adapted tests in mei/test_base-py and doctest for figuredBass/notatio…
Mar 27, 2023
4e5fdec
simplification of a if else statement from review
Mar 27, 2023
a16c2d5
Redo ornament accidentals to use accidental names, and then resolveOt…
gregchapman-dev Mar 28, 2023
e76f405
Oops, missed one.
gregchapman-dev Mar 28, 2023
ad8b09d
finally cleaned up for tests and flake8 and mypy
Mar 28, 2023
f519b9d
Modify makeAccidentals to deal with ornament accidentals.
gregchapman-dev Mar 28, 2023
333721f
Fix a crash in Stream.pitches when a GeneralNote has no pitches (e.g.…
gregchapman-dev Mar 28, 2023
75420e1
Merge pull request #1542 from cuthbertLab/micro-optimize-expander
mscuthbert Mar 29, 2023
81b2bd5
Implement useful `PercussionChord.pitches` property
jacobtylerwalls Mar 29, 2023
e45753d
Merge pull request #1547 from cuthbertLab/percussion-chord-pitches-prop
mscuthbert Mar 30, 2023
37763c4
Ignore Unpitched objects in key analysis (#1543)
jacobtylerwalls Mar 30, 2023
e9274e9
fix opfrac for small numbers
mscuthbert Mar 30, 2023
4ad73d0
buggy
mscuthbert Mar 30, 2023
187a2dc
deal with 1.49999999; typing
mscuthbert Mar 30, 2023
f6242a0
fix uncovered error
mscuthbert Mar 30, 2023
9011556
minor docs fixes to coreInsert
mscuthbert Mar 30, 2023
f00e741
First swath of review response.
gregchapman-dev Mar 30, 2023
f738377
Remove Trill/GeneralMordent/Turn size property. Clients now have to …
gregchapman-dev Mar 30, 2023
1d87d2d
Oops, fix that test.
gregchapman-dev Mar 30, 2023
9536d10
Merge pull request #1549 from cuthbertLab/core-insert-docs
mscuthbert Mar 30, 2023
f05ec0a
Merge pull request #1548 from cuthbertLab/small-opfrac
mscuthbert Mar 30, 2023
59e02d5
Placate flake8.
gregchapman-dev Mar 30, 2023
3283c3f
Refine headerdoc a bit. Put three-quarters-flat back as accidental b…
gregchapman-dev Mar 30, 2023
4cae3c2
Even more headerdoc/tests.
gregchapman-dev Mar 30, 2023
695a1e9
Update music21/musicxml/xmlToM21.py
mxordn Mar 30, 2023
53263a5
A few style tweaks and a bunch of headerdoc.
gregchapman-dev Mar 31, 2023
e90ae4f
Turns out you really need an explit octave before you transpose, or y…
gregchapman-dev Mar 31, 2023
053d933
Implement InvertedTrill, simplify InvertedTurn, fix TrillRecognizer t…
gregchapman-dev Mar 31, 2023
3414cc2
Oops typo.
gregchapman-dev Mar 31, 2023
7851f48
I missed some implicit octave transposes.
gregchapman-dev Apr 1, 2023
0150cc4
Add some more tests.
gregchapman-dev Apr 1, 2023
7997ce6
Fix a couple tests.
gregchapman-dev Apr 1, 2023
44450c4
Revert changes to insert() in favor of explicit sort in test
jacobtylerwalls Apr 1, 2023
65692c8
Remove test relating to insert(ignoreSort=True)
jacobtylerwalls Apr 1, 2023
b9a1494
Implement Trill/Mordent/Turn on Unpitched. More tests/fixes.
gregchapman-dev Apr 1, 2023
cf27d2b
Test some edge conditions.
gregchapman-dev Apr 2, 2023
b0ab490
Add tests for Trill/GeneralMordent/Turn.updateAccidentalDisplay.
gregchapman-dev Apr 2, 2023
08fd681
When setting pitch.accidental to a float, do your best to put the qua…
gregchapman-dev Apr 2, 2023
edfba3b
Be consistent about _direction typing.
gregchapman-dev Apr 2, 2023
bcc3808
Test and fix ornament <accidental-mark> parse and write.
gregchapman-dev Apr 2, 2023
a9de789
Fix pitch.py test expectations to match new quarter-tone preference o…
gregchapman-dev Apr 2, 2023
8ed3beb
Do the "microtones to quarter tones" operation in Ornament et al, ins…
gregchapman-dev Apr 8, 2023
3308ee4
Remove "microtones to quarter tones" operation from pitch.accidental …
gregchapman-dev Apr 8, 2023
19c91f4
Address a few review comments.
gregchapman-dev Apr 8, 2023
b23eba0
Undo my workaround for issue #1553 (transpose can lose microtones), a…
gregchapman-dev Apr 9, 2023
c7965bf
And test that fix.
gregchapman-dev Apr 9, 2023
367e9ab
A little more testing.
gregchapman-dev Apr 10, 2023
77af85e
{Trill,GeneralMordent}.accidentName -> accidental (with displayStatus…
gregchapman-dev Apr 15, 2023
40f89f7
Improvements to the documentation.
gregchapman-dev Apr 17, 2023
4d321cf
More review response.
gregchapman-dev Apr 18, 2023
a401969
Fix a typo, make mypy happier.
gregchapman-dev Apr 18, 2023
d82183f
Fix test to not use back-tick for half-flat.
gregchapman-dev Apr 18, 2023
6c3da7c
Move two new routines from stream/base.py to stream/makeNotation.py.
gregchapman-dev Apr 18, 2023
9678517
Add documentation for makeOrnamentalAccidentals.
gregchapman-dev Apr 18, 2023
2526b68
Merge pull request #1545 from gregchapman-dev/gregc/ornamentAccidentals
mscuthbert Apr 19, 2023
3c4b95b
Update layout docs
mscuthbert Apr 21, 2023
1573e37
Merge pull request #1554 from cuthbertLab/update_layout_docs
mscuthbert Apr 21, 2023
b2af34b
Fix stripTies when accidentals are neutral & none
TimFelixBeyer Apr 30, 2023
5c232c9
Merge branch 'master' into quantization-improvements
mscuthbert May 1, 2023
0206700
No need to sort all if just getting the minimum
mscuthbert May 1, 2023
18b750f
rem unused heapq
mscuthbert May 1, 2023
df97c67
trailing whitespace (editing on github, bad idea)
mscuthbert May 1, 2023
696545a
Merge pull request #1540 from jacobtylerwalls/quantization-improvements
mscuthbert May 1, 2023
9c67043
Improve measure numbers on ClercqTemperley
mscuthbert May 7, 2023
0d3386b
lint, docs improve, formalDivision editorial
mscuthbert May 7, 2023
6973483
mypy
mscuthbert May 7, 2023
6d83988
test issue
mscuthbert May 7, 2023
83696c5
make sections more testable
mscuthbert May 7, 2023
42534a3
Merge pull request #1558 from cuthbertLab/ct_measure_numbers
mscuthbert May 8, 2023
dd4f03f
fix jupyter/colab MIDI
mscuthbert May 11, 2023
8d5cce8
Merge pull request #1565 from cuthbertLab/modernm21j
mscuthbert May 11, 2023
8dbf9ed
Avoid altering score on write
TimFelixBeyer May 9, 2023
067dc62
Skip extra deepcopy for flat Part
mscuthbert May 9, 2023
8a8c167
Add regression test
jacobtylerwalls May 11, 2023
e4a294c
Add `requests` to minimum requirements
jacobtylerwalls May 11, 2023
9f1cf26
Fix reading unknown MetaMessage
TimFelixBeyer May 14, 2023
d6303db
Fix event parsing
TimFelixBeyer May 14, 2023
851c776
Remove byte order mark from corpus.virtual
jacobtylerwalls May 14, 2023
8e29ff4
Don't set status byte on Meta Message
TimFelixBeyer May 15, 2023
e36a014
Merge branch 'cuthbertLab:master' into master
TimFelixBeyer May 15, 2023
0f9d6cc
Merge pull request #1569 from cuthbertLab/bom
mscuthbert May 15, 2023
820efd9
Prevent doubly-flatted sevenths in chord symbols
jacobtylerwalls May 15, 2023
0911ff1
Update octave in expected result
jacobtylerwalls May 15, 2023
892bd30
Fix MIDI parsing with unknown meta message
TimFelixBeyer May 15, 2023
54b2753
Merge pull request #1572 from cuthbertLab/doubly-flatted-chord-symbol
mscuthbert May 16, 2023
c20a4cd
Merge pull request #1573 from TimFelixBeyer/patch-2
mscuthbert May 16, 2023
2cc61a4
Simplify to elif
TimFelixBeyer May 16, 2023
3f1af1f
Increase default MIDI ticksPerQuarter
TimFelixBeyer May 17, 2023
9bab45f
Update tests
TimFelixBeyer May 17, 2023
05548b0
ticksAtStart = ticksPerQuarter
mscuthbert May 17, 2023
6201cf0
Merge pull request #1577 from TimFelixBeyer/patch-6
mscuthbert May 17, 2023
acf926c
Merge pull request #1575 from TimFelixBeyer/patch-4
mscuthbert May 18, 2023
1cccdf5
make matchByPitch handle None/natural accidental
TimFelixBeyer May 21, 2023
868136a
fix linting
TimFelixBeyer May 21, 2023
3b17a68
Add MusicXML sound tag tempo parsing
TimFelixBeyer May 21, 2023
e8db15d
Add and fix tests, increment version number
TimFelixBeyer May 21, 2023
c1d166f
fix pylint
TimFelixBeyer May 21, 2023
2f6833a
Merge pull request #1556 from TimFelixBeyer/master
mscuthbert May 27, 2023
013bdc3
Add typing and docs
TimFelixBeyer May 27, 2023
41bf5dd
Fix typing
TimFelixBeyer May 27, 2023
46ee5dc
Fix failing braille tests
TimFelixBeyer May 27, 2023
18fdfd6
Fix further tests and typing
TimFelixBeyer May 27, 2023
e25334b
Merge pull request #1579 from TimFelixBeyer/patch-8
mscuthbert May 27, 2023
ffacccf
Simpler pitch comparisons, typing
mscuthbert May 27, 2023
c56dd47
Merge branch 'master' into pitchEquiv
mscuthbert May 27, 2023
8ebea2e
cleanup typing on spanner
mscuthbert May 27, 2023
bddd31a
Merge pull request #1582 from cuthbertLab/pitchEquiv
mscuthbert May 27, 2023
bcb87e8
Add MusicXML security warning
mscuthbert May 31, 2023
a6b6ad4
Update info for linux/additional components
mscuthbert Jun 1, 2023
fd07402
fix doctest
mscuthbert Jun 1, 2023
2dd2fa6
Merge pull request #1586 from cuthbertLab/additional_linux_docs
mscuthbert Jun 1, 2023
2ca1f13
Merge pull request #1584 from cuthbertLab/musicxml-security-warning
mscuthbert Jun 1, 2023
878e0de
Set ABC version from %%abc-version directive
mscuthbert Jun 2, 2023
e40fa08
more tests
mscuthbert Jun 3, 2023
bb5a579
mid-fix
mscuthbert Jun 3, 2023
7558873
better typing and version fixes
mscuthbert Jun 3, 2023
80c32c9
Merge branch 'master' into file_widget
mscuthbert Jun 3, 2023
6ff290d
work
mscuthbert Jun 3, 2023
32352df
fix lint
mscuthbert Jun 3, 2023
4f8d003
Update email on Readme
mscuthbert Jun 3, 2023
8749987
simplify structure and lint
mscuthbert Jun 3, 2023
64836b9
MIDI is working again
mscuthbert Jun 3, 2023
c7769a5
import abcFormat (TODO: remove circular)
mscuthbert Jun 3, 2023
4e6f057
autodetect multiple pages
mscuthbert Jun 3, 2023
a6955a1
Fix all to work with Jupyter 7/JupyterLab
mscuthbert Jun 4, 2023
2cad009
Merge pull request #1591 from cuthbertLab/mscuthbert-patch-1
mscuthbert Jun 4, 2023
4b5155c
github autopilot introduced blank line!
mscuthbert Jun 4, 2023
69bcccb
More email things
mscuthbert Jun 4, 2023
5562943
Merge pull request #1589 from cuthbertLab/abc-directive-version
mscuthbert Jun 4, 2023
048a384
Merge pull request #1593 from cuthbertLab/new_email
mscuthbert Jun 4, 2023
55207a2
Refactor out MuseScore/Jupyter code
mscuthbert Jun 4, 2023
3b6a927
lint and flake
mscuthbert Jun 4, 2023
58f5495
Merge pull request #1592 from cuthbertLab/file_widget
mscuthbert Jun 4, 2023
ac23422
add lusitano, comment out warning
mscuthbert Jun 4, 2023
3ea66e4
Merge branch 'master' into aloha_oe
mscuthbert Jun 4, 2023
cfdc95b
Speed up quantize()
TimFelixBeyer Jun 5, 2023
fcc1bd5
Merge pull request #1594 from TimFelixBeyer/patch-8
mscuthbert Jun 5, 2023
61dcdbc
Merge branch 'master' into aloha_oe
mscuthbert Jun 5, 2023
149516f
compromise on fullRest
mscuthbert Jun 5, 2023
425ec69
fix lint / flake
mscuthbert Jun 5, 2023
405b1fc
Merge pull request #1595 from cuthbertLab/aloha_oe
mscuthbert Jun 5, 2023
c0f8c3d
Search.base with Unpitched etc
mscuthbert Jun 6, 2023
d9676fb
Merge pull request #1597 from cuthbertLab/search_with_unpitched
mscuthbert Jun 6, 2023
c8a1869
Metronome silent; Jupyter (v9)
mscuthbert Jun 7, 2023
06fd20c
temp disable extension
mscuthbert Jun 10, 2023
b04bfe4
corpusFilePath to metadata and as id
mscuthbert Jun 10, 2023
aed4ace
Fix plotting in recent matplotlib/notebook
mscuthbert Jun 10, 2023
35d2ce8
Doc tests up to Chapter 22
mscuthbert Jun 11, 2023
b3c3513
lint and use callProcess in plot; docs pass
mscuthbert Jun 11, 2023
30afe10
docfix, lint, mypy
mscuthbert Jun 11, 2023
9b13e16
Merge pull request #1601 from cuthbertLab/pre-9.0
mscuthbert Jun 11, 2023
e0c761b
Move incomplete docs to testsAndInProgress
mscuthbert Jun 13, 2023
0fdf0b4
Merge pull request #1603 from cuthbertLab/docsReorg
mscuthbert Jun 13, 2023
27a1c44
added figuredBass tag to mei import
Feb 26, 2023
621aef3
fixed import and moved figuredBassIndication to harmony
Feb 26, 2023
7e416cf
added meterSig to mei import
Feb 26, 2023
8085310
first working musicxml output for FiguredBassIndication Objects
Feb 28, 2023
f10cbb9
improved figuredbass mei and musicxml import/export deals now with ex…
Feb 28, 2023
52363bc
stable import and export of figured base. Some unicode characters mig…
Mar 4, 2023
413c17b
cleanup mei import and musicxml export
Mar 4, 2023
8244b3e
xml import and export fixed problems with more than two figures per note
Mar 7, 2023
f9179f7
fixed wrong offsets for multiple figures
Mar 21, 2023
3649265
fixed export of multiple figures, where an already exsiting duration …
Mar 22, 2023
e497210
cleaned upcomments and stuff
Mar 22, 2023
c6ac193
added support for prefix tags and solved problems with empty number tags
Mar 22, 2023
d8cef56
figured bass is now checked
Mar 22, 2023
4f098b1
updated mei/base.py
Mar 23, 2023
9717173
mei/base.py recent update to upstream
Mar 23, 2023
0f4aba5
full merge of mei/base.py
Mar 23, 2023
c8318c5
edit in mei/test_base.py to pass new fb element in part list
Mar 23, 2023
7076597
suggestions from review added, smaller formatting improvements
Mar 27, 2023
d6c49db
adapted tests in mei/test_base-py and doctest for figuredBass/notatio…
Mar 27, 2023
9688055
simplification of a if else statement from review
Mar 27, 2023
97882bc
finally cleaned up for tests and flake8 and mypy
Mar 28, 2023
dcd1477
Update music21/musicxml/xmlToM21.py
mxordn Mar 30, 2023
1164fd9
rebase 8.3
Jun 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 56 additions & 14 deletions music21/figuredBass/notation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@
(2,): (6, 4, 2),
}

prefixes = ['+', '#', '++', '##']
suffixes = ['\\']

modifiersDictXmlToM21 = {'sharp': '#',
'flat': 'b',
'natural': '\u266e',
'double-sharp': '##',
'flat-flat': 'bb',
'backslash': '\\',
'slash': '/'}

modifiersDictM21ToXml = {'#': 'sharp',
'b': 'flat',
'##': 'double-sharp',
'bb': 'flat-flat',
'\\': 'backslash',
'+': 'sharp',
'\u266f': 'sharp',
'\u266e': 'natural',
'\u266d': 'flat',
'\u20e5': 'sharp'}

class Notation(prebase.ProtoM21Object):
'''
Expand Down Expand Up @@ -79,6 +100,7 @@ class Notation(prebase.ProtoM21Object):

* '13' -> '13,11,9,7,5,3'

* '_' -> treated as an extender

Figures are saved in order from left to right as found in the notationColumn.

Expand All @@ -104,11 +126,11 @@ class Notation(prebase.ProtoM21Object):
<music21.figuredBass.notation.Modifier + sharp>,
<music21.figuredBass.notation.Modifier None None>)
>>> n1.figures[0]
<music21.figuredBass.notation.Figure 6 <Modifier None None>>
<music21.figuredBass.notation.Figure 6 Mods: <Modifier None None> hasExt: False>
>>> n1.figures[1]
<music21.figuredBass.notation.Figure 4 <Modifier + sharp>>
<music21.figuredBass.notation.Figure 4 Mods: <Modifier + sharp> hasExt: False>
>>> n1.figures[2]
<music21.figuredBass.notation.Figure 2 <Modifier None None>>
<music21.figuredBass.notation.Figure 2 Mods: <Modifier None None> hasExt: False>


Here, a stand-alone '#' is being passed to Notation.
Expand All @@ -121,9 +143,9 @@ class Notation(prebase.ProtoM21Object):
(<music21.figuredBass.notation.Modifier None None>,
<music21.figuredBass.notation.Modifier # sharp>)
>>> n2.figures[0]
<music21.figuredBass.notation.Figure 5 <Modifier None None>>
<music21.figuredBass.notation.Figure 5 Mods: <Modifier None None> hasExt: False>
>>> n2.figures[1]
<music21.figuredBass.notation.Figure 3 <Modifier # sharp>>
<music21.figuredBass.notation.Figure 3 Mods: <Modifier # sharp> hasExt: False>


Now, a stand-alone b is being passed to Notation as part of a larger notationColumn.
Expand All @@ -136,9 +158,9 @@ class Notation(prebase.ProtoM21Object):
(<music21.figuredBass.notation.Modifier b flat>,
<music21.figuredBass.notation.Modifier b flat>)
>>> n3.figures[0]
<music21.figuredBass.notation.Figure 6 <Modifier b flat>>
<music21.figuredBass.notation.Figure 6 Mods: <Modifier b flat> hasExt: False>
>>> n3.figures[1]
<music21.figuredBass.notation.Figure 3 <Modifier b flat>>
<music21.figuredBass.notation.Figure 3 Mods: <Modifier b flat> hasExt: False>
'''
_DOC_ORDER = ['notationColumn', 'figureStrings', 'numbers', 'modifiers',
'figures', 'origNumbers', 'origModStrings', 'modifierStrings']
Expand Down Expand Up @@ -189,12 +211,14 @@ def __init__(self, notationColumn=None):
self.origModStrings = None
self.numbers = None
self.modifierStrings = None
self.hasExtenders = False
self._parseNotationColumn()
self._translateToLonghand()

# Convert to convenient notation
self.modifiers = None
self.figures = None
self.figuresFromNotationColumn = None
self._getModifiers()
self._getFigures()

Expand Down Expand Up @@ -227,8 +251,8 @@ def _parseNotationColumn(self):
'''
delimiter = '[,]'
figures = re.split(delimiter, self.notationColumn)
patternA1 = '([0-9]*)'
patternA2 = '([^0-9]*)'
patternA1 = '([0-9_]*)'
patternA2 = '([^0-9_]*)'
numbers = []
modifierStrings = []
figureStrings = []
Expand All @@ -248,7 +272,11 @@ def _parseNotationColumn(self):
number = None
modifierString = None
if m1:
number = int(m1[0].strip())
if '_' in m1:
self.hasExtenders = True
number = '_'
else:
number = int(m1[0].strip())
if m2:
modifierString = m2[0].strip()

Expand Down Expand Up @@ -357,9 +385,9 @@ def _getFigures(self):
>>> from music21.figuredBass import notation as n
>>> notation2 = n.Notation('-6,-') #__init__ method calls _getFigures()
>>> notation2.figures[0]
<music21.figuredBass.notation.Figure 6 <Modifier - flat>>
<music21.figuredBass.notation.Figure 6 Mods: <Modifier - flat> hasExt: False>
>>> notation2.figures[1]
<music21.figuredBass.notation.Figure 3 <Modifier - flat>>
<music21.figuredBass.notation.Figure 3 Mods: <Modifier - flat> hasExt: False>
'''
figures = []

Expand All @@ -371,6 +399,14 @@ def _getFigures(self):

self.figures = figures

figuresFromNotaCol = []

for i, number in enumerate(self.origNumbers):
modifierString = self.origModStrings[i]
figure = Figure(number, modifierString)
figuresFromNotaCol.append(figure)

self.figuresFromNotationColumn = figuresFromNotaCol

class NotationException(exceptions21.Music21Exception):
pass
Expand All @@ -387,7 +423,7 @@ class Figure(prebase.ProtoM21Object):
>>> from music21.figuredBass import notation
>>> f1 = notation.Figure(4, '+')
>>> f1
<music21.figuredBass.notation.Figure 4 <Modifier + sharp>>
<music21.figuredBass.notation.Figure 4 Mods: <Modifier + sharp> hasExt: False>

>>> f1.number
4
Expand Down Expand Up @@ -416,10 +452,12 @@ def __init__(self, number=1, modifierString=None):
self.number = number
self.modifierString = modifierString
self.modifier = Modifier(modifierString)
# look for extenders underscore
self.isExtender: bool = (self.number == '_')

def _reprInternal(self):
mod = repr(self.modifier).replace('music21.figuredBass.notation.', '')
return f'{self.number} {mod}'
return f'{self.number} Mods: {mod} hasExt: {self.isExtender}'


# ------------------------------------------------------------------------------
Expand All @@ -433,6 +471,10 @@ def _reprInternal(self):
'++': '##',
'+++': '###',
'++++': '####',
'\u266f': '#',
'\u266e': 'n',
'\u266d': 'b',
'\u20e5': '#'
}


Expand Down
28 changes: 28 additions & 0 deletions music21/harmony.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from music21 import environment
from music21 import exceptions21
from music21.figuredBass import realizerScale
from music21.figuredBass import notation
from music21 import interval
from music21 import key
from music21 import pitch
Expand Down Expand Up @@ -2492,6 +2493,33 @@ def transpose(self: NCT, _value, *, inPlace=False) -> NCT | None:

# ------------------------------------------------------------------------------

class FiguredBassIndication(Harmony):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering what you think we should do about the existing figure property, which as of now will give:

>>> fbi = harmony.FiguredBassIndication(root='D', figure='643')
>>> fbi
<FiguredBassIndication figures: >
>>> fbi.figure
'643'

isFigure = True
tstamp = 1
Comment on lines +2504 to +2505
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are class attributes, won't each instance contaminate the the others when attempting to set them? Also, can you explain what tstamp is needed for? (Can we just use .duration and writeAsChord=False?)

def __init__(self, figs: str | list | None = None, **keywords):
super().__init__(**keywords)
if figs:
if isinstance(figs, list):
_figs: str = ','.join(figs)
elif isinstance(figs, str):
if ',' in figs:
_figs = figs
else:
_figs = ','.join(figs)
else:
_figs = ''
self._fig_notation = notation.Notation(_figs)

@property
def fig_notation(self) -> notation.Notation:
return self._fig_notation

@fig_notation.setter
def fig_notation(self, figs):
self._fig_notation = notation.Notation(figs)

def __repr__(self):
return f'<{self.__class__.__name__} figures: {self.fig_notation.notationColumn}>'

def realizeChordSymbolDurations(piece):
'''
Expand Down
82 changes: 82 additions & 0 deletions music21/mei/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
from music21 import stream
from music21 import spanner
from music21 import tie
from music21 import harmony


if t.TYPE_CHECKING:
Expand Down Expand Up @@ -466,6 +467,17 @@ def allPartsPresent(scoreElem) -> tuple[str, ...]:
partNs.append(staffDef.get('n'))
if not partNs:
raise MeiValidityError(_SEEMINGLY_NO_PARTS)

# Get information of possible <harm> tags in the score. If there are tags prepare a list to
# store them and process them later.
# TODO: Maybe to be put in a separate function e.g. like allPartsPresent
figuredBassQuery = f'.//{MEI_NS}fb'
if scoreElem.findall(figuredBassQuery):
environLocal.printDebug('harm tag found!')
partNs.append('fb')
# here other <harm> elements (e.g. chordsymbols) can be added.
# … 'if …'

return tuple(partNs)


Expand Down Expand Up @@ -2493,6 +2505,48 @@ def chordFromElement(elem, slurBundle=None):

return theChord

def harmFromElement(elem, slurBundle=None) -> tuple:
# other tags than <fb> to be added…
tagToFunction = {f'{MEI_NS}fb': figuredbassFromElement}

fb_harmony_tag: tuple = ()

# Collect all elements in a measure and go throug extenders
# tstamp has to be used as a duration marker between two elements

for subElement in _processEmbeddedElements(elem.findall('*'),
tagToFunction,
elem.tag, slurBundle):
subElement.tstamp = float(elem.get('tstamp'))
subElement.offset = subElement.tstamp - 1
fb_harmony_tag = (subElement.tstamp - 1, subElement)

return fb_harmony_tag

def figuredbassFromElement(elem, slurBundle=None) -> harmony.FiguredBassIndication:
if elem.get(_XMLID):
fb_id = elem.get(_XMLID)
fb_notation: str = ''
fb_notation_list: list[str] = []
dauer: float = 0
# loop through all child elements and collect <f> tags
for subElement in elem.findall('*'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to loop through all of the elements -- you can just do an xquery to find just the <f> elements and return a list of those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like elem.findall('f'{MEI_NS}f'')? Then the first if statement is not necessary. I think I did this to be able to grab other harmony tags here in the future.

if subElement.tag == f'{MEI_NS}f':
if subElement.text is not None:
fb_notation_list.append(subElement.text)
else:
if 'extender' in subElement.attrib.keys():
fb_notation_list.append('_')
if 'dur.metrical' in subElement.attrib.keys():
dauer = float(subElement.attrib['dur.metrical'])

# Generate a FiguredBassIndication object and set the collected information
fb_notation = ','.join(fb_notation_list)
theFbNotation = harmony.FiguredBassIndication(fb_notation)
theFbNotation.id = fb_id
theFbNotation.duration = duration.Duration(quarterLength=dauer)

return theFbNotation

def clefFromElement(elem, slurBundle=None): # pylint: disable=unused-argument
'''
Expand Down Expand Up @@ -3131,10 +3185,12 @@ def measureFromElement(elem, backupNum, expectedNs, slurBundle=None, activeMeter
'''
staves = {}
stavesWaiting = {} # for staff-specific objects processed before the corresponding staff
harmElements: dict = {'fb': []}

# mapping from tag name to our converter function
staffTag = f'{MEI_NS}staff'
staffDefTag = f'{MEI_NS}staffDef'
harmTag = f'{MEI_NS}harm'

# track the bar's duration
maxBarDuration = None
Expand All @@ -3154,6 +3210,10 @@ def measureFromElement(elem, backupNum, expectedNs, slurBundle=None, activeMeter
environLocal.warn(_UNIMPLEMENTED_IMPORT.format('<staffDef>', '@n'))
else:
stavesWaiting[whichN] = staffDefFromElement(eachElem, slurBundle)
elif harmTag == eachElem.tag:
# get all information stored in <harm> tags
harmElements['fb'].append(harmFromElement(eachElem))

elif eachElem.tag not in _IGNORE_UNPROCESSED:
environLocal.printDebug(_UNPROCESSED_SUBELEMENT.format(eachElem.tag, elem.tag))

Expand All @@ -3166,6 +3226,10 @@ def measureFromElement(elem, backupNum, expectedNs, slurBundle=None, activeMeter
# *start* of the <measure> in which it appears.
staves[whichN].insert(0, eachObj)

# Add <harm> objects to the staves dict
staves['fb'] = harmElements
# other childs of <harm> tags can be added here…

# create rest-filled measures for expected parts that had no <staff> tag in this <measure>
for eachN in expectedNs:
if eachN not in staves:
Expand Down Expand Up @@ -3485,6 +3549,9 @@ def scoreFromElement(elem, slurBundle):

# Get a tuple of all the @n attributes for the <staff> tags in this score. Each <staff> tag
# corresponds to what will be a music21 Part.

# UPDATE: If <harm> tags are found, they will also be collected as a separate 'part'
# to process them later.
allPartNs = allPartsPresent(elem)

# This is the actual processing.
Expand All @@ -3494,6 +3561,14 @@ def scoreFromElement(elem, slurBundle):
# We must iterate here over "allPartNs," which preserves the part-order found in the MEI
# document. Iterating the keys in "parsed" would not preserve the order.
environLocal.printDebug('*** making the Score')

# Extract collected <harm> information stored in the dict unter the 'fb' key
harms: list[dict] | None = None
if 'fb' in parsed.keys():
harms = parsed['fb'][0]
del parsed['fb']
allPartNs = allPartNs[0:-1]

theScore = [stream.Part() for _ in range(len(allPartNs))]
for i, eachN in enumerate(allPartNs):
# set "atSoundingPitch" so transposition works
Expand All @@ -3502,6 +3577,13 @@ def scoreFromElement(elem, slurBundle):
theScore[i].append(eachObj)
theScore = stream.Score(theScore)

# loop through measures to insert harm elements from harms list at the right offsets
if harms:
for index, measureOffset in enumerate(theScore.measureOffsetMap().keys()):
hms = harms[index]['fb']
for h in hms:
theScore.insert(measureOffset + h[0], h[1])

# put slurs in the Score
theScore.append(list(slurBundle))
# TODO: when all the Slur objects are at the end, they'll only be outputted properly if the
Expand Down
Loading