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.

528 satır
20KB

  1. #!/usr/bin/env ruby
  2. # bess1.rb -- some examples from clm/rt.lisp and clm/bess5.cl
  3. # Copyright (C) 2002--2009 Michael Scholz
  4. # Author: Michael Scholz <mi-scholz@users.sourceforge.net>
  5. # Created: Sun Sep 15 19:11:12 CEST 2002
  6. # Changed: Tue Sep 29 02:05:49 CEST 2009
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. # Commentary:
  19. # Requires sndlib.so and libxm.so!
  20. #
  21. # This file provides simple mono real time output to DAC. Tempo,
  22. # frequency, amplitude, and FM index can be controlled via sliders.
  23. # The music algorithms are taken from clm/rt.lisp and clm/bess5.cl.
  24. #
  25. # Bess.new.start -- starts a Motif widget with two DAC tests.
  26. #
  27. # Bess.new.start(:srate, $clm_srate # 22050
  28. # :bufsize, $clm_rt_bufsize # 128
  29. # :sample_type, $clm_sample_type # Mus_lshort
  30. # :which, :agn # :agn or :vct_test
  31. # :play, false)
  32. # Code:
  33. def warn(*args)
  34. str = format(*args) << ($! ? ": #{$!}" : "") << "\n"
  35. str << (($@ and $DEBUG) ? "\n[#{$@.join("\n")}]" : "")
  36. $stdout.print str
  37. $! = nil
  38. end
  39. def die(*args)
  40. warn(*args)
  41. exit 1
  42. end
  43. def rbm_require(lib)
  44. puts "loading #{lib.inspect}" if $VERBOSE
  45. require lib.to_s
  46. rescue ScriptError
  47. die "\aScriptError"
  48. end
  49. rbm_require "sndlib"
  50. $output = nil # holds fd from mus_audio_open_output()
  51. $clm_srate = 22050
  52. $clm_sample_type = Mus_lshort
  53. $clm_rt_bufsize = 128
  54. module Bess_utils
  55. def rbm_random(n)
  56. mus_random(n).abs
  57. end
  58. def get_args(args, key, val)
  59. if(key == :help and (args == key or args.member?(key) or args.assoc(key)))
  60. val = true
  61. elsif(args.member?(key))
  62. x = args[args.index(key) + 1]
  63. val = ((x == nil) ? val : x)
  64. elsif(args.assoc(key))
  65. val = (args.assoc(key)[1] rescue val)
  66. end
  67. val
  68. end
  69. def seconds2samples(sec)
  70. sr = (mus_srate() rescue $clm_srate)
  71. (sec * sr).round
  72. end
  73. def envelope_interp(*args)
  74. x = args[0]
  75. env = args[1]
  76. base = args[2]
  77. if (not env) or env.empty?
  78. 0.0
  79. elsif x <= env[0] or env[2..-1].empty?
  80. env[1]
  81. elsif env[2] > x
  82. if env[1] == env[3] or (base and base == 0.0)
  83. env[1]
  84. elsif (not base) or base == 1.0
  85. env[1] + (x - env[0]) * ((env[3] - env[1]) / (env[2] - env[0]))
  86. else
  87. env[1] + ((env[3] - env[1]) / (base - 1.0)) *
  88. ((base ** ((x - env[0]) / (env[2] - env[0]))) - 1.0)
  89. end
  90. else
  91. envelope_interp(x, env[2..-1])
  92. end
  93. end
  94. include Math
  95. # simple violin, see snd/fm.html
  96. def make_rt_violin(dur = 1.0, freq = 440.0, amp = 0.3, *args)
  97. fm_index = get_args(args, :fm_index, 1.0)
  98. amp_env = get_args(args, :amp_env, [0, 0, 25, 1, 75, 1, 100, 0])
  99. frq_scl = hz2radians(freq)
  100. maxdev = frq_scl * fm_index
  101. index1 = maxdev * (5.0 / log(freq))
  102. index2 = maxdev * 3.0 * ((8.5 - log(freq)) / (3.0 + freq / 1000.0))
  103. index3 = maxdev * (4.0 / sqrt(freq))
  104. carrier = make_oscil(:frequency, freq)
  105. fmosc1 = make_oscil(:frequency, freq)
  106. fmosc2 = make_oscil(:frequency, freq * 3.0)
  107. fmosc3 = make_oscil(:frequency, freq * 4.0)
  108. ampf = make_env(:envelope, amp_env, :scaler, amp, :duration, dur)
  109. indf1 = make_env(:envelope, [0, 1, 25, 0.4, 75, 0.6, 100, 0], :scaler, index1, :duration, dur)
  110. indf2 = make_env(:envelope, [0, 1, 25, 0.4, 75, 0.6, 100, 0], :scaler, index2, :duration, dur)
  111. indf3 = make_env(:envelope, [0, 1, 25, 0.4, 75, 0.6, 100, 0], :scaler, index3, :duration, dur)
  112. pervib = make_triangle_wave(:frequency, 0.5, :amplitude, 0.0025 * frq_scl)
  113. ranvib = make_rand_interp(:frequency, 16.0, :amplitude, 0.005 * frq_scl)
  114. lambda do | |
  115. vib = triangle_wave(pervib) + rand_interp(ranvib)
  116. env(ampf) * oscil(carrier,
  117. vib + env(indf1) * oscil(fmosc1, vib) +
  118. env(indf2) * oscil(fmosc2, 3.0 * vib) +
  119. env(indf3) * oscil(fmosc3, 4.0 * vib))
  120. end
  121. end
  122. end
  123. # class Agn is a simplified translation of clm/bess5.cl and
  124. # clm/clm-example.lisp.
  125. class Agn
  126. include Bess_utils
  127. def initialize
  128. @tempo = 0.25
  129. @amp = 1.0
  130. @freq = 1.0
  131. @index = 1.0
  132. @play = false
  133. @lim = 256
  134. @time = 60
  135. @octs = Array.new(@lim + 1) do |i| (4 + 2 * rbell(rbm_random(1.0))).floor end
  136. @rhys = Array.new(@lim + 1) do |i| (4 + 6 * rbm_random(1.0)).floor end
  137. @amps = Array.new(@lim + 1) do |i| (1 + 8 * rbell(rbm_random(1.0))).floor end
  138. @pits = Array.new(@lim + 1) do |i|
  139. [0, 0, 2, 4, 11, 11, 5, 6, 7, 9, 2, 0, 0].at((12 * rbm_random(1.0)).floor)
  140. end
  141. @begs = Array.new(@lim + 1) do |i|
  142. if rbm_random(1.0) < 0.9
  143. (4 + 2 * rbm_random(1.0)).floor
  144. else
  145. (6 * rbm_random(4.0)).floor
  146. end
  147. end
  148. end
  149. # called by XtAppAddWorkProc
  150. def rt_send2dac(func)
  151. if @play
  152. mus_audio_write($output, vct2sound_data(vct_map!(make_vct($clm_rt_bufsize), func.call),
  153. make_sound_data(1, $clm_rt_bufsize), 0),
  154. $clm_rt_bufsize)
  155. false
  156. else
  157. mus_audio_close($output)
  158. $output = nil
  159. true
  160. end
  161. end
  162. # see clm/rt.lisp
  163. def make_vct_test(*args)
  164. srate = get_args(args, :srate, $clm_srate)
  165. bufsize = get_args(args, :bufsize, $clm_rt_bufsize)
  166. sample_type = get_args(args, :sample_type, $clm_sample_type)
  167. $clm_srate = set_mus_srate(srate).to_i
  168. $clm_rt_bufsize = bufsize
  169. $output = mus_audio_open_output(Mus_audio_default, srate, 1, sample_type, bufsize * 2)
  170. mode = [0, 12, 2, 4, 14, 4, 5, 5, 0, 7, 7, 11, 11]
  171. pits = Array.new(@lim + 1) do rbm_random(12.0).floor end
  172. begs = Array.new(@lim + 1) do 1 + rbm_random(3.0).floor end
  173. cellbeg, cellsiz, cellctr = 0, 6, 0
  174. func = nil
  175. len = dur = 0
  176. lambda do | |
  177. if len > 1
  178. len -= 1
  179. else
  180. dur = @tempo * begs[cellctr + 1]
  181. cellctr += 1
  182. if cellctr > (cellsiz + cellbeg)
  183. cellbeg += 1 if rbm_random(1.0) > 0.5
  184. cellsiz += 1 if rbm_random(1.0) > 0.5
  185. cellctr = cellbeg
  186. end
  187. func = make_rt_violin(dur, @freq * 16.351 * 16 * 2 ** (mode[pits[cellctr]] / 12.0),
  188. @amp * 0.3, :fm_index, @index)
  189. len = (seconds2samples(dur) / bufsize).ceil
  190. end
  191. func
  192. end
  193. end
  194. def tune(x)
  195. [1.0, 256.0 / 243, 9.0 / 8, 32.0 / 27, 81.0 / 64,
  196. 4.0 / 3, 1024.0 / 729, 3.0 / 2, 128.0 / 81, 27.0 / 16,
  197. 16.0 / 9, 243.0 / 128, 2.0].at(x % 12) * 2 ** x.divmod(12).first
  198. end
  199. def rbell(x)
  200. envelope_interp(x * 100, [0, 0, 10, 0.25, 90, 1.0, 100, 1.0])
  201. end
  202. # see clm/bess5.cl
  203. def make_agn(*args)
  204. srate = get_args(args, :srate, $clm_srate)
  205. bufsize = get_args(args, :bufsize, $clm_rt_bufsize)
  206. sample_type = get_args(args, :sample_type, $clm_sample_type)
  207. $clm_srate = set_mus_srate(srate).to_i
  208. $clm_rt_bufsize = bufsize
  209. $output = mus_audio_open_output(Mus_audio_default, srate, 1, sample_type, bufsize * 2)
  210. die("can't open DAC (%s)", $output.inspect) if $output < 0
  211. wins = [[0, 0, 40, 0.1, 60, 0.2, 75, 0.4, 82, 1, 90, 1, 100, 0],
  212. [0, 0, 60, 0.1, 80, 0.2, 90, 0.4, 95, 1, 100, 0],
  213. [0, 0, 10, 1, 16, 0, 32, 0.1, 50, 1, 56, 0, 60, 0, 90, 0.3, 100, 0],
  214. [0, 0, 30, 1, 56, 0, 60, 0, 90, 0.3, 100, 0],
  215. [0, 0, 50, 1, 80, 0.3, 100, 0],
  216. [0, 0, 40, 0.1, 60, 0.2, 75, 0.4, 82, 1, 90, 1, 100, 0],
  217. [0, 0, 40, 0.1, 60, 0.2, 75, 0.4, 82, 1, 90, 1, 100, 0],
  218. [0, 0, 10, 1, 32, 0.1, 50, 1, 90, 0.3, 100, 0],
  219. [0, 0, 60, 0.1, 80, 0.3, 95, 1, 100, 0],
  220. [0, 0, 80, 0.1, 90, 1, 100, 0]]
  221. cellbeg, cellsiz, cellctr, whichway = 0, 4, 0, 1
  222. nextbeg = beg = 0.0
  223. func = nil
  224. len = dur = 0
  225. lambda do | |
  226. if len > 1
  227. len -= 1
  228. else
  229. beg += nextbeg
  230. nextbeg += [0.025, @tempo * (0.95 + rbm_random(0.1)) * @begs[cellctr]].max
  231. dur = [0.025, @tempo * (0.85 + rbm_random(0.1)) * @rhys[cellctr]].max
  232. freq = @freq * 16.351 * tune(@pits[cellctr]) * 2 ** @octs[cellctr]
  233. dur += dur if freq < 100
  234. ampl = @amp * 10 * [0.003, @amps[cellctr] * 0.01].max
  235. ind = @index * rbm_random(1.0) * 3.0
  236. cellctr += 1
  237. if cellctr > (cellsiz + cellbeg)
  238. cellbeg += 1
  239. if rbm_random(1.0) > 0.5
  240. cellsiz += whichway
  241. end
  242. if cellsiz > 10 and rbm_random(1.0) > 0.99
  243. whichway = -2
  244. if cellsiz > 6 and rbm_random(1.0) > 0.999
  245. whichway = -1
  246. if cellsiz < 4
  247. whichway = 1
  248. end
  249. end
  250. end
  251. nextbeg += rbm_random(1.0)
  252. cellctr = cellbeg
  253. end
  254. func = make_rt_violin(dur, freq, ampl, :fm_index, ind,
  255. :amp_env, wins[(10 * (beg - beg.floor)).floor])
  256. len = (seconds2samples(dur) / bufsize).ceil
  257. end
  258. func
  259. end
  260. end
  261. end
  262. class Bess < Agn
  263. rbm_require "libxm"
  264. def initialize
  265. super
  266. @sliderback = "lightsteelblue"
  267. @background = "lightsteelblue1"
  268. @which = @proc = nil
  269. @shell_app = @form = nil
  270. @tl = @ts = @fl = @fs = @al = @as = @il = @is = nil
  271. 1.upto(15) do |i|
  272. trap(i) do |sig|
  273. puts "\nSignal #{sig} received. Process #{$$} canceled."
  274. RXtRemoveWorkProc(@proc) if @proc
  275. exit 0
  276. end
  277. end
  278. end
  279. def get_color(color)
  280. col = RXColor()
  281. dpy = RXtDisplay(@shell_app[0])
  282. cmap = RDefaultColormap(dpy, RDefaultScreen(dpy))
  283. warn("Can't allocate #{color.inspect}!") if RXAllocNamedColor(dpy, cmap, color, col, col).zero?
  284. Rpixel(col)
  285. end
  286. def set_label(wid, *args)
  287. RXtVaSetValues(wid, [RXmNlabelString, RXmStringCreate(format(*args), RXmFONTLIST_DEFAULT_TAG)])
  288. end
  289. def make_label(wid, name)
  290. RXtCreateManagedWidget(name, RxmLabelWidgetClass, @form,
  291. [RXmNleftAttachment, RXmATTACH_FORM,
  292. RXmNbottomAttachment, RXmATTACH_NONE,
  293. RXmNtopAttachment, RXmATTACH_WIDGET,
  294. RXmNtopWidget, wid,
  295. RXmNrightAttachment, RXmATTACH_NONE,
  296. RXmNalignment, RXmALIGNMENT_END,
  297. RXmNrecomputeSize, false,
  298. RXmNbackground, get_color(@background)])
  299. end
  300. def make_scale_label(wid)
  301. RXtCreateManagedWidget("label", RxmLabelWidgetClass, @form,
  302. [RXmNleftAttachment, RXmATTACH_WIDGET,
  303. RXmNleftWidget, wid,
  304. RXmNbottomAttachment, RXmATTACH_NONE,
  305. RXmNtopAttachment, RXmATTACH_OPPOSITE_WIDGET,
  306. RXmNtopWidget, wid,
  307. RXmNrightAttachment, RXmATTACH_NONE,
  308. RXmNbackground, get_color(@background)])
  309. end
  310. def make_scale(wid)
  311. RXtCreateManagedWidget("scale", RxmScaleWidgetClass, @form,
  312. [RXmNleftAttachment, RXmATTACH_WIDGET,
  313. RXmNleftWidget, wid,
  314. RXmNbottomAttachment, RXmATTACH_NONE,
  315. RXmNtopAttachment, RXmATTACH_OPPOSITE_WIDGET,
  316. RXmNtopWidget, wid,
  317. RXmNrightAttachment, RXmATTACH_FORM,
  318. RXmNshowValue, false,
  319. RXmNorientation, RXmHORIZONTAL,
  320. RXmNheight, 20,
  321. RXmNbackground, get_color(@sliderback)])
  322. end
  323. # return label and scale widget
  324. def make_scales(wid, name, val, callback)
  325. label = make_scale_label(make_label(wid, name))
  326. scale = make_scale(label)
  327. set_label(label, val.kind_of?(Integer) ? "%4d" : "%4.3f", val)
  328. RXtAddCallback(scale, RXmNdragCallback, callback, label)
  329. RXtAddCallback(scale, RXmNvalueChangedCallback, callback ,label)
  330. [label, scale]
  331. end
  332. def do_play(*args)
  333. if @play
  334. case @which
  335. when :agn
  336. func = make_agn(*args)
  337. when :vct_test
  338. func = make_vct_test(*args)
  339. else
  340. func = make_agn(*args)
  341. end
  342. @proc = RXtAppAddWorkProc(@shell_app[1], lambda do |c| rt_send2dac(func) end)
  343. else
  344. RXtRemoveWorkProc(@proc) if @proc
  345. end
  346. end
  347. def set_defaults(parent)
  348. @tempo = 0.25
  349. @amp = 1.0
  350. @freq = 1.0
  351. @index = 1.0
  352. low_tempo = 0.05
  353. high_tempo = 0.5
  354. low_freq = 0.1
  355. high_freq = 4.0
  356. high_index = 2.0
  357. set_label(@tl, "%4.3f", @tempo)
  358. RXmScaleSetValue(@ts, (100 * (@tempo - low_tempo) / (high_tempo - low_tempo)).round)
  359. set_label(@fl, "%4.3f", @freq)
  360. RXmScaleSetValue(@fs, (100 * (@freq - low_freq) / (high_freq - low_freq)).round)
  361. set_label(@al, "%4.3f", @amp)
  362. RXmScaleSetValue(@as, (100 * @amp).round)
  363. set_label(@il, "%4.3f", @index)
  364. RXmScaleSetValue(@is, (100 * (@index / high_index)).round)
  365. end
  366. def start(*args)
  367. @play = get_args(args, :play, false)
  368. @which = get_args(args, :which, :agn)
  369. # rest args are going to make_vct_test() or make_agn()
  370. cargs = [$0] + $*
  371. @shell_app = RXtVaOpenApplication("FM", cargs.length, cargs, RapplicationShellWidgetClass,
  372. [RXmNallowShellResize, true, RXmNtitle, "FM forever!"])
  373. RXtAddEventHandler(@shell_app[0], 0, true,
  374. lambda do |w, c, i, f| R_XEditResCheckMessages(w, c, i, f) end)
  375. @form = RXtCreateManagedWidget("form", RxmFormWidgetClass, @shell_app[0],
  376. [RXmNresizePolicy, RXmRESIZE_GROW,
  377. RXmNbackground, get_color(@background)])
  378. play = RXtCreateManagedWidget("play", RxmToggleButtonWidgetClass, @form,
  379. [RXmNtopAttachment, RXmATTACH_FORM,
  380. RXmNleftAttachment, RXmATTACH_FORM,
  381. RXmNrightAttachment, RXmATTACH_NONE,
  382. RXmNbottomAttachment, RXmATTACH_NONE,
  383. RXmNbackground, get_color(@background)])
  384. radio = RXmCreateRadioBox(@form, "radio",
  385. [RXmNorientation, RXmHORIZONTAL,
  386. RXmNtopAttachment, RXmATTACH_FORM,
  387. RXmNleftAttachment, RXmATTACH_WIDGET,
  388. RXmNleftWidget, play,
  389. RXmNrightAttachment, RXmATTACH_NONE,
  390. RXmNbottomAttachment, RXmATTACH_NONE,
  391. RXmNbackground, get_color(@background)])
  392. p_agn = RXtCreateManagedWidget("agn", RxmToggleButtonWidgetClass, radio,
  393. [RXmNtopAttachment, RXmATTACH_FORM,
  394. RXmNleftAttachment, RXmATTACH_FORM,
  395. RXmNrightAttachment, RXmATTACH_NONE,
  396. RXmNbottomAttachment, RXmATTACH_NONE,
  397. RXmNbackground, get_color(@background)])
  398. p_test = RXtCreateManagedWidget("test", RxmToggleButtonWidgetClass, radio,
  399. [RXmNtopAttachment, RXmATTACH_FORM,
  400. RXmNleftAttachment, RXmATTACH_WIDGET,
  401. RXmNleftWidget, p_agn,
  402. RXmNrightAttachment, RXmATTACH_NONE,
  403. RXmNbottomAttachment, RXmATTACH_NONE,
  404. RXmNbackground, get_color(@background)])
  405. quit = RXtCreateManagedWidget(" quit ", RxmPushButtonWidgetClass, @form,
  406. [RXmNtopAttachment, RXmATTACH_FORM,
  407. RXmNleftAttachment, RXmATTACH_WIDGET,
  408. RXmNleftWidget, radio,
  409. RXmNrightAttachment, RXmATTACH_FORM,
  410. RXmNbottomAttachment, RXmATTACH_NONE,
  411. RXmNbackground, get_color(@background)])
  412. sep = RXtCreateManagedWidget("sep", RxmSeparatorWidgetClass, @form,
  413. [RXmNleftAttachment, RXmATTACH_FORM,
  414. RXmNbottomAttachment, RXmATTACH_NONE,
  415. RXmNtopAttachment, RXmATTACH_WIDGET,
  416. RXmNtopWidget, radio,
  417. RXmNrightAttachment, RXmATTACH_FORM,
  418. RXmNheight, 4,
  419. RXmNorientation, RXmHORIZONTAL])
  420. RXmToggleButtonSetState(play, @play, true)
  421. RXtAddCallback(play, RXmNvalueChangedCallback,
  422. lambda do |w, c, i|
  423. @play = Rset(i)
  424. set_defaults(sep) if @play
  425. do_play(*args)
  426. end)
  427. RXmToggleButtonSetState(p_agn, @which == :agn, true)
  428. RXtAddCallback(p_agn, RXmNvalueChangedCallback,
  429. lambda do |w, c, i|
  430. @which = c if Rset(i)
  431. @play = false
  432. RXmToggleButtonSetState(play, @play, true)
  433. end, :agn)
  434. RXmToggleButtonSetState(p_test, @which == :vct_test, true)
  435. RXtAddCallback(p_test, RXmNvalueChangedCallback,
  436. lambda do |w, c, i|
  437. @which = c if Rset(i)
  438. @play = false
  439. RXmToggleButtonSetState(play, @play, true)
  440. end, :vct_test)
  441. RXtAddCallback(quit, RXmNactivateCallback,
  442. lambda do |w, c, i|
  443. RXtRemoveWorkProc(@proc) if @proc
  444. exit 0
  445. end)
  446. low_tempo = 0.05
  447. high_tempo = 0.5
  448. low_freq = 0.1
  449. high_freq = 4.0
  450. high_index = 2.0
  451. @tl, @ts = make_scales(sep, " tempo:", @tempo,
  452. lambda do |w, c, i|
  453. @tempo = low_tempo + Rvalue(i) * (high_tempo - low_tempo) * 0.01
  454. set_label(c, "%4.3f", @tempo)
  455. end)
  456. RXmScaleSetValue(@ts, (100 * (@tempo - low_tempo) / (high_tempo - low_tempo)).round)
  457. @fl, @fs = make_scales(@ts, " freq:", @freq,
  458. lambda do |w, c, i|
  459. @freq = low_freq + Rvalue(i) * ((high_freq - low_freq) * 0.01)
  460. set_label(c, "%4.3f", @freq)
  461. end)
  462. RXmScaleSetValue(@fs, (100 * (@freq - low_freq) / (high_freq - low_freq)).round)
  463. @al, @as = make_scales(@fs, " amp:", @amp,
  464. lambda do |w, c, i|
  465. @amp = Rvalue(i) * 0.01
  466. set_label(c, "%4.3f", @amp)
  467. end)
  468. RXmScaleSetValue(@as, (100 * @amp).round)
  469. @il, @is = make_scales(@as, " index:", @index,
  470. lambda do |w, c, i|
  471. @index = Rvalue(i) * high_index * 0.01
  472. set_label(c, "%4.3f", @index)
  473. end)
  474. RXmScaleSetValue(@is, (100 * (@index / high_index)).round)
  475. do_play(*args)
  476. RXtManageChild(radio)
  477. RXtRealizeWidget(@shell_app[0])
  478. RXtAppMainLoop(@shell_app[1])
  479. end
  480. end
  481. begin
  482. # Bess.new.start(:srate, $clm_srate,
  483. # :bufsize, $clm_rt_bufsize,
  484. # :sample_type, $clm_sample_type,
  485. # :which, :agn,
  486. # :play, false)
  487. Bess.new.start
  488. end
  489. # bess1.rb ends here