_music_keyboard.tcl 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. # TODO:
  2. # - optimize by precalcing the notes for all reg values
  3. # - make a better visualisation if many channels are available (grouping?)
  4. set_help_text toggle_music_keyboard \
  5. {Puts a music keyboard on the On-Screen-Display for each channel and each
  6. supported sound chip. It is not very practical yet if you have many
  7. sound chips in your currently running MSX. The redder a key on the
  8. keyboard is, the louder the note is played. Use the command again to
  9. remove it. Note: it cannot handle run-time insertion/removal of sound
  10. devices. It can handle changing of machines, though. Not all chips are
  11. supported yet and some channels give not so useful note output (but
  12. still it is nice to see something happening).
  13. Note that displaying these keyboard may cause quite some CPU load!}
  14. namespace eval music_keyboard {
  15. # some useful constants
  16. variable note_strings [list "C" "C#" "D" "D#" "E" "F" "F#" "G" "G#" "A" "A#" "B"]
  17. # we define these outside the proc to gain some speed (they are precalculated)
  18. variable loga [expr {log(2 ** (1 / 12.0))}]
  19. variable r3 [expr {log(440.0) / $loga - 57}]
  20. variable keyb_dict
  21. variable note_key_color
  22. variable num_notes
  23. variable machine_switch_trigger_id 0
  24. variable frame_trigger_id 0
  25. proc freq_to_note {freq} {
  26. variable loga
  27. variable r3
  28. expr {($freq < 16) ? -1 : (log($freq) / $loga - $r3)}
  29. }
  30. proc keyboard_init {} {
  31. variable num_notes
  32. variable note_strings
  33. variable note_key_color
  34. variable keyb_dict
  35. variable machine_switch_trigger_id
  36. foreach soundchip [machine_info sounddevice] {
  37. set channel_count [soundchip_utils::get_num_channels $soundchip]
  38. for {set channel 0} {$channel < $channel_count} {incr channel} {
  39. set freq_expr [soundchip_utils::get_frequency_expr $soundchip $channel]
  40. # skip devices/channels which don't have freq expressions (not implemented yet)
  41. if {$freq_expr eq "x"} continue
  42. dict set keyb_dict $soundchip $channel [dict create \
  43. freq_expr $freq_expr \
  44. vol_expr [soundchip_utils::get_volume_expr $soundchip $channel] \
  45. prev_note 0]
  46. }
  47. }
  48. # and now create the visualisation (keyboards)
  49. set num_octaves 9
  50. set num_notes [expr {$num_octaves * 12}]
  51. set key_width 3
  52. set border 1
  53. set yborder 1
  54. set step_white [expr {$key_width + $border}] ;# total width of white keys
  55. set keyboard_height 8
  56. set white_key_height [expr {$keyboard_height - $yborder}]
  57. osd create rectangle music_keyboard -scaled true
  58. set channel_count 0
  59. dict for {soundchip chip_dict} $keyb_dict {
  60. dict for {channel chan_dict} $chip_dict {
  61. osd create rectangle music_keyboard.chip${soundchip}ch${channel} \
  62. -y [expr {$channel_count * $keyboard_height}] \
  63. -w [expr {($num_octaves * 7) * $step_white + $border}] \
  64. -h $keyboard_height \
  65. -rgba 0x101010A0
  66. set nof_blacks 0
  67. for {set note 0} {$note < $num_notes} {incr note} {
  68. set z -1
  69. set xcor 0
  70. if {[string range [lindex $note_strings [expr {$note % 12}]] end end] eq "#"} {
  71. # black key
  72. dict set note_key_color $note 0x000000
  73. set h [expr {round($white_key_height * 0.7)}]
  74. set xcor [expr {($key_width + 1) / 2}]
  75. incr nof_blacks
  76. } else {
  77. # white key
  78. set h $white_key_height
  79. dict set note_key_color $note 0xFFFFFF
  80. set z -2
  81. }
  82. osd create rectangle music_keyboard.chip${soundchip}ch${channel}.key${note} \
  83. -x [expr {($note - $nof_blacks) * $step_white + $border + $xcor}] \
  84. -y 0 -z $z \
  85. -w $key_width -h $h \
  86. -rgb [dict get $note_key_color $note]
  87. }
  88. set next_to_kbd_x [expr {($num_notes - $nof_blacks) * $step_white + $border}]
  89. osd create rectangle music_keyboard.ch${channel}chip${soundchip}infofield \
  90. -x $next_to_kbd_x -y [expr {$channel_count * $keyboard_height}] \
  91. -w [expr {320 - $next_to_kbd_x}] -h $keyboard_height \
  92. -rgba 0x000000A0
  93. osd create text music_keyboard.ch${channel}chip${soundchip}infofield.notetext \
  94. -rgb 0xFFFFFF -size [expr {round($keyboard_height * 0.75)}]
  95. osd create text music_keyboard.ch${channel}chip${soundchip}infofield.chlabel \
  96. -rgb 0x1F1FFF -size [expr {round($keyboard_height * 0.75)}] \
  97. -x 10 -text "[expr {$channel + 1}] ($soundchip)"
  98. incr channel_count
  99. }
  100. }
  101. set machine_switch_trigger_id [after machine_switch [namespace code music_keyboard_reset]]
  102. }
  103. proc update_keyboard {} {
  104. variable keyb_dict
  105. variable num_notes
  106. variable note_strings
  107. variable note_key_color
  108. variable frame_trigger_id
  109. dict for {soundchip chip_dict} $keyb_dict {
  110. dict for {channel chan_dict} $chip_dict {
  111. set freq_expr [dict get $chan_dict freq_expr]
  112. set vol_expr [dict get $chan_dict vol_expr]
  113. set prev_note [dict get $chan_dict prev_note]
  114. set note [expr {round([freq_to_note [eval $freq_expr]])}]
  115. if {$note != $prev_note} {
  116. osd configure music_keyboard.chip${soundchip}ch${channel}.key${prev_note} \
  117. -rgb [dict get $note_key_color $prev_note]
  118. }
  119. if {($note < $num_notes) && ($note > 0)} {
  120. set volume [eval $vol_expr]
  121. set deviation [expr {round(255 * $volume)}]
  122. set color [dict get $note_key_color $note]
  123. set color [expr {($color > 0x808080)
  124. ? ($color - (($deviation << 8) + $deviation))
  125. : ($color + ($deviation << 16))}]
  126. osd configure music_keyboard.chip${soundchip}ch${channel}.key${note} \
  127. -rgb $color
  128. if {$deviation > 0} {
  129. set note_text [lindex $note_strings [expr {$note % 12}]]
  130. } else {
  131. set note_text ""
  132. }
  133. dict set keyb_dict $soundchip $channel prev_note $note
  134. } else {
  135. set note_text ""
  136. }
  137. osd configure music_keyboard.ch${channel}chip${soundchip}infofield.notetext \
  138. -text $note_text
  139. }
  140. }
  141. set frame_trigger_id [after frame [namespace code update_keyboard]]
  142. }
  143. proc music_keyboard_reset {} {
  144. if {![osd exists music_keyboard]} {
  145. error "Please fix a bug in this script!"
  146. }
  147. toggle_music_keyboard
  148. toggle_music_keyboard
  149. }
  150. proc toggle_music_keyboard {} {
  151. variable machine_switch_trigger_id
  152. variable frame_trigger_id
  153. variable keyb_dict
  154. if {[osd exists music_keyboard]} {
  155. after cancel $machine_switch_trigger_id
  156. after cancel $frame_trigger_id
  157. osd destroy music_keyboard
  158. unset keyb_dict
  159. } else {
  160. keyboard_init
  161. update_keyboard
  162. }
  163. return ""
  164. }
  165. namespace export toggle_music_keyboard
  166. } ;# namespace music_keyboard
  167. namespace import music_keyboard::*