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
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 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
--broadcast : Broadcast all incomings commands to all client. Default option.
--no-broadcast : Do not broadcast all incomings commands to all client.
--reset : Send reset values to local device a startup. Default option.
--no-reset : Do not send reset values to local device a startup.
--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.
@ -78,11 +84,17 @@ Can be webpages or midi instrument/software. Multiple clients types is supported
Options :
servername : Remote server 'local', 'xrkia' ('local' by default). Servers must be described in jamidi.json
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
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 :
python3 mainwip.py
python3 main.py
2/ In a browser open 2 instances of each of these :
indexaurora.html is a three rotating encoders example.
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..
@ -114,7 +131,7 @@ var LJ = 'ws://127.0.0.1:8081/'
3/ Run :
python3 mainwip.py
python3 main.py
4/

266
client.py
View File

@ -1,37 +1,27 @@
#!/usr/bin/python2.7
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
'''
NozoidUI Client v0.0.1
Input : local midi instruments
Output : nozoidtUI server
Websocket INSTALLER
https://files.pythonhosted.org/packages/8b/0f/52de51b9b450ed52694208ab952d5af6ebbcbce7f166a48784095d930d8c/websocket_client-0.57.0.tar.gz
Jamidi Client v0.1b
Input : local midi (hardware/software) instruments
Output : Jamidi server
'''
print("")
print("")
print("Jamidi Client")
print("v0.1b")
print ""
print ""
print "NozoidUI Client"
print "v0.0.1"
from multiprocessing import Process, Queue, TimeoutError
import subprocess
#from multiprocessing import Process, Queue, TimeoutError
#import subprocess
import sys
import traceback
import os
import time
import json
from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF,
PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE)
@ -44,27 +34,46 @@ import websocket
try:
import thread
import _thread
except ImportError:
import _thread as thread
import time
import argparse
debug = 1
#
# webUI server
#
serverIP = "xrkia.org"
# serverIP = "127.0.0.1"
# serverIP = "10.8.0.46"
wsPORT = 8081
print ("")
print ("Arguments parsing if needed...")
argsparser = argparse.ArgumentParser(description="Jamidi Client v0.1b commands help mode")
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
#
# default local MIDI device
nozmidi = "BCR2000 Port 1"
# nozmidi = "Arturia BeatStep"
# nozmidi = "Virtual Midi A"
@ -79,30 +88,151 @@ midichanMMO3 = 1
resetMMO3 = [0] * 32
resetOCS2 = [0] * 32
# /cc cc number value
def cc(midichannel, ccnumber, value, mididest):
songs = ["song1", "song2"]
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":
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
def reset(nozoid):
print ""
print "reseting", nozoid
print("")
print(GetTime(),"reseting", nozoid)
if nozoid == "mmo3":
for ccnumber in xrange(0,32):
midi3.MidiMsg([CONTROLLER_CHANGE+midichanMMO3-1, ccnumber, resetMMO3[ccnumber]], nozmidi)
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]
else:
for ccnumber in xrange(0,32):
midi3.MidiMsg([CONTROLLER_CHANGE+midichanOCS2-1, ccnumber, resetOCS2[ccnumber]], nozmidi)
sendWSall("/ocs2/cc/"+str(ccnumber)+" "+str(resetMMO3[ccnumber]))
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)
print("")
#
# Websocket part
#
@ -132,11 +262,11 @@ def on_open(ws):
print("thread terminating...")
thread.start_new_thread(run, ())
_thread.start_new_thread(run, ())
def on_message(ws, message):
#
print ""
print("")
print(message)
if len(message) > 200:
message = message[:200]+'..'
@ -144,22 +274,31 @@ def on_message(ws, message):
oscpath = message.split(" ")
if debug > 0:
#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("/")
print "WS command was :",wscommand
print("WS command was :",wscommand)
if len(oscpath) == 1:
args[0] = "noargs"
#print "noargs command"
elif wscommand[2] == "cc":
if wscommand[1] == "ocs2":
print "Incoming OCS-2 WS"
cc(midichanOCS2, int(wscommand[3]), int(oscpath[1]), nozmidi)
else:
print "Incoming MMO-3 WS"
cc(midichanMMO3, int(wscommand[3]), int(oscpath[1]), nozmidi)
elif wscommand[1] == "ocs2":
if wscommand[2] == "cc":
print("Incoming OCS-2 WS CC", wscommand[3], ":", int(oscpath[1]))
cc(Confs["ocs2"][0]["midichan"], int(wscommand[3]), int(oscpath[1]), Confs["default"][0]["mididevice"])
if wscommand[2] == "OSC1":
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":
@ -173,15 +312,21 @@ def on_message(ws, 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 ?
try:
print ""
print "Connecting to NozoidUI server..."
print "at", serverIP, "port",wsPORT
print("")
print("Connecting to Jamidi server...")
print("at", serverIP, "port",wsPORT)
#websocket.enableTrace(True)
ws = websocket.WebSocketApp("ws://"+str(serverIP)+":"+str(wsPORT),
@ -192,21 +337,22 @@ try:
midi3.ws = ws
midi3.wsmode = True
print "Midi Configuration..."
print "Midi Destination", nozmidi
print("Midi Configuration...")
midi3.check()
ws.on_open = on_open
ws.run_forever()
except Exception:
traceback.print_exc()
except KeyboardInterrupt:
pass
# Gently stop on CTRL C
print "Fin de NozoidUI."
print("Fin de Jamidi.")