Skip to content
This repository has been archived by the owner on Apr 6, 2022. It is now read-only.

Commit

Permalink
Merge pull request #30 from DDMAL/dev
Browse files Browse the repository at this point in the history
Beta 3
  • Loading branch information
raviraina authored May 19, 2021
2 parents cd3f5e0 + 3913869 commit e40e020
Show file tree
Hide file tree
Showing 46 changed files with 7,544 additions and 88 deletions.
48 changes: 39 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,58 @@ MEI2Volpiano requires at least Python 3.6.

As long as you're in the python environment, you can execute `mei2volpiano` or the shorthand `mei2vol` while in your python virtual environment

### Flags

| Flag | Use |
| ------------- |:-------------:|
| `-W` or `-N` | Used to specify the type of MEI to converted (Neume or CWN) |
| `-t txt` or `-t mei`| Used to specify whether the user is inputtng MEI files or a text file containing MEI paths |
| `--export` | Signifies that the converted Volpiano string(s) should be outputted to '.txt' files |

### Standard Usage (Neume notation)

To output the MEI file's volpiano string to the terminal, run

`mei2vol -mei filename1.mei`
`mei2vol -N -t mei filename1.mei`

Multiple files can be passed in at once

`mei2vol -mei filename1.mei filename2.mei`
`mei2vol -N -t mei filename1.mei filename2.mei`

To output the volpiano string(s) to a text file, use the `-export` flag as such
### Western

`mei2vol -mei filename1.mei -export`
To convert MEI files written in Common Western Music Notation (CWMN), run

and the program will output each mei file's volpiano to a similarly named file as its input.
`mei2vol -W -t mei filename1.mei`

All of the CWMN files processed by this library (so far) come from [this collection](https://github.com/DDMAL/Andrew-Hughes-Chant/tree/master/file_structure_text_file_MEI_file). Thus, we followed the conventions of those files. Namely:

- Every neume is encoded as a quarter note
- Stemless notes
- Syllables are preceded by their notes
- All notes must have syllables after them

The resulting volpiano string will have multiple notes seperated by two hyphens. This seperation is dictated by the syllables, representented by: `<syl>`. The notes themselves are located with the `<note>` tag and represented by the `pname` attribute.

To make it easier to pass in multiple MEI files, the `-txt` file can be used
### Mutiple MEI File Runs

`mei2vol -txt filename1.txt`
To make it easier to pass in multiple MEI files, the `-t` flag can be specified as `txt`:

`mei2vol -t txt filename1.txt` or `mei2vol -t txt filename1.txt filename2.txt ...`

where the ".txt" file being passed in must hold the name/relative path of the required MEI files on distinct lines.

The `-export` tag can be used on any valid input to the program. `-mei` or `-txt` are required flags for the program to identify the file(s) you are attempting to input.
**Note: If passing inputs through this method, the formats of the MEI files within the text file must be of the same type** (either neume for `-N` or western for `-W`)

### Exporting

The `--export` tag can be used on any valid input to the program. Simply tack it on to the end of your command like so

`mei2vol -N -t mei filename1.mei --export`

and the program will output each mei file's volpiano to a similarly named file as its input.


## Tests

To run the current test suite, execute `pytest`
To run the current test suite, execute `pytest`
117 changes: 84 additions & 33 deletions mei2volpiano/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,123 @@
See README for details.
"""


import os.path
import argparse
import mei2volpiano
from timeit import default_timer as timer


# driver code for CLI program
def main():
"""
This is the command line application MEI2Volpiano
usage: mei2vol [-h] (-mei MEI [MEI ...] | -txt [TXT]) [-export]
mei2vol: error: one of the arguments -mei -txt is required
usage: driver.py [-h] (-N | -W) -t [T] [--export] mei [mei ...]
positional arguments:
mei One or multiple MEI, or text file(s) with each relative MEI file/path to be converted per line
optional arguments:
-h, --help show this help message and exit
-N An MEI neume encoded music file representing neume notation
-W An MEI western encoded music file representing western notation
-t [T] Flag indicating whether the inputs will be mei or txt files
--export flag indicating output to be sent to a .txt file (name corresponding with input mei)
"""
start = timer()
parser = argparse.ArgumentParser()
option = parser.add_mutually_exclusive_group(required=True)

# check the validity of file(s) being passed into program
def check_file_validity(fname, valid_ext):
ext = os.path.splitext(fname)[1][1:]
if ext != valid_ext:
parser.error(f'Unexpected file type for the specified flag\nInput Type: {ext} \nExpected Type: {valid_ext}')
return fname

if isinstance(valid_ext, list):
if fname in valid_ext:
return fname
else:
print(fname)
parser.error(
"Invalid choice for type -t. Please choose from 'mei' or 'txt'"
)
else:
ext = os.path.splitext(fname)[1][1:]
if ext != valid_ext:
parser.error(
f"Unexpected file type for the specified flag\nInput Type: {ext} \nExpected Type: {valid_ext}"
)
return fname

# options for either neume or CWN
option.add_argument(
"-mei",
type=lambda fname: check_file_validity(fname, "mei"),
nargs="+",
help="An MEI encoded music file",
"-N",
action="store_true",
help="An MEI neume encoded music file representing neume notation",
)

option.add_argument(
"-txt",
"-W",
action="store_true",
help="An MEI western encoded music file representing western notation",
)

parser.add_argument(
"-t",
nargs="?",
type=lambda fname: check_file_validity(fname, "txt"),
help="A text file with each MEI file/path to be converted per line",
required=True,
type=lambda fname: check_file_validity(fname, ["txt", "mei"]),
help="Flag indicating whether the inputs will be mei or txt files",
)

parser.add_argument(
"mei",
nargs="+",
# type=lambda fname: check_file_validity(fname, "txt"),
help="One or multiple MEI, or text file(s) with each relative MEI file/path to be converted per line",
)

parser.add_argument(
"-export",
"--export",
action="store_true",
help="flag output converted volpiano to a .txt file (name corresponding with input)",
help="flag indicating output to be sent to a .txt file (name corresponding with input mei)",
)

args = vars(parser.parse_args()) # stores each positional input in dict

lib = mei2volpiano.MEItoVolpiano()
vol_strings = []
f_names = []

if args["mei"] and args["txt"]:
parser.error('Cannot use both "-mei" and "-txt" simultaneously')

if args["txt"] is not None:
txt_file = open(args["txt"])
for mei_file in txt_file:
f_names.append(mei_file.strip())
vol_strings.append(lib.convert_mei_volpiano(mei_file.strip()))

if args["mei"] is not None:
for mei_file in args["mei"]:
with open(mei_file, "r") as f:
f_names.append(mei_file)
vol_strings.append(lib.convert_mei_volpiano(f))
# verify each file input matches (no mismatch extensions)
ftype = None
for pos_args in args["mei"]:
if not ftype:
ftype = os.path.splitext(pos_args)[1][1:]
else:
check_file_validity(pos_args, ftype)

if args["W"]:
if args["t"] == "mei":
for mei_file in args["mei"]:
with open(mei_file, "r") as f:
f_names.append(mei_file)
vol_strings.append(lib.Wconvert_mei_volpiano(f))
if args["t"] == "txt":
for txt_file in args["mei"]:
txt_file = open(txt_file, "r")
for mei_file in txt_file:
f_names.append(mei_file.strip())
vol_strings.append(lib.Wconvert_mei_volpiano(mei_file.strip()))

if args["N"]:
if args["t"] == "mei":
for mei_file in args["mei"]:
with open(mei_file, "r") as f:
f_names.append(mei_file)
vol_strings.append(lib.convert_mei_volpiano(f))
if args["t"] == "txt":
for txt_file in args["mei"]:
txt_file = open(txt_file, "r")
for mei_file in txt_file:
f_names.append(mei_file.strip())
vol_strings.append(lib.convert_mei_volpiano(mei_file.strip()))

name_vol_pairs = list(zip(f_names, vol_strings))

Expand All @@ -81,7 +132,7 @@ def check_file_validity(fname, valid_ext):
out.write(pair[1])

for pair in name_vol_pairs:
print( f"\nThe corresponding Volpiano string for {pair[0]} is: \n{pair[1]}\n")
print(f"\nThe corresponding Volpiano string for {pair[0]} is: \n{pair[1]}\n")

# testing time
elapsed_time = timer() - start
Expand Down
114 changes: 77 additions & 37 deletions mei2volpiano/mei2volpiano.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class MEItoVolpiano:
^ convert_mei_volpiano handles all methods in main.
[Western]:
Wsylb_volpiano_map(list[elements]) -> dict[str, str]
Wconvert_mei_volpiano(file) -> str
^ Wconvert_mei_volpiano calls methods in Main to give
the volpiano string for MEI files written in Western notation.
[Debugging]:
find_clefs(list[elements]) -> list[str]
find_notes(list[elements]) -> list[str]
Expand All @@ -48,10 +55,8 @@ def get_mei_elements(self, filename: str) -> list:
tree = ET.parse(filename)
root = tree.getroot()
mei_element_objects = root.findall(".//")
elements = []
for mei_element in mei_element_objects:
elements.append(mei_element)
return elements

return [mei_element for mei_element in mei_element_objects]

def find_clefs(self, elements: list) -> list:
"""Finds all clefs in a given elements list
Expand Down Expand Up @@ -186,6 +191,41 @@ def sylb_volpiano_map(self, elements: list) -> dict:

return syl_note

def Wsylb_volpiano_map(self, elements: list) -> dict:
"""Western notation - Creates a dictionary of syllables and their volpiano values.
Args:
elements (list): List of elements
Returns:
syl_note (dict): Dictionary {identifier: volpiano notes} of
syllables and their unique data base numbers as keys and volpiano
notes with correct octaves as values.
"""
syl_note = {"dummy": ""}
dbase_bias = 0
last = "dummy"
num = True
for element in elements:
if element.tag == f"{NAMESPACE}syl":
key = self.get_syl_key(element, dbase_bias)
if num:
syl_note[key] = f"{syl_note[last]}"
num = False
else:
syl_note[key] = f'{"--"}{syl_note[last]}'
dbase_bias += 1
syl_note["dummy"] = ""
last = "dummy"
if element.tag == f"{NAMESPACE}note":
note = element.attrib["pname"]
ocv = element.attrib["oct"]
ocv = int(ocv) - 2
ocv = f"{ocv}"
volpiano = self.get_volpiano(note, ocv)
syl_note[last] = f"{syl_note[last]}{volpiano}"
return syl_note

def get_syl_key(self, element: object, bias: int) -> str:
"""Finds the dictionary key of a syllable from their 'syl' and database
identifier.
Expand Down Expand Up @@ -222,39 +262,24 @@ def get_volpiano(self, note: str, ocv: str) -> str:
octave.
"""
oct1 = {"g": "9", "a": "a", "b": "b"}
oct2 = {"c": "c", "d": "d", "e": "e", "f": "f", "g": "g", "a": "h", "b": "j"}
oct3 = {"c": "k", "d": "l", "e": "m", "f": "n", "g": "o", "a": "p", "b": "q"}
oct4 = {"c": "r", "d": "s"}

error = "OCTAVE_ERROR"

if ocv == "1":
if note in oct1:
return oct1[note]
else:
error = "NOTE_NOT_IN_OCTAVE"
return error
elif ocv == "2":
if note in oct2:
return oct2[note]
else:
error = "NOTE_NOT_IN_OCTAVE"
return error
elif ocv == "3":
if note in oct3:
return oct3[note]
else:
error = "NOTE_NOT_IN_OCTAVE"
return error
elif ocv == "4":
if note in oct4:
return oct4[note]
else:
error = "NOTE_NOT_IN_OCTAVE"
return error
else:
return error
octs = {
"1": {"g": "9", "a": "a", "b": "b"},
"2": {"c": "c", "d": "d", "e": "e", "f": "f", "g": "g", "a": "h", "b": "j"},
"3": {"c": "k", "d": "l", "e": "m", "f": "n", "g": "o", "a": "p", "b": "q"},
"4": {"c": "r", "d": "s"},
}

oct_error = "OCTAVE_RANGE_ERROR"
note_error = "NOTE_NOT_IN_OCTAVE"

for key in octs:
if key == ocv:
if note in octs[key]:
return octs[key][note]
else:
return note_error

return oct_error

def export_volpiano(self, mapping_dictionary: dict) -> str:
"""Creates volpiano string with clef attached.
Expand Down Expand Up @@ -284,3 +309,18 @@ def convert_mei_volpiano(self, filename: str) -> str:
mapped_values = self.sylb_volpiano_map(elements)
volpiano = self.export_volpiano(mapped_values)
return volpiano

def Wconvert_mei_volpiano(self, filename: str) -> str:
"""All-in-one method for converting MEI in Western notation to volpiano.
Args:
filename (file): Open MEI file you want the volpiano of.
Returns:
volpiano (str): Valid volpiano string representation of the input.
"""

elements = self.get_mei_elements(filename)
mapped_values = self.Wsylb_volpiano_map(elements)
volpiano = self.export_volpiano(mapped_values)
return volpiano
Binary file added resources/.DS_Store
Binary file not shown.
Binary file added resources/neume_mei/.DS_Store
Binary file not shown.
File renamed without changes.
Loading

0 comments on commit e40e020

Please sign in to comment.