Class: RoadToRubykaigi::SignalWindow

Inherits:
Object
  • Object
show all
Defined in:
lib/road_to_rubykaigi/signal_window.rb

Defined Under Namespace

Classes: StepCadence

Constant Summary collapse

BUFFER_SECONDS =
0.5
READY_FILL_RATIO =

window must be 80% filled (by time) before considered ready

0.8

Instance Method Summary collapse

Instance Method Details

#axis_intensitiesObject

Per-axis RMS spread. Same unit as motion_intensity but kept separate so callers can compare how energy is distributed across axes.



78
79
80
# File 'lib/road_to_rubykaigi/signal_window.rb', line 78

def axis_intensities
  [0, 1, 2].map { |index| Math.sqrt(axis_variance(index)) }
end

#buffer_sample(sample) ⇒ Object



54
55
56
57
58
59
60
# File 'lib/road_to_rubykaigi/signal_window.rb', line 54

def buffer_sample(sample)
  now = Time.now
  @samples << { time: now, sample: sample }
  window_start = now - BUFFER_SECONDS
  @samples.shift while @samples.first[:time] < window_start
  @step_cadence.record(sample)
end

#cadence_hzObject



62
# File 'lib/road_to_rubykaigi/signal_window.rb', line 62

def cadence_hz = @step_cadence.hz

#cadence_ready?Boolean

Returns:

  • (Boolean)


63
# File 'lib/road_to_rubykaigi/signal_window.rb', line 63

def cadence_ready? = @step_cadence.ready?

#fullObject



115
116
117
# File 'lib/road_to_rubykaigi/signal_window.rb', line 115

def full
  self
end

#full?Boolean

Returns:

  • (Boolean)


65
66
67
68
# File 'lib/road_to_rubykaigi/signal_window.rb', line 65

def full?
  return false if @samples.size < 2
  (@samples.last[:time] - @samples.first[:time]) >= BUFFER_SECONDS * READY_FILL_RATIO
end

#last_magnitudeObject

Raw acceleration magnitude of the latest sample: sqrt(x² + y² + z²).



83
84
85
86
# File 'lib/road_to_rubykaigi/signal_window.rb', line 83

def last_magnitude
  sample = @samples.last[:sample]
  Math.sqrt(sample[0] ** 2 + sample[1] ** 2 + sample[2] ** 2)
end

#last_sampleObject

Raw [x, y, z] of the latest sample (before variance/intensity).



89
90
91
# File 'lib/road_to_rubykaigi/signal_window.rb', line 89

def last_sample
  @samples.last[:sample]
end

#last_vertical_acceleration(gravity) ⇒ Object

Signed vertical acceleration of the latest sample after removing gravity. Positive = accelerating upward, negative = downward.



95
96
97
98
99
100
101
102
103
104
# File 'lib/road_to_rubykaigi/signal_window.rb', line 95

def last_vertical_acceleration(gravity)
  sample = @samples.last[:sample]
  # Magnitude of the gravity reference vector (used to normalize the
  # projection below and as the resting 1g offset).
  gravity_magnitude = Math.sqrt(gravity[0] ** 2 + gravity[1] ** 2 + gravity[2] ** 2)
  # Sample projected onto the vertical axis, normalized to g units.
  projection = (sample[0] * gravity[0] + sample[1] * gravity[1] + sample[2] * gravity[2]) / gravity_magnitude
  # Subtract the resting offset.
  projection - gravity_magnitude
end

#mag_jerkObject

Average absolute change in magnitude between consecutive samples. Walking/running produce sharp footstrike impacts (high jerk), while jumping produces smoother acceleration curves (low jerk).



109
110
111
112
113
# File 'lib/road_to_rubykaigi/signal_window.rb', line 109

def mag_jerk
  mags = @samples.map { |entry| s = entry[:sample]; Math.sqrt(s[0] ** 2 + s[1] ** 2 + s[2] ** 2) }
  deltas = mags.each_cons(2).map { |a, b| (b - a).abs }
  deltas.sum / deltas.size
end

#motion_intensityObject

Returns how far samples in the window spread from their mean position (RMS distance across all 3 axes).



72
73
74
# File 'lib/road_to_rubykaigi/signal_window.rb', line 72

def motion_intensity
  Math.sqrt(axis_variance(0) + axis_variance(1) + axis_variance(2))
end

#tail(seconds:) ⇒ Object

Sub-window containing samples from the last ‘seconds`, for continuation detection.



120
121
122
123
124
# File 'lib/road_to_rubykaigi/signal_window.rb', line 120

def tail(seconds:)
  return SignalWindow.new([]) if @samples.empty?
  cutoff = @samples.last[:time] - seconds
  SignalWindow.new(@samples.select { |entry| entry[:time] >= cutoff })
end