Module: Musa::Datasets::GDVd

Includes:
DeltaD, DeltaI, Helper
Defined in:
lib/musa-dsl/datasets/gdvd.rb

Overview

Delta-encoded score events for efficient compression.

GDVd (Grade/Duration/Velocity delta) represents musical events using delta encoding - storing only changes from previous events. Extends DeltaD for flexible duration encoding and DeltaI for indexed deltas.

Purpose

GDVd provides efficient delta encoding for musical sequences:

  • Compact storage: Only changed values are stored
  • Efficient serialization: Neuma format uses delta notation
  • Lossless compression: Full reconstruction via #to_gdv
  • Musical patterns: Captures relative motion (intervals, velocity changes)

Encoding Types

Each parameter can be encoded as absolute or delta:

Pitch Encoding

Absolute:

  • abs_grade: Set grade to specific value
  • abs_sharps: Set chromatic alteration
  • abs_octave: Set octave to specific value

Delta:

  • delta_grade: Change grade by semitones
  • delta_sharps: Change chromatic alteration
  • delta_interval: Change by scale interval (with delta_interval_sign)
  • delta_octave: Change octave

Duration Encoding (from DeltaD)

  • abs_duration: Set duration
  • delta_duration: Add to duration
  • factor_duration: Multiply duration

Velocity Encoding

  • abs_velocity: Set velocity
  • delta_velocity: Add to velocity

Natural Keys

  • :abs_grade, :abs_sharps, :abs_octave: Absolute pitch
  • :delta_grade, :delta_sharps, :delta_interval, :delta_interval_sign, :delta_octave: Delta pitch
  • :abs_velocity, :delta_velocity: Velocity encoding
  • :abs_duration, :delta_duration, :factor_duration: Duration encoding
  • :modifiers: Hash of additional modifiers

Reconstruction

Delta events require a previous event for reconstruction:

gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
previous = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
gdv = gdvd.to_gdv(scale, previous: previous)
# => { grade: 2, octave: 0, duration: 1.0, velocity: 1 }

Neuma Delta Notation

Delta events use special notation in Neuma format:

  • Delta grade: "+2" or "-2" (semitone change)
  • Delta sharps: "+#" or "-_" (chromatic change)
  • Delta octave: "+o1" or "-o1" (octave change)
  • Delta duration: "+0.5" or "-0.5" (duration change)
  • Factor duration: "*2" or "*0.5" (duration multiply)
  • Delta velocity: "+f" or "-f" (dynamics change)

Examples:

First event (absolute encoding)

gdvd = { abs_grade: 0, abs_duration: 1.0, abs_velocity: 0 }.extend(GDVd)
gdvd.base_duration = 1/4r
gdvd.to_neuma  # => "(0 4 mp)"

Delta encoding (unchanged duration)

gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
gdvd.base_duration = 1/4r
gdvd.to_neuma  # => "(+2 +f)"
# Grade +2 semitones, velocity +1 (one f louder)

Chromatic change

gdvd = { delta_sharps: 1 }.extend(GDVd)
gdvd.to_neuma  # => "(+#)"
# Add one sharp

Duration multiplication

gdvd = { factor_duration: 2 }.extend(GDVd)
gdvd.base_duration = 1/4r
gdvd.to_neuma  # => "(. *2)"
# Double duration

Reconstruction from delta

previous = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = gdvd.to_gdv(scale, previous: previous)
# => { grade: 2, octave: 0, duration: 1.0, velocity: 1 }

Octave change

gdvd = { delta_grade: -2, delta_octave: 1 }.extend(GDVd)
gdvd.to_neuma  # => "(-2 +o1)"
# Down 2 semitones, up one octave

See Also:

Constant Summary collapse

NaturalKeys =

Natural keys for delta encoding.

Returns:

(NaturalKeys +
                    [:abs_grade, :abs_sharps, :abs_octave,
:delta_grade, :delta_sharps, :delta_interval_sign, :delta_interval, :delta_octave,
:abs_velocity, :delta_velocity,
:modifiers]).freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#base_durationRational

Base duration for time calculations.

Returns:



137
138
139
# File 'lib/musa-dsl/datasets/gdvd.rb', line 137

def base_duration
  @base_duration
end

Instance Method Details

#to_gdv(scale, previous:) ⇒ GDV

Reconstructs absolute GDV from delta encoding.

Applies delta changes to previous event to create new absolute event. Handles all encoding types (abs_, delta_, factor_) appropriately.

Examples:

Basic delta reconstruction

previous = { grade: 0, octave: 0, duration: 1.0, velocity: 0 }.extend(GDV)
gdvd = { delta_grade: 2, delta_velocity: 1 }.extend(GDVd)
gdv = gdvd.to_gdv(scale, previous: previous)
# => { grade: 2, octave: 0, duration: 1.0, velocity: 1 }

Absolute override

previous = { grade: 0, duration: 1.0 }.extend(GDV)
gdvd = { abs_grade: 5, abs_duration: 2.0 }.extend(GDVd)
gdv = gdvd.to_gdv(scale, previous: previous)
# => { grade: 5, duration: 2.0 }

Duration factor

previous = { grade: 0, duration: 1.0 }.extend(GDV)
gdvd = { factor_duration: 2 }.extend(GDVd)
gdv = gdvd.to_gdv(scale, previous: previous)
# => { grade: 0, duration: 2.0 }

Parameters:

  • scale (Musa::Scales::Scale)

    reference scale for pitch calculations

  • previous (GDV)

    previous absolute event (required for reconstruction)

Returns:

  • (GDV)

    reconstructed absolute event



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
# File 'lib/musa-dsl/datasets/gdvd.rb', line 184

def to_gdv(scale, previous:)
  r = previous.clone.delete_if {|k,_| !GDV::NaturalKeys.include?(k)}.extend GDV

  r.base_duration = @base_duration

  if include?(:abs_grade)
    if self[:abs_grade] == :silence
      r[:silence] = true
    else
      r.delete :silence
      r.delete :sharps

      r[:grade] = scale[self[:abs_grade]].wide_grade
      r[:sharps] = self[:abs_sharps] if include?(:abs_sharps)
    end

  elsif include?(:delta_grade)
    r.delete :silence

    r[:grade], r[:sharps] =
        normalize_to_scale(scale,
                           scale[r[:grade]].wide_grade + self[:delta_grade],
                           (r[:sharps] || 0) + (self[:delta_sharps] || 0))

    r.delete :sharps if r[:sharps].zero?

  elsif include?(:delta_interval)
    r.delete :silence

    sign = self[:delta_interval_sign] || 1

    r[:grade], r[:sharps] =
        normalize_to_scale scale,
                           scale[r[:grade]].wide_grade,
                           sign * scale.kind.tuning.scale_system.intervals[self[:delta_interval]]

    r.delete :sharps if r[:sharps].zero?

  elsif include?(:delta_sharps)
    r.delete :silence

    r[:grade], r[:sharps] =
        normalize_to_scale scale,
                           scale[r[:grade]].wide_grade,
                           (r[:sharps] || 0) + self[:delta_sharps]

    r.delete :sharps if r[:sharps].zero?
  end

  if include?(:abs_octave)
    r[:octave] = self[:abs_octave]
  elsif include?(:delta_octave)
    r[:octave] += self[:delta_octave]
  end

  if include?(:abs_duration)
    r[:duration] = self[:abs_duration]
  elsif include?(:delta_duration)
    r[:duration] += self[:delta_duration]
  elsif include?(:factor_duration)
    r[:duration] *= self[:factor_duration]
  end

  if include?(:abs_velocity)
    r[:velocity] = self[:abs_velocity]
  elsif include?(:delta_velocity)
    r[:velocity] += self[:delta_velocity]
  end

  if include?(:modifiers)
    self[:modifiers].each_pair do |k, v|
      r[k] = v
    end
  end

  (keys - NaturalKeys).each { |k| r[k] = self[k] }

  r
end

#to_neumaString

Converts to Neuma delta notation string.

Neuma delta format uses special notation for changes:

([grade_delta] [octave_delta] [duration_delta] [velocity_delta] [modifiers...])
  • Grade delta: "+2" or "-2" (semitone change)
  • Sharp delta: "+#" or "-_" (chromatic change)
  • Octave delta: "+o1" or "-o1" (octave change)
  • Duration delta: "+0.5", "-0.5", or "*2" (duration change)
  • Velocity delta: "+f" or "-f" (dynamics change by f's)

Examples:

Delta grade

gdvd = { delta_grade: 2 }.extend(GDVd)
gdvd.base_duration = 1/4r
gdvd.to_neuma  # => "(+2)"

Multiple deltas

gdvd = { delta_grade: -2, delta_velocity: 1 }.extend(GDVd)
gdvd.base_duration = 1/4r
gdvd.to_neuma  # => "(-2 +f)"

Duration factor

gdvd = { factor_duration: 2 }.extend(GDVd)
gdvd.base_duration = 1/4r
gdvd.to_neuma  # => "(. *2)"

Chromatic change

gdvd = { delta_sharps: 1 }.extend(GDVd)
gdvd.to_neuma  # => "(+#)"

Absolute values

gdvd = { abs_grade: 0, abs_duration: 1.0 }.extend(GDVd)
gdvd.base_duration = 1/4r
gdvd.to_neuma  # => "(0 4)"

Returns:

  • (String)

    Neuma delta notation



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/musa-dsl/datasets/gdvd.rb', line 324

def to_neuma
  @base_duration ||= Rational(1,4)

  attributes = []

  c = 0

  if include?(:abs_grade)
    attributes[c] = self[:abs_grade].to_s

  elsif include?(:delta_grade)
    attributes[c] = positive_sign_of(self[:delta_grade]) + self[:delta_grade].to_s unless self[:delta_grade].zero?

  elsif include?(:delta_interval)

    attributes[c] = self[:delta_interval_sign] if include?(:delta_interval_sign)
    attributes[c] ||= ''
    attributes[c] += self[:delta_interval].to_s
  end

  if include?(:delta_sharps) && !self[:delta_sharps].zero?
    char = self[:delta_sharps] > 0 ? '#' : '_'
    sign = attributes[c].nil? ? positive_sign_of(self[:delta_sharps]) : ''

    attributes[c] ||= ''
    attributes[c] += sign + char * self[:delta_sharps].abs
  end

  attributes[c] = '.' if attributes[c].nil? || attributes[c].empty?

  if include?(:abs_octave)
    attributes[c += 1] = 'o' + self[:abs_octave].to_s
  elsif include?(:delta_octave)
    attributes[c += 1] = sign_of(self[:delta_octave]) + 'o' + self[:delta_octave].abs.to_s if self[:delta_octave] != 0
  end

  if include?(:abs_duration)
    attributes[c += 1] = (self[:abs_duration] / @base_duration).to_s
  elsif include?(:delta_duration)
    attributes[c += 1] = positive_sign_of(self[:delta_duration]) + (self[:delta_duration] / @base_duration).to_s
  elsif include?(:factor_duration)
    attributes[c += 1] = '*' + self[:factor_duration].to_s
  end

  if include?(:abs_velocity)
    attributes[c += 1] = velocity_of(self[:abs_velocity])
  elsif include?(:delta_velocity)
    attributes[c += 1] = sign_of(self[:delta_velocity]) + 'f' * self[:delta_velocity].abs
  end

  (keys - NaturalKeys).each do |k|
    attributes[c += 1] = modificator_string(k, self[k])
  end

  '(' + attributes.join(' ') + ')'
end

#valid?Boolean Originally defined in module E

Checks if event is valid.

Base implementation always returns true. Subclasses should override to implement specific validation logic.

Examples:

event.valid?  # => true

Returns:

  • (Boolean)

    true if valid

#validate!void Originally defined in module E

This method returns an undefined value.

Validates event, raising if invalid.

Examples:

event.validate!  # Raises if invalid

Raises:

  • (RuntimeError)

    if event is not valid