bugfixs
This commit is contained in:
parent
6f53bfb3b8
commit
62c37a6e06
45
client.py
45
client.py
@ -8,6 +8,20 @@ Jamidi Client v0.1b
|
|||||||
Input : local midi (hardware/software) instruments
|
Input : local midi (hardware/software) instruments
|
||||||
Output : Jamidi server
|
Output : Jamidi server
|
||||||
|
|
||||||
|
|
||||||
|
ws = websocket.WebSocketApp("ws://"+str(serverIP)+":"+str(wsPORT),
|
||||||
|
on_message = on_message,
|
||||||
|
on_error = on_error,
|
||||||
|
on_close = on_close)
|
||||||
|
|
||||||
|
ws.on_open = on_open
|
||||||
|
ws.run_forever()
|
||||||
|
ws.send(message)
|
||||||
|
ws.send_message_to_all(msg = message)
|
||||||
|
|
||||||
|
ws.close()
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
@ -25,11 +39,12 @@ 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)
|
||||||
|
|
||||||
|
sys.path.append('libs/')
|
||||||
import midi3
|
import midi3
|
||||||
import types
|
import types
|
||||||
import websocket
|
import websocket
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import _thread
|
import _thread
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -123,7 +138,7 @@ def findMidiRules(rulename,ruletype):
|
|||||||
# Settings from jamidi.json
|
# Settings from jamidi.json
|
||||||
#
|
#
|
||||||
|
|
||||||
# Load midi routing definitions in clientconfr.json
|
# Load midi definitions in jamidi.json
|
||||||
def LoadConfs():
|
def LoadConfs():
|
||||||
global Confs, nbmidiconf
|
global Confs, nbmidiconf
|
||||||
|
|
||||||
@ -134,7 +149,7 @@ def LoadConfs():
|
|||||||
Confs = json.loads(s)
|
Confs = json.loads(s)
|
||||||
|
|
||||||
|
|
||||||
# return midi confname number for given type 'Specials', 'cc2cc'
|
# return midi confname number for given type
|
||||||
def findConfs(confname,conftype):
|
def findConfs(confname,conftype):
|
||||||
|
|
||||||
#print("searching", midiconfname,'...')
|
#print("searching", midiconfname,'...')
|
||||||
@ -272,8 +287,9 @@ 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(GetTime(),"Main got from WS", client['id'], "said :", message, "splitted in an oscpath :", oscpath)
|
||||||
print("client got from WS :", message, "splitted in an oscpath :", oscpath)
|
else:
|
||||||
|
print(GetTime(),"Main got WS Client", client['id'], "said :", message)
|
||||||
|
|
||||||
wscommand = oscpath[0].split("/")
|
wscommand = oscpath[0].split("/")
|
||||||
|
|
||||||
@ -286,6 +302,22 @@ def on_message(ws, message):
|
|||||||
args[0] = "noargs"
|
args[0] = "noargs"
|
||||||
#print "noargs command"
|
#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
|
||||||
|
|
||||||
|
cc(Confs[wscommand[1]][0]["midichan"], ccvr, ccvl, Confs[wscommand[1]][0]["mididevice"])
|
||||||
|
|
||||||
|
'''
|
||||||
# CC : /device/cc/2 127
|
# CC : /device/cc/2 127
|
||||||
elif wscommand[1] == "ocs2":
|
elif wscommand[1] == "ocs2":
|
||||||
if wscommand[2] == "cc":
|
if wscommand[2] == "cc":
|
||||||
@ -303,7 +335,7 @@ def on_message(ws, message):
|
|||||||
|
|
||||||
if wscommand[2] == "OSC1":
|
if wscommand[2] == "OSC1":
|
||||||
print("Incoming MMO-3 WS OSC1", wscommand[3], ":", int(oscpath[1]))
|
print("Incoming MMO-3 WS OSC1", wscommand[3], ":", int(oscpath[1]))
|
||||||
|
'''
|
||||||
|
|
||||||
# RESET : /device/reset 1
|
# RESET : /device/reset 1
|
||||||
elif wscommand[2] == "reset":
|
elif wscommand[2] == "reset":
|
||||||
@ -356,6 +388,7 @@ try:
|
|||||||
|
|
||||||
midi3.ws = ws
|
midi3.ws = ws
|
||||||
midi3.clientmode = True
|
midi3.clientmode = True
|
||||||
|
midi3.Confs = Confs
|
||||||
|
|
||||||
print("Midi Configuration...")
|
print("Midi Configuration...")
|
||||||
midi3.check()
|
midi3.check()
|
||||||
|
34
jamidi.json
34
jamidi.json
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
"ocs2": [
|
"ocs2": [
|
||||||
{
|
{
|
||||||
"_comment": "OCS-2 parameters",
|
"_comment": "OCS-2 device parameters",
|
||||||
"type": "mididevice",
|
"type": "mididevice",
|
||||||
"mididevice": "UM-ONE:UM-ONE MIDI 1 20:0",
|
"mididevice": "UM-ONE:UM-ONE MIDI 1 20:0",
|
||||||
"midichan" : 2
|
"midichan" : 2
|
||||||
@ -43,19 +43,47 @@
|
|||||||
|
|
||||||
"mmo3": [
|
"mmo3": [
|
||||||
{
|
{
|
||||||
"_comment": "MMO-3 parameters",
|
"_comment": "MMO-3 device parameters",
|
||||||
"type": "mididevice",
|
"type": "mididevice",
|
||||||
"mididevice": "UM-ONE:UM-ONE MIDI 1 20:0",
|
"mididevice": "UM-ONE:UM-ONE MIDI 1 20:0",
|
||||||
"midichan" : 1
|
"midichan" : 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ocs2": [
|
||||||
|
{
|
||||||
|
"_comment": "OCS-2 control with BCR2000",
|
||||||
|
"type": "mididevice",
|
||||||
|
"mididevice": "BCR2000 Port 1",
|
||||||
|
"midichan" : 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"mmo3": [
|
||||||
|
{
|
||||||
|
"_comment": "MMO-3 control with BCR2000",
|
||||||
|
"type": "mididevice",
|
||||||
|
"mididevice": "BCR2000 Port 1",
|
||||||
|
"midichan" : 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"launchpad": [
|
||||||
|
{
|
||||||
|
"_comment": "Launchpad mini device parameters",
|
||||||
|
"type": "mididevice",
|
||||||
|
"mididevice": "Launchpad Mini",
|
||||||
|
"midichan" : 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
"default": [
|
"default": [
|
||||||
{
|
{
|
||||||
"_comment": "Client : default midi device",
|
"_comment": "Client : default midi device",
|
||||||
"type": "mididevice",
|
"type": "mididevice",
|
||||||
"mididevice": "BCR2000 Port 1",
|
"mididevice": "BCR2000 Port 1",
|
||||||
"midichan" : 1
|
"midichan" : 3
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
40
main.py
Normal file → Executable file
40
main.py
Normal file → Executable file
@ -4,6 +4,17 @@
|
|||||||
'''
|
'''
|
||||||
Jamidi Server v0.1b
|
Jamidi Server v0.1b
|
||||||
|
|
||||||
|
wserver = WebsocketServer(wsPORT,host=serverIP)
|
||||||
|
|
||||||
|
wserver.set_fn_new_client(new_client)
|
||||||
|
wserver.set_fn_client_left(client_left)
|
||||||
|
wserver.set_fn_message_received(message_received)
|
||||||
|
|
||||||
|
wserver.run_forever()
|
||||||
|
|
||||||
|
wserver.send_message_to_all(message)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
@ -18,6 +29,8 @@ import time
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
sys.path.append('libs/')
|
||||||
import midi3
|
import midi3
|
||||||
|
|
||||||
from websocket_server import WebsocketServer
|
from websocket_server import WebsocketServer
|
||||||
@ -25,6 +38,7 @@ from websocket_server import WebsocketServer
|
|||||||
import types, json
|
import types, json
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
debug = 1
|
debug = 1
|
||||||
|
|
||||||
|
|
||||||
@ -66,6 +80,14 @@ else:
|
|||||||
print("Current values update at startup disabled")
|
print("Current values update at startup disabled")
|
||||||
current = True
|
current = True
|
||||||
|
|
||||||
|
# Reset at startup ?
|
||||||
|
if args.reset == False:
|
||||||
|
print("Reset at startup disabled")
|
||||||
|
startreset = False
|
||||||
|
else:
|
||||||
|
print("Reset at startup enabled")
|
||||||
|
startreset = True
|
||||||
|
|
||||||
|
|
||||||
# reset = [64,64,0,32,96] # un truc comme ca pour les valeurs de reset ?
|
# reset = [64,64,0,32,96] # un truc comme ca pour les valeurs de reset ?
|
||||||
resetMMO3 = [0] * 32
|
resetMMO3 = [0] * 32
|
||||||
@ -138,7 +160,7 @@ def sendallcurrentccvalues(nozoid):
|
|||||||
# Settings from jamidi.json
|
# Settings from jamidi.json
|
||||||
#
|
#
|
||||||
|
|
||||||
# Load midi routing definitions in clientconfr.json
|
# Load midi definitions in jamidi.json
|
||||||
def LoadConfs():
|
def LoadConfs():
|
||||||
global Confs, nbmidiconf
|
global Confs, nbmidiconf
|
||||||
|
|
||||||
@ -149,7 +171,7 @@ def LoadConfs():
|
|||||||
Confs = json.loads(s)
|
Confs = json.loads(s)
|
||||||
|
|
||||||
|
|
||||||
# return midi confname number for given type 'Specials', 'cc2cc'
|
# return midi confname number for given type
|
||||||
def findConfs(confname,conftype):
|
def findConfs(confname,conftype):
|
||||||
|
|
||||||
#print("searching", midiconfname,'...')
|
#print("searching", midiconfname,'...')
|
||||||
@ -209,11 +231,8 @@ def message_received(client, wserver, message):
|
|||||||
else:
|
else:
|
||||||
print(GetTime(),"Main got WS Client", client['id'], "said :", message)
|
print(GetTime(),"Main got WS Client", client['id'], "said :", message)
|
||||||
|
|
||||||
|
|
||||||
# wscommand will be like ['', 'ocs2', 'cc', '9']
|
|
||||||
wscommand = oscpath[0].split("/")
|
wscommand = oscpath[0].split("/")
|
||||||
|
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
print("wscommand :",wscommand)
|
print("wscommand :",wscommand)
|
||||||
@ -257,9 +276,6 @@ def message_received(client, wserver, message):
|
|||||||
elif wscommand[2] == "noteoff":
|
elif wscommand[2] == "noteoff":
|
||||||
midi3.NoteOff(int(oscpath[1]), Confs[wscommand[1]][0]["mididevice"])
|
midi3.NoteOff(int(oscpath[1]), Confs[wscommand[1]][0]["mididevice"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ws.send("/noteon "+str(msg[1])+" "+str(msg[2]))
|
|
||||||
|
|
||||||
# Loop back : WS Client -> server -> WS Client
|
# Loop back : WS Client -> server -> WS Client
|
||||||
sendWSall(message)
|
sendWSall(message)
|
||||||
@ -293,8 +309,9 @@ try:
|
|||||||
# Websocket startup
|
# Websocket startup
|
||||||
wserver = WebsocketServer(wsPORT,host=serverIP)
|
wserver = WebsocketServer(wsPORT,host=serverIP)
|
||||||
midi3.ws = wserver
|
midi3.ws = wserver
|
||||||
|
midi3.Confs = Confs
|
||||||
|
midi3.findJamDevice("UM-ONE:UM-ONE MIDI 1 20:0", 1)
|
||||||
|
|
||||||
#print wserver
|
|
||||||
print("")
|
print("")
|
||||||
print(GetTime(),"Launching Jamidi Websocket server...")
|
print(GetTime(),"Launching Jamidi Websocket server...")
|
||||||
print(GetTime(),"at", serverIP, "port",wsPORT)
|
print(GetTime(),"at", serverIP, "port",wsPORT)
|
||||||
@ -302,7 +319,8 @@ try:
|
|||||||
wserver.set_fn_client_left(client_left)
|
wserver.set_fn_client_left(client_left)
|
||||||
wserver.set_fn_message_received(message_received)
|
wserver.set_fn_message_received(message_received)
|
||||||
|
|
||||||
if reset == True:
|
if startreset == True:
|
||||||
|
print("resetting nozoids...")
|
||||||
reset("mmo3")
|
reset("mmo3")
|
||||||
reset("ocs2")
|
reset("ocs2")
|
||||||
|
|
||||||
@ -312,6 +330,8 @@ try:
|
|||||||
|
|
||||||
wserver.run_forever()
|
wserver.run_forever()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
562
midi3.py
562
midi3.py
@ -1,562 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Midi3 light version for soundt/Jamidi/clapt
|
|
||||||
v0.7.0
|
|
||||||
|
|
||||||
Midi Handler :
|
|
||||||
|
|
||||||
- Hook to the MIDI host
|
|
||||||
- Enumerate connected midi devices and spawn a process/device to handle incoming events
|
|
||||||
|
|
||||||
by Sam Neurohack
|
|
||||||
from /team/laser
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import time
|
|
||||||
from threading import Thread
|
|
||||||
|
|
||||||
import rtmidi
|
|
||||||
from rtmidi.midiutil import open_midiinput
|
|
||||||
from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF,
|
|
||||||
PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE)
|
|
||||||
import mido
|
|
||||||
from mido import MidiFile
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
import weakref
|
|
||||||
import sys
|
|
||||||
from sys import platform
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
is_py2 = sys.version[0] == '2'
|
|
||||||
if is_py2:
|
|
||||||
from queue import Queue
|
|
||||||
from OSC import OSCServer, OSCClient, OSCMessage
|
|
||||||
else:
|
|
||||||
from queue import Queue
|
|
||||||
from OSC3 import OSCServer, OSCClient, OSCMessage
|
|
||||||
|
|
||||||
|
|
||||||
print("")
|
|
||||||
|
|
||||||
midiname = ["Name"] * 16
|
|
||||||
midiport = [rtmidi.MidiOut() for i in range(16) ]
|
|
||||||
|
|
||||||
OutDevice = []
|
|
||||||
InDevice = []
|
|
||||||
|
|
||||||
# max 16 midi port array
|
|
||||||
|
|
||||||
midinputsname = ["Name"] * 16
|
|
||||||
midinputsqueue = [Queue() for i in range(16) ]
|
|
||||||
midinputs = []
|
|
||||||
|
|
||||||
debug = 0
|
|
||||||
|
|
||||||
# False = server / True = Client
|
|
||||||
clientmode = False
|
|
||||||
|
|
||||||
#Mser = False
|
|
||||||
|
|
||||||
MidInsNumber = 0
|
|
||||||
|
|
||||||
|
|
||||||
clock = mido.Message(type="clock")
|
|
||||||
|
|
||||||
start = mido.Message(type ="start")
|
|
||||||
stop = mido.Message(type ="stop")
|
|
||||||
ccontinue = mido.Message(type ="continue")
|
|
||||||
reset = mido.Message(type ="reset")
|
|
||||||
songpos = mido.Message(type ="songpos")
|
|
||||||
|
|
||||||
#mode = "maxwell"
|
|
||||||
|
|
||||||
'''
|
|
||||||
print "clock",clock)
|
|
||||||
print "start",start)
|
|
||||||
print "continue", ccontinue)
|
|
||||||
print "reset",reset)
|
|
||||||
print "sonpos",songpos)
|
|
||||||
'''
|
|
||||||
|
|
||||||
try:
|
|
||||||
input = raw_input
|
|
||||||
except NameError:
|
|
||||||
# Python 3
|
|
||||||
Exception = Exception
|
|
||||||
|
|
||||||
|
|
||||||
STATUS_MAP = {
|
|
||||||
'noteon': NOTE_ON,
|
|
||||||
'noteoff': NOTE_OFF,
|
|
||||||
'programchange': PROGRAM_CHANGE,
|
|
||||||
'controllerchange': CONTROLLER_CHANGE,
|
|
||||||
'pitchbend': PITCH_BEND,
|
|
||||||
'polypressure': POLY_PRESSURE,
|
|
||||||
'channelpressure': CHANNEL_PRESSURE
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
notes = ["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"]
|
|
||||||
def midi2note(midinote):
|
|
||||||
|
|
||||||
print("midinote",midinote, "note", notes[midinote%12]+str(round(midinote/12)))
|
|
||||||
return notes[midinote%12]+str(round(midinote/12))
|
|
||||||
|
|
||||||
# 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
|
|
||||||
#
|
|
||||||
|
|
||||||
mqueue = Queue()
|
|
||||||
inqueue = Queue()
|
|
||||||
|
|
||||||
#
|
|
||||||
# Events from Generic MIDI Handling
|
|
||||||
#
|
|
||||||
|
|
||||||
def MidinProcess(inqueue, portname):
|
|
||||||
|
|
||||||
inqueue_get = inqueue.get
|
|
||||||
|
|
||||||
while True:
|
|
||||||
time.sleep(0.001)
|
|
||||||
msg = inqueue_get()
|
|
||||||
print("")
|
|
||||||
print("Generic from", portname,"msg : ", msg)
|
|
||||||
|
|
||||||
# Note On
|
|
||||||
if msg[0]==NOTE_ON:
|
|
||||||
|
|
||||||
MidiChannel = msg[0]-144
|
|
||||||
MidiNote = msg[1]
|
|
||||||
MidiVel = msg[2]
|
|
||||||
print("NOTE ON :", MidiNote, 'velocity :', MidiVel, "Channel", MidiChannel)
|
|
||||||
#NoteOn(msg[1],msg[2],mididest)
|
|
||||||
wssend("/noteon "+str(msg[1])+" "+str(msg[2]))
|
|
||||||
|
|
||||||
'''
|
|
||||||
# Sampler mode : note <63 launch snare.wav / note > 62 kick.wav
|
|
||||||
if MidiNote < 63 and MidiVel >0:
|
|
||||||
|
|
||||||
if platform == 'darwin':
|
|
||||||
os.system("afplay snare.wav")
|
|
||||||
else:
|
|
||||||
os.system("aplay snare.wav")
|
|
||||||
|
|
||||||
|
|
||||||
if MidiNote > 62 and MidiVel >0:
|
|
||||||
|
|
||||||
if platform == 'darwin':
|
|
||||||
os.system("afplay kick.wav")
|
|
||||||
else:
|
|
||||||
os.system("aplay kick.wav")
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Note Off
|
|
||||||
if msg[0]==NOTE_OFF:
|
|
||||||
print("NOTE OFF :", MidiNote, 'velocity :', MidiVel, "Channel", MidiChannel)
|
|
||||||
#NoteOff(msg[1],msg[2], mididest)
|
|
||||||
wssend("/noteoff "+str(msg[1]))
|
|
||||||
|
|
||||||
|
|
||||||
# MMO-3 Midi CC message CHANNEL 1
|
|
||||||
if msg[0] == CONTROLLER_CHANGE:
|
|
||||||
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]))
|
|
||||||
|
|
||||||
|
|
||||||
# OCS-2 Midi CC message CHANNEL 2
|
|
||||||
if msg[0] == CONTROLLER_CHANGE+1:
|
|
||||||
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]))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# other midi message
|
|
||||||
if msg[0] != NOTE_OFF and msg[0] != NOTE_ON and msg[0] != CONTROLLER_CHANGE:
|
|
||||||
pass
|
|
||||||
'''
|
|
||||||
print("from", portname,"other midi message")
|
|
||||||
MidiMsg(msg[0],msg[1],msg[2],mididest)
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def NoteOn(note,color, mididest):
|
|
||||||
global MidInsNumber
|
|
||||||
|
|
||||||
|
|
||||||
for port in range(MidInsNumber):
|
|
||||||
|
|
||||||
# To mididest
|
|
||||||
if midiname[port].find(mididest) == 0:
|
|
||||||
midiport[port].send_message([NOTE_ON, note, color])
|
|
||||||
|
|
||||||
# To All
|
|
||||||
elif mididest == "all" and midiname[port].find(mididest) != 0 and midiname[port].find(BhorealMidiName) != 0 and midiname[port].find(LaunchMidiName) != 0:
|
|
||||||
midiport[port].send_message([NOTE_ON, note, color])
|
|
||||||
|
|
||||||
'''
|
|
||||||
# To Launchpad, if present.
|
|
||||||
elif mididest == "launchpad" and midiname[port].find(LaunchMidiName) == 0:
|
|
||||||
launchpad.PadNoteOn(note%64,color)
|
|
||||||
|
|
||||||
# To Bhoreal, if present.
|
|
||||||
elif mididest == "bhoreal" and midiname[port].find(BhorealMidiName) == 0:
|
|
||||||
gstt.BhorLeds[note%64]=color
|
|
||||||
midiport[port].send_message([NOTE_ON, note%64, color])
|
|
||||||
#bhorosc.sendosc("/bhoreal", [note%64 , 0])
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def NoteOff(note, mididest):
|
|
||||||
global MidInsNumber
|
|
||||||
|
|
||||||
|
|
||||||
for port in range(MidInsNumber):
|
|
||||||
|
|
||||||
# To mididest
|
|
||||||
if midiname[port].find(mididest) != -1:
|
|
||||||
midiport[port].send_message([NOTE_OFF, note, 0])
|
|
||||||
|
|
||||||
# To All
|
|
||||||
elif mididest == "all" and midiname[port].find(mididest) == -1 and midiname[port].find(BhorealMidiName) == -1 and midiname[port].find(LaunchMidiName) == -1:
|
|
||||||
midiport[port].send_message([NOTE_OFF, note, 0])
|
|
||||||
|
|
||||||
'''
|
|
||||||
# To Launchpad, if present.
|
|
||||||
elif mididest == "launchpad" and midiname[port].find(LaunchMidiName) == 0:
|
|
||||||
launchpad.PadNoteOff(note%64)
|
|
||||||
|
|
||||||
# To Bhoreal, if present.
|
|
||||||
elif mididest == "bhoreal" and midiname[port].find(BhorealMidiName) == 0:
|
|
||||||
midiport[port].send_message([NOTE_OFF, note%64, 0])
|
|
||||||
gstt.BhorLeds[note%64] = 0
|
|
||||||
#bhorosc.sendosc("/bhoreal", [note%64 , 0])
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Generic call back : new msg forwarded to queue
|
|
||||||
class AddQueue(object):
|
|
||||||
def __init__(self, portname, port):
|
|
||||||
self.portname = portname
|
|
||||||
self.port = port
|
|
||||||
#print "AddQueue", port)
|
|
||||||
self._wallclock = time.time()
|
|
||||||
|
|
||||||
def __call__(self, event, data=None):
|
|
||||||
message, deltatime = event
|
|
||||||
self._wallclock += deltatime
|
|
||||||
#print "inqueue : [%s] @%0.6f %r" % ( self.portname, self._wallclock, message))
|
|
||||||
message.append(deltatime)
|
|
||||||
midinputsqueue[self.port].put(message)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# MIDI OUT Handling
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class OutObject():
|
|
||||||
|
|
||||||
_instances = set()
|
|
||||||
counter = 0
|
|
||||||
|
|
||||||
def __init__(self, name, kind, port):
|
|
||||||
|
|
||||||
self.name = name
|
|
||||||
self.kind = kind
|
|
||||||
self.port = port
|
|
||||||
|
|
||||||
self._instances.add(weakref.ref(self))
|
|
||||||
OutObject.counter += 1
|
|
||||||
|
|
||||||
print("Adding OutDevice name", self.name, "kind", self.kind, "port", self.port)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def getinstances(cls):
|
|
||||||
dead = set()
|
|
||||||
for ref in cls._instances:
|
|
||||||
obj = ref()
|
|
||||||
if obj is not None:
|
|
||||||
yield obj
|
|
||||||
else:
|
|
||||||
dead.add(ref)
|
|
||||||
cls._instances -= dead
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
OutObject.counter -= 1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def OutConfig():
|
|
||||||
global midiout, MidInsNumber
|
|
||||||
|
|
||||||
#
|
|
||||||
if len(OutDevice) == 0:
|
|
||||||
print("")
|
|
||||||
print("MIDIout...")
|
|
||||||
print("List and attach to available devices on host with IN port :")
|
|
||||||
|
|
||||||
# Display list of available midi IN devices on the host, create and start an OUT instance to talk to each of these Midi IN devices
|
|
||||||
midiout = rtmidi.MidiOut()
|
|
||||||
available_ports = midiout.get_ports()
|
|
||||||
|
|
||||||
for port, name in enumerate(available_ports):
|
|
||||||
|
|
||||||
midiname[port]=name
|
|
||||||
midiport[port].open_port(port)
|
|
||||||
#print )
|
|
||||||
#print "New OutDevice [%i] %s" % (port, name))
|
|
||||||
|
|
||||||
OutDevice.append(OutObject(name, "generic", port))
|
|
||||||
|
|
||||||
#print "")
|
|
||||||
print(len(OutDevice), "Out devices")
|
|
||||||
#ListOutDevice()
|
|
||||||
MidInsNumber = len(OutDevice)+1
|
|
||||||
|
|
||||||
def ListOutDevice():
|
|
||||||
|
|
||||||
for item in OutObject.getinstances():
|
|
||||||
|
|
||||||
print(item.name)
|
|
||||||
|
|
||||||
def FindOutDevice(name):
|
|
||||||
|
|
||||||
port = -1
|
|
||||||
for item in OutObject.getinstances():
|
|
||||||
#print "searching", name, "in", item.name)
|
|
||||||
if name == item.name:
|
|
||||||
#print 'found port',item.port)
|
|
||||||
port = item.port
|
|
||||||
return port
|
|
||||||
|
|
||||||
|
|
||||||
def DelOutDevice(name):
|
|
||||||
|
|
||||||
Outnumber = Findest(name)
|
|
||||||
print('deleting OutDevice', name)
|
|
||||||
|
|
||||||
if Outnumber != -1:
|
|
||||||
print('found OutDevice', Outnumber)
|
|
||||||
delattr(OutObject, str(name))
|
|
||||||
print("OutDevice", Outnumber,"was removed")
|
|
||||||
else:
|
|
||||||
print("OutDevice was not found")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# MIDI IN Handling
|
|
||||||
# Create processing thread and queue for each device
|
|
||||||
#
|
|
||||||
|
|
||||||
class InObject():
|
|
||||||
|
|
||||||
_instances = set()
|
|
||||||
counter = 0
|
|
||||||
|
|
||||||
def __init__(self, name, kind, port, rtmidi):
|
|
||||||
|
|
||||||
self.name = name
|
|
||||||
self.kind = kind
|
|
||||||
self.port = port
|
|
||||||
self.rtmidi = rtmidi
|
|
||||||
self.queue = Queue()
|
|
||||||
|
|
||||||
self._instances.add(weakref.ref(self))
|
|
||||||
InObject.counter += 1
|
|
||||||
|
|
||||||
print("Adding InDevice name", self.name, "kind", self.kind, "port", self.port)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def getinstances(cls):
|
|
||||||
dead = set()
|
|
||||||
for ref in cls._instances:
|
|
||||||
obj = ref()
|
|
||||||
if obj is not None:
|
|
||||||
yield obj
|
|
||||||
else:
|
|
||||||
dead.add(ref)
|
|
||||||
cls._instances -= dead
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
InObject.counter -= 1
|
|
||||||
|
|
||||||
|
|
||||||
def InConfig():
|
|
||||||
|
|
||||||
print("")
|
|
||||||
print("MIDIin...")
|
|
||||||
|
|
||||||
# client mode
|
|
||||||
if debug > 0:
|
|
||||||
if clientmode == True:
|
|
||||||
print("midi3 in client mode")
|
|
||||||
else:
|
|
||||||
print("midi3 in server mode")
|
|
||||||
|
|
||||||
print("List and attach to available devices on host with OUT port :")
|
|
||||||
|
|
||||||
if platform == 'darwin':
|
|
||||||
mido.set_backend('mido.backends.rtmidi/MACOSX_CORE')
|
|
||||||
|
|
||||||
genericnumber = 0
|
|
||||||
|
|
||||||
for port, name in enumerate(mido.get_input_names()):
|
|
||||||
|
|
||||||
|
|
||||||
outport = FindOutDevice(name)
|
|
||||||
midinputsname[port]=name
|
|
||||||
|
|
||||||
#print "name",name, "Port",port, "Outport", outport)
|
|
||||||
# print "midinames", midiname)
|
|
||||||
|
|
||||||
#ListInDevice()
|
|
||||||
|
|
||||||
try:
|
|
||||||
#print name, name.find("RtMidi output"))
|
|
||||||
if name.find("RtMidi output") > -1:
|
|
||||||
print("No thread started for device", name)
|
|
||||||
else:
|
|
||||||
portin = object
|
|
||||||
port_name = ""
|
|
||||||
portin, port_name = open_midiinput(outport)
|
|
||||||
#midinputs.append(portin)
|
|
||||||
InDevice.append(InObject(name, "generic", outport, portin))
|
|
||||||
|
|
||||||
thread = Thread(target=MidinProcess, args=(midinputsqueue[port],port_name))
|
|
||||||
thread.setDaemon(True)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
#print "Thread launched for midi port", port, "portname", port_name, "Inname", midiname.index(port_name)
|
|
||||||
#print "counter", InObject.counter
|
|
||||||
#midinputs[port].set_callback(AddQueue(name),midinputsqueue[port])
|
|
||||||
#midinputs[port].set_callback(AddQueue(name))
|
|
||||||
#genericnumber += 1
|
|
||||||
InDevice[InObject.counter-1].rtmidi.set_callback(AddQueue(name,port))
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
#print "")
|
|
||||||
print(InObject.counter, "In devices")
|
|
||||||
#ListInDevice()
|
|
||||||
|
|
||||||
|
|
||||||
def ListInDevice():
|
|
||||||
|
|
||||||
#print "known IN devices :"
|
|
||||||
for item in InObject.getinstances():
|
|
||||||
|
|
||||||
print(item.name)
|
|
||||||
print("")
|
|
||||||
|
|
||||||
def FindInDevice(name):
|
|
||||||
|
|
||||||
port = -1
|
|
||||||
for item in InObject.getinstances():
|
|
||||||
#print "searching", name, "in", item.name)
|
|
||||||
if name in item.name:
|
|
||||||
#print 'found port',item.port)
|
|
||||||
port = item.port
|
|
||||||
return port
|
|
||||||
|
|
||||||
|
|
||||||
def DelInDevice(name):
|
|
||||||
|
|
||||||
Innumber = Findest(name)
|
|
||||||
print('deleting InDevice', name)
|
|
||||||
|
|
||||||
if Innumber != -1:
|
|
||||||
print('found InDevice', Innumber)
|
|
||||||
delattr(InObject, str(name))
|
|
||||||
print("InDevice", Innumber,"was removed")
|
|
||||||
else:
|
|
||||||
print("InDevice was not found")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def End():
|
|
||||||
global midiout
|
|
||||||
|
|
||||||
#midiin.close_port()
|
|
||||||
midiout.close_port()
|
|
||||||
|
|
||||||
#del virtual
|
|
||||||
if launchpad.Here != -1:
|
|
||||||
del launchpad.Here
|
|
||||||
if bhoreal.Here != -1:
|
|
||||||
del bhoreal.Here
|
|
||||||
if LPD8.Here != -1:
|
|
||||||
del LPD8.Here
|
|
||||||
|
|
||||||
# mididest : all or specifiname, won't be sent to launchpad or Bhoreal.
|
|
||||||
def MidiMsg(midimsg, mididest):
|
|
||||||
|
|
||||||
|
|
||||||
desterror = -1
|
|
||||||
|
|
||||||
print("midi3 got midimsg", midimsg, "for", mididest)
|
|
||||||
|
|
||||||
for port in range(len(OutDevice)):
|
|
||||||
# To mididest
|
|
||||||
if midiname[port].find(mididest) != -1:
|
|
||||||
if debug>0:
|
|
||||||
print("midi 3 sending to name", midiname[port], "port", port, ":", midimsg)
|
|
||||||
midiport[port].send_message(midimsg)
|
|
||||||
desterror = 0
|
|
||||||
|
|
||||||
if desterror == -1:
|
|
||||||
print("mididest",mididest, ": ** This midi destination doesn't exists **")
|
|
||||||
|
|
||||||
# send midi msg over ws.
|
|
||||||
#if clientmode == True:
|
|
||||||
# ws.send("/ocs2/cc/1 2")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def NoteOn(note, velocity, mididest):
|
|
||||||
global MidInsNumber
|
|
||||||
|
|
||||||
|
|
||||||
for port in range(MidInsNumber):
|
|
||||||
|
|
||||||
# To mididest
|
|
||||||
if midiname[port].find(mididest) == 0:
|
|
||||||
midiport[port].send_message([NOTE_ON, note, velocity])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def listdevice(number):
|
|
||||||
|
|
||||||
return midiname[number]
|
|
||||||
|
|
||||||
def check():
|
|
||||||
|
|
||||||
OutConfig()
|
|
||||||
InConfig()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
2
nozWS.py
2
nozWS.py
@ -424,7 +424,7 @@ try:
|
|||||||
on_close = on_close)
|
on_close = on_close)
|
||||||
|
|
||||||
midi3.ws = ws
|
midi3.ws = ws
|
||||||
midi3.wsmode = True
|
midi3.clientmode = True
|
||||||
|
|
||||||
print("Midi Configuration...")
|
print("Midi Configuration...")
|
||||||
print("Midi Destination", nozmidi)
|
print("Midi Destination", nozmidi)
|
||||||
|
@ -1,371 +0,0 @@
|
|||||||
# Author: Johan Hanssen Seferidis
|
|
||||||
# License: MIT
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import struct
|
|
||||||
from base64 import b64encode
|
|
||||||
from hashlib import sha1
|
|
||||||
import logging
|
|
||||||
from socket import error as SocketError
|
|
||||||
import errno
|
|
||||||
|
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
from SocketServer import ThreadingMixIn, TCPServer, StreamRequestHandler
|
|
||||||
else:
|
|
||||||
from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logging.basicConfig()
|
|
||||||
|
|
||||||
'''
|
|
||||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
|
||||||
0 1 2 3
|
|
||||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
||||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
|
||||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
|
||||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
|
||||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
|
||||||
| |1|2|3| |K| | |
|
|
||||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
|
||||||
| Extended payload length continued, if payload len == 127 |
|
|
||||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
|
||||||
| Payload Data continued ... |
|
|
||||||
+---------------------------------------------------------------+
|
|
||||||
'''
|
|
||||||
|
|
||||||
FIN = 0x80
|
|
||||||
OPCODE = 0x0f
|
|
||||||
MASKED = 0x80
|
|
||||||
PAYLOAD_LEN = 0x7f
|
|
||||||
PAYLOAD_LEN_EXT16 = 0x7e
|
|
||||||
PAYLOAD_LEN_EXT64 = 0x7f
|
|
||||||
|
|
||||||
OPCODE_CONTINUATION = 0x0
|
|
||||||
OPCODE_TEXT = 0x1
|
|
||||||
OPCODE_BINARY = 0x2
|
|
||||||
OPCODE_CLOSE_CONN = 0x8
|
|
||||||
OPCODE_PING = 0x9
|
|
||||||
OPCODE_PONG = 0xA
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------- API ---------------------------------
|
|
||||||
|
|
||||||
class API():
|
|
||||||
|
|
||||||
def run_forever(self):
|
|
||||||
try:
|
|
||||||
logger.info("Listening on port %d for clients.." % self.port)
|
|
||||||
self.serve_forever()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self.server_close()
|
|
||||||
logger.info("Server terminated.")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e), exc_info=True)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
def new_client(self, client, server):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def client_left(self, client, server):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def message_received(self, client, server, message):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_fn_new_client(self, fn):
|
|
||||||
self.new_client = fn
|
|
||||||
|
|
||||||
def set_fn_client_left(self, fn):
|
|
||||||
self.client_left = fn
|
|
||||||
|
|
||||||
def set_fn_message_received(self, fn):
|
|
||||||
self.message_received = fn
|
|
||||||
|
|
||||||
def send_message(self, client, msg):
|
|
||||||
self._unicast_(client, msg)
|
|
||||||
|
|
||||||
def send_message_to_all(self, msg):
|
|
||||||
self._multicast_(msg)
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------- Implementation -----------------------------
|
|
||||||
|
|
||||||
class WebsocketServer(ThreadingMixIn, TCPServer, API):
|
|
||||||
"""
|
|
||||||
A websocket server waiting for clients to connect.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
port(int): Port to bind to
|
|
||||||
host(str): Hostname or IP to listen for connections. By default 127.0.0.1
|
|
||||||
is being used. To accept connections from any client, you should use
|
|
||||||
0.0.0.0.
|
|
||||||
loglevel: Logging level from logging module to use for logging. By default
|
|
||||||
warnings and errors are being logged.
|
|
||||||
|
|
||||||
Properties:
|
|
||||||
clients(list): A list of connected clients. A client is a dictionary
|
|
||||||
like below.
|
|
||||||
{
|
|
||||||
'id' : id,
|
|
||||||
'handler' : handler,
|
|
||||||
'address' : (addr, port)
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
allow_reuse_address = True
|
|
||||||
daemon_threads = True # comment to keep threads alive until finished
|
|
||||||
|
|
||||||
clients = []
|
|
||||||
id_counter = 0
|
|
||||||
|
|
||||||
def __init__(self, port, host='127.0.0.1', loglevel=logging.WARNING):
|
|
||||||
logger.setLevel(loglevel)
|
|
||||||
TCPServer.__init__(self, (host, port), WebSocketHandler)
|
|
||||||
self.port = self.socket.getsockname()[1]
|
|
||||||
|
|
||||||
def _message_received_(self, handler, msg):
|
|
||||||
self.message_received(self.handler_to_client(handler), self, msg)
|
|
||||||
|
|
||||||
def _ping_received_(self, handler, msg):
|
|
||||||
handler.send_pong(msg)
|
|
||||||
|
|
||||||
def _pong_received_(self, handler, msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _new_client_(self, handler):
|
|
||||||
self.id_counter += 1
|
|
||||||
client = {
|
|
||||||
'id': self.id_counter,
|
|
||||||
'handler': handler,
|
|
||||||
'address': handler.client_address
|
|
||||||
}
|
|
||||||
self.clients.append(client)
|
|
||||||
self.new_client(client, self)
|
|
||||||
|
|
||||||
def _client_left_(self, handler):
|
|
||||||
client = self.handler_to_client(handler)
|
|
||||||
self.client_left(client, self)
|
|
||||||
if client in self.clients:
|
|
||||||
self.clients.remove(client)
|
|
||||||
|
|
||||||
def _unicast_(self, to_client, msg):
|
|
||||||
to_client['handler'].send_message(msg)
|
|
||||||
|
|
||||||
def _multicast_(self, msg):
|
|
||||||
for client in self.clients:
|
|
||||||
self._unicast_(client, msg)
|
|
||||||
|
|
||||||
def handler_to_client(self, handler):
|
|
||||||
for client in self.clients:
|
|
||||||
if client['handler'] == handler:
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketHandler(StreamRequestHandler):
|
|
||||||
|
|
||||||
def __init__(self, socket, addr, server):
|
|
||||||
self.server = server
|
|
||||||
StreamRequestHandler.__init__(self, socket, addr, server)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
StreamRequestHandler.setup(self)
|
|
||||||
self.keep_alive = True
|
|
||||||
self.handshake_done = False
|
|
||||||
self.valid_client = False
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
while self.keep_alive:
|
|
||||||
if not self.handshake_done:
|
|
||||||
self.handshake()
|
|
||||||
elif self.valid_client:
|
|
||||||
self.read_next_message()
|
|
||||||
|
|
||||||
def read_bytes(self, num):
|
|
||||||
# python3 gives ordinal of byte directly
|
|
||||||
bytes = self.rfile.read(num)
|
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
return map(ord, bytes)
|
|
||||||
else:
|
|
||||||
return bytes
|
|
||||||
|
|
||||||
def read_next_message(self):
|
|
||||||
try:
|
|
||||||
b1, b2 = self.read_bytes(2)
|
|
||||||
except SocketError as e: # to be replaced with ConnectionResetError for py3
|
|
||||||
if e.errno == errno.ECONNRESET:
|
|
||||||
logger.info("Client closed connection.")
|
|
||||||
print("Error: {}".format(e))
|
|
||||||
self.keep_alive = 0
|
|
||||||
return
|
|
||||||
b1, b2 = 0, 0
|
|
||||||
except ValueError as e:
|
|
||||||
b1, b2 = 0, 0
|
|
||||||
|
|
||||||
fin = b1 & FIN
|
|
||||||
opcode = b1 & OPCODE
|
|
||||||
masked = b2 & MASKED
|
|
||||||
payload_length = b2 & PAYLOAD_LEN
|
|
||||||
|
|
||||||
if opcode == OPCODE_CLOSE_CONN:
|
|
||||||
logger.info("Client asked to close connection.")
|
|
||||||
self.keep_alive = 0
|
|
||||||
return
|
|
||||||
if not masked:
|
|
||||||
logger.warn("Client must always be masked.")
|
|
||||||
self.keep_alive = 0
|
|
||||||
return
|
|
||||||
if opcode == OPCODE_CONTINUATION:
|
|
||||||
logger.warn("Continuation frames are not supported.")
|
|
||||||
return
|
|
||||||
elif opcode == OPCODE_BINARY:
|
|
||||||
logger.warn("Binary frames are not supported.")
|
|
||||||
return
|
|
||||||
elif opcode == OPCODE_TEXT:
|
|
||||||
opcode_handler = self.server._message_received_
|
|
||||||
elif opcode == OPCODE_PING:
|
|
||||||
opcode_handler = self.server._ping_received_
|
|
||||||
elif opcode == OPCODE_PONG:
|
|
||||||
opcode_handler = self.server._pong_received_
|
|
||||||
else:
|
|
||||||
logger.warn("Unknown opcode %#x." % opcode)
|
|
||||||
self.keep_alive = 0
|
|
||||||
return
|
|
||||||
|
|
||||||
if payload_length == 126:
|
|
||||||
payload_length = struct.unpack(">H", self.rfile.read(2))[0]
|
|
||||||
elif payload_length == 127:
|
|
||||||
payload_length = struct.unpack(">Q", self.rfile.read(8))[0]
|
|
||||||
|
|
||||||
masks = self.read_bytes(4)
|
|
||||||
message_bytes = bytearray()
|
|
||||||
for message_byte in self.read_bytes(payload_length):
|
|
||||||
message_byte ^= masks[len(message_bytes) % 4]
|
|
||||||
message_bytes.append(message_byte)
|
|
||||||
opcode_handler(self, message_bytes.decode('utf8'))
|
|
||||||
|
|
||||||
def send_message(self, message):
|
|
||||||
self.send_text(message)
|
|
||||||
|
|
||||||
def send_pong(self, message):
|
|
||||||
self.send_text(message, OPCODE_PONG)
|
|
||||||
|
|
||||||
def send_text(self, message, opcode=OPCODE_TEXT):
|
|
||||||
"""
|
|
||||||
Important: Fragmented(=continuation) messages are not supported since
|
|
||||||
their usage cases are limited - when we don't know the payload length.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Validate message
|
|
||||||
if isinstance(message, bytes):
|
|
||||||
message = try_decode_UTF8(message) # this is slower but ensures we have UTF-8
|
|
||||||
if not message:
|
|
||||||
logger.warning("Can\'t send message, message is not valid UTF-8")
|
|
||||||
return False
|
|
||||||
elif sys.version_info < (3,0) and (isinstance(message, str) or isinstance(message, unicode)):
|
|
||||||
pass
|
|
||||||
elif isinstance(message, str):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
logger.warning('Can\'t send message, message has to be a string or bytes. Given type is %s' % type(message))
|
|
||||||
return False
|
|
||||||
|
|
||||||
header = bytearray()
|
|
||||||
payload = encode_to_UTF8(message)
|
|
||||||
payload_length = len(payload)
|
|
||||||
|
|
||||||
# Normal payload
|
|
||||||
if payload_length <= 125:
|
|
||||||
header.append(FIN | opcode)
|
|
||||||
header.append(payload_length)
|
|
||||||
|
|
||||||
# Extended payload
|
|
||||||
elif payload_length >= 126 and payload_length <= 65535:
|
|
||||||
header.append(FIN | opcode)
|
|
||||||
header.append(PAYLOAD_LEN_EXT16)
|
|
||||||
header.extend(struct.pack(">H", payload_length))
|
|
||||||
|
|
||||||
# Huge extended payload
|
|
||||||
elif payload_length < 18446744073709551616:
|
|
||||||
header.append(FIN | opcode)
|
|
||||||
header.append(PAYLOAD_LEN_EXT64)
|
|
||||||
header.extend(struct.pack(">Q", payload_length))
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception("Message is too big. Consider breaking it into chunks.")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.request.send(header + payload)
|
|
||||||
|
|
||||||
def read_http_headers(self):
|
|
||||||
headers = {}
|
|
||||||
# first line should be HTTP GET
|
|
||||||
http_get = self.rfile.readline().decode().strip()
|
|
||||||
assert http_get.upper().startswith('GET')
|
|
||||||
# remaining should be headers
|
|
||||||
while True:
|
|
||||||
header = self.rfile.readline().decode().strip()
|
|
||||||
if not header:
|
|
||||||
break
|
|
||||||
head, value = header.split(':', 1)
|
|
||||||
headers[head.lower().strip()] = value.strip()
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def handshake(self):
|
|
||||||
headers = self.read_http_headers()
|
|
||||||
|
|
||||||
try:
|
|
||||||
assert headers['upgrade'].lower() == 'websocket'
|
|
||||||
except AssertionError:
|
|
||||||
self.keep_alive = False
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
key = headers['sec-websocket-key']
|
|
||||||
except KeyError:
|
|
||||||
logger.warning("Client tried to connect but was missing a key")
|
|
||||||
self.keep_alive = False
|
|
||||||
return
|
|
||||||
|
|
||||||
response = self.make_handshake_response(key)
|
|
||||||
self.handshake_done = self.request.send(response.encode())
|
|
||||||
self.valid_client = True
|
|
||||||
self.server._new_client_(self)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def make_handshake_response(cls, key):
|
|
||||||
return \
|
|
||||||
'HTTP/1.1 101 Switching Protocols\r\n'\
|
|
||||||
'Upgrade: websocket\r\n' \
|
|
||||||
'Connection: Upgrade\r\n' \
|
|
||||||
'Sec-WebSocket-Accept: %s\r\n' \
|
|
||||||
'\r\n' % cls.calculate_response_key(key)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def calculate_response_key(cls, key):
|
|
||||||
GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
|
||||||
hash = sha1(key.encode() + GUID.encode())
|
|
||||||
response_key = b64encode(hash.digest()).strip()
|
|
||||||
return response_key.decode('ASCII')
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
self.server._client_left_(self)
|
|
||||||
|
|
||||||
|
|
||||||
def encode_to_UTF8(data):
|
|
||||||
try:
|
|
||||||
return data.encode('UTF-8')
|
|
||||||
except UnicodeEncodeError as e:
|
|
||||||
logger.error("Could not encode data to UTF-8 -- %s" % e)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
raise(e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def try_decode_UTF8(data):
|
|
||||||
try:
|
|
||||||
return data.decode('utf-8')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
raise(e)
|
|
Loading…
Reference in New Issue
Block a user