Sampled Instrument Player with static and monolithic design. All instruments are built-in.
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.
 
 

162 lines
6.4 KiB

#! /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 <http://www.gnu.org/licenses/>.
"""
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.auditionerInstrumentChanged = []
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 _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)
#Inject our derived Callbacks into the parent module
template.engine.api.callbacks = ClientCallbacks()
from template.engine.api import callbacks
_templateStartEngine = startEngine
def startEngine(nsmClient):
_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")
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)
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):
print ("unloading is not implemented yet")
instrument = Instrument.allInstruments[idkey]
callbacks._instrumentStatusChanged(*instrument.idKey)
def loadInstrumentSamples(idkey:tuple):
"""Load one .sfz from a library."""
instrument = Instrument.allInstruments[idkey]
callbacks._startLoadingSamples(*instrument.idKey)
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 auditionerInstrument(idkey:tuple):
"""Load an indendepent instance of an instrument into the auditioner port"""
libraryId, instrumentId = idkey
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)