Browse Source

cbox update and out support in the template

master
Nils 10 months ago
parent
commit
537aa813df
  1. 2
      template/calfbox/cleanpythonbuild.sh
  2. 8
      template/calfbox/jackio.c
  3. 110
      template/calfbox/sampler_api_load_stress_test.py
  4. 39
      template/calfbox/send_pattern_to_midi_out_example.py
  5. 3
      template/engine/api.py
  6. 10
      template/engine/input_midi.py

2
template/calfbox/cleanpythonbuild.sh

@ -5,7 +5,7 @@ rm build -rf
set -e
sh autogen.sh
./configure --prefix=/usr --without-python
make
make CFLAGS="-O0 -g"
python3 setup.py build
sudo python3 setup.py install
sudo make install

8
template/calfbox/jackio.c

@ -245,18 +245,10 @@ static int process_cb(jack_nframes_t nframes, void *arg)
cbox_midi_merger_render(&midiout->hdr.merger);
if (midiout->hdr.buffer.count)
{
uint8_t tmp_data[4];
for (uint32_t i = 0; i < midiout->hdr.buffer.count; i++)
{
const struct cbox_midi_event *event = cbox_midi_buffer_get_event(&midiout->hdr.buffer, i);
const uint8_t *pdata = cbox_midi_event_get_data(event);
if ((pdata[0] & 0xF0) == 0x90 && !pdata[2] && event->size == 3)
{
tmp_data[0] = pdata[0] & ~0x10;
tmp_data[1] = pdata[1];
tmp_data[2] = pdata[2];
pdata = tmp_data;
}
if (jack_midi_event_write(pbuf, event->time, pdata, event->size))
{
g_warning("MIDI buffer overflow on JACK output port '%s'", midiout->hdr.name);

110
template/calfbox/sampler_api_load_stress_test.py

@ -0,0 +1,110 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
NUMBER_OF_INSTRUMENTS = 240
"""
2021-11-13 Benchmark:
NumberOfInstruments,StartInSeconds,QuitInSeconds
30, 5, 3
60, 10, 6
120, 21, 12
240, 42, 25
Conclusion: Linear time.
"""
from calfbox import cbox
import atexit
from datetime import datetime
#Capture Ctlr+C / SIGINT and let @atexit handle the rest.
import signal
import sys
def signal_handler(sig, frame):
sys.exit(0) #atexit will trigger
signal.signal(signal.SIGINT, signal_handler)
def cmd_dumper(cmd, fb, args):
#print ("%s(%s)" % (cmd, ",".join(list(map(repr,args)))))
pass
def stopSession():
"""This got registered with atexit in the nsm new or open callback above.
will handle all python exceptions, but not segfaults of C modules. """
print()
print("Starting Quit through @atexit, stopSession")
starttime = datetime.now()
#Don't do that. We are just a client.
#cbox.Transport.stop()
#print("@atexit: Calfbox Transport stopped ")
cbox.stop_audio()
print("@atexit: Calfbox Audio stopped ")
cbox.shutdown_engine()
print("@atexit: Calfbox Engine shutdown ")
endtime = datetime.now() - starttime
print (f"Shutdown took {endtime.seconds} seconds for {NUMBER_OF_INSTRUMENTS} instruments")
cbox.init_engine()
cbox.start_audio(cmd_dumper)
atexit.register(stopSession) #this will handle all python exceptions, but not segfaults of C modules.
scenes = {}
jackAudioOutLefts = {}
jackAudioOutRights = {}
outputMergerRouters = {}
routerToGlobalSummingStereoMixers = {}
lmixUuid = cbox.JackIO.create_audio_output('left_mix', "#1") #add "#1" as second parameter for auto-connection to system out 1
rmixUuid = cbox.JackIO.create_audio_output('right_mix', "#2") #add "#2" as second parameter for auto-connection to system out 2
cboxMidiPortUids = {}
sfzSamplerLayers = {}
instrumentLayers = {}\
print (f"Creating {NUMBER_OF_INSTRUMENTS} instruments")
starttime = datetime.now()
for i in range(NUMBER_OF_INSTRUMENTS):
scenes[i] = cbox.Document.get_engine().new_scene()
scenes[i].clear()
#instrumentLayer = scenes[i].status().layers[0].get_instrument()
sfzSamplerLayers[i] = scenes[i].add_new_instrument_layer(str(i), "sampler") #"sampler" is the cbox sfz engine
scenes[i].status().layers[0].get_instrument().engine.load_patch_from_string(0, "", "", "") #fill with null instruments
jackAudioOutLefts[i] = cbox.JackIO.create_audio_output(str(i) +"_L")
jackAudioOutRights[i] = cbox.JackIO.create_audio_output(str(i) +"_R")
outputMergerRouters[i] = cbox.JackIO.create_audio_output_router(jackAudioOutLefts[i], jackAudioOutRights[i])
outputMergerRouters[i].set_gain(-3.0)
#instrumentLayer = sfzSamplerLayers[i].get_instrument()
instrumentLayers[i] = scenes[i].status().layers[0].get_instrument()
instrumentLayers[i].get_output_slot(0).rec_wet.attach(outputMergerRouters[i]) #output_slot is 0 based and means a pair. Most sfz instrument have only one stereo pair. #TODO: And what if not?
routerToGlobalSummingStereoMixers[i] = cbox.JackIO.create_audio_output_router(lmixUuid, rmixUuid)
routerToGlobalSummingStereoMixers[i].set_gain(-3.0)
instrument = sfzSamplerLayers[i].get_instrument()
instrument.get_output_slot(0).rec_wet.attach(routerToGlobalSummingStereoMixers[i])
#Create Midi Input Port
cboxMidiPortUids[i] = cbox.JackIO.create_midi_input(str(i) + "midi_in")
cbox.JackIO.set_appsink_for_midi_input(cboxMidiPortUids[i], True) #This sounds like a program wide sink, but it is needed for every port.
cbox.JackIO.route_midi_input(cboxMidiPortUids[i], scenes[i].uuid) #Route midi input to the scene. Without this we have no sound, but the python processor would still work.
#Actually load the instrument
programNumber = 0
#program = instrumentLayers[i].engine.load_patch_from_tar(programNumber, "bug.tar", f'Saw{1}.sfz', f'Saw{i+1}') #tar_name, sfz_name, display_name
program = instrumentLayers[i].engine.load_patch_from_string(programNumber, ".", "", "<group> <region> sample=*saw") #fill with null instruments
print (f"[{i}]", program.status())
instrumentLayers[i].engine.set_patch(1, programNumber) #1 is the channel, counting from 1. #TODO: we want this to be on all channels.
endtime = datetime.now() - starttime
print (f"Creation took {endtime.seconds} seconds for {NUMBER_OF_INSTRUMENTS} instruments")
print()
print("Press Ctrl+C for a controlled shutdown.")
print()
while True:
cbox.call_on_idle(cmd_dumper)

39
template/calfbox/send_pattern_to_midi_out_example.py

@ -0,0 +1,39 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
from calfbox import cbox
def cmd_dumper(cmd, fb, args):
print ("%s(%s)" % (cmd, ",".join(list(map(repr,args)))))
cbox.init_engine()
cbox.start_audio(cmd_dumper)
outportname = "CboxSendPattern"
cboxMidiOutUuid = cbox.JackIO.create_midi_output(outportname) #Add a named midi out port
cbox.JackIO.rename_midi_output(cboxMidiOutUuid, outportname) #For good measure.
outputScene = cbox.Document.get_engine().new_scene() #Create a new scene that will play the pattern. The pattern is not saved in the scene, it is not a track or so.
outputScene.clear() #For good measure.
outputScene.add_new_midi_layer(cboxMidiOutUuid) #Connect the scene to our midi output port. Without this there will be no midi out.
# Send 8 pitches 0x90 with velocity 0
# Create a binary blob that contains the MIDI events
pblob = bytes()
for pitch in range(0,8):
# note on
pblob += cbox.Pattern.serialize_event(1, 0x90, pitch, 0) #tick in pattern, midi, pitch, velocity
# Create a new pattern object using events from the blob
allNoteOnZeroPattern = cbox.Document.get_song().pattern_from_blob(pblob, 0) #0 ticks.
print ("\nThis example sends midi events from a pattern without any tracks. Rolling transport or not doesn't matter.")
print("Ready!")
counter = 0 #To add delay
while True:
cbox.call_on_idle(cmd_dumper)
if counter > 10**5 * 4 :
print ("Send pattern")
outputScene.play_pattern(allNoteOnZeroPattern, 150.0) #150 tempo
counter = 0
counter += 1

3
template/engine/api.py

@ -381,6 +381,9 @@ def playPause():
cbox.Transport.play()
#It takes a few ms for playbackStatus to catch up. If you call it here again it will not be updated.
def stop():
cbox.Transport.stop()
def getPlaybackTicks()->int:
return cbox.Transport.status().pos_ppqn

10
template/engine/input_midi.py

@ -101,6 +101,14 @@ class MidiInput(object):
raise ValueError("Channels are from 1 to 16 (inclusive). You sent " + str(channel))
self.realtimeMidiThroughLayer.set_out_channel(channel)
def connectToHardware(self, portPattern:str):
if not portPattern:
portPattern = ".*"
hardwareMidiPorts = set(cbox.JackIO.get_ports(portPattern, cbox.JackIO.MIDI_TYPE, cbox.JackIO.PORT_IS_SOURCE | cbox.JackIO.PORT_IS_PHYSICAL))
for hp in hardwareMidiPorts:
cbox.JackIO.port_connect(hp, cbox.JackIO.status().client_name + ":" + self.portName)
class MidiProcessor(object):
"""
@ -245,7 +253,7 @@ class MidiProcessor(object):
def notePrinter(self, state:bool):
if state:
def _printer(timestamp, channel, note, velocity):
print(f"[{timestamp}] Chan: {channel} Note: {pitch.midi_notenames_english[note]}: Vel: {velocity}")
print(f"[{timestamp}] Chan: {channel} Note: {note} -> {pitch.midi_notenames_english[note]}: Vel: {velocity}")
self.callbacks3[(MidiProcessor.SIMPLE_EVENT, MidiProcessor.M_NOTE_ON)] = _printer
else:
try:

Loading…
Cancel
Save