pad.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. #!/usr/bin/python
  2. # Type of LED Functions.
  3. # 0=off,
  4. # 1=green,
  5. # 2=green blink,
  6. # 3=red,
  7. # 4=red blink,
  8. # 5=yellow,
  9. # 6=yellow blink,
  10. # 7-127=green,
  11. # Type of LED Ring Functions
  12. # 0=off,
  13. # 1=Single,
  14. # 2=Volume Style,
  15. # 3=Pan Style,
  16. # 4-127=Single
  17. import logging
  18. import sys
  19. import time
  20. import rtmidi
  21. import random
  22. from rtmidi.midiutil import open_midioutput
  23. from rtmidi.midiconstants import NOTE_OFF, NOTE_ON
  24. from simplemidi.midiio import MidiInputPort, MidiOutputPort
  25. from simplemidi.midimessage import MidiType
  26. from simplemidi.alsaconnector import AlsaConnector
  27. from simplemidi.options import *
  28. """
  29. Classe qui represente un bouton
  30. """
  31. class Button:
  32. def __init__(self, port, index, channel=1):
  33. self.channel=channel # canal midi : int
  34. self.oport=port # port de sortie : MidiOut
  35. self.index=index # index (=note) du bouton : int
  36. Button.LED_UNKNOWN=-1 # etat du bouton (couleur de la led) incoonu
  37. Button.OFF=0
  38. self.state=Button.LED_UNKNOWN # etat de la led : int
  39. self.mindata=0 #valeur minimal que le bouton peut predre : int
  40. self.maxdata=0 #valeur max que le bouton peut predre : int
  41. self.values=[] # liste des valeurs que le bouton peut predre : int
  42. self.lastState=0 # etat précédent (pour la fonction toggle()) : int
  43. def onNoteOn(self, channel, idx, val):
  44. pass
  45. def onNoteOff(self, channel, idx, val):
  46. pass
  47. """
  48. Envoi un note on pour changer de couleur
  49. dat: int: couleur a mettre
  50. """
  51. def send(self, data):
  52. if data<=self.maxdata and data>=self.mindata:
  53. self.state=data
  54. if data!=Button.OFF and (data in self.values):
  55. self.lastState=data
  56. self.oport.noteOn(1, self.index, data)
  57. time.sleep(0.00001)
  58. """
  59. Demande au bouton de passe à la couleur suivante
  60. """
  61. def next(self):
  62. if self.state==Button.LED_UNKNOWN:
  63. return
  64. x=self.state+1
  65. if x>self.maxdata:
  66. x=self.mindata
  67. self.send(x)
  68. """
  69. Demande au bouton de passe à la couleur précédente
  70. """
  71. def prev(self):
  72. if self.state==Button.LED_UNKNOWN:
  73. return
  74. x=self.state-1
  75. if x<self.mindata:
  76. x=self.maxdata
  77. self.send(x)
  78. """
  79. Prend (parmis les valeur valide) une couleur au hasard
  80. """
  81. def random(self):
  82. self.send(random.sample(self.values, 1)[0])
  83. """
  84. Bascule d'un état allumé à un etat eteint (et vis versa)
  85. """
  86. def toggle(self):
  87. if self.lastState==Button.OFF:
  88. self.lastState=self.values[0]
  89. if self.state!=Button.LED_UNKNOWN and self.state!=Button.OFF:
  90. self.send(0)
  91. else:
  92. self.send(self.lastState)
  93. """
  94. Gere les boutons classiques a 7 états (les boutons de 0 a 63 dans
  95. l' AKAI APC Mini
  96. """
  97. class LedButton(Button):
  98. def __init__(self, port, index, channel=1):
  99. Button.__init__(self, port, index, channel)
  100. LedButton.UNKNOWN=Button.LED_UNKNOWN
  101. LedButton.OFF=0
  102. LedButton.GREEN=1
  103. LedButton.GREEN_BLINK=2
  104. LedButton.RED=3
  105. LedButton.RED_BLINK=4
  106. LedButton.YELLOW=5
  107. LedButton.YELLOW_BLINK=6
  108. self.maxdata=6
  109. self.values=[0,1,2,3,4,5,6]
  110. """
  111. Eteint la led du bouton
  112. """
  113. def off(self):
  114. self.send(LedButton.OFF)
  115. """
  116. Passe le bouton en vert
  117. blink: bool : Fait clignter la led
  118. """
  119. def green(self, blink=False):
  120. self.send(LedButton.GREEN_BLINK if blink else LedButton.GREEN)
  121. """
  122. Passe le bouton en rouge
  123. blink: bool : Fait clignter la led
  124. """
  125. def red(self, blink=False):
  126. self.send(LedButton.RED_BLINK if blink else LedButton.RED)
  127. """
  128. Passe le bouton en jaune
  129. blink: bool : Fait clignter la led
  130. """
  131. def yellow(self, blink=False):
  132. self.send(LedButton.YELLOW_BLINK if blink else LedButton.YELLOW)
  133. class LedCtrlButton(Button):
  134. def __init__(self, port, index, channel=1):
  135. Button.__init__(self, port, index, channel)
  136. LedCtrlButton.UNKNOWN=Button.LED_UNKNOWN
  137. LedCtrlButton.OFF=0
  138. LedCtrlButton.ON=1
  139. LedCtrlButton.BLINK=2
  140. self.maxdata=2
  141. self.values=[0,1,2]
  142. def off(self):
  143. self.send(LedCtrlButton.OFF)
  144. def on(self):
  145. self.send(LedCtrlButton.ON)
  146. def blink(self):
  147. self.send(LedCtrlButton.BLINK)
  148. MIDI_ERROR_STRING={
  149. rtmidi.ERRORTYPE_WARNING:"ERRORTYPE_WARNING",
  150. rtmidi.ERRORTYPE_DEBUG_WARNING:"ERRORTYPE_DEBUG_WARNING",
  151. rtmidi.ERRORTYPE_UNSPECIFIED:"ERRORTYPE_UNSPECIFIED",
  152. rtmidi.ERRORTYPE_NO_DEVICES_FOUND:"ERRORTYPE_NO_DEVICES_FOUND",
  153. rtmidi.ERRORTYPE_INVALID_DEVICE:"ERRORTYPE_INVALID_DEVICE",
  154. rtmidi.ERRORTYPE_MEMORY_ERROR:"ERRORTYPE_MEMORY_ERROR",
  155. rtmidi.ERRORTYPE_INVALID_PARAMETER:"ERRORTYPE_INVALID_PARAMETER",
  156. rtmidi.ERRORTYPE_INVALID_USE:"ERRORTYPE_INVALID_USE",
  157. rtmidi.ERRORTYPE_DRIVER_ERROR:"ERRORTYPE_DRIVER_ERROR",
  158. rtmidi.ERRORTYPE_SYSTEM_ERROR:"ERRORTYPE_SYSTEM_ERROR",
  159. rtmidi.ERRORTYPE_THREAD_ERROR:"ERRORTYPE_THREAD_ERROR"
  160. }
  161. """
  162. Classe qui gère un Pad
  163. """
  164. class Pad():
  165. _DEFAULT_PARAMS={
  166. 'port_in': MidiInputPort._DEFAULT_PARAMS,
  167. 'port_out': MidiOutputPort._DEFAULT_PARAMS
  168. }
  169. def __init__(self, adapter, params={}):
  170. param=initParams(Pad._DEFAULT_PARAMS, params)
  171. self.oport=MidiOutputPort.fromParams(param['port_out'])
  172. self.iport=MidiInputPort.fromParams(param['port_in'])
  173. self.iport.setInputCallback(self.inputMessage, self)
  174. self.iport.setErrorCallback(self.inputError, self)
  175. self.ledbuttons={} # liste des boutons
  176. self.input={} # listes des états des boutons
  177. self.ledbuttonsWidth=0 # nombre de bouton de note par ligne
  178. self.ledbuttonsHeight=0 # nombre de bouton de note par collone
  179. self.adapt(adapter)
  180. self.clear()
  181. """
  182. Permet d'appeler un adapteur qui permettra de configurer les boutons
  183. """
  184. def adapt(self, adapter):
  185. adapter.adapt(self)
  186. self.onAdapt()
  187. """
  188. Appelé quand le pad a ete adapté
  189. """
  190. def onAdapt(self):
  191. pass
  192. """
  193. Attend tant qu'un boutton est presse ou relache
  194. idx: int : L'id de la touche, un liste d'id ou None pour n'importe que touche
  195. state: bool: True pour une attente de NOTE_ON False pour NOTE_OFF
  196. t: float : temps a attendre entre 2 poll
  197. func: callback : permet d appeler une callback a chaque sondage
  198. data: * : permet de passer un parametre a la fonction
  199. Retourne l'id qui donne le signal
  200. """
  201. def waitForInput(self, idx=None, state=True, t=0.01, func=None, data=None):
  202. while True:
  203. i=self.pollInput(idx, state)
  204. if i!=None:
  205. return i
  206. if func!=None:
  207. func(data)
  208. time.sleep(t)
  209. """
  210. Sonde si un boutton est presse ou relache
  211. idx: int : L'id de la touche, un liste d'id ou None pour n'importe que touche
  212. state: bool: True pour une attente de NOTE_ON False pour NOTE_OFFnction
  213. Retourne l'id qui du bouton ou None
  214. """
  215. def pollInput(self, idx=None, state=True):
  216. if isinstance(idx, int):
  217. idx=[idx]
  218. if idx==None:
  219. idx=[]
  220. for i in self.keys():
  221. idx.append(i)
  222. for i in idx:
  223. if self.input[i]==state:
  224. return i
  225. return None
  226. """
  227. Eteint tous les boutons
  228. """
  229. def clear(self):
  230. for k in self.keys():
  231. self.ledbuttons[k].off()
  232. """
  233. Fonction qui va recevoir toutes les erreurs MIDI du pad
  234. """
  235. def inputError(self, code, msg):
  236. ERR("Midi error:"+ str(MIDI_ERROR_STRING[code])+" : "+str(msg))
  237. """
  238. Fonction qui va recevoir tous les messages MIDI du pad
  239. """
  240. def inputMessage(self, delta, msg):
  241. channel=msg.channel
  242. if msg.getType()==NOTE_ON:
  243. index=msg.key
  244. self._onNoteOn(channel, index, msg.velocity)
  245. if self.ledbuttons[index]:
  246. self.ledbuttons[index].onNoteOn(channel, index, msg.velocity)
  247. elif msg.getType()==NOTE_OFF:
  248. index=msg.key
  249. self._onNoteOff(channel, index, msg.velocity)
  250. if self.ledbuttons[index]:
  251. self.ledbuttons[index].onNoteOff(channel, index, msg.velocity)
  252. elif msg.getType()==MidiType.CONTROL_CHANGE:
  253. index=msg.key
  254. self.onControlChange(channel,index, msg.value)
  255. """
  256. Fonction appellé en cas d'appui sur une touche
  257. """
  258. def onNoteOn(self, channel, idx, val):
  259. pass
  260. """
  261. Fonction appellé quand une touche est relachée
  262. """
  263. def onNoteOff(self, channel, idx, val):
  264. pass
  265. def onControlChange(self, channel, key, value):
  266. pass
  267. def _onNoteOn(self, channel, idx, val):
  268. self.input[idx]=True
  269. self.onNoteOn(channel, idx, val)
  270. def _onNoteOff(self, channel, idx, val):
  271. self.input[idx]=False
  272. self.onNoteOff(channel, idx, val)
  273. def getInputState(self, idx):
  274. if idx in self.input.keys():
  275. return self.input[idx]
  276. return None
  277. @staticmethod
  278. def matrixToArray(mat):
  279. out=[]
  280. for y in mat:
  281. for x in y:
  282. out.append(x)
  283. return out
  284. """
  285. Mappe un vecteur ou une matrice sur une partie du pad
  286. """
  287. def mapSubRect(self, data, start, end=None):
  288. if (isinstance(data, list) or isinstance(data, tuple)) and len(data)>0:
  289. if (isinstance(data[0], list) or isinstance(data[0], tuple)):
  290. end=(start[0]+len(data[0]), start[1]+len(data[1]))
  291. data=Pad.matrixToArray(data)
  292. if end==None:
  293. end=(self.ledbuttonsWidth, self.ledbuttonsHeight)
  294. w=end[0]-start[0]
  295. realw= w if start[0]+w<self.ledbuttonsWidth else self.ledbuttonsWidth
  296. for j in range(start[1], end[1]):
  297. for i in range(start[0], end[0]):
  298. if i>= realw: continue
  299. n=(j-start[1])*w+(i-start[0])
  300. self[i,j]=data[n]
  301. def fill(self, value, matrixOnly=True):
  302. if matrixOnly:
  303. x=[]
  304. for i in range(self.ledbuttonsWidth*self.ledbuttonsHeight):
  305. x.append(value)
  306. self.map(x)
  307. else:
  308. for k in self.keys():
  309. self[k]=value
  310. def blink(self, value, n=2):
  311. self.fill(value)
  312. time.sleep(n/2.0)
  313. def loadingScreen(self, tt):
  314. t=tt/9.0
  315. for i in range(64,64+8):
  316. self[i].on()
  317. time.sleep(t)
  318. self[i].off()
  319. for k in range(2):
  320. for i in range(64,64+8):
  321. self[i].on()
  322. time.sleep(t/3)
  323. for i in range(64,64+8):
  324. self[i].off()
  325. if k<2: time.sleep(t/3)
  326. def showText(self, text):
  327. mat=text.getDataToShow()
  328. self.mapSubRect(mat, text.pos, (text.dim[0]+text.pos[0],text.dim[1]+text.pos[1]))
  329. text.step()
  330. """
  331. Mappe un vecteur ou une matrice tous les boutons de notes
  332. """
  333. def map(self, data):
  334. first=self.ledbuttons[0].index
  335. self.mapSubRect(data, (int(first/self.ledbuttonsWidth),first%self.ledbuttonsWidth))
  336. #liste des id des boutons
  337. def keys(self):
  338. return self.ledbuttons.keys()
  339. # transforeme une coordonnée d'uune matrice ou d'un vecteur en une coordonée d'un vecteur
  340. def getIndex(self, x, y=-1):
  341. return x if y<0 else x+y*self.ledbuttonsWidth
  342. # renvoie le bouton a l'adresse x, y
  343. def getButton(self, x, y=-1):
  344. return self.ledbuttons[self.getIndex(x,y)]
  345. def __getitem__(self,key):
  346. if isinstance(key, tuple):
  347. return self.getButton(key[0], key[1])
  348. else:
  349. return self.getButton(key)
  350. def __setitem__(self,key,value):
  351. if isinstance(key, tuple):
  352. x=self.getButton(key[0], key[1])
  353. if x:
  354. x.send(value)
  355. else:
  356. x=self.getButton(key).send(value)
  357. if x:
  358. x.send(value)
  359. def connectOut(self, port):
  360. port=AlsaConnector.portAuto(port)
  361. self.oport.connect(port)
  362. INFO("Connected to input port: "+str(port)+"\n")
  363. def connectIn(self, port):
  364. port=AlsaConnector.portAuto(port)
  365. self.iport.connect(port)
  366. INFO("Connected to output port: "+str(port)+"\n")
  367. def close(self):
  368. self.oport.close()
  369. self.iport.close()
  370. TEXT_DATA={
  371. 'A': [[1,1,1,0], [1,0,1,0], [1,1,1,0], [1,0,1,0]],
  372. 'R': [[1,1,1,0], [1,1,0,0], [1,0,1,0], [1,0,1,0]],
  373. 'T': [[1,1,1,0], [0,1,0,0], [0,1,0,0], [0,1,0,0]],
  374. 'Y': [[1,0,1,0], [1,0,1,0], [0,1,0,0], [0,1,0,0]],
  375. 'G': [[1,1,1,0], [1,0,0,0], [1,0,1,0], [1,1,1,0]],
  376. 'I': [[0,1,0,0], [0,1,0,0], [0,1,0,0], [0,1,0,0]],
  377. 'L': [[0,1,0,0], [0,1,0,0], [0,1,0,0], [0,1,1,0]],
  378. 'N': [[1,0,1,0], [1,1,1,0], [1,1,1,0], [1,0,1,0]],
  379. 'O': [[1,1,1,0], [1,0,1,0], [1,0,1,0], [1,1,1,0]],
  380. 'C': [[1,1,1,0], [1,0,0,0], [1,0,0,0], [1,1,1,0]],
  381. 'S': [[1,1,1,0], [1,1,1,0], [0,0,1,0], [1,1,1,0]],
  382. ' ': [[0,0,0,0], [0,0,0,0], [0,0,0,0], [0,0,0,0]],
  383. ':': [[0,0,0,0], [0,1,0,0], [0,0,0,0], [0,1,0,0]],
  384. '0': [[1,1,1,0], [1,0,1,0], [1,0,1,0], [1,1,1,0]],
  385. '1': [[0,1,0,0], [1,1,0,0], [0,1,0,0], [0,1,0,0]],
  386. '2': [[1,1,1,0], [0,0,1,0], [0,1,0,0], [1,1,1,0]],
  387. '3': [[0,1,1,0], [0,0,1,0], [0,1,1,0], [0,1,1,0]],
  388. '4': [[0,0,1,0], [0,1,1,0], [1,1,1,1], [0,0,1,0]],
  389. '5': [[1,1,1,0], [1,0,0,0], [1,1,1,0], [1,1,1,0]],
  390. '6': [[0,1,1,0], [0,1,0,0], [0,1,1,0], [0,1,1,0]],
  391. '7': [[1,1,0,0], [0,1,0,0], [1,1,1,0], [0,1,0,0]],
  392. '8': [[1,1,1,0], [1,1,1,0], [1,0,1,0], [1,1,1,0]],
  393. '9': [[0,1,1,0], [0,1,1,0], [0,0,1,0], [0,1,1,0]],
  394. 'M': [[1,0,1,0], [1,1,1,0], [1,1,1,0], [1,1,1,0]],
  395. 'E': [[0,1,1,0], [0,1,1,0], [0,1,0,0], [0,1,1,0]],
  396. 'V': [[1,0,1,0], [1,0,1,0], [1,0,1,0], [0,1,0,0]],
  397. '!': [[0,1,0,0], [0,1,0,0], [0,0,0,0], [0,1,0,0]],
  398. '?': [[1,1,1,0], [0,1,1,0], [0,0,0,0], [0,1,0,0]]
  399. }
  400. """
  401. Classe qui gère les donnes a afficher pour afficher du texte défilant
  402. """
  403. class Text:
  404. def __init__(self, txt, dim, pos=(0,0)):
  405. self.pos=pos # position : (x, y)
  406. self.dim=dim # dimension : (w,h)
  407. self.txt=txt.upper() # texte a afficher : str
  408. self.len=len(txt)*4 # longueur en cases (pas en caracteres) : int
  409. self.matrix=[] # toutes les données : Matrix [ [..] .. [..] ]
  410. for y in range(4):
  411. for x in range(self.len):
  412. self.matrix.append(0)
  413. self.offset=-1 # décalage de la partie a afficher actuellement : in
  414. self.color=1 # couleur du text : int
  415. self.backgroundcolor=0 #couleur du fond : int
  416. for i in range(len(self.txt)):
  417. self.map(self.txt[i], i*4)
  418. """
  419. Récupère un vecteur de bit a afficher pour une lettre
  420. color: int : Couleur du texte
  421. backcolor: int : couleur du fond
  422. """
  423. @staticmethod
  424. def letterVector(char, color=1, backcolor=0):
  425. x=[]
  426. c=TEXT_DATA[char]
  427. for j in reversed(range(4)):
  428. for i in range(4):
  429. x.append(color if c[j][i]>0 else backcolor)
  430. return x
  431. """
  432. Map un vecteur ou une matrix dans les données a afficher
  433. """
  434. def map(self, char, off):
  435. c=TEXT_DATA[char]
  436. for j in reversed(range(4)):
  437. for i in range(off, off+4):
  438. self.matrix[i+(3-j)*self.len]=c[j][i-off]
  439. """
  440. Change la couleur du text
  441. c: int : Couleur du texte
  442. """
  443. def setColor(self, c):
  444. self.color=c
  445. """
  446. Change la couleur du fond
  447. c: int : Couleur du fond
  448. """
  449. def setBackgroundColor(self, c):
  450. self.backgroundcolor=c
  451. """
  452. Recupere le vecteur a mapper sur le pad
  453. """
  454. def getDataToShow(self):
  455. out=[]
  456. for j in range(4):
  457. for i in range(self.offset, self.offset+self.dim[0]):
  458. i=i%self.len
  459. xx=self.matrix[i+j*self.len]
  460. x= self.color if xx>0 else self.backgroundcolor
  461. out.append(x)
  462. return out
  463. """
  464. Donne le nombre d'étape pour afficher completement le text n fois
  465. n: (int = 1) : Nombre de fois a afficher
  466. """
  467. def stepCount(self, n=1):
  468. return (self.len-self.dim[0]+1)*n
  469. """
  470. Décale d'une position (case) le texte
  471. """
  472. def step(self):
  473. self.offset=(self.offset+1)%self.len
  474. import os
  475. def setRealtime(rt=os.SCHED_FIFO):
  476. try:
  477. os.sched_setscheduler(0, rt, os.sched_param(0))
  478. return True
  479. except Exception as err:
  480. ERR("Impossible de passer en temps-réel: "+str(err))
  481. return False