Ejemplo n.º 1
0
    def checkForExplicitVerticalRestPositions(self) -> None:
        # default to treble clef
        defaultBaseline: int = Convert.kernClefToBaseline('*clefG2')
        baselines: t.List[int] = [defaultBaseline] * (self.maxTrack + 1)

        for line in self._lines:
            if line.isInterpretation:
                for tok in line.tokens():
                    if not tok.isKern:
                        continue
                    if not tok.isClef:
                        continue

                    baselines[tok.track] = Convert.kernClefToBaseline(tok.text)

            if not line.isData:
                continue

            for tok in line.tokens():
                if not tok.isKern:
                    continue
                if not tok.isRest:
                    continue

                self._checkRestForVerticalPositioning(tok,
                                                      baselines[tok.track])
Ejemplo n.º 2
0
    def _parseEventsIn(self, m21Stream: Union[m21.stream.Voice, m21.stream.Measure],
                             voiceIndex: int,
                             emptyStartDuration: HumNum = HumNum(0),
                             emptyEndDuration: HumNum = HumNum(0)):
        if emptyStartDuration > 0:
            # make m21 hidden rests totalling this duration, and pretend they
            # were at the beginning of m21Stream
            durations: List[HumNum] = Convert.getPowerOfTwoDurationsWithDotsAddingTo(emptyStartDuration)
            startTime: HumNum = self.startTime
            for duration in durations:
                m21StartRest: m21.note.Rest = m21.note.Rest(duration=m21.duration.Duration(duration))
                m21StartRest.style.hideObjectOnPrint = True
                event: EventData = EventData(m21StartRest, -1, voiceIndex, self, offsetInScore=startTime)
                if event is not None:
                    self.events.append(event)
                startTime += duration

        m21FlatStream: Union[m21.stream.Voice, m21.stream.Measure] = m21Stream.flat
        for elementIndex, element in enumerate(m21FlatStream):
            event: EventData = EventData(element, elementIndex, voiceIndex, self)
            if event is not None:
                self.events.append(event)

        if emptyEndDuration > 0:
            # make m21 hidden rests totalling this duration, and pretend they
            # were at the end of m21Stream
            durations: List[HumNum] = Convert.getPowerOfTwoDurationsWithDotsAddingTo(emptyEndDuration)
            startTime: HumNum = self.startTime + self.duration - emptyEndDuration
            for duration in durations:
                m21EndRest: m21.note.Rest = m21.note.Rest(duration=m21.duration.Duration(duration))
                m21EndRest.style.hideObjectOnPrint = True
                event: EventData = EventData(m21EndRest, -1, voiceIndex, self, offsetInScore=startTime)
                if event is not None:
                    self.events.append(event)
                startTime += duration
Ejemplo n.º 3
0
    def _parseEventsIn(
            self,
            m21Stream: t.Union[m21.stream.Voice, m21.stream.Measure],
            voiceIndex: int,
            emptyStartDuration: HumNumIn = 0,
            emptyEndDuration: HumNumIn = 0
    ) -> None:
        event: EventData
        durations: t.List[HumNum]
        startTime: HumNum
        if emptyStartDuration > 0:
            # make m21 hidden rests totalling this duration, and pretend they
            # were at the beginning of m21Stream
            durations = Convert.getPowerOfTwoDurationsWithDotsAddingTo(emptyStartDuration)
            startTime = self.startTime
            for duration in durations:
                m21StartRest: m21.note.Rest = m21.note.Rest(
                    duration=m21.duration.Duration(duration)
                )
                m21StartRest.style.hideObjectOnPrint = True
                event = EventData(m21StartRest, -1, voiceIndex, self, offsetInScore=startTime)
                if event is not None:
                    self.events.append(event)
                startTime = opFrac(startTime + duration)

        for elementIndex, element in enumerate(m21Stream.recurse()
                                                    .getElementsNotOfClass(m21.stream.Stream)):
            event = EventData(element, elementIndex, voiceIndex, self)
            if event is not None:
                self.events.append(event)
                # Make a separate event for any DynamicWedge (in score's
                #   spannerBundle) that starts with this element.
                #   Why?
                #       1. So we don't put the end of the wedge in the same slice as the
                #           endNote (wedges end at the end time of the endNote, not at
                #           the start time of the endNote).
                #       2. So wedge starts/ends will go in their own slice if necessary (e.g.
                #           if we choose not to export the voice-with-only-invisible-rests we
                #           may have made to position them correctly.
                extraEvents: t.List[EventData] = self._parseDynamicWedgesStartedOrStoppedAt(event)
                if extraEvents:
                    self.events += extraEvents

        if emptyEndDuration > 0:
            # make m21 hidden rests totalling this duration, and pretend they
            # were at the end of m21Stream
            durations = Convert.getPowerOfTwoDurationsWithDotsAddingTo(emptyEndDuration)
            startTime = opFrac(self.startTime + self.duration - opFrac(emptyEndDuration))
            for duration in durations:
                m21EndRest: m21.note.Rest = m21.note.Rest(duration=m21.duration.Duration(duration))
                m21EndRest.style.hideObjectOnPrint = True
                event = EventData(m21EndRest, -1, voiceIndex, self, offsetInScore=startTime)
                if event is not None:
                    self.events.append(event)
                startTime = opFrac(startTime + duration)
Ejemplo n.º 4
0
    def generateLinkedTieStartsAndEnds(self,
                                       linkedTieStarts: [tuple],
                                       linkedTieEnds: [tuple],
                                       linkSignifier: str) -> bool:
        # Use this in the future to limit to grand-staff search (or 3 staves for organ):
        # vector<vector<HTp> > tracktokens;
        # this->getTrackSeq(tracktokens, spinestart, OPT_DATA | OPT_NOEMPTY);

        # Only analyzing linked ties for now (others ties are handled without analysis in
        # HumdrumFile.createMusic21Stream, for example).
        if linkSignifier is None or linkSignifier == '':
            return True

        lstart: str     = linkSignifier + '['
        lmiddle: str    = linkSignifier + '_'
        lend: str       = linkSignifier + ']'

        startDatabase: [tuple] = [(None,-1)] * 400

        for line in self._lines:
            if not line.isData:
                continue

            for tok in line.tokens():
                if not tok.isKern:
                    continue
                if not tok.isData:
                    continue
                if tok.isNull:
                    continue
                if tok.isRest:
                    continue

                for subtokenIdx, subtokenStr in enumerate(tok.subtokens):
                    if lstart in subtokenStr:
                        b40: int = Convert.kernToBase40(subtokenStr)
                        startDatabase[b40] = (tok, subtokenIdx)

                    if lend in subtokenStr:
                        b40: int = Convert.kernToBase40(subtokenStr)
                        if startDatabase[b40][0] is not None:
                            linkedTieStarts.append(startDatabase[b40])
                            linkedTieEnds.append( (tok, subtokenIdx) )
                            startDatabase[b40] = (None, -1)

                    if lmiddle in subtokenStr:
                        b40: int = Convert.kernToBase40(subtokenStr)
                        if startDatabase[b40][0] is not None:
                            linkedTieStarts.append(startDatabase[b40])
                            linkedTieEnds.append( (tok, subtokenIdx) )
                        startDatabase[b40] = (tok, subtokenIdx)

        return True
Ejemplo n.º 5
0
    def analyzeRScale(self) -> bool:
        numActiveTracks: int = 0  # number of tracks currently having an active rscale parameter
        rscales: t.List[HumNum] = [opFrac(1)] * (self.maxTrack + 1)
        ttrack: int

        for line in self._lines:
            if line.isInterpretation:
                for token in line.tokens():
                    if not token.isKern:
                        continue

                    if not token.text.startswith('*rscale:'):
                        continue

                    value: HumNum = opFrac(1)
                    m = re.search(r'\*rscale:(\d+)/(\d+)', token.text)
                    if m is not None:
                        top: int = int(m.group(1))
                        bot: int = int(m.group(2))
                        value = opFrac(Fraction(top, bot))
                    else:
                        m = re.search(r'\*rscale:(\d+)', token.text)
                        if m is not None:
                            top = int(m.group(1))
                            value = opFrac(top)

                    ttrack = token.track
                    if value == 1:
                        if rscales[ttrack] != 1:
                            rscales[ttrack] = opFrac(1)
                            numActiveTracks -= 1
                    else:
                        if rscales[ttrack] == 1:
                            numActiveTracks += 1
                        rscales[ttrack] = value
                continue

            if numActiveTracks == 0:
                continue

            if not line.isData:
                continue

            for token in line.tokens():
                ttrack = token.track
                if rscales[ttrack] == 1:
                    continue
                if not token.isKern:
                    continue
                if token.isNull:
                    continue
                if token.duration < 0:
                    continue

                dur: HumNum = opFrac(token.durationNoDots * rscales[ttrack])
                vis: str = Convert.durationToRecip(dur)
                vis += '.' * token.dotCount
                token.setValue('LO', 'N', 'vis', vis)

        return True
Ejemplo n.º 6
0
    def assignRhythmFromRecip(self, spineStart: HumdrumToken) -> bool:
        currTok: t.Optional[HumdrumToken] = spineStart
        while currTok is not None:
            if not currTok.isData:
                currTok = currTok.nextToken0
                continue

            if currTok.isNull:
                # This should not occur in a well-formed **recip spine, but
                # treat as a zero duration.
                currTok = currTok.nextToken0
                continue

            currTok.ownerLine.duration = Convert.recipToDuration(currTok.text)
            currTok = currTok.nextToken0

        # now go back and set the absolute position from the start of the file.
        totalDurSoFar: HumNum = opFrac(0)
        for line in self._lines:
            line.durationFromStart = totalDurSoFar
            if line.duration is None or line.duration < 0:
                line.duration = opFrac(0)
            totalDurSoFar = opFrac(totalDurSoFar + line.duration)

        # Analyze durations to/from barlines:
        success = self.analyzeMeter()
        if not success:
            return False
        success = self.analyzeNonNullDataTokens()
        if not success:
            return False

        return True
Ejemplo n.º 7
0
    def _checkRestForVerticalPositioning(rest: HumdrumToken,
                                         baseline: int) -> bool:
        m = re.search(r'([A-Ga-g]+)', rest.text)
        if m is None:
            return False

        pitch: str = m.group(1)
        b7: int = Convert.kernToBase7(pitch)
        if b7 < 0:
            # that wasn't really a pitch, no vertical positioning for you
            return False

        diff: int = (b7 - baseline) + 100
        if diff % 2 != 0:
            # force to every other diatonic step (staff lines)
            if rest.duration > 1:
                b7 -= 1
            else:
                b7 += 1

        # Instead of ploc and oloc for MEI (which humlib does), compute and save what the
        # stepShift for a music21 rest should be.
        # stepShift = 0 means "on the middle line", -1 means "in the first space below the
        # midline", +2 means "on the line above the midline", etc.
        # baseline is the base7 pitch of the lowest line in the staff -> stepShift = -4
        # b7 is the base7 pitch (including octave) of where the rest should go, so:

        # midline is 2 spaces + 2 lines (4 diatonic steps) above baseline
        midline: int = baseline + 4
        stepShift: int = b7 - midline
        rest.setValue('auto', 'stepShift', str(stepShift))

        return True
Ejemplo n.º 8
0
    def processFile(self):
        if self.infileNeedsToBeParsed():
            self.infile.analyzeBase()
            self.infile.analyzeStructure()
            if not self.infile.isValid:
                return

        for _ in range(0, self.infile.maxTrack + 1):
            self.firstTremoloLinesInTrack.append([])
            self.lastTremoloLinesInTrack.append([])

        for line in reversed(list(self.infile.lines())):
            if not line.isData:
                continue
            if line.duration == 0:
                # don't deal with grace notes
                continue

            for token in line.tokens():
                if not token.isKern:
                    continue
                if token.isNull:
                    continue

                m = re.search(r'@(\d+)@', token.text)
                if not m:
                    continue

                self.markupTokens.insert(
                    0, token)  # markupTokens is in forward order

                value: int = int(m.group(1))
                duration: HumNum = Convert.recipToDuration(token.text)
                count: HumNum = duration
                count *= value
                count /= 4
                increment: HumNum = HumNum(4)
                increment /= value

                if '@@' in token.text:
                    count *= 2

                if count.denominator != 1:
                    print(f'Error: tremolo time value cannot be used: {value}',
                          file=sys.stderr)
                    continue

                kcount: int = count.numerator
                startTime: HumNum = token.durationFromStart
                for k in range(1, kcount):
                    timestamp: HumNum = startTime + (increment * k)
                    self.infile.insertNullDataLine(timestamp)

        self.expandTremolos()
        self.addTremoloInterpretations()
Ejemplo n.º 9
0
    def beat(self, beatDuration=HumNum(1, 4)) -> HumNum:
        if isinstance(beatDuration, HumNum):
            pass
        elif isinstance(beatDuration,
                        str):  # recip format string, e.g. '4' means 1/4
            beatDuration = Convert.recipToDuration(beatDuration)
        else:
            beatDuration = HumNum(beatDuration)

        if beatDuration == HumNum(0):
            return HumNum(0)
        beatInMeasure = (self.durationFromBarline / beatDuration) + 1
        return beatInMeasure
Ejemplo n.º 10
0
    def tpq(self) -> int:
        if self._ticksPerQuarterNote > 0:
            return self._ticksPerQuarterNote

        durationSet: t.Set[Fraction] = self.getPositiveLineDurationFractions()
        denoms: t.List[int] = []
        for dur in durationSet:
            if dur.denominator > 1:
                denoms.append(dur.denominator)

        lcm: int = 1
        if len(denoms) > 0:
            lcm = Convert.getLcm(denoms)
        self._ticksPerQuarterNote = lcm
        return self._ticksPerQuarterNote
Ejemplo n.º 11
0
    def beat(self, beatDuration: t.Union[str, HumNumIn] = Fraction(1, 4)) -> HumNum:
        if isinstance(beatDuration, str):  # recip format string, e.g. '4' means 1/4
            beatDuration = Convert.recipToDuration(beatDuration)
        else:
            beatDuration = opFrac(beatDuration)

        if t.TYPE_CHECKING:
            assert isinstance(beatDuration, (float, Fraction))

        if beatDuration == 0:
            # avoid divide by 0, just return beatInMeasure = 0
            return opFrac(0)

        beatInMeasure = opFrac((self.durationFromBarline / beatDuration) + 1)
        return beatInMeasure
Ejemplo n.º 12
0
    def hasPickup(self) -> int:
        barlineIdx: int = -1
        tsig: t.Optional[HumdrumToken] = None

        for i, line in enumerate(self._lines):
            if line.isBarline:
                if barlineIdx > 0:
                    # second barline found, so stop looking for time signature
                    break
                barlineIdx = i
                continue

            if not line.isInterpretation:
                continue

            if tsig is not None:
                continue

            for token in line.tokens():
                if token.isTimeSignature:
                    tsig = token
                    break

        if tsig is None:
            # no time signature so return 0
            return 0

        if barlineIdx < 0:
            # no barlines in music
            return 0

        mdur: HumNum = self._lines[barlineIdx].durationFromStart
        tdur: HumNum = Convert.timeSigToDuration(tsig)
        if mdur == tdur:
            # first measure is exactly as long as the time signature says: no pickup measure
            return 0

        return barlineIdx
Ejemplo n.º 13
0
def test_getMetronomeMarkInfo():
    # no parens 'M.M.'
    text = 'Vivace. M.M. [half-dot] = 69.'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, 'Vivace.')
    CheckString(mmStr, 'M.M.')
    CheckString(refStr, 'half-dot')
    CheckString(bpmStr, '69.')

    # no parens, no tempoName, 'M M'
    text = 'M M [half-dot] = 63.'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, '')
    CheckString(mmStr, 'M M')
    CheckString(refStr, 'half-dot')
    CheckString(bpmStr, '63.')

    # parens include M.M. (doesn't actually work, but we expect that so the test passes)
    text = '(M.M. [half] = 72.)'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, '(')  # we might should fix this... should be ''
    CheckString(mmStr, 'M.M.')
    CheckString(refStr, 'half')
    CheckString(bpmStr, '72.')

    # parens, no tempoName, no space between M.M. and open paren
    text = 'M.M.([quarter] = 160)'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, '')
    CheckString(mmStr, 'M.M.')
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '160')

    # no parens, no tempoName, no space between M.M. and open bracket
    text = 'M.M.[quarter] = 160'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, '')
    CheckString(mmStr, 'M.M.')
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '160')

    # bad: tempName at end (some of the Chopin first editions have this)
    # it will parse everything else correctly, but tempoName == ''
    text = 'M.M. [quarter]=104. Allegretto.'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, '')
    CheckString(mmStr, 'M.M.')
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '104.')

    # M:M: (ok fine)
    text = 'Allegro con fuoco. M:M: [quarter]=152.'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, 'Allegro con fuoco.')
    CheckString(mmStr, 'M:M:')
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '152.')

    # M. M.
    text = 'ANDANTE. M. M. [quarter] = 92.'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, 'ANDANTE.')
    CheckString(mmStr, 'M. M.')
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '92.')

    # M. M., parens no tempoName
    text = 'M. M. ([quarter] = 144)'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, '')
    CheckString(mmStr, 'M. M.')
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '144')

    # M. M., no parens, no tempoName
    text = 'M. M. [quarter]=34.'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, '')
    CheckString(mmStr, 'M. M.')
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '34.')

    # no mmStr, yes tempoName
    text = 'Allegro [quarter]=128'
    tempoName, mmStr, refStr, bpmStr = Convert.getMetronomeMarkInfo(text)
    CheckString(tempoName, 'Allegro')
    CheckIsNone(mmStr)
    CheckString(refStr, 'quarter')
    CheckString(bpmStr, '128')
Ejemplo n.º 14
0
    def analyzeKernAccidentals(self) -> bool:
        # ottava marks must be analyzed first:
        self.analyzeOttavas()

        # We will mark a "visible accidental" in four (Humdrum) situations:
        # 1. there is an 'n' specified with no "hidden" mark (a 'y' immediately
        #    following the 'n', or a 'yy' anywhere in the token)
        # 2. there is a single 'X' immediately following the '#', '-', or 'n'
        # 3. There is an editorial accidental signifier character immediately following
        #    the '#', '-', or 'n'.  We will also obey the edittype in the signifier.
        # 4. There is a linked layout param (('N', 'acc') and ('A', 'vis')) to specify
        #    display of a specific accidental (or combination, such as 'n#')

        # These are a part of what Verovio does.  I believe the rest of what Verovio does
        # is clearly about rendering decisions based on key signature and previous accidentals
        # in the measure, and thus belong in a renderer, not in a converter.

        for line in self._lines:
            if not line.hasSpines:
                continue

            if not line.isData:
                continue

            for token in line.tokens():
                if not token.isKern:
                    continue

                if token.isNull:
                    continue

                if token.isRest:
                    continue

                for k, subtok in enumerate(token.subtokens):
                    accid: int = Convert.kernToAccidentalCount(subtok)
                    isHidden: bool = False
                    if 'yy' not in subtok:  # if note is hidden accidental hiding isn't necessary
                        if ('ny' in subtok or '#y' in subtok
                                or '-y' in subtok):
                            isHidden = True

                    if accid == 0 and 'n' in subtok and not isHidden:
                        # if humdrum data specifies 'n', we'll put in a cautionary accidental
                        token.setValue('auto', str(k), 'cautionaryAccidental',
                                       'true')
                        token.setValue('auto', str(k), 'visualAccidental',
                                       'true')
                    elif 'XX' not in subtok:
                        # The accidental is not necessary. See if there is a single "X"
                        # immediately after the accidental which means to force it to
                        # display.
                        if '#X' in subtok or '-X' in subtok or 'nX' in subtok:
                            token.setValue('auto', str(k),
                                           'cautionaryAccidental', 'true')
                            token.setValue('auto', str(k), 'visualAccidental',
                                           'true')

                    # editorialAccidental analysis
                    isEditorial: bool = False
                    editType: str = ''
                    for x, signifier in enumerate(
                            self._signifiers.editorialAccidentals):
                        if signifier in subtok:
                            isEditorial = True
                            editType = self._signifiers.editorialAccidentalTypes[
                                x]
                            break

                    subTokenIdx: int = k
                    if len(token.subtokens) == 1:
                        subTokenIdx = -1
                    editType2: str = token.layoutParameter(
                        'A', 'edit', subTokenIdx)
                    if editType2 and not editType:
                        isEditorial = True
                        if editType2 == 'true':
                            # default editorial accidental type
                            editType = ''
                            # use the first editorial accidental RDF style in file if present
                            if self._signifiers.editorialAccidentalTypes:
                                editType = self._signifiers.editorialAccidentalTypes[
                                    0]
                        else:
                            editType = editType2

                    if isEditorial:
                        token.setValue('auto', str(k), 'editorialAccidental',
                                       'true')
                        token.setValue('auto', str(k), 'visualAccidental',
                                       'true')
                        if editType:
                            token.setValue('auto', str(k),
                                           'editorialAccidentalStyle',
                                           editType)

                    # layout parameters (('N', 'acc') and ('A', 'vis')) can also be used
                    # to specify a specific accidental or combination to be made visible.
                    # 'A', 'vis' takes priority over 'N', 'acc', if both are present
                    layoutAccidental: str = token.layoutParameter(
                        'A', 'vis', subTokenIdx)
                    if not layoutAccidental:
                        layoutAccidental = token.layoutParameter(
                            'N', 'acc', subTokenIdx)
                    if layoutAccidental:
                        token.setValue('auto', str(k), 'cautionaryAccidental',
                                       layoutAccidental)
                        token.setValue('auto', str(k), 'visualAccidental',
                                       layoutAccidental)

                    # check for accidentals on trills, mordents, and turns
#                     if 't' in subtok:
#                     elif 'T' in subtok:
#                     elif 'M' in subtok:
#                     elif 'm' in subtok:
#                     elif 'W' in subtok:
#                     elif 'w' in subtok:
#                     elif '$' in subtok:
#                     elif 'S' in subtok:

# Indicate that the accidental analysis has been done:
        self.setValue('auto', 'accidentalAnalysis', 'true')

        return True
Ejemplo n.º 15
0
    def expandTremolo(self, token: HumdrumToken):
        value: int = 0
        addBeam: bool = False
        tnotes: int = -1

        m = re.search(r'@(\d+)@', token.text)
        if not m:
            return

        value = int(m.group(1))
        duration: HumNum = Convert.recipToDuration(token.text)
        count: HumNum = HumNum(duration * value / 4)
        if count.denominator != 1:
            print(f'Error: non-integer number of tremolo notes: {token}',
                  file=sys.stderr)
            return
        if value < 8:
            print(
                f'Error: tremolo notes can only be eighth-notes or shorter: {token}',
                file=sys.stderr)
            return
        if float(duration) > 0.5:
            # needs to be less than one for tuplet quarter note tremolos
            addBeam = True

        # There are cases where duration < 1 need added beams
        # when the note is not already in a beam.  Such as
        # a plain 8th note with a slash.  This needs to be
        # converted into two 16th notes with a beam so that
        # *tremolo can reduce it back into a tremolo, since
        # it will only reduce beam groups.

        repeat: HumNum = duration
        repeat *= value
        repeat /= 4
        increment: HumNum = HumNum(4)
        increment /= value
        if repeat.denominator != 1:
            print(
                f'Error: tremolo repetition count must be an integer: {token}',
                file=sys.stderr)
            return
        tnotes = repeat.numerator

        self.storeFirstTremoloNoteInfo(token)

        beams: int = int(math.log(float(value), 2)) - 2
        markup: str = f'@{value.numerator}@'
        base: str = re.sub(markup, '', token.text)

        # complicated beamings are not allowed yet (no internal L/J markers in tremolo beam)
        hasBeamStart: bool = 'L' in base
        hasBeamStop: bool = 'J' in base

        if addBeam:
            hasBeamStart = True
            hasBeamStop = True

        # Currently not allowed to add tremolo to beamed notes, so remove all beaming:
        base = re.sub(r'[LJKk]+', '', base)
        startBeam: str = 'L' * beams
        endBeam: str = 'J' * beams

        # Set the rhythm of the tremolo notes.
        # Augmentation dot is expected adjacent to regular rhythm value.
        # Maybe allow anywhere?
        base = re.sub(r'\d+%?\d*\.*', str(value.numerator), base)
        initial: str = base
        if hasBeamStart:
            initial += startBeam
        terminal: str = base
        if hasBeamStop:
            terminal += endBeam

        # remove slur end from start of tremolo:
        terminal = re.sub(r'[(]+[<>]', '', terminal)

        token.text = initial
        token.ownerLine.createLineFromTokens()

        # Now fill in the rest of the tremolos.
        startTime: HumNum = token.durationFromStart
        timestamp: HumNum = startTime + increment
        currTok: HumdrumToken = token.nextToken(0)
        counter: int = 1

        while currTok is not None:
            if not currTok.isData:
                currTok = currTok.nextToken(0)
                continue

            duration: HumNum = currTok.ownerLine.duration
            if duration == 0:
                # grace note line, so skip
                currTok = currTok.nextToken(0)
                continue

            cstamp: HumNum = currTok.durationFromStart
            if cstamp < timestamp:
                currTok = currTok.nextToken(0)
                continue

            if cstamp > timestamp:
                print('\tWarning: terminating tremolo insertion early',
                      file=sys.stderr)
                print(f'\tCSTAMP : {cstamp} TSTAMP : {timestamp}',
                      file=sys.stderr)
                break

            counter += 1
            if counter == tnotes:
                currTok.text = terminal
                self.storeLastTremoloNoteInfo(currTok)
            else:
                currTok.text = base
            currTok.ownerLine.createLineFromTokens()
            if counter >= tnotes:
                # done with inserting of tremolo notes.
                break

            timestamp += increment
            currTok = currTok.nextToken(0)
Ejemplo n.º 16
0
    def generateLinkedTieStartsAndEnds(self, linkedTieStarts: t.List[t.Tuple[
        HumdrumToken, int]], linkedTieEnds: t.List[t.Tuple[HumdrumToken, int]],
                                       linkSignifier: str) -> bool:
        # Use this in the future to limit to grand-staff search (or 3 staves for organ):
        # vector<vector<HTp> > tracktokens;
        # this->getTrackSeq(tracktokens, spinestart, OPT_DATA | OPT_NOEMPTY);

        # Only analyzing linked ties for now (others ties are handled without analysis in
        # HumdrumFile.createMusic21Stream, for example).
        if linkSignifier is None or linkSignifier == '':
            return True

        lstart: str = linkSignifier + '['
        lmiddle: str = linkSignifier + '_'
        lend: str = linkSignifier + ']'

        startDatabase: t.List[t.Tuple[t.Optional[HumdrumToken],
                                      int]] = [(None, -1)] * 400

        for line in self._lines:
            if not line.isData:
                continue

            for tok in line.tokens():
                if not tok.isKern:
                    continue
                if not tok.isData:
                    continue
                if tok.isNull:
                    continue
                if tok.isRest:
                    continue

                for subtokenIdx, subtokenStr in enumerate(tok.subtokens):
                    if lstart in subtokenStr:
                        startb40: int = Convert.kernToBase40(subtokenStr)
                        startDatabase[startb40] = (tok, subtokenIdx)

                    if lend in subtokenStr:
                        endb40: int = Convert.kernToBase40(subtokenStr)
                        endEntry: t.Tuple[t.Optional[HumdrumToken],
                                          int] = startDatabase[endb40]
                        if endEntry[0] is not None:
                            if t.TYPE_CHECKING:
                                assert isinstance(endEntry[0], HumdrumToken)
                            linkedTieStarts.append((endEntry[0], endEntry[1]))
                            linkedTieEnds.append((tok, subtokenIdx))
                            startDatabase[endb40] = (None, -1)

                    if lmiddle in subtokenStr:
                        middleb40: int = Convert.kernToBase40(subtokenStr)
                        middleEntry: t.Tuple[t.Optional[HumdrumToken],
                                             int] = (startDatabase[middleb40])
                        if middleEntry[0] is not None:
                            if t.TYPE_CHECKING:
                                assert isinstance(middleEntry[0], HumdrumToken)
                            linkedTieStarts.append(
                                (middleEntry[0], middleEntry[1]))
                            linkedTieEnds.append((tok, subtokenIdx))
                        startDatabase[middleb40] = (tok, subtokenIdx)

        return True
Ejemplo n.º 17
0
    def expandFingerTremolo(self, token1: HumdrumToken, token2: HumdrumToken):
        if token2 is None:
            return

        m = re.search(r'@@(\d+)@@', token1.text)
        if not m:
            return

        value: int = int(m.group(1))
        if not Convert.isPowerOfTwo(HumNum(value)):
            print(f'Error: not a power of two: {token1}', file=sys.stderr)
            return
        if value < 8:
            print(
                f'Error: tremolo can only be eighth-notes or shorter: {token1}',
                file=sys.stderr)
            return

        duration: HumNum = Convert.recipToDuration(token1.text)
        count: HumNum = duration

        count *= value
        count /= 4
        if count.denominator != 1:
            print(
                f'Error: tremolo repetition count must be an integer: {token1}',
                file=sys.stderr)
            return
        increment: HumNum = HumNum(4)
        increment /= value

        tnotes: int = count.numerator * 2

        self.storeFirstTremoloNoteInfo(token1)

        beams: int = int(math.log(float(value), 2)) - 2
        markup: str = f'@@{value}@@'
        base1: str = token1.text
        base1 = re.sub(markup, '', base1)
        # Currently not allowed to add tremolo to beamed notes, so remove all beaming:
        base1 = re.sub(r'[LJKk]+', '', base1)
        startBeam: str = 'L' * beams
        endBeam: str = 'J' * beams

        # Set the rhythm of the tremolo notes.
        # Augmentation dot is expected adjacent to regular rhythm value.
        # Maybe allow anywhere?
        base1 = re.sub(r'\d+%?\d*\.*', str(value), base1)
        initial: str = base1 + startBeam
        # remove slur end from start of tremolo
        initial = re.sub(r'[)]+[<>]?', '', initial)

        # remove slur information from middle of tremolo
        base1 = re.sub(r'[()]+[<>]?', '', base1)

        token1.text = initial
        token1.ownerLine.createLineFromTokens()

        base2: str = token2.text
        base2 = re.sub(markup, '', base2)
        base2 = re.sub(r'[LJKk]+', '', base2)
        base2 = re.sub(r'\d+%?\d*\.*', str(value), base2)

        terminal: str = base2 + endBeam
        # remove slur start information from end of tremolo:
        terminal = re.sub(r'[(]+[<>]?', '', terminal)

        state: bool = False

        # Now fill in the rest of the tremolos.
        startTime: HumNum = token1.durationFromStart
        timestamp: HumNum = startTime + increment
        currTok: HumdrumToken = token1.nextToken(0)
        counter: int = 1
        while currTok is not None:
            if not currTok.isData:
                currTok = currTok.nextToken(0)
                continue

            cstamp: HumNum = currTok.durationFromStart
            if cstamp < timestamp:
                currTok = currTok.nextToken(0)
                continue

            if cstamp > timestamp:
                print('\tWarning: terminating tremolo insertion early',
                      file=sys.stderr)
                print(f'\tCSTAMP : {cstamp} TSTAMP : {timestamp}',
                      file=sys.stderr)
                break

            counter += 1
            if counter == tnotes:
                currTok.text = terminal
                self.storeLastTremoloNoteInfo(currTok)
            else:
                if state:
                    currTok.text = base1
                else:
                    currTok.text = base2
                state = not state

            currTok.ownerLine.createLineFromTokens()
            if counter >= tnotes:
                # done with inserting of tremolo notes
                break

            timestamp += increment
            currTok = currTok.nextToken(0)