_record_channels.tcl 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. namespace eval record_channels {
  2. set_help_text record_channels \
  3. {Convenience function to control recording of individual channels of
  4. sounddevice(s).
  5. There are three subcommands (start, stop and list) to respectively start
  6. recording additional channels, to stop recording all or some channels and
  7. to list which channels are currently being recorded.
  8. record_channels [start] [<device> [<channels>]]
  9. record_channels stop [<device> [<channels>]]
  10. record_channels list
  11. When starting recording, you can optionally specify a prefix for the
  12. destination file names with the -prefix option.
  13. Some examples will make it much clearer:
  14. - To start recording:
  15. record_channels start PSG record all PSG channels
  16. record_channels PSG the 'start' keyword can be left out
  17. record_channels SCC 1,3-5 only record channels 1 and 3 to 5
  18. record_channels SCC PSG 1 record all SCC channels + PSG channel 1
  19. record_channels all record all channels of all devices
  20. record_channels all -prefix t record all channels of all devices using
  21. prefix 't'
  22. - To stop recording
  23. record_channels stop stop all recording
  24. record_channels stop PSG stop recording all PSG channels
  25. record_channels stop SCC 3,5 stop recording SCC channels 3 and 5
  26. - To show the current status
  27. record_channels list shows which channels are being recorded
  28. }
  29. set mute_help_text \
  30. {Convenience function to control (un)muting of individual channels of
  31. soundevice(s).
  32. Examples:
  33. mute_channels PSG mute all PSG channels
  34. mute_channels SCC 2,4 mute SCC channels 2 and 4
  35. unmute_channels PSG 1 SCC 3-5 unmute PSG channel 1, SCC channels 3 to 5
  36. mute_channels show which channels are currently muted
  37. unmute_channels unmute all channels on all devices
  38. solo PSG 2 mute everything except PSG channel 2
  39. }
  40. set_help_text mute_channels $mute_help_text
  41. set_help_text unmute_channels $mute_help_text
  42. set_help_text solo $mute_help_text
  43. set_tabcompletion_proc record_channels [namespace code tab_sounddevice_channels]
  44. set_tabcompletion_proc mute_channels [namespace code tab_sounddevice_channels]
  45. set_tabcompletion_proc unmute_channels [namespace code tab_sounddevice_channels]
  46. set_tabcompletion_proc solo [namespace code tab_sounddevice_channels]
  47. proc tab_sounddevice_channels {args} {
  48. set result [machine_info sounddevice]
  49. if {([lindex $args 0] eq "record_channels") && ([llength $args] == 2)} {
  50. set result [concat $result "start stop list all"]
  51. }
  52. return $result
  53. }
  54. proc parse_channel_numbers {str} {
  55. set result [list]
  56. foreach a [split $str ", "] {
  57. set b [split $a "-"]
  58. foreach c $b {
  59. if {![string is integer $c]} {
  60. error "Not an integer: $c"
  61. }
  62. }
  63. switch [llength $b] {
  64. 0 {}
  65. 1 {lappend result [lindex $b 0]}
  66. 2 {for {set i [lindex $b 0]} {$i <= [lindex $b 1]} {incr i} {
  67. lappend result $i
  68. }}
  69. default {error "Invalid range: $a"}
  70. }
  71. }
  72. return [lsort -unique $result]
  73. }
  74. proc get_all_channels {device} {
  75. set i 1
  76. set channels [list]
  77. while {[info exists ::${device}_ch${i}_record]} {
  78. lappend channels $i
  79. incr i
  80. }
  81. return $channels
  82. }
  83. proc get_all_devices_all_channels {} {
  84. set result [list]
  85. foreach device [machine_info sounddevice] {
  86. lappend result $device [get_all_channels $device]
  87. }
  88. return $result
  89. }
  90. proc get_recording_channels {} {
  91. set result [list]
  92. set sounddevices [machine_info sounddevice]
  93. foreach device $sounddevices {
  94. set active [list]
  95. foreach ch [get_all_channels $device] {
  96. set var ::${device}_ch${ch}_record
  97. if {[set $var] ne ""} {
  98. lappend active $ch
  99. }
  100. }
  101. if {[llength $active]} {
  102. lappend result "$device: $active"
  103. }
  104. }
  105. return $result
  106. }
  107. proc get_muted_channels {} {
  108. set result [list]
  109. set sounddevices [machine_info sounddevice]
  110. foreach device $sounddevices {
  111. set active [list]
  112. foreach ch [get_all_channels $device] {
  113. set var ::${device}_ch${ch}_mute
  114. if {[set $var]} {
  115. lappend active $ch
  116. }
  117. }
  118. if {[llength $active]} {
  119. lappend result "$device: $active"
  120. }
  121. }
  122. return $result
  123. }
  124. proc parse_device_channels {tokens} {
  125. set sounddevices [machine_info sounddevice]
  126. set device_channels [list]
  127. if {[lindex $tokens 0] == "all"} {
  128. foreach device $sounddevices {
  129. lappend device_channels $device [get_all_channels $device]
  130. }
  131. return $device_channels
  132. }
  133. while {[llength $tokens]} {
  134. set device [lindex $tokens 0]
  135. set tokens [lrange $tokens 1 end]
  136. if {$device ni $sounddevices} {
  137. error "Unknown sounddevice: $device"
  138. }
  139. set range [lindex $tokens 0]
  140. if {($range ne "") && ($range ni $sounddevices)} {
  141. set channels [parse_channel_numbers $range]
  142. set tokens [lrange $tokens 1 end]
  143. foreach ch $channels {
  144. if {![info exists ::${device}_ch${ch}_record]} {
  145. error "No channel $ch on sounddevice $device"
  146. }
  147. }
  148. } else {
  149. set channels [get_all_channels $device]
  150. }
  151. lappend device_channels $device $channels
  152. }
  153. return $device_channels
  154. }
  155. proc record_channels {args} {
  156. set start true
  157. set device_channels [list]
  158. # parse subcommand (default is start)
  159. set first [lindex $args 0]
  160. switch $first {
  161. list {
  162. return [join [get_recording_channels] "\n"]
  163. }
  164. start -
  165. stop {
  166. set start [string equal $first "start"]
  167. set args [lrange $args 1 end]
  168. }
  169. }
  170. if {$start} {
  171. set prefix [utils::filename_clean [guess_title]]
  172. # see if there's a -prefix option to override the default
  173. set prefix_index [lsearch -exact $args "-prefix"]
  174. if {$prefix_index >= 0 && $prefix_index < ([llength $args] - 1)} {
  175. set prefix [lindex $args [expr {$prefix_index + 1}]]
  176. set args [lreplace $args $prefix_index [expr {$prefix_index + 1}]]
  177. }
  178. }
  179. # parse devices/channels
  180. set device_channels [parse_device_channels $args]
  181. # stop without any further arguments -> stop all
  182. if {!$start && ![llength $device_channels]} {
  183. foreach device [machine_info sounddevice] {
  184. set channels [get_all_channels $device]
  185. lappend device_channels $device $channels
  186. }
  187. }
  188. set retval ""
  189. # actually start/stop recording
  190. foreach {device channels} $device_channels {
  191. foreach ch $channels {
  192. set var ::${device}_ch${ch}_record
  193. if {$start} {
  194. set directory [file normalize $::env(OPENMSX_USER_DATA)/../soundlogs]
  195. # create dir always
  196. file mkdir $directory
  197. set software_section $prefix
  198. if {$software_section ne ""} {
  199. set software_section "${software_section}-"
  200. }
  201. set $var [utils::get_next_numbered_filename $directory "${software_section}${device}-ch${ch}_" ".wav"]
  202. append retval "Recording $device channel $ch to [set $var]...\n"
  203. } else {
  204. if {[set $var] ne ""} {
  205. append retval "Stopped recording $device channel $ch to [set $var]...\n"
  206. }
  207. set $var ""
  208. }
  209. }
  210. }
  211. return $retval
  212. }
  213. proc do_mute_channels {device_channels state} {
  214. foreach {device channels} $device_channels {
  215. foreach ch $channels {
  216. set ::${device}_ch${ch}_mute $state
  217. }
  218. }
  219. }
  220. proc mute_channels {args} {
  221. # parse devices/channels
  222. set device_channels [parse_device_channels $args]
  223. # no argumnets specified, list muted channels
  224. if {![llength $device_channels]} {
  225. return [join [get_muted_channels] "\n"]
  226. }
  227. # actually mute channels
  228. do_mute_channels $device_channels true
  229. }
  230. proc unmute_channels {args} {
  231. # parse devices/channels
  232. set device_channels [parse_device_channels $args]
  233. # no arguments specified, unmute all channels
  234. if {![llength $device_channels]} {
  235. set device_channels [get_all_devices_all_channels]
  236. }
  237. #actually unmute channels
  238. do_mute_channels $device_channels false
  239. }
  240. proc solo {args} {
  241. # parse devices/channels
  242. set device_channels [parse_device_channels $args]
  243. # mute everything, unmute specified channels
  244. do_mute_channels [get_all_devices_all_channels] true
  245. do_mute_channels $device_channels false
  246. }
  247. namespace export record_channels
  248. namespace export mute_channels
  249. namespace export unmute_channels
  250. namespace export solo
  251. } ;# namspace record_channels
  252. namespace import record_channels::*