summaryrefslogtreecommitdiff
path: root/rubber.rb
blob: 7181d77b84a266919050833579212d872d85e314 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# rubber.rb -- Translation of rubber.scm

# Translator/Author: Michael Scholz <mi-scholz@users.sourceforge.net>
# Created: 03/02/28 03:04:03
# Changed: 14/11/23 06:14:52

# module Rubber (see rubber.scm)
#  add_named_mark(samp, name, snd, chn)
#  derumble_sound(snd, chn)
#  sample_sound(snd, chn)
#  unsample_sound(snd, chn)
#  crossings()
#  env_add(s0, s1, samps)
#  rubber_sound(stretch, snd, chn)

require "clm"

module Rubber
  doc "#{self.class} #{self.name} is the translation of rubber.scm.\n"
  
  $zeros_checked = 8
  $extension = 10.0
  $show_details = false
  
  def add_named_mark(samp, name, snd = false, chn = false)
    m = add_mark(samp.round, snd, chn)
    set_mark_name(m, name)
    m
  end

  def derumble_sound(snd = false, chn = false)
    old_length = framples(snd, chn)
    pow2 = (log([old_length, srate(snd)].min) / log(2)).ceil
    fftlen = (2 ** pow2).round
    flt_env = [0.0, 0.0, 32.0 / srate(snd), 0.0,
               40.0 / srate(snd), 1.0, 1.0, 1.0]
    filter_sound(flt_env, fftlen, snd, chn)
    set_framples(old_length, snd, chn)
  end

  def sample_sound(snd = false, chn = false)
    if $extension != 1.0
      src_sound(1.0 / $extension, 1.0, snd, chn)
    end
  end

  def unsample_sound(snd = false, chn = false)
    if $extension != 1.0
      src_sound($extension, 1.0, snd, chn)
    end
  end

  def crossings
    crosses = 0
    sr0 = make_sampler(0)
    samp0 = next_sample(sr0)
    len = framples()
    sum = 0.0
    last_cross = 0
    silence = $extension * 0.001
    (0...len).each do |i|
      samp1 = next_sample(sr0)
      if samp0 <= 0.0 and
          samp1 > 0.0 and
          (i - last_cross) > 4 and
          sum > silence
        crosses += 1
        last_cross = i
        sum = 0.0
      end
      sum += samp0.abs
      samp0 = samp1
    end
    crosses
  end

  def env_add(s0, s1, samps)
    data = make_vct(samps.round)
    x = 1.0
    xinc = 1.0 / samps
    sr0 = make_sampler(s0.round)
    sr1 = make_sampler(s1.round)
    (0...samps).each do |i|
      data[i] = x * next_sample(sr0) + (1.0 - x) * next_sample(sr1)
      x += xinc
    end
    data
  end

  def rubber_sound(stretch, snd = false, chn = false)
    as_one_edit(lambda do | |
                  derumble_sound(snd, chn)
                  sample_sound(snd, chn)
                  crosses = crossings()
                  cross_samples = make_vct(crosses)
                  cross_weights = make_vct(crosses)
                  cross_marks = make_vct(crosses)
                  cross_periods = make_vct(crosses)
                  sr0 = make_sampler(0, snd, chn)
                  samp0 = next_sample(sr0)
                  len = framples()
                  sum = 0.0
                  last_cross = 0
                  cross = 0
                  silence = $extension * 0.001
                  (0...len).each do |i|
                    samp1 = next_sample(sr0)
                    if samp0 <= 0.0 and
                        samp1 > 0.0 and
                        (i - last_cross) > 4 and
                        sum > silence
                      last_cross = i
                      sum = 0.0
                      cross_samples[cross] = i
                      cross += 1
                    end
                    sum += samp0.abs
                    samp0 = samp1
                  end
                  (0...(crosses - 1)).each do |i|
                    start = cross_samples[i]
                    autolen = 0
                    s0 = start
                    pow2 = (log($extension * (srate() / 40.0)) / log(2)).ceil
                    fftlen = (2 ** pow2).round
                    len4 = fftlen / 4
                    data = make_vct(fftlen)
                    reader = make_sampler(s0.round)
                    (0...fftlen).each do |j|
                      data[j] = next_sample(reader)
                    end
                    autocorrelate(data)
                    autolen = 0
                    (1...len4).detect do |j|
                      if data[j] < data[j + 1] and data[j + 1] > data[j + 2]
                        autolen = j * 2
                        true
                      end
                    end
                    next_start = start + autolen
                    min_i = i + 1
                    min_samps = (cross_samples[min_i] - next_start).abs
                    ((i + 2)...[crosses, i + $zeros_checked].min).each do |k|
                      dist = (cross_samples[k] - next_start).abs
                      if dist < min_samps
                        min_samps = dist
                        min_i = k
                      end
                    end
                    current_mark = min_i
                    current_min = 0.0
                    s0 = start
                    s1 = cross_samples[current_mark]
                    len = autolen
                    sr0 = make_sampler(s0.round)
                    sr1 = make_sampler(s1.round)
                    ampsum = 0.0
                    diffsum = 0.0
                    (0...len).each do |dummy|
                      samp0 = next_sample(sr0)
                      samp1 = next_sample(sr1)
                      ampsum += samp0.abs
                      diffsum += (samp1 - samp0).abs
                    end
                    if diffsum == 0.0
                      current_min = 0.0
                    else
                      current_min = diffsum / ampsum
                    end
                    min_samps = 0.5 * current_min
                    top = [crosses - 1, current_mark, i + $zeros_checked].min
                    ((i + 1)...top).each do |k|
                      wgt = 0.0
                      s0 = start
                      s1 = cross_samples[k]
                      len = autolen
                      sr0 = make_sampler(s0.round)
                      sr1 = make_sampler(s1.round)
                      ampsum = 0.0
                      diffsum = 0.0
                      (0...len).each do |dummy|
                        samp0 = next_sample(sr0)
                        samp1 = next_sample(sr1)
                        ampsum += samp0.abs
                        diffsum += (samp1 - samp0).abs
                      end
                      if diffsum == 0.0
                        wgt = 0.0
                      else
                        wgt = diffsum / ampsum
                      end
                      if wgt < min_samps
                        min_samps = wgt
                        min_i = k
                      end
                    end
                    unless current_mark == min_i
                      cross_weights[i] = 1000.0
                    else
                      cross_weights[i] = current_min
                      cross_marks[i] = current_mark
                      cross_periods[i] = cross_samples[current_mark] -
                                         cross_samples[i]
                    end
                  end
                  len = framples(snd, chn)
                  adding = (stretch > 1.0)
                  samps = ((stretch - 1.0).abs * len).round
                  needed_samps = (adding ? samps : [len, samps * 2].min)
                  handled = 0
                  mult = 1
                  curs = 0
                  weigths = cross_weights.length
                  edits = make_vct(weigths)
                  until curs == weigths or handled >= needed_samps
                    best_mark = -1
                    old_handled = handled
                    cur = 0
                    curmin = cross_weights[0]
                    cross_weights.each_with_index do |cross_w, i|
                      if cross_w < curmin
                        cur = i
                        curmin = cross_w
                      end
                    end
                    best_mark = cur
                    handled += cross_periods[best_mark].round
                    if (handled < needed_samps) or
                       ((handled - needed_samps) < (needed_samps - old_handled))
                      edits[curs] = best_mark
                      curs += 1
                    end
                    cross_weights[best_mark] = 1000.0
                  end
                  mult = (needed_samps / handled).ceil if curs >= weigths
                  changed_len = 0
                  weights = cross_weights.length
                  (0...curs).detect do |i|
                    best_mark = edits[i].round
                    beg = cross_samples[best_mark].to_i
                    next_beg = cross_samples[cross_marks[best_mark].round]
                    len = cross_periods[best_mark].to_i
                    if len > 0
                      if adding
                        new_samps = env_add(beg, next_beg, len)
                        if $show_details
                          add_named_mark(beg,
                                         format("%d:%d",
                                                i, (len / $extension).round))
                        end
                        insert_samples(beg, len, new_samps)
                        if mult > 1
                          (1...mult).each do |k|
                            insert_samples(beg + k * len, len, new_samps)
                          end
                        end
                        changed_len = changed_len + mult * len
                        (0...weights).each do |j|
                          curbeg = cross_samples[j]
                          if curbeg > beg
                            cross_samples[j] = curbeg + len
                          end
                        end
                      else
                        frms = framples()
                        if beg >= frms
                          Snd.display("trouble at %d: %d of %d", i, beg, frms)
                        end
                        if $show_details
                          add_named_mark(beg - 1,
                                         format("%d:%d",
                                                i, (len / $extension).round))
                        end
                        delete_samples(beg, len)
                        changed_len += len
                        fin = beg + len
                        (0...weights).each do |j|
                          curbeg = cross_samples[j]
                          if curbeg > beg
                            if curbeg < fin
                              cross_periods[j] = 0
                            else
                              cross_samples[j] = curbeg - len
                            end
                          end
                        end
                      end
                    end
                    changed_len > samps
                  end
                  if $show_details
                    Snd.display("wanted: %d, got %d",
                                samps.round, changed_len.round)
                  end
                  unsample_sound(snd, chn)
                  if $show_details
                    frms0 = framples(snd, chn, 0)
                    Snd.display("%f -> %f (%f)",
                                frms0,
                                framples(snd,chn),
                                (stretch * frms0).round)
                  end
                end,
                format("rubber_sound(%f, %p, %p,", stretch, snd, chn))
  end
end

include Rubber

# rubber.rb ends here