# Path: pym2149/clock.py
# class ClockInfo:
# 
#     trishapes = frozenset([0xa, 0xe])
#     tonescale = 16
# 
#     @classmethod
#     def _shapescale(cls, shape):
#         # Musically, the triangular shapes have twice the scale:
#         return 512 if shape in cls.trishapes else 256
# 
#     @types(Config, Platform, YMFile)
#     def __init__(self, config, platform, ymfile = None):
#         self.nomclock = config.nominalclock
#         if self.nomclock % config.underclock:
#             raise Exception(f"Clock {self.nomclock} not divisible by underclock {config.underclock}.")
#         self.implclock = self.nomclock // config.underclock
#         if ymfile is not None and self.nomclock != ymfile.nominalclock:
#             log.info("Context clock %s overridden to: %s", ymfile.nominalclock, self.nomclock)
#         if self.implclock != self.nomclock:
#             log.debug("Clock adjusted to %s to take advantage of non-trivial underclock.", self.implclock)
#         if config.underclock < 1 or defaultscale % config.underclock:
#             raise Exception(f"underclock must be a factor of {defaultscale}.")
#         self.scale = defaultscale // config.underclock
#         if config.freqclamp:
#             # The 0 case just means that 1 is audible:
#             self.mintoneperiod = max(1, self._toneperiodclampor0(platform.outputrate))
#             log.debug("Minimum tone period: %s", self.mintoneperiod)
#         else:
#             self.mintoneperiod = 1
# 
#     def _toneperiodclampor0(self, outrate):
#         # Largest period with frequency strictly greater than Nyquist, or 0 if there isn't one:
#         return (self.implclock - 1) // (self.scale * outrate)
# 
#     def _convert(self, freqorperiod, scale):
#         return self.nomclock / (scale * freqorperiod)
# 
#     def toneperiod(self, freq):
#         return self._convert(freq, self.tonescale)
# 
#     def noiseperiod(self, freq):
#         return self._convert(freq, 16) # First notch at freq.
# 
#     def envperiod(self, freq, shape):
#         return self._convert(freq, self._shapescale(shape))
# 
#     def tonefreq(self, period):
#         return self._convert(period, self.tonescale)
# 
#     def envfreq(self, period, shape):
#         return self._convert(period, self._shapescale(shape))
# 
# Path: pym2149/iface.py
# class Platform: pass
# 
# Path: pym2149/minblep.py
# def loadorcreate(platform, clockinfo):
# 
# Path: pym2149/nod.py
# class Block:
# 
#     def __init__(self, framecount):
#         # If it's a numpy type it can cause overflow in osc module, so ensure arbitrary precision:
#         self.framecount = int(framecount)
# 
#     def __repr__(self):
#         return f"{type(self).__name__}({self.framecount!r})"
# 
# Path: pym2149/timer.py
# class MinBlockRateTimer(SimpleTimer):
# 
#     def __init__(self, clock, minblockrate):
#         # If division not exact, rate will slightly exceed given minimum:
#         self.maxblocksize = int(clock // minblockrate)
#         if not self.maxblocksize:
#             raise Exception(clock, minblockrate)
#         super().__init__(clock)
# 
#     def blocksforperiod(self, refreshrate):
#         wholeperiodblock, = super().blocksforperiod(refreshrate)
#         blockticks = wholeperiodblock.framecount
#         while blockticks:
#             size = min(blockticks, self.maxblocksize)
#             b = Block(size)
#             blockticks -= size
#             yield b
# 
# class SimpleTimer(Timer):
# 
#     def __init__(self, clock):
#         self.carryticks = 0
#         self.clock = clock
# 
#     def blocksforperiod(self, refreshrate):
#         available = self.carryticks + self.clock
#         blockticks = int(round(available / refreshrate))
#         self.carryticks = available - blockticks * refreshrate
#         yield Block(blockticks)
# 
#     def __del__(self):
#         if self.carryticks:
#             log.warning("Non-zero carry on dispose: %s", self.carryticks)


from .clock import ClockInfo
from .iface import Platform
from .minblep import MinBleps
from .nod import Block
from .timer import MinBlockRateTimer, SimpleTimer
from diapyr import types
# 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.
#
# pym2149 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 pym2149.  If not, see <http://www.gnu.org/licenses/>.


class SyncTimer(SimpleTimer):
    'Fill platform buffer most efficiently.'

    @types(Platform, MinBleps, ClockInfo)
    def __init__(self, platform, minbleps, clockinfo):
        self.naiverate = clockinfo.implclock
        super().__init__(self.naiverate)
        self.buffersize = platform.buffersize
        self.naivex = 0
        self.bufferx = 0
        self.minbleps = minbleps

    def blocksforperiod(self, refreshrate):
        wholeperiodblock, = super().blocksforperiod(refreshrate)
        naiveN = wholeperiodblock.framecount
        while naiveN:
            naiven = min(naiveN, self.minbleps.getminnaiven(self.naivex, self.buffersize - self.bufferx))

# Path: pym2149/clock.py
# class ClockInfo:
# 
#     trishapes = frozenset([0xa, 0xe])
#     tonescale = 16
# 
#     @classmethod
#     def _shapescale(cls, shape):
#         # Musically, the triangular shapes have twice the scale:
#         return 512 if shape in cls.trishapes else 256
# 
#     @types(Config, Platform, YMFile)
#     def __init__(self, config, platform, ymfile = None):
#         self.nomclock = config.nominalclock
#         if self.nomclock % config.underclock:
#             raise Exception(f"Clock {self.nomclock} not divisible by underclock {config.underclock}.")
#         self.implclock = self.nomclock // config.underclock
#         if ymfile is not None and self.nomclock != ymfile.nominalclock:
#             log.info("Context clock %s overridden to: %s", ymfile.nominalclock, self.nomclock)
#         if self.implclock != self.nomclock:
#             log.debug("Clock adjusted to %s to take advantage of non-trivial underclock.", self.implclock)
#         if config.underclock < 1 or defaultscale % config.underclock:
#             raise Exception(f"underclock must be a factor of {defaultscale}.")
#         self.scale = defaultscale // config.underclock
#         if config.freqclamp:
#             # The 0 case just means that 1 is audible:
#             self.mintoneperiod = max(1, self._toneperiodclampor0(platform.outputrate))
#             log.debug("Minimum tone period: %s", self.mintoneperiod)
#         else:
#             self.mintoneperiod = 1
# 
#     def _toneperiodclampor0(self, outrate):
#         # Largest period with frequency strictly greater than Nyquist, or 0 if there isn't one:
#         return (self.implclock - 1) // (self.scale * outrate)
# 
#     def _convert(self, freqorperiod, scale):
#         return self.nomclock / (scale * freqorperiod)
# 
#     def toneperiod(self, freq):
#         return self._convert(freq, self.tonescale)
# 
#     def noiseperiod(self, freq):
#         return self._convert(freq, 16) # First notch at freq.
# 
#     def envperiod(self, freq, shape):
#         return self._convert(freq, self._shapescale(shape))
# 
#     def tonefreq(self, period):
#         return self._convert(period, self.tonescale)
# 
#     def envfreq(self, period, shape):
#         return self._convert(period, self._shapescale(shape))
# 
# Path: pym2149/iface.py
# class Platform: pass
# 
# Path: pym2149/minblep.py
# def loadorcreate(platform, clockinfo):
# 
# Path: pym2149/nod.py
# class Block:
# 
#     def __init__(self, framecount):
#         # If it's a numpy type it can cause overflow in osc module, so ensure arbitrary precision:
#         self.framecount = int(framecount)
# 
#     def __repr__(self):
#         return f"{type(self).__name__}({self.framecount!r})"
# 
# Path: pym2149/timer.py
# class MinBlockRateTimer(SimpleTimer):
# 
#     def __init__(self, clock, minblockrate):
#         # If division not exact, rate will slightly exceed given minimum:
#         self.maxblocksize = int(clock // minblockrate)
#         if not self.maxblocksize:
#             raise Exception(clock, minblockrate)
#         super().__init__(clock)
# 
#     def blocksforperiod(self, refreshrate):
#         wholeperiodblock, = super().blocksforperiod(refreshrate)
#         blockticks = wholeperiodblock.framecount
#         while blockticks:
#             size = min(blockticks, self.maxblocksize)
#             b = Block(size)
#             blockticks -= size
#             yield b
# 
# class SimpleTimer(Timer):
# 
#     def __init__(self, clock):
#         self.carryticks = 0
#         self.clock = clock
# 
#     def blocksforperiod(self, refreshrate):
#         available = self.carryticks + self.clock
#         blockticks = int(round(available / refreshrate))
#         self.carryticks = available - blockticks * refreshrate
#         yield Block(blockticks)
# 
#     def __del__(self):
#         if self.carryticks:
#             log.warning("Non-zero carry on dispose: %s", self.carryticks)


# Path: pym2149/timerimpl.py
from .clock import ClockInfo
from .iface import Platform
from .minblep import MinBleps
from .nod import Block
from .timer import MinBlockRateTimer, SimpleTimer
from diapyr import types

# 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.
#
# pym2149 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 pym2149.  If not, see <http://www.gnu.org/licenses/>.


class SyncTimer(SimpleTimer):
    'Fill platform buffer most efficiently.'

    @types(Platform, MinBleps, ClockInfo)
    def __init__(self, platform, minbleps, clockinfo):
        self.naiverate = clockinfo.implclock
        super().__init__(self.naiverate)
        self.buffersize = platform.buffersize
        self.naivex = 0
        self.bufferx = 0
        self.minbleps = minbleps

    def blocksforperiod(self, refreshrate):
        wholeperiodblock, = super().blocksforperiod(refreshrate)
        naiveN = wholeperiodblock.framecount
        while naiveN:
            naiven = min(naiveN, self.minbleps.getminnaiven(self.naivex, self.buffersize - self.bufferx))

            yield Block(naiven)