#! /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 application 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 Modules from typing import List, Set, Dict, Tuple #Third Party Modules from calfbox import cbox #Template Modules import template.engine.api #we need direct access to the module to inject data in the provided structures. but we also need the functions directly. next line: from template.engine.api import * #Our Modules from engine.instrument import Instrument #New callbacks class ClientCallbacks(Callbacks): #inherits from the templates api callbacks def __init__(self): super().__init__() self.tempCallback = [] self.instrumentListMetadata = [] self.startLoadingSamples = [] self.instrumentStatusChanged = [] self.startLoadingAuditionerInstrument = [] self.auditionerInstrumentChanged = [] self.auditionerVolumeChanged = [] self.instrumentMidiNoteOnActivity = [] def _tempCallback(self): """Just for copy paste during development""" export = session.data.export() for func in self.tempCallback: func(export) callbacks._dataChanged() def _instrumentListMetadata(self): """All libraries with all instruments as meta-data dicts. Hirarchy. A dict of dicts. Will be sent once on program start. Does not wait until actual samples are loaded but as soon as all data is parsed.""" export = session.data.exportMetadata() for func in self.instrumentListMetadata: func(export) def _instrumentStatusChanged(self, libraryId:int, instrumentId:int): """For example the current variant changed""" export = session.data.libraries[libraryId].instruments[instrumentId].exportStatus() for func in self.instrumentStatusChanged: func(export) callbacks._dataChanged() def _startLoadingSamples(self, libraryId:int, instrumentId:int): """The sample loading of this instrument has started. Start flashing an LED or so. The end of loading is signaled by a statusChange callback with the current variant included """ key = (libraryId, instrumentId) for func in self.startLoadingSamples: func(key) def _startLoadingAuditionerInstrument(self, libraryId:int, instrumentId:int): """The sample loading of the auditioner has started. Start flashing an LED or so. The end of loading is signaled by a _auditionerInstrumentChanged callback with the current variant included. """ key = (libraryId, instrumentId) for func in self.startLoadingAuditionerInstrument: func(key) def _auditionerInstrumentChanged(self, libraryId:int, instrumentId:int): """We just send the key. It is assumed that the receiver already has a key:metadata database""" export = session.data.libraries[libraryId].instruments[instrumentId].exportMetadata() for func in self.auditionerInstrumentChanged: func(export) def _auditionerVolumeChanged(self): export = session.data.auditioner.volume for func in self.auditionerVolumeChanged: func(export) def _instrumentMidiNoteOnActivity(self, libraryId:int, instrumentId:int): key = (libraryId, instrumentId) for func in self.instrumentMidiNoteOnActivity: func(key) #Inject our derived Callbacks into the parent module template.engine.api.callbacks = ClientCallbacks() from template.engine.api import callbacks _templateStartEngine = startEngine def startEngine(nsmClient, additionalData): session.data.parseAndLoadInstrumentLibraries(additionalData["baseSamplePath"]) _templateStartEngine(nsmClient) #loads save files or creates empty structure. #Send initial Callbacks to create the first GUI state. #The order of initial callbacks must not change to avoid GUI problems. #For example it is important that the tracks get created first and only then the number of measures logger.info("Sending initial callbacks to GUI") #In opposite to other LSS programs loading is delayed because load times are so big. #Here we go, when everything is already in place and we have callbacks if session.data.cachedSerializedDataForStartEngine: #We loaded a save file logger.info("We started from a save-file. Restoring saved state now:") session.data.loadCachedSerializedData() #Inject the _instrumentMidiNoteOnActivity callback into session.data for access. session.data.instrumentMidiNoteOnActivity = callbacks._instrumentMidiNoteOnActivity callbacks._instrumentListMetadata() #Relatively quick. Happens only once in the program #One round of status updates for all instruments. Still no samples loaded, but we saved the status of each with our own data. for instrument in Instrument.allInstruments.values(): callbacks._instrumentStatusChanged(*instrument.idKey) callbacks._auditionerVolumeChanged() logger.info("Tembro api startEngine complete") def loadAllInstrumentSamples(): """Actually load all instrument samples""" for instrument in Instrument.allInstruments.values(): callbacks._startLoadingSamples(*instrument.idKey) instrument.loadSamples() callbacks._instrumentStatusChanged(*instrument.idKey) callbacks._dataChanged def unloadInstrumentSamples(idkey:tuple): instrument = Instrument.allInstruments[idkey] instrument.disable() callbacks._instrumentStatusChanged(*instrument.idKey) callbacks._dataChanged def loadInstrumentSamples(idkey:tuple): """Load one .sfz from a library.""" instrument = Instrument.allInstruments[idkey] callbacks._startLoadingSamples(*instrument.idKey) if not instrument.enabled: instrument.enable() instrument.loadSamples() callbacks._instrumentStatusChanged(*instrument.idKey) callbacks._dataChanged def chooseVariantByIndex(idkey:tuple, variantIndex:int): """Choose a variant of an already enabled instrument""" libraryId, instrumentId = idkey instrument = Instrument.allInstruments[idkey] instrument.chooseVariantByIndex(variantIndex) callbacks._instrumentStatusChanged(*instrument.idKey) callbacks._dataChanged def setInstrumentMixerVolume(idkey:tuple, value:float): """From 0 to -21. Default is -3.0 """ instrument = Instrument.allInstruments[idkey] instrument.mixerLevel = value libraryId, instrumentId = idkey callbacks._instrumentStatusChanged(libraryId, instrumentId) def setInstrumentMixerEnabled(idkey:tuple, state:bool): instrument = Instrument.allInstruments[idkey] instrument.setMixerEnabled(state) libraryId, instrumentId = idkey callbacks._instrumentStatusChanged(libraryId, instrumentId) def auditionerInstrument(idkey:tuple): """Load an indendepent instance of an instrument into the auditioner port""" libraryId, instrumentId = idkey callbacks._startLoadingAuditionerInstrument(libraryId, instrumentId) originalInstrument = Instrument.allInstruments[idkey] #It does not matter if the originalInstrument has its samples loaded or not. We just want the path if originalInstrument.currentVariant: var = originalInstrument.currentVariant else: var = originalInstrument.defaultVariant session.data.auditioner.loadInstrument(originalInstrument.tarFilePath, originalInstrument.rootPrefixPath, var) callbacks._auditionerInstrumentChanged(libraryId, instrumentId) def getAvailableAuditionerPorts()->dict: """Fetches a new port list each time it is called. No cache.""" return session.data.auditioner.getAvailablePorts() def connectAuditionerPort(externalPort:str): """externalPort is in the Client:Port JACK format. If externalPort evaluates to False it will disconnect any port.""" session.data.auditioner.connectMidiInputPort(externalPort) def setAuditionerVolume(value:float): """From 0 to -21. Default is -3.0 """ session.data.auditioner.volume = value callbacks._auditionerVolumeChanged()