Exemple #1
0
 def __init__(self):
     '''
     Constructor
     '''
     self._staffs = []
     self.drumKit = DrumKit.DrumKit()
     self._callBack = None
     self._callBacksEnabled = True
     self.scoreData = ScoreMetaData()
     self._sections = []
     self._formatState = None
     self.paperSize = "Letter"
     counter = CounterRegistry().getCounterByIndex(0)
     self.defaultCount = makeSimpleCount(counter, 4)
     self.systemSpacing = 25
     self.fontOptions = FontOptions()
     self.lilysize = 20
     self.lilypages = 0
     self.lilyFill = False
Exemple #2
0
 def __init__(self):
     '''
     Constructor
     '''
     self._staffs = []
     self.drumKit = DrumKit.DrumKit()
     self._callBack = None
     self._callBacksEnabled = True
     self.scoreData = ScoreMetaData()
     self._sections = []
     self._formatState = None
     self.paperSize = "Letter"
     counter = CounterRegistry().getCounterByIndex(0)
     self.defaultCount = makeSimpleCount(counter, 4)
     self.systemSpacing = 25
     self.fontOptions = FontOptions()
     self.lilysize = 20
     self.lilypages = 0
     self.lilyFill = False
Exemple #3
0
class Score(object):
    '''
    classdocs
    '''


    def __init__(self):
        '''
        Constructor
        '''
        self._staffs = []
        self.drumKit = DrumKit.DrumKit()
        self._callBack = None
        self._callBacksEnabled = True
        self.scoreData = ScoreMetaData()
        self._sections = []
        self._formatState = None
        self.paperSize = "Letter"
        counter = CounterRegistry().getCounterByIndex(0)
        self.defaultCount = makeSimpleCount(counter, 4)
        self.systemSpacing = 25
        self.fontOptions = FontOptions()
        self.lilysize = 20
        self.lilypages = 0
        self.lilyFill = False

    def __len__(self):
        return sum(len(staff) for staff in self._staffs)

    def _runCallBack(self, position):
        if self._callBack is not None and self._callBacksEnabled:
            self._callBack(position)

    def turnOnCallBacks(self):
        self._callBacksEnabled = True

    def turnOffCallBacks(self):
        self._callBacksEnabled = False

    def setCallBack(self, callBack):
        self._callBack = callBack

    def clearCallBack(self):
        self._callBack = None

    def _setStaffCallBack(self, staff, staffIndex):
        def wrappedCallBack(position):
            position.staffIndex = staffIndex
            self._runCallBack(position)
        staff.setCallBack(wrappedCallBack)

    def _checkStaffIndex(self, index):
        if not (0 <= index < self.numStaffs()):
            raise BadTimeError()

    def _checkDrumIndex(self, index):
        if not(0 <= index < len(self.drumKit)):
            raise BadTimeError()

    def _checkMeasureIndex(self, index, endOk = False):
        if not(0 <= index < self.numMeasures()):
            if not (endOk and index == self.numMeasures()):
                raise BadTimeError()

    def iterStaffs(self):
        return iter(self._staffs)

    def iterMeasures(self):
        for staff in self.iterStaffs():
            for measure in staff:
                yield measure

    def iterMeasuresBetween(self, start, end):
        if self.getMeasureIndex(end) < self.getMeasureIndex(start):
            start, end = end, start
        staffIndex = start.staffIndex
        measureIndex = start.measureIndex
        absIndex = self.getMeasureIndex(start)
        while staffIndex < end.staffIndex:
            staff = self.getStaff(staffIndex)
            while measureIndex < staff.numMeasures():
                yield (staff[measureIndex], absIndex,
                       NotePosition(staffIndex, measureIndex))
                measureIndex += 1
                absIndex += 1
            measureIndex = 0
            staffIndex += 1
        staff = self.getStaff(staffIndex)
        while measureIndex <= end.measureIndex:
            yield (staff[measureIndex], absIndex,
                   NotePosition(staffIndex, measureIndex))
            absIndex += 1
            measureIndex += 1

    @staticmethod
    def _readAlternates(text):
        alternates = [t.strip() for t in
                      text.split(",")]
        theseAlternates = set()
        for aText in alternates:
            if "-" in aText:
                aStart, aEnd = aText.split("-")
                for aVal in xrange(int(aStart), int(aEnd.rstrip(".")) + 1):
                    theseAlternates.add(aVal)
            else:
                theseAlternates.add(int(aText.rstrip(".")))
        return theseAlternates

    @staticmethod
    def _findRepeatData(measures, index, alternateIndexes):
        start = index
        numMeasures = len(measures)
        numRepeats = 0
        alternates = set()
        while index < numMeasures:
            measure = measures[index]
            if index in alternateIndexes:
                numRepeats += len(alternateIndexes[index])
                alternates.update(alternateIndexes[index])
            if (measure.isRepeatEnd() or measure.isSectionEnd()):
                if alternates:
                    if (index + 1 == numMeasures or
                        not measures[index + 1].alternateText
                        or measures[index + 1].isRepeatStart()
                        or measure.isSectionEnd()):
                        if alternates != set(xrange(1, numRepeats + 1)):
                            raise InconsistentRepeats(start, index)
                        return index + 1, numRepeats
                else:
                    numRepeats = measure.repeatCount
                    return index + 1, numRepeats
            index += 1
        return index, numRepeats


    def iterMeasuresWithRepeats(self):
        measures = list(self.iterMeasures())
        alternates = {}
        repeatData = {}
        repeatStart = -1
        for index, measure in enumerate(measures):
            if measure.alternateText:
                alternates[index] = self._readAlternates(measure.alternateText)
        for index, measure in enumerate(measures):
            if measure.isRepeatStart():
                repeatData[index] = self._findRepeatData(measures, index,
                                                         alternates)
        index = 0
        repeatStart = 0
        repeatNum = -1
        afterRepeat = -1
        latestRepeatEnd = -1
        numMeasures = len(measures)
        alternateStarts = {}
        while index < numMeasures:
            measure = measures[index]
            if measure.isRepeatStart():
                repeatStart = index
                afterRepeat, numRepeats = repeatData[index]
                repeatNum += 1
            if index in alternates and repeatNum > -1:
                theseAlternates = alternates[index]
                alternateStarts.update((aStart, index) for aStart
                                       in theseAlternates)
                if repeatNum + 1 not in theseAlternates:
                    index = alternateStarts.get(repeatNum + 1,
                                                latestRepeatEnd + 1)
                    continue
            yield measure, index
            if measure.isRepeatEnd() and repeatNum > -1:
                if alternateStarts:
                    if repeatNum < numRepeats - 1:
                        if index > latestRepeatEnd:
                            latestRepeatEnd = index
                        index = repeatStart
                    else:
                        index = afterRepeat
                        repeatNum = -1
                        alternateStarts = {}
                elif repeatNum < numRepeats - 1:
                    index = repeatStart
                else:
                    index += 1
                    repeatNum = -1
            elif measure.isSectionEnd():
                repeatNum = -1
                index += 1
                alternateStarts = {}
            else:
                index += 1

    def getMeasure(self, index):
        self._checkMeasureIndex(index)
        staff, index = self._staffContainingMeasure(index)
        return staff[index]

    def getItemAtPosition(self, position):
        if position.staffIndex is None:
            return self
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        if position.measureIndex is None:
            return staff
        if position.drumIndex is not None:
            self._checkDrumIndex(position.drumIndex)
        return staff.getItemAtPosition(position)

    def getStaff(self, index):
        return self._staffs[index]

    def numStaffs(self):
        return len(self._staffs)

    def _addStaff(self):
        newStaff = Staff()
        self._staffs.append(newStaff)
        self._setStaffCallBack(newStaff, self.numStaffs() - 1)

    def _deleteStaffByIndex(self, index):
        self._checkStaffIndex(index)
        staff = self._staffs[index]
        staff.clearCallBack()
        if staff.isSectionEnd():
            if index == 0 or self.getStaff(index - 1).isSectionEnd():
                position = NotePosition(staffIndex = index)
                sectionIndex = self.getSectionIndex(position)
                self._deleteSectionTitle(sectionIndex)
            else:
                prevStaff = self.getStaff(index - 1)
                position = NotePosition(staffIndex = index - 1,
                                        measureIndex =
                                        prevStaff.numMeasures() - 1)
                prevStaff.setSectionEnd(position, True)
        self._staffs.pop(index)
        for offset, nextStaff in enumerate(self._staffs[index:]):
            self._setStaffCallBack(nextStaff, index + offset)

    def numMeasures(self):
        return sum(staff.numMeasures() for staff in self.iterStaffs())

    def _staffContainingMeasure(self, index):
        measuresSoFar = 0
        for staff in self.iterStaffs():
            if measuresSoFar <= index < measuresSoFar + staff.numMeasures():
                return staff, index - measuresSoFar
            measuresSoFar += staff.numMeasures()
        raise BadTimeError()

    def getMeasureIndex(self, position):
        self._checkStaffIndex(position.staffIndex)
        index = 0
        for staffIndex in range(0, position.staffIndex):
            index += self.getStaff(staffIndex).numMeasures()
        index += position.measureIndex
        return index

    def getMeasurePosition(self, index):
        staffIndex = 0
        staff = self.getStaff(0)
        while index >= staff.numMeasures():
            index -= staff.numMeasures()
            staffIndex += 1
            if staffIndex == self.numStaffs():
                break
            staff = self.getStaff(staffIndex)
        if staffIndex == self.numStaffs():
            raise BadTimeError(index)
        return NotePosition(staffIndex = staffIndex,
                            measureIndex = index)


    def insertMeasureByIndex(self, width, index = None, counter = None):
        if index is None:
            index = self.numMeasures()
        if self.numStaffs() == 0:
            self._addStaff()
            staff = self.getStaff(0)
        elif index == self.numMeasures():
            staff = self.getStaff(-1)
            index = staff.numMeasures()
        else:
            staff, index = self._staffContainingMeasure(index)
        newMeasure = Measure(width)
        newMeasure.counter = counter
        staff.insertMeasure(NotePosition(measureIndex = index),
                            newMeasure)
        return newMeasure

    def insertMeasureByPosition(self, width, position = None, counter = None):
        if position is None:
            if self.numStaffs() == 0:
                self._addStaff()
            position = NotePosition(self.numStaffs() - 1)
            staff = self.getStaff(self.numStaffs() - 1)
            position.measureIndex = staff.numMeasures()
        self._checkStaffIndex(position.staffIndex)
        newMeasure = Measure(width)
        newMeasure.counter = counter
        staff = self.getStaff(position.staffIndex)
        staff.insertMeasure(position, newMeasure)
        return newMeasure

    def deleteMeasureByIndex(self, index):
        self._checkMeasureIndex(index)
        np = self.getMeasurePosition(index)
        self.deleteMeasureByPosition(np)

    def deleteMeasureByPosition(self, position):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        if (staff.isSectionEnd()
            and position.measureIndex == staff.numMeasures() - 1):
            sectionIndex = self.getSectionIndex(position)
            self._deleteSectionTitle(sectionIndex)
        staff.deleteMeasure(position)

    def deleteMeasuresAtPosition(self, position, numToDelete):
        position = position.makeMeasurePosition()
        staff = self.getStaff(position.staffIndex)
        for dummyIndex in xrange(numToDelete):
            if position.measureIndex == staff.numMeasures():
                if staff.numMeasures() == 0:
                    self._deleteStaffByIndex(position.staffIndex)
                else:
                    position.staffIndex += 1
                    position.measureIndex = 0
                staff = self.getStaff(position.staffIndex)
            staff.deleteMeasure(position)

    def trailingEmptyMeasures(self):
        emptyMeasures = []
        np = NotePosition(staffIndex = self.numStaffs() - 1)
        staff = self.getItemAtPosition(np)
        np.measureIndex = staff.numMeasures() - 1
        measure = self.getItemAtPosition(np)
        while ((np.staffIndex > 0 or np.measureIndex > 0)
               and measure.isEmpty()):  # pylint:disable-msg=E1103
            emptyMeasures.append(np.makeMeasurePosition())
            if np.measureIndex == 0:
                np.staffIndex -= 1
                staff = self.getStaff(np.staffIndex)
                np.measureIndex = staff.numMeasures()
            np.measureIndex -= 1
            measure = self.getItemAtPosition(np)
        return emptyMeasures

    def copyMeasure(self, position):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        return staff.copyMeasure(position)

    def pasteMeasure(self, position, notes, copyMeasureDecorations = False):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        return staff.pasteMeasure(position, notes, copyMeasureDecorations)

    def pasteMeasureByIndex(self, index, notes, copyMeasureDecorations = False):
        position = self.getMeasurePosition(index)
        self.pasteMeasure(position, notes, copyMeasureDecorations)

    def numSections(self):
        return len(self._sections)

    def getSectionIndex(self, position):
        ends = []
        for staffIndex, staff in enumerate(self.iterStaffs()):
            assert(staff.isConsistent())
            if staff.isSectionEnd():
                ends.append(staffIndex)
        assert(len(ends) == self.numSections())
        return bisect.bisect_left(ends, position.staffIndex)

    def getSectionTitle(self, index):
        return self._sections[index]

    def setSectionTitle(self, index, title):
        self._sections[index] = title

    def _deleteSectionTitle(self, index):
        self._sections.pop(index)

    def getSectionStartStaffIndex(self, position):
        startIndex = position.staffIndex
        while (startIndex > 0 and
               not self.getStaff(startIndex - 1).isSectionEnd()):
            startIndex -= 1
        return startIndex

    def deleteSection(self, position):
        sectionIndex = self.getSectionIndex(position)
        if sectionIndex == self.numSections():
            return
        startIndex = position.staffIndex
        while (startIndex > 0 and
               not self.getStaff(startIndex - 1).isSectionEnd()):
            startIndex -= 1
        while not self.getStaff(startIndex).isSectionEnd():
            self._deleteStaffByIndex(startIndex)
        self._deleteStaffByIndex(startIndex)

    def iterSections(self):
        return iter(self._sections)

    def setSectionEnd(self, position, onOff):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        sectionIndex = self.getSectionIndex(position)
        if onOff:
            self._sections.insert(sectionIndex, "Section Title")
        else:
            if sectionIndex < self.numSections():
                self._sections.pop(sectionIndex)
        staff.setSectionEnd(position, onOff)

    def iterMeasuresInSection(self, sectionIndex):
        if not(0 <= sectionIndex < self.numSections()):
            raise BadTimeError()
        thisSection = 0
        inSection = (thisSection == sectionIndex)
        for measure in self.iterMeasures():
            if inSection:
                yield measure
                if measure.isSectionEnd():
                    break
            if measure.isSectionEnd():
                thisSection += 1
                inSection = (thisSection == sectionIndex)

    def nextMeasure(self, position):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        if not (0 <= position.measureIndex < staff.numMeasures()):
            raise BadTimeError()
        position = position.makeMeasurePosition()
        position.measureIndex += 1
        if position.measureIndex == staff.numMeasures():
            position.staffIndex += 1
            if position.staffIndex == self.numStaffs():
                position.staffIndex = None
                position.measureIndex = None
            else:
                position.measureIndex = 0
        return position

    def insertSectionCopy(self, position, sectionIndex):
        self.turnOffCallBacks()
        position = position.makeMeasurePosition()
        try:
            self._checkStaffIndex(position.staffIndex)
            sectionMeasures = list(self.iterMeasuresInSection(sectionIndex))
            sectionTitle = "Copy of " + self.getSectionTitle(sectionIndex)
            newIndex = self.getSectionIndex(position)
            self._sections.insert(newIndex, sectionTitle)
            for measure in sectionMeasures:
                newMeasure = self.insertMeasureByPosition(len(measure),
                                                          position,
                                                          measure.counter)
                newMeasure.pasteMeasure(measure, True)
                position.measureIndex += 1
        finally:
            self.turnOnCallBacks()

    def addNote(self, position, head = None):
        self._checkStaffIndex(position.staffIndex)
        self._checkDrumIndex(position.drumIndex)
        if head is None:
            head = self.drumKit[position.drumIndex].head
        self.getStaff(position.staffIndex).addNote(position, head)

    def deleteNote(self, position):
        self._checkStaffIndex(position.staffIndex)
        self._checkDrumIndex(position.drumIndex)
        self.getStaff(position.staffIndex).deleteNote(position)

    def toggleNote(self, position, head = None):
        self._checkStaffIndex(position.staffIndex)
        self._checkDrumIndex(position.drumIndex)
        if head is None:
            head = self.drumKit[position.drumIndex].head
        self.getStaff(position.staffIndex).toggleNote(position, head)

    def notePlus(self, pos, ticks):
        self._checkStaffIndex(pos.staffIndex)
        self._checkDrumIndex(pos.drumIndex)
        staff = self.getStaff(pos.staffIndex)
        measure = staff[pos.measureIndex]
        pos.noteTime += ticks
        while pos.noteTime >= len(measure):
            pos.noteTime -= len(measure)
            pos.measureIndex += 1
            if pos.measureIndex >= staff.numMeasures():
                pos.measureIndex = 0
                pos.staffIndex += 1
                if pos.staffIndex == self.numStaffs():
                    return None
                staff = self.getStaff(pos.staffIndex)
            measure = staff[pos.measureIndex]
        return pos

    def tickDifference(self, second, first):
        """Calculate the difference in ticks between NotePositions first and 
        second.
        """
        current = first.makeMeasurePosition()
        end = second.makeMeasurePosition()
        end.noteTime = None
        end.drumIndex = None
        ticks = 0
        direction = 1
        offset = first.noteTime
        if end < current:
            current, end = end, current
            direction = -1
            offset = second.noteTime
        while current < end:
            ticks += len(self.getItemAtPosition(current)) - offset
            current = self.nextMeasure(current)
            offset = 0
        if direction == 1:
            ticks += second.noteTime - offset
        else:
            ticks += first.noteTime - offset
        ticks *= direction
        return ticks


    def _getFormatState(self):
        return [(staff.numMeasures(), self.numVisibleLines(index))
                for index, staff in enumerate(self.iterStaffs())]

    def saveFormatState(self):
        self._formatState = self._getFormatState()

    def formatScore(self, width = None, ignoreErrors = True):
        if width is None:
            width = self.scoreData.width
        measures = list(self.iterMeasures())
        if not self._formatState:
            self.saveFormatState()
        for staff in self.iterStaffs():
            staff.clear()
        staff = self.getStaff(0)
        staffIndex = 0
        for measureIndex, measure in enumerate(measures):
            staff.addMeasure(measure)
            while staff.gridWidth() > width:
                if staff.numMeasures() == 1:
                    if ignoreErrors:
                        break
                    else:
                        raise OverSizeMeasure(measure)
                else:
                    staff.deleteLastMeasure()
                    staffIndex += 1
                    if staffIndex == self.numStaffs():
                        self._addStaff()
                    staff = self.getStaff(staffIndex)
                    staff.addMeasure(measure)
            if (measure.isLineEnd() and
                measureIndex != len(measures) - 1):
                staffIndex += 1
                if staffIndex == self.numStaffs():
                    self._addStaff()
                staff = self.getStaff(staffIndex)
        while self.numStaffs() > staffIndex + 1:
            staff = self._staffs.pop()
            staff.clearCallBack()
        return self._formatState != self._getFormatState()

    def changeKit(self, newKit, changes):
        for measure in self.iterMeasures():
            measure.changeKit(newKit, changes)
        self.drumKit = newKit

    def numVisibleLines(self, index):
        if self.scoreData.emptyLinesVisible:
            return len(self.drumKit)
        else:
            staff = self.getStaff(index)
            count = sum(drum.locked or staff.lineIsVisible(index)
                        for index, drum in enumerate(self.drumKit))
            if count == 0:
                return 1
            else:
                return count

    def nthVisibleLineIndex(self, staffIndex, lineIndex):
        count = -1
        staff = self.getStaff(staffIndex)
        for lineNum, drum in enumerate(self.drumKit):
            if (drum.locked or self.scoreData.emptyLinesVisible
                or staff.lineIsVisible(lineNum)):
                count += 1
                if count == lineIndex:
                    return lineNum
        if count == -1 and lineIndex == 0:
            return 0
        raise BadTimeError(staffIndex)

    def _iterLinesLockedOrWithNotes(self, staffIndex):
        staff = self.getStaff(staffIndex)
        count = 0
        for lineNum, drum in enumerate(self.drumKit):
            if (drum.locked
                or staff.lineIsVisible(lineNum)):
                count += 1
                yield drum
        if count == 0:
            yield self.drumKit[0]

    def iterVisibleLines(self, staffIndex, forceIgnoreEmpty = False):
        if self.scoreData.emptyLinesVisible and not forceIgnoreEmpty:
            return iter(self.drumKit)
        else:
            return self._iterLinesLockedOrWithNotes(staffIndex)

    def write(self, handle):
        indenter = fileUtils.Indenter(handle)
        self.scoreData.save(indenter)
        self.drumKit.write(indenter)
        for measure in self.iterMeasures():
            measure.write(indenter)
        for title in self._sections:
            indenter("SECTION_TITLE", title)
        indenter("PAPER_SIZE", self.paperSize)
        indenter("LILYSIZE", self.lilysize)
        indenter("LILYPAGES", self.lilypages)
        if self.lilyFill:
            indenter("LILYFILL", "YES")
        self.defaultCount.write(indenter, True)
        indenter("SYSTEM_SPACE", self.systemSpacing)
        self.fontOptions.write(indenter)


    def read(self, handle):
        # Read from the input file
        scoreIterator = fileUtils.dbFileIterator(handle)
        self.lilyFill = False
        def _readMeasure(lineData):
            measureWidth = int(lineData)
            measure = self.insertMeasureByIndex(measureWidth)
            measure.read(scoreIterator)
        with scoreIterator.section(None, None) as section:
            section.readSubsection("SCORE_METADATA", self.scoreData.load)
            section.readCallback("START_BAR", _readMeasure)
            section.readSubsection("KIT_START", self.drumKit.read)
            section.readCallback("SECTION_TITLE",
                                 self._sections.append)
            section.readString("PAPER_SIZE", self, "paperSize")
            section.readPositiveInteger("LILYSIZE", self, "lilySize")
            section.readNonNegativeInteger("LILYPAGES", self, "lilyPages")
            section.readBoolean("LILYFILL", self, "lilyFill")
            section.readSubsection("DEFAULT_COUNT_INFO_START",
                                   lambda i: self.defaultCount.read(i, True))
            section.readNonNegativeInteger("SYSTEM_SPACE", self,
                                           "systemSpacing")
            section.readSubsection("FONT_OPTIONS_START", self.fontOptions.read)
        # Check that all the note heads are valid
        for measure in self.iterMeasures():
            for np, head in measure:
                if not self.drumKit[np.drumIndex].isAllowedHead(head):
                    self.drumKit[np.drumIndex].addNoteHead(head)
        # Format the score appropriately
        self.formatScore(self.scoreData.width)
        # Make sure we've got the right number of section titles
        assert(all(staff.isConsistent() for staff in self.iterStaffs()))
        numSections = len([staff for staff in self.iterStaffs()
                           if staff.isSectionEnd()])
        if numSections > self.numSections():
            self._sections += ["Section Title"] * (numSections
                                                   - self.numSections())
        elif numSections < self.numSections():
            self._sections = self._sections[:numSections]

    def hashScore(self):
        scoreString = StringIO()
        self.write(scoreString)
        scoreString = scoreString.getvalue()
        return hashlib.md5(scoreString).digest() #pylint:disable-msg=E1101
Exemple #4
0
class Score(object):
    '''
    classdocs
    '''
    def __init__(self):
        '''
        Constructor
        '''
        self._staffs = []
        self.drumKit = DrumKit.DrumKit()
        self._callBack = None
        self._callBacksEnabled = True
        self.scoreData = ScoreMetaData()
        self._sections = []
        self._formatState = None
        self.paperSize = "Letter"
        counter = CounterRegistry().getCounterByIndex(0)
        self.defaultCount = makeSimpleCount(counter, 4)
        self.systemSpacing = 25
        self.fontOptions = FontOptions()
        self.lilysize = 20
        self.lilypages = 0
        self.lilyFill = False

    def __len__(self):
        return sum(len(staff) for staff in self._staffs)

    def _runCallBack(self, position):
        if self._callBack is not None and self._callBacksEnabled:
            self._callBack(position)

    def turnOnCallBacks(self):
        self._callBacksEnabled = True

    def turnOffCallBacks(self):
        self._callBacksEnabled = False

    def setCallBack(self, callBack):
        self._callBack = callBack

    def clearCallBack(self):
        self._callBack = None

    def _setStaffCallBack(self, staff, staffIndex):
        def wrappedCallBack(position):
            position.staffIndex = staffIndex
            self._runCallBack(position)

        staff.setCallBack(wrappedCallBack)

    def _checkStaffIndex(self, index):
        if not (0 <= index < self.numStaffs()):
            raise BadTimeError()

    def _checkDrumIndex(self, index):
        if not (0 <= index < len(self.drumKit)):
            raise BadTimeError()

    def _checkMeasureIndex(self, index, endOk=False):
        if not (0 <= index < self.numMeasures()):
            if not (endOk and index == self.numMeasures()):
                raise BadTimeError()

    def iterStaffs(self):
        return iter(self._staffs)

    def iterMeasures(self):
        for staff in self.iterStaffs():
            for measure in staff:
                yield measure

    def iterMeasuresBetween(self, start, end):
        if self.getMeasureIndex(end) < self.getMeasureIndex(start):
            start, end = end, start
        staffIndex = start.staffIndex
        measureIndex = start.measureIndex
        absIndex = self.getMeasureIndex(start)
        while staffIndex < end.staffIndex:
            staff = self.getStaff(staffIndex)
            while measureIndex < staff.numMeasures():
                yield (staff[measureIndex], absIndex,
                       NotePosition(staffIndex, measureIndex))
                measureIndex += 1
                absIndex += 1
            measureIndex = 0
            staffIndex += 1
        staff = self.getStaff(staffIndex)
        while measureIndex <= end.measureIndex:
            yield (staff[measureIndex], absIndex,
                   NotePosition(staffIndex, measureIndex))
            absIndex += 1
            measureIndex += 1

    @staticmethod
    def _readAlternates(text):
        alternates = [t.strip() for t in text.split(",")]
        theseAlternates = set()
        for aText in alternates:
            if "-" in aText:
                aStart, aEnd = aText.split("-")
                for aVal in xrange(int(aStart), int(aEnd.rstrip(".")) + 1):
                    theseAlternates.add(aVal)
            else:
                theseAlternates.add(int(aText.rstrip(".")))
        return theseAlternates

    @staticmethod
    def _findRepeatData(measures, index, alternateIndexes):
        start = index
        numMeasures = len(measures)
        numRepeats = 0
        alternates = set()
        while index < numMeasures:
            measure = measures[index]
            if index in alternateIndexes:
                numRepeats += len(alternateIndexes[index])
                alternates.update(alternateIndexes[index])
            if (measure.isRepeatEnd() or measure.isSectionEnd()):
                if alternates:
                    if (index + 1 == numMeasures
                            or not measures[index + 1].alternateText
                            or measures[index + 1].isRepeatStart()
                            or measure.isSectionEnd()):
                        if alternates != set(xrange(1, numRepeats + 1)):
                            raise InconsistentRepeats(start, index)
                        return index + 1, numRepeats
                else:
                    numRepeats = measure.repeatCount
                    return index + 1, numRepeats
            index += 1
        return index, numRepeats

    def iterMeasuresWithRepeats(self):
        measures = list(self.iterMeasures())
        alternates = {}
        repeatData = {}
        repeatStart = -1
        for index, measure in enumerate(measures):
            if measure.alternateText:
                alternates[index] = self._readAlternates(measure.alternateText)
        for index, measure in enumerate(measures):
            if measure.isRepeatStart():
                repeatData[index] = self._findRepeatData(
                    measures, index, alternates)
        index = 0
        repeatStart = 0
        repeatNum = -1
        afterRepeat = -1
        latestRepeatEnd = -1
        numMeasures = len(measures)
        alternateStarts = {}
        while index < numMeasures:
            measure = measures[index]
            if measure.isRepeatStart():
                repeatStart = index
                afterRepeat, numRepeats = repeatData[index]
                repeatNum += 1
            if index in alternates and repeatNum > -1:
                theseAlternates = alternates[index]
                alternateStarts.update(
                    (aStart, index) for aStart in theseAlternates)
                if repeatNum + 1 not in theseAlternates:
                    index = alternateStarts.get(repeatNum + 1,
                                                latestRepeatEnd + 1)
                    continue
            yield measure, index
            if measure.isRepeatEnd() and repeatNum > -1:
                if alternateStarts:
                    if repeatNum < numRepeats - 1:
                        if index > latestRepeatEnd:
                            latestRepeatEnd = index
                        index = repeatStart
                    else:
                        index = afterRepeat
                        repeatNum = -1
                        alternateStarts = {}
                elif repeatNum < numRepeats - 1:
                    index = repeatStart
                else:
                    index += 1
                    repeatNum = -1
            elif measure.isSectionEnd():
                repeatNum = -1
                index += 1
                alternateStarts = {}
            else:
                index += 1

    def getMeasure(self, index):
        self._checkMeasureIndex(index)
        staff, index = self._staffContainingMeasure(index)
        return staff[index]

    def getItemAtPosition(self, position):
        if position.staffIndex is None:
            return self
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        if position.measureIndex is None:
            return staff
        if position.drumIndex is not None:
            self._checkDrumIndex(position.drumIndex)
        return staff.getItemAtPosition(position)

    def getStaff(self, index):
        return self._staffs[index]

    def numStaffs(self):
        return len(self._staffs)

    def _addStaff(self):
        newStaff = Staff()
        self._staffs.append(newStaff)
        self._setStaffCallBack(newStaff, self.numStaffs() - 1)

    def _deleteStaffByIndex(self, index):
        self._checkStaffIndex(index)
        staff = self._staffs[index]
        staff.clearCallBack()
        if staff.isSectionEnd():
            if index == 0 or self.getStaff(index - 1).isSectionEnd():
                position = NotePosition(staffIndex=index)
                sectionIndex = self.getSectionIndex(position)
                self._deleteSectionTitle(sectionIndex)
            else:
                prevStaff = self.getStaff(index - 1)
                position = NotePosition(staffIndex=index - 1,
                                        measureIndex=prevStaff.numMeasures() -
                                        1)
                prevStaff.setSectionEnd(position, True)
        self._staffs.pop(index)
        for offset, nextStaff in enumerate(self._staffs[index:]):
            self._setStaffCallBack(nextStaff, index + offset)

    def numMeasures(self):
        return sum(staff.numMeasures() for staff in self.iterStaffs())

    def _staffContainingMeasure(self, index):
        measuresSoFar = 0
        for staff in self.iterStaffs():
            if measuresSoFar <= index < measuresSoFar + staff.numMeasures():
                return staff, index - measuresSoFar
            measuresSoFar += staff.numMeasures()
        raise BadTimeError()

    def getMeasureIndex(self, position):
        self._checkStaffIndex(position.staffIndex)
        index = 0
        for staffIndex in range(0, position.staffIndex):
            index += self.getStaff(staffIndex).numMeasures()
        index += position.measureIndex
        return index

    def getMeasurePosition(self, index):
        staffIndex = 0
        staff = self.getStaff(0)
        while index >= staff.numMeasures():
            index -= staff.numMeasures()
            staffIndex += 1
            if staffIndex == self.numStaffs():
                break
            staff = self.getStaff(staffIndex)
        if staffIndex == self.numStaffs():
            raise BadTimeError(index)
        return NotePosition(staffIndex=staffIndex, measureIndex=index)

    def insertMeasureByIndex(self, width, index=None, counter=None):
        if index is None:
            index = self.numMeasures()
        if self.numStaffs() == 0:
            self._addStaff()
            staff = self.getStaff(0)
        elif index == self.numMeasures():
            staff = self.getStaff(-1)
            index = staff.numMeasures()
        else:
            staff, index = self._staffContainingMeasure(index)
        newMeasure = Measure(width)
        newMeasure.counter = counter
        staff.insertMeasure(NotePosition(measureIndex=index), newMeasure)
        return newMeasure

    def insertMeasureByPosition(self, width, position=None, counter=None):
        if position is None:
            if self.numStaffs() == 0:
                self._addStaff()
            position = NotePosition(self.numStaffs() - 1)
            staff = self.getStaff(self.numStaffs() - 1)
            position.measureIndex = staff.numMeasures()
        self._checkStaffIndex(position.staffIndex)
        newMeasure = Measure(width)
        newMeasure.counter = counter
        staff = self.getStaff(position.staffIndex)
        staff.insertMeasure(position, newMeasure)
        return newMeasure

    def deleteMeasureByIndex(self, index):
        self._checkMeasureIndex(index)
        np = self.getMeasurePosition(index)
        self.deleteMeasureByPosition(np)

    def deleteMeasureByPosition(self, position):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        if (staff.isSectionEnd()
                and position.measureIndex == staff.numMeasures() - 1):
            sectionIndex = self.getSectionIndex(position)
            self._deleteSectionTitle(sectionIndex)
        staff.deleteMeasure(position)

    def deleteMeasuresAtPosition(self, position, numToDelete):
        position = position.makeMeasurePosition()
        staff = self.getStaff(position.staffIndex)
        for dummyIndex in xrange(numToDelete):
            if position.measureIndex == staff.numMeasures():
                if staff.numMeasures() == 0:
                    self._deleteStaffByIndex(position.staffIndex)
                else:
                    position.staffIndex += 1
                    position.measureIndex = 0
                staff = self.getStaff(position.staffIndex)
            staff.deleteMeasure(position)

    def trailingEmptyMeasures(self):
        emptyMeasures = []
        np = NotePosition(staffIndex=self.numStaffs() - 1)
        staff = self.getItemAtPosition(np)
        np.measureIndex = staff.numMeasures() - 1
        measure = self.getItemAtPosition(np)
        while ((np.staffIndex > 0 or np.measureIndex > 0)
               and measure.isEmpty()):  # pylint:disable-msg=E1103
            emptyMeasures.append(np.makeMeasurePosition())
            if np.measureIndex == 0:
                np.staffIndex -= 1
                staff = self.getStaff(np.staffIndex)
                np.measureIndex = staff.numMeasures()
            np.measureIndex -= 1
            measure = self.getItemAtPosition(np)
        return emptyMeasures

    def copyMeasure(self, position):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        return staff.copyMeasure(position)

    def pasteMeasure(self, position, notes, copyMeasureDecorations=False):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        return staff.pasteMeasure(position, notes, copyMeasureDecorations)

    def pasteMeasureByIndex(self, index, notes, copyMeasureDecorations=False):
        position = self.getMeasurePosition(index)
        self.pasteMeasure(position, notes, copyMeasureDecorations)

    def numSections(self):
        return len(self._sections)

    def getSectionIndex(self, position):
        ends = []
        for staffIndex, staff in enumerate(self.iterStaffs()):
            assert (staff.isConsistent())
            if staff.isSectionEnd():
                ends.append(staffIndex)
        assert (len(ends) == self.numSections())
        return bisect.bisect_left(ends, position.staffIndex)

    def getSectionTitle(self, index):
        return self._sections[index]

    def setSectionTitle(self, index, title):
        self._sections[index] = title

    def _deleteSectionTitle(self, index):
        self._sections.pop(index)

    def getSectionStartStaffIndex(self, position):
        startIndex = position.staffIndex
        while (startIndex > 0
               and not self.getStaff(startIndex - 1).isSectionEnd()):
            startIndex -= 1
        return startIndex

    def deleteSection(self, position):
        sectionIndex = self.getSectionIndex(position)
        if sectionIndex == self.numSections():
            return
        startIndex = position.staffIndex
        while (startIndex > 0
               and not self.getStaff(startIndex - 1).isSectionEnd()):
            startIndex -= 1
        while not self.getStaff(startIndex).isSectionEnd():
            self._deleteStaffByIndex(startIndex)
        self._deleteStaffByIndex(startIndex)

    def iterSections(self):
        return iter(self._sections)

    def setSectionEnd(self, position, onOff):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        sectionIndex = self.getSectionIndex(position)
        if onOff:
            self._sections.insert(sectionIndex, "Section Title")
        else:
            if sectionIndex < self.numSections():
                self._sections.pop(sectionIndex)
        staff.setSectionEnd(position, onOff)

    def iterMeasuresInSection(self, sectionIndex):
        if not (0 <= sectionIndex < self.numSections()):
            raise BadTimeError()
        thisSection = 0
        inSection = (thisSection == sectionIndex)
        for measure in self.iterMeasures():
            if inSection:
                yield measure
                if measure.isSectionEnd():
                    break
            if measure.isSectionEnd():
                thisSection += 1
                inSection = (thisSection == sectionIndex)

    def nextMeasure(self, position):
        self._checkStaffIndex(position.staffIndex)
        staff = self.getStaff(position.staffIndex)
        if not (0 <= position.measureIndex < staff.numMeasures()):
            raise BadTimeError()
        position = position.makeMeasurePosition()
        position.measureIndex += 1
        if position.measureIndex == staff.numMeasures():
            position.staffIndex += 1
            if position.staffIndex == self.numStaffs():
                position.staffIndex = None
                position.measureIndex = None
            else:
                position.measureIndex = 0
        return position

    def insertSectionCopy(self, position, sectionIndex):
        self.turnOffCallBacks()
        position = position.makeMeasurePosition()
        try:
            self._checkStaffIndex(position.staffIndex)
            sectionMeasures = list(self.iterMeasuresInSection(sectionIndex))
            sectionTitle = "Copy of " + self.getSectionTitle(sectionIndex)
            newIndex = self.getSectionIndex(position)
            self._sections.insert(newIndex, sectionTitle)
            for measure in sectionMeasures:
                newMeasure = self.insertMeasureByPosition(
                    len(measure), position, measure.counter)
                newMeasure.pasteMeasure(measure, True)
                position.measureIndex += 1
        finally:
            self.turnOnCallBacks()

    def addNote(self, position, head=None):
        self._checkStaffIndex(position.staffIndex)
        self._checkDrumIndex(position.drumIndex)
        if head is None:
            head = self.drumKit[position.drumIndex].head
        self.getStaff(position.staffIndex).addNote(position, head)

    def deleteNote(self, position):
        self._checkStaffIndex(position.staffIndex)
        self._checkDrumIndex(position.drumIndex)
        self.getStaff(position.staffIndex).deleteNote(position)

    def toggleNote(self, position, head=None):
        self._checkStaffIndex(position.staffIndex)
        self._checkDrumIndex(position.drumIndex)
        if head is None:
            head = self.drumKit[position.drumIndex].head
        self.getStaff(position.staffIndex).toggleNote(position, head)

    def notePlus(self, pos, ticks):
        self._checkStaffIndex(pos.staffIndex)
        self._checkDrumIndex(pos.drumIndex)
        staff = self.getStaff(pos.staffIndex)
        measure = staff[pos.measureIndex]
        pos.noteTime += ticks
        while pos.noteTime >= len(measure):
            pos.noteTime -= len(measure)
            pos.measureIndex += 1
            if pos.measureIndex >= staff.numMeasures():
                pos.measureIndex = 0
                pos.staffIndex += 1
                if pos.staffIndex == self.numStaffs():
                    return None
                staff = self.getStaff(pos.staffIndex)
            measure = staff[pos.measureIndex]
        return pos

    def tickDifference(self, second, first):
        """Calculate the difference in ticks between NotePositions first and 
        second.
        """
        current = first.makeMeasurePosition()
        end = second.makeMeasurePosition()
        end.noteTime = None
        end.drumIndex = None
        ticks = 0
        direction = 1
        offset = first.noteTime
        if end < current:
            current, end = end, current
            direction = -1
            offset = second.noteTime
        while current < end:
            ticks += len(self.getItemAtPosition(current)) - offset
            current = self.nextMeasure(current)
            offset = 0
        if direction == 1:
            ticks += second.noteTime - offset
        else:
            ticks += first.noteTime - offset
        ticks *= direction
        return ticks

    def _getFormatState(self):
        return [(staff.numMeasures(), self.numVisibleLines(index))
                for index, staff in enumerate(self.iterStaffs())]

    def saveFormatState(self):
        self._formatState = self._getFormatState()

    def formatScore(self, width=None, ignoreErrors=True):
        if width is None:
            width = self.scoreData.width
        measures = list(self.iterMeasures())
        if not self._formatState:
            self.saveFormatState()
        for staff in self.iterStaffs():
            staff.clear()
        staff = self.getStaff(0)
        staffIndex = 0
        for measureIndex, measure in enumerate(measures):
            staff.addMeasure(measure)
            while staff.gridWidth() > width:
                if staff.numMeasures() == 1:
                    if ignoreErrors:
                        break
                    else:
                        raise OverSizeMeasure(measure)
                else:
                    staff.deleteLastMeasure()
                    staffIndex += 1
                    if staffIndex == self.numStaffs():
                        self._addStaff()
                    staff = self.getStaff(staffIndex)
                    staff.addMeasure(measure)
            if (measure.isLineEnd() and measureIndex != len(measures) - 1):
                staffIndex += 1
                if staffIndex == self.numStaffs():
                    self._addStaff()
                staff = self.getStaff(staffIndex)
        while self.numStaffs() > staffIndex + 1:
            staff = self._staffs.pop()
            staff.clearCallBack()
        return self._formatState != self._getFormatState()

    def changeKit(self, newKit, changes):
        for measure in self.iterMeasures():
            measure.changeKit(newKit, changes)
        self.drumKit = newKit

    def numVisibleLines(self, index):
        if self.scoreData.emptyLinesVisible:
            return len(self.drumKit)
        else:
            staff = self.getStaff(index)
            count = sum(drum.locked or staff.lineIsVisible(index)
                        for index, drum in enumerate(self.drumKit))
            if count == 0:
                return 1
            else:
                return count

    def nthVisibleLineIndex(self, staffIndex, lineIndex):
        count = -1
        staff = self.getStaff(staffIndex)
        for lineNum, drum in enumerate(self.drumKit):
            if (drum.locked or self.scoreData.emptyLinesVisible
                    or staff.lineIsVisible(lineNum)):
                count += 1
                if count == lineIndex:
                    return lineNum
        if count == -1 and lineIndex == 0:
            return 0
        raise BadTimeError(staffIndex)

    def _iterLinesLockedOrWithNotes(self, staffIndex):
        staff = self.getStaff(staffIndex)
        count = 0
        for lineNum, drum in enumerate(self.drumKit):
            if (drum.locked or staff.lineIsVisible(lineNum)):
                count += 1
                yield drum
        if count == 0:
            yield self.drumKit[0]

    def iterVisibleLines(self, staffIndex, forceIgnoreEmpty=False):
        if self.scoreData.emptyLinesVisible and not forceIgnoreEmpty:
            return iter(self.drumKit)
        else:
            return self._iterLinesLockedOrWithNotes(staffIndex)

    def write(self, handle):
        indenter = fileUtils.Indenter(handle)
        self.scoreData.save(indenter)
        self.drumKit.write(indenter)
        for measure in self.iterMeasures():
            measure.write(indenter)
        for title in self._sections:
            indenter("SECTION_TITLE", title)
        indenter("PAPER_SIZE", self.paperSize)
        indenter("LILYSIZE", self.lilysize)
        indenter("LILYPAGES", self.lilypages)
        if self.lilyFill:
            indenter("LILYFILL", "YES")
        self.defaultCount.write(indenter, True)
        indenter("SYSTEM_SPACE", self.systemSpacing)
        self.fontOptions.write(indenter)

    def read(self, handle):
        # Read from the input file
        scoreIterator = fileUtils.dbFileIterator(handle)
        self.lilyFill = False

        def _readMeasure(lineData):
            measureWidth = int(lineData)
            measure = self.insertMeasureByIndex(measureWidth)
            measure.read(scoreIterator)

        with scoreIterator.section(None, None) as section:
            section.readSubsection("SCORE_METADATA", self.scoreData.load)
            section.readCallback("START_BAR", _readMeasure)
            section.readSubsection("KIT_START", self.drumKit.read)
            section.readCallback("SECTION_TITLE", self._sections.append)
            section.readString("PAPER_SIZE", self, "paperSize")
            section.readPositiveInteger("LILYSIZE", self, "lilySize")
            section.readNonNegativeInteger("LILYPAGES", self, "lilyPages")
            section.readBoolean("LILYFILL", self, "lilyFill")
            section.readSubsection("DEFAULT_COUNT_INFO_START",
                                   lambda i: self.defaultCount.read(i, True))
            section.readNonNegativeInteger("SYSTEM_SPACE", self,
                                           "systemSpacing")
            section.readSubsection("FONT_OPTIONS_START", self.fontOptions.read)
        # Check that all the note heads are valid
        for measure in self.iterMeasures():
            for np, head in measure:
                if not self.drumKit[np.drumIndex].isAllowedHead(head):
                    self.drumKit[np.drumIndex].addNoteHead(head)
        # Format the score appropriately
        self.formatScore(self.scoreData.width)
        # Make sure we've got the right number of section titles
        assert (all(staff.isConsistent() for staff in self.iterStaffs()))
        numSections = len(
            [staff for staff in self.iterStaffs() if staff.isSectionEnd()])
        if numSections > self.numSections():
            self._sections += ["Section Title"
                               ] * (numSections - self.numSections())
        elif numSections < self.numSections():
            self._sections = self._sections[:numSections]

    def hashScore(self):
        scoreString = StringIO()
        self.write(scoreString)
        scoreString = scoreString.getvalue()
        return hashlib.md5(scoreString).digest()  #pylint:disable-msg=E1101