#! /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 " )