From b5f0f74d88d45645f4fe207c8fde1371804afd80 Mon Sep 17 00:00:00 2001 From: "Nicholas H." Date: Mon, 22 Jul 2024 20:17:49 -0400 Subject: [PATCH] Add dotted notes and make tests work cross-platform (#12) * 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 --- mmp_to_musicxml/converter.py | 33 +++++-- mmp_to_musicxml/tests/test_key_signatures.py | 98 ++++++++------------ readme.md | 6 +- 3 files changed, 64 insertions(+), 73 deletions(-) diff --git a/mmp_to_musicxml/converter.py b/mmp_to_musicxml/converter.py index 55443dc..6b844b8 100644 --- a/mmp_to_musicxml/converter.py +++ b/mmp_to_musicxml/converter.py @@ -1,4 +1,5 @@ import logging +import os import sys import xml.etree.ElementTree as ET @@ -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, @@ -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", @@ -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: @@ -620,8 +629,10 @@ 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 @@ -629,9 +640,9 @@ def convert_file(self, filepath: str): 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() @@ -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('\n') @@ -937,3 +948,7 @@ def convert_file(self, filepath: str): data = minidom.parseString(ET.tostring(score_partwise, encoding="unicode")).toprettyxml(indent=" ") data = data.replace("", "") # 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) diff --git a/mmp_to_musicxml/tests/test_key_signatures.py b/mmp_to_musicxml/tests/test_key_signatures.py index b0ff248..c1d3c9a 100644 --- a/mmp_to_musicxml/tests/test_key_signatures.py +++ b/mmp_to_musicxml/tests/test_key_signatures.py @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/readme.md b/readme.md index 323a5c8..3022c95 100644 --- a/readme.md +++ b/readme.md @@ -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`!