Class: Vizcore::Analysis::Pipeline
- Inherits:
-
Object
- Object
- Vizcore::Analysis::Pipeline
- 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
- DEFAULT_FFT_PREVIEW_BINS =
32- BEATS_PER_BAR =
4- BEATS_PER_PHRASE =
32- BEAT_SUBDIVISIONS =
{ beat_2: 2, beat_4: 4, beat_8: 8, beat_triplet: 3 }.freeze
- BAND_KEYS =
%i[sub low mid high].freeze
- SILENCE_RESET_FRAMES =
90
Instance Attribute Summary collapse
-
#band_splitter ⇒ Object
readonly
Returns the value of attribute band_splitter.
-
#beat_detector ⇒ Object
readonly
Returns the value of attribute beat_detector.
-
#bpm_estimator ⇒ Object
readonly
Returns the value of attribute bpm_estimator.
-
#fft_processor ⇒ Object
readonly
Returns the value of attribute fft_processor.
-
#smoother ⇒ Object
readonly
Returns the value of attribute smoother.
Instance Method Summary collapse
-
#audio_normalize=(settings) ⇒ Hash
Normalized settings.
- #bpm_lock=(settings) ⇒ Float?
-
#call(samples) ⇒ Hash
Normalized analysis payload consumed by frame broadcaster.
- #fft_preview_bins=(value) ⇒ Integer
-
#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, onset_sensitivity: 1.0, fft_preview_bins: DEFAULT_FFT_PREVIEW_BINS, peak_hold_frames: 0, silence_reset_frames: SILENCE_RESET_FRAMES) ⇒ Pipeline
constructor
A new instance of Pipeline.
- #onset_sensitivity=(value) ⇒ Float
- #peak_hold_frames=(value) ⇒ Integer
- #silence_reset_frames=(value) ⇒ Integer
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, onset_sensitivity: 1.0, fft_preview_bins: DEFAULT_FFT_PREVIEW_BINS, peak_hold_frames: 0, silence_reset_frames: SILENCE_RESET_FRAMES) ⇒ Pipeline
Returns a new instance of Pipeline.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/vizcore/analysis/pipeline.rb', line 34 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, onset_sensitivity: 1.0, fft_preview_bins: DEFAULT_FFT_PREVIEW_BINS, peak_hold_frames: 0, silence_reset_frames: SILENCE_RESET_FRAMES) @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.onset_sensitivity = onset_sensitivity self.fft_preview_bins = fft_preview_bins self.peak_hold_frames = peak_hold_frames self.silence_reset_frames = silence_reset_frames @beat_pulse = 0.0 @beat_phase = 0.0 @last_bpm = 0.0 self.bpm_lock = { bpm: bpm, locked: bpm_lock } self.audio_normalize = audio_normalize @silent_frame_count = 0 @band_peak_state = {} @previous_onset_amplitude = 0.0 @previous_onset_bands = {} @previous_flux_spectrum = nil end |
Instance Attribute Details
#band_splitter ⇒ Object (readonly)
Returns the value of attribute band_splitter.
18 19 20 |
# File 'lib/vizcore/analysis/pipeline.rb', line 18 def band_splitter @band_splitter end |
#beat_detector ⇒ Object (readonly)
Returns the value of attribute beat_detector.
18 19 20 |
# File 'lib/vizcore/analysis/pipeline.rb', line 18 def beat_detector @beat_detector end |
#bpm_estimator ⇒ Object (readonly)
Returns the value of attribute bpm_estimator.
18 19 20 |
# File 'lib/vizcore/analysis/pipeline.rb', line 18 def bpm_estimator @bpm_estimator end |
#fft_processor ⇒ Object (readonly)
Returns the value of attribute fft_processor.
18 19 20 |
# File 'lib/vizcore/analysis/pipeline.rb', line 18 def fft_processor @fft_processor end |
#smoother ⇒ Object (readonly)
Returns the value of attribute smoother.
18 19 20 |
# File 'lib/vizcore/analysis/pipeline.rb', line 18 def smoother @smoother end |
Instance Method Details
#audio_normalize=(settings) ⇒ Hash
Returns normalized settings.
60 61 62 63 |
# File 'lib/vizcore/analysis/pipeline.rb', line 60 def audio_normalize=(settings) @audio_normalize = normalize_audio_normalize(settings) @normalizer = build_normalizer(@audio_normalize) end |
#bpm_lock=(settings) ⇒ Float?
67 68 69 70 71 |
# File 'lib/vizcore/analysis/pipeline.rb', line 67 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.
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 |
# File 'lib/vizcore/analysis/pipeline.rb', line 99 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) tempo = tempo_features(beat_detected: beat_detected, beat_count: beat[:beat_count], bpm: bpm) peak = peak_level(samples) spectrum_preview = preview_spectrum(fft[:magnitudes], bins: @fft_preview_bins) spectral = spectral_features(fft[:magnitudes], spectrum_preview) normalized = normalize_features( amplitude: amplitude, bands: bands, fft: spectrum_preview ) band_peaks = update_band_peaks(normalized[:bands]) 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]), peak: peak, bands: @smoother.smooth_hash(normalized[:bands], namespace: :bands), band_peaks: band_peaks, 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], beat_phase: tempo[:beat_phase], beat_2: tempo[:beat_2], beat_4: tempo[:beat_4], beat_8: tempo[:beat_8], beat_triplet: tempo[:beat_triplet], bar_phase: tempo[:bar_phase], bar_count: tempo[:bar_count], phrase_count: tempo[:phrase_count], bpm: bpm, bpm_confidence: bpm_confidence, spectral_centroid: spectral[:centroid], spectral_rolloff: spectral[:rolloff], spectral_flatness: spectral[:flatness], spectral_flux: spectral[:flux], zero_crossing_rate: zero_crossing_rate(samples), peak_frequency: fft[:peak_frequency] } end |
#fft_preview_bins=(value) ⇒ Integer
81 82 83 |
# File 'lib/vizcore/analysis/pipeline.rb', line 81 def fft_preview_bins=(value) @fft_preview_bins = normalize_integer(value, fallback: DEFAULT_FFT_PREVIEW_BINS, min: 8, max: 128) end |
#onset_sensitivity=(value) ⇒ Float
75 76 77 |
# File 'lib/vizcore/analysis/pipeline.rb', line 75 def onset_sensitivity=(value) @onset_sensitivity = normalize_positive_number(value, fallback: 1.0) end |
#peak_hold_frames=(value) ⇒ Integer
87 88 89 |
# File 'lib/vizcore/analysis/pipeline.rb', line 87 def peak_hold_frames=(value) @peak_hold_frames = normalize_integer(value, fallback: 0, min: 0, max: 10_000) end |
#silence_reset_frames=(value) ⇒ Integer
93 94 95 |
# File 'lib/vizcore/analysis/pipeline.rb', line 93 def silence_reset_frames=(value) @silence_reset_frames = normalize_integer(value, fallback: SILENCE_RESET_FRAMES, min: 1, max: 10_000) end |