def setUp(self): song_data = self.__song_data = SongData([ BarInfo() ], [], 0) self.__bar_chords1 = BarChords() self.__bar_chords1.set_song_data(song_data) self.__bar_chords1.set_chords([['CM7', ' trail1 '], [ 'Am', ' trail2 ']]) self.__bar_chords2 = BarChords() self.__bar_chords2.set_song_data(song_data) self.__bar_chords2.set_chords([['CM7', ' trail1 '], [ 'Am', ' trail2 '], [ '/', ' '], ['G', ' trail3 ']])
class TestBarChords(unittest.TestCase): def setUp(self): song_data = self.__song_data = SongData([ BarInfo() ], [], 0) self.__bar_chords1 = BarChords() self.__bar_chords1.set_song_data(song_data) self.__bar_chords1.set_chords([['CM7', ' trail1 '], [ 'Am', ' trail2 ']]) self.__bar_chords2 = BarChords() self.__bar_chords2.set_song_data(song_data) self.__bar_chords2.set_chords([['CM7', ' trail1 '], [ 'Am', ' trail2 '], [ '/', ' '], ['G', ' trail3 ']]) def test_set_chord1(self): bar_chords = self.__bar_chords1 bar_chords.set_chord(0, 'A') assert bar_chords.get_chords() == [['A', ' trail1 '], [ 'Am', ' trail2 ']] bar_chords.set_chord(0, '') assert bar_chords.get_chords() == [['/', ' trail1 '], [ 'Am', ' trail2 ']] # remove the last chord assert '\n' == bar_chords.get_eol() bar_chords.set_chord(1, '') assert bar_chords.get_chords() == [['/', ' trail1 ']] assert ' trail2 \n' == bar_chords.get_eol() bar_chords.set_chord(0, '') assert bar_chords.get_chords() == [['/', ' trail1 ']] bar_chords.set_chord(3, 'BM6') assert bar_chords.get_chords() == [['/', ' trail1 '], ['/', ' '], ['/', ' '], ['BM6', '']] def test_set_chord2(self): bar_chords = self.__bar_chords2 bar_chords.set_chord(2, 'B') assert bar_chords.get_chords() == [['CM7', ' trail1 '], [ 'Am', ' trail2 '], [ 'B', ' '], ['G', ' trail3 ']] bar_chords.set_chord(0, '') assert bar_chords.get_chords() == [['/', ' trail1 '], [ 'Am', ' trail2 '], [ 'B', ' '], ['G', ' trail3 ']] # remove the last chord assert '\n' == bar_chords.get_eol() bar_chords.set_chord(3, '') assert bar_chords.get_chords() == [['/', ' trail1 '], [ 'Am', ' trail2 '], [ 'B', ' ']] assert ' trail3 \n' == bar_chords.get_eol()
def parse(inpath): """ Process a mma input file. """ song_bar_info = [] song_bar_chords = [] song_bar_count = 0 bar_number = 0 bar_info = BarInfo() bar_chords = BarChords() while True: curline = inpath.readline() # EOF if not curline: song_bar_info.append( bar_info ) # song_bar_info has always one element more then song_bar_chords song_bar_count = bar_number return SongData(song_bar_info, song_bar_chords, song_bar_count) """ convert 0xa0 (non-breakable space) to 0x20 (regular space). """ curline = curline.replace('\xa0', '\x20') # empty line if curline.rstrip('\n').strip() == '': bar_info.add_line([Glob.A_UNKNOWN, curline]) continue l = curline.split() # line beginning with macro if l[0][0] == '$': wline = get_wrapped_line(inpath, curline) wline.insert(0, Glob.A_UNKNOWN) bar_info.add_line(wline) continue """ Handle BEGIN and END here. This is outside of the Repeat/End and variable expand loops so SHOULD be pretty bullet proof. Note that the beginData stuff is global to this module ... the Include/Use directives check to make sure we're not doing that inside a Begin/End. beginData[] is a list which we append to as more Begins are encountered. The placement here is pretty deliberate. Variable expand comes later so you can't macroize BEGIN ... I think this makes sense. The tests for 'begin', 'end' and the appending of the current begin[] stuff have to be here, in this order. """ action = l[0].upper() # 1st arg in line # parse BEGIN and END block if action == 'BEGIN': block_action = l[1].upper() begin_block = parse_begin_block(inpath, curline) if block_action in supported_block_actions: tokens = parse_supported_block_action(block_action, begin_block) begin_block = tokens begin_block.insert(0, Glob.A_BEGIN_BLOCK) begin_block.insert(1, block_action) bar_info.add_line(begin_block) continue # parse MSET block if action == 'MSET': mset_block = parse_mset_block(inpath, curline) mset_block.insert(0, Glob.A_UNKNOWN) bar_info.add_line(mset_block) continue # parse IF - ENDIF block if action == 'IF': if_block = parse_if_block(inpath, curline) if_block.insert(0, Glob.A_UNKNOWN) bar_info.add_line(if_block) continue # supported commands if action in supported_actions: wline = get_wrapped_line_join(inpath, curline) tokens = parse_supported_action(action, wline) tokens.insert(0, action) bar_info.add_line(tokens) continue # if the command is in the simple function table if action in simple_funcs: wline = get_wrapped_line(inpath, curline) wline.insert(0, Glob.A_UNKNOWN) bar_info.add_line(wline) continue """ We have several possibilities ... 1. The command is a valid assigned track name, 2. The command is a valid track name, but needs to be dynamically allocated, 3. It's really a chord action """ # track function BASS/DRUM/APEGGIO/CHORD ... if '-' in action: trk_class, ext = action.split('-', 1) #@UnusedVariable else: trk_class = action if trk_class in trk_classes: # parsing track sequence ? parse_seq = len(l) >= 1 and l[1].upper() == 'SEQUENCE' wline = [] while True: wline.extend(get_wrapped_line(inpath, curline)) if not parse_seq: break """ Count the number of { and } and if they don't match read more lines and append. If we get to the EOF then we're screwed and we error out. """ wline2 = ''.join(wline) if wline2.count('{') == wline2.count('}'): break curline = inpath.readline() if not curline: raise ValueError("Reached EOF, Sequence {}s do not match") wline.insert(0, Glob.A_UNKNOWN) bar_info.add_line(wline) continue # join the wrapped line into one line wline = get_wrapped_line_join(inpath, curline) if wline[0].replace('\\\n', '').strip() == '': # line is a comment or empty wrapped line act = Glob.A_REMARK if wline[1].strip() else Glob.A_UNKNOWN bar_info.add_line([act, wline[0], wline[1]]) continue l, eol = wline ### Gotta be a chord data line! """ A data line can have an optional bar number at the start of the line. Makes debugging input easier. The next block strips leading integers off the line. Note that a line number on a line by itself it okay. """ before_number = '' if action.isdigit(): # isdigit() matches '1', '1234' but not '1a'! l2 = l.lstrip() before_number_len = len(l) - len(l2) before_number = l[0:before_number_len] l = l2 numstr = l.split()[0] bar_chords.set_number(int(numstr)) l = l[len(numstr):] # remove number if len(l.strip()) == 0: # ignore empty lines bar_info.add_line([Glob.A_UNKNOWN, wline[0] + wline[1]]) continue """ We now have a valid line. It'll look something like: 'Cm', '/', 'z', 'F#@4.5' { lyrics } [ solo ] * 2 Special processing in needed for 'z' options in chords. A 'z' can be of the form 'CHORDzX', 'z!' or just 'z'. """ after_number = None last_chord = [] ctable = [] i = 0 solo_count = 0 lyrics_count = 0 mismatched_solo = "Mismatched {}s for solo found in chord line" mismatched_lyrics = "Mismatched []s for lyrics found in chord line" while True: chars = '' while i < len(l): ch = l[i] if ch == '{': """ Extract solo(s) from line ... this is anything in {}s. The solo data is pushed into RIFFs and discarded from the current line. """ solo_count += 1 elif ch == '[': """ Set lyrics from [stuff] in the current line. NOTE: lyric.extract() inserts previously created data from LYRICS SET and inserts the chord names if that flag is active. """ lyrics_count += 1 elif ch == '}': solo_count -= 1 if solo_count < 0: raise ValueError(mismatched_solo) elif ch == ']': lyrics_count -= 1 if lyrics_count < 0: raise ValueError(mismatched_lyrics) elif ch == '*': """ A bar can have an optional repeat count. This must be at the end of bar in the form '* xx'. """ pass elif ch in '\t\n\\ 0123456789': # white spaces, \ and repeat count pass elif solo_count == 0 and lyrics_count == 0: # found beginning of the chord break chars += ch i += 1 if i == len(l): # no more chord is coming if solo_count != 0: raise ValueError(mismatched_solo) if lyrics_count != 0: raise ValueError(mismatched_lyrics) if after_number == None: after_number = chars else: last_chord.append(chars) ctable.append(last_chord) break else: # chord beginning if after_number == None: after_number = chars else: last_chord.append(chars) ctable.append(last_chord) chord_begin = i # find the end of the chord while i < len(l): if l[i] in '{}[]*\t\n\\ ': break i += 1 # chord examples: '/', 'z', 'Am7@2', 'Am6zC@3' c = l[chord_begin:i] last_chord = [c] # the trailing string of the last chord can possibly include '\n' after which # it would be difficult to add further chords. Therefore move the trailing string # of the last chord to eol eol = last_chord[1] + eol last_chord[1] = '' bar_chords.set_before_number(before_number) bar_chords.set_after_number(after_number) bar_chords.set_eol(eol) bar_chords.set_chords(ctable) song_bar_info.append(bar_info) song_bar_chords.append(bar_chords) bar_number = bar_number + 1 bar_info = BarInfo() bar_chords = BarChords()
def create_bar_chords(self): bar_chords = BarChords() bar_chords.set_song_data(self) return bar_chords
def parse(inpath): """ Process a mma input file. """ song_bar_info = [] song_bar_chords = [] song_bar_count = 0 bar_number = 0 bar_info = BarInfo() bar_chords = BarChords() while True: curline = inpath.readline() # EOF if not curline: song_bar_info.append(bar_info) # song_bar_info has always one element more then song_bar_chords song_bar_count = bar_number return SongData(song_bar_info, song_bar_chords, song_bar_count) """ convert 0xa0 (non-breakable space) to 0x20 (regular space). """ curline = curline.replace('\xa0', '\x20') # empty line if curline.rstrip('\n').strip() == '': bar_info.add_line([Glob.A_UNKNOWN, curline]); continue l = curline.split() # line beginning with macro if l[0][0] == '$': wline = get_wrapped_line(inpath, curline) wline.insert(0, Glob.A_UNKNOWN) bar_info.add_line(wline) continue """ Handle BEGIN and END here. This is outside of the Repeat/End and variable expand loops so SHOULD be pretty bullet proof. Note that the beginData stuff is global to this module ... the Include/Use directives check to make sure we're not doing that inside a Begin/End. beginData[] is a list which we append to as more Begins are encountered. The placement here is pretty deliberate. Variable expand comes later so you can't macroize BEGIN ... I think this makes sense. The tests for 'begin', 'end' and the appending of the current begin[] stuff have to be here, in this order. """ action = l[0].upper() # 1st arg in line # parse BEGIN and END block if action == 'BEGIN': block_action = l[1].upper() begin_block = parse_begin_block(inpath, curline) if block_action in supported_block_actions: tokens = parse_supported_block_action(block_action, begin_block) begin_block = tokens begin_block.insert(0, Glob.A_BEGIN_BLOCK) begin_block.insert(1, block_action) bar_info.add_line(begin_block) continue # parse MSET block if action == 'MSET': mset_block = parse_mset_block(inpath, curline) mset_block.insert(0, Glob.A_UNKNOWN) bar_info.add_line(mset_block) continue # parse IF - ENDIF block if action == 'IF': if_block = parse_if_block(inpath, curline) if_block.insert(0, Glob.A_UNKNOWN) bar_info.add_line(if_block) continue # supported commands if action in supported_actions: wline = get_wrapped_line_join(inpath, curline) tokens = parse_supported_action(action, wline) tokens.insert(0, action) bar_info.add_line(tokens) continue # if the command is in the simple function table if action in simple_funcs: wline = get_wrapped_line(inpath, curline) wline.insert(0, Glob.A_UNKNOWN) bar_info.add_line(wline) continue """ We have several possibilities ... 1. The command is a valid assigned track name, 2. The command is a valid track name, but needs to be dynamically allocated, 3. It's really a chord action """ # track function BASS/DRUM/APEGGIO/CHORD ... if '-' in action: trk_class, ext = action.split('-', 1) #@UnusedVariable else: trk_class = action if trk_class in trk_classes: # parsing track sequence ? parse_seq = len(l) >= 1 and l[1].upper() == 'SEQUENCE' wline = [] while True: wline.extend(get_wrapped_line(inpath, curline)) if not parse_seq: break """ Count the number of { and } and if they don't match read more lines and append. If we get to the EOF then we're screwed and we error out. """ wline2 = ''.join(wline) if wline2.count('{') == wline2.count('}'): break curline = inpath.readline() if not curline: raise ValueError("Reached EOF, Sequence {}s do not match") wline.insert(0, Glob.A_UNKNOWN) bar_info.add_line(wline) continue # join the wrapped line into one line wline = get_wrapped_line_join(inpath, curline) if wline[0].replace('\\\n', '').strip() == '': # line is a comment or empty wrapped line act = Glob.A_REMARK if wline[1].strip() else Glob.A_UNKNOWN bar_info.add_line([act , wline[0], wline[1]]) continue l, eol = wline ### Gotta be a chord data line! """ A data line can have an optional bar number at the start of the line. Makes debugging input easier. The next block strips leading integers off the line. Note that a line number on a line by itself it okay. """ before_number = '' if action.isdigit(): # isdigit() matches '1', '1234' but not '1a'! l2 = l.lstrip() before_number_len = len(l) - len(l2) before_number = l[0:before_number_len] l = l2 numstr = l.split()[0] bar_chords.set_number(int(numstr)) l = l[len(numstr):] # remove number if len(l.strip()) == 0: # ignore empty lines bar_info.add_line([ Glob.A_UNKNOWN, wline[0] + wline[1] ]) continue """ We now have a valid line. It'll look something like: 'Cm', '/', 'z', 'F#@4.5' { lyrics } [ solo ] * 2 Special processing in needed for 'z' options in chords. A 'z' can be of the form 'CHORDzX', 'z!' or just 'z'. """ after_number = None last_chord = [] ctable = [] i = 0 solo_count = 0 lyrics_count = 0 mismatched_solo = "Mismatched {}s for solo found in chord line" mismatched_lyrics = "Mismatched []s for lyrics found in chord line" while True: chars = '' while i < len(l): ch = l[i] if ch == '{': """ Extract solo(s) from line ... this is anything in {}s. The solo data is pushed into RIFFs and discarded from the current line. """ solo_count += 1 elif ch == '[': """ Set lyrics from [stuff] in the current line. NOTE: lyric.extract() inserts previously created data from LYRICS SET and inserts the chord names if that flag is active. """ lyrics_count += 1 elif ch == '}': solo_count -= 1 if solo_count < 0: raise ValueError(mismatched_solo) elif ch == ']': lyrics_count -= 1 if lyrics_count < 0: raise ValueError(mismatched_lyrics) elif ch == '*': """ A bar can have an optional repeat count. This must be at the end of bar in the form '* xx'. """ pass elif ch in '\t\n\\ 0123456789': # white spaces, \ and repeat count pass elif solo_count == 0 and lyrics_count == 0: # found beginning of the chord break chars += ch i += 1 if i == len(l): # no more chord is coming if solo_count != 0: raise ValueError(mismatched_solo) if lyrics_count != 0: raise ValueError(mismatched_lyrics) if after_number == None: after_number = chars else: last_chord.append(chars) ctable.append(last_chord) break else: # chord beginning if after_number == None: after_number = chars else: last_chord.append(chars) ctable.append(last_chord) chord_begin = i # find the end of the chord while i < len(l): if l[i] in '{}[]*\t\n\\ ': break i += 1 # chord examples: '/', 'z', 'Am7@2', 'Am6zC@3' c = l[chord_begin:i] last_chord = [ c ] # the trailing string of the last chord can possibly include '\n' after which # it would be difficult to add further chords. Therefore move the trailing string # of the last chord to eol eol = last_chord[1] + eol last_chord[1] = '' bar_chords.set_before_number(before_number) bar_chords.set_after_number(after_number) bar_chords.set_eol(eol) bar_chords.set_chords(ctable) song_bar_info.append(bar_info) song_bar_chords.append(bar_chords) bar_number = bar_number + 1 bar_info = BarInfo() bar_chords = BarChords()