Music production session manager
https://www.laborejo.org
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.
92 lines
4.1 KiB
92 lines
4.1 KiB
3 years ago
|
#! /usr/bin/env python3
|
||
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
Copyright 2021, 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 <http://www.gnu.org/licenses/>.
|
||
|
"""
|
||
|
|
||
|
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)
|