not finished commit

This commit is contained in:
Sam Neurohack 2021-01-06 03:32:39 +01:00
commit a5d76ba126
18 changed files with 4517 additions and 0 deletions

2874
OSC3.py Executable file

File diff suppressed because it is too large Load Diff

4
README.md Normal file
View File

@ -0,0 +1,4 @@
midiOSCesp
v0.1
More soon.

10
esp/boot.py Executable file
View File

@ -0,0 +1,10 @@
# This file is executed on every boot (including wake-boot from deepsleep)
#import esp
#esp.osdebug(None)
import uos, machine
#uos.dupterm(None, 1) # disable REPL on UART(0)
import gc
#import webrepl
#webrepl.start()
gc.collect()

62
esp/leds.py Normal file
View File

@ -0,0 +1,62 @@
import time
import machine, neopixel
lednumber = 8
np = neopixel.NeoPixel(machine.Pin(14), lednumber)
def demo(np):
n = np.n
# cycle
for i in range(4 * n):
for j in range(n):
np[j] = (0, 0, 0)
np[i % n] = (255, 255, 255)
np.write()
time.sleep_ms(25)
# bounce
for i in range(4 * n):
for j in range(n):
np[j] = (0, 0, 128)
if (i // n) % 2 == 0:
np[i % n] = (0, 0, 0)
else:
np[n - 1 - (i % n)] = (0, 0, 0)
np.write()
time.sleep_ms(60)
# fade in/out
for i in range(0, 4 * 256, 8):
for j in range(n):
if (i // 256) % 2 == 0:
val = i & 0xff
else:
val = 255 - (i & 0xff)
np[j] = (val, 0, 0)
np.write()
# clear
for i in range(n):
np[i] = (0, 0, 0)
np.write()
def npcolor(r, g, b):
for i in range(lednumber):
np[i] = (r, g, b)
np.write()
def npcycle():
for i in range(2 * lednumber):
for j in range(lednumber):
np[j] = (0, 0, 0)
np[i % lednumber] = (126, 20, 126)
np.write()
time.sleep_ms(150)
npcolor(0,0,0)
npcycle()
npcolor(0,0,0)
#demo(np)

184
esp/lserver.py Normal file
View File

@ -0,0 +1,184 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, time
import socket
from uosc.server import run_server, split_oscstr, parse_message, parse_bundle
from uosc.client import send
from machine import Pin
import mynetconf as netconf
import machine, neopixel
print('Loading OSC server module...')
MAX_DGRAM_SIZE = 1472
OSCport = 9001
ledpin = Pin(2, Pin.OUT)
stripin = Pin(14, Pin.OUT)
ESPledstate = False
n = 8
np = neopixel.NeoPixel(stripin, n)
def ESPledon():
global ESPledstate
ledpin.value(0) # LED ON
ESPledstate = True
def ESPledoff():
global ESPledstate
ledpin.value(1) # LED OFF
ESPledstate = False
ESPledoff()
def npcycle():
for i in range(2 * n):
for j in range(n):
np[j] = (0, 0, 0)
np[i % n] = (126, 20, 126)
np.write()
time.sleep_ms(150)
'''
def npcycle2(r, g, b, wait):
print(r,g,b,wait)
for i in range(2 * n):
for j in range(n):
np[j] = (0, 0, 0)
np[int(i % n)] = (r, g, b)
#print(np)
np.write()
print()
for count in range(n):
print(i%n)
time.sleep(wait)
'''
def npcolor(r, g, b):
for i in range(n):
np[i] = (r, g, b)
np.write()
def npbounce():
for i in range(4 * n):
for j in range(n):
np[j] = (0, 0, 128)
if (i // n) % 2 == 0:
np[i % n] = (0, 0, 0)
else:
np[n - 1 - (i % n)] = (0, 0, 0)
np.write()
time.sleep_ms(60)
def OSCHandler(t, msg):
print()
print("OSCHandler")
print("OSC address:", msg[0]) # /on
print("Type tags:", msg[1]) # 'i'
print("Arguments:", msg[2]) # (1,)
print()
# /noteon midichannel note velocity
if msg[0] == "/noteon":
print("NOTEON channel", msg[2][0], "note",msg[2][1], "velocity", msg[2][2])
np[int(msg[2][1]%n)] = (255, 0, 153)
np.write()
# /noteoff midichannel note
if msg[0] =='/noteoff':
print("NOTEOFF channel", msg[2][0], "note",msg[2][1])
np[int(msg[2][1]%n)] = (0, 0, 0)
np.write()
def handle_rawosc(data, src, strict=False):
try:
head, _ = split_oscstr(data, 0)
if __debug__: print("head", head)
if head.startswith('/'):
messages = [(-1, parse_message(data, strict))]
elif head == '#bundle':
messages = parse_bundle(data, strict)
except Exception as exc:
if __debug__:
pass
return
try:
for timetag, (oscaddr, tags, args) in messages:
if __debug__:
print("OSC address: %s" % oscaddr)
print("OSC type tags: %r" % tags)
print("OSC arguments: %r" % (args,))
print("Dispatching", timetag, (oscaddr, tags, args, src))
OSCHandler(timetag, (oscaddr, tags, args, src))
except Exception as exc:
print("Exception in OSC handler: %s", exc)
def run_server(saddr, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if __debug__: print("Created OSC UDP server socket.")
sock.bind((saddr, port))
print("Listening for OSC messages on %s:%i.", saddr, port)
try:
while True:
data, caddr = sock.recvfrom(MAX_DGRAM_SIZE)
if __debug__: print("Server received", len(data), "bytes from",caddr)
handle_rawosc(data, caddr)
print()
finally:
sock.close()
print("Bye!")
def main(ip):
#import time
ESPledon()
print("Ledserver main IP",ip)
time.sleep(1)
npcolor(0,0,0)
#npcycle(255, 0, 0, 2)
#npbounce()
npcycle()
npcolor(0,0,0)
start = time.time()
try:
run_server("0.0.0.0", int(netconf.OSCin))
except KeyboardInterrupt:
pass
ESPledoff()
if __name__ == '__main__':
main(0)

12
esp/main.py Executable file
View File

@ -0,0 +1,12 @@
'''
midiOSCesp v0.1b ESP8266 side
'''
from wlan import do_connect
import mynetconf as netconf
print('connection')
ip = do_connect(netconf.ssid, netconf.password)
print("in main IP :",ip)
from lserver import main
main(ip)

8
esp/netconf.py Normal file
View File

@ -0,0 +1,8 @@
ssid = 'ssidname'
password = 'password'
ip = '192.168.2.142'
mask = '255.255.255.0'
gateway = '192.168.2.254'
dns = '1.1.1.1'
OSCin = 9001
OSCout = 9002

11
esp/uosc/__init__.py Executable file
View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
"""A minimal OSC client and server library for MicroPython.
To use it with the unix port of MicroPython, install the required modules from
the micropython-lib:
$ for mod in argparse ffilib logging socket struct; do
micropython -m upip install micropython-$mod
done
"""

34
esp/uosc/__main__.py Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env micropython
# -*- coding: utf-8 -*-
import argparse
import logging
import sys
from uosc.server import run_server
DEFAULT_ADDRESS = '0.0.0.0'
DEFAULT_PORT = 9001
def main(args=None):
ap = argparse.ArgumentParser()
ap.add_argument('-v', '--verbose', action="store_true",
help="Enable debug logging")
ap.add_argument('-a', '--address', default=DEFAULT_ADDRESS,
help="OSC server address (default: %s)" % DEFAULT_ADDRESS)
ap.add_argument('-p', '--port', type=int, default=DEFAULT_PORT,
help="OSC server port (default: %s)" % DEFAULT_PORT)
args = ap.parse_args(args if args is not None else sys.argv[1:])
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
try:
run_server(args.address, int(args.port))
except KeyboardInterrupt:
pass
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]) or 0)

204
esp/uosc/client.py Executable file
View File

@ -0,0 +1,204 @@
# -*- coding: utf-8 -*-
#
# uosc/client.py
#
"""Simple OSC client."""
import socket
try:
from ustruct import pack
except ImportError:
from struct import pack
from uosc.common import Bundle, to_frac
if isinstance('', bytes):
have_bytes = False
unicodetype = unicode # noqa
else:
have_bytes = True
unicodetype = str
TYPE_MAP = {
int: 'i',
float: 'f',
bytes: 'b',
bytearray: 'b',
unicodetype: 's',
True: 'T',
False: 'F',
None: 'N',
}
def pack_addr(addr):
"""Pack a (host, port) tuple into the format expected by socket methods."""
if isinstance(addr, (bytes, bytearray)):
return addr
if len(addr) != 2:
raise NotImplementedError("Only IPv4/v6 supported")
addrinfo = socket.getaddrinfo(addr[0], addr[1])
return addrinfo[0][4]
def pack_timetag(t):
"""Pack an OSC timetag into 64-bit binary blob."""
return pack('>II', *to_frac(t))
def pack_string(s, encoding='utf-8'):
"""Pack a string into a binary OSC string."""
if isinstance(s, unicodetype):
s = s.encode(encoding)
assert all((i if have_bytes else ord(i)) < 128 for i in s), (
"OSC strings may only contain ASCII chars.")
slen = len(s)
return s + b'\0' * (((slen + 4) & ~0x03) - slen)
def pack_blob(b, encoding='utf-8'):
"""Pack a bytes, bytearray or tuple/list of ints into a binary OSC blob."""
if isinstance(b, (tuple, list)):
b = bytearray(b)
elif isinstance(b, unicodetype):
b = b.encode(encoding)
blen = len(b)
b = pack('>I', blen) + bytes(b)
return b + b'\0' * (((blen + 3) & ~0x03) - blen)
def pack_bundle(bundle):
"""Return bundle data packed into a binary string."""
data = []
for msg in bundle:
if isinstance(msg, Bundle):
msg = pack_bundle(msg)
elif isinstance(msg, tuple):
msg = create_message(*msg)
data.append(pack('>I', len(msg)) + msg)
return b'#bundle\0' + pack_timetag(bundle.timetag) + b''.join(data)
def pack_midi(val):
assert not isinstance(val, unicodetype), (
"Value with tag 'm' or 'r' must be bytes, bytearray or a sequence of "
"ints, not %s" % unicodetype)
if not have_bytes and isinstance(val, str):
val = (ord(c) for c in val)
return pack('BBBB', *tuple(val))
def create_message(address, *args):
"""Create an OSC message with given address pattern and arguments.
The OSC types are either inferred from the Python types of the arguments,
or you can pass arguments as 2-item tuples with the OSC typetag as the
first item and the argument value as the second. Python objects are mapped
to OSC typetags as follows:
* ``int``: i
* ``float``: f
* ``str``: s
* ``bytes`` / ``bytearray``: b
* ``None``: N
* ``True``: T
* ``False``: F
If you want to encode a Python object to another OSC type, you have to pass
a ``(typetag, data)`` tuple, where ``data`` must be of the appropriate type
according to the following table:
* c: ``str`` of length 1
* h: ``int``
* d: ``float``
* I: ``None`` (unused)
* m: ``tuple / list`` of 4 ``int``s or ``bytes / bytearray`` of length 4
* r: same as 'm'
* t: OSC timetag as as ``int / float`` seconds since the NTP epoch
* S: ``str``
"""
assert address.startswith('/'), "Address pattern must start with a slash."
data = []
types = [',']
for arg in args:
type_ = type(arg)
if isinstance(arg, tuple):
typetag, arg = arg
else:
typetag = TYPE_MAP.get(type_) or TYPE_MAP.get(arg)
if typetag in 'ifd':
data.append(pack('>' + typetag, arg))
elif typetag in 'sS':
data.append(pack_string(arg))
elif typetag == 'b':
data.append(pack_blob(arg))
elif typetag in 'rm':
data.append(pack_midi(arg))
elif typetag == 'c':
data.append(pack('>I', ord(arg)))
elif typetag == 'h':
data.append(pack('>q', arg))
elif typetag == 't':
data.append(pack_timetag(arg))
elif typetag not in 'IFNT':
raise TypeError("Argument of type '%s' not supported." % type_)
types.append(typetag)
return pack_string(address) + pack_string(''.join(types)) + b''.join(data)
class Client:
def __init__(self, host, port=None):
if port is None:
if isinstance(host, (list, tuple)):
host, port = host
else:
port = host
host = '127.0.0.1'
self.dest = pack_addr((host, port))
self.sock = None
def send(self, msg, *args, **kw):
dest = pack_addr(kw.get('dest', self.dest))
if not self.sock:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if isinstance(msg, Bundle):
msg = pack_bundle(msg)
elif args or isinstance(msg, unicodetype):
msg = create_message(msg, *args)
self.sock.sendto(msg, dest)
def close(self):
if self.sock:
self.sock.close()
self.sock = None
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def send(dest, address, *args):
with Client(dest) as client:
client.send(address, *args)

62
esp/uosc/common.py Executable file
View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
#
# uosc/common.py
#
"""OSC message parsing and building functions."""
try:
from time import time
except ImportError:
from utime import time
# UNIX_EPOCH = datetime.date(*time.gmtime(0)[0:3])
# NTP_EPOCH = datetime.date(1900, 1, 1)
# NTP_DELTA = (UNIX_EPOCH - NTP_EPOCH).days * 24 * 3600
NTP_DELTA = 2208988800
ISIZE = 4294967296 # 2**32
class Impulse:
pass
class Bundle:
"""Container for an OSC bundle."""
def __init__(self, *items):
"""Create bundle from given OSC timetag and messages/sub-bundles.
An OSC timetag can be given as the first positional argument, and must
be an int or float of seconds since the NTP epoch (1990-01-01 00:00).
It defaults to the current time.
Pass in messages or bundles via positional arguments as binary data
(bytes as returned by ``create_message`` resp. ``Bundle.pack``) or as
``Bundle`` instances or (address, *args) tuples.
"""
if items and isinstance(items[0], (int, float)):
self.timetag = items[0]
items = items[1:]
else:
self.timetag = time() + NTP_DELTA
self._items = list(items)
def add(self, *items):
self._items.extend(list(items))
def __iter__(self):
return iter(self._items)
def to_frac(t):
"""Return seconds and fractional part of NTP timestamp as 2-item tuple."""
sec = int(t)
return sec, int(abs(t - sec) * ISIZE)
def to_time(sec, frac):
"""Return NTP timestamp from integer seconds and fractional part."""
return sec + float(frac) / ISIZE

163
esp/uosc/server.py Executable file
View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
#
# uosc/server.py
#
"""A minimal OSC UDP server."""
import socket
try:
from ustruct import unpack
except ImportError:
from struct import unpack
from uosc.common import Impulse, to_time
#if __debug__:
# from uosc.socketutil import get_hostport
MAX_DGRAM_SIZE = 1472
def split_oscstr(msg, offset):
end = msg.find(b'\0', offset)
return msg[offset:end].decode('utf-8'), (end + 4) & ~0x03
def split_oscblob(msg, offset):
start = offset + 4
size = unpack('>I', msg[offset:start])[0]
return msg[start:start + size], (start + size + 4) & ~0x03
def parse_timetag(msg, offset):
"""Parse an OSC timetag from msg at offset."""
return to_time(unpack('>II', msg[offset:offset + 4]))
def parse_message(msg, strict=False):
args = []
addr, ofs = split_oscstr(msg, 0)
if not addr.startswith('/'):
raise ValueError("OSC address pattern must start with a slash.")
# type tag string must start with comma (ASCII 44)
if ofs < len(msg) and msg[ofs:ofs + 1] == b',':
tags, ofs = split_oscstr(msg, ofs)
tags = tags[1:]
else:
errmsg = "Missing/invalid OSC type tag string."
if strict:
raise ValueError(errmsg)
else:
print(errmsg + ' Ignoring arguments.')
tags = ''
for typetag in tags:
size = 0
if typetag in 'ifd':
size = 8 if typetag == 'd' else 4
args.append(unpack('>' + typetag, msg[ofs:ofs + size])[0])
elif typetag in 'sS':
s, ofs = split_oscstr(msg, ofs)
args.append(s)
elif typetag == 'b':
s, ofs = split_oscblob(msg, ofs)
args.append(s)
elif typetag in 'rm':
size = 4
args.append(unpack('BBBB', msg[ofs:ofs + size]))
elif typetag == 'c':
size = 4
args.append(chr(unpack('>I', msg[ofs:ofs + size])[0]))
elif typetag == 'h':
size = 8
args.append(unpack('>q', msg[ofs:ofs + size])[0])
elif typetag == 't':
size = 8
args.append(parse_timetag(msg, ofs))
elif typetag in 'TFNI':
args.append({'T': True, 'F': False, 'I': Impulse}.get(typetag))
else:
raise ValueError("Type tag '%s' not supported." % typetag)
ofs += size
return (addr, tags, tuple(args))
def parse_bundle(bundle, strict=False):
"""Parse a binary OSC bundle.
Returns a generator which walks over all contained messages and bundles
recursively, depth-first. Each item yielded is a (timetag, message) tuple.
"""
if not bundle.startswith(b'#bundle\0'):
raise TypeError("Bundle must start with b'#bundle\\0'.")
ofs = 16
timetag = to_time(*unpack('>II', bundle[8:ofs]))
while True:
if ofs >= len(bundle):
break
size = unpack('>I', bundle[ofs:ofs + 4])[0]
element = bundle[ofs + 4:ofs + 4 + size]
ofs += size + 4
if element.startswith(b'#bundle'):
for el in parse_bundle(element):
yield el
else:
yield timetag, parse_message(element, strict)
def handle_osc(data, src, dispatch=None, strict=False):
try:
head, _ = split_oscstr(data, 0)
if head.startswith('/'):
messages = [(-1, parse_message(data, strict))]
elif head == '#bundle':
messages = parse_bundle(data, strict)
except Exception as exc:
pass
#if __debug__:
# print(data)
return
try:
for timetag, (oscaddr, tags, args) in messages:
if __debug__:
print("OSC address: %s" % oscaddr)
print("OSC type tags: %r" % tags)
print("OSC arguments: %r" % (args,))
if dispatch:
dispatch(timetag, (oscaddr, tags, args, src))
except Exception as exc:
print("Exception in OSC handler: %s", exc)
def run_server(saddr, port, handler=handle_osc):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if __debug__: print("Created OSC UDP server socket.")
sock.bind((saddr, port))
print("Listening for OSC messages on %s:%i.", saddr, port)
try:
while True:
data, caddr = sock.recvfrom(MAX_DGRAM_SIZE)
handler(data, caddr)
finally:
sock.close()
print("Bye!")

35
esp/uosc/socketutil.py Executable file
View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
#
# uosc/socketutil.py
#
import socket
INET_ADDRSTRLEN = 16
INET6_ADDRSTRLEN = 46
inet_ntoa = getattr(socket, 'inet_ntoa', None)
if not inet_ntoa:
import ffilib
inet_ntoa = ffilib.libc().func("s", "inet_ntoa", "p")
inet_ntop = getattr(socket, 'inet_ntop', None)
if not inet_ntop:
import ffilib
_inet_ntop = ffilib.libc().func("s", "inet_ntop", "iPpi")
def inet_ntop(af, addr):
buf = bytearray(INET_ADDRSTRLEN if af == socket.AF_INET else
INET6_ADDRSTRLEN)
res = _inet_ntop(af, addr, buf, INET_ADDRSTRLEN)
return res
def get_hostport(addr):
if isinstance(addr, tuple):
return addr
af, addr, port = socket.sockaddr(addr)
return inet_ntop(af, addr), port

86
esp/uosc/threadedclient.py Executable file
View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""OSC client running in a separate thread.
Communicates with the main thread via a queue. Provides the same API as the
non-threaded client, with a few threading-related extensions:
from uosc.threadedclient import ThreadedClient
# start=True starts the thread immediately
osc = ThreadedClient('192.168.0.42', 9001, start=True)
# if the OSC message can not placed in the queue within timeout
# raises a queue.Full error
osc.send('/pi', 3.14159, timeout=1.0)
# Stops and joins the thread and closes the client socket
osc.close()
"""
import logging
import threading
try:
import queue
except ImportError:
import Queue as queue
from uosc.client import Client
log = logging.getLogger(__name__)
class ThreadedClient(threading.Thread):
def __init__(self, host, port=None, start=False, timeout=3.0):
super(ThreadedClient, self).__init__()
self.host = host
self.port = port
self.timeout = timeout
self._q = queue.Queue()
if start:
self.start()
def run(self, *args, **kw):
self.client = Client((self.host, self.port))
while True:
msg = self._q.get()
if msg is None:
break
addr, msg = msg
log.debug("Sending OSC msg %s, %r", addr, msg)
self.client.send(addr, *msg)
self.client.close()
def send(self, addr, *args, **kw):
self._q.put((addr, args), timeout=kw.get('timeout', self.timeout))
def close(self, **kw):
timeout = kw.get('timeout', self.timeout)
log.debug("Emptying send queue...")
while True:
try:
self._q.get_nowait()
except queue.Empty:
break
if self.is_alive():
log.debug("Signalling OSC client thread to exit...")
self._q.put(None, timeout=timeout)
log.debug("Joining OSC client thread...")
self.join(timeout)
if self.is_alive():
log.warning("OSC client thread still alive after join().")
def __enter__(self):
return self
def __exit__(self, *args):
self.close()

29
esp/wlan.py Executable file
View File

@ -0,0 +1,29 @@
def do_connect(ssid, password, tries=5):
from network import WLAN, STA_IF
from time import sleep
import netconf
print('Loading Wifi module...')
sta_if = WLAN(STA_IF)
if not sta_if.isconnected():
sta_if.active(True)
sta_if.connect(ssid, password)
for i in range(tries):
print('Connecting to network (try {})...'.format(i+1))
if sta_if.isconnected():
sta_if.disconnect()
sta_if.status()
sta_if.ifconfig((netconf.ip, netconf.mask, netconf.gateway, netconf.dns))
sta_if.connect()
curconf = sta_if.ifconfig()
print('network config:', curconf)
return curconf[0]
sleep(1)
else:
print("Failed to connect in {} seconds.".format(tries))
if __name__ == '__main__':
do_connect(netconf.ssid, netconf.password)

43
log.py Normal file
View File

@ -0,0 +1,43 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
'''
Log in color from
https://stackoverflow.com/questions/287871/how-to-print-colored-text-in-terminal-in-python
usage :
import log
log.info("Hello World")
log.err("System Error")
'''
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = "\033[1m"
def disable():
HEADER = ''
OKBLUE = ''
OKGREEN = ''
WARNING = ''
FAIL = ''
ENDC = ''
def infog( msg):
print(OKGREEN + msg + ENDC)
def info( msg):
print(OKBLUE + msg + ENDC)
def warn( msg):
print(WARNING + msg + ENDC)
def err( msg):
print(FAIL + msg + ENDC)

104
main.py Executable file
View File

@ -0,0 +1,104 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# -*- mode: Python -*-
'''
midiOSCesp v0.1b Computer side
Hook to all midi ports, forward in OSC these midi events :
/noteon MidiChannel, note, velocity
/noteoff MidiChannel, note
/rawcc MidiChannel, CCnumber, CCvalue
/clock
/start
/stop
'''
print("")
print("")
print("midiOSCesp Server")
print("v0.1b")
import sys
import traceback
import os
import time
from rtmidi.midiconstants import (CHANNEL_PRESSURE, CONTROLLER_CHANGE, NOTE_ON, NOTE_OFF,
PITCH_BEND, POLY_PRESSURE, PROGRAM_CHANGE)
#sys.path.append('libs/')
import midix
#import socket
import types, json
import argparse
import _thread, time
print ("")
print ("Arguments parsing if needed...")
argsparser = argparse.ArgumentParser(description="MidiOSCesp v0.1b commands help mode")
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()
# Debug/verbose mode ?
if args.verbose == False:
print("Debug mode disabled")
debug = 0
else:
print("Debug mode enabled")
debug = 1
#
# Midi part
#
print("Midi Configuration...")
# print("Midi Destination", nozmidi)
midix.check()
def GetTime():
return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
# /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)
midix.MidiMsg([CONTROLLER_CHANGE+midichannel-1, ccnumber, value], mididest)
#
# Running...
#
try:
print(GetTime(),"midi2osc running forever...")
while True:
time.sleep(1)
except Exception:
traceback.print_exc()
# Gently stop on CTRL C
print("Fin de midiOSCesp.")

592
midix.py Normal file
View File

@ -0,0 +1,592 @@
#!/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
Midi conversions from https://github.com/craffel/pretty-midi
"""
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, TIMING_CLOCK, SONG_CONTINUE, SONG_START, SONG_STOP)
import mido
from mido import MidiFile
import traceback
import weakref
import sys
from sys import platform
import os
import re
from collections import deque
import log
from queue import Queue
from OSC3 import OSCServer, OSCClient, OSCMessage
print("")
midiname = ["Name"] * 16
midiport = [rtmidi.MidiOut() for i in range(16) ]
OutDevice = []
InDevice = []
midisync = True
# max 16 midi port array
midinputsname = ["Name"] * 16
midinputsqueue = [Queue() for i in range(16) ]
midinputs = []
debug = 1
#Mser = False
MidInsNumber = 0
serverIP = "192.168.2.142"
OSCPORT = 9001
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
}
def SendLeds(oscaddress,oscargs=''):
oscmsg = OSCMessage()
oscmsg.setAddress(oscaddress)
oscmsg.append(oscargs)
osclientlj = OSCClient()
osclientlj.connect((serverIP, OSCPORT))
#print("MIDI Aurora sending UI :", oscmsg, "to",TouchOSCIP,":",TouchOSCPort)
try:
osclientlj.sendto(oscmsg, (serverIP, OSCPORT))
oscmsg.clearData()
except:
log.err('Connection to Aurora UI refused : died ?')
pass
#time.sleep(0.001
def GetTime():
return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())
# /cc cc number value
def cc(midichannel, ccnumber, value, mididest):
if debug>0:
print("Midix sending Midi channel", midichannel, "cc", ccnumber, "value", value, "to", mididest)
MidiMsg([CONTROLLER_CHANGE+midichannel-1, ccnumber, value], mididest)
#
# MIDI Startup and handling
#
mqueue = Queue()
inqueue = Queue()
bpm = 0
running = True
samples = deque()
last_clock = None
#
# Events from Generic MIDI Handling
#
def MidinProcess(inqueue, portname):
inqueue_get = inqueue.get
bpm = 0
samples = deque()
last_clock = None
while True:
time.sleep(0.001)
msg = inqueue_get()
#print("")
#print("Generic from", portname,"msg : ", msg)
# NOTE ON message on all midi channels
if NOTE_ON -1 < msg[0] < 160 and msg[2] !=0 :
MidiChannel = msg[0]-144
MidiNote = msg[1]
MidiVel = msg[2]
print()
print("NOTE ON :", MidiNote, 'velocity :', MidiVel, "Channel", MidiChannel)
#print("Midi in process send /aurora/noteon "+str(msg[1])+" "+str(msg[2]))
SendLeds("/noteon",[MidiChannel, msg[1], 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 or Note with 0 velocity on all midi channels
if NOTE_OFF -1 < msg[0] < 145 or (NOTE_OFF -1 < msg[0] < 160 and msg[2] == 0):
print("NOTE_off :",NOTE_OFF)
if msg[0] > 143:
MidiChannel = msg[0]-144
else:
MidiChannel = msg[0]-128
#print("NOTE OFF :", MidiNote, "Channel", MidiChannel)
#print("Midi in process send /aurora/noteoff "+str(msg[1]))
SendLeds("/noteoff",[MidiChannel, msg[1]])
# # CC on all Midi Channels
if CONTROLLER_CHANGE -1 < msg[0] < 192:
MidiChannel = msg[0]-175
print()
#print("channel", MidiChannel, "CC :", msg[1], msg[2])
print("Midi in process send /aurora/rawcc "+str(msg[0]-175-1)+" "+str(msg[1])+" "+str(msg[2]))
SendLeds("/rawcc",[msg[0]-175-1, msg[1], msg[2]])
'''
# MMO-3 Midi CC message CHANNEL 1
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")
WScom.send("/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")
WScom.send("/ocs2/cc/"+str(msg[1])+" "+str(msg[2]))
'''
if msg[0] == TIMING_CLOCK:
now = time.time()
if last_clock is not None:
samples.append(now - last_clock)
last_clock = now
if len(samples) > 24:
samples.popleft()
if len(samples) >= 2:
#bpm = 2.5 / (sum(samples) / len(samples))
#print("%.2f bpm" % bpm)
bpm = round(2.5 / (sum(samples) / len(samples))) # Against BPM lot very tiny change :
sync = True
# print("MIDI BPM", bpm)
#print("Midi clock : BPM", bpm)
SendLeds("/clock",[])
# SendAU("/aurora/bpm",[bpm])
if msg[0] in (SONG_CONTINUE, SONG_START):
running = True
#print("START/CONTINUE received.")
#print("Midi in process send /aurora/start")
SendLeds("/start",[])
if msg[0] == SONG_STOP:
running = False
#print("STOP received.")
#print("Midi in process send /aurora/stop")
SendLeds("/stop",[])
'''
# 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):
#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 debug >0:
print("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+midichannel, note, color])
# To All
elif mididest == "all" and midiname[port].find(mididest) != 0:
midiport[port].send_message([NOTE_ON+midichannel, note, color])
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:
midiport[port].send_message([NOTE_OFF, note, 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("")
log.info("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("")
log.info("MIDIin...")
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)
if midisync == True:
portin.ignore_types(timing=False)
#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()
# mididest : all or specifiname, won't be sent to launchpad or Bhoreal.
def MidiMsg(midimsg, mididest):
desterror = -1
print("jamidi3 got midimsg", midimsg, "for", mididest)
for port in range(len(OutDevice)):
# To mididest
if midiname[port].find(mididest) != -1:
if debug>0:
print("jamidi3 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 **")
def listdevice(number):
return midiname[number]
def check():
OutConfig()
InConfig()