Picadae hardware and control code
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.

picadae_api.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import os,sys,argparse,yaml,types,select,serial,logging,time,datetime
  2. from enum import Enum
  3. from multiprocessing import Process, Pipe
  4. # Message header id's for messages passed between the application
  5. # process and the microcontroller and video processes
  6. class TinyOp(Enum):
  7. setPwmOp = 0
  8. noteOnVelOp = 1
  9. noteOnUsecOp = 2
  10. noteOffOp = 3
  11. setReadAddr = 4
  12. writeOp = 5
  13. writeTableOp = 6
  14. invalidOp = 7
  15. class TinyRegAddr(Enum):
  16. kRdRegAddrAddr = 0
  17. kRdTableAddrAddr = 1
  18. kRdEEAddrAddr = 2
  19. kRdSrcAddr = 3
  20. kWrRegAddrAddr = 4
  21. kWrTableAddrAddr = 5
  22. kWrEEAddrAddr = 6
  23. kWrDstAddr = 7
  24. kTmrCoarseAddr = 8
  25. kTmrFineAddr = 9
  26. kTmrPrescaleAddr = 10
  27. kPwmDutyAddr = 11
  28. kPwmFreqAddr = 12
  29. kPwmDivAddr = 13
  30. kStateAddr = 14
  31. kErrorCodeAddr = 15
  32. class TinyConst(Enum):
  33. kRdRegSrcId = TinyRegAddr.kRdRegAddrAddr.value # 0
  34. kRdTableSrcId = TinyRegAddr.kRdTableAddrAddr.value # 1
  35. kRdEESrcId = TinyRegAddr.kRdEEAddrAddr.value # 2
  36. kWrRegDstId = TinyRegAddr.kWrRegAddrAddr.value # 4
  37. kWrTableDstId = TinyRegAddr.kWrTableAddrAddr.value # 5
  38. kWrEEDstId = TinyRegAddr.kWrEEAddrAddr.value # 6
  39. kWrAddrFl = 0x08 # first avail bit above kWrEEAddr
  40. class SerialMsgId(Enum):
  41. QUIT_MSG = 0xffff
  42. DATA_MSG = 0xfffe
  43. class Result(object):
  44. def __init__( self, value=None, msg=None ):
  45. self.value = value
  46. self.msg = msg
  47. def set_error( self, msg ):
  48. if self.msg is None:
  49. self.msg = ""
  50. self.msg += " " + msg
  51. def __bool__( self ):
  52. return self.msg is None
  53. def _serial_process_func( serial_dev, baud, pipe ):
  54. reset_N = 0
  55. drop_N = 0
  56. noSync_N = 0
  57. with serial.Serial(serial_dev, baud) as port:
  58. while True:
  59. # get the count of available bytes in the serial port buffer
  60. bytes_waiting_N = port.in_waiting
  61. # if no serial port bytes are available then sleep ....
  62. if bytes_waiting_N == 0:
  63. time.sleep(0.01) # ... for 10 ms
  64. else: # read the serial port ...
  65. v = port.read(bytes_waiting_N)
  66. pipe.send((SerialMsgId.DATA_MSG,v)) # ... and send it to the parent
  67. msg = None
  68. if pipe.poll(): # non-blocking check for parent process messages
  69. try:
  70. msg = pipe.recv()
  71. except EOFError:
  72. break
  73. # if an incoming message was received
  74. if msg != None:
  75. # this is a shutdown msg
  76. if msg[0] == SerialMsgId.QUIT_MSG:
  77. pipe.send(msg) # ... send quit msg back
  78. break
  79. # this is a data xmit msg
  80. elif msg[0] == SerialMsgId.DATA_MSG:
  81. port.write(msg[1])
  82. class SerialProcess(Process):
  83. def __init__(self,serial_dev,serial_baud):
  84. self.parent_end, child_end = Pipe()
  85. super(SerialProcess, self).__init__(target=_serial_process_func, name="Serial", args=(serial_dev,serial_baud,child_end,))
  86. self.doneFl = False
  87. def quit(self):
  88. # send quit msg to the child process
  89. self.parent_end.send((SerialMsgId.QUIT_MSG,0))
  90. def send(self,msg_id,value):
  91. # send a msg to the child process
  92. self.parent_end.send((msg_id,value))
  93. return Result()
  94. def recv(self):
  95. #
  96. x = None
  97. if not self.doneFl and self.parent_end.poll():
  98. x = self.parent_end.recv()
  99. if x[0] == SerialMsgId.QUIT_MSG:
  100. self.doneFl = True
  101. return x
  102. def is_done(self):
  103. return self.doneFl
  104. class Picadae:
  105. def __init__( self, key_mapL, i2c_base_addr=21, serial_dev='/dev/ttyACM0', serial_baud=38400, prescaler_usec=16 ):
  106. """
  107. key_mapL = [{ index, board, ch, type, midi, class }]
  108. serial_dev = /dev/ttyACM0
  109. serial_baud = 38400
  110. i2c_base_addr = 1
  111. """
  112. self.serialProc = SerialProcess( serial_dev, serial_baud )
  113. self.keyMapD = { d['midi']:d for d in key_mapL }
  114. self.i2c_base_addr = i2c_base_addr
  115. self.prescaler_usec = prescaler_usec
  116. self.log_level = 0
  117. self.serialProc.start()
  118. def close( self ):
  119. self.serialProc.quit()
  120. def wait_for_serial_sync(self, timeoutMs=10000):
  121. # wait for the letter 'a' to come back from the serial port
  122. result = self.block_on_serial_read(1,timeoutMs)
  123. if result and len(result.value)>0 and result.value[0] == ord('a'):
  124. pass
  125. else:
  126. result.set_error("Serial sync failed.")
  127. return result
  128. def write( self, i2c_addr, reg_addr, byteL ):
  129. return self._send( 'w', i2c_addr, reg_addr, [ len(byteL) ] + byteL )
  130. def call_op( self, midi_pitch, op_code, argL ):
  131. return self.write( self._pitch_to_i2c_addr( midi_pitch ), op_code, argL )
  132. def set_read_addr( self, i2c_addr, mem_id, addr ):
  133. return self. write(i2c_addr, TinyOp.setReadAddr.value,[ mem_id, addr ])
  134. def read_request( self, i2c_addr, reg_addr, byteOutN ):
  135. return self._send( 'r', i2c_addr, reg_addr,[ byteOutN ] )
  136. def block_on_serial_read( self, byteOutN, time_out_ms=250 ):
  137. ts = datetime.datetime.now() + datetime.timedelta(milliseconds=time_out_ms)
  138. retL = []
  139. while datetime.datetime.now() < ts and len(retL) < byteOutN:
  140. # If a value is available at the serial port return is otherwise return None.
  141. x = self.serialProc.recv()
  142. if x is not None and x[0] == SerialMsgId.DATA_MSG:
  143. for b in x[1]:
  144. retL.append(int(b))
  145. time.sleep(0.01)
  146. result = Result(value=retL)
  147. if len(retL) < byteOutN:
  148. result.set_error("Serial port time out on read.")
  149. return result
  150. def block_on_picadae_read( self, midi_pitch, mem_id, reg_addr, byteOutN, time_out_ms=250 ):
  151. i2c_addr = self._pitch_to_i2c_addr( midi_pitch )
  152. result = self.set_read_addr( i2c_addr, mem_id, reg_addr )
  153. if result:
  154. result = self.read_request( i2c_addr, TinyOp.setReadAddr.value, byteOutN )
  155. if result:
  156. result = self.block_on_serial_read( byteOutN, time_out_ms )
  157. return result
  158. def block_on_picadae_read_reg( self, midi_pitch, reg_addr, byteOutN=1, time_out_ms=250 ):
  159. return self.block_on_picadae_read( midi_pitch,
  160. TinyRegAddr.kRdRegAddrAddr.value,
  161. reg_addr,
  162. byteOutN,
  163. time_out_ms )
  164. def note_on_vel( self, midi_pitch, midi_vel ):
  165. return self.call_op( midi_pitch, TinyOp.noteOnVelOp.value, [self._validate_vel(midi_vel)] )
  166. def note_on_us( self, midi_pitch, pulse_usec ):
  167. return self.call_op( midi_pitch, TinyOp.noteOnUsecOp.value, list(self._usec_to_coarse_and_fine(pulse_usec)) )
  168. def note_off( self, midi_pitch ):
  169. return self.call_op( midi_pitch, TinyOp.noteOffOp.value,
  170. [0] ) # TODO: sending a dummy byte because we can't handle sending a command with no data bytes.
  171. def set_velocity_map( self, midi_pitch, midi_vel, pulse_usec ):
  172. coarse,fine = self._usec_to_coarse_and_fine( pulse_usec )
  173. src = TinyConst.kWrAddrFl.value | TinyConst.kWrTableDstId.value
  174. addr = midi_vel*2
  175. return self.call_op( midi_pitch, TinyOp.writeOp.value, [ src, addr, coarse, fine ] )
  176. def get_velocity_map( self, midi_pitch, midi_vel, time_out_ms=250 ):
  177. byteOutN = 2
  178. return self.block_on_picadae_read( midi_pitch, TinyConst.kRdTableSrcId.value, midi_vel*2, byteOutN, time_out_ms )
  179. def set_pwm( self, midi_pitch, duty_cycle_pct ):
  180. return self.call_op( midi_pitch, TinyOp.setPwmOp.value, [ int( duty_cycle_pct * 255.0 /100.0 )])
  181. def get_pwm( self, midi_pitch, time_out_ms=250 ):
  182. return self.block_on_picadae_read_reg( midi_pitch, TinyRegAddr.kPwmDutyAddr.value, time_out_ms=time_out_ms )
  183. def get_pwm_freq( self, midi_pitch, time_out_ms=250 ):
  184. return self.block_on_picadae_read_reg( midi_pitch, TinyRegAddr.kPwmFreqAddr.value, time_out_ms=time_out_ms )
  185. def get_pwm_div( self, midi_pitch, time_out_ms=250 ):
  186. return self.block_on_picadae_read_reg( midi_pitch, TinyRegAddr.kPwmDivAddr.value, time_out_ms=time_out_ms )
  187. def write_table( self, midi_pitch, time_out_ms=250 ):
  188. # TODO: sending a dummy byte because we can't handle sending a command with no data bytes.
  189. return self.call_op( midi_pitch, TinyOp.writeTableOp.value,[0])
  190. def make_note( self, midi_pitch, atk_us, dur_ms ):
  191. # TODO: handle error on note_on_us()
  192. self.note_on_us(midi_pitch, atk_us);
  193. time.sleep( dur_ms / 1000.0 )
  194. return self.note_off(midi_pitch)
  195. def make_seq( self, midi_pitch, base_atk_us, dur_ms, delta_us, note_cnt ):
  196. for i in range(note_cnt):
  197. self.make_note( midi_pitch, base_atk_us + i*delta_us, dur_ms )
  198. time.sleep( dur_ms / 1000.0 )
  199. return Result()
  200. def set_log_level( self, log_level ):
  201. self.log_level = log_level
  202. return Result()
  203. def _pitch_to_i2c_addr( self, pitch ):
  204. return self.keyMapD[ pitch ]['index'] + self.i2c_base_addr
  205. def _validate_vel( self, vel ):
  206. return vel
  207. def _usec_to_coarse_and_fine( self, usec ):
  208. coarse_usec = self.prescaler_usec*255 # usec's in one coarse tick
  209. coarse = int( usec / coarse_usec )
  210. fine = int((usec - coarse*coarse_usec) / self.prescaler_usec)
  211. assert( coarse <= 255 )
  212. assert( fine <= 255)
  213. print("C:%i F:%i : %i " % (coarse,fine, coarse*coarse_usec + fine*self.prescaler_usec ))
  214. return coarse,fine
  215. def _send( self, opcode, i2c_addr, reg_addr, byteL ):
  216. self._print( opcode, i2c_addr, reg_addr, byteL )
  217. byteA = bytearray( [ord(opcode), i2c_addr, reg_addr ] + byteL )
  218. return self.serialProc.send(SerialMsgId.DATA_MSG, byteA )
  219. def _print( self, opcode, i2c_addr, reg_addr, byteL ):
  220. if self.log_level:
  221. s = "{} {} {}".format( opcode, i2c_addr, reg_addr )
  222. for x in byteL:
  223. s += " {}".format(x)
  224. print(s)