You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.5 KiB
182 lines
5.5 KiB
#! /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 <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
"""
|
|
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")
|
|
|