Module: Musa::Datasets::PDV

Includes:
AbsD, Helper
Defined in:
lib/musa-dsl/datasets/pdv.rb

Overview

MIDI-style musical events with absolute pitches.

PDV (Pitch/Duration/Velocity) represents musical events using MIDI-like absolute pitch numbers. Extends AbsD for duration support.

Purpose

PDV is the MIDI representation layer of the dataset framework:

  • Uses absolute MIDI pitch numbers (0-127)
  • Uses MIDI velocity values (0-127)
  • Direct mapping to MIDI messages
  • Machine-oriented (not human-readable)

Contrast with GDV which uses score notation (scale degrees, dynamics).

Natural Keys

  • :pitch: MIDI pitch number (0-127) or :silence for rests
  • :velocity: MIDI velocity (0-127)
  • :duration: Event duration (from AbsD)
  • :note_duration, :forward_duration: Additional duration keys (from AbsD)

Conversions

To GDV (Score Notation)

Converts MIDI pitches to scale degrees using a scale reference:

pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(PDV)
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = pdv.to_gdv(scale)
# => { grade: 0, octave: 0, duration: 1.0, velocity: 0 }
  • Pitch → Grade: Finds closest scale degree
  • Chromatic notes: Represented as grade + sharps
  • Velocity: Maps MIDI 0-127 to dynamics -5 to +4 (ppp to fff)

Velocity Mapping

MIDI velocities are mapped to musical dynamics:

MIDI 1-1    

Base Duration

The base_duration attribute defines the unit for duration values, typically 1/4r (quarter note).

Examples:

Basic MIDI event

pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(Musa::Datasets::PDV)
pdv.base_duration = 1/4r
# C4 (middle C) for 1 beat at mf dynamics

Silence (rest)

pdv = { pitch: :silence, duration: 1.0 }.extend(PDV)
# Rest for 1 beat

With articulation

pdv = {
  pitch: 64,
  duration: 1.0,
  note_duration: 0.5,  # Staccato
  velocity: 80
}.extend(PDV)

Convert to score notation

pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(PDV)
pdv.base_duration = 1/4r
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = pdv.to_gdv(scale)
# => { grade: 0, octave: 0, duration: 1.0, velocity: 0 }

Chromatic pitch

pdv = { pitch: 61, duration: 1.0, velocity: 64 }.extend(PDV)
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = pdv.to_gdv(scale)
# => { grade: 0, octave: 0, sharps: 1, duration: 1.0, velocity: 0 }
# C# represented as C (grade 0) + 1 sharp

Preserve additional keys

pdv = {
  pitch: 60,
  duration: 1.0,
  velocity: 64,
  custom_key: :value
}.extend(PDV)
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = pdv.to_gdv(scale)
# custom_key copied to GDV (not a natural key)

See Also:

Constant Summary collapse

NaturalKeys =

Natural keys for MIDI events.

Returns:

(NaturalKeys + [:pitch, :velocity]).freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#base_durationRational

Base duration for time calculations.

Returns:



121
122
123
# File 'lib/musa-dsl/datasets/pdv.rb', line 121

def base_duration
  @base_duration
end

Instance Method Details

#durationNumeric Originally defined in module AbsD

Returns event duration.

Examples:

event.duration  # => 1.0

Returns:

  • (Numeric)

    duration

#forward_durationNumeric Originally defined in module AbsD

Returns forward duration (time until next event).

Defaults to :duration if :forward_duration not specified.

Examples:

event.forward_duration  # => 1.0

Returns:

  • (Numeric)

    forward duration

#note_durationNumeric Originally defined in module AbsD

Returns actual note duration.

Defaults to :duration if :note_duration not specified.

Examples:

event.note_duration  # => 0.5 (staccato)

Returns:

  • (Numeric)

    note duration

#to_gdv(scale) ⇒ GDV

Converts to GDV (score notation).

Translates MIDI representation to score notation using a scale:

  • MIDI pitch → scale degree (grade + octave + sharps)
  • MIDI velocity → dynamics (-5 to +4)
  • Duration values copied
  • Additional keys preserved

Examples:

Basic conversion

pdv = { pitch: 60, duration: 1.0, velocity: 64 }.extend(PDV)
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = pdv.to_gdv(scale)

Chromatic note

pdv = { pitch: 61, duration: 1.0 }.extend(PDV)
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = pdv.to_gdv(scale)
# => { grade: 0, octave: 0, sharps: 1, duration: 1.0 }

Silence

pdv = { pitch: :silence, duration: 1.0 }.extend(PDV)
scale = Musa::Scales::Scales.et12[440.0].major[60]
gdv = pdv.to_gdv(scale)
# => { grade: :silence, duration: 1.0 }

Parameters:

Returns:

  • (GDV)

    score notation dataset



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/musa-dsl/datasets/pdv.rb', line 151

def to_gdv(scale)
  gdv = {}.extend GDV
  gdv.base_duration = @base_duration

  if self[:pitch]
    if self[:pitch] == :silence
      gdv[:grade] = :silence
    else
      note = scale.note_of_pitch(self[:pitch], allow_chromatic: true)

      if background_note = note.background_note
        gdv[:grade] = background_note.grade
        gdv[:octave] = background_note.octave
        gdv[:sharps] = note.background_sharps
      else
        gdv[:grade] = note.grade
        gdv[:octave] = note.octave
      end
    end
  end

  gdv[:duration] = self[:duration] if self[:duration]

  if self[:velocity]
    # ppp = 16 ... fff = 127
    # TODO create a customizable MIDI velocity to score dynamics bidirectional conversor
    gdv[:velocity] = [1..1, 2..8, 9..16, 17..33, 34..48, 49..64, 65..80, 81..96, 97..112, 113..127].index { |r| r.cover? self[:velocity] } - 5
  end

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

  gdv
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