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
def _createRecipTokenFromDuration(duration: HumNumIn) -> HumdrumToken: dur: HumNum = opFrac(duration) dur = opFrac(dur / opFrac(4)) # convert to quarter note units durFraction: Fraction = Fraction(dur) if durFraction.numerator == 0: # if the GridSlice is at the end of a measure, the # time between the starttime/endtime of the GridSlice should # be subtracted from the endtime of the current GridMeasure. return HumdrumToken('g') if durFraction.numerator == 1: return HumdrumToken(str(durFraction.denominator)) if durFraction.numerator % 3 == 0: dotdur: HumNum = dur * opFrac(Fraction(2, 3)) dotdurFraction: Fraction = Fraction(dotdur) if dotdurFraction.numerator == 1: return HumdrumToken(str(dotdurFraction.denominator) + '.') # try to fit to two dots here # try to fit to three dots here return HumdrumToken( str(durFraction.denominator) + '%' + str(durFraction.numerator))
def transferSidesFromStaff(line: HumdrumLine, staff: GridStaff, emptyStr: str, maxxcount: int, maxdcount: int, maxvcount: int): sides: GridSide = staff.sides vcount: int = sides.verseCount # XMLID if maxxcount > 0: xmlId: HumdrumToken = sides.xmlId if xmlId is not None: line.appendToken(xmlId) else: line.appendToken(HumdrumToken(emptyStr)) # DYNAMICS if maxdcount > 0: dynamics: HumdrumToken = sides.dynamics if dynamics is not None: line.appendToken(dynamics) else: line.appendToken(HumdrumToken(emptyStr)) # VERSES for i in range(0, vcount): verse: HumdrumToken = sides.getVerse(i) if verse is not None: line.appendToken(verse) else: line.appendToken(HumdrumToken(emptyStr)) for i in range(vcount, maxvcount): line.appendToken(HumdrumToken(emptyStr))
def prepareDurations(self, token: HumdrumToken, state: int, startDur: HumNumIn) -> bool: if state != token.rhythmAnalysisState: return self.isValid token.incrementRhythmAnalysisState() durSum: HumNum = opFrac(startDur) success = self.setLineDurationFromStart(token, durSum) if not success: return self.isValid if token.duration > 0: durSum = opFrac(durSum + token.duration) reservoir: t.List[HumdrumToken] = [] startDurs: t.List[HumNum] = [] # Assign line durationFromStarts for primary track first tcount: int = token.nextTokenCount while tcount > 0: for i, tok in enumerate(token.nextTokens): if i == 0: # we'll deal with token 0 ourselves below continue reservoir.append(tok) startDurs.append(durSum) if t.TYPE_CHECKING: # we know here that token.nextTokenCount > 0, so # token.nextToken0 is not None assert isinstance(token.nextToken0, HumdrumToken) token = token.nextToken0 if state != token.rhythmAnalysisState: break token.incrementRhythmAnalysisState() success = self.setLineDurationFromStart(token, durSum) if not success: return self.isValid if token.duration > 0: durSum = opFrac(durSum + token.duration) tcount = token.nextTokenCount if tcount == 0 and token.isTerminateInterpretation: success = self.setLineDurationFromStart(token, durSum) if not success: return self.isValid # Process secondary tracks next: newState: int = state for i in reversed(range(0, len(reservoir))): self.prepareDurations(reservoir[i], newState, startDurs[i]) return self.isValid
def addLayoutParameter(self, associatedSlice: GridSlice, partIndex: int, staffIndex: int, voiceIndex: int, locomment: str) -> None: # add this '!LO:' string just before this associatedSlice if len(self.slices) == 0: # something strange happened: expecting at least one item in measure. # associatedSlice is supposed to already be in the measure. return associatedSliceIdx: t.Optional[int] = None if associatedSlice is None: # place at end of measure (associate with imaginary slice just off the end) associatedSliceIdx = len(self.slices) else: # find owning line (associatedSlice) foundIt: bool = False for associatedSliceIdx in range(len(self.slices) - 1, -1, -1): gridSlice: GridSlice = self.slices[associatedSliceIdx] if gridSlice is associatedSlice: foundIt = True break if not foundIt: # cannot find owning line (a.k.a. associatedSlice is not in this GridMeasure) return # see if the previous slice is a layout slice we can use prevIdx: int = associatedSliceIdx - 1 prevSlice: GridSlice = self.slices[prevIdx] if prevSlice.isLocalLayoutSlice: prevStaff: GridStaff = prevSlice.parts[partIndex].staves[staffIndex] prevVoice: GridVoice = self._getIndexedVoice_AppendingIfNecessary(prevStaff.voices, voiceIndex) if prevVoice.token is None or prevVoice.token.text == '!': prevVoice.token = HumdrumToken(locomment) return # if we get here, we couldn't use the previous slice, so we need to insert # a new Layout slice to use, just before the associated slice. insertPoint: int = associatedSliceIdx newSlice: GridSlice if associatedSlice is not None: newSlice = GridSlice(self, associatedSlice.timestamp, SliceType.Layouts) newSlice.initializeBySlice(associatedSlice) self.slices.insert(insertPoint, newSlice) else: newSlice = GridSlice(self, self.timestamp + self.duration, SliceType.Layouts) newSlice.initializeBySlice(self.slices[-1]) self.slices.append(newSlice) newStaff: GridStaff = newSlice.parts[partIndex].staves[staffIndex] newVoice: GridVoice = self._getIndexedVoice_AppendingIfNecessary(newStaff.voices, voiceIndex) newVoice.token = HumdrumToken(locomment)
def addToken(self, tok: t.Union[HumdrumToken, str], parti: int, staffi: int, voicei: int) -> None: if isinstance(tok, str): tok = HumdrumToken(tok) if not 0 <= parti < len(self.parts): raise HumdrumInternalError( f'Error: part index {parti} is out of range(0, {len(self.parts)})' ) if staffi < 0: raise HumdrumInternalError(f'Error: staff index {staffi} < 0') part: GridPart = self.parts[parti] # fill in enough staves to get you to staffi if staffi >= len(part.staves): for _ in range(len(part.staves), staffi + 1): part.staves.append(GridStaff()) staff: GridStaff = part.staves[staffi] # fill in enough voices to get you to voicei if voicei >= len(staff.voices): for _ in range(len(staff.voices), voicei + 1): staff.voices.append(GridVoice()) voice: t.Optional[GridVoice] = staff.voices[voicei] if t.TYPE_CHECKING: # because we just filled staff.voices[voicei] in with a GridVoice if necessary assert isinstance(voice, GridVoice) # Ok, finally do what you came to do... voice.token = tok
def dataType(self): # -> HumdrumToken if self._dataTypeTokenCached is not None: return self._dataTypeTokenCached from converter21.humdrum import HumdrumToken if self.ownerLine is None: return HumdrumToken('') tok = self.ownerLine.trackStart(self.track) if tok is None: return HumdrumToken('') # cache it self._dataTypeTokenCached = tok return tok
def addToken(self, tok: Union[HumdrumToken, str], parti: int, staffi: int, voicei: int): if isinstance(tok, str): tok = HumdrumToken(tok) if not 0 <= parti < len(self.parts): raise HumdrumInternalError( f'Error: part index {parti} is out of range(0, {len(self.parts)})' ) if staffi < 0: raise HumdrumInternalError(f'Error: staff index {staffi} < 0') part: GridPart = self.parts[parti] # fill in enough staves to get you to staffi if staffi >= len(part.staves): for _ in range(len(part.staves), staffi + 1): part.staves.append(GridStaff()) staff: GridStaff = part.staves[staffi] # fill in enough voices to get you to voicei if voicei >= len(staff.voices): for _ in range(len(staff.voices), voicei + 1): staff.voices.append(GridVoice()) voice: GridVoice = staff.voices[voicei] # Ok, finally do what you came to do... voice.token = tok
def appendToken(self, token, tabCount: int = 0): # token can be HumdrumToken or str if isinstance(token, str): token = HumdrumToken(token) self._tokens.append(token) self._numTabsAfterToken.append(tabCount)
def token(self, newToken: Union[HumdrumToken, str]): if isinstance(newToken, HumdrumToken) or newToken is None: self._token = newToken elif isinstance(newToken, str): self._token = HumdrumToken(newToken) else: raise HumdrumInternalError(f'invalid type of token: {newToken}') self._isTransfered = False
def insertToken(self, index: int, token, tabCount: int): # token can be HumdrumToken or str if isinstance(token, HumdrumToken): self._tokens.insert(index, token) self._numTabsAfterToken.insert(index, tabCount) return if isinstance(token, str): self._tokens.insert(index, HumdrumToken(token)) self._numTabsAfterToken.insert(index, tabCount)
def createTokensFromLine(self) -> int: # returns number of tokens created ''' // delete previous tokens (will need to re-analyze structure // of file after this). ''' self._tokens = [] self._numTabsAfterToken = [] if self.text == '': # one empty token token = HumdrumToken() token.ownerLine = self self._tokens = [token] self._numTabsAfterToken = [0] return 1 if self.text.startswith('!!'): # global, so just one token for the whole line token = HumdrumToken(self.text) token.ownerLine = self self._tokens = [token] self._numTabsAfterToken = [0] return 1 # tokenStrList: [str] = self.text.split('\t') # for tokenStr in tokenStrList: # token = HumdrumToken(tokenStr) # token.ownerLine = self # self._tokens.append(token) # self._numTabsAfterToken.append(1) for m in re.finditer(r'([^\t]+)(\t*)', self.text): # m is a match object containing two groups: first the token, then any trailing tabs tokenStr = m.group(1) if tokenStr is None: break tabsStr = m.group(2) if tabsStr is None: numTabsAfterThisToken = 0 else: numTabsAfterThisToken = len(tabsStr) token = HumdrumToken(tokenStr) token.ownerLine = self self._tokens.append(token) self._numTabsAfterToken.append(numTabsAfterThisToken) return len(self._tokens)
def __init__( self, token: t.Optional[t.Union[HumdrumToken, str]] = None, duration: HumNumIn = opFrac(0) ) -> None: if isinstance(token, str): token = HumdrumToken(token) self._token: t.Optional[HumdrumToken] = token self._nextDur = opFrac(duration) # self._prevDur = opFrac(0) # appears to be unused (never set to anything but zero) self._isTransfered: bool = False
def addDynamicsLayoutParameters(self, associatedSlice: GridSlice, partIndex: int, staffIndex: int, locomment: str): if len(self.slices) == 0: # something strange happened: expecting at least one item in measure. return # associatedSlice is supposed to already be in the measure # find owning line (associatedSlice) foundIt: bool = False associatedSliceIdx: int = None for associatedSliceIdx in range(len(self.slices) - 1, -1, -1): # loop in reverse index order gridSlice: GridSlice = self.slices[associatedSliceIdx] if gridSlice is associatedSlice: foundIt = True break if not foundIt: # cannot find owning line (a.k.a. associatedSlice is not in this GridMeasure) return # see if the previous slice is a layout slice we can use prevIdx: int = associatedSliceIdx - 1 prevSlice: GridSlice = self.slices[prevIdx] if prevSlice.isLocalLayoutSlice: prevStaff: GridStaff = prevSlice.parts[partIndex].staves[ staffIndex] if prevStaff.dynamics is None: prevStaff.dynamics = HumdrumToken(locomment) return # if we get here, we couldn't use the previous slice, so we need to insert # a new Layout slice to use, just before the associated slice. insertPoint: int = associatedSliceIdx newSlice: GridSlice = GridSlice(self, associatedSlice.timestamp, SliceType.Layouts) newSlice.initializeBySlice(associatedSlice) self.slices.insert(insertPoint, newSlice) newStaff: GridStaff = newSlice.parts[partIndex].staves[staffIndex] newStaff.dynamics = HumdrumToken(locomment)
def copyStructure(self, fromLine: 'HumdrumLine', nullStr: str) -> None: for fromToken in fromLine.tokens(): newToken: HumdrumToken = HumdrumToken(nullStr) newToken.ownerLine = self newToken.copyStructure(fromToken) self._tokens.append(newToken) self.createLineFromTokens() # pylint: disable=protected-access self._numTabsAfterToken = fromLine._numTabsAfterToken self._rhythmAnalyzed = fromLine._rhythmAnalyzed # pylint: enable=protected-access self.ownerFile = fromLine.ownerFile
def __init__(self, token: Union[HumdrumToken, str] = None, duration: HumNum = HumNum(0)): self._token: HumdrumToken = None if isinstance(token, HumdrumToken) or token is None: self._token = token elif isinstance(token, str): self._token = HumdrumToken(token) else: raise HumdrumInternalError(f'invalid type of token: {token}') self._nextDur = duration # self._prevDur = HumNum(0) # appears to be unused (never set to anything but zero) self._isTransfered: bool = False
def transferSidesFromPart(line: HumdrumLine, part: GridPart, emptyStr: str, maxhcount: int, maxfcount: int): sides: GridSide = part.sides hcount: int = sides.harmonyCount # FIGURED BASS if maxfcount > 0: figuredBass: HumdrumToken = sides.figuredBass if figuredBass is not None: line.appendToken(figuredBass) else: line.appendToken(HumdrumToken(emptyStr)) # HARMONY for _ in range(0, hcount): harmony: HumdrumToken = sides.harmony if harmony is not None: line.appendToken(harmony) else: line.appendToken(HumdrumToken(emptyStr)) for _ in range(hcount, maxhcount): line.appendToken(HumdrumToken(emptyStr))
def _createRecipTokenFromDuration(duration: HumNum) -> HumdrumToken: duration /= 4 # convert to quarter note units if duration.numerator == 0: # if the GridSlice is at the end of a measure, the # time between the starttime/endtime of the GridSlice should # be subtracted from the endtime of the current GridMeasure. return HumdrumToken('g') if duration.numerator == 1: return HumdrumToken(str(duration.denominator)) if duration.numerator % 3 == 0: dotdur: HumNum = (duration * 2) / 3 if dotdur.numerator == 1: return HumdrumToken(str(dotdur.denominator) + '.') # try to fit to two dots here # try to fit to three dots here return HumdrumToken( str(duration.denominator) + '%' + str(duration.numerator))
def setVerse(self, index: int, token: Union[HumdrumToken, str]): if isinstance(token, str): # make it into a proper token token = HumdrumToken(token) if index == self.verseCount: self._verses.append(token) elif index < self.verseCount: # Insert in a slot which might already have a verse token self._verses[index] = token else: # add more than one verse spot, and insert verse: for _ in range(self.verseCount, index + 1): # verseCount through index, inclusive self._verses.append(None) self._verses[index] = token
def setNullTokenLayer(self, layerIndex: int, sliceType: SliceType, nextDur: HumNum): if sliceType == SliceType.Invalid: return if sliceType == SliceType.GlobalLayouts: return if sliceType == SliceType.GlobalComments: return if sliceType == SliceType.ReferenceRecords: return nullStr: str = '' if sliceType < SliceType.Data_: nullStr = '.' elif sliceType <= SliceType.Measure_: nullStr = '=' elif sliceType <= SliceType.Interpretation_: nullStr = '*' elif sliceType <= SliceType.Spined_: nullStr = '!' else: raise HumdrumInternalError( f'!!STRANGE ERROR: {self}, SLICE TYPE: {sliceType}') if layerIndex < len(self.voices): if (self.voices[layerIndex] is not None and self.voices[layerIndex].token is not None): if self.voices[layerIndex].token.text == nullStr: # there is already a null data token here, so don't # replace it. return raise HumdrumExportError( 'Warning, existing token: \'{self.voices[layerIndex].token.text}\' where a null token should be.' ) token: HumdrumToken = HumdrumToken(nullStr) self.setTokenLayer(layerIndex, token, nextDur)
def _linkSlurOrPhraseEndpoints(slurOrPhrase: str, startTok: HumdrumToken, endTok: HumdrumToken) -> None: if slurOrPhrase == HumdrumToken.SLUR: prefix = 'slur' else: prefix = 'phrase' durTag: str = prefix + 'Duration' endTag: str = prefix + 'EndId' startTag: str = prefix + 'StartId' startNumberTag: str = prefix + 'StartNumber' endNumberTag: str = prefix + 'EndNumber' startCountTag: str = prefix + 'StartCount' endCountTag: str = prefix + 'EndCount' startCount: int = startTok.getValueInt('auto', startCountTag) + 1 openCount: int = startTok.text.count(slurOrPhrase) openEnumeration = openCount - startCount + 1 if openEnumeration > 1: openSuffix: str = str(openEnumeration) endTag += openSuffix durTag += openSuffix endCount: int = endTok.getValueInt('auto', endCountTag) + 1 closeEnumeration: int = endCount if closeEnumeration > 1: closeSuffix: str = str(closeEnumeration) startTag += closeSuffix startNumberTag += closeSuffix duration: HumNum = opFrac(endTok.durationFromStart - startTok.durationFromStart) startTok.setValue('auto', endTag, endTok) startTok.setValue('auto', 'id', startTok) startTok.setValue('auto', endNumberTag, closeEnumeration) startTok.setValue('auto', durTag, duration) startTok.setValue('auto', startCountTag, startCount) endTok.setValue('auto', startTag, startTok) endTok.setValue('auto', 'id', endTok) endTok.setValue('auto', startNumberTag, openEnumeration) endTok.setValue('auto', endCountTag, endCount)
def token(self, newToken: t.Optional[t.Union[HumdrumToken, str]]) -> None: if isinstance(newToken, str): newToken = HumdrumToken(newToken) self._token = newToken self._isTransfered = False
def CheckHumdrumToken( token: HumdrumToken, expectedText: str = '', expectedDataType: str = '', expectedTokenType: str = TOKENTYPE_DATA, expectedSpecificType: str = SPECIFICTYPE_NOTHINGSPECIFIC, expectedDuration: str = HumNum(-1) ): #print('CheckHumdrumToken: token = "{}"'.format(token)) # set up some derived expectations expectedIsData = expectedTokenType == TOKENTYPE_DATA expectedIsBarline = expectedTokenType == TOKENTYPE_BARLINE expectedIsInterpretation = expectedTokenType == TOKENTYPE_INTERPRETATION expectedIsLocalComment = expectedTokenType == TOKENTYPE_LOCALCOMMENT expectedIsGlobalComment = expectedTokenType == TOKENTYPE_GLOBALCOMMENT # expectedIsGlobalReference = expectedTokenType == TOKENTYPE_GLOBALREFERENCE # expectedIsUniversalComment = expectedTokenType == TOKENTYPE_UNIVERSALCOMMENT # expectedIsUniversalReference = expectedTokenType == TOKENTYPE_UNIVERSALREFERENCE expectedIsNote = expectedSpecificType == SPECIFICTYPE_NOTE expectedIsRest = expectedSpecificType == SPECIFICTYPE_REST expectedIsNullData = expectedSpecificType == SPECIFICTYPE_NULLDATA expectedIsNullInterpretation = expectedSpecificType == SPECIFICTYPE_NULLINTERPRETATION expectedIsNullComment = expectedSpecificType == SPECIFICTYPE_NULLCOMMENT expectedIsNull = expectedIsNullData or expectedIsNullInterpretation or expectedIsNullComment expectedIsKeySignature = expectedSpecificType == SPECIFICTYPE_KEYSIGNATURE expectedIsTimeSignature = expectedSpecificType == SPECIFICTYPE_TIMESIGNATURE expectedIsClef = expectedSpecificType == SPECIFICTYPE_CLEF expectedIsSplitInterpretation = expectedSpecificType == SPECIFICTYPE_SPLIT expectedIsMergeInterpretation = expectedSpecificType == SPECIFICTYPE_MERGE expectedIsExchangeInterpretation = expectedSpecificType == SPECIFICTYPE_EXCHANGE expectedIsAddInterpretation = expectedSpecificType == SPECIFICTYPE_ADD expectedIsTerminateInterpretation = expectedSpecificType == SPECIFICTYPE_TERMINATE expectedIsExclusiveInterpretation = expectedSpecificType == SPECIFICTYPE_EXINTERP expectedIsManipulator = expectedIsSplitInterpretation \ or expectedIsMergeInterpretation \ or expectedIsExchangeInterpretation \ or expectedIsAddInterpretation \ or expectedIsTerminateInterpretation \ or expectedIsExclusiveInterpretation expectedIsKern = expectedDataType == '**kern' expectedIsMens = expectedDataType == '**mens' expectedIsRecip = expectedDataType == '**recip' expectedIsStaffDataType = expectedIsKern or expectedIsMens # text assert token.text == expectedText assert str(token) == expectedText # duration assert token.duration == expectedDuration assert token.scaledDuration(HumNum(1,5)) == expectedDuration * HumNum(1,5) # Token Type assert token.isData == expectedIsData assert token.isBarline == expectedIsBarline assert token.isInterpretation == expectedIsInterpretation assert token.isLocalComment == expectedIsLocalComment assert token.isGlobalComment == expectedIsGlobalComment # assert token.isGlobalReference == expectedIsGlobalReference # assert token.isUniversalComment == expectedIsUniversalComment # assert token.isUniversalReference == expectedIsUniversalReference # Specific Type assert token.isNullData == expectedIsNullData assert token.isNull == expectedIsNull assert token.isNote == expectedIsNote assert token.isRest == expectedIsRest assert token.isKeySignature == expectedIsKeySignature assert token.isTimeSignature == expectedIsTimeSignature assert token.isClef == expectedIsClef assert token.isSplitInterpretation == expectedIsSplitInterpretation assert token.isMergeInterpretation == expectedIsMergeInterpretation assert token.isExchangeInterpretation == expectedIsExchangeInterpretation assert token.isAddInterpretation == expectedIsAddInterpretation assert token.isTerminateInterpretation == expectedIsTerminateInterpretation assert token.isExclusiveInterpretation == expectedIsExclusiveInterpretation assert token.isManipulator == expectedIsManipulator # Data Type assert token.dataType.text == expectedDataType assert token.isDataType(expectedDataType) == True if expectedDataType[:2] == '**': assert token.isDataType(expectedDataType[1:]) == False assert token.isDataType(expectedDataType[2:]) == True assert token.isKern == expectedIsKern assert token.isMens == expectedIsMens assert token.isStaffDataType == expectedIsStaffDataType
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)
def __init__( self, line: str = '', asGlobalToken: bool = False, ownerFile=None # HumdrumFile ) -> None: from converter21.humdrum import HumdrumFile super().__init__() # initialize the HumHash fields ''' _text: the line's text string ''' if len(line) > 0 and line[-1] == '\n': # strip off any trailing LF line = line[:-1] self._text: str = line ''' // owner: This is the HumdrumFile which manages the given line. ''' self._ownerFile: HumdrumFile = ownerFile ''' // m_lineindex: Used to store the index number of the HumdrumLine in // the owning HumdrumFile object. // This variable is filled by HumdrumFileStructure::analyzeLines(). ''' self._lineIndex: int = -1 ''' // m_tokens: Used to store the individual tab-separated token fields // on a line. These are prepared automatically after reading in // a full line of text (which is accessed throught the string parent // class). If the full line is changed, the tokens are not updated // automatically -- use createTokensFromLine(). Likewise the full // text line is not updated if any tokens are changed -- use // createLineFromTokens() in that case. The second case is more // useful: you can read in a HumdrumFile, tweak the tokens, then // reconstruct the full line and print out again. // This variable is filled by HumdrumFile::read(). // The contents of this vector should be deleted when deconstructing // a HumdrumLine object. ''' self._tokens: t.List[HumdrumToken] = [] if asGlobalToken: self._tokens = [HumdrumToken(line)] ''' // m_tabs: Used to store a count of the number of tabs between // each token on a line. This is the number of tabs after the // token at the given index (so no tabs before the first token). ''' self._numTabsAfterToken: t.List[int] = [] if asGlobalToken: self._numTabsAfterToken = [0] ''' // m_duration: This is the "duration" of a line. The duration is // equal to the minimum time unit of all durational tokens on the // line. This also includes null tokens when the duration of a // previous note in a previous spine is ending on the line, so it is // not just the minimum duration on the line. // This variable is filled by HumdrumFileStructure::analyzeRhythm(). ''' self._duration: HumNum = -1.0 ''' // m_durationFromStart: This is the cumulative duration of all lines // prior to this one in the owning HumdrumFile object. For example, // the first notes in a score start at time 0, If the duration of the // first data line is 1 quarter note, then the durationFromStart for // the second line will be 1 quarter note. // This variable is filled by HumdrumFileStructure::analyzeRhythm(). ''' self._durationFromStart: HumNum = -1.0 ''' // m_durationFromBarline: This is the cumulative duration from the // last barline to the current data line. // This variable is filled by HumdrumFileStructure::analyzeMeter(). ''' self._durationFromBarline: HumNum = -1.0 ''' // m_durationToBarline: This is the duration from the start of the // current line to the next barline in the owning HumdrumFile object. // This variable is filled by HumdrumFileStructure::analyzeMeter(). ''' self._durationToBarline: HumNum = -1.0 ''' // m_linkedParameters: List of Humdrum tokens which are parameters // (mostly only layout parameters at the moment) ''' self._linkedParameters: t.List[HumdrumToken] = [] ''' // m_rhythm_analyzed: True if duration information from HumdrumFile // has been added to line. ''' self._rhythmAnalyzed: bool = False ''' We are also a HumHash, and that was initialized via super() above. But we want our default HumHash prefix to be '!!', not '' ''' self.prefix: str = '!!'
def insertToken(self, index: int, token: t.Union[HumdrumToken, str], tabCount: int = 0) -> None: if isinstance(token, str): token = HumdrumToken(token) self._tokens.insert(index, token) self._numTabsAfterToken.insert(index, tabCount)
def appendToken(self, token: t.Union[HumdrumToken, str], tabCount: int = 0) -> None: if isinstance(token, str): token = HumdrumToken(token) self._tokens.append(token) self._numTabsAfterToken.append(tabCount)
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)
def _linkTieEndpoints(tieStart: HumdrumToken, startSubtokenIdx: int, tieEnd: HumdrumToken, endSubtokenIdx: int) -> None: durTag: str = 'tieDuration' startTag: str = 'tieStart' endTag: str = 'tieEnd' startNumTag: str = 'tieStartSubtokenNumber' endNumTag: str = 'tieEndSubtokenNumber' startSubtokenNumber: int = startSubtokenIdx + 1 endSubtokenNumber: int = endSubtokenIdx + 1 if tieStart.isChord: if startSubtokenNumber > 0: startSuffix: str = str(startSubtokenNumber) durTag += startSuffix endNumTag += startSuffix endTag += startSuffix if tieEnd.isChord: if endSubtokenNumber > 0: endSuffix: str = str(endSubtokenNumber) startTag += endSuffix startNumTag += endSuffix tieStart.setValue('auto', endTag, tieEnd) tieStart.setValue('auto', 'id', tieStart) if endSubtokenNumber > 0: tieStart.setValue('auto', endNumTag, str(endSubtokenNumber)) tieEnd.setValue('auto', startTag, tieStart) tieEnd.setValue('auto', 'id', tieEnd) if startSubtokenNumber > 0: tieEnd.setValue('auto', startNumTag, str(startSubtokenNumber)) duration: HumNum = opFrac(tieEnd.durationFromStart - tieStart.durationFromStart) tieStart.setValue('auto', durTag, duration)
def transferTokens(self, outFile: HumdrumFile, recip: bool): line: HumdrumLine = HumdrumLine() voice: GridVoice = None emptyStr: str = '.' if self.isMeasureSlice: if len(self.parts) > 0: if len(self.parts[0].staves[0].voices) > 0: voice = self.parts[0].staves[0].voices[0] if voice.token is not None: emptyStr = voice.token.text else: emptyStr = '=YYYYYY' else: emptyStr = '=YYYYYY' elif self.isInterpretationSlice: emptyStr = '*' elif self.isLocalLayoutSlice: emptyStr = '!' elif not self.hasSpines: emptyStr = '???' if recip: token: HumdrumToken = None if self.isNoteSlice: token = self._createRecipTokenFromDuration(self.duration) elif self.isClefSlice: token = HumdrumToken('*') emptyStr = '*' elif self.isMeasureSlice: if len(self.parts[0].staves[0]) > 0: voice = self.parts[0].staves[0].voices[0] token = HumdrumToken(voice.token.text) else: token = HumdrumToken('=XXXXX') emptyStr = token.text elif self.isInterpretationSlice: token = HumdrumToken('*') emptyStr = '*' elif self.isGraceSlice: token = HumdrumToken('q') emptyStr = '.' elif self.hasSpines: token = HumdrumToken('55') emptyStr = '!' if token is not None: if self.hasSpines: line.appendToken(token) else: token = None # extract the Tokens from each part/staff for p in range(len(self.parts) - 1, -1, -1): part = self.parts[p] if not self.hasSpines and p != 0: continue for s in range(len(part.staves) - 1, -1, -1): staff = part.staves[s] if not self.hasSpines and s != 0: continue if len(staff.voices) == 0: # 888: fix this later. For now if there are no notes # 888: ... on the staff, add a null token. Fix so that # 888: ... all open voices are given null tokens. line.appendToken(HumdrumToken(emptyStr)) else: for voice in staff.voices: # NOT reversed (voices different from parts/staves) if voice is not None and voice.token is not None: line.appendToken(voice.token) else: line.appendToken(HumdrumToken(emptyStr)) if not self.hasSpines: # Don't add sides to non-spined lines continue maxxcount: int = self.getXmlIdCount(p, s) maxdcount: int = self.getDynamicsCount(p, s) maxvcount: int = self.getVerseCount(p, s) self.transferSidesFromStaff(line, staff, emptyStr, maxxcount, maxdcount, maxvcount) if not self.hasSpines: # Don't add sides to non-spined lines continue maxhcount: int = self.getHarmonyCount(p) maxfcount: int = self.getFiguredBassCount(p) self.transferSidesFromPart(line, part, emptyStr, maxhcount, maxfcount) outFile.appendLine(line)