1# rubber.rb -- Translation of rubber.scm
2
3# Translator/Author: Michael Scholz <mi-scholz@users.sourceforge.net>
4# Created: 03/02/28 03:04:03
5# Changed: 14/11/23 06:14:52
6
7# module Rubber (see rubber.scm)
8#  add_named_mark(samp, name, snd, chn)
9#  derumble_sound(snd, chn)
10#  sample_sound(snd, chn)
11#  unsample_sound(snd, chn)
12#  crossings()
13#  env_add(s0, s1, samps)
14#  rubber_sound(stretch, snd, chn)
15
16require "clm"
17
18module Rubber
19  doc "#{self.class} #{self.name} is the translation of rubber.scm.\n"
20
21  $zeros_checked = 8
22  $extension = 10.0
23  $show_details = false
24
25  def add_named_mark(samp, name, snd = false, chn = false)
26    m = add_mark(samp.round, snd, chn)
27    set_mark_name(m, name)
28    m
29  end
30
31  def derumble_sound(snd = false, chn = false)
32    old_length = framples(snd, chn)
33    pow2 = (log([old_length, srate(snd)].min) / log(2)).ceil
34    fftlen = (2 ** pow2).round
35    flt_env = [0.0, 0.0, 32.0 / srate(snd), 0.0,
36               40.0 / srate(snd), 1.0, 1.0, 1.0]
37    filter_sound(flt_env, fftlen, snd, chn)
38    set_framples(old_length, snd, chn)
39  end
40
41  def sample_sound(snd = false, chn = false)
42    if $extension != 1.0
43      src_sound(1.0 / $extension, 1.0, snd, chn)
44    end
45  end
46
47  def unsample_sound(snd = false, chn = false)
48    if $extension != 1.0
49      src_sound($extension, 1.0, snd, chn)
50    end
51  end
52
53  def crossings
54    crosses = 0
55    sr0 = make_sampler(0)
56    samp0 = next_sample(sr0)
57    len = framples()
58    sum = 0.0
59    last_cross = 0
60    silence = $extension * 0.001
61    (0...len).each do |i|
62      samp1 = next_sample(sr0)
63      if samp0 <= 0.0 and
64          samp1 > 0.0 and
65          (i - last_cross) > 4 and
66          sum > silence
67        crosses += 1
68        last_cross = i
69        sum = 0.0
70      end
71      sum += samp0.abs
72      samp0 = samp1
73    end
74    crosses
75  end
76
77  def env_add(s0, s1, samps)
78    data = make_vct(samps.round)
79    x = 1.0
80    xinc = 1.0 / samps
81    sr0 = make_sampler(s0.round)
82    sr1 = make_sampler(s1.round)
83    (0...samps).each do |i|
84      data[i] = x * next_sample(sr0) + (1.0 - x) * next_sample(sr1)
85      x += xinc
86    end
87    data
88  end
89
90  def rubber_sound(stretch, snd = false, chn = false)
91    as_one_edit(lambda do | |
92                  derumble_sound(snd, chn)
93                  sample_sound(snd, chn)
94                  crosses = crossings()
95                  cross_samples = make_vct(crosses)
96                  cross_weights = make_vct(crosses)
97                  cross_marks = make_vct(crosses)
98                  cross_periods = make_vct(crosses)
99                  sr0 = make_sampler(0, snd, chn)
100                  samp0 = next_sample(sr0)
101                  len = framples()
102                  sum = 0.0
103                  last_cross = 0
104                  cross = 0
105                  silence = $extension * 0.001
106                  (0...len).each do |i|
107                    samp1 = next_sample(sr0)
108                    if samp0 <= 0.0 and
109                        samp1 > 0.0 and
110                        (i - last_cross) > 4 and
111                        sum > silence
112                      last_cross = i
113                      sum = 0.0
114                      cross_samples[cross] = i
115                      cross += 1
116                    end
117                    sum += samp0.abs
118                    samp0 = samp1
119                  end
120                  (0...(crosses - 1)).each do |i|
121                    start = cross_samples[i]
122                    autolen = 0
123                    s0 = start
124                    pow2 = (log($extension * (srate() / 40.0)) / log(2)).ceil
125                    fftlen = (2 ** pow2).round
126                    len4 = fftlen / 4
127                    data = make_vct(fftlen)
128                    reader = make_sampler(s0.round)
129                    (0...fftlen).each do |j|
130                      data[j] = next_sample(reader)
131                    end
132                    autocorrelate(data)
133                    autolen = 0
134                    (1...len4).detect do |j|
135                      if data[j] < data[j + 1] and data[j + 1] > data[j + 2]
136                        autolen = j * 2
137                        true
138                      end
139                    end
140                    next_start = start + autolen
141                    min_i = i + 1
142                    min_samps = (cross_samples[min_i] - next_start).abs
143                    ((i + 2)...[crosses, i + $zeros_checked].min).each do |k|
144                      dist = (cross_samples[k] - next_start).abs
145                      if dist < min_samps
146                        min_samps = dist
147                        min_i = k
148                      end
149                    end
150                    current_mark = min_i
151                    current_min = 0.0
152                    s0 = start
153                    s1 = cross_samples[current_mark]
154                    len = autolen
155                    sr0 = make_sampler(s0.round)
156                    sr1 = make_sampler(s1.round)
157                    ampsum = 0.0
158                    diffsum = 0.0
159                    (0...len).each do |dummy|
160                      samp0 = next_sample(sr0)
161                      samp1 = next_sample(sr1)
162                      ampsum += samp0.abs
163                      diffsum += (samp1 - samp0).abs
164                    end
165                    if diffsum == 0.0
166                      current_min = 0.0
167                    else
168                      current_min = diffsum / ampsum
169                    end
170                    min_samps = 0.5 * current_min
171                    top = [crosses - 1, current_mark, i + $zeros_checked].min
172                    ((i + 1)...top).each do |k|
173                      wgt = 0.0
174                      s0 = start
175                      s1 = cross_samples[k]
176                      len = autolen
177                      sr0 = make_sampler(s0.round)
178                      sr1 = make_sampler(s1.round)
179                      ampsum = 0.0
180                      diffsum = 0.0
181                      (0...len).each do |dummy|
182                        samp0 = next_sample(sr0)
183                        samp1 = next_sample(sr1)
184                        ampsum += samp0.abs
185                        diffsum += (samp1 - samp0).abs
186                      end
187                      if diffsum == 0.0
188                        wgt = 0.0
189                      else
190                        wgt = diffsum / ampsum
191                      end
192                      if wgt < min_samps
193                        min_samps = wgt
194                        min_i = k
195                      end
196                    end
197                    unless current_mark == min_i
198                      cross_weights[i] = 1000.0
199                    else
200                      cross_weights[i] = current_min
201                      cross_marks[i] = current_mark
202                      cross_periods[i] = cross_samples[current_mark] -
203                                         cross_samples[i]
204                    end
205                  end
206                  len = framples(snd, chn)
207                  adding = (stretch > 1.0)
208                  samps = ((stretch - 1.0).abs * len).round
209                  needed_samps = (adding ? samps : [len, samps * 2].min)
210                  handled = 0
211                  mult = 1
212                  curs = 0
213                  weigths = cross_weights.length
214                  edits = make_vct(weigths)
215                  until curs == weigths or handled >= needed_samps
216                    best_mark = -1
217                    old_handled = handled
218                    cur = 0
219                    curmin = cross_weights[0]
220                    cross_weights.each_with_index do |cross_w, i|
221                      if cross_w < curmin
222                        cur = i
223                        curmin = cross_w
224                      end
225                    end
226                    best_mark = cur
227                    handled += cross_periods[best_mark].round
228                    if (handled < needed_samps) or
229                       ((handled - needed_samps) < (needed_samps - old_handled))
230                      edits[curs] = best_mark
231                      curs += 1
232                    end
233                    cross_weights[best_mark] = 1000.0
234                  end
235                  mult = (needed_samps / handled).ceil if curs >= weigths
236                  changed_len = 0
237                  weights = cross_weights.length
238                  (0...curs).detect do |i|
239                    best_mark = edits[i].round
240                    beg = cross_samples[best_mark].to_i
241                    next_beg = cross_samples[cross_marks[best_mark].round]
242                    len = cross_periods[best_mark].to_i
243                    if len > 0
244                      if adding
245                        new_samps = env_add(beg, next_beg, len)
246                        if $show_details
247                          add_named_mark(beg,
248                                         format("%d:%d",
249                                                i, (len / $extension).round))
250                        end
251                        insert_samples(beg, len, new_samps)
252                        if mult > 1
253                          (1...mult).each do |k|
254                            insert_samples(beg + k * len, len, new_samps)
255                          end
256                        end
257                        changed_len = changed_len + mult * len
258                        (0...weights).each do |j|
259                          curbeg = cross_samples[j]
260                          if curbeg > beg
261                            cross_samples[j] = curbeg + len
262                          end
263                        end
264                      else
265                        frms = framples()
266                        if beg >= frms
267                          Snd.display("trouble at %d: %d of %d", i, beg, frms)
268                        end
269                        if $show_details
270                          add_named_mark(beg - 1,
271                                         format("%d:%d",
272                                                i, (len / $extension).round))
273                        end
274                        delete_samples(beg, len)
275                        changed_len += len
276                        fin = beg + len
277                        (0...weights).each do |j|
278                          curbeg = cross_samples[j]
279                          if curbeg > beg
280                            if curbeg < fin
281                              cross_periods[j] = 0
282                            else
283                              cross_samples[j] = curbeg - len
284                            end
285                          end
286                        end
287                      end
288                    end
289                    changed_len > samps
290                  end
291                  if $show_details
292                    Snd.display("wanted: %d, got %d",
293                                samps.round, changed_len.round)
294                  end
295                  unsample_sound(snd, chn)
296                  if $show_details
297                    frms0 = framples(snd, chn, 0)
298                    Snd.display("%f -> %f (%f)",
299                                frms0,
300                                framples(snd,chn),
301                                (stretch * frms0).round)
302                  end
303                end,
304                format("rubber_sound(%f, %p, %p,", stretch, snd, chn))
305  end
306end
307
308include Rubber
309
310# rubber.rb ends here
311