Class: Vizcore::Analysis::Pipeline

Inherits:
Object
  • Object
show all
Defined in:
lib/vizcore/analysis/pipeline.rb

Overview

End-to-end analysis pipeline from PCM samples to renderer-ready features.

Constant Summary collapse

BEAT_PULSE_DECAY =
0.86
BEAT_PULSE_FLOOR =
0.001
DEFAULT_NOISE_GATE =
0.01
DEFAULT_AUDIO_NORMALIZE =
{ mode: :off }.freeze
SILENCE_RESET_FRAMES =
90

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sample_rate: 44_100, fft_size: 1024, window: :hamming, beat_detector: nil, bpm_estimator: nil, smoother: nil, noise_gate: DEFAULT_NOISE_GATE, audio_normalize: nil, bpm: nil, bpm_lock: false) ⇒ Pipeline

Returns a new instance of Pipeline.

Parameters:

  • sample_rate (Integer) (defaults to: 44_100)
  • fft_size (Integer) (defaults to: 1024)
  • window (Symbol) (defaults to: :hamming)
  • beat_detector (Vizcore::Analysis::BeatDetector, nil) (defaults to: nil)
  • bpm_estimator (Vizcore::Analysis::BPMEstimator, nil) (defaults to: nil)
  • smoother (Vizcore::Analysis::Smoother, nil) (defaults to: nil)
  • noise_gate (Numeric) (defaults to: DEFAULT_NOISE_GATE)

    RMS threshold below which input is treated as silence

  • audio_normalize (Hash, nil) (defaults to: nil)

    optional audio normalization settings

  • bpm (Numeric, nil) (defaults to: nil)

    fixed BPM value used when bpm_lock is true

  • bpm_lock (Boolean) (defaults to: false)

    true when BPM output should stay fixed



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/vizcore/analysis/pipeline.rb', line 25

def initialize(sample_rate: 44_100, fft_size: 1024, window: :hamming, beat_detector: nil, bpm_estimator: nil, smoother: nil, noise_gate: DEFAULT_NOISE_GATE, audio_normalize: nil, bpm: nil, bpm_lock: false)
  @fft_processor = FFTProcessor.new(sample_rate: sample_rate, fft_size: fft_size, window: window)
  @band_splitter = BandSplitter.new(sample_rate: sample_rate, fft_size: fft_size)
  @beat_detector = beat_detector || BeatDetector.new
  @analysis_frame_rate = sample_rate.to_f / fft_size.to_f
  @bpm_estimator = bpm_estimator || BPMEstimator.new(frame_rate: @analysis_frame_rate)
  @smoother = smoother || Smoother.new(alpha: 0.35)
  @noise_gate = normalize_noise_gate(noise_gate)
  self.bpm_lock = { bpm: bpm, locked: bpm_lock }
  self.audio_normalize = audio_normalize
  @beat_pulse = 0.0
  @last_bpm = 0.0
  @silent_frame_count = 0
  @previous_onset_amplitude = 0.0
  @previous_onset_bands = {}
end

Instance Attribute Details

#band_splitterObject (readonly)

Returns the value of attribute band_splitter.



13
14
15
# File 'lib/vizcore/analysis/pipeline.rb', line 13

def band_splitter
  @band_splitter
end

#beat_detectorObject (readonly)

Returns the value of attribute beat_detector.



13
14
15
# File 'lib/vizcore/analysis/pipeline.rb', line 13

def beat_detector
  @beat_detector
end

#bpm_estimatorObject (readonly)

Returns the value of attribute bpm_estimator.



13
14
15
# File 'lib/vizcore/analysis/pipeline.rb', line 13

def bpm_estimator
  @bpm_estimator
end

#fft_processorObject (readonly)

Returns the value of attribute fft_processor.



13
14
15
# File 'lib/vizcore/analysis/pipeline.rb', line 13

def fft_processor
  @fft_processor
end

#smootherObject (readonly)

Returns the value of attribute smoother.



13
14
15
# File 'lib/vizcore/analysis/pipeline.rb', line 13

def smoother
  @smoother
end

Instance Method Details

#audio_normalize=(settings) ⇒ Hash

Returns normalized settings.

Parameters:

  • settings (Hash, nil)

Returns:

  • (Hash)

    normalized settings



44
45
46
47
# File 'lib/vizcore/analysis/pipeline.rb', line 44

def audio_normalize=(settings)
  @audio_normalize = normalize_audio_normalize(settings)
  @normalizer = build_normalizer(@audio_normalize)
end

#bpm_lock=(settings) ⇒ Float?

Parameters:

  • settings (Hash)

Returns:

  • (Float, nil)


51
52
53
54
55
# File 'lib/vizcore/analysis/pipeline.rb', line 51

def bpm_lock=(settings)
  values = symbolize_hash(settings)
  @locked_bpm = normalize_locked_bpm(values[:bpm], bpm_lock: values[:locked])
  @last_bpm = @locked_bpm if @locked_bpm
end

#call(samples) ⇒ Hash

Returns normalized analysis payload consumed by frame broadcaster.

Parameters:

  • samples (Array<Numeric>)

    audio frame samples

Returns:

  • (Hash)

    normalized analysis payload consumed by frame broadcaster



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
# File 'lib/vizcore/analysis/pipeline.rb', line 59

def call(samples)
  amplitude = rms(samples)
  if silence?(amplitude)
    track_silent_frame(samples)
    return silent_frame(reset_tempo: sustained_silence?)
  end

  @silent_frame_count = 0

  fft = @fft_processor.call(samples)
  bands = @band_splitter.call(fft[:magnitudes])
  beat = @beat_detector.call(samples)
  beat_detected = beat[:beat]
  confidence = beat_confidence(beat)
  @beat_pulse = beat_detected ? 1.0 : @beat_pulse * BEAT_PULSE_DECAY
  @beat_pulse = 0.0 if @beat_pulse < BEAT_PULSE_FLOOR
  bpm = resolve_bpm(beat_detected)
  normalized = normalize_features(
    amplitude: amplitude,
    bands: bands,
    fft: preview_spectrum(fft[:magnitudes])
  )
  onsets = detect_onsets(amplitude: normalized[:amplitude], bands: normalized[:bands])
  drums = detect_drum_sources(bands: normalized[:bands], onsets: onsets[:bands])

  {
    amplitude: @smoother.smooth(:amplitude, normalized[:amplitude]),
    bands: @smoother.smooth_hash(normalized[:bands], namespace: :bands),
    fft: @smoother.smooth_array(normalized[:fft], namespace: :fft),
    onset: onsets[:amplitude],
    onsets: onsets[:bands],
    drums: drums,
    beat: beat_detected,
    beat_confidence: confidence,
    beat_pulse: @beat_pulse,
    beat_count: beat[:beat_count],
    bpm: bpm,
    peak_frequency: fft[:peak_frequency]
  }
end