# DrumScript/notation_generator/helpers.py
"""
Helper functions for musical calculations.
"""
import math
from typing import Any
from . import constants # Import the whole constants module
[docs]
def round_to_nearest_subdivision(time_in_beats: float, subdivision: int) -> float:
"""
Rounds a time value (in beats) to the nearest specified rhythmic subdivision.
:param time_in_beats: Time in beats.
:type time_in_beats: float
:param subdivision: Subdivision grid (e.g., 16).
:type subdivision: int
:return: Quantized time in beats.
:rtype: float
"""
if subdivision == 0:
raise ValueError("Subdivision cannot be zero.")
unit_duration_in_beats = 4.0 / subdivision
rounded_time = round(time_in_beats / unit_duration_in_beats) * unit_duration_in_beats
return rounded_time
[docs]
def get_note_duration_name(duration_beats: float, tempo_bpm: int) -> str:
"""
Converts a duration in beats to a standard musical note name (e.g., 'quarter', 'eighth').
This function can be further refined for more complex duration mappings (e.g., dotted notes).
:param duration_beats: Duration in beats.
:type duration_beats: float
:param tempo_bpm: Tempo in BPM.
:type tempo_bpm: int
:return: Name of the note duration (e.g., 'quarter').
:rtype: str
"""
# This is a simplified mapping. For real applications, you might need a more robust system
# that considers tempo and exact beat fractions.
if abs(duration_beats - constants.DURATION_WHOLE) < 0.001:
return "whole"
elif abs(duration_beats - constants.DURATION_HALF) < 0.001:
return "half"
elif abs(duration_beats - constants.DURATION_QUARTER) < 0.001:
return "quarter"
elif abs(duration_beats - constants.DURATION_EIGHTH) < 0.001:
return "eighth"
elif abs(duration_beats - constants.DURATION_SIXTEENTH) < 0.001:
return "16th"
elif abs(duration_beats - constants.DURATION_THIRTY_SECOND) < 0.001:
return "32nd"
else:
# Fallback for unexpected durations, or calculate based on smallest unit
# For simplicity, returning a generic 'note' or 'n/th' for now
return f"{duration_beats} beats"
[docs]
def calculate_cents_difference(freq1: float, freq2: float) -> float:
"""
Calculates the difference between two frequencies in cents.
Useful for tuning or pitch analysis.
:param freq1: First frequency in Hz.
:type freq1: float
:param freq2: Second frequency in Hz.
:type freq2: float
:return: Difference in cents. Positive if freq2 is higher, negative if lower.
:rtype: float
"""
if freq1 <= 0 or freq2 <= 0:
raise ValueError("Frequencies must be positive.")
return 1200 * math.log2(freq2 / freq1)
# --------------------------------------------------------------------------uncomment during testing
# from datetime import datetime
# print("\n# ------------------------------------------------------------------------------------")
# datetimestamp = datetime.now()
# print(f'\ndate/time: {datetimestamp}')
# --------------------------------------------------------------------------------------------------