Export Hoot memory dumps to VOPM instruments
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.

235 lignes
6.8KB

  1. #!/usr/bin/python3
  2. import argparse, collections, sys
  3. # MiOPMdrv sound bank Paramer Ver2002.04.22
  4. # LFO: LFRQ AMD PMD WF NFRQ
  5. # @:[Num] [Name]
  6. # CH: PAN FL CON AMS PMS SLOT NE
  7. # [OPname]: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
  8. OpNames = ["M1","C1","M2","C2"]
  9. OpIdx = [0,2,1,3]
  10. def Error(*args):
  11. print(sys.argv[0]+":", *args, file=sys.stderr)
  12. sys.exit(1)
  13. def PrintChannel(pan, fl, con, ams, pms, slot, ne):
  14. print("CH:", pan, fl, con, ams, pms, slot, ne)
  15. def PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen):
  16. print(OpNames[op] + ":", ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
  17. class Operator:
  18. def __init__(self, num):
  19. self.num = num
  20. for n in "ar d1r d2r rr d1l tl ks mul dt1 dt2 amsen".split():
  21. setattr(self, n, 0)
  22. def setDT1Mul(self, b):
  23. self.dt1, self.mul = (b & 0b01110000) >> 4, (b & 0b00001111)
  24. def setKSAR(self, b):
  25. self.ks, self.ar = (b & 0b11000000) >> 6, (b & 0b00011111)
  26. def setAmsenD1R(self, b):
  27. self.amsen, self.d1r = (b & 0b10000000) >> 7, (b & 0b00011111)
  28. def setDT2D2R(self, b):
  29. self.dt2, self.d2r = (b & 0b11000000) >> 6, (b & 0b00011111)
  30. def setD1LRR(self, b):
  31. self.d1l, self.rr = (b & 0b11110000) >> 4, (b & 0b00001111)
  32. class Instrument:
  33. def __init__(self, num, name):
  34. self.num = num
  35. self.name = name
  36. self.slot = 120
  37. self.pan = 64
  38. self.ne = 0
  39. for n in "fl con ams pms".split():
  40. setattr(self, n, 0)
  41. self.ops = [Operator(i) for i in range(4)]
  42. def render(self):
  43. print("@:%d %s" % (self.num, self.name))
  44. print("LFO: 0 0 0 0 0")
  45. PrintChannel(self.pan, self.fl, self.con, self.ams, self.pms, self.slot, self.ne)
  46. for o in self.ops:
  47. PrintOp(o.num, o.ar, o.d1r, o.d2r, o.rr, o.d1l, o.tl, o.ks, o.mul, o.dt1, o.dt2, o.amsen)
  48. def setFlCon(self, b):
  49. self.fl, self.con = (b & 0b00111000) >> 3, (b & 0b00000111)
  50. def setAMSPMS(self, b):
  51. self.ams, self.pms = (b & 0b00110000) >> 4, (b & 0b00000111)
  52. # """
  53. def __hash__(self):
  54. get = lambda n, d = 1: [getattr(self.ops[x], n) // d for x in range(4)]
  55. data = bytes([self.con] + get("tl", 4))
  56. #print(data, data.__hash__())
  57. return data.__hash__()
  58. def __eq__(self, other):
  59. return self.__hash__() == other.__hash__()
  60. # """
  61. class RecordGrabber:
  62. def __init__(self, fields, recordSize, **options):
  63. self.fields = fields
  64. self.recordSize = recordSize
  65. self.instStep = 1
  66. for k, v in options:
  67. setattr(self, k, v)
  68. def __call__(self,data):
  69. result = []
  70. f = self.fields
  71. for n in range(0, len(data) // self.recordSize):
  72. inst = Instrument(n, ("Ins%d" % n) if "name" not in f else
  73. f["name"](data, ofs))
  74. ofs = n * self.recordSize
  75. get = lambda s: data[ofs + f[s]] if s in f else 0
  76. inst.setFlCon(get("flcon"))
  77. inst.setAMSPMS(get("amspms"))
  78. for o in range(4):
  79. getOp = lambda s: data[ofs + f[s] + OpIdx[o]] if s in f else 0
  80. op = inst.ops[o]
  81. op.setDT1Mul(getOp("dt1mul"))
  82. op.tl = getOp("tl")
  83. op.setKSAR(getOp("ksar"))
  84. op.setAmsenD1R(getOp("amsend1r"))
  85. op.setDT2D2R(getOp("dt2d2r"))
  86. op.setD1LRR(getOp("d1lrr"))
  87. #inst.render()
  88. result.append(inst)
  89. #print(inst.__hash__())
  90. return result
  91. def PrintOPM(data):
  92. result = []
  93. for i in range(8):
  94. inst = Instrument(i, ("Ins%d" % i))
  95. inst.ne = (data[0xf] & 0b10000000) >> 7
  96. meta = data[0x20 + i : 0x3f + i : 8]
  97. inst.setFlCon(meta[0])
  98. inst.setAMSPMS(meta[3])
  99. for o in range(4):
  100. op = inst.ops[o]
  101. get = lambda x: data[x + i + OpIdx[o]*8]
  102. op.setDT1Mul(get(0x40))
  103. op.tl = get(0x60)
  104. op.setKSAR(get(0x80))
  105. op.setAmsenD1R(get(0xa0))
  106. op.setDT2D2R(get(0xc0))
  107. op.setD1LRR(get(0xe0))
  108. return result
  109. def PrintOPNBasic(data, opna = False):
  110. result = []
  111. # SR in opn parlance is equivalent to D2R in opm, thanks yamaha
  112. # likewise, SL -> D1L and DR -> D1R
  113. ofs = 0x100 if opna else 0
  114. for i in range(3):
  115. n = i + (3 if opna else 0)
  116. inst = Instrument(n, ("Ins%d" % n))
  117. inst.setFlCon(data[ofs + 0xb0 + i])
  118. inst.setAMSPMS(data[ofs + 0xb4 + i])
  119. for o in range(4):
  120. op = inst.ops[o]
  121. get = lambda x: data[ofs + x + i + OpIdx[o]*4]
  122. op.getDT1Mul(get(0x30))
  123. op.tl = get(0x40)
  124. op.getKSAR(get(0x50))
  125. op.getAmsenD1R(get(0x60))
  126. op.getDT2D2R(get(0x70))
  127. op.getD1LRR(get(0x80))
  128. result.append(inst)
  129. return result
  130. def PrintOPN(data):
  131. PrintOPNBasic(data)
  132. def PrintOPNA(data):
  133. PrintOPNBasic(data)
  134. PrintOPNBasic(data, True)
  135. TypeFuncs = {
  136. "opn": PrintOPN,
  137. "opna": PrintOPNA,
  138. "opm": PrintOPM,
  139. "raw": RecordGrabber({
  140. "flcon": 0x18,
  141. "amspms": 0x19,
  142. "dt1mul": 0x0,
  143. "tl": 0x04,
  144. "ksar": 0x08,
  145. "amsend1r": 0x0c,
  146. "dt2d2r": 0x10,
  147. "d1lrr": 0x14
  148. }, 0x20),
  149. "solfeace": RecordGrabber({
  150. "flcon": 0x00,
  151. "dt1mul": 0x02,
  152. "tl": 0x06,
  153. "ksar": 0x0a,
  154. "amsend1r": 0x0e,
  155. "dt2d2r": 0x12,
  156. "d1lrr": 0x16,
  157. "name": lambda data, ofs: data[ofs + 0x1a : ofs + 0x20].decode("shift-jis")
  158. }, 0x20)
  159. }
  160. Parser = argparse.ArgumentParser(
  161. prog="hootvopm",
  162. description="Convert Yamaha OPM/OPN data to VOPM instruments")
  163. Parser.add_argument("file", help="file to read (- for stdin)")
  164. Parser.add_argument("-b", "--binary", dest="binary", action="store_true",
  165. help="read binary output instead of comma-separated hex")
  166. Parser.add_argument("-t", "--type", dest="type", action="store",
  167. default="raw", choices=TypeFuncs.keys(),
  168. help="chip/format type (default: raw)")
  169. Args = Parser.parse_args()
  170. Filename = Args.file
  171. DataFile = open(Filename,"r" + ("b" if Args.binary else "")) if Filename != "-" else sys.stdin
  172. Data = [] if not Args.binary else DataFile.read()
  173. if not Args.binary:
  174. for line in DataFile.readlines():
  175. for byte in line.strip().split(",")[:16]:
  176. Data.append(int(byte,16))
  177. print("// Exported by hootvopm : https://git.lain.church/whut/hootvopm")
  178. print("// Chip type:", Args.type)
  179. Instruments = set(TypeFuncs[Args.type.lower()](Data))
  180. #print(len(Instruments))
  181. Instruments = sorted(list(Instruments), key = lambda x: x.num)
  182. count = 0
  183. for i in Instruments:
  184. i.num = count
  185. i.render()
  186. count += 1