Module: Musa::Datasets::P

Includes:
Dataset
Defined in:
lib/musa-dsl/datasets/p.rb

Overview

Point series: sequential points in time with durations.

P (Point series) represents sequential points in time as arrays with alternating structure: [point, duration, point, duration, point, ...].

Structure

The array alternates between points and durations:

[point₀, duration₀, point₁, duration₁, point₂]
  • Points (odd positions): Any data (numbers, hashes, complex structures, etc.)
  • Durations (even positions): Time between points (numbers)
  • Last point: Final point has no duration (sequence end)

This compact format efficiently represents timed sequences without repeating time information.

Conversions

P can be converted to two different representations:

1. Timed Series (to_timed_serie)

Converts to series of AbsTimed events with absolute time and value. Each value gets a timestamp based on cumulative durations.

p = [60, 4, 64, 8, 67].extend(P)
serie = p.to_timed_serie
# Yields:
# { time: 0, value: 60 }
# { time: 1.0, value: 64 }  (4 * base_duration = 1.0)
# { time: 3.0, value: 67 }  (8 * base_duration = 2.0)

2. Parameter Segment Series (to_ps_serie)

Converts to series of PS (Parameter Segment) objects representing continuous changes between consecutive points.

p = [60, 4, 64, 8, 67].extend(P)
serie = p.to_ps_serie
# Yields PS objects:
# { from: 60, to: 64, duration: 1.0, right_open: true }
# { from: 64, to: 67, duration: 2.0, right_open: false }

Point Transformation

The #map method transforms points while preserving durations:

p = [60, 4, 64, 8, 67].extend(P)
p2 = p.map { |point| point + 12 }
# => [72, 4, 76, 8, 79]
# Durations unchanged, points transformed

Examples:

Basic point series (MIDI pitches)

# MIDI pitches with durations in quarter notes
p = [60, 4, 64, 8, 67].extend(Musa::Datasets::P)
# 60 (C4) for 4 quarters → 64 (E4) for 8 quarters → 67 (G4)

Hash points (complex data structures)

p = [
  { pitch: 60, velocity: 64 }, 4,
  { pitch: 64, velocity: 80 }, 8,
  { pitch: 67, velocity: 64 }
].extend(Musa::Datasets::P)

Convert to timed serie

p = [60, 4, 64, 8, 67].extend(Musa::Datasets::P)
serie = p.to_timed_serie(base_duration: 1/4r)
# base_duration: quarter note = 1/4 beat

Start at specific time

serie = p.to_timed_serie(time_start: 10)
# First event at time 10

Start time from component

p = [{ time: 100, pitch: 60 }, 4, { time: 200, pitch: 64 }].extend(P)
serie = p.to_timed_serie(time_start_component: :time)
# First event at time 100 (from first point's :time)

Transform points

p = [60, 4, 64, 8, 67].extend(Musa::Datasets::P)
p2 = p.map { |point| point + 12 }
# Transform each point (e.g., transpose pitches up one octave)

See Also:

Defined Under Namespace

Classes: PtoTimedSerie

Instance Method Summary collapse

Instance Method Details

#map {|point| ... } ⇒ P

Maps over points, preserving durations.

Transforms each point (odd positions) using the block while keeping durations (even positions) unchanged.

Examples:

Transform points (e.g., transpose pitches)

p = [60, 4, 64, 8, 67].extend(P)
p.map { |point| point + 12 }
# => [72, 4, 76, 8, 79]

Transform hash points

p = [{ pitch: 60 }, 4, { pitch: 64 }].extend(P)
p.map { |point| point.merge(velocity: 80) }
# Adds velocity to each point

Yield Parameters:

  • point (Object)

    each point in the series

Yield Returns:

  • (Object)

    transformed point

Returns:

  • (P)

    new P with transformed points



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/musa-dsl/datasets/p.rb', line 184

def map(&block)
  i = 0
  clone.map! do |element|
    # Process with block only the points (points are the alternating elements because P
    # structure is <point> <duration> <point> <duration> <point>)
    #
    if (i += 1) % 2 == 1
      block.call(element)
    else
      element
    end
  end
end

#to_ps_serie(base_duration: nil) ⇒ Musa::Series::Serie<PS>

Converts to series of parameter segments.

Creates Musa::Datasets::PS objects representing continuous changes from each point to the next. Useful for glissandi, parameter sweeps, or any continuous interpolation between points.

Examples:

Create parameter segments

p = [60, 4, 64, 8, 67].extend(P)
serie = p.to_ps_serie
segment1 = serie.next_value
# => { from: 60, to: 64, duration: 1.0, right_open: true }

Parameters:

  • base_duration (Rational) (defaults to: nil)

    duration unit multiplier (default: 1/4r) Durations in P are multiplied by this to get actual time

Returns:



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/musa-dsl/datasets/p.rb', line 115

def to_ps_serie(base_duration: nil)
  base_duration ||= 1/4r # TODO review incoherence between neumalang 1/4r base duration for quarter notes and general 1r size of bar

  # TODO if instead of using clone (needed because of p.shift) we use index counter the P elements would be evaluated on the last moment

  Musa::Series::Constructors.E(clone, base_duration) do |p, base_duration|
    (p.size >= 3) ?
      { from: p.shift,
        duration: p.shift * base_duration,
        to: p.first,
        right_open: (p.length > 1) }.extend(PS).tap { |_| _.base_duration = base_duration } : nil
  end
end

#to_timed_serie(time_start: nil, time_start_component: nil, base_duration: nil) ⇒ PtoTimedSerie

Converts to series of timed events (AbsTimed).

Creates series yielding AbsTimed events with absolute time and value. Each value is emitted at its calculated time point based on cumulative durations.

Examples:

Basic timed serie

p = [60, 4, 64, 8, 67].extend(P)
serie = p.to_timed_serie
serie.next_value  # => { time: 0, value: 60 }
serie.next_value  # => { time: 1.0, value: 64 }
serie.next_value  # => { time: 3.0, value: 67 }

Custom start time

serie = p.to_timed_serie(time_start: 10)
# First event at time 10

Start time from component

p = [{ time: 100, pitch: 60 }, 4, { pitch: 64 }].extend(P)
serie = p.to_timed_serie(time_start_component: :time)
# First event at time 100

Parameters:

  • time_start (Numeric) (defaults to: nil)

    starting time offset (default: 0)

  • time_start_component (Symbol) (defaults to: nil)

    key in first value to use as time offset If provided, adds first[time_start_component] to time_start

  • base_duration (Rational) (defaults to: nil)

    duration unit multiplier (default: 1/4r)

Returns:



156
157
158
159
160
161
162
163
# File 'lib/musa-dsl/datasets/p.rb', line 156

def to_timed_serie(time_start: nil, time_start_component: nil, base_duration: nil)
  time_start ||= 0r
  time_start += self.first[time_start_component] if time_start_component

  base_duration ||= 1/4r # TODO review incoherence between neumalang 1/4r base duration for quarter notes and general 1r size of bar

  PtoTimedSerie.new(self, base_duration, time_start)
end