_soundchip_utils.tcl 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. # Several utility procs for usage in other scripts
  2. # don't export anything, just use it from the namespace,
  3. # because these scripts aren't useful for console users
  4. # and should therefore not be exported to the global
  5. # namespace.
  6. #
  7. # These procs are specifically for sound chips.
  8. #
  9. # Born to prevent duplication between scripts for common stuff.
  10. namespace eval soundchip_utils {
  11. proc get_num_channels {soundchip} {
  12. set num 1
  13. while {[info exists ::${soundchip}_ch${num}_mute]} {
  14. incr num
  15. }
  16. expr {$num - 1}
  17. }
  18. # It is advised to cache the result of this proc for each channel of each sound device
  19. # before using it, because then a lot will be pre-evaluated and at run time only the
  20. # actual variable stuff will be evaluated.
  21. # We cannot cache it here, because we don't know the names of the sound chips in the
  22. # machine you are going to use this on.
  23. # @param soundchip the name of the soundchip as it appears in the output of
  24. # "machine_info sounddevice"
  25. # @channel the channel for which you want the expression to get the volume, the
  26. # first channel is channel 0 and the channels are the ones as they are output
  27. # by the record_channels command.
  28. # @return expression to calculate the volume of the device for that channel in
  29. # range [0-1]; returns just 'x' in case the chip is not supported.
  30. # @todo:
  31. # - frequency expressions for (some of?) the drum channels are not correct
  32. # - implement volume for MoonSound FM (tricky stuff)
  33. # - actually, don't use regs to calc volume but actual wave data (needs openMSX
  34. # changes)
  35. #
  36. proc get_volume_expr {soundchip channel} {
  37. switch [machine_info sounddevice $soundchip] {
  38. "PSG" {
  39. set regs "\"${soundchip} regs\""
  40. return "set keybits \[debug read $regs 7\]; set val \[debug read $regs [expr {$channel + 8}]\]; expr {(\$val & 0x10) ? 1.0 : ((\$val & 0xF) / 15.0) * !((\$keybits >> $channel) & (\$keybits >> [expr {$channel + 3}]) & 1)}"
  41. }
  42. "MoonSound wave-part" {
  43. set regs "\"${soundchip} regs\""
  44. return "expr {((\[debug read $regs [expr {$channel + 0x68}]\]) >> 7) ? (127 - (\[debug read $regs [expr {$channel + 0x50}]\] >> 1)) / 127.0 : 0.0}";
  45. }
  46. "Konami SCC" -
  47. "Konami SCC+" {
  48. set regs "\"${soundchip} SCC\""
  49. return "expr {((\[debug read $regs [expr {$channel + 0xAA}]\] &0xF)) / 15.0 * ((\[debug read $regs 0xAF\] >> $channel) &1)}"
  50. }
  51. "MSX-MUSIC" {
  52. set regs "\"${soundchip} regs\""
  53. set vol_expr "(\[debug read $regs [expr {$channel + 0x30}]\] & 0x0F)"
  54. set keyon_expr "(\[debug read $regs [expr {$channel + 0x20}]\] & 0x10)"
  55. set music_mode_expr "$keyon_expr ? ((15 - $vol_expr) / 15.0) : 0.0"
  56. set rhythm_expr "\[debug read $regs 0x0E\]"
  57. if {$channel < 6} {
  58. # always melody channel
  59. return "expr {$music_mode_expr}"
  60. } elseif {$channel < 9} {
  61. # melody channel when not in rhythm mode
  62. return "expr {($rhythm_expr & 0x20) ? 0.0 : $music_mode_expr}"
  63. } elseif {$channel < 14} {
  64. # rhythm channel (when rhythm mode enabled)
  65. if {$channel < 12} {
  66. set vol_expr "(\[debug read $regs [expr {$channel + 0x30 - 3}]\] & 0x0F)"
  67. } else {
  68. set vol_expr "(\[debug read $regs [expr {$channel + 0x2E - 3}]\] >> 4)"
  69. }
  70. switch $channel {
  71. 9 {set mask 0x30} ;# BD
  72. 10 {set mask 0x28} ;# SD
  73. 11 {set mask 0x22} ;# T-CYM
  74. 12 {set mask 0x21} ;# HH
  75. 13 {set mask 0x24} ;# TOM
  76. }
  77. return "expr {($rhythm_expr & $mask) ? (15 - $vol_expr) / 15.0 : 0.0}"
  78. } else {
  79. error "Unknown channel: $channel for $soundchip!"
  80. }
  81. }
  82. "MSX-AUDIO" {
  83. set regs "\"${soundchip} regs\""
  84. set ofst [expr {0x43 + $channel + 5 * ($channel / 3)}]
  85. set keyon_expr "(\[debug read $regs [expr {$channel + 0xB0}]\] & 0x20)"
  86. set vol_expr {(63 - (\[debug read $regs $ofst\] & 63)) / 63.0}
  87. set music_mode_expr "($keyon_expr ? [subst $vol_expr] : 0.0)"
  88. set rhythm_expr "\[debug read $regs 0xBD\]"
  89. if {$channel < 6} {
  90. # always melody channel
  91. return "expr {$music_mode_expr}"
  92. } elseif {$channel < 9} {
  93. # melody channel when not in rhythm mode
  94. return "expr {($rhythm_expr & 0x20) ? 0.0 : $music_mode_expr}"
  95. } elseif {$channel < 14} {
  96. # rhythm channel (when rhythm mode enabled)
  97. switch $channel {
  98. 9 {set mask 0x30; set ofst 0x53} ;# BD (slot 16)
  99. 10 {set mask 0x28; set ofst 0x54} ;# SD (slot 17)
  100. 11 {set mask 0x22; set ofst 0x55} ;# CYM (slot 18)
  101. 12 {set mask 0x21; set ofst 0x51} ;# HH (slot 13)
  102. 13 {set mask 0x24; set ofst 0x52} ;# TOM (slot 14)
  103. }
  104. return "expr {($rhythm_expr & $mask) ? [subst $vol_expr] : 0.0}"
  105. } elseif {$channel < 15} {
  106. # ADPCM
  107. # can we output 0 when no sample is playing?
  108. return "expr {\[debug read $regs 0x12\] / 255.0}"
  109. } else {
  110. error "Unknown channel: $channel for $soundchip!"
  111. }
  112. }
  113. "DCSG" {
  114. set regs "\"${soundchip} regs\""
  115. set addr [expr {$channel*3 + 2}]
  116. if {${channel} == 3} {
  117. incr addr -1
  118. }
  119. return "expr {(15 - \[debug read $regs $addr\]) / 15.0}"
  120. }
  121. default {
  122. return "x"
  123. }
  124. }
  125. }
  126. # It is advised to cache the result of this proc for each channel of each sound device
  127. # before using it, because then a lot will be pre-evaluated and at run time only the
  128. # actual variable stuff will be evaluated.
  129. # We cannot cache it here, because we don't know the names of the sound chips in the
  130. # machine you are going to use this on.
  131. # @param soundchip the name of the soundchip as it appears in the output of
  132. # "machine_info sounddevice"
  133. # @channel the channel for which you want the expression to get the frequency, the
  134. # first channel is channel 0 and the channels are the ones as they are output
  135. # by the record_channels command.
  136. # @return expression to calculate the frequency of the device for that channel in
  137. # Hz; returns just 'x' in case the chip is not supported.
  138. # @todo:
  139. # - implement frequency for MoonSound
  140. #
  141. proc get_frequency_expr {soundchip channel} {
  142. switch [machine_info sounddevice $soundchip] {
  143. "PSG" {
  144. set regs "\"${soundchip} regs\""
  145. set basefreq [expr {3579545.454545 / 32.0}]
  146. return "set val \[expr {\[debug read $regs \[expr {0 + ($channel * 2)}\]\] + 256 * ((\[debug read $regs \[expr {1 + ($channel * 2)}\]\]) & 15)}\]; expr {$basefreq/(\$val < 1 ? 1 : \$val)}"
  147. }
  148. "Konami SCC" -
  149. "Konami SCC+" {
  150. set regs "\"${soundchip} SCC\""
  151. set basefreq [expr {3579545.454545 / 32.0}]
  152. return "set val \[expr {\[debug read $regs \[expr {0xA0 + 0 + ($channel * 2)}\]\] + 256 * ((\[debug read $regs \[expr {0xA0 + 1 + ($channel * 2)}\]\]) & 15)}\]; expr {$basefreq/(\$val < 1 ? 1 : \$val)}"
  153. }
  154. "MSX-MUSIC" {
  155. set regs "\"${soundchip} regs\""
  156. set basefreq [expr {3579545.454545 / 72.0}]
  157. set factor [expr {$basefreq / (1 << 18)}]
  158. if {$channel >= 9} {
  159. #drums
  160. incr channel -3
  161. }
  162. return "expr {(\[debug read $regs [expr {$channel + 0x10}]\] + 256 * ((\[debug read $regs [expr {$channel + 0x20}]\]) & 1)) * $factor * (1 << (((\[debug read $regs [expr {$channel + 0x20}]\]) & 15) >> 1))}"
  163. }
  164. "MSX-AUDIO" {
  165. set regs "\"${soundchip} regs\""
  166. set basefreq [expr {3579545.454545 / 72.0}]
  167. if {$channel == 14} { ;# ADPCM
  168. set factor [expr {$basefreq / (1 << 16)}]
  169. return "expr {(\[debug read $regs 0x10\] + 256 * \[debug read $regs 0x11\]) * $factor / 10}";# /10 is just to make it fall a bit into a decent range...
  170. } else {
  171. set factor [expr {$basefreq / (1 << 19)}]
  172. if {$channel >= 9} {
  173. #drums
  174. incr channel -3
  175. }
  176. return "expr {(\[debug read $regs [expr {$channel + 0xA0}]\] + 256 * ((\[debug read $regs [expr {$channel + 0xB0}]\]) & 3)) * $factor * (1 << (((\[debug read $regs [expr {$channel + 0xB0}]\]) & 31) >> 2))}"
  177. }
  178. }
  179. "DCSG" {
  180. set regs "\"${soundchip} regs\""
  181. set basefreq [expr {(3579545.454545 / 8.0) / 2.0}]
  182. set addr [expr {$channel*3}]
  183. set next_addr [expr {$addr + 1}]
  184. if {${channel} == 3} {
  185. # noise channel not supported
  186. return "x"
  187. } else {
  188. return "set period \[expr {\[debug read $regs $addr\] + (\[debug read $regs $next_addr\] << 4)}\]; if {\$period == 0} {set period 1024}; expr {$basefreq / (2*\$period)}"
  189. }
  190. }
  191. default {
  192. return "x"
  193. }
  194. }
  195. }
  196. namespace export get_num_channels
  197. namespace export get_volume_expr
  198. namespace export get_frequency_expr
  199. } ;# namespace soundchip_utils
  200. # Don't import in global namespace, these are only useful in other scripts.