Added UDP connector for ORCA Livecoding !!

This commit is contained in:
sam 2020-05-05 22:42:22 +02:00
parent 012bef55b1
commit b79a3487eb
8 changed files with 850 additions and 319 deletions

View File

@ -4,7 +4,7 @@ By llstr, Sam Neurohack
LICENCE : CC NC
Midi and more exchanges over LAN/Internet
Midi and more exchanges over LAN/Internet. Now with ORCA live coding support !!
Imagine Bob own a TR 808 at home and Alice a nozoid OCS-2. They can control each other devices in a webpage or a midi controller.
If John has a mutliple encoders midi controller at home and want to control Alice and Bob devices, all changes made by John will be displayed to everyone and played by devices.
@ -25,6 +25,38 @@ More you can use also a vcvrack complex patch to drive light fixtures, laser abs
- Jamidi is experimental and nowhere safe. Network managment and security is not in jamidi scope. Some ideas : All computers can be on the same encrypted vpn (tinc, zerotier,..). The server can run on a VPS and all client connect to it. The server is at home, closer to midi devices,...
- Look at jamidi.json for set your configuration. 2 element types : midi device and IP servers
# ORCA livecoding support.
Livecode some instrument somewhere else. https://github.com/hundredrabbits/Orca
On computer linked with desired midi instrument :
python3 main.py
Livecoder should configure ORCA and use it :
CTRL K ip:ipaddress
CTRL K udp:udport (default is 8083)
MIDI CCs base 36 use ;cmnd
m : midi channel 0-F (0-15)
n : number 0-Z (0-35)
d : data 0-Z will output (d/36)*127
MIDI NOTES use ;nmonv
m : midi channel 0-F (0-15)
o : octave 0-8
n : Note A-G. For note with # : a-g
v : velocity 0-Z will output (v/36)*127
@ -39,9 +71,9 @@ or
/tr808/note/1
cc : is a "command". Currently cc, reset (highly specific to nozoid synthetiser). You can add any tyoe of "command".
cc : is a "command". Currently cc, reset (highly specific to nozoid synthetiser). You can add any tyoe of "command".
ocs2 : is a "device", that must be described, follow examples in jamidi.json. Here the Alice OCS2 will reveive broadcasted midi informations.
ocs2 : is a "device", that must be described, follow examples in jamidi.json. Here the Alice OCS2 will reveive broadcasted midi informations.
/ocs2 and /tr808 changes made by John will be displayed to everyone and played by devices.
@ -55,46 +87,55 @@ Websocket default port is 8081 but one can change.
Will receive all "commands" from all clients, forward them to local devices and broadcast them too.
servername : 'local', 'llstrvpn'. Servers (IP, port,...) must be described in jamidi.json
How to Run python server :
python3 main.py
Options :
servername : 'local', 'llstrvpn'. Servers (IP, port,...) must be described in jamidi.json
-h Show this help message and exit
--broadcast : Broadcast all incomings commands to all client. Default option.
-s SERVERNAME Servername: 'local', 'llstrvpn' (local by default)
--no-broadcast : Do not broadcast all incomings commands to all client.
-d DEVICENAME Mididevice for incoming ORCA via OSC (mmo3 by default)
-nocurrent Do not send all current CC values to all new client (enabled by default)
--reset : Send reset values to local device at startup. Default option.
-nobroadcast Do not broadcast all incomings commands to all client (enabled by default)
--no-reset : Do not send reset values to local device at startup.
-noreset Do not broadcast all incomings commands to all client (enabled by default)
-nothrough Disable the builtin midithrough from any midi IN to --device enabled by default
--current : Send all current CC values to all new client. Default option.
--no-current : Do not send all current CC values to all new client.
-verbose Enable debug mode (disabled by default)
# How it work : Clients
Can be webpages or midi instrument/software. Multiple clients types is supported.
Send midi over network to Jamidi. Can be webpages or midi instrument/software. Multiple clients types is supported.
How to Run python client :
python3 client.py
Options :
servername : Remote server 'local', 'xrkia' ('local' by default). Servers must be described in jamidi.json
default : Network <-> default midi device (True or False)
-s servername servername: 'local', 'xrkia' ('local' by default)
-nodefault Do not send reset values to local device a startup.
Some "rules" are available. Say you want all network incoming midi CC 1 channel 0 goes to a specific device on channel 3 CC 48. Rules must be described in rules.json
Each rule may happen at all time or only during a "song".
How to Run python client :
python3 client.py

View File

@ -51,6 +51,19 @@
}
],
"sam" :
[
{
"_comment": "Server is laser.teamlaser.fr",
"type": "serverconf",
"name": "tmlsr",
"IP": "192.168.2.43",
"port": 8081,
"oscport": 8082,
"udport": 8083
}
],
"sq-1": [
{
"_comment": "SQ-1 device parameters",
@ -89,59 +102,6 @@
"midichan" : 1,
"xname" : "mmo3"
}
],
"ocs2bcr": [
{
"_comment": "OCS-2 control with BCR2000",
"type": "mididevice",
"mididevice": "BCR2000 Port 1",
"midichan" : 2,
"xname" : "ocs2"
}
],
"mmo3bcr": [
{
"_comment": "MMO-3 control with BCR2000",
"type": "mididevice",
"mididevice": "BCR2000 Port 1",
"midichan" : 1,
"xname" : "mmo3"
}
],
"launchpad": [
{
"_comment": "Launchpad mini device parameters",
"type": "mididevice",
"mididevice": "Launchpad Mini",
"midichan" : 0,
"xname" : "launchpad"
}
],
"maxwell": [
{
"_comment": "Mawell device parameters",
"type": "mididevice",
"mididevice": "to Maxwell 1",
"midichan" : 0,
"xname" : "ocs2"
}
],
"default": [
{
"_comment": "Client : default midi device",
"type": "mididevice",
"mididevice": "BCR2000 Port 1",
"midichan" : 3,
"xname" : "default"
}
]
]
}

207
libs/OSCom.py Normal file
View File

@ -0,0 +1,207 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
'''
OSCcom for jamidi v0.1b
OSCom.Start(serverIP, OSCPORT)
default handler : handler(path, tags, args, source)
register particular OSC command in Start(): i.e oscserver.addMsgHandler( "/n", Note)
'''
import midi3
#import socket
import types, json
from OSC3 import OSCServer, OSCClient, OSCMessage
import _thread, time
import gstt
import WScom, UDPcom
import midi3
#base36 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
def GetTime():
return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
# this method of reporting timeouts only works by convention
# that before calling handle_request() field .timed_out is
# set to False
def handle_timeout(self):
self.timed_out = True
def Start(serverIP, OSCPORT):
global oscserver
#print(GetTime(),gstt.oscname, gstt.Confs[gstt.oscname][0]["midichan"])
#print(gstt.Confs)
#print(gstt.Confs[gstt.oscname])
for i in range(len(gstt.Confs[gstt.oscname])):
print(GetTime(),gstt.oscname, gstt.Confs[gstt.oscname][i]["midichan"])
oscserver = OSCServer( (serverIP, OSCPORT) )
oscserver.timeout = 0
# funny python's way to add a method to an instance of a class
import types
oscserver.handle_timeout = types.MethodType(handle_timeout, oscserver)
oscserver.addMsgHandler( "default", handler )
oscserver.addMsgHandler( "/n", Note)
oscserver.addMsgHandler( "/c", CC)
oscserver.addMsgHandler( "/p", PB)
_thread.start_new_thread(osc_thread, ())
# RAW OSC Frame available ?
def OSCframe():
# clear timed_out flag
oscserver.timed_out = False
# handle all pending requests then return
while not oscserver.timed_out:
oscserver.handle_request()
# OSC server Thread : handler, dacs reports and simulator points sender to UI.
def osc_thread():
#print("osc Thread launched")
try:
while True:
time.sleep(0.005)
OSCframe()
except Exception as e:
import sys, traceback
print('\n---------------------')
print('Exception: %s' % e)
print('- - - - - - - - - - -')
traceback.print_tb(sys.exc_info()[2])
print("\n")
# Properly close the system. Todo
def Stop():
oscserver.close()
# default handler
def handler(path, tags, args, source):
oscaddress = ''.join(path.split("/"))
print()
print("Jamidi Default OSC Handler got from " + str(source[0]),"OSC msg", path, "args", args)
#print("OSC address", path)
#print("find.. /bhoreal ?", path.find('/bhoreal'))
if len(args) > 0:
#print("with args", args)
pass
'''
# for example
if path == '/truc':
arg1 = args[0]
arg2 = args[1])
'''
'''
MIDI NOTES
=n in ORCA
/n in OSC
ORCA OSC
=nmonv /n m o n v
m : midi channel (0-15 / ORCA 0-F)
o : octave (0-8 / ORCA 0-7)
n : Note A to G
v : velocity 0-Z will output (v/36)*127
'''
def Note(path, tags, args, source):
#print('Note from ORCA received',args)
midichannel = int(args[0],36)
octave = int(args[1],36)
note = args[2]
velocity = int((int(args[3],36)/36)*127)
if note.istitle() == True:
notename = str(note)+ str(octave)
else:
notename = str(note)+ "#"+ str(octave)
if gstt.debug > 0:
print("incoming note", note, octave, notename, midi3.note2midi(notename) )
for mididevice in midi3.findJamDevices(gstt.oscname):
midi3.NoteOn(midi3.note2midi(notename), velocity, mididevice)
#midi3.NoteOn(int(wspath[1]), int(wspath[2]), gstt.Confs[wscommand[1]][0]["mididevice"])
'''
CC
=c in ORCA
/c in OSC
ORCA OSC
=cmcd /c m n d
m : midi channel
n : number (0-35 / ORCA 0-Z)
d : data 0-Z will output (d/36)*127
'''
def CC(path, tags, args, source):
midichannel = int(args[0],36)
ccvr = int(args[1],36)
ccvl = int((int(args[2],36)/36)*127)
if gstt.debug > 0:
print("ccvr=%d/ccvl=%d"%(ccvr,ccvl))
if gstt.oscname == "ocs2":
gstt.crtvalueOCS2[ccvr]=ccvl
else:
gstt.crtvalueMMO3[ccvr]=ccvl
for mididevice in midi3.findJamDevices(gstt.oscname):
midi3.cc(gstt.Confs[gstt.oscname][0]["midichan"], ccvr, ccvl, mididevice)
def PB(path, tags, args, source):
#print("Pitch number",ccnumber, value)
midichannel = int(args[0])
ccnumber = int(args[1])
ccdata = int(args[3])
'''
# If needed to send some OSC
def SendOSC(ip,port,oscaddress,oscargs=''):
oscmsg = OSCMessage()
oscmsg.setAddress(oscaddress)
oscmsg.append(oscargs)
osclient = OSCClient()
osclient.connect((ip, port))
if gstt.debug == True :
print("sending OSC message : ", oscmsg, "to", ip, ":", port)
try:
osclient.sendto(oscmsg, (ip, port))
oscmsg.clearData()
return True
except:
print ('Connection to', ip, 'refused : died ?')
return False
'''

175
libs/UDPcom.py Normal file
View File

@ -0,0 +1,175 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
'''
UDPcom for jamidi v0.1b
UDPcom.Start(serverIP, UDPORT)
Handler : udp_thread()
Read below for :
- MIDI NOTES use ;nmonv
- MIDI CCs use ;cmnd
'''
import midi3
#import socket
import types, json
import socket
import _thread, time
import midi3
import WScom, OSCom
import gstt
import time
#base36 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
def GetTime():
return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
def udp_thread():
while True:
payload, client_address = sock.recvfrom(1024)
udpath = payload.decode('utf_8')
if gstt.debug > 1:
print(GetTime(),"UDP got", udpath, "from", str(client_address))
#print(udpath[0:1], " ",udpath[1:2], " ",udpath[2:3], " ",udpath[3:4], " " )
if udpath[0:1] == "n":
Note(udpath)
if udpath[0:1] == "c":
CC(udpath)
if udpath[0:1] == "f":
FullCC(udpath)
time.sleep(0.005)
def Start(serverIP, UDPORT):
global sock
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server = ( serverIP,UDPORT)
sock.bind(server)
_thread.start_new_thread(udp_thread, ())
'''
MIDI NOTES use ;nmonv
m : midi channel 0-F (0-15)
o : octave 0-8
n : Note A-G. For note with # : a-g
v : velocity 0-Z will output (v/36)*127
'''
def Note(udpnote):
if gstt.debug>0:
print()
print(GetTime(),'UDPNote from ORCA received', udpnote, udpnote[1:1])
midichannel = int(udpnote[1:2],36)
octave = int(udpnote[2:3],36)
note = udpnote[3:4]
velocity = (int(udpnote[4:5],36)/36)*127
#if gstt.debug>0:
print(GetTime(),'UDPNote from ORCA received:','midichannel:',midichannel,'octave:',octave,'note:',note,'velocity:',velocity)
#if octave < 9 or midichannel < 16 or int(note,36) < 10 or int(note,36) > 16:
if octave < 9 and midichannel < 16 and int(note,36) >= 10 and int(note,36) <= 16:
if note.istitle() == True:
notename = str(note.upper())+ str(octave)
else:
notename = str(note.upper())+ "#"+ str(octave)
if gstt.debug > 0:
print(GetTime(),"Incoming note", notename, "=", midi3.note2midi(notename), "velocity", velocity, "for channel", midichannel)
for mididevice in midi3.findJamDevices(gstt.oscname):
midi3.NoteOn(midi3.note2midi(notename), int(velocity), mididevice, midichannel-1)
# if sending note back to WS users :
#WScom.sendWSall("/"+midi3.findJamName(gstt.oscname, midichannel)+"/noteon "+str(midi3.note2midi(notename)))
else:
print(GetTime(),"Note", midichannel, octave, note, velocity,"had offchart parameters.")
'''
MIDI CCs base 36 use ;cmnd
m : midi channel 0-F (0-15)
n : number 0-Z (0-35)
d : data 0-Z will output (d/36)*127
'''
def CC(udpcc):
print()
midichannel = int(udpcc[1:2],36)
#midichannel = base36.index(udpcc[1:2].upper())
ccvr = int(udpcc[2:3],36)
ccvl = int((int(udpcc[3:4],36)/36)*127)
if midichannel < 16:
if gstt.debug > 0:
print(GetTime(),"ccvr=%d/ccvl=%d"%(ccvr,ccvl))
if gstt.oscname == "ocs2":
gstt.crtvalueOCS2[ccvr]=ccvl
else:
gstt.crtvalueMMO3[ccvr]=ccvl
for mididevice in midi3.findJamDevices(gstt.oscname):
midi3.cc(midichannel, ccvr, ccvl, mididevice)
#midi3.cc(gstt.Confs[gstt.oscname][0]["midichan"], ccvr, ccvl, mididevice)
WScom.sendWSall("/"+midi3.findJamName(mididevice, midichannel)+"/cc/"+str(ccvr)+" "+str(ccvl))
else:
print(GetTime(),"Bad midichannel")
'''
MIDI Full CCs all 128 channels and data use ;fmnndd
m : midi channel 0-F (0-15)
nn : number 0-3J (0-127)
dd : data 0-3J (0-127)
'''
def FullCC(udpcc):
print()
midichannel = int(udpcc[1:2],36)
#midichannel = base36.index(udpcc[1:2].upper())
ccvr = int(udpcc[2:4],36)
ccvl = int(udpcc[4:6],36)
if midichannel < 16:
if gstt.debug > 0:
print(GetTime(),"ccvr=%d/ccvl=%d"%(ccvr,ccvl))
if gstt.oscname == "ocs2":
gstt.crtvalueOCS2[ccvr]=ccvl
else:
gstt.crtvalueMMO3[ccvr]=ccvl
for mididevice in midi3.findJamDevices(gstt.oscname):
midi3.cc(midichannel, ccvr, ccvl, mididevice)
WScom.sendWSall("/"+midi3.findJamName(mididevice, midichannel)+"/cc/"+str(ccvr)+" "+str(ccvl))
else:
print(GetTime(),"Bad midichannel")

169
libs/WScom.py Normal file
View File

@ -0,0 +1,169 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
'''
WScom for jamidi v0.1b
WScom.Start(serverIP, wsPORT)
WScom.runforever()
handler : message_received(client, wserver, message)
'''
import midi3
#import socket
import types, json
import _thread, time
from websocket_server import WebsocketServer
import gstt
import UDPcom, OSCom
def Start(serverIP, wsPORT):
global wserver
wserver = WebsocketServer(wsPORT,host=serverIP)
midi3.ws = wserver
wserver.set_fn_new_client(new_client)
wserver.set_fn_client_left(client_left)
wserver.set_fn_message_received(message_received)
def runforever():
wserver.run_forever()
# Called for every WS client connecting (after handshake)
def new_client(client, wserver):
print(midi3.GetTime(),"New WS client connected and was given id %d" % client['id'])
#sendWSall("/status Hello %d" % client['id'])
if gstt.current == True:
sendallcurrentccvalues("mmo3")
sendallcurrentccvalues("ocs2")
gstt.Players+=1
sendWSall("/status Hello %d" %(client['id']))
if gstt.Players > 1:
#sendWSall("/gstt.Players %d" %(Players))
sendWSall("/players (players:%d)" %(gstt.Players))
else:
sendWSall("/players (player:%d)" %(gstt.Players))
# Called for every WS client disconnecting
def client_left(client, wserver):
try:
print(midi3.GetTime(),"WS Client(%d) disconnected" % client['id'])
gstt.Players-=1
sendWSall("/players %d" %(gstt.Players))
except:
print("Something weird if coming from",client,"on the wire...")
pass
# Called for each WS received message.
def message_received(client, wserver, message):
print("")
if len(message) > 200:
message = message[:200]+'..'
wspath = message.split(" ")
if gstt.debug > 0:
print(midi3.GetTime(),"Main got from WS", client['id'], "said :", message, "splitted in an wspath :", wspath)
else:
print(midi3.GetTime(),"Main got WS Client", client['id'], "said :", message)
wscommand = wspath[0].split("/")
# gstt.debug
if gstt.debug > 0:
print("wscommand :",wscommand)
# noarg
if len(wspath) == 1:
args[0] = "noargs"
#print "noargs command"
# CC : /device/cc/2 127
elif wscommand[2] == "cc":
ccvr=int(wscommand[3]) #cc variable
ccvl=int(wspath[1]) #cc value
if gstt.debug > 0:
print("ccvr=%d/ccvl=%d"%(ccvr,ccvl))
if wscommand[1] == "ocs2":
gstt.crtvalueOCS2[ccvr]=ccvl
else:
gstt.crtvalueMMO3[ccvr]=ccvl
for mididevice in midi3.findJamDevices(wscommand[1]):
midi3.cc(gstt.Confs[wscommand[1]][0]["midichan"], ccvr, ccvl, mididevice)
# RESET : /device/reset 1
elif wscommand[2] == "reset":
if wscommand[1] == "ocs2":
reset("ocs2")
else:
reset("mmo3")
# NOTEON : /device/noteon note velocity
elif wscommand[2] == "noteon":
for mididevice in midi3.findJamDevices(wscommand[1]):
midi3.NoteOn(int(wspath[1]), int(wspath[2]), mididevice)
#midi3.NoteOn(int(wspath[1]), int(wspath[2]), gstt.Confs[wscommand[1]][0]["mididevice"])
# NOTEOFF /device/noteoff note
elif wscommand[2] == "noteoff":
for mididevice in midi3.findJamDevices(wscommand[1]):
midi3.NoteOff(int(wspath[1]), mididevice)
#midi3.NoteOff(int(wspath[1]), gstt.Confs[wscommand[1]][0]["mididevice"])
# Loop back : WS Client -> server -> WS Client
sendWSall(message)
# Send through websocket.
# Different websocket library for client (websocket) or server (websocket_server.
# ws object is added here by main.py or client.py startup : midi3.ws =
def send(message):
if gstt.clientmode == True:
send(message)
else:
wserver.send_message_to_all(msg = message)
def sendWSall(message):
if gstt.broadcast == True:
if gstt.debug >0:
print(midi3.GetTime(),"WS sending to all %s" % (message))
wserver.send_message_to_all(message)
# /send all current cc values
def sendallcurrentccvalues(nozoid):
if gstt.broadcast == True:
#print ""
print(midi3.GetTime(),"sending all current cc values of", nozoid)
if nozoid == "mmo3":
for ccnumber in range(0,32):
sendWSall("/mmo3/cc/"+str(ccnumber)+" "+str(gstt.crtvalueMMO3[ccnumber]))
else:
for ccnumber in range(0,32):
sendWSall("/ocs2/cc/"+str(ccnumber)+" "+str(gstt.crtvalueOCS2[ccnumber]))

39
libs/gstt.py Normal file
View File

@ -0,0 +1,39 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
"""
Jamidi states
v0.2.4b
LICENCE : CC
by Sam Neurohack
from /team/laser
"""
# reset = [64,64,0,32,96] # un truc comme ca pour les valeurs de reset ?
resetMMO3 = [0] * 32
resetOCS2 = [0] * 32
# record current values
crtvalueMMO3 = [0] * 32
crtvalueOCS2 = [0] * 32
# record number of loaded pages (aka client id or players)
Players=0
oscname = ""
Confs = []
debug = 0
BS="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def to_base(s, b):
res = ""
while s:
res+=BS[s%b]
s//= b
return res[::-1] or "0"

View File

@ -36,6 +36,8 @@ import sys
from sys import platform
import os
import re
import gstt
import WScom
is_py2 = sys.version[0] == '2'
@ -61,10 +63,8 @@ midinputsname = ["Name"] * 16
midinputsqueue = [Queue() for i in range(16) ]
midinputs = []
debug = 0
# False = server / True = Client
clientmode = False
gstt.clientmode = False
#Mser = False
@ -152,6 +152,7 @@ def note2midi(note_name):
pitch = match.group('n').upper()
offset = acc_map[match.group('off')]
octave = int(match.group('oct'))
except:
raise ValueError('Improper note format: {}'.format(note_name))
# Convert from the extrated ints to a full note number
@ -189,17 +190,17 @@ def midi2hz(note_number):
# in a 440 Hz tuning
return 440.0*(2.0**((note_number - 69)/12.0))
# /cc cc number value
def cc(midichannel, ccnumber, value, mididest):
if gstt.debug>0:
print(GetTime(),"Jamidi Sending Midi channel", midichannel, "cc", ccnumber, "value", value, "to", mididest)
MidiMsg([CONTROLLER_CHANGE+midichannel-1, ccnumber, value], mididest)
# Send through websocket.
# Different websocket library for client (websocket) or server (websocket_server.
# ws object is added here by main.py or client.py startup : midi3.ws =
def wssend(message):
if clientmode == True:
ws.send(message)
else:
ws.send_message_to_all(msg = message)
#
# MIDI Startup and handling
@ -231,8 +232,18 @@ def MidinProcess(inqueue, portname):
MidiVel = msg[2]
print(GetTime(),"NOTE ON :", MidiNote, 'velocity :', MidiVel, "Channel", MidiChannel)
#NoteOn(msg[1],msg[2],mididest)
#NoteOn(msg[1],msg[2],mididest,MidiChannel-1)
#beware !!
if gstt.nothrough == False:
for mididevice in findJamDevices(gstt.oscname):
#NoteOn(msg[1],msg[2],"XXXX",MidiChannel-1)
if gstt.debug>0:
print(GetTime(),"mididevice/oscname:",mididevice,"/",gstt.oscname)
NoteOn(msg[1],msg[2],mididevice,MidiChannel-1)
print(GetTime(),"Midi in process send /"+findJamName(portname, MidiChannel)+"/noteon "+str(msg[1])+" "+str(msg[2]))
wssend("/"+findJamName(portname, MidiChannel)+"/noteon "+str(msg[1])+" "+str(msg[2]))
WScom.send("/"+findJamName(portname, MidiChannel)+"/noteon "+str(msg[1])+" "+str(msg[2]))
'''
# Sampler mode : note <63 launch snare.wav / note > 62 kick.wav
@ -264,7 +275,7 @@ def MidinProcess(inqueue, portname):
print(GetTime(),"NOTE OFF :", MidiNote, 'velocity :', MidiVel, "Channel", MidiChannel)
#NoteOff(msg[1],msg[2], mididest)
print(GetTime(),"Midi in process send /"+findJamName(portname, MidiChannel)+"/noteoff "+str(msg[1]))
wssend("/"+findJamName(portname, MidiChannel)+"/noteoff "+str(msg[1]))
WScom.send("/"+findJamName(portname, MidiChannel)+"/noteoff "+str(msg[1]))
# # CC on all Midi Channels
@ -274,7 +285,7 @@ def MidinProcess(inqueue, portname):
#findJamName(portname, MidiChannel)
print(GetTime(),"channel", MidiChannel, " ",findJamName(portname, MidiChannel), " CC :", msg[1], msg[2])
print(GetTime(),"Midi in process send /"+findJamName(portname, MidiChannel)+"/cc/"+str(msg[1])+" "+str(msg[2])+" to WS")
wssend("/"+findJamName(portname, MidiChannel)+"/cc/"+str(msg[1])+" "+str(msg[2]))
WScom.send("/"+findJamName(portname, MidiChannel)+"/cc/"+str(msg[1])+" "+str(msg[2]))
'''
@ -282,7 +293,7 @@ def MidinProcess(inqueue, portname):
if CONTROLLER_CHANGE -1 < msg[0] < 192:
print("channel 1 (MMO-3) CC :", msg[1], msg[2])
print("Midi in process send /mmo3/cc/"+str(msg[1])+" "+str(msg[2])+" to WS")
wssend("/mmo3/cc/"+str(msg[1])+" "+str(msg[2]))
WScom.send("/mmo3/cc/"+str(msg[1])+" "+str(msg[2]))
# OCS-2 Midi CC message CHANNEL 2
@ -290,7 +301,7 @@ def MidinProcess(inqueue, portname):
print("channel 2 (OCS-2) CC :", msg[1], msg[2])
print("Midi in process send /ocs2/cc/"+str(msg[1])+" "+str(msg[2])+" to WS")
wssend("/ocs2/cc/"+str(msg[1])+" "+str(msg[2]))
WScom.send("/ocs2/cc/"+str(msg[1])+" "+str(msg[2]))
'''
@ -303,19 +314,24 @@ def MidinProcess(inqueue, portname):
'''
def NoteOn(note,color, mididest):
#def NoteOn(note, color, mididest):
#https://pypi.org/project/python-rtmidi/0.3a/
#à NOTE_ON=#90 et NOTE_OFF=#80 on ajoute le channel (0 le premier) pour envoyer effectivement sur le channel
def NoteOn(note, color, mididest, midichannel=0):
global MidInsNumber
if gstt.debug >0:
print(GetTime(),"Sending", note, color, "to", mididest, "on channel", midichannel)
for port in range(MidInsNumber):
# To mididest
if midiname[port].find(mididest) == 0:
midiport[port].send_message([NOTE_ON, note, color])
midiport[port].send_message([NOTE_ON+midichannel, note, color])
# To All
elif mididest == "all" and midiname[port].find(mididest) != 0:
midiport[port].send_message([NOTE_ON, note, color])
midiport[port].send_message([NOTE_ON+midichannel, note, color])
@ -491,8 +507,8 @@ def InConfig():
print(GetTime(),"MIDIin...")
# client mode
if debug > 0:
if clientmode == True:
if gstt.debug > 0:
if gstt.clientmode == True:
print(GetTime(),"midi3 in client mode")
else:
print(GetTime(),"midi3 in server mode")
@ -603,8 +619,8 @@ def MidiMsg(midimsg, mididest):
for port in range(len(OutDevice)):
# To mididest
if midiname[port].find(mididest) != -1:
if debug>0:
print(GetTime(),"jamidi 3 sending to name", midiname[port], "port", port, ":", midimsg)
if gstt.debug>0:
print(GetTime(),"jamidi3 sending to name", midiname[port], "port", port, ":", midimsg)
midiport[port].send_message(midimsg)
desterror = 0
@ -612,11 +628,11 @@ def MidiMsg(midimsg, mididest):
print(GetTime(),"mididest",mididest, ": ** This midi destination doesn't exists **")
# send midi msg over ws.
#if clientmode == True:
#if gstt.clientmode == True:
# ws.send("/ocs2/cc/1 2")
'''
def NoteOn(note, velocity, mididest):
global MidInsNumber
@ -626,7 +642,7 @@ def NoteOn(note, velocity, mididest):
# To mididest
if midiname[port].find(mididest) == 0:
midiport[port].send_message([NOTE_ON, note, velocity])
'''
def listdevice(number):
@ -638,17 +654,20 @@ def listdevice(number):
# return device name for given mididevice and midichannel
def findJamName(mididevice, midichan):
#print("searching", mididevice, "channel", midichan,'...')
for (k, v) in Confs.items():
if gstt.debug >0:
print(GetTime(),"Findjamname searching", mididevice, "channel", midichan,'...')
for (k, v) in gstt.Confs.items():
#print("Key: " + k)
#print("Value: " + str(v))
if v[0]["type"] == "mididevice":
#print(v[0]["mididevice"],v[0]["midichan"], type(v[0]["midichan"]))
#print(k, v[0]["mididevice"],v[0]["midichan"],type(v[0]["midichan"]),v[0]["xname"], "?")
if (v[0]["mididevice"] == mididevice) and (v[0]["midichan"] == midichan):
print(GetTime(),"Incoming event from", k, "xname", v[0]["xname"])
return v[0]["xname"]
return "None"
@ -656,15 +675,17 @@ def findJamName(mididevice, midichan):
def findJamDevices(name):
devices = []
print (GetTime(),"searching", name)
for (k, v) in Confs.items():
if gstt.debug >0:
print (GetTime(),"Findjamdevice searching", name)
for (k, v) in gstt.Confs.items():
if v[0]["type"] == "mididevice":
#print(k, name,v[0]["xname"])
if v[0]["xname"] == name:
#print(v[0]["mididevice"])
devices.append(v[0]["mididevice"])
# print(devices)
if gstt.debug>0:
print(GetTime(),devices)
return devices

353
main.py
View File

@ -14,6 +14,21 @@ wserver.run_forever()
wserver.send_message_to_all(message)
ORCA:
CTRL K pour rentrer une commande
CTRL K ip:127.0.0.1
CTRL K osc:8082
CTRL K cc:0
CTRL K udp:udport
/p
Pitch bend
'''
@ -33,28 +48,36 @@ from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON,
sys.path.append('libs/')
import midi3
from websocket_server import WebsocketServer
#import socket
import types, json
import argparse
import _thread, time
debug = 1
from midi3 import note2midi
from midi3 import GetTime
import OSCom
import WScom
import UDPcom
import gstt
print ("")
print ("Arguments parsing if needed...")
argsparser = argparse.ArgumentParser(description="Jamidi Server v0.1b commands help mode")
argsparser.add_argument("-s","--servername",help="Servername: 'local', 'llstrvpn' (local by default)", type=str)
argsparser.add_argument('--current',help="Send all current CC values to all new client. Default option" , dest='current', action='store_true')
argsparser.add_argument('--no-current',help="Do not send all current CC values to all new client. ", dest='current', action='store_false')
argsparser.add_argument('--broadcast',help="Broadcast all incomings commands to all client. Default option" , dest='broadcast', action='store_true')
argsparser.add_argument('--no-broadcast',help="Do not broadcast all incomings commands to all client", dest='broadcast', action='store_false')
argsparser.add_argument('--reset',help="Send reset values to local device a startup. Default option" , dest='reset', action='store_true')
argsparser.add_argument('--no-reset',help="Do not send reset values to local device a startup.", dest='reset', action='store_false')
argsparser.set_defaults(reset=True)
argsparser.add_argument("-d","--device",help="midi device for incoming ORCA via UDP (mmo3 by default)", type=str)
argsparser.add_argument('-nothrough',help="Disable the builtin midithrough from any midi IN to --device enabled by default", dest='nothrough', action='store_true')
argsparser.set_defaults(nothrough=False)
argsparser.add_argument('-nocurrent',help="Do not send all current CC values to all new client (enabled by default)", dest='current', action='store_false')
argsparser.set_defaults(current=True)
argsparser.add_argument('-nobroadcast',help="Do not broadcast all incomings commands to all client (enabled by default)", dest='broadcast', action='store_false')
argsparser.set_defaults(broadcast=True)
argsparser.add_argument('-noreset',help="Do not broadcast all incomings commands to all client (enabled by default)", dest='reset', action='store_false')
argsparser.set_defaults(reset=True)
argsparser.add_argument('-verbose',help="Enable debug mode (disabled by default)", dest='verbose', action='store_true')
argsparser.set_defaults(verbose=False)
args = argsparser.parse_args()
@ -64,41 +87,95 @@ if args.servername:
else:
servername = "local"
# ORCA destination device
if args.device:
gstt.oscname = args.device
else:
gstt.oscname = "mmo3"
# Broadcast commands to all clients ?
if args.broadcast == False:
print("Broadcast disabled")
broadcast = False
gstt.broadcast = False
else:
print("Broadcast enabled")
broadcast = True
gstt.broadcast = True
# Send current values to all new client ?
if args.current == False:
print("Do not send current values at startup disabled")
current = False
gstt.current = False
else:
print("Current values update at startup disabled")
current = True
gstt.current = True
# Reset at startup ?
if args.reset == False:
print("Reset at startup disabled")
startreset = False
gstt.startreset = False
else:
print("Reset at startup enabled")
startreset = True
gstt.startreset = True
# Debug/verbose mode ?
if args.verbose == False:
print("Debug mode disabled")
gstt.debug = 0
else:
print("Debug mode enabled")
gstt.debug = 1
# nomidithrough mode ?
if args.nothrough == False:
print("Midi through mode")
gstt.nothrough = False
else:
print("No midi through mode")
gstt.nothrough = True
# reset = [64,64,0,32,96] # un truc comme ca pour les valeurs de reset ?
resetMMO3 = [0] * 32
resetOCS2 = [0] * 32
#base36 = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
# record current values
crtvalueMMO3 = [0] * 32
crtvalueOCS2 = [0] * 32
'''
base36 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z}
transpose =
"0": None, "1": None, "2": None, "3":, None, "4":, None, "5":, "6":, "7":, "8":, "9":, "A": "A0", "B": B0,
C ,"C0", D : "D0", "E": "E0" F G H I J K L M N
"C0" "D0" "E0" "F0" "G0" "A0" "B0" "C1" "D1" "E1" "F1" "G1"
O P Q R S T U V W X Y Z
"A1" "B1" "C2" "D2" "E2" "F2" "G2" "A2" "B2" "C3" "D3" "E3"
'''
#
# Settings from jamidi.json
#
# Load midi definitions in jamidi.json
def LoadConfs():
if os.path.exists('jamidi.json'):
f=open("jamidi.json","r")
s = f.read()
gstt.Confs = json.loads(s)
#print(GetTime(),gstt.Confs)
# return midi confname number for given type
def findConfs(confname,conftype):
#print("searching", midiconfname,'...')
position = -1
for counter in range(len(gstt.Confs[conftype])):
if confname == gstt.Confs[conftype][counter]['name']:
#print(confname, "is ", counter)
position = counter
return position
LoadConfs()
# record number of loaded pages (aka client id or players)
Players=0
#
@ -117,8 +194,8 @@ def GetTime():
# /cc cc number value
def cc(midichannel, ccnumber, value, mididest):
if debug>0:
print("Jamidi Sending Midi channel", midichannel, "cc", ccnumber, "value", value, "to", mididest)
if gstt.debug>0:
print(GetTime(),"Jamidi Sending Midi channel", midichannel, "cc", ccnumber, "value", value, "to", mididest)
midi3.MidiMsg([CONTROLLER_CHANGE+midichannel-1, ccnumber, value], mididest)
@ -131,173 +208,17 @@ def reset(nozoid):
if nozoid == "mmo3":
for ccnumber in range(0,32):
midi3.MidiMsg([CONTROLLER_CHANGE+Confs["mmo3"][0]["midichan"]-1, ccnumber, resetMMO3[ccnumber]], Confs["mmo3"][0]["mididevice"])
sendWSall("/mmo3/cc/"+str(ccnumber)+" "+str(resetMMO3[ccnumber]))
crtvalueMMO3[ccnumber]=resetMMO3[ccnumber]
midi3.MidiMsg([CONTROLLER_CHANGE+gstt.Confs["mmo3"][0]["midichan"]-1, ccnumber, gstt.resetMMO3[ccnumber]], gstt.Confs["mmo3"][0]["mididevice"])
WScom.sendWSall("/mmo3/cc/"+str(ccnumber)+" "+str(gstt.resetMMO3[ccnumber]))
gstt.crtvalueMMO3[ccnumber]=gstt.resetMMO3[ccnumber]
else:
for ccnumber in range(0,32):
midi3.MidiMsg([CONTROLLER_CHANGE+Confs["ocs2"][0]["midichan"]-1, ccnumber, resetOCS2[ccnumber]], Confs["ocs2"][0]["mididevice"])
sendWSall("/ocs2/cc/"+str(ccnumber)+" "+str(resetOCS2[ccnumber]))
crtvalueOCS2[ccnumber]=resetOCS2[ccnumber]
print("End of reset for", nozoid)
midi3.MidiMsg([CONTROLLER_CHANGE+gstt.Confs["ocs2"][0]["midichan"]-1, ccnumber, gstt.resetOCS2[ccnumber]], gstt.Confs["ocs2"][0]["mididevice"])
WScom.sendWSall("/ocs2/cc/"+str(ccnumber)+" "+str(gstt.resetOCS2[ccnumber]))
gstt.crtvalueOCS2[ccnumber]=gstt.resetOCS2[ccnumber]
print(GetTime(),"End of reset for", nozoid)
print("")
# /send all current cc values
def sendallcurrentccvalues(nozoid):
if broadcast == True:
#print ""
print(GetTime(),"sending all current cc values of", nozoid)
if nozoid == "mmo3":
for ccnumber in range(0,32):
sendWSall("/mmo3/cc/"+str(ccnumber)+" "+str(crtvalueMMO3[ccnumber]))
else:
for ccnumber in range(0,32):
sendWSall("/ocs2/cc/"+str(ccnumber)+" "+str(crtvalueOCS2[ccnumber]))
#
# Settings from jamidi.json
#
# Load midi definitions in jamidi.json
def LoadConfs():
global Confs, nbmidiconf
if os.path.exists('jamidi.json'):
f=open("jamidi.json","r")
s = f.read()
Confs = json.loads(s)
# return midi confname number for given type
def findConfs(confname,conftype):
#print("searching", midiconfname,'...')
position = -1
for counter in range(len(Confs[conftype])):
if confname == Confs[conftype][counter]['name']:
#print(confname, "is ", counter)
position = counter
return position
#
# Websocket part
#
# Called for every WS client connecting (after handshake)
def new_client(client, wserver):
global Players
print(GetTime(),"New WS client connected and was given id %d" % client['id'])
#sendWSall("/status Hello %d" % client['id'])
if current == True:
sendallcurrentccvalues("mmo3")
sendallcurrentccvalues("ocs2")
Players+=1
sendWSall("/status Hello %d" %(client['id']))
if Players > 1:
#sendWSall("/players %d" %(Players))
sendWSall("/players (players:%d)" %(Players))
else:
sendWSall("/players (player:%d)" %(Players))
# Called for every WS client disconnecting
def client_left(client, wserver):
global Players
try:
print(GetTime(),"WS Client(%d) disconnected" % client['id'])
Players-=1
sendWSall("/players %d" %(Players))
except:
print("Something weird if coming from",client,"on the wire...")
pass
# Called for each WS received message.
def message_received(client, wserver, message):
print("")
if len(message) > 200:
message = message[:200]+'..'
oscpath = message.split(" ")
if debug > 0:
print(GetTime(),"Main got from WS", client['id'], "said :", message, "splitted in an oscpath :", oscpath)
else:
print(GetTime(),"Main got WS Client", client['id'], "said :", message)
wscommand = oscpath[0].split("/")
# debug
if debug > 0:
print("wscommand :",wscommand)
# noarg
if len(oscpath) == 1:
args[0] = "noargs"
#print "noargs command"
# CC : /device/cc/2 127
elif wscommand[2] == "cc":
ccvr=int(wscommand[3]) #cc variable
ccvl=int(oscpath[1]) #cc value
if debug > 0:
print("ccvr=%d/ccvl=%d"%(ccvr,ccvl))
if wscommand[1] == "ocs2":
#cc(Confs[wscommand[1]][0]["midichan"], ccvr, ccvl, Confs[wscommand[1]][0]["mididevice"])
crtvalueOCS2[ccvr]=ccvl
else:
#cc(Confs[wscommand[1]][0]["midichan"], ccvr, ccvl, Confs[wscommand[1]][0]["mididevice"])
crtvalueMMO3[ccvr]=ccvl
for mididevice in midi3.findJamDevices(wscommand[1]):
cc(Confs[wscommand[1]][0]["midichan"], ccvr, ccvl, mididevice)
# RESET : /device/reset 1
elif wscommand[2] == "reset":
if wscommand[1] == "ocs2":
reset("ocs2")
else:
reset("mmo3")
# NOTEON : /device/noteon note velocity
elif wscommand[2] == "noteon":
for mididevice in midi3.findJamDevices(wscommand[1]):
midi3.NoteOn(int(oscpath[1]), int(oscpath[2]), mididevice)
#midi3.NoteOn(int(oscpath[1]), int(oscpath[2]), Confs[wscommand[1]][0]["mididevice"])
# NOTEOFF /device/noteoff note
elif wscommand[2] == "noteoff":
for mididevice in midi3.findJamDevices(wscommand[1]):
midi3.NoteOff(int(oscpath[1]), mididevice)
#midi3.NoteOff(int(oscpath[1]), Confs[wscommand[1]][0]["mididevice"])
# Loop back : WS Client -> server -> WS Client
sendWSall(message)
def sendWSall(message):
if broadcast == True:
if debug >0:
print(GetTime(),"sending to all %s" % (message))
wserver.send_message_to_all(message)
@ -305,45 +226,43 @@ def sendWSall(message):
# Running...
#
LoadConfs()
serverIP = Confs[servername][0]["IP"]
wsPORT = Confs[servername][0]["port"]
print(Confs["ocs2"][0]["mididevice"])
serverIP = gstt.Confs[servername][0]["IP"]
wsPORT = gstt.Confs[servername][0]["port"]
OSCPORT = gstt.Confs[servername][0]["oscport"]
UDPORT = gstt.Confs[servername][0]["udport"]
print("Running....")
print()
print(GetTime(),"Launching servers...")
print(GetTime(),"Launching OSC Server", serverIP,':', OSCPORT)
OSCom.Start(serverIP, OSCPORT)
print(GetTime(),"Launching WS Server", serverIP,':', wsPORT)
UDPcom.Start(serverIP, UDPORT)
print(GetTime(),"Launching UDP Server", serverIP,':', UDPORT)
WScom.Start(serverIP, wsPORT)
if gstt.startreset == True:
print(GetTime(),"resetting nozoids...")
reset("mmo3")
reset("ocs2")
# Main
# Main loop do nothing. Maybe do the webui server ?
try:
#while True:
# Websocket startup
wserver = WebsocketServer(wsPORT,host=serverIP)
midi3.ws = wserver
midi3.Confs = Confs
#midi3.findJamDevices("ocs2")
print("")
print(GetTime(),"Launching Jamidi Websocket server...")
print(GetTime(),"at", serverIP, "port",wsPORT)
wserver.set_fn_new_client(new_client)
wserver.set_fn_client_left(client_left)
wserver.set_fn_message_received(message_received)
print(GetTime(),"Jamidi running forever...")
if startreset == True:
print("resetting nozoids...")
reset("mmo3")
reset("ocs2")
#print ""
print(GetTime(),"WS server running forever...")
wserver.run_forever()
WScom.runforever()
except Exception:
traceback.print_exc()
finally:
OSCom.Stop()
# Gently stop on CTRL C