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



189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/musa-dsl/datasets/p.rb', line 189

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

line = [60, 4, 64, 8, 67].extend(P)
serie = line.to_ps_serie.i  # a prototype cannot be consumed
serie.next_value
# => { from: 60, to: 64, duration: 1r, 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
128
129
130
131
# File 'lib/musa-dsl/datasets/p.rb', line 115

def to_ps_serie(base_duration: nil)
  # A quarter of a bar. Not "a quarter note": that is what it lasts in 4/4
  # and nowhere else -- durations are fractions of a bar throughout the
  # library (see {Musa::Datasets::AbsD}), and the two readings part company
  # as soon as the meter is not 4/4.
  base_duration ||= 1/4r

  # 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

line = [60, 4, 64, 8, 67].extend(P)
serie = line.to_timed_serie.i  # a prototype cannot be consumed
serie.next_value  # => { time: 0r, value: 60 }
serie.next_value  # => { time: 1r, value: 64 }
serie.next_value  # => { time: 3r, 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 to time_start

  • base_duration (Rational) (defaults to: nil)

    duration unit multiplier (default: 1/4r)

Returns:



160
161
162
163
164
165
166
167
168
# File 'lib/musa-dsl/datasets/p.rb', line 160

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

  # A quarter of a bar; see the note in #to_ps_serie above.
  base_duration ||= 1/4r

  PtoTimedSerie.new(self, base_duration, time_start)
end