#! /usr/bin/env python3 # -*- coding: utf-8 -*- """ Copyright 2022, Nils Hilbricht, Germany ( https://www.hilbricht.net ) This file is part of the Laborejo Software Suite ( https://www.laborejo.org ), This application is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ """ This file handles various durations and their conversions. """ import logging; logger = logging.getLogger(__name__); logger.info("import") from typing import Union from engine.config import METADATA #Suggested import in other files: #from template.engine.duration import DB, DL, D1, D2, D4, D8, D16, D32, D64, D128 #import template.engine.duration as duration MAXIMUM_TICK_DURATION = 2**31-1 D4:int = METADATA["quarterNoteInTicks"] #type: ignore D8 = int(D4 / 2) D16 = int(D8 / 2) D32 = int(D16 / 2) D64 = int(D32 / 2) D128 = int(D64 / 2) D256 = int(D128 / 2) D512 = int(D256 / 2) D1024 = int(D512 / 2) # set this to a number with many factors, like 210. According to http://homes.sice.indiana.edu/donbyrd/CMNExtremes.htm this is the real world limit. if not int(D1024) == D1024: logger.error(f"Warning: Lowest duration D0124 has decimal places: {D1024} but should be a plain integer. Your D4 value: {D4} has not enough 2^n factors. ") D2 = D4 *2 D1 = D2 *2 DB = D1 * 2 #Breve DL = DB * 2 #Longa DM = DL * 2 #Maxima if not int(D128) == D128: raise ValueError(f"Durations must be integers (or .0). Yours: {D128}") D_BASE_DURATIONS = (D1024, D512, D256, D64, D32, D16, D8, D4, D2, D1, DB, DL, DM) #Duration Keywords D_DEFAULT = 0 D_STACCATO = 1 D_TENUTO = 2 D_TIE = 3 def _baseDurationToTraditionalNumber(baseDuration:int)->int: """4 = Quarter, 8 = Eighth, 0 = Brevis, -1= Longa Created in the loop below on startup""" if baseDuration == D4: return 4 elif baseDuration == D8: return 8 elif baseDuration == D16: return 16 elif baseDuration == D2: return 2 elif baseDuration == D1: return 1 elif baseDuration == DB: return 0 elif baseDuration == DL: return -1 elif baseDuration == DM: return -2 elif baseDuration == D32: return 32 elif baseDuration == D64: return 64 elif baseDuration == D128: return 128 elif baseDuration == D256: return 256 elif baseDuration == D512: return 512 elif baseDuration == D1024: return 1024 elif baseDuration == 0: #for KeySignatures and chords on init etc. return 0 else: raise ValueError("baseDuration does not match any traditioanl note duration value") baseDurations = (0, D1024, D512, D256, D128, D64, D32, D16, D8, D4, D2, D1, DB, DL, DM) #from constants.py baseDurationToTraditionalNumber = {} for baseDuration in baseDurations: baseDurationToTraditionalNumber[baseDuration] = _baseDurationToTraditionalNumber(baseDuration) traditionalNumberToBaseDuration = {} for baseDuration in baseDurations: traditionalNumberToBaseDuration[_baseDurationToTraditionalNumber(baseDuration)] = baseDuration def jackBBTicksToDuration(beatTypeAsTraditionalNumber:int, jackTicks:int, jackBeatTicks:int)->Union[float, None]: if None in (beatTypeAsTraditionalNumber, jackTicks, jackBeatTicks): return None else: if not beatTypeAsTraditionalNumber in traditionalNumberToBaseDuration: raise ValueError(f"{beatTypeAsTraditionalNumber} must be one of {traditionalNumberToBaseDuration}") beatTypeDuration = traditionalNumberToBaseDuration[beatTypeAsTraditionalNumber] #print (beatTypeAsTraditionalNumber, beatTypeDuration, jackTicks, jackBeatTicks) factor = beatTypeDuration / jackBeatTicks return jackTicks * factor def lyDurationLogToTicks(durationLog:int)->int: return 2**(8 - durationLog) * 6 ticksToLyDurationLogDict = { DM: -3, # Maxima DL: -2, # Longa DB: -1, # Breve D1: 0, # Whole D2: 1, #Half D4: 2, #Quarter D8: 3, #Eighth D16: 4, #Sixteenth D32: 5, #1/32 D64: 6, #1/64 D128: 7, #1/128 D256: 8, #1/256 } ticksToLilypondDict = {} for ourticks, lyAsInt in baseDurationToTraditionalNumber.items(): ly = str(lyAsInt) ticksToLilypondDict[ourticks] = ly ticksToLilypondDict[ourticks * 1.5] = ly + "." #dot ticksToLilypondDict[ourticks * 1.75] = ly + ".." #double dot ticksToLilypondDict[DM] = "\\maxima" ticksToLilypondDict[DL] = "\\longa" ticksToLilypondDict[DB] = "\\breve" ticksToLilypondDict[DM*1.5] = "\\maxima." ticksToLilypondDict[DL*1.5] = "\\longa." ticksToLilypondDict[DB*1.5] = "\\breve." ticksToLilypondDict[DM*1.75] = "\\maxima.." ticksToLilypondDict[DL*1.75] = "\\longa.." ticksToLilypondDict[DB*1.75] = "\\breve.." def ticksToLilypond(ticks:int)->str: """Returns a lilypond string with the number 1, 2, 4, 8 etc. It must be a string and not a number because we have dots and words like maxima and brevis. This is mostly used for tempo items. """ if ticks in ticksToLilypondDict: return ticksToLilypondDict[ticks] else: raise ValueError(f"{ticks} not in duration.py ticksToLilypondDict")