This commit is contained in:
sam 2020-04-21 15:18:24 +02:00
parent 0528576418
commit 0fe991e0f2
2 changed files with 225 additions and 62 deletions

View File

@ -40,6 +40,7 @@ or
/tr808/note/1 /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. /ocs2 and /tr808 changes made by John will be displayed to everyone and played by devices.
@ -60,12 +61,17 @@ Options :
servername : 'local', 'llstrvpn'. Servers (IP, port,...) must be described in jamidi.json servername : 'local', 'llstrvpn'. Servers (IP, port,...) must be described in jamidi.json
--broadcast : Broadcast all incomings commands to all client. Default option. --broadcast : Broadcast all incomings commands to all client. Default option.
--no-broadcast : Do not broadcast all incomings commands to all client. --no-broadcast : Do not broadcast all incomings commands to all client.
--reset : Send reset values to local device a startup. Default option. --reset : Send reset values to local device a startup. Default option.
--no-reset : Do not send reset values to local device a startup. --no-reset : Do not send reset values to local device a startup.
--current : Send all current CC values to all new client. Default option. --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. --no-current : Do not send all current CC values to all new client.
@ -78,11 +84,17 @@ Can be webpages or midi instrument/software. Multiple clients types is supported
Options : Options :
servername : Remote server 'local', 'xrkia' ('local' by default). Servers must be described in jamidi.json servername : Remote server 'local', 'xrkia' ('local' by default). Servers must be described in jamidi.json
default : Network <-> default midi device (True or False) default : Network <-> default midi device (True or False)
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 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". Each rule may happen at all time or only during a "song".
How to Run python client :
python3 client.py
@ -90,13 +102,18 @@ Each rule may happen at all time or only during a "song".
1/ Run : 1/ Run :
python3 mainwip.py
python3 main.py
2/ In a browser open 2 instances of each of these : 2/ In a browser open 2 instances of each of these :
indexaurora.html is a three rotating encoders example. indexaurora.html is a three rotating encoders example.
mmo3.html and ocs2.html demo to control 2 real life nozoids. mmo3.html and ocs2.html demo to control 2 real life nozoids.
3/ Each aurora pages talk to each other. Each mmo3 to each other and so on.. 3/ Each aurora pages talk to each other. Each mmo3 to each other and so on..
@ -114,7 +131,7 @@ var LJ = 'ws://127.0.0.1:8081/'
3/ Run : 3/ Run :
python3 mainwip.py python3 main.py
4/ 4/

266
client.py
View File

@ -1,37 +1,27 @@
#!/usr/bin/python2.7 #!/usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# -*- mode: Python -*- # -*- mode: Python -*-
''' '''
NozoidUI Client v0.0.1 Jamidi Client v0.1b
Input : local midi instruments
Output : nozoidtUI server
Websocket INSTALLER
https://files.pythonhosted.org/packages/8b/0f/52de51b9b450ed52694208ab952d5af6ebbcbce7f166a48784095d930d8c/websocket_client-0.57.0.tar.gz
Input : local midi (hardware/software) instruments
Output : Jamidi server
''' '''
print("")
print("")
print("Jamidi Client")
print("v0.1b")
print "" #from multiprocessing import Process, Queue, TimeoutError
print "" #import subprocess
print "NozoidUI Client"
print "v0.0.1"
from multiprocessing import Process, Queue, TimeoutError
import subprocess
import sys import sys
import traceback import traceback
import os import os
import time import time
import json
from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF, from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF,
PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE) PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE)
@ -44,27 +34,46 @@ import websocket
try: try:
import thread import _thread
except ImportError: except ImportError:
import _thread as thread import _thread as thread
import time
import argparse
debug = 1 debug = 1
#
# webUI server
#
serverIP = "xrkia.org" print ("")
# serverIP = "127.0.0.1" print ("Arguments parsing if needed...")
# serverIP = "10.8.0.46" argsparser = argparse.ArgumentParser(description="Jamidi Client v0.1b commands help mode")
wsPORT = 8081 argsparser.add_argument("-s","--servername",help="servername: 'local', 'xrkia' ('local' by default)", type=str)
argsparser.add_argument('--default',help="All incoming midi <-> default midi device. Default option." , dest='default', action='store_true')
argsparser.add_argument('--no-default',help="Do not send reset values to local device a startup.", dest='default', action='store_false')
argsparser.set_defaults(default=True)
args = argsparser.parse_args()
# Server
if args.servername:
servername = args.servername
else:
servername = "local"
# Default midi device like controller
if args.default == False:
print("Default device disabled")
defaultdevice = False
else:
print("Default device enabled")
defaultdevice = True
# #
# Midi part # Midi part
# #
# default local MIDI device
nozmidi = "BCR2000 Port 1" nozmidi = "BCR2000 Port 1"
# nozmidi = "Arturia BeatStep" # nozmidi = "Arturia BeatStep"
# nozmidi = "Virtual Midi A" # nozmidi = "Virtual Midi A"
@ -79,30 +88,151 @@ midichanMMO3 = 1
resetMMO3 = [0] * 32 resetMMO3 = [0] * 32
resetOCS2 = [0] * 32 resetOCS2 = [0] * 32
# /cc cc number value songs = ["song1", "song2"]
def cc(midichannel, ccnumber, value, mididest): song = 0
print "NozoidUI Sending Midi channel", midichannel, "cc", ccnumber, "value", value, "to", mididest
def GetTime():
return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
#
# Settings from clientruler.json
#
# Load midi routing definitions in clientruler.json
def LoadMidiRules():
global MidiRules, nbmidirule
if os.path.exists('rules.json'):
f=open("rules.json","r")
s = f.read()
MidiRules = json.loads(s)
# return midi rulename number for given type 'Specials', 'cc2cc'
def findMidiRules(rulename,ruletype):
#print("searching", midirulename,'...')
position = -1
for counter in range(len(MidiRules[ruletype])):
if rulename == MidiRules[ruletype][counter]['name']:
#print(rulename, "is ", counter)
position = counter
return position
#
# Settings from jamidi.json
#
# Load midi routing definitions in clientconfr.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 'Specials', 'cc2cc'
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
def curved(value):
return round(np.sqrt(value)*11.27)
# /cc cc number value
def cc(midichannel, ccnumber, value, mididest):
#print "cc()"
print("Jamidi Sending locally Midi channel", midichannel, "cc", ccnumber, "value", value, "to", mididest)
# Apply CC routing ?
if len(MidiRules["cc2cc"]) > 0 :
#print "cc2cc test for channel", midichannel, "CC", ccnumber, "val", value, "song", songs[song]
for counter in range(len(MidiRules["cc2cc"])):
if (MidiRules["cc2cc"][counter]["songname"] == songs[song] or MidiRules["cc2cc"][counter]["songname"] == "all") and (MidiRules["cc2cc"][counter]["chanIN"] == midichannel or MidiRules["cc2cc"][counter]["chanIN"] == "all") and (MidiRules["cc2cc"][counter]["ccs"] == ccnumber or MidiRules["cc2cc"][counter]["ccs"] == "all"):
print("cc2cc routing for song",MidiRules["cc2cc"][counter]["songname"], ":", MidiRules["cc2cc"][counter]["name"])
#print("cc2cc got song :", MidiRules["cc2cc"][counter]["songname"]," IN Channel :", MidiRules["cc2cc"][counter]["chanIN"]," Code :", MidiRules["cc2cc"][counter]["code"], " value :",MidiRules["ZnotesLcc"][counter]["valuetype"], )
if MidiRules["cc2cc"][counter]["valuetype"] == "linear":
print("Linear", MidiVal)
midi3.MidiMsg((176 + MidiRules["cc2cc"][counter]["chanOUT"], MidiRules["cc2cc"][counter]["ccOUT"], MidiVal), MidiRules["cc2cc"][counter]["mididest"])
if MidiRules["cc2cc"][counter]["valuetype"] == "curved":
print(MidiVal,"got curved", curved(MidiVal))
midi3.MidiMsg((176 + MidiRules["cc2cc"][counter]["chanOUT"], MidiRules["cc2cc"][counter]["ccOUT"], curved(MidiVal)), MidiRules["cc2cc"][counter]["mididest"])
#else:
print("Jamidi Sending locally Midi channel", midichannel, "cc", ccnumber, "value", value, "to", mididest)
#if mididest == "BCR2000 Port 1": #if mididest == "BCR2000 Port 1":
midi3.MidiMsg([CONTROLLER_CHANGE+midichannel-1, ccnumber, value], mididest) midi3.MidiMsg([CONTROLLER_CHANGE+midichannel-1, ccnumber, value], mididest)
'''
# Specials features ?
if len(MidiRules['Specials']) > 0:
for counter in range(len(MidiRules["Specials"])):
# print()
# print("Name", MidiRules["Specials"][counter]["name"])
# print("Song", MidiRules["Specials"][counter]["songname"], songs[song]) # name, "all"
# print("Channel", MidiRules["Specials"][ counter]["chanIN"], MidiChannel) # number, "all"
# print("Note", MidiRules["Specials"][counter]["notes"], MidiNote) # number, "all"
# print("Notetype", MidiRules["Specials"][counter]["notetype"], "on") # "on", "off", "all"
if (MidiRules["Specials"][counter]["songname"] == songs[song] or MidiRules["Specials"][counter]["songname"] == "all") and (MidiRules["Specials"][counter]["chanIN"] == MidiChannel or MidiRules["Specials"][counter]["chanIN"] == "all") and (MidiRules["Specials"][counter]["notes"] == MidiNote or MidiRules["Specials"][counter]["notes"] == "all") and (MidiRules["Specials"][counter]["notetype"] == "off" or MidiRules["Specials"][counter]["notetype"] == "all") :
midirulecode = MidiRules["Specials"][counter]["code"]
print("Specials function :",MidiRules["Specials"][counter]["songname"], ":", MidiRules["Specials"][counter]["name"], midirulecode)
# python function
if midirulecode.count('.') > 0:
#print(midirulecode+"("+str(MidiNote)+')')
eval(midirulecode+"("+str(MidiNote)+')')
# Maxwell function
elif midirulecode.count('/') > 0:
#print("Specials NoteON got :", MidiRules["Specials"][counter]["songname"]," IN Channel :", MidiRules["Specials"][counter]["chanIN"]," Code :", midirulecode, " CC", maxwellccs.FindCC(MidiRules["Specials"][counter]["code"]), " value :",MidiRules["Specials"][counter]["valuetype"], " laser :", MidiRules["ZccLcc"][counter]["laser"] )
midi3.MidiMsg((CONTROLLER_CHANGE, maxwellccs.FindCC(MidiRules["Specials"][counter]["code"]), MidiRules["Specials"][counter]["valuetype"]), mididest, laser = MidiRules["Specials"][counter]["laser"])
'''
# /reset nozoids with "default" values # /reset nozoids with "default" values
def reset(nozoid): def reset(nozoid):
print "" print("")
print "reseting", nozoid print(GetTime(),"reseting", nozoid)
if nozoid == "mmo3": if nozoid == "mmo3":
for ccnumber in xrange(0,32): for ccnumber in range(0,32):
midi3.MidiMsg([CONTROLLER_CHANGE+midichanMMO3-1, ccnumber, resetMMO3[ccnumber]], nozmidi) midi3.MidiMsg([CONTROLLER_CHANGE+Confs["mmo3"][0]["midichan"]-1, ccnumber, resetMMO3[ccnumber]], Confs["mmo3"][0]["mididevice"])
sendWSall("/mmo3/cc/"+str(ccnumber)+" "+str(resetMMO3[ccnumber])) sendWSall("/mmo3/cc/"+str(ccnumber)+" "+str(resetMMO3[ccnumber]))
crtvalueMMO3[ccnumber]=resetMMO3[ccnumber]
else: else:
for ccnumber in xrange(0,32): for ccnumber in range(0,32):
midi3.MidiMsg([CONTROLLER_CHANGE+midichanOCS2-1, ccnumber, resetOCS2[ccnumber]], nozmidi) midi3.MidiMsg([CONTROLLER_CHANGE+Confs["ocs2"][0]["midichan"]-1, ccnumber, resetOCS2[ccnumber]], Confs["ocs2"][0]["mididevice"])
sendWSall("/ocs2/cc/"+str(ccnumber)+" "+str(resetMMO3[ccnumber])) sendWSall("/ocs2/cc/"+str(ccnumber)+" "+str(resetOCS2[ccnumber]))
crtvalueOCS2[ccnumber]=resetOCS2[ccnumber]
print("End of reset for", nozoid)
print("")
# #
# Websocket part # Websocket part
# #
@ -132,11 +262,11 @@ def on_open(ws):
print("thread terminating...") print("thread terminating...")
thread.start_new_thread(run, ()) _thread.start_new_thread(run, ())
def on_message(ws, message): def on_message(ws, message):
# #
print "" print("")
print(message) print(message)
if len(message) > 200: if len(message) > 200:
message = message[:200]+'..' message = message[:200]+'..'
@ -144,22 +274,31 @@ def on_message(ws, message):
oscpath = message.split(" ") oscpath = message.split(" ")
if debug > 0: if debug > 0:
#print "Client got from WS", client['id'], "said :", message, "splitted in an oscpath :", oscpath #print "Client got from WS", client['id'], "said :", message, "splitted in an oscpath :", oscpath
print "Client got from WS said :", message, "splitted in an oscpath :", oscpath print("clientruler got from WS :", message, "splitted in an oscpath :", oscpath)
wscommand = oscpath[0].split("/") wscommand = oscpath[0].split("/")
print "WS command was :",wscommand print("WS command was :",wscommand)
if len(oscpath) == 1: if len(oscpath) == 1:
args[0] = "noargs" args[0] = "noargs"
#print "noargs command" #print "noargs command"
elif wscommand[2] == "cc": elif wscommand[1] == "ocs2":
if wscommand[1] == "ocs2": if wscommand[2] == "cc":
print "Incoming OCS-2 WS" print("Incoming OCS-2 WS CC", wscommand[3], ":", int(oscpath[1]))
cc(midichanOCS2, int(wscommand[3]), int(oscpath[1]), nozmidi) cc(Confs["ocs2"][0]["midichan"], int(wscommand[3]), int(oscpath[1]), Confs["default"][0]["mididevice"])
else:
print "Incoming MMO-3 WS" if wscommand[2] == "OSC1":
cc(midichanMMO3, int(wscommand[3]), int(oscpath[1]), nozmidi) print("Incoming OCS-2 WS OSC1", wscommand[3], ":", int(oscpath[1]))
elif wscommand[1] == "mmo3":
if wscommand[2] == "cc":
print("Incoming MMO-3 WS CC", wscommand[3], ":", int(oscpath[1]))
cc(Confs["mmo3"][0]["midichan"], int(wscommand[3]), int(oscpath[1]), Confs["default"][0]["mididevice"])
if wscommand[2] == "OSC1":
print("Incoming MMO-3 WS OSC1", wscommand[3], ":", int(oscpath[1]))
elif wscommand[2] == "reset": elif wscommand[2] == "reset":
@ -173,15 +312,21 @@ def on_message(ws, message):
# sendWSall(message) # sendWSall(message)
LoadMidiRules()
LoadConfs()
print "Running...." serverIP = Confs[servername][0]["IP"]
#serverIP = "10.8.0.46"
wsPORT = Confs[servername][0]["port"]
print("Running....")
# Main loop do nothing. Maybe do the webui server ? # Main loop do nothing. Maybe do the webui server ?
try: try:
print "" print("")
print "Connecting to NozoidUI server..." print("Connecting to Jamidi server...")
print "at", serverIP, "port",wsPORT print("at", serverIP, "port",wsPORT)
#websocket.enableTrace(True) #websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://"+str(serverIP)+":"+str(wsPORT), ws = websocket.WebSocketApp("ws://"+str(serverIP)+":"+str(wsPORT),
@ -192,21 +337,22 @@ try:
midi3.ws = ws midi3.ws = ws
midi3.wsmode = True midi3.wsmode = True
print "Midi Configuration..." print("Midi Configuration...")
print "Midi Destination", nozmidi
midi3.check() midi3.check()
ws.on_open = on_open ws.on_open = on_open
ws.run_forever() ws.run_forever()
except Exception:
traceback.print_exc()
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
# Gently stop on CTRL C # Gently stop on CTRL C
print "Fin de NozoidUI." print("Fin de Jamidi.")