Music production session manager
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

93 lines
4.2 KiB

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
Copyright 2022, Nils Hilbricht, Germany ( )
This file is part of the Laborejo Software Suite ( ),
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
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__);"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"""
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!")
logger.error("JackClient error: " + status.value)
sys.exit(0) #atexit will trigger
atexit.register(lambda c=self._jacklibClient: jacklib.client_close(c))
#This client does not jack.activate() yet because we are only using the pull API of jack and not any of the callbacks
#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):
def playPause(self):
pos = jacklib.jack_position_t() #pos._fields_
state = jacklib.transport_query(self._jacklibClient, pointer(pos))
if state == jacklib.JackTransportStopped:
elif state == jacklib.JackTransportStarting: