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
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()
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
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
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 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)