You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

redilysis.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. #!/usr/bin/python3
  2. # -*- coding: utf-8 -*-
  3. # -*- mode: Python -*-
  4. '''
  5. redilysis
  6. v0.1.0
  7. A complex effect that depends on redis keys for audio analysis
  8. see https://git.interhacker.space/teamlase/redilysis for more informations
  9. about the redilysis project
  10. LICENCE : CC
  11. by cocoa
  12. '''
  13. from __future__ import print_function
  14. import argparse
  15. import ast
  16. import os
  17. import math
  18. import random
  19. import redis
  20. import sys
  21. import time
  22. name = "filters::redilysis"
  23. def debug(*args, **kwargs):
  24. if( verbose == False ):
  25. return
  26. print(*args, file=sys.stderr, **kwargs)
  27. def msNow():
  28. return time.time()
  29. # The list of available modes => redis keys each requires to run
  30. oModeList = {
  31. "rms_noise": ["rms"],
  32. "rms_size": ["rms"],
  33. "bpm_size": ["bpm"],
  34. "bpm_detect_size": ["bpm","bpm_delay","bpm_sample_interval","beats"]
  35. }
  36. CHAOS = 1
  37. REDISLATENCY = 30
  38. REDIS_FREQ = 300
  39. # General Args
  40. argsparser = argparse.ArgumentParser(description="Redilysis filter")
  41. argsparser.add_argument("-v","--verbose",action="store_true",help="Verbose")
  42. # Redis Args
  43. argsparser.add_argument("-i","--ip",help="IP address of the Redis server ",default="127.0.0.1",type=str)
  44. argsparser.add_argument("-p","--port",help="Port of the Redis server ",default="6379",type=str)
  45. argsparser.add_argument("-s","--redis-freq",help="Query Redis every x (in milliseconds). Default:{}".format(REDIS_FREQ),default=REDIS_FREQ,type=int)
  46. # General args
  47. argsparser.add_argument("-x","--centerX",help="geometrical center X position",default=400,type=int)
  48. argsparser.add_argument("-y","--centerY",help="geometrical center Y position",default=400,type=int)
  49. argsparser.add_argument("-f","--fps",help="Frame Per Second",default=30,type=int)
  50. # Modes And Common Modes Parameters
  51. argsparser.add_argument("-l","--redisLatency",help="Latency in ms to substract. Default:{}".format(REDISLATENCY),default=REDISLATENCY,type=float)
  52. argsparser.add_argument("-m","--modelist",required=True,help="Comma separated list of modes to use from: {}".format("i, ".join(oModeList.keys())),type=str)
  53. argsparser.add_argument("--chaos",help="How much disorder to bring. High value = More chaos. Default {}".format(CHAOS), default=CHAOS, type=str)
  54. args = argsparser.parse_args()
  55. ip = args.ip
  56. port = args.port
  57. redisFreq = args.redis_freq / 1000
  58. verbose = args.verbose
  59. fps = args.fps
  60. centerX = args.centerX
  61. centerY = args.centerY
  62. redisLatency = args.redisLatency
  63. chaos = float(args.chaos)
  64. optimal_looptime = 1 / fps
  65. modeList = args.modelist.split(",")
  66. redisKeys = []
  67. for mode in modeList:
  68. if not mode in oModeList:
  69. print("Mode '{}' is invalid. Exiting.".format(mode))
  70. sys.exit(2)
  71. redisKeys += oModeList[mode]
  72. redisKeys = list(set(redisKeys))
  73. debug(name,"Redis Keys:{}".format(redisKeys))
  74. redisData = {}
  75. redisLastHit = msNow() - 99999
  76. r = redis.Redis(
  77. host=ip,
  78. port=port)
  79. # Records the last bpm
  80. tsLastBeat = time.time()
  81. def gauss(x, mu, sigma):
  82. return( math.exp(-math.pow((x-mu),2)/(2*math.pow(sigma,2))/math.sqrt(2*math.pi*math.pow(sigma,2))))
  83. previousPTTL = 0
  84. tsNextBeatsList = []
  85. def bpmDetect( ):
  86. """
  87. An helper to compute the next beat time in milliseconds
  88. Returns True if the cache was updated
  89. """
  90. global tsNextBeatsList
  91. global previousPTTL
  92. global redisLastHit
  93. global redisLatency
  94. # Get the redis PTTL value for bpm
  95. PTTL = redisData["bpm_pttl"]
  96. # Skip early if PTTL < 0
  97. if PTTL < 0 :
  98. debug(name,"bpmDetect skip detection : PTTL expired for 'bpm' key")
  99. return False
  100. # Skip early if the record hasn't been rewritten
  101. if PTTL <= previousPTTL :
  102. previousPTTL = PTTL
  103. #debug(name,"bpmDetect skip detection : {} <= {}".format(PTTL, previousPTTL))
  104. return False
  105. debug(name,"bpmDetect running detection : {} > {}".format(PTTL, previousPTTL))
  106. previousPTTL = PTTL
  107. # Skip early if beat list is empty
  108. beatsList = ast.literal_eval(redisData["beats"])
  109. tsNextBeatsList = []
  110. if( len(beatsList) == 0 ):
  111. return True
  112. # Read from redis
  113. bpm = float(redisData["bpm"])
  114. msBpmDelay = float(redisData["bpm_delay"])
  115. samplingInterval = float(redisData["bpm_sample_interval"])
  116. # Calculate some interpolations
  117. lastBeatTiming = float(beatsList[len(beatsList) - 1])
  118. msPTTLDelta = 2 * samplingInterval - float(PTTL)
  119. sPerBeat = 60 / bpm
  120. lastBeatDelay = msBpmDelay - lastBeatTiming*1000 + msPTTLDelta
  121. countBeatsPast = math.floor( (lastBeatDelay / 1000) / sPerBeat)
  122. #debug(name,"bpmDetect lastBeatTiming:{}\tmsPTTLDelta:{}\tsPerBeat:{}".format(lastBeatTiming,msPTTLDelta,sPerBeat))
  123. #debug(name,"lastBeatDelay:{}\t countBeatsPast:{}".format(lastBeatDelay, countBeatsPast))
  124. for i in range( countBeatsPast, 1000):
  125. beatTime = i * sPerBeat - lastBeatTiming
  126. if beatTime < 0:
  127. continue
  128. if beatTime * 1000 > 2 * samplingInterval :
  129. break
  130. #debug(name, "bpmDetect beat add beatTime:{} redisLastHit:{}".format(beatTime, redisLastHit))
  131. tsNextBeatsList.append( redisLastHit + beatTime - redisLatency/1000)
  132. debug(name, "bpmDetect new tsNextBeatsList:{}".format(tsNextBeatsList))
  133. return True
  134. def bpm_detect_size( pl ):
  135. bpmDetect()
  136. # Find the next beat in the list
  137. tsNextBeat = 0
  138. now = time.time()
  139. msNearestBeat = None
  140. msRelativeNextBTList = list(map( lambda a: abs(now - a) * 1000, tsNextBeatsList))
  141. msToBeat = min( msRelativeNextBTList)
  142. #debug(name,"bpm_detect_size msRelativeNextBTList:{} msToBeat:{}".format(msRelativeNextBTList,msToBeat))
  143. # Calculate the intensity based on bpm coming/leaving
  144. # The curb is a gaussian
  145. mu = 15
  146. intensity = gauss( msToBeat, 0 , mu)
  147. #debug(name,"bpm_size","mu:{}\t msToBeat:{}\tintensity:{}".format(mu, msToBeat, intensity))
  148. if msToBeat < 20:
  149. debug(name,"bpm_detect_size kick:{}".format(msToBeat))
  150. pass
  151. for i, point in enumerate(pl):
  152. ref_x = point[0]-centerX
  153. ref_y = point[1]-centerY
  154. #debug(name,"In new ref x:{} y:{}".format(point[0]-centerX,point[1]-centerY))
  155. angle=math.atan2( point[0] - centerX , point[1] - centerY )
  156. l = ref_y / math.cos(angle)
  157. new_l = l * intensity
  158. #debug(name,"bpm_size","angle:{} l:{} new_l:{}".format(angle,l,new_l))
  159. new_x = math.sin(angle) * new_l + centerX
  160. new_y = math.cos(angle) * new_l + centerY
  161. #debug(name,"x,y:({},{}) x',y':({},{})".format(point[0],point[1],new_x,new_y))
  162. pl[i][0] = new_x
  163. pl[i][1] = new_y
  164. #debug( name,"bpm_detect_size output:{}".format(pl))
  165. return( pl );
  166. def bpm_size( pl ):
  167. global tsLastBeat
  168. bpm = float(redisData["bpm"])
  169. # msseconds ber beat
  170. msPerBeat = int(60 / bpm * 1000)
  171. # Calculate the intensity based on bpm coming/leaving
  172. # The curb is a gaussian
  173. mu = math.sqrt(msPerBeat)
  174. msTimeToLastBeat = (time.time() - tsLastBeat) * 1000
  175. msTimeToNextBeat = (msPerBeat - msTimeToLastBeat)
  176. intensity = gauss( msTimeToNextBeat, 0 , mu)
  177. debug(name,"bpm_size","msPerBeat:{}\tmu:{}".format(msPerBeat, mu))
  178. debug(name,"bpm_size","msTimeToLastBeat:{}\tmsTimeToNextBeat:{}\tintensity:{}".format(msTimeToLastBeat, msTimeToNextBeat, intensity))
  179. if msTimeToNextBeat <= 0 :
  180. tsLastBeat = time.time()
  181. for i, point in enumerate(pl):
  182. ref_x = point[0]-centerX
  183. ref_y = point[1]-centerY
  184. #debug(name,"In new ref x:{} y:{}".format(point[0]-centerX,point[1]-centerY))
  185. angle=math.atan2( point[0] - centerX , point[1] - centerY )
  186. l = ref_y / math.cos(angle)
  187. new_l = l * intensity
  188. #debug(name,"bpm_size","angle:{} l:{} new_l:{}".format(angle,l,new_l))
  189. new_x = math.sin(angle) * new_l + centerX
  190. new_y = math.cos(angle) * new_l + centerY
  191. #debug(name,"x,y:({},{}) x',y':({},{})".format(point[0],point[1],new_x,new_y))
  192. pl[i][0] = new_x
  193. pl[i][1] = new_y
  194. #debug( name,"bpm_noise output:{}".format(pl))
  195. return pl
  196. def rms_size( pl ):
  197. rms = float(redisData["rms"])
  198. for i, point in enumerate(pl):
  199. ref_x = point[0]-centerX
  200. ref_y = point[1]-centerY
  201. debug(name,"In new ref x:{} y:{}".format(point[0]-centerX,point[1]-centerY))
  202. angle=math.atan2( point[0] - centerX , point[1] - centerY )
  203. l = ref_y / math.cos(angle)
  204. debug(name,"angle:{} l:{}".format(angle,l))
  205. new_l = l + rms * chaos
  206. new_x = math.sin(angle) * new_l + centerX
  207. new_y = math.cos(angle) * new_l + centerY
  208. debug(name,"x,y:({},{}) x',y':({},{})".format(point[0],point[1],new_x,new_y))
  209. pl[i][0] = new_x
  210. pl[i][1] = new_y
  211. #debug( name,"rms_noise output:{}".format(pl))
  212. return pl
  213. def rms_noise( pl ):
  214. rms = float(redisData["rms"])
  215. debug(name, "pl:{}".format(pl))
  216. for i, point in enumerate(pl):
  217. #debug(name,"rms_noise chaos:{} rms:{}".format(chaos, rms))
  218. xRandom = random.uniform(-1,1) * rms * chaos
  219. yRandom = random.uniform(-1,1) * rms * chaos
  220. #debug(name,"rms_noise xRandom:{} yRandom:{}".format(xRandom, yRandom))
  221. pl[i][0] += xRandom
  222. pl[i][1] += yRandom
  223. #debug( name,"rms_noise output:{}".format(pl))
  224. return pl
  225. def refreshRedis():
  226. global redisLastHit
  227. global redisData
  228. # Skip if cache is sufficent
  229. diff = msNow() - redisLastHit
  230. if diff < redisFreq :
  231. #debug(name, "refreshRedis not updating redis, {} < {}".format(diff, redisFreq))
  232. pass
  233. else:
  234. #debug(name, "refreshRedis updating redis, {} > {}".format(diff, redisFreq))
  235. redisLastHit = msNow()
  236. for key in redisKeys:
  237. redisData[key] = r.get(key).decode('ascii')
  238. #debug(name,"refreshRedis key:{} value:{}".format(key,redisData[key]))
  239. # Only update the TTLs
  240. if 'bpm' in redisKeys:
  241. redisData['bpm_pttl'] = r.pttl('bpm')
  242. #debug(name,"refreshRedis key:bpm_ttl value:{}".format(redisData["bpm_pttl"]))
  243. #debug(name,"redisData:{}".format(redisData))
  244. return True
  245. try:
  246. while True:
  247. refreshRedis()
  248. start = time.time()
  249. line = sys.stdin.readline()
  250. if line == "":
  251. time.sleep(0.01)
  252. line = line.rstrip('\n')
  253. pointsList = ast.literal_eval(line)
  254. # Do the filter
  255. for mode in modeList:
  256. pointsList = locals()[mode](pointsList)
  257. print( pointsList, flush=True )
  258. looptime = time.time() - start
  259. # debug(name+" looptime:"+str(looptime))
  260. if( looptime < optimal_looptime ):
  261. time.sleep( optimal_looptime - looptime)
  262. # debug(name+" micro sleep:"+str( optimal_looptime - looptime))
  263. except EOFError:
  264. debug(name+" break")# no more information