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.
603 lines
17 KiB
603 lines
17 KiB
3 years ago
|
#! /usr/bin/env python3
|
||
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
Copyright, Nils Hilbricht, Germany ( https://www.hilbricht.net )
|
||
|
|
||
|
This code 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 re
|
||
|
from calfbox import cbox #use the globally installed calfbox
|
||
|
from asyncio import get_event_loop
|
||
|
from sys import stdout, maxsize
|
||
|
import os, signal
|
||
|
|
||
|
D1024 =210 * 2**0 # = 210. The lcm of 2, 3, 5, 7 . according to www.informatics.indiana.edu/donbyrd/CMNExtremes.htm this is the real world limit.
|
||
|
D512 = 210 * 2**1
|
||
|
D256 = 210 * 2**2
|
||
|
D128 = 210 * 2**3
|
||
|
D64 = 210 * 2**4
|
||
|
D32 = 210 * 2**5
|
||
|
D16 = 210 * 2**6 #16th 13440 ticks
|
||
|
D8 = 210 * 2**7 #eigth 26880 ticks
|
||
|
D4 = 210 * 2**8 #quarter 53760 ticks
|
||
|
D2 = 210 * 2**9 #half 107520 ticks
|
||
|
D1 = 210 * 2**10 #whole 215040 ticks
|
||
|
DB = 210 * 2**11 #brevis 430080 ticks
|
||
|
DL = 210 * 2**12 #longa
|
||
|
DM = 210 * 2**13 #maxima
|
||
|
#MAXIMUM = 0x7FFFFFFF # 31bit. maximum number of calfbox ticks allowed for its timeline, for example for the song duration
|
||
|
MAXIMUM = 100 * D1
|
||
|
#max_pulses = min(2**31, 2**31 * ppqn * bpm / (60 * sample_rate))
|
||
|
|
||
|
ly2pitch = {
|
||
|
"ceses,,," : 00,
|
||
|
"ces,,," : 10,
|
||
|
"c,,," : 20,
|
||
|
"cis,,," : 30,
|
||
|
"cisis,,," : 40,
|
||
|
"deses,,," : 50,
|
||
|
"des,,," : 60,
|
||
|
"d,,," : 70,
|
||
|
"dis,,," : 80,
|
||
|
"disis,,," : 90,
|
||
|
"eeses,,," : 100,
|
||
|
"ees,,," : 110,
|
||
|
"e,,," : 120,
|
||
|
"eis,,," : 130,
|
||
|
"eisis,,," : 140,
|
||
|
"feses,,," : 150,
|
||
|
"fes,,," : 160,
|
||
|
"f,,," : 170,
|
||
|
"fis,,," : 180,
|
||
|
"fisis,,," : 190,
|
||
|
"geses,,," : 200,
|
||
|
"ges,,," : 210,
|
||
|
"g,,," : 220,
|
||
|
"gis,,," : 230,
|
||
|
"gisis,,," : 240,
|
||
|
"aeses,,," : 250,
|
||
|
"aes,,," : 260,
|
||
|
"a,,," : 270,
|
||
|
"ais,,," : 280,
|
||
|
"aisis,,," : 290,
|
||
|
"beses,,," : 300,
|
||
|
"bes,,," : 310,
|
||
|
"b,,," : 320,
|
||
|
"bis,,," : 330,
|
||
|
"bisis,,," : 340,
|
||
|
"ceses,," : 350,
|
||
|
"ces,," : 360,
|
||
|
"c,," : 370,
|
||
|
"cis,," : 380,
|
||
|
"cisis,," : 390,
|
||
|
"deses,," : 400,
|
||
|
"des,," : 410,
|
||
|
"d,," : 420,
|
||
|
"dis,," : 430,
|
||
|
"disis,," : 440,
|
||
|
"eeses,," : 450,
|
||
|
"ees,," : 460,
|
||
|
"e,," : 470,
|
||
|
"eis,," : 480,
|
||
|
"eisis,," : 490,
|
||
|
"feses,," : 500,
|
||
|
"fes,," : 510,
|
||
|
"f,," : 520,
|
||
|
"fis,," : 530,
|
||
|
"fisis,," : 540,
|
||
|
"geses,," : 550,
|
||
|
"ges,," : 560,
|
||
|
"g,," : 570,
|
||
|
"gis,," : 580,
|
||
|
"gisis,," : 590,
|
||
|
"aeses,," : 600,
|
||
|
"aes,," : 610,
|
||
|
"a,," : 620,
|
||
|
"ais,," : 630,
|
||
|
"aisis,," : 640,
|
||
|
"beses,," : 650,
|
||
|
"bes,," : 660,
|
||
|
"b,," : 670,
|
||
|
"bis,," : 680,
|
||
|
"bisis,," : 690,
|
||
|
"ceses," : 700,
|
||
|
"ces," : 710,
|
||
|
"c," : 720,
|
||
|
"cis," : 730,
|
||
|
"cisis," : 740,
|
||
|
"deses," : 750,
|
||
|
"des," : 760,
|
||
|
"d," : 770,
|
||
|
"dis," : 780,
|
||
|
"disis," : 790,
|
||
|
"eeses," : 800,
|
||
|
"ees," : 810,
|
||
|
"e," : 820,
|
||
|
"eis," : 830,
|
||
|
"eisis," : 840,
|
||
|
"feses," : 850,
|
||
|
"fes," : 860,
|
||
|
"f," : 870,
|
||
|
"fis," : 880,
|
||
|
"fisis," : 890,
|
||
|
"geses," : 900,
|
||
|
"ges," : 910,
|
||
|
"g," : 920,
|
||
|
"gis," : 930,
|
||
|
"gisis," : 940,
|
||
|
"aeses," : 950,
|
||
|
"aes," : 960,
|
||
|
"a," : 970,
|
||
|
"ais," : 980,
|
||
|
"aisis," : 990,
|
||
|
"beses," : 1000,
|
||
|
"bes," : 1010,
|
||
|
"b," : 1020,
|
||
|
"bis," : 1030,
|
||
|
"bisis," : 1040,
|
||
|
"ceses" : 1050,
|
||
|
"ces" : 1060,
|
||
|
"c" : 1070,
|
||
|
"cis" : 1080,
|
||
|
"cisis" : 1090,
|
||
|
"deses" : 1100,
|
||
|
"des" : 1110,
|
||
|
"d" : 1120,
|
||
|
"dis" : 1130,
|
||
|
"disis" : 1140,
|
||
|
"eeses" : 1150,
|
||
|
"ees" : 1160,
|
||
|
"e" : 1170,
|
||
|
"eis" : 1180,
|
||
|
"eisis" : 1190,
|
||
|
"feses" : 1200,
|
||
|
"fes" : 1210,
|
||
|
"f" : 1220,
|
||
|
"fis" : 1230,
|
||
|
"fisis" : 1240,
|
||
|
"geses" : 1250,
|
||
|
"ges" : 1260,
|
||
|
"g" : 1270,
|
||
|
"gis" : 1280,
|
||
|
"gisis" : 1290,
|
||
|
"aeses" : 1300,
|
||
|
"aes" : 1310,
|
||
|
"a" : 1320,
|
||
|
"ais" : 1330,
|
||
|
"aisis" : 1340,
|
||
|
"beses" : 1350,
|
||
|
"bes" : 1360,
|
||
|
"b" : 1370,
|
||
|
"bis" : 1380,
|
||
|
"bisis" : 1390,
|
||
|
"ceses'" : 1400,
|
||
|
"ces'" : 1410,
|
||
|
"c'" : 1420,
|
||
|
"cis'" : 1430,
|
||
|
"cisis'" : 1440,
|
||
|
"deses'" : 1450,
|
||
|
"des'" : 1460,
|
||
|
"d'" : 1470,
|
||
|
"dis'" : 1480,
|
||
|
"disis'" : 1490,
|
||
|
"eeses'" : 1500,
|
||
|
"ees'" : 1510,
|
||
|
"e'" : 1520,
|
||
|
"eis'" : 1530,
|
||
|
"eisis'" : 1540,
|
||
|
"feses'" : 1550,
|
||
|
"fes'" : 1560,
|
||
|
"f'" : 1570,
|
||
|
"fis'" : 1580,
|
||
|
"fisis'" : 1590,
|
||
|
"geses'" : 1600,
|
||
|
"ges'" : 1610,
|
||
|
"g'" : 1620,
|
||
|
"gis'" : 1630,
|
||
|
"gisis'" : 1640,
|
||
|
"aeses'" : 1650,
|
||
|
"aes'" : 1660,
|
||
|
"a'" : 1670,
|
||
|
"ais'" : 1680,
|
||
|
"aisis'" : 1690,
|
||
|
"beses'" : 1700,
|
||
|
"bes'" : 1710,
|
||
|
"b'" : 1720,
|
||
|
"bis'" : 1730,
|
||
|
"bisis'" : 1740,
|
||
|
"ceses''" : 1750,
|
||
|
"ces''" : 1760,
|
||
|
"c''" : 1770,
|
||
|
"cis''" : 1780,
|
||
|
"cisis''" : 1790,
|
||
|
"deses''" : 1800,
|
||
|
"des''" : 1810,
|
||
|
"d''" : 1820,
|
||
|
"dis''" : 1830,
|
||
|
"disis''" : 1840,
|
||
|
"eeses''" : 1850,
|
||
|
"ees''" : 1860,
|
||
|
"e''" : 1870,
|
||
|
"eis''" : 1880,
|
||
|
"eisis''" : 1890,
|
||
|
"feses''" : 1900,
|
||
|
"fes''" : 1910,
|
||
|
"f''" : 1920,
|
||
|
"fis''" : 1930,
|
||
|
"fisis''" : 1940,
|
||
|
"geses''" : 1950,
|
||
|
"ges''" : 1960,
|
||
|
"g''" : 1970,
|
||
|
"gis''" : 1980,
|
||
|
"gisis''" : 1990,
|
||
|
"aeses''" : 2000,
|
||
|
"aes''" : 2010,
|
||
|
"a''" : 2020,
|
||
|
"ais''" : 2030,
|
||
|
"aisis''" : 2040,
|
||
|
"beses''" : 2050,
|
||
|
"bes''" : 2060,
|
||
|
"b''" : 2070,
|
||
|
"bis''" : 2080,
|
||
|
"bisis''" : 2090,
|
||
|
"ceses'''" : 2100,
|
||
|
"ces'''" : 2110,
|
||
|
"c'''" : 2120,
|
||
|
"cis'''" : 2130,
|
||
|
"cisis'''" : 2140,
|
||
|
"deses'''" : 2150,
|
||
|
"des'''" : 2160,
|
||
|
"d'''" : 2170,
|
||
|
"dis'''" : 2180,
|
||
|
"disis'''" : 2190,
|
||
|
"eeses'''" : 2200,
|
||
|
"ees'''" : 2210,
|
||
|
"e'''" : 2220,
|
||
|
"eis'''" : 2230,
|
||
|
"eisis'''" : 2240,
|
||
|
"feses'''" : 2250,
|
||
|
"fes'''" : 2260,
|
||
|
"f'''" : 2270,
|
||
|
"fis'''" : 2280,
|
||
|
"fisis'''" : 2290,
|
||
|
"geses'''" : 2300,
|
||
|
"ges'''" : 2310,
|
||
|
"g'''" : 2320,
|
||
|
"gis'''" : 2330,
|
||
|
"gisis'''" : 2340,
|
||
|
"aeses'''" : 2350,
|
||
|
"aes'''" : 2360,
|
||
|
"a'''" : 2370,
|
||
|
"ais'''" : 2380,
|
||
|
"aisis'''" : 2390,
|
||
|
"beses'''" : 2400,
|
||
|
"bes'''" : 2410,
|
||
|
"b'''" : 2420,
|
||
|
"bis'''" : 2430,
|
||
|
"bisis'''" : 2440,
|
||
|
"ceses''''" : 2450,
|
||
|
"ces''''" : 2460,
|
||
|
"c''''" : 2470,
|
||
|
"cis''''" : 2480,
|
||
|
"cisis''''" : 2490,
|
||
|
"deses''''" : 2500,
|
||
|
"des''''" : 2510,
|
||
|
"d''''" : 2520,
|
||
|
"dis''''" : 2530,
|
||
|
"disis''''" : 2540,
|
||
|
"eeses''''" : 2550,
|
||
|
"ees''''" : 2560,
|
||
|
"e''''" : 2570,
|
||
|
"eis''''" : 2580,
|
||
|
"eisis''''" : 2590,
|
||
|
"feses''''" : 2600,
|
||
|
"fes''''" : 2610,
|
||
|
"f''''" : 2620,
|
||
|
"fis''''" : 2630,
|
||
|
"fisis''''" : 2640,
|
||
|
"geses''''" : 2650,
|
||
|
"ges''''" : 2660,
|
||
|
"g''''" : 2670,
|
||
|
"gis''''" : 2680,
|
||
|
"gisis''''" : 2690,
|
||
|
"aeses''''" : 2700,
|
||
|
"aes''''" : 2710,
|
||
|
"a''''" : 2720,
|
||
|
"ais''''" : 2730,
|
||
|
"aisis''''" : 2740,
|
||
|
"beses''''" : 2750,
|
||
|
"bes''''" : 2760,
|
||
|
"b''''" : 2770,
|
||
|
"bis''''" : 2780,
|
||
|
"bisis''''" : 2790,
|
||
|
"ceses'''''" : 2800,
|
||
|
"ces'''''" : 2810,
|
||
|
"c'''''" : 2820,
|
||
|
"cis'''''" : 2830,
|
||
|
"cisis'''''" : 2840,
|
||
|
"deses'''''" : 2850,
|
||
|
"des'''''" : 2860,
|
||
|
"d'''''" : 2870,
|
||
|
"dis'''''" : 2880,
|
||
|
"disis'''''" : 2890,
|
||
|
"eeses'''''" : 2900,
|
||
|
"ees'''''" : 2910,
|
||
|
"e'''''" : 2920,
|
||
|
"eis'''''" : 2930,
|
||
|
"eisis'''''" : 2940,
|
||
|
"feses'''''" : 2950,
|
||
|
"fes'''''" : 2960,
|
||
|
"f'''''" : 2970,
|
||
|
"fis'''''" : 2980,
|
||
|
"fisis'''''" : 2990,
|
||
|
"geses'''''" : 3000,
|
||
|
"ges'''''" : 3010,
|
||
|
"g'''''" : 3020,
|
||
|
"gis'''''" : 3030,
|
||
|
"gisis'''''" : 3040,
|
||
|
"aeses'''''" : 3050,
|
||
|
"aes'''''" : 3060,
|
||
|
"a'''''" : 3070,
|
||
|
"ais'''''" : 3080,
|
||
|
"aisis'''''" : 3090,
|
||
|
"beses'''''" : 3100,
|
||
|
"bes'''''" : 3110,
|
||
|
"b'''''" : 3120,
|
||
|
"bis'''''" : 3130,
|
||
|
"bisis'''''" : 3140,
|
||
|
#"r" : float('inf'), a rest is not a pitch
|
||
|
}
|
||
|
|
||
|
def plain(pitch):
|
||
|
""" Extract the note from a note-number, without any octave but with the tailing zero.
|
||
|
This means we double-use the lowest octave as abstract version."""
|
||
|
#Dividing through the octave, 350, results in the number of the octave and the note as remainder.
|
||
|
return divmod(pitch, 350)[1]
|
||
|
|
||
|
def octave(pitch):
|
||
|
"""Return the octave of given note. Lowest 0 is X,,,"""
|
||
|
return divmod(pitch, 350)[0]
|
||
|
|
||
|
def halfToneDistanceFromC(pitch):
|
||
|
"""Return the half-tone step distance from C. The "sounding" interval"""
|
||
|
return {
|
||
|
#00 : 10, # ceses,,, -> bes
|
||
|
#10 : 11, # ces,,, -> b
|
||
|
|
||
|
00 : -2, # ceses,,, -> bes
|
||
|
10 : -1, # ces,,, -> b
|
||
|
20 : 0, # c,,,
|
||
|
30 : 1, # cis,,,
|
||
|
40 : 2, # cisis,,, -> d ...
|
||
|
50 : 0, # deses,,,
|
||
|
60 : 1, # des,,,
|
||
|
70 : 2, # d,,,
|
||
|
80 : 3, # dis,,,
|
||
|
90 : 4, # disis,,,
|
||
|
100 : 2, # eeses,,,
|
||
|
110 : 3, # ees,,,
|
||
|
120 : 4, # e,,,
|
||
|
130 : 5, # eis,,,
|
||
|
140 : 6, # eisis,,,
|
||
|
150 : 3, # feses,,,
|
||
|
160 : 4, # fes,,,
|
||
|
170 : 5, # f,,,
|
||
|
180 : 6, # fis,,,
|
||
|
190 : 7, # fisis,,,
|
||
|
200 : 5, # geses,,,
|
||
|
210 : 6, # ges,,,
|
||
|
220 : 7, # g,,,
|
||
|
230 : 8, # gis,,,
|
||
|
240 : 9, # gisis,,,
|
||
|
250 : 7, # aeses,,,
|
||
|
260 : 8, # aes,,,
|
||
|
270 : 9, # a,,,
|
||
|
280 : 10, # ais,,,
|
||
|
290 : 11, # aisis,,,
|
||
|
300 : 9, # beses,,,
|
||
|
310 : 10, # bes,,,
|
||
|
320 : 11, # b,,,
|
||
|
330 : 12, # bis,,,
|
||
|
340 : 13, # bisis,,,
|
||
|
#330 : 0, # bis,,,
|
||
|
#340 : 1, # bisis,,,
|
||
|
}[plain(pitch)]
|
||
|
|
||
|
|
||
|
lyToMidi = {} #filled for all pitches on startup, below
|
||
|
for ly, pitch in ly2pitch.items():
|
||
|
octOffset = (octave(pitch) +1) * 12 #twelve tones per midi octave
|
||
|
lyToMidi[ly] = octOffset + halfToneDistanceFromC(pitch)
|
||
|
|
||
|
|
||
|
lyToTicks = {
|
||
|
"16" : D16,
|
||
|
"8" : D8,
|
||
|
"4" : D4,
|
||
|
"2" : D2,
|
||
|
"1" : D1,
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
def ly(lilypondString):
|
||
|
"""Take string of simple lilypond notes, return midi pitches as generator of (pitch, ticks)"""
|
||
|
lastDur = "4"
|
||
|
for lyNote in lilypondString.split(" "):
|
||
|
try:
|
||
|
lyPitch, lyDur = re.split(r'(\d+)', lyNote)[0:2]
|
||
|
lastDur = lyDur
|
||
|
except ValueError:
|
||
|
lyPitch = re.split(r'(\d+)', lyNote)[0]
|
||
|
lyDur = lastDur
|
||
|
|
||
|
yield (lyToMidi[lyPitch], lyToTicks[lyDur])
|
||
|
|
||
|
|
||
|
def ly2cbox(lilypondString):
|
||
|
"""Return (pbytes, durationInTicks)
|
||
|
a python byte data type with midi data for cbox"""
|
||
|
#cbox.Pattern.serialize_event(position, midibyte1 (noteon), midibyte2(pitch), midibyte3(velocity))
|
||
|
pblob = bytes()
|
||
|
startTick = 0
|
||
|
for midiPitch, durationInTicks in ly(lilypondString):
|
||
|
endTick = startTick + durationInTicks - 1 #-1 ticks to create a small logical gap. This is nothing compared to our tick value dimensions, but it is enough for the midi protocol to treat two notes as separate ones. Imporant to say that this does NOT affect the next note on. This will be mathematically correct anyway.
|
||
|
pblob += cbox.Pattern.serialize_event(startTick, 0x90, midiPitch, 100) # note on
|
||
|
pblob += cbox.Pattern.serialize_event(endTick , 0x80, midiPitch, 100) # note off
|
||
|
startTick = startTick + durationInTicks #no -1 for the next note
|
||
|
return pblob, startTick
|
||
|
|
||
|
|
||
|
cboxTracks = {} #trackName:(cboxTrack,cboxMidiOutUuid)
|
||
|
def cboxSetTrack(trackName, durationInTicks, pattern):
|
||
|
"""Creates or resets calfbox tracks including jack connections
|
||
|
Keeps jack connections alive.
|
||
|
|
||
|
pattern is most likely a single pattern created through cbox.Document.get_song().pattern_from_blob
|
||
|
But it can also be a list of such patterns. In this case all patterns must be the same duration
|
||
|
and the parameter durationInTicks is the length of ONE pattern.
|
||
|
"""
|
||
|
|
||
|
if not trackName in cboxTracks:
|
||
|
cboxMidiOutUuid = cbox.JackIO.create_midi_output(trackName)
|
||
|
calfboxTrack = cbox.Document.get_song().add_track()
|
||
|
cboxTracks[trackName] = (calfboxTrack, cboxMidiOutUuid)
|
||
|
else:
|
||
|
calfboxTrack, cboxMidiOutUuid = cboxTracks[trackName]
|
||
|
calfboxTrack.delete()
|
||
|
|
||
|
calfboxTrack = cbox.Document.get_song().add_track()
|
||
|
calfboxTrack.set_external_output(cboxMidiOutUuid)
|
||
|
cbox.JackIO.rename_midi_output(cboxMidiOutUuid, trackName)
|
||
|
calfboxTrack.set_name(trackName)
|
||
|
|
||
|
if type(pattern) is cbox.DocPattern:
|
||
|
calfboxTrack.add_clip(0, 0, durationInTicks, pattern) #pos, offset, length(and not end-position, but is the same for the complete track), pattern
|
||
|
else: #iterable
|
||
|
assert iter(pattern)
|
||
|
#durationInTicks is the length of ONE pattern.
|
||
|
for i, pat in enumerate(pattern):
|
||
|
calfboxTrack.add_clip(i*durationInTicks, 0, durationInTicks, pat) #pos, offset, length, pattern.
|
||
|
calfboxTrack.add_clip(i*durationInTicks, 0, durationInTicks, pat) #pos, offset, length, pattern.
|
||
|
|
||
|
return calfboxTrack
|
||
|
|
||
|
|
||
|
def ly2Track(trackName, lyString):
|
||
|
"""Convert a simple string of lilypond notes to a cbox track and add that to the score"""
|
||
|
music = lyString
|
||
|
cboxBlob, durationInTicks = ly2cbox(music)
|
||
|
pattern = cbox.Document.get_song().pattern_from_blob(cboxBlob, durationInTicks)
|
||
|
cboxSetTrack(trackName, durationInTicks, pattern)
|
||
|
|
||
|
def getLongestTrackDuationInTicks():
|
||
|
return max( max(clip.pos + clip.length for clip in cboxTrack.track.status().clips) for cboxTrack in cbox.Document.get_song().status().tracks if cboxTrack.track.status().clips)
|
||
|
#for cboxTrack in cbox.Document.get_song().status().tracks:
|
||
|
# print (cboxTrack.track)
|
||
|
#TODO: These are different than the one above. It is more. Why?
|
||
|
#for cboxTrack, cboxMidiOutUuid in cboxTracks.values():
|
||
|
# print (cboxTrack.status())
|
||
|
|
||
|
|
||
|
def cboxLoop(eventLoop):
|
||
|
cbox.call_on_idle()
|
||
|
assert eventLoop.is_running()
|
||
|
|
||
|
#it is not that simple. status = "[Running]" if cbox.Transport.status().playing else "[Stopped]"
|
||
|
if cbox.Transport.status().playing == 1:
|
||
|
status = "[Running]"
|
||
|
elif cbox.Transport.status().playing == 0:
|
||
|
status = "[Stopped]"
|
||
|
elif cbox.Transport.status().playing == 2:
|
||
|
status = "[Stopping]"
|
||
|
elif cbox.Transport.status().playing is None:
|
||
|
status = "[Uninitialized]"
|
||
|
else:
|
||
|
raise ValueError("Unknown playback status: {}".format(cbox.Transport.status().playing))
|
||
|
|
||
|
stdout.write(" \r") #it is a hack but it cleans the line from old artefacts
|
||
|
stdout.write('{}: {}\r'.format(status, cbox.Transport.status().pos_ppqn))
|
||
|
stdout.flush()
|
||
|
eventLoop.call_later(0.1, cboxLoop, eventLoop) #100ms delay
|
||
|
|
||
|
eventLoop = get_event_loop()
|
||
|
def initCbox(clientName, internalEventProcessor=True, commonMidiInput=True):
|
||
|
cbox.init_engine("")
|
||
|
cbox.Config.set("io", "client_name", clientName)
|
||
|
cbox.Config.set("io", "enable_common_midi_input", commonMidiInput) #the default "catch all" midi input
|
||
|
cbox.start_audio()
|
||
|
scene = cbox.Document.get_engine().new_scene()
|
||
|
scene.clear()
|
||
|
cbox.do_cmd("/master/set_ppqn_factor", None, [D4]) #quarter note has how many ticks?
|
||
|
|
||
|
cbox.Transport.stop()
|
||
|
cbox.Transport.seek_ppqn(0)
|
||
|
cbox.Transport.set_tempo(120.0) #must be float
|
||
|
|
||
|
if internalEventProcessor:
|
||
|
eventLoop.call_soon(cboxLoop, eventLoop)
|
||
|
return scene, cbox, eventLoop
|
||
|
|
||
|
|
||
|
def connectPhysicalKeyboards(port="midi"):
|
||
|
midiKeyboards = cbox.JackIO.get_ports(".*", cbox.JackIO.MIDI_TYPE, cbox.JackIO.PORT_IS_SOURCE | cbox.JackIO.PORT_IS_PHYSICAL)
|
||
|
ourMidiInPort = cbox.Config.get("io", "client_name",) + ":" + port
|
||
|
for keyboard in midiKeyboards:
|
||
|
cbox.JackIO.port_connect(keyboard, ourMidiInPort)
|
||
|
|
||
|
def start(autoplay = False, userfunction = None, tempo = 120):
|
||
|
def ask_exit():
|
||
|
print()
|
||
|
eventLoop.stop()
|
||
|
shutdownCbox()
|
||
|
|
||
|
try:
|
||
|
dur = getLongestTrackDuationInTicks()
|
||
|
print("Starting with supplied music data. Setting sond duration to longest track")
|
||
|
assert dur > 0
|
||
|
cbox.Document.get_song().set_loop(dur, dur) #set playback length for the entire score.
|
||
|
except ValueError:
|
||
|
print ("Starting without a track. Setting song duration to a high value to generate recording space")
|
||
|
cbox.Document.get_song().set_loop(MAXIMUM, MAXIMUM) #set playback length for the entire score.
|
||
|
|
||
|
cbox.Transport.set_tempo(float(tempo))
|
||
|
cbox.Document.get_song().update_playback()
|
||
|
|
||
|
for signame in ('SIGINT', 'SIGTERM'):
|
||
|
eventLoop.add_signal_handler(getattr(signal, signame), ask_exit)
|
||
|
|
||
|
if userfunction:
|
||
|
print ("Send SIGUSR1 with following command to trigger user function")
|
||
|
print ("kill -10 {}".format(os.getpid()))
|
||
|
print ()
|
||
|
eventLoop.add_signal_handler(getattr(signal, "SIGUSR1"), userfunction)
|
||
|
|
||
|
print ("Use jack transport to control playback")
|
||
|
print ("Press Ctrl+C to abort")
|
||
|
print("pid %s: send SIGINT or SIGTERM to exit." % os.getpid())
|
||
|
|
||
|
try:
|
||
|
eventLoop.run_forever()
|
||
|
finally:
|
||
|
eventLoop.close()
|
||
|
|
||
|
|
||
|
def shutdownCbox():
|
||
|
cbox.Transport.stop()
|
||
|
cbox.Transport.seek_ppqn(0)
|
||
|
cbox.stop_audio()
|
||
|
cbox.shutdown_engine()
|