Picadae hardware and control code
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

picadae_api.py 14KB

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