You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

416 satır
16KB

  1. #!/usr/local/bin/ruby -wd
  2. # bess -- Translation of Bill Schottstaedt's bess.scm to Ruby.
  3. # Copyright (c) 2002--2009 Michael Scholz <mi-scholz@users.sourceforge.net>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. # 1. Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. #
  15. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  16. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  19. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  21. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  22. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  23. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  24. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  25. # SUCH DAMAGE.
  26. begin require 'rubygems' rescue LoadError end
  27. file = File.basename __FILE__
  28. banner = "This is #{file.upcase} v1.9, (C) 2002--2009 Michael Scholz"
  29. def warn(*args)
  30. str = format(*args) << ($! ? ": #{$!}" : "") << "\n"
  31. str << (($@ and $DEBUG) ? "\n[#{$@.join("\n")}]" : "")
  32. $stdout.print str
  33. $! = nil
  34. end
  35. def die(*args)
  36. warn(*args)
  37. exit 1
  38. end
  39. def rbm_require(lib)
  40. require lib.to_s
  41. rescue ScriptError
  42. die "\aScriptError"
  43. end
  44. 1.upto(15) do |i|
  45. trap(i) do |sig| die("\nSignal #{sig} received. Process #{$$} canceled.") end
  46. end
  47. class Bess
  48. def initialize(banner, file)
  49. @bufsize = 256
  50. @srate = 22050
  51. @chans = 1
  52. @play = 0.0
  53. @freq = 220.0
  54. @fm_index1 = 1.0
  55. @fm_ratio1 = 1
  56. @amp = 0.5
  57. @sliderback = "lightsteelblue"
  58. @background = "lightsteelblue1"
  59. @low_freq = 40.0
  60. @high_freq = 2000.0
  61. @high_index1 = 3.0
  62. @high_ratio = 10
  63. get_options(banner, file)
  64. rbm_require(:libxm)
  65. rbm_require(:sndlib)
  66. set_mus_srate(@srate)
  67. end
  68. def get_options(banner, file)
  69. vers = false
  70. help = false
  71. vers_msg = "#{banner}
  72. #{file.capitalize} comes with ABSOLUTELY NO WARRANTY.
  73. You may redistribute copies of #{file.capitalize}
  74. under the terms of the GNU General Public License.
  75. For more information about these matters, see the file named COPYING."
  76. help_msg = "#{banner}
  77. #{file.capitalize} is a Ruby script working with sndlib.so and
  78. libxm.so which must be in the Ruby library path, for example in
  79. /usr/local/lib/ruby/site_ruby, or the environment variable $RUBYLIB
  80. must be set correctly. It opens the DAC, creates some scale widgets,
  81. and starts two CLM oscillators doing frequency modulation in
  82. semi-real-time. This is a translation of bess.scm of Bill
  83. Schottstaedt\'s Snd sound editor.
  84. Default values shown in brackets.
  85. Usage: #{file} [ options ] [ -- X options ]
  86. -p, --play play immediately (#{@play.nonzero? ? "yes" : "no"})
  87. -f, --frequency NUMBER frequency between #{@low_freq} and #{@high_freq} (#{@freq})
  88. -i, --index1 NUMBER fm_index1 between 0 and #{@high_index1} (#{@fm_index1})
  89. -r, --ratio NUMBER ratio between 0 and #{@high_ratio} (#{@fm_ratio1})
  90. -a, --amplitude NUMBER amplitude between 0 and 1 (#{@amp})
  91. -B, --bufsize NUMBER buffer size (#{@bufsize})
  92. -S, --srate NUMBER sampling rate (#{@srate})
  93. -C, --channels NUMBER number of channels (#{@chans})
  94. -b --background COLOR background color (#{@background})
  95. -s, --sliderback COLOR slider background color (#{@sliderback})
  96. -V, --version display version information and exit
  97. -h, --help display this help message and exit
  98. Example: #{file} -pf1000 -r3 -b ivory1 -s ivory3"
  99. rbm_require "getoptlong"
  100. GetoptLong.new(["--play", "-p", GetoptLong::NO_ARGUMENT],
  101. ["--frequency", "-f", GetoptLong::REQUIRED_ARGUMENT],
  102. ["--index1", "-i", GetoptLong::REQUIRED_ARGUMENT],
  103. ["--ratio", "-r", GetoptLong::REQUIRED_ARGUMENT],
  104. ["--amplitude", "-a", GetoptLong::REQUIRED_ARGUMENT],
  105. ["--bufsize", "-B", GetoptLong::REQUIRED_ARGUMENT],
  106. ["--srate", "-S", GetoptLong::REQUIRED_ARGUMENT],
  107. ["--channels", "-C", GetoptLong::REQUIRED_ARGUMENT],
  108. ["--background", "-b", GetoptLong::REQUIRED_ARGUMENT],
  109. ["--sliderback", "-s", GetoptLong::REQUIRED_ARGUMENT],
  110. ["--version", "-V", GetoptLong::NO_ARGUMENT],
  111. ["--help", "-h", GetoptLong::NO_ARGUMENT]).each do |name, arg|
  112. case name
  113. when "--play"
  114. @play = 1.0
  115. when "--frequency"
  116. @freq = arg.to_f.abs
  117. @freq = @freq < @low_freq ? @low_freq : @freq > @high_freq ? @high_freq : @freq
  118. when "--index1"
  119. ind = arg.to_f.abs
  120. @fm_index1 = ind > @high_index1 ? @high_index1 : ind
  121. when "--ratio"
  122. rat = arg.to_i.abs
  123. @fm_ratio1 = rat > @high_ratio ? @high_ratio : rat
  124. when "--amplitude"
  125. amp = arg.to_f.abs
  126. @amp = amp > 1 ? 1 : amp
  127. when "--bufsize"
  128. @bufsize = arg.to_i
  129. when "--srate"
  130. @srate = arg.to_i
  131. when "--channels"
  132. @chans = arg.to_i
  133. when "--sliderback"
  134. @sliderback = arg
  135. when "--background"
  136. @background = arg
  137. when "--version"
  138. vers = true
  139. when "--help"
  140. help = true
  141. end
  142. end
  143. die help_msg if help
  144. die vers_msg if vers
  145. end
  146. def get_color(color)
  147. col = RXColor()
  148. dpy = RXtDisplay(@shell_app[0])
  149. cmap = RDefaultColormap(dpy, RDefaultScreen(dpy))
  150. warn("Can't allocate #{color.inspect}!") if RXAllocNamedColor(dpy, cmap, color, col, col).zero?
  151. Rpixel(col)
  152. end
  153. def set_label(wid, *args)
  154. RXtVaSetValues(wid, [RXmNlabelString, RXmStringCreate(format(*args), RXmFONTLIST_DEFAULT_TAG)])
  155. end
  156. def make_label(wid, name)
  157. RXtCreateManagedWidget(name, RxmLabelWidgetClass, @form,
  158. [RXmNleftAttachment, RXmATTACH_FORM,
  159. RXmNbottomAttachment, RXmATTACH_NONE,
  160. RXmNtopAttachment, RXmATTACH_WIDGET,
  161. RXmNtopWidget, wid,
  162. RXmNrightAttachment, RXmATTACH_NONE,
  163. RXmNalignment, RXmALIGNMENT_END,
  164. RXmNwidth, 80, #114,
  165. RXmNrecomputeSize, false,
  166. RXmNbackground, get_color(@background)])
  167. end
  168. def make_scale_label(wid)
  169. RXtCreateManagedWidget("label", RxmLabelWidgetClass, @form,
  170. [RXmNleftAttachment, RXmATTACH_WIDGET,
  171. RXmNleftWidget, wid,
  172. RXmNbottomAttachment, RXmATTACH_NONE,
  173. RXmNtopAttachment, RXmATTACH_OPPOSITE_WIDGET,
  174. RXmNtopWidget, wid,
  175. RXmNrightAttachment, RXmATTACH_NONE,
  176. RXmNbackground, get_color(@background)])
  177. end
  178. def make_scale(wid)
  179. RXtCreateManagedWidget("scale", RxmScaleWidgetClass, @form,
  180. [RXmNleftAttachment, RXmATTACH_WIDGET,
  181. RXmNleftWidget, wid,
  182. RXmNbottomAttachment, RXmATTACH_NONE,
  183. RXmNtopAttachment, RXmATTACH_OPPOSITE_WIDGET,
  184. RXmNtopWidget, wid,
  185. RXmNrightAttachment, RXmATTACH_FORM,
  186. RXmNshowValue, false,
  187. RXmNorientation, RXmHORIZONTAL,
  188. RXmNbackground, get_color(@sliderback)])
  189. end
  190. def make_scales(wid, name, val, callback)
  191. label = make_scale_label(make_label(wid, name))
  192. scale = make_scale(label)
  193. set_label(label, val.kind_of?(Integer) ? "%8d" : "%8.3f", val)
  194. RXtAddCallback(scale, RXmNdragCallback, callback, label)
  195. RXtAddCallback(scale, RXmNvalueChangedCallback, callback ,label)
  196. scale
  197. end
  198. def start_dac(&body)
  199. args = [$0] + $*
  200. @shell_app = RXtVaOpenApplication("FM", args.length, args, RapplicationShellWidgetClass,
  201. [RXmNallowShellResize, true, RXmNtitle, "FM forever!"])
  202. RXtAddEventHandler(@shell_app[0], 0, true,
  203. lambda do |w, c, i, f| R_XEditResCheckMessages(w, c, i, f) end)
  204. @form = RXtCreateManagedWidget("form", RxmFormWidgetClass, @shell_app[0],
  205. [RXmNresizePolicy, RXmRESIZE_GROW,
  206. RXmNbackground, get_color(@background)])
  207. play_button = RXtCreateManagedWidget("play", RxmToggleButtonWidgetClass, @form,
  208. [RXmNtopAttachment, RXmATTACH_FORM,
  209. RXmNleftAttachment, RXmATTACH_FORM,
  210. RXmNrightAttachment, RXmATTACH_NONE,
  211. RXmNbottomAttachment, RXmATTACH_NONE,
  212. RXmNbackground, get_color(@background)])
  213. RXmToggleButtonSetState(play_button, @play.nonzero? ? true : false, false)
  214. RXtAddCallback(play_button, RXmNvalueChangedCallback,
  215. lambda do |w, c, i| @play = Rset(i) ? 1.0 : 0.0 end)
  216. quit_button = RXtCreateManagedWidget(" quit ", RxmPushButtonWidgetClass, @form,
  217. [RXmNtopAttachment, RXmATTACH_FORM,
  218. RXmNleftAttachment, RXmATTACH_NONE,
  219. RXmNrightAttachment, RXmATTACH_FORM,
  220. RXmNbottomAttachment, RXmATTACH_NONE,
  221. RXmNbackground, get_color(@background)])
  222. RXtAddCallback(quit_button, RXmNactivateCallback, lambda do |w, c, i| exit(0) end)
  223. wid = make_scales(play_button, " carrier:", @freq,
  224. lambda do |w, c, i|
  225. @freq = @low_freq + Rvalue(i) * ((@high_freq - @low_freq) / 100.0)
  226. set_label(c, "%8.3f", @freq)
  227. end)
  228. RXmScaleSetValue(wid, (100 * (@freq - @low_freq) / (@high_freq - @low_freq)).round)
  229. wid = make_scales(wid, " amplitude:", @amp,
  230. lambda do |w, c, i|
  231. @amp = Rvalue(i) / 100.0
  232. set_label(c, "%8.3f", @amp)
  233. end)
  234. RXmScaleSetValue(wid, (100 * @amp).round)
  235. wid = make_scales(wid, "fm index 1:", @fm_index1,
  236. lambda do |w, c, i|
  237. @fm_index1 = Rvalue(i) * (@high_index1 / 100.0)
  238. set_label(c, "%8.3f", @fm_index1)
  239. end)
  240. RXmScaleSetValue(wid, (100 * @fm_index1 / @high_index1).round)
  241. wid = make_scales(wid, "c/m ratio 1:", @fm_ratio1,
  242. lambda do |w, c, i|
  243. @fm_ratio1 = (Rvalue(i) * (@high_ratio / 100.0)).round
  244. set_label(c, "%8d", @fm_ratio1)
  245. end)
  246. RXmScaleSetValue(wid, (@fm_ratio1 * 100 / @high_ratio).round)
  247. if defined? @fm_index2
  248. wid = make_scales(wid, "fm index 2:", @fm_index2,
  249. lambda do |w, c, i|
  250. @fm_index2 = Rvalue(i) * (@high_index2 / 100.0)
  251. set_label(c, "%8.3f", @fm_index2)
  252. end)
  253. RXmScaleSetValue(wid, (100 * @fm_index2 / @high_index2).round)
  254. wid = make_scales(wid, "c/m ratio 2:", @fm_ratio2,
  255. lambda do |w, c, i|
  256. @fm_ratio2 = (Rvalue(i) * (@high_ratio / 100.0)).round
  257. set_label(c, "%8d", @fm_ratio2)
  258. end)
  259. RXmScaleSetValue(wid, (@fm_ratio2 * 100 / @high_ratio).round)
  260. end
  261. if defined? @fm_index3
  262. wid = make_scales(wid, "fm index 3:", @fm_index2,
  263. lambda do |w, c, i|
  264. @fm_index2 = Rvalue(i) * (@high_index3 / 100.0)
  265. set_label(c, "%8.3f", @fm_index2)
  266. end)
  267. RXmScaleSetValue(wid, (100 * @fm_index3 / @high_index3).round)
  268. wid = make_scales(wid, "c/m ratio 3:", @fm_ratio3,
  269. lambda do |w, c, i|
  270. @fm_ratio3 = (Rvalue(i) * (@high_ratio / 100.0)).round
  271. set_label(c, "%8d", @fm_ratio3)
  272. end)
  273. RXmScaleSetValue(wid, (@fm_ratio3 * 100 / @high_ratio).round)
  274. end
  275. proc = nil
  276. data = make_sound_data(@chans, @bufsize)
  277. port = mus_audio_open_output(0, @srate, @chans, Mus_lshort, @bufsize * 2)
  278. die("Can't open DAC!") if port < 0
  279. RXmAddWMProtocolCallback(@shell_app[0],
  280. RXmInternAtom(RXtDisplay(@shell_app[0]), "WM_DELETE_WINDOW", false),
  281. lambda do |w, c, i|
  282. RXtRemoveWorkProc(proc)
  283. mus_audio_close(port)
  284. end, false)
  285. proc = RXtAppAddWorkProc(@shell_app[1], lambda do |dummy|
  286. @bufsize.times do |i|
  287. @chans.times do |c|
  288. sound_data_set!(data, c, i, body.call)
  289. end
  290. end
  291. mus_audio_write(port, data, @bufsize)
  292. false
  293. end)
  294. RXtRealizeWidget(@shell_app[0])
  295. RXtAppMainLoop(@shell_app[1])
  296. rescue
  297. die("start_dac() { ... }")
  298. end
  299. end
  300. # test functions
  301. def bess(banner, file, &body)
  302. b = Bess.new(banner, file)
  303. b.make_ffm()
  304. b.start_dac() do b.instance_eval(&body) end
  305. rescue
  306. die("bess(banner, file, osf, mdf) { ... }")
  307. end
  308. class Bess
  309. def make_fm
  310. @osc = make_oscil(0.0)
  311. @mod = make_oscil(0.0)
  312. end
  313. def fm
  314. @amp * @play * oscil(@osc, in_hz(@freq) + @fm_index1 * oscil(@mod, in_hz(@fm_ratio1 * @freq)))
  315. end
  316. def make_ffm
  317. @osc = make_oscil(0.0)
  318. @md1 = make_oscil(0.0)
  319. @md2 = make_oscil(0.0)
  320. @md3 = make_oscil(0.0)
  321. @fm_index1 = 1.0
  322. @fm_index2 = 0.0
  323. @fm_index3 = 0.0
  324. @fm_ratio1 = 1
  325. @fm_ratio2 = 1
  326. @fm_ratio3 = 1
  327. @high_index2 = 3.0
  328. @high_index2 = 1.0
  329. @high_index3 = 0.25
  330. @amp = 0.5
  331. end
  332. def ffm_rb
  333. @amp * @play * oscil(@osc, in_hz(@freq) + @fm_index1 * oscil(@md1, in_hz(@fm_ratio1 * @freq)) +
  334. @fm_index2 * oscil(@md2, in_hz(@fm_ratio2 * @freq)) +
  335. @fm_index3 * oscil(@md3, in_hz(@fm_ratio3 * @freq)))
  336. end
  337. def ffm
  338. ffm_c(@amp, @play, @freq, @fm_index1, @fm_index2, @fm_index3,
  339. @fm_ratio1, @fm_ratio2, @fm_ratio3, @osc, @md1, @md2, @md3)
  340. end
  341. rbm_require 'inline'
  342. include Inline
  343. inline do |ffm_c|
  344. fft_c.c "
  345. #include <sndlib.h>
  346. #include <clm.h>
  347. typedef struct {
  348. mus_any *gen;
  349. VALUE *vcts;
  350. int nvcts;
  351. void *input_ptree;
  352. } mus_xen;
  353. double
  354. fft_c(double amp,
  355. double play,
  356. double freq,
  357. double fm_index1,
  358. double fm_index2,
  359. double fm_index3,
  360. double fm_ratio1,
  361. double fm_ratio2,
  362. double fm_ratio3,
  363. mus_any *osc,
  364. mus_any *md1,
  365. mus_any *md2,
  366. mus_any *md3)
  367. {
  368. return (amp * play * mus_oscil(osc, mus_hz2radians(freq) +
  369. fm_index1 * mus_oscil(md1, mus_hz2radians(fm_ratio1 * freq), 0.0) +
  370. fm_index2 * mus_oscil(md2, mus_hz2radians(fm_ratio2 * freq), 0.0) +
  371. fm_index3 * mus_oscil(md3, mus_hz2radians(fm_ratio3 * freq), 0.0), 0.0));
  372. }"
  373. end
  374. end
  375. begin
  376. bess(banner, file) do ffm_rb() end
  377. end
  378. # bess.rb ends here