#! /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 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 . """ import logging; logger = logging.getLogger(__name__); logger.info("import") #Standard Library import atexit import sys #Third Party import engine.jacklib as jacklib from ctypes import pointer #Our Modules from engine.config import METADATA #includes METADATA only. No other environmental setup is executed. from engine.jacklib.helpers import get_jack_status_error_string class AgordejoJackClient(object): """Singleton. Created in api.startEngine. If client cannot be started the program will exit from here. Most error sources are already ruled out in start.py.""" def __init__(self): status = jacklib.jack_status_t() self._jacklibClient = jacklib.client_open(METADATA["name"], jacklib.JackNoStartServer, status) err = get_jack_status_error_string(status) if not status.value == 0: #Decide if a name collision is important or not. Agordejo cannot be started two times anyway. So this would be another client called Agordejo? if status.value & jacklib.JackNameNotUnique: logger.warning(f"Another JACK client called {METADATA['name']} exist. We will rename ourselve, but this is most likely a real problem. Agordejo is not supposed to be started twice. Please investigate!") else: logger.error("JackClient error: " + status.value) sys.exit(0) #atexit will trigger atexit.register(lambda c=self._jacklibClient: jacklib.client_close(c)) #Callbacks. They are mirrored by the api Callbacks without the callback_ prefix so a GUI can directly access them. #However, they are mutable lists. And we define all actual callback-sender here. the api calls them via our Object/Instance self.callback_setPlaybackSeconds = [] def _setPlaybackSeconds(self): """Added to the fast event loop. Therefore called VERY often. Yes, this is not a super accurate function because while we iterate transport already progresses""" pos = jacklib.jack_position_t() #pos._fields_ state = jacklib.transport_query(self._jacklibClient, pointer(pos)) #this actually sets the pos and info. We need this, even if we don't use "state" here. #if not pos.frame_rate: # return currenTimeInSeconds = round(pos.frame / pos.frame_rate, 8) for func in self.callback_setPlaybackSeconds: func(currenTimeInSeconds, not state == jacklib.JackTransportStopped) #current time in seconds and if playback is running #Public API. Called via api.jackClient.rewind() def _seek(self, frame:int): jacklib.transport_locate(self._jacklibClient, frame) def seek(self, seconds): pos = jacklib.jack_position_t() state = jacklib.transport_query(self._jacklibClient, pointer(pos)) #this actually sets the pos and info. We need this, even if we don't use "state" here. self._seek(int(seconds * pos.frame_rate)) def rewind(self): self._seek(0) def playPause(self): pos = jacklib.jack_position_t() #pos._fields_ state = jacklib.transport_query(self._jacklibClient, pointer(pos)) if state == jacklib.JackTransportStopped: jacklib.transport_start(self._jacklibClient) elif state == jacklib.JackTransportStarting: pass else: jacklib.transport_stop(self._jacklibClient)