Exemplo n.º 1
0
 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 ']])
Exemplo n.º 2
0
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()
Exemplo n.º 3
0
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()
Exemplo n.º 4
0
 def create_bar_chords(self):
     bar_chords = BarChords()
     bar_chords.set_song_data(self)
     return bar_chords
Exemplo n.º 5
0
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()