""" Manage the sound volume of a SlimProto based player. Copyright 2010 Doug Winter https://github.com/winjer/squeal/blob/master/src/squeal/player/volume.py """ from __future__ import annotations from typing import ClassVar class SlimProtoVolume: """Represents a sound volume. This is an awful lot more complex than it sounds.""" minimum: int = 0 maximum: int = 100 step: int = 1 # this map is taken from Slim::Player::Squeezebox2 in the squeezecenter source # i don't know how much magic it contains, or any way I can test it old_map: ClassVar[list[int]] = [ 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 8, 9, 9, 10, 11, 12, 13, 14, 15, 16, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 42, 43, 44, 46, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 61, 63, 65, 66, 68, 69, 71, 72, 74, 75, 77, 79, 80, 82, 84, 85, 87, 89, 90, 92, 94, 96, 97, 99, 101, 103, 104, 106, 108, 110, 112, 113, 115, 117, 119, 121, 123, 125, 127, 128, ] # new gain parameters, from the same place total_volume_range: int = -50 # dB step_point: int = ( -1 ) # Number of steps, up from the bottom, where a 2nd volume ramp kicks in. step_fraction: int = ( 1 # fraction of totalVolumeRange where alternate volume ramp kicks in. ) def __init__(self) -> None: """Initialize class.""" self.volume = 50 def increment(self) -> None: """Increment the volume.""" self.volume += self.step if self.volume > self.maximum: self.volume = self.maximum def decrement(self) -> None: """Decrement the volume.""" self.volume -= self.step if self.volume < self.minimum: self.volume = self.minimum def old_gain(self) -> int: """Return the "Old" gain value as required by the squeezebox.""" if self.volume <= 0: return 0 return self.old_map[self.volume] def decibels(self) -> float: """Return the "new" gain value in decibels.""" # ruff: noqa: ERA001 step_db = self.total_volume_range * self.step_fraction max_volume_db = 0 # different on the boom? # Equation for a line: # y = mx+b # y1 = mx1+b, y2 = mx2+b. # y2-y1 = m(x2 - x1) # y2 = m(x2 - x1) + y1 slope_high = max_volume_db - step_db / (100.0 - self.step_point) slope_low = step_db - self.total_volume_range / (self.step_point - 0.0) x2 = self.volume if x2 > self.step_point: m = slope_high x1 = 100 y1 = max_volume_db else: m = slope_low x1 = 0 y1 = self.total_volume_range return m * (x2 - x1) + y1 def new_gain(self) -> float: """Return new gainvalue of the volume control.""" if self.volume <= 0: return 0 decibel = self.decibels() floatmult = 10 ** (decibel / 20.0) # avoid rounding errors somehow if -30 <= decibel <= 0: return int(floatmult * (1 << 8) + 0.5) * (1 << 8) return int((floatmult * (1 << 16)) + 0.5)