midiio.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. #!/usr/bin/python
  2. from .midimessage import *
  3. from rtmidi import MidiIn
  4. from rtmidi import MidiOut
  5. from rtmidi import MidiOut
  6. import copy
  7. import time
  8. from .alsaconnector import AlsaConnector, AlsaPort
  9. from .options import *
  10. from .utils import *
  11. ALSA = AlsaConnector()
  12. class MIDIPortException(Exception):
  13. def __init__(self, message):
  14. super().__init__(message)
  15. def _MidiPort__input_pad_error_cb(err, msg, obj):
  16. obj.inputError(err, msg)
  17. class _MidiPort():
  18. _DEFAULT_PARAMS={
  19. 'port': None,
  20. 'port_name' : "Midi Port",
  21. 'client_name' : "Simple MIDI",
  22. 'error_callback' : None
  23. }
  24. def __init__(self, direction,params):
  25. param=initParams(_MidiPort._DEFAULT_PARAMS, params)
  26. _MidiPort.INPUT=0
  27. _MidiPort.OUTPUT=1
  28. self.direction=direction
  29. self.port=param['port']
  30. self.portName=param['port_name']
  31. self.clientName=param['client_name']
  32. self.errorCb=param['error_callback']
  33. if self.port==None:
  34. if direction==_MidiPort.INPUT:
  35. self.port=MidiIn()
  36. else:
  37. self.port=MidiOut()
  38. self.port.set_error_callback(__input_pad_error_cb, self)
  39. self.alsaPort=None
  40. def _updateAlsaPort(self):
  41. cid=self.alsaPort.clientId
  42. pid=self.alsaPort.portId
  43. self.alsaPort=AlsaConnector.getPortFromId((cid, pid))
  44. self.portName=self.alsaPort.name
  45. def _initClientId(self, cid, pid):
  46. x=None
  47. if self.direction==_MidiPort.INPUT:
  48. x=MidiOut()
  49. else:
  50. x=MidiIn()
  51. self.alsaPort = AlsaConnector.findMe(cid, pid)
  52. def inputError(self, err, msg):
  53. ERR("MIDI Error :"+type(err).__name__+" "+msg)
  54. if self.errorCb:
  55. self.errorCb[0](self.errorCb[1], err, msg)
  56. def __input_pad_error_cb(err, msg):
  57. if self.errorCb:
  58. self.errorCb[0](self.errorCb[1], err, msg)
  59. def setErrorCallback(self, fct, data):
  60. self.errorCb=(fct,data)
  61. def cancelErrorCallback(self):
  62. self.errorCb=None
  63. def setClientName(self, name):
  64. self.clientName=name.replace(':', ' ')
  65. self.port.set_client_name(self.clientName)
  66. if self.isOpen(): self._updateAlsaPort()
  67. def setPortName(self, name):
  68. self.portName=name.replace(':', ' ')
  69. self.portName=name
  70. self.port.set_port_name(self.portName)
  71. if self.isOpen(): self._updateAlsaPort()
  72. def getPortName(self, n=None):
  73. if n==None:
  74. return self.portName
  75. return self.port.get_port_name(n)
  76. def isOpen(self):
  77. return self.port.is_port_open()
  78. def getPorts(self):
  79. return self.port.get_ports()
  80. def getPortCount(self):
  81. return self.port.get_port_count()
  82. def getApi(self):
  83. return self.port.get_current_api()
  84. def open(self, port=None):
  85. if self.isOpen():
  86. return True
  87. if port==None:
  88. port=self.portName
  89. self.setClientName(self.clientName)
  90. self.port.open_virtual_port(port)
  91. if self.isOpen():
  92. self._initClientId(self.clientName,port)
  93. INFO("ALSA Midi port: "+str(self.alsaPort)+" created")
  94. return True
  95. return False
  96. def connect(self, to):
  97. if isinstance(to, tuple):
  98. if self.direction==_MidiPort.OUTPUT:
  99. DEBUG("Connecting '"+str(self.alsaPort)+"' to "+str(to)+" ...")
  100. ALSA.connect(self.alsaPort.globalId, to)
  101. else:
  102. DEBUG("Connecting '"+str(to)+"' to "+str(self.alsaPort)+" ...")
  103. ALSA.connect(to, self.alsaPort.globalId)
  104. elif isinstance(to, str):
  105. port=AlsaPort(to)
  106. return self.connect(port.globalId)
  107. elif isinstance(to, int):
  108. return self.connect(self.getPorts()[to])
  109. elif isinstance(to, AlsaPort):
  110. return self.connect(to.globalId)
  111. def disconnect(self, to):
  112. if self.direction==_MidiPort.INPUT:
  113. ALSA.disconnect(to, self.portId)
  114. else:
  115. ALSA.disconnect(self.portId, to)
  116. def close(self):
  117. self.port.close_port()
  118. return not self.isOpen()
  119. """
  120. #
  121. # Input
  122. #
  123. #
  124. """
  125. def _on_input_callback(data, obj):
  126. obj._on_input_callback(data)
  127. class MidiInputPort(_MidiPort):
  128. _DEFAULT_PARAMS= dictAssign(_MidiPort._DEFAULT_PARAMS,{
  129. 'mode': 0,
  130. 'ignore_midi_type' : {},
  131. 'separate_callback': {},
  132. 'input_callback': None
  133. })
  134. @staticmethod
  135. def fromParams(param):
  136. param=initParams(MidiInputPort._DEFAULT_PARAMS, param)
  137. if param['port']==None:
  138. return MidiInputPort(param)
  139. else:
  140. return param
  141. def __init__(self, params):
  142. param=initParams(MidiInputPort._DEFAULT_PARAMS, params)
  143. _MidiPort.__init__(self, 0, param)
  144. MidiInputPort.MODE_QUEUE=0
  145. MidiInputPort.MODE_CALLBACK=1
  146. self.port.set_callback(_on_input_callback, self)
  147. self.port.ignore_types(False,False,False)
  148. self.queue=Queue()
  149. self.inputCb=param['input_callback']
  150. self.separatesCB=param['separate_callback']
  151. self.mode=param['mode']
  152. self.ignore=param['ignore_midi_type']
  153. """
  154. Modes
  155. """
  156. def setMode(self, mode):
  157. if self.mode!=MidiInputPort.MODE_QUEUE and \
  158. self.mode!=MidiInputPort.MODE_CALLBACK:
  159. raise MIDIPortException("Bad input mode ("+str(self.mode)+")")
  160. self.mode=mode
  161. def getMode(self):
  162. return self.mode
  163. """
  164. Callbacks
  165. """
  166. def setInputCallback(self, fct, data, typ=None):
  167. self.mode=MidiInputPort.MODE_CALLBACK
  168. if typ==None:
  169. self.inputCb=(fct,data)
  170. else:
  171. self.separatesCB[typ]=(fct, data)
  172. def cancelInputCallback(self, typ=None):
  173. if typ==None:
  174. self.inputCb=None
  175. else:
  176. self.separatesCB[typ]=None
  177. if len(self.separatesCB.keys())==0 and self.inputCb==None:
  178. self.mode=MidiInputPort.MODE_QUEUE
  179. def cancelAllCallbacks(self):
  180. self.inputCb=None
  181. self.separatesCB={}
  182. self.mode=MidiInputPort.MODE_QUEUE
  183. def _on_input_callback(self, data):
  184. t=data[1]
  185. obj=MidiMessage.parse(data[0])
  186. typ=obj.getType()
  187. if self.mode==MidiInputPort.MODE_QUEUE:
  188. if not ((typ in self.ignore.keys()) and self.ignore[typ]==True):
  189. self.queue.enqueue( (t,obj) )
  190. elif self.mode==MidiInputPort.MODE_CALLBACK:
  191. if self.inputCb and self.inputCb[0]:
  192. self.inputCb[0]( t, obj)
  193. if typ in self.separatesCB:
  194. cb=self.separatesCB[typ]
  195. if cb[0]:
  196. cb[0](t, obj)
  197. """
  198. Others
  199. """
  200. def get(self, sync=False):
  201. if sync: return self.getSync()
  202. if self.mode==MidiInputPort.MODE_QUEUE:
  203. x=self.queue.peek()
  204. if x: self.queue.dequeue()
  205. return x
  206. else:
  207. raise MIDIPortException("Bad mode for : get")
  208. def getSync(self):
  209. return self.queue.dequeue()
  210. def ignoreMessages(self, data):
  211. for k in data.keys():
  212. self.ignore[k]=data[k]
  213. """
  214. MIDIInputPort.ALL_IGNORED={ # True event is skipped, False or undefinedevent pass
  215. MidiType.NOTE_ON:True,
  216. MidiType.NOTE_OFF:True,
  217. MidiType.KEY_PRESSURE:True,
  218. MidiType.CONTROL_CHANGE:True,
  219. MidiType.PROGRAM_CHANGE:True,
  220. MidiType.CHANNEL_PRESSURE:True,
  221. MidiType.PITCH_WHEEL_CHANGE:True,
  222. MidiType.SYSTEM_EXCLUSIVE:True,
  223. MidiType.SONG_POSITION_POINTER:True,
  224. MidiType.SONG_SELECT:True,
  225. MidiType.TUNE_REQUEST:True,
  226. MidiType.END_OF_EXCUSIVE:True,
  227. MidiType.TIMING_CLOCK:True,
  228. MidiType.START:True,
  229. MidiType.CONTINUE:True,
  230. MidiType.STOP:True,
  231. MidiType.ACTIVE_SENSING:True,
  232. MidiType.META:True,
  233. MidiType.RESET:True,
  234. MidiType.META_SEQ:True,
  235. MidiType.META_TEXT:True,
  236. MidiType.META_COPYRIGHT:True,
  237. MidiType.META_TRACK_NAME:True,
  238. MidiType.META_INSTRUMENT_NAME:True,
  239. MidiType.META_LYRICS:True,
  240. MidiType.META_TEXT_MARKER:True,
  241. MidiType.META_CUE_POINT:True,
  242. MidiType.META_CHANNEL_PREFIX:True,
  243. MidiType.META_END_OF_TRACK:True,
  244. MidiType.META_SET_TEMPO:True,
  245. MidiType.META_SMPTE_OFFSET:True,
  246. MidiType.META_TIME_SIGNATURE:True
  247. }
  248. """
  249. """
  250. #
  251. # Output
  252. #
  253. #
  254. """
  255. class MidiOutputPort(_MidiPort):
  256. _DEFAULT_PARAMS=_MidiPort._DEFAULT_PARAMS
  257. @staticmethod
  258. def fromParams(param):
  259. param=initParams(MidiOutputPort._DEFAULT_PARAMS, param)
  260. if param['port']==None:
  261. return MidiOutputPort(param)
  262. else:
  263. return param
  264. def __init__(self, params):
  265. _MidiPort.__init__(self, 1, params)
  266. def send(self, param):
  267. if isinstance(param, (bytes, list)):
  268. send_message(self.port, param)
  269. #self.port.send_message(param)
  270. else:
  271. send_message(self.port, param.bytes())
  272. #self.port.send_message(param.bytes())
  273. def noteOn(self, ch, key, vel):
  274. self.send(NoteOn.new(ch, key, vel).bytes())
  275. def noteOff(self, ch, key, vel):
  276. self.send(NoteOff.new(ch, key, vel).bytes())
  277. def keyPressure(self, ch, key, pressure):
  278. self.send(KeyPressure.new(ch, key, pressure).bytes())
  279. def controlChange(self, ch, key, value):
  280. self.send(ControlChange.new(ch, key, value).bytes())
  281. def programChange(self, ch, key):
  282. self.send(ProgramChange.new(ch, key).bytes())
  283. def channelPressure(self, ch, pressure):
  284. self.send(ChannelPressure.new(ch, pressure).bytes())
  285. def pitchWheelChange(self, ch, pressure):
  286. self.send(PitchWheelChange.new(ch, pressure).bytes())
  287. def systemExclusive(self, data):
  288. self.send(SystemExclusive.new(data).bytes())
  289. def songPointerPosition(self, beats):
  290. self.send(SongPointerPosition.new(beats).bytes())
  291. def songSelect(self, song):
  292. self.send(SongSelect.new(song).bytes())
  293. def tuneRequest(self):
  294. self.send(TuneRequest.new().bytes())
  295. def timingClock(self):
  296. self.send(TimingClock.new().bytes())
  297. def start(self):
  298. self.send(Start.new().bytes())
  299. def continuer(self):
  300. self.send(Continue.new().bytes())
  301. def stop(self):
  302. self.send(Stop.new().bytes())
  303. def activeSensing(self):
  304. self.send(ActiveSensing.new().bytes())
  305. def endOfTrack(self):
  306. self.send(EndOfTrack.new().bytes())
  307. def sequenceNumber(self):
  308. self.send(SequenceNumber.new().bytes())
  309. def textEvent(self, text):
  310. self.send(TextEvent.new(text).bytes())
  311. def copyright(self, text):
  312. self.send(Copyright.new(text).bytes())
  313. def trackName(self, text):
  314. self.send(TrackName.new(text).bytes())
  315. def instrumentName(self, text):
  316. self.send(InstrumentName.new(text).bytes())
  317. def textLyrics(self, text):
  318. self.send(TextLyrics.new(text).bytes())
  319. def textMarker(self, text):
  320. self.send(TextMarker.new(text).bytes())
  321. def textEvent(self, text):
  322. self.send(TextEvent.new(text).bytes())
  323. def cuePoint(self, text):
  324. self.send(CuePoint.new(text).bytes())
  325. def channelPrefix(self, cc):
  326. self.send(ChannelPrefix.new(cc).bytes())
  327. def setTempo(self, tempo):
  328. self.send(SetTempo.new(tempo).bytes())
  329. def SMPTEOffset(self, hh, mm, ss, fr, ff):
  330. self.send(SMPTEOffset.new(hh, mm, ss, fr, ff).bytes())
  331. def timeSignature(self, numerator, denominator, clic, notes):
  332. self.send(SMPTEOffset.new(numerator, denominator, clic, notes).bytes())
  333. if __name__=="__main__":
  334. ipp=MidiInputPort()
  335. ipp.setClientName("Test 1")
  336. ipp.open("d")
  337. ippp=MidiInputPort()
  338. ippp.setClientName("Test 1")
  339. ippp.open("c")
  340. op=MidiOutputPort()
  341. op.setClientName("Test 1")
  342. op.open("b")
  343. opp=MidiOutputPort()
  344. opp.setClientName("Test 1")
  345. opp.open("a")
  346. time.sleep(100)
  347. """
  348. [
  349. ('System', 0,
  350. [
  351. ('Timer', 0, ([], [])),
  352. ('Announce', 1,
  353. (
  354. [
  355. (128, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0}),
  356. (130, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0})
  357. ],
  358. []
  359. )
  360. )
  361. ]
  362. )
  363. ,('Midi Through', 14,
  364. [
  365. ('Midi Through Port-0', 0,
  366. (
  367. [(128, 0, {'queue': 0, 'exclusive': 0, 'time_update': 1, 'time_real': 1})],
  368. [(128, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0})]
  369. )
  370. )
  371. ]
  372. ), ('APC MINI', 28,
  373. [
  374. ('APC MINI MIDI 1', 0,
  375. (
  376. [(128, 0, {'queue': 0, 'exclusive': 0, 'time_update': 1, 'time_real': 1})],
  377. [(128, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0})]
  378. )
  379. )
  380. ]
  381. ), ('a2jmidid', 128,
  382. [
  383. ('port', 0,
  384. (
  385. [(14, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0}), (28, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0}), (129, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0})],
  386. [(0, 1, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0}), (14, 0, {'queue': 0, 'exclusive': 0, 'time_update': 1, 'time_real': 1}), (28, 0, {'queue': 0, 'exclusive': 0, 'time_update': 1, 'time_real': 1}), (129, 1, {'queue': 0, 'exclusive': 0, 'time_update': 1, 'time_real': 1})]
  387. )
  388. )
  389. ]
  390. ), ('mididings', 129,
  391. [
  392. ('in_1', 0,
  393. (
  394. [],
  395. [(128, 0, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0})]
  396. )
  397. ),
  398. ('out_1', 1,
  399. (
  400. [(128, 0, {'queue': 0, 'exclusive': 0, 'time_update': 1, 'time_real': 1})],
  401. []
  402. )
  403. )
  404. ]
  405. ), ('Patchage', 130,
  406. [
  407. ('System Announcement Reciever', 0,
  408. (
  409. [],
  410. [(0, 1, {'queue': 0, 'exclusive': 0, 'time_update': 0, 'time_real': 0})]
  411. )
  412. )
  413. ]
  414. ), ('pyalsa-10554', 131,
  415. [
  416. ]
  417. )
  418. ]
  419. """