From 19cf16472c408a37a15bbd344a9e6d0d644549f2 Mon Sep 17 00:00:00 2001 From: melkisedeath <manoskaristineos@gmail.com> Date: Tue, 3 Dec 2024 12:31:20 +0100 Subject: [PATCH] Removed rntxt refs. --- partitura/__init__.py | 1 - partitura/io/__init__.py | 1 - partitura/io/importrntxt.py | 236 ------------------------------------ tests/test_rntxt.py | 39 ------ 4 files changed, 277 deletions(-) delete mode 100644 partitura/io/importrntxt.py delete mode 100644 tests/test_rntxt.py diff --git a/partitura/__init__.py b/partitura/__init__.py index 19da628d..c9efb295 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -14,7 +14,6 @@ from .io.exportmusicxml import save_musicxml from .io.importmei import load_mei from .io.importkern import load_kern -from .io.importrntxt import load_rntxt from .io.importmusic21 import load_music21 from .io.importdcml import load_dcml from .io.importmidi import load_score_midi, load_performance_midi, midi_to_notearray diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index 11d337fd..9c3bacb3 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -14,7 +14,6 @@ from .importmatch import load_match from .importmei import load_mei from .importkern import load_kern -from .importrntxt import load_rntxt from .exportkern import save_kern from .importparangonada import load_parangonada_csv from .exportparangonada import save_parangonada_csv diff --git a/partitura/io/importrntxt.py b/partitura/io/importrntxt.py deleted file mode 100644 index 54cebe70..00000000 --- a/partitura/io/importrntxt.py +++ /dev/null @@ -1,236 +0,0 @@ -import re -import partitura.score as spt -import partitura.io as sptio -import os.path as osp -import numpy as np -from urllib.parse import urlparse -import urllib.request -from partitura.utils.music import key_name_to_fifths_mode - - -def load_rntxt(path: spt.Path, part=None, return_part=False): - if sptio.is_url(path): - data = load_data_from_url(path) - lines = data.split("\n") - else: - if not osp.exists(path): - raise FileNotFoundError(f"File not found: {path}") - with open(path, "r") as f: - lines = f.readlines() - assert validate_rntxt(lines) - - # remove empty lines - lines = [line for line in lines if line.strip()] - - parser = RntxtParser(part) - parser.parse(lines) - if return_part or part is None: - return parser.part - return - - -def validate_rntxt(lines): - # TODO: Implement - return True - - -def load_data_from_url(url: str): - with urllib.request.urlopen(url) as response: - data = response.read().decode() - return data - - -class RntxtParser: - """ - A parser for RNtxt format to a partitura Part. - - For full specification of the format visit: - https://github.com/MarkGotham/When-in-Rome/blob/master/syntax.md - """ - def __init__(self, score=None): - # Initialize parser state - self.part = spt.Part(id="rn", part_name="Rn", part_abbreviation="rnp") - self.current_measure = None - self.current_position = 0 - self.measure_beat_position = 1 - self.current_time_signature = spt.TimeSignature(4, 4) - self.time_signature_style = 'Normal' # 'Normal', 'Slow', 'Fast' - self.key = 'C' - self.pedal = None - self.metadata = {} - self.measures = {} - # If a score is provided, copy relevant information - if score is not None: - self._initialize_from_score(score) - else: - # Add default staff - self.part.add(spt.Staff(number=1, lines=5), 0) - - def _initialize_from_score(self, score): - # Copy measures, time signatures, and key signatures from the reference score - self.ref_part = score.parts[0] - for measure in self.ref_part.measures: - self.part.add(measure, measure.start.t, measure.end.t) - for time_sig in self.ref_part.time_sigs: - self.part.add(time_sig, time_sig.start.t) - for key_sig in self.ref_part.key_sigs: - self.part.add(key_sig, key_sig.start.t) - self.measures = {m.number: m for m in self.part.measures} - - def parse(self, lines): - for line_num, line in enumerate(lines, 1): - line = line.strip() - if not line or line.startswith('#'): - continue - try: - if ':' in line: - keyword = line.split(':', 1)[0].strip() - if keyword in ('Composer', 'Title', 'Analyst'): - self._handle_metadata(line) - elif keyword == 'Note': - self._handle_note_line(line) - elif keyword == 'Time Signature': - self._handle_time_signature(line) - elif keyword == 'Pedal': - self._handle_pedal(line) - else: - self._handle_line(line) - else: - self._handle_line(line) - except Exception as e: - print(f"Error parsing line {line_num}: {line}") - print(e) - self._calculate_ending_times() - - def _handle_metadata(self, line): - key, value = line.split(':', 1) - self.metadata[key.strip()] = value.strip() - - def _handle_note_line(self, line): - # Notes can be stored or logged as needed - pass - - def _handle_pedal(self, line): - # Parse pedal information - pass - - def _handle_line(self, line): - if re.match(r'm\d+(-\d+)?\s*=', line): - self._handle_repeat(line) - elif line.startswith('m'): - self._handle_measure(line) - else: - raise ValueError(f"Unknown line format: {line}") - - def _handle_repeat(self, line): - # Implement repeat logic - pass - - def _handle_measure(self, line): - elements = line.strip().split() - measure_info = elements[0] - measure_match = re.match(r'm(\d+)(?:-(\d+))?', measure_info) - if not measure_match: - raise ValueError(f"Invalid measure number: {measure_info}") - measure_number = int(measure_match.group(1)) - if measure_number not in self.measures: - # Check if previous measure is there - if measure_number - 1 in self.measures: - previous_measure_start = self.measures[measure_number - 1].start.t - # get the current time signature - current_time_signature_beats = self.current_time_signature.beats - self.current_position = self.part.beat_map( - self.part.inv_beat_map(previous_measure_start) + current_time_signature_beats) - self.current_measure = spt.Measure(number=measure_number) - self.measures[measure_number] = self.current_measure - self.part.add(self.current_measure, self.current_position) - else: - self.current_measure = self.measures[measure_number] - self.current_position = self.current_measure.start.t - self.measure_beat_position = 1 - for element in elements[1:]: - self._handle_element(element) - - def _handle_element(self, element): - if element.startswith('b'): - beat_match = re.match(r'b(\d+(\.\d+)?)', element) - if beat_match: - self.measure_beat_position = float(beat_match.group(1)) - self._update_current_position() - else: - raise ValueError(f"Invalid beat format: {element}") - elif re.match(r'.*:', element): - self._handle_key(element) - elif all(c in "|:" for c in element): - self._handle_barline(element) - else: - self._handle_roman_numeral(element) - - def _update_current_position(self): - self.current_position = self.part.beat_map(self.part.inv_beat_map(self.current_measure.start.t) + self.measure_beat_position) - - def _get_beat_duration(self): - # Calculate beat duration based on the time signature and style - nom, denom = self.current_time_signature.beats, self.current_time_signature.beat_type - quarter_note_duration = 1 # Assuming a quarter note duration of 1 - beat_duration = (4 / denom) * quarter_note_duration - if self.time_signature_style == 'Fast' and nom % 3 == 0 and denom == 8: - beat_duration *= 3 # Compound meter counted in dotted quarters - elif self.time_signature_style == 'Slow' and nom % 3 == 0 and denom == 8: - beat_duration /= 3 # Compound meter counted in eighth notes - return beat_duration - - def _handle_time_signature(self, line): - time_signature = line.split(':', 1)[1].strip() - style = 'Normal' - if 'Slow' in time_signature: - style = 'Slow' - time_signature = time_signature.replace('Slow', '').strip() - elif 'Fast' in time_signature: - style = 'Fast' - time_signature = time_signature.replace('Fast', '').strip() - if time_signature == 'C': - nom, denom = 4, 4 - elif time_signature == 'Cut': - nom, denom = 2, 2 - else: - nom, denom = map(int, time_signature.split('/')) - self.current_time_signature = spt.TimeSignature(nom, denom) - self.time_signature_style = style - self.part.add(self.current_time_signature, self.current_position) - - def _handle_barline(self, element): - # Implement barline handling if needed - pass - - def _handle_roman_numeral(self, element): - try: - rn = spt.RomanNumeral(text=element, local_key=self.key) - self.part.add(rn, self.current_position) - except Exception as e: - raise ValueError(f"Error parsing Roman numeral '{element}': {e}") - - def _handle_key(self, element): - match = re.match(r'([A-Ga-g])([#b]*):', element) - if not match: - raise ValueError(f"Invalid key signature: {element}") - note, accidental = match.groups() - mode = 'minor' if note.islower() else 'major' - key_name = note.upper() + accidental - key_str = f"{key_name}{('m' if mode == 'minor' else '')}" - fifths, mode = key_name_to_fifths_mode(key_str) - ks = spt.KeySignature(fifths=fifths, mode=mode) - self.key = element.strip(":") - self.part.add(ks, self.current_position) - - def _calculate_ending_times(self): - romans = sorted(self.part.iter_all(spt.RomanNumeral), key=lambda rn: rn.start.t) - for i, rn in enumerate(romans[:-1]): - rn.end = spt.TimePoint(t=romans[i + 1].start.t) - if romans: - last_rn = romans[-1] - last_rn.end = self.part.end_time or (last_rn.start.t + 1) - - - - diff --git a/tests/test_rntxt.py b/tests/test_rntxt.py deleted file mode 100644 index fb8569c7..00000000 --- a/tests/test_rntxt.py +++ /dev/null @@ -1,39 +0,0 @@ -from partitura import load_rntxt, load_kern -from partitura.score import RomanNumeral -from partitura import load_musicxml -import urllib.request -import unittest -import os -from tests import KERN_PATH - - -class TextRNtxtImport(unittest.TestCase): - - def test_chorale_001_from_url(self): - score_path = os.path.join(KERN_PATH, "chor228.krn") - rntxt_url = "https://raw.githubusercontent.com/MarkGotham/When-in-Rome/master/Corpus/Early_Choral/Bach%2C_Johann_Sebastian/Chorales/228/analysis.txt" - score = load_kern(score_path) - rn_part = load_rntxt(rntxt_url, score, return_part=True) - romans = list(rn_part.iter_all(RomanNumeral)) - roots = [r.root for r in romans] - bass = [r.bass_note for r in romans] - primary_degree = [r.primary_degree for r in romans] - secondary_degree = [r.secondary_degree for r in romans] - local_key = [r.local_key for r in romans] - quality = [r.quality for r in romans] - inversion = [r.inversion for r in romans] - expected_roots = ['A', 'A', 'E', 'A', 'A', 'G', 'C', 'C', 'G', 'G', 'A', 'A', 'E', 'E', 'E', 'E', 'A', 'D', 'G#', 'A', 'E', 'A', 'D', 'A', 'B'] - expected_bass = ['A', 'A', 'G#', 'A', 'A', 'B', 'C', 'E', 'G', 'G', 'A', 'A', 'E', 'E', 'E', 'D', 'C', 'C', 'B', 'A', 'E', 'A', 'F', 'E', 'D'] - expected_pdegree = ['i', 'i', 'V', 'i', 'vi', 'V', 'I', 'I', 'V', 'V', 'vi', 'i', 'V', 'V', 'V', 'V', 'i', 'IV', 'viio', 'i', 'V', 'i', 'iv', 'i', 'iio'] - expected_sdegree = ['i', 'i', 'i', 'i', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i'] - expected_lkey = ['a', 'a', 'a', 'a', 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] - expected_quality = ['min', 'min', 'maj', 'min', 'min', 'maj', 'maj', 'maj', 'maj', '7', 'min', 'min', 'maj', 'maj', 'maj', '7', 'min', '7', 'dim', 'min', 'maj', 'min', 'min', 'min', 'dim7'] - expected_inversion = [0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 1, 3, 1, 0, 0, 0, 1, 2, 1] - self.assertEqual(roots, expected_roots) - self.assertEqual(bass, expected_bass) - self.assertEqual(primary_degree, expected_pdegree) - self.assertEqual(secondary_degree, expected_sdegree) - self.assertEqual(local_key, expected_lkey) - self.assertEqual(quality, expected_quality) - self.assertEqual(inversion, expected_inversion) -