Skip to content

Commit

Permalink
Add dotted notes and make tests work cross-platform (#12)
Browse files Browse the repository at this point in the history
* now takes into account dotted notes! (eighth, quarter, and half) :D. and trying to make tests work cross-platform. also made a few edits like modify some variable names and finally close the musicxml file after writing.

* update readme

* readme edit
  • Loading branch information
syncopika authored Jul 23, 2024
1 parent 1752988 commit b5f0f74
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 73 deletions.
33 changes: 24 additions & 9 deletions mmp_to_musicxml/converter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import os
import sys
import xml.etree.ElementTree as ET

Expand Down Expand Up @@ -132,8 +133,11 @@ class MMP_MusicXML_Converter:
# these numbers are based on note lengths in LMMS.
NOTE_TYPE = {
"whole": 192,
"half-dotted": 144,
"half": 96,
"quarter-dotted": 72,
"quarter": 48,
"eighth-dotted": 36,
"eighth": 24,
"16th": 12,
"32nd": 6,
Expand All @@ -147,11 +151,11 @@ class MMP_MusicXML_Converter:
NOTE_LENGTHS = {
192: "whole",
168: "half",
144: "half",
144: "half-dotted",
96: "half",
72: "quarter",
72: "quarter-dotted",
48: "quarter",
36: "eighth",
36: "eighth-dotted",
24: "eighth",
12: "16th",
6: "32nd",
Expand Down Expand Up @@ -336,7 +340,12 @@ def add_note(self, parent_node: ET.Element, note: ET.Element, is_chord=False, le

# need to identify the note type
new_type = ET.SubElement(new_note, "type")
new_type.text = self.find_closest_note_type(note_length)
new_type.text = self.find_closest_note_type(note_length).split("-")[0]

# add dot element if a dotted note
if "dotted" in self.find_closest_note_type(note_length):
dot = ET.SubElement(new_note, "dot")

return new_note

def add_rest(self, parent_node: ET.Element, rest_type: str) -> ET.Element:
Expand Down Expand Up @@ -620,18 +629,20 @@ def create_length_table(self, notes: List[ET.Element]) -> dict:

return length_table

def convert_file(self, filepath: str):
def convert_file(self, filepath: str) -> str:
"""Does the converting from .mmp to MusicXML.
Returns the path of the new MusicXML file.
"""
file = filepath

if ".mmp" not in file:
raise ValueError("not an .mmp file!")

# extract just the file's name
lastSlashIndex = file.rfind("/")
extensionIndex = file.rfind(".mmp")
outputFileName = file[(lastSlashIndex+1):extensionIndex]
last_slash_index = file.rfind("/")
extension_index = file.rfind(".mmp")
output_file_name = file[(last_slash_index+1):extension_index]

tree = ET.parse(file)
root = tree.getroot()
Expand All @@ -657,7 +668,7 @@ def convert_file(self, filepath: str):
#logging.debug("Duration of a measure (with 32nd notes): " + str(int(TIME_SIGNATURE_NUMERATOR) * int(NUM_DIVISIONS)))

# write a new xml file
new_file = open(outputFileName + ".xml", "w")
new_file = open(output_file_name + ".xml", "w")

# add the appropriate headers first
new_file.write('<?xml version="1.0" encoding="UTF-8"?>\n')
Expand Down Expand Up @@ -937,3 +948,7 @@ def convert_file(self, filepath: str):
data = minidom.parseString(ET.tostring(score_partwise, encoding="unicode")).toprettyxml(indent=" ")
data = data.replace("<?xml version=\"1.0\" ?>", "") # toprettyxml adds a xml declaration, but I have it already written to the file
new_file.write(data)

new_file.close()

return os.path.realpath(new_file.name)
98 changes: 37 additions & 61 deletions mmp_to_musicxml/tests/test_key_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,20 @@
def test_a():
converter = MMP_MusicXML_Converter(key_signature='a')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\a.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\a.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\a.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'a.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'a.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

os.remove(output)

def test_d():
converter = MMP_MusicXML_Converter(key_signature='d')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\d.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\d.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\d.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'd.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'd.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -33,11 +29,9 @@ def test_d():
def test_gb():
converter = MMP_MusicXML_Converter(key_signature='gb')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\gb.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\gb.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\gb.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'gb.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'gb.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -46,11 +40,9 @@ def test_gb():
def test_cb():
converter = MMP_MusicXML_Converter(key_signature='cb')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\cb.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\cb.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\cb.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'cb.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'cb.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -59,11 +51,9 @@ def test_cb():
def test_gb_chromatic():
converter = MMP_MusicXML_Converter(key_signature='gb')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\gb-chromatic.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\gb-chromatic.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\gb-chromatic.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'gb-chromatic.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'gb-chromatic.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -72,11 +62,9 @@ def test_gb_chromatic():
def test_f_chromatic():
converter = MMP_MusicXML_Converter(key_signature='f')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\f-chromatic.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\f-chromatic.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\f-chromatic.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'f-chromatic.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'f-chromatic.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -85,11 +73,9 @@ def test_f_chromatic():
def test_cs():
converter = MMP_MusicXML_Converter(key_signature='cs')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\cs.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\cs.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\cs.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'cs.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'cs.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -98,11 +84,9 @@ def test_cs():
def test_fs():
converter = MMP_MusicXML_Converter(key_signature='fs')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\fs.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\fs.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\fs.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'fs.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'fs.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -111,11 +95,9 @@ def test_fs():
def test_fsm():
converter = MMP_MusicXML_Converter(key_signature='a')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\fsm.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\fsm.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\fsm.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'fsm.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'fsm.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -124,11 +106,9 @@ def test_fsm():
def test_asm():
converter = MMP_MusicXML_Converter(key_signature='cs')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\asm.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\asm.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\asm.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'asm.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'asm.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -137,11 +117,9 @@ def test_asm():
def test_gsm():
converter = MMP_MusicXML_Converter(key_signature='b')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\gsm.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\gsm.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\gsm.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'gsm.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'gsm.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand All @@ -150,11 +128,9 @@ def test_gsm():
def test_g_melodic_minor():
converter = MMP_MusicXML_Converter(key_signature='bb')

testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig\\g-minor-melodic.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig/expected_output\\g-minor-melodic.xml')
output = os.path.join(os.path.dirname(__file__), 'test_key_sig\\g-minor-melodic.xml')

converter.convert_file(testfile)
testfile = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'g-minor-melodic.mmp')
testfile_expected_output = os.path.join(os.path.dirname(__file__), 'test_key_sig', 'expected_output', 'g-minor-melodic.xml')
output = converter.convert_file(testfile)

assert filecmp.cmp(output, testfile_expected_output, shallow=False) is True

Expand Down
6 changes: 3 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ The idea is to help provide significant time savings in getting your music from
Try it in the browser! https://syncopika.github.io/mmp-to-MusicXML/

### USAGE:
Run `python convert-mmp.py [file path to an .mmp file]` or import the module into another script and use it there. There are a couple optional arguments you can supply to check if certain instrument notes fall out of the expected range (via `-c`) or to set the key signature of the piece (via `-k`). See `python convert-mmp.py -h` for more details.
Run `python convert-mmp.py [file path to an .mmp file]` or import the module into another script and use it there. There are a few optional arguments you can supply, e.g. to check if certain instrument notes fall out of the expected range (via `-c`) or to set the key signature of the piece (via `-k`). See `python convert-mmp.py -h` for more details and options.

The output will be named whatever the file's name is as an xml file in the same directory. You can then use MuseScore to view it. I've not tested with other notation software.

some things to note as of now:
- the smallest note type the script can understand is a 64th note, so anything smaller will break things
- no tied and/or dotted notes
- no tied notes (and currently only eighth, quarter and half dotted notes are supported)
- can't identify intended triplets
- notes that extend past a measure are truncated to fit in the measure they start in
- I've specified some instruments for the program to identify based on the track names - i.e. flute, piano, clarinet since I work with a lot of those instrument soundfonts. I should extend this to accept TripleOscillator tracks, for example, though as well.
- I've specified some instruments for the program to identify by default based on the track names - i.e. flute, piano, clarinet since I work with a lot of those instrument soundfonts.
- Additionally, I've added a rudimentary note checking feature that'll evaluate certain instruments' notes (see `mmp_to_musicxml/utils/note_checker.py`) and output warnings for any notes that don't fall in the traditional range.

You can try out the script with the included test .mmp files, or check out some of my results in `/example_output`!
Expand Down

0 comments on commit b5f0f74

Please sign in to comment.