87 lines
4.6 KiB
Python
87 lines
4.6 KiB
Python
import importlib.util
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
SPEC = importlib.util.spec_from_file_location('abc_tools', ROOT / 'skills/yue2-music/scripts/abc_tools.py')
|
|
a = importlib.util.module_from_spec(SPEC)
|
|
sys.modules[SPEC.name] = a
|
|
SPEC.loader.exec_module(a)
|
|
|
|
def score(vocal, ins='Z|', meter='4/4', key='C', extra=''):
|
|
return ('X:1\nT:\nM:' + meter + '\nL:1/32\nQ:1/4=88\n'
|
|
'V: Vocal clef=treble name="Vocal Melody" snm="Vocal"\n'
|
|
'V: Ins clef=treble name="Ins Melody" snm="Inst."\n'
|
|
'K:' + key + '\n% verse\nV: Vocal\n' + vocal + '\nV: Ins\n' + ins + '\n' + extra)
|
|
|
|
class ABCChecks(unittest.TestCase):
|
|
def test_key_relative_pitch(self):
|
|
self.assertEqual(a.parse(score('F8C8=F8F8|', key='D')).voices['Vocal'].notes,
|
|
[[0,66,1],[1,61,1],[2,65,1],[3,65,1]])
|
|
def test_octave_accidental_propagation(self):
|
|
self.assertEqual([x[1] for x in a.parse(score('^F8f8=F8f8|')).voices['Vocal'].notes], [66,78,65,77])
|
|
def test_bar_accidental_reset(self):
|
|
self.assertEqual([x[1] for x in a.parse(score('^F32|F32|', 'Z2|')).voices['Vocal'].notes],[66,65])
|
|
def test_crossbar_tie(self):
|
|
notes=a.parse(score('^F32-|F8F24|','Z2|')).voices['Vocal'].notes
|
|
self.assertEqual(notes, [[0,66,5],[5,65,3]])
|
|
def test_chord_split_tie(self):
|
|
p=a.parse(score('"C"C16-"Am7"C16|'))
|
|
self.assertEqual(p.voices['Vocal'].notes, [[0,60,4]])
|
|
self.assertEqual(p.voices['Vocal'].chords, [(0,'C'),(2,'Am7')])
|
|
def test_double_accidentals(self):
|
|
self.assertEqual([x[1] for x in a.parse(score('^^F16__B16|')).voices['Vocal'].notes], [67,69])
|
|
def test_minor_key(self):
|
|
self.assertEqual(a.parse(score('F32|',key='F#m')).voices['Vocal'].notes[0][1],66)
|
|
def test_inline_key_changes(self):
|
|
p=a.parse(score('F16[K:D]F16|','z16[K:D]z16|'))
|
|
self.assertEqual([x[1] for x in p.voices['Vocal'].notes],[65,66])
|
|
def test_compound_meter(self):
|
|
self.assertEqual(a.parse(score('C8D8E8|',meter='6/8')).voices['Vocal'].time,3)
|
|
def test_meter_change(self):
|
|
tail='V: Vocal\nM:3/4\nD24|\nV: Ins\nM:3/4\nZ|\n'
|
|
self.assertEqual(a.parse(score('C32|',extra=tail)).voices['Vocal'].time,7)
|
|
def test_compressed_rests(self):
|
|
self.assertEqual(len(a.parse(score('Z4|','Z4|')).voices['Vocal'].bars),4)
|
|
def test_strip_header_preserved(self):
|
|
src=score('"Cmaj7/G"C16"Dbaug"C16|','E32|')
|
|
dest=a.strip_chords(src)
|
|
self.assertIn('name="Vocal Melody"',dest)
|
|
self.assertIn('name="Ins Melody"',dest)
|
|
self.assertTrue(a.compare(a.parse(src),a.parse(dest))['match'])
|
|
self.assertEqual(a.parse(dest).voices['Vocal'].chords,[])
|
|
def test_voice_selection(self):
|
|
src=score('"C"C32|','E32|')
|
|
dest=a.parse(a.strip_chords(src,'Ins'))
|
|
self.assertEqual(dest.voices['Vocal'].notes,[])
|
|
self.assertEqual(dest.voices['Ins'].notes,[[0,64,4]])
|
|
def test_equal_sound_different_ties(self):
|
|
self.assertTrue(a.compare(a.parse(score('C32|')),a.parse(score('C16-C16|')))['match'])
|
|
def test_rearticulation_changes(self):
|
|
self.assertFalse(a.compare(a.parse(score('C32|')),a.parse(score('C16C16|')))['match'])
|
|
def test_onset_changes(self):
|
|
self.assertFalse(a.compare(a.parse(score('C16z16|')),a.parse(score('z16C16|')))['match'])
|
|
def test_tempo_change_explicit(self):
|
|
p=a.parse(score('C32|')); q=a.parse(score('C32|').replace('=88','=80'))
|
|
self.assertFalse(a.compare(p,q)['match'])
|
|
self.assertTrue(a.compare(p,q,allow_tempo_change=True)['match'])
|
|
def test_bad_input_cases(self):
|
|
examples=[score('C16|'), score('C48|'), score('C32-|'), score('C32-|D32|','Z2|'),
|
|
score('C32-|z32|','Z2|'), score('C32-|=C32|','Z2|',key='D').replace('C32-|','^C32-|'),
|
|
score('z32-|'), score('^z32|'), score('C10C22|'), score('C/2C31|'),
|
|
score('(3C8D8E8|'), score('[CEG]32|'), score('C16>C16|'), score('{C}D32|'),
|
|
score('C32||'), score('C32:|'), score('Z1|'), score('Z5|'),
|
|
score('"C13"C32|'), score('"A7alt"C32|'), score('C32"C"|'),
|
|
score('C32|','"C"Z|'),score('C16[K:D]C16|'),
|
|
score('C32|').replace('L:1/32','L:1/3'),
|
|
score('C32|').replace('K:C','K:Cmix'),score('C32|').replace('% verse','%%MIDI program 1'),
|
|
score('C32|').replace('C32|','C32|C32|C32|C32|C32|'),
|
|
score('C32|','Z2|')]
|
|
for i,text in enumerate(examples):
|
|
with self.subTest(case=i):
|
|
with self.assertRaises(a.AbcError): a.parse(text)
|
|
|
|
if __name__=='__main__': unittest.main(verbosity=2)
|