Files
smthemex-ComfyUI_YuE/tests/test_skill_abc_tools.py
2026-09-15 11:04:52 +08:00

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)