-
Notifications
You must be signed in to change notification settings - Fork 0
/
ui24r-paramrecorder.py
executable file
·222 lines (173 loc) · 6.35 KB
/
ui24r-paramrecorder.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/bin/env python3
# pip install websocket-client
# pip install websockets
import websocket
import threading
import logging
import re
import os
from pathlib import Path
try:
import thread
except ImportError:
import _thread as thread
import time
from datetime import datetime
import asyncio
import websockets
import argparse
import sys
dataContainer = {}
armed = False
recFile = None
recStartTime = 0
recStateRemote = 0
sessionName = ""
def main():
mixerIp = None
socketIp = None
socketPort = None
parser = argparse.ArgumentParser(description='param recorder for Soundcraft Ui24r mixing console')
parser.add_argument('mixerip', type=str, help='IP or domain name of the Ui24r')
parser.add_argument('--socketIp', type=str, help='IP or domain of optional created websocket')
parser.add_argument('--socketPort', type=int, help='port of optional created websocket')
args = parser.parse_args()
tasks = []
if args.socketIp != None and args.socketPort != None:
tasks.append(
createSocketAndServe(args.socketIp, args.socketPort),
)
elif args.socketIp != None or args.socketPort != None:
parser.print_help()
sys.exit()
tasks.append(createMixerWebSocket(args.mixerip))
logging.basicConfig(level=logging.INFO)
asyncio.get_event_loop().run_until_complete(asyncio.wait(tasks))
asyncio.get_event_loop().run_forever()
async def recordingStatus(websocket, path):
while True:
await websocket.send('{armed: %s}' % str(armed).lower())
await asyncio.sleep(1)
async def createSocketAndServe(socketIp, socketPort):
logging.info("creating server. listening on port %s" % str(socketPort))
ws_server = await websockets.serve(recordingStatus, socketIp, socketPort)
def onMixerSocketMessage(ws, message):
#print(message)
# we often get multiline messages. process each line separately
for line in message.split('\n'):
if line == "2::":
# skip useless messages
continue
if line[:4] == "3:::":
# remove unneeded prefix of each websocket message in case it exists
line = line[4:]
if line[:4] == "RTA^" or line[:4] == "VU2^" or line[:4] == "VUA^":
# skip all those realtime data thats only needed for visualizing audio
continue
match = re.match('^SET([DS])\^([^\^]*)\^(.*)$', line)
if not match:
# @TODO: do we need some other stuff that gets dropped here?
continue
handleMixerParam(match.group(2), castValue( match.group(1), match.group(3) ))
def castValue(valueType, value):
if valueType == "S":
return value
if value == "0" or value == "1":
return int(value)
return float(value)
# we need 3 parameters to be able to persist recording:
# *) recStateRemote: the record status of the ui24r
# *) the current time of ui24r's recording
# *) the session name to be able to create a directory for persisting in filesystem
#
# only when we have all of these we can start the recording
# but we have to apply an offset (timestamp correction) of x seconds int the past as soon as we have everything we need for recording
def handleMixerParam(paramName, paramValue):
global sessionName, recStateRemote, recStartTime, dataContainer
dataContainer[ paramName ] = paramValue
if paramName == "var.mtk.rec.currentState":
recStateRemote = paramValue
if recStateRemote == 1:
recStartTime = 0
if paramName == "var.mtk.rec.session":
sessionName = paramValue
# too bad we do not have a session name after first second of recording
# so apply the offset into the past
if paramName == "var.mtk.rec.time" and recStartTime == 0:
if recStateRemote == 1:
recStartTime = int(round(time.time() * 1000)) - (1000 * paramValue)
if armed == False and sessionName != "" and recStateRemote == 1 and recStartTime > 0:
recStart()
if armed == True:
recordParamChange(paramName, paramValue)
if recStateRemote == 0:
recStop()
def recStart():
global armed, recFile
# include current second in filename to avoid conflicts
recFile = Path(
"%s/recordings/%s-recsession-%s.uiparamrecording.txt" % (
os.path.dirname(os.path.abspath(__file__)),
datetime.today().strftime('%Y.%m.%d--%H.%M.%S'),
sessionName
)
)
# dump all data to have initial param values
dumpAllToFile()
logging.info("started recording to file:\n %s" % str(recFile))
# theoretically we have had an untracked param change during the last few miliseconds
# @TODO: is it necessary to collect those and append?
armed = True
def dumpAllToFile():
global recFile
f = recFile.open("a")
for key, value in dataContainer.items():
f.write("0 %s %s\n" % (key, value))
f.close()
def recordParamChange(paramName, paramValue):
global recFile
if isBlacklisted(paramName):
return
logging.debug("recording param: %s -> %s" % (paramName, paramValue))
with recFile.open("a") as f:
f.write("%s %s %s\n" % (getRelativeTime(), paramName, paramValue))
def isBlacklisted(paramName):
blackList = [
"var.mtk.bufferfill",
"var.mtk.freespace"
]
return paramName in blackList
def getRelativeTime():
return (float(round(time.time() * 1000)) - recStartTime) / 1000
def recStop():
global armed, recStartTime
armed = False
recStartTime = 0
logging.info("recording stopped")
def onMixerSocketError(ws, error):
logging.critical("socket error %s" % error)
def onMixerSocketClose(ws):
print("### closed ###")
def onMixerSocketOpen(ws):
logging.info("connecting to %s" % ws.url)
def run(*args):
logging.info("connection established")
while True:
ws.send("3:::ALIVE")
time.sleep(1)
print("thread terminating...")
thread.start_new_thread(run, ())
# @TODO: handle connect error
async def createMixerWebSocket(ip):
ws = websocket.WebSocketApp(
"ws://%s/socket.io/1/websocket" % ip,
on_open = onMixerSocketOpen,
on_message = onMixerSocketMessage,
on_error = onMixerSocketError,
on_close = onMixerSocketClose
)
wst = threading.Thread(target=ws.run_forever)
wst.daemon = True
wst.start()
if __name__ == "__main__":
main()