Module: Muze::Onset

Defined in:
lib/muze/onset/onset_detect.rb

Overview

Onset detection routines.

Class Method Summary collapse

Class Method Details

.onset_detect(y: nil, sr: 22_050, onset_envelope: nil, hop_length: 512, backtrack: false, units: :frames, pre_max: 1, post_max: 1, pre_avg: 1, post_avg: 1, delta: nil, wait: 0, adaptive: false, energy: nil) ⇒ Array<Integer, Float>

Parameters:

  • y (Numo::SFloat, Array<Float>, nil) (defaults to: nil)
  • sr (Integer) (defaults to: 22_050)
  • onset_envelope (Numo::SFloat, Array<Float>, nil) (defaults to: nil)
  • hop_length (Integer) (defaults to: 512)
  • backtrack (Boolean) (defaults to: false)
  • units (Symbol) (defaults to: :frames)

    :frames, :samples, or :time

Returns:

  • (Array<Integer, Float>)

Raises:



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
# File 'lib/muze/onset/onset_detect.rb', line 56

def onset_detect(y: nil, sr: 22_050, onset_envelope: nil, hop_length: 512, backtrack: false, units: :frames, pre_max: 1, post_max: 1, pre_avg: 1, post_avg: 1, delta: nil, wait: 0, adaptive: false, energy: nil)
  validate_positive_integer!(sr, "sr")
  validate_positive_integer!(hop_length, "hop_length")
  validate_peak_picker_args!(pre_max:, post_max:, pre_avg:, post_avg:, wait:, delta:)
  raise Muze::ParameterError, "backtrack must be true or false" unless [true, false].include?(backtrack)
  raise Muze::ParameterError, "adaptive must be true or false" unless [true, false].include?(adaptive)

  envelope = if onset_envelope
               onset_envelope.is_a?(Numo::NArray) ? onset_envelope.to_a : Array(onset_envelope)
             else
              onset_strength(y:, sr:, hop_length:).to_a
             end
  validate_finite_array!(envelope, "onset_envelope")

  return [] if envelope.length < 3

  threshold = delta || detection_threshold(envelope)
  peaks = detect_peaks(envelope, threshold, pre_max:, post_max:, pre_avg:, post_avg:, wait:, adaptive:)
  if backtrack
    energy_curve = energy ? Array(energy) : envelope
    validate_finite_array!(energy_curve, "energy")
    peaks = backtrack_onsets(energy_curve, peaks)
  end

  convert_units(peaks, units:, sr:, hop_length:)
end

.onset_strength(y: nil, sr: 22_050, s: nil, hop_length: 512, n_fft: 2048, lag: 1, log: false, max_size: 1, normalize: false) ⇒ Numo::SFloat

Returns onset envelope per frame.

Parameters:

  • y (Numo::SFloat, Array<Float>, nil) (defaults to: nil)
  • sr (Integer) (defaults to: 22_050)
  • s (Numo::SFloat, nil) (defaults to: nil)
  • hop_length (Integer) (defaults to: 512)
  • n_fft (Integer) (defaults to: 2048)

Returns:

  • (Numo::SFloat)

    onset envelope per frame

Raises:



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
# File 'lib/muze/onset/onset_detect.rb', line 14

def onset_strength(y: nil, sr: 22_050, s: nil, hop_length: 512, n_fft: 2048, lag: 1, log: false, max_size: 1, normalize: false)
  validate_positive_integer!(sr, "sr")
  validate_positive_integer!(hop_length, "hop_length")
  validate_positive_integer!(n_fft, "n_fft")
  validate_positive_integer!(lag, "lag")
  validate_positive_integer!(max_size, "max_size")
  raise Muze::ParameterError, "log must be true or false" unless [true, false].include?(log)
  raise Muze::ParameterError, "normalize must be true or false" unless [true, false].include?(normalize)

  spectrum = if s
               provided = Numo::SFloat.cast(s)
               validate_finite_array!(provided.to_a.flatten, "s")
               provided
             else
               Muze::Feature.melspectrogram(y:, sr:, n_fft:, hop_length:, n_mels: 40)
             end

  spectrum = spectrum.expand_dims(1) if spectrum.ndim == 1
  spectrum = Muze.power_to_db(spectrum, ref: :max) if log
  spectrum = local_max_filter(spectrum, max_size:) if max_size > 1
  _, frames = spectrum.shape
  envelope = Numo::SFloat.zeros(frames)

  frames.times do |frame_index|
    next if frame_index < lag

    delta = spectrum[true, frame_index] - spectrum[true, frame_index - lag]
    envelope[frame_index] = delta.clip(0.0, Float::INFINITY).sum
  end

  peak = envelope.max
  envelope = envelope / peak if normalize && peak.positive?
  envelope
end