Class: Musa::Scales::NoteInScale

Inherits:
Object
  • Object
show all
Defined in:
lib/musa-dsl/music/scales.rb

Overview

Note within a scale context.

NoteInScale represents a specific note within a scale, providing rich musical functionality including:

  • Pitch and frequency information
  • Interval navigation (up, down, by named intervals)
  • Chromatic alterations (sharp, flat)
  • Scale navigation (change scales while keeping pitch)
  • Chord construction
  • Octave transposition

Creation

Notes are created via scale access, not directly:

scale = tuning.major[60]
note = scale.tonic           # NoteInScale instance
note = scale[:V]             # Another NoteInScale

Basic Properties

note.pitch       # MIDI pitch number
note.grade       # Scale degree (0-based)
note.octave      # Octave relative to scale root
note.frequency   # Frequency in Hz
note.functions   # Function names for this degree

Interval Navigation

Natural intervals (diatonic, within scale):

note.up(2)        # Up 2 scale degrees
note.down(1)      # Down 1 scale degree

Chromatic intervals (by semitones or named intervals):

note.up(:P5)      # Up perfect fifth
note.up(7)        # Up 7 semitones (if chromatic specified)
note.down(:M3)    # Down major third

Chromatic Alterations

note.sharp        # Raise by 1 semitone
note.sharp(2)     # Raise by 2 semitones
note.flat         # Lower by 1 semitone
note.flat(2)      # Lower by 2 semitones

Scale Navigation

note.scale(:minor)     # Same pitch in minor scale
note.major             # Same pitch in major scale
note.chromatic         # Same pitch in chromatic scale

Chord Construction

note.chord                      # Build triad
note.chord :seventh             # Build seventh chord
note.chord quality: :minor      # Build with features

Background Scale Context

Chromatic notes remember their diatonic context:

c# = c_major.tonic.sharp        # C# in C major context
c#.background_scale             # => c_major
c#.background_note              # => C (natural)
c#.background_sharps            # => 1

Examples:

Basic usage

c_major = tuning.major[60]
tonic = c_major.tonic
tonic.pitch       # => 60
tonic.frequency   # => ~261.63 Hz

Interval navigation

tonic.up(:P5).pitch        # => 67 (G)
tonic.up(4, :natural).pitch # => 71 (4 scale degrees = B)

Chromatic alterations

tonic.sharp.pitch  # => 61 (C#)
tonic.flat.pitch   # => 59 (B)

Chord building

tonic.chord              # C major triad
tonic.chord :seventh     # C major 7th

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scale, grade, octave, pitch, background_scale: nil, background_grade: nil, background_octave: nil, background_sharps: nil) ⇒ NoteInScale

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates a note within a scale.

Parameters:

  • scale (Scale)

    parent scale

  • grade (Integer)

    scale degree (0-based)

  • octave (Integer)

    octave relative to scale root

  • pitch (Numeric)

    MIDI pitch (Integer, Rational, or Float for microtones)

  • background_scale (Scale, nil) (defaults to: nil)

    diatonic context for chromatic notes

  • background_grade (Integer, nil) (defaults to: nil)

    diatonic grade for chromatic notes

  • background_octave (Integer, nil) (defaults to: nil)

    diatonic octave for chromatic notes

  • background_sharps (Integer, nil) (defaults to: nil)

    sharps/flats from diatonic note



1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
# File 'lib/musa-dsl/music/scales.rb', line 1165

def initialize(scale, grade, octave, pitch, background_scale: nil, background_grade: nil, background_octave: nil, background_sharps: nil)
  @scale = scale
  @grade = grade
  @octave = octave
  @pitch = pitch

  @background_scale = background_scale
  @background_grade = background_grade
  @background_octave = background_octave
  @background_sharps = background_sharps

  @scale.kind.tuning.scale_system.scale_kind_classes.each_key do |name|
    define_singleton_method name do
      scale(name)
    end
  end
end

Instance Attribute Details

#background_scaleScale? (readonly)

Background diatonic scale (for chromatic notes).

Returns:



1253
1254
1255
# File 'lib/musa-dsl/music/scales.rb', line 1253

def background_scale
  @background_scale
end

#background_sharpsInteger? (readonly)

Sharps/flats from background note.

Returns:

  • (Integer, nil)


1268
1269
1270
# File 'lib/musa-dsl/music/scales.rb', line 1268

def background_sharps
  @background_sharps
end

#gradeInteger (readonly)

Scale degree (0-based).

Returns:

  • (Integer)


1185
1186
1187
# File 'lib/musa-dsl/music/scales.rb', line 1185

def grade
  @grade
end

#pitchNumeric (readonly)

MIDI pitch number.

Returns:

  • (Numeric)


1189
1190
1191
# File 'lib/musa-dsl/music/scales.rb', line 1189

def pitch
  @pitch
end

Instance Method Details

#==(other) ⇒ Boolean

Checks note equality.

Notes are equal if they have same scale, grade, octave, and pitch.

Parameters:

Returns:

  • (Boolean)


1492
1493
1494
1495
1496
1497
1498
# File 'lib/musa-dsl/music/scales.rb', line 1492

def ==(other)
  self.class == other.class &&
      @scale == other.scale &&
      @grade == other.grade &&
      @octave == other.octave &&
      @pitch == other.pitch
end

#background_noteNoteInScale?

Returns the diatonic note this chromatic note is based on.

Examples:

c# = c_major.tonic.sharp
c#.background_note.pitch  # => 60 (C natural)

Returns:



1262
1263
1264
# File 'lib/musa-dsl/music/scales.rb', line 1262

def background_note
  @background_scale[@background_grade + (@background_octave || 0) * @background_scale.kind.class.grades] if @background_grade
end

#chord(*feature_values, allow_chromatic: nil, move: nil, duplicate: nil, **features_hash) ⇒ Chord

Builds a chord rooted on this note.

Creates a chord using this note as the root. Chord can be specified by:

  • Feature values (:triad, :seventh, :major, :minor, etc.)
  • Feature hash (quality:, size:)
  • Chord definition name (not shown here, see Chord.with_root)

If no features specified, defaults to major triad.

Examples:

Default triad

note.chord  # Major triad

Specified size

note.chord :seventh   # Seventh chord matching scale
note.chord :ninth     # Ninth chord

With features

note.chord quality: :minor, size: :seventh
note.chord :minor, :seventh  # Same as above

With voicing

note.chord :seventh, move: {root: -1}, duplicate: {fifth: 1}

Parameters:

  • feature_values (Array<Symbol>)

    feature values (size, quality, etc.)

  • allow_chromatic (Boolean) (defaults to: nil)

    allow non-diatonic chord notes

  • move (Hash{Symbol => Integer}) (defaults to: nil)

    initial octave moves

  • duplicate (Hash{Symbol => Integer, Array<Integer>}) (defaults to: nil)

    initial duplications

  • features_hash (Hash)

    feature key-value pairs

Returns:

  • (Chord)

    chord rooted on this note

See Also:

  • Chord class


1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
# File 'lib/musa-dsl/music/scales.rb', line 1470

def chord(*feature_values,
          allow_chromatic: nil,
          move: nil,
          duplicate: nil,
          **features_hash)

  features = { size: :triad } if feature_values.empty? && features_hash.empty?
  features ||= Musa::Chords::ChordDefinition.features_from(feature_values, features_hash)

  Musa::Chords::Chord.with_root(self,
                                allow_chromatic: allow_chromatic,
                                move: move,
                                duplicate: duplicate,
                                **features)
end

#down(interval_name_or_interval, natural_or_chromatic = nil) ⇒ NoteInScale

Navigates downward by interval.

Same as #up but in reverse direction.

Examples:

note.down(2, :natural)  # Down 2 scale degrees
note.down(:P5)          # Down perfect fifth

Parameters:

  • interval_name_or_interval (Symbol, Integer)

    interval

  • natural_or_chromatic (Symbol, nil) (defaults to: nil)

    :natural or :chromatic

Returns:



1351
1352
1353
# File 'lib/musa-dsl/music/scales.rb', line 1351

def down(interval_name_or_interval, natural_or_chromatic = nil)
  up(interval_name_or_interval, natural_or_chromatic, sign: -1)
end

#flat(count = nil) ⇒ NoteInScale

Lowers note by semitones (adds flats).

Examples:

note.flat.pitch     # Down 1 semitone
note.flat(2).pitch  # Down 2 semitones

Parameters:

  • count (Integer, nil) (defaults to: nil)

    number of semitones (default 1)

Returns:



1376
1377
1378
1379
# File 'lib/musa-dsl/music/scales.rb', line 1376

def flat(count = nil)
  count ||= 1
  sharp(-count)
end

#frequencyFloat

Calculates frequency in Hz.

Uses the scale system's frequency calculation (equal temperament, just intonation, etc.) and the tuning's A frequency.

Examples:

c_major.tonic.frequency  # => ~261.63 Hz (middle C at A=440)

Returns:

  • (Float)

    frequency in Hz



1390
1391
1392
# File 'lib/musa-dsl/music/scales.rb', line 1390

def frequency
  @scale.kind.tuning.frequency_of_pitch(@pitch, @scale.root_pitch)
end

#functionsArray<Symbol>

Returns function names for this scale degree.

Examples:

c_major.tonic.functions  # => [:I, :_1, :tonic, :first]

Returns:

  • (Array<Symbol>)

    function symbols



1197
1198
1199
# File 'lib/musa-dsl/music/scales.rb', line 1197

def functions
  @scale.kind.class.pitches[grade][:functions]
end

#inspectString Also known as: to_s

Returns string representation.

Returns:



1503
1504
1505
# File 'lib/musa-dsl/music/scales.rb', line 1503

def inspect
  "<NoteInScale: grade = #{@grade} octave = #{@octave} pitch = #{@pitch} scale = (#{@scale.kind.class.name} on #{scale.root_pitch})>"
end

#octave(octave = nil, absolute: false) ⇒ Integer, NoteInScale

Transposes note or returns current octave.

Without argument: Returns current octave relative to scale root.

With argument: Returns note transposed by octave offset.

Examples:

Query octave

note.octave  # => 0 (at scale root octave)

Transpose relative

note.octave(1).pitch   # Up one octave from current
note.octave(-1).pitch  # Down one octave from current

Transpose absolute

note.octave(2, absolute: true).pitch  # At octave 2, regardless of current

Parameters:

  • octave (Integer, nil) (defaults to: nil)

    octave offset (nil to query current)

  • absolute (Boolean) (defaults to: false)

    if true, ignore current octave

Returns:

  • (Integer, NoteInScale)

    current octave or transposed note

Raises:

  • (ArgumentError)

    if octave is not integer



1221
1222
1223
1224
1225
1226
1227
1228
1229
# File 'lib/musa-dsl/music/scales.rb', line 1221

def octave(octave = nil, absolute: false)
  if octave.nil?
    @octave
  else
    raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i

    @scale[@grade + ((absolute ? 0 : @octave) + octave) * @scale.kind.class.grades]
  end
end

#on(scale) ⇒ NoteInScale?

Finds this note in another scale.

Searches for a note with the same pitch in the target scale.

Examples:

c_major_tonic = c_major.tonic
c_minor = tuning.minor[60]
c_major_tonic.on(c_minor)  # C in C minor scale

Parameters:

  • scale (Scale)

    target scale to search

Returns:



1435
1436
1437
# File 'lib/musa-dsl/music/scales.rb', line 1435

def on(scale)
  scale.note_of_pitch @pitch
end

#scale(kind_id_or_kind = nil) ⇒ Scale, NoteInScale

Changes scale while keeping pitch, or returns current scale.

Without argument: Returns current scale.

With argument: Returns note at same pitch in different scale kind.

Examples:

Query current scale

note.scale  # => <Scale: kind = MajorScaleKind ...>

Change to minor

note.scale(:minor)  # Same pitch in minor scale

Dynamic method

note.minor   # Same as note.scale(:minor)
note.major   # Same as note.scale(:major)

Parameters:

  • kind_id_or_kind (Symbol, ScaleKind, nil) (defaults to: nil)

    scale kind or ID

Returns:



1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
# File 'lib/musa-dsl/music/scales.rb', line 1412

def scale(kind_id_or_kind = nil)
  if kind_id_or_kind.nil?
    @scale
  else
    if kind_id_or_kind.is_a? ScaleKind
      kind_id_or_kind[@pitch]
    else
      @scale.kind.tuning[kind_id_or_kind][@pitch]
    end
  end
end

#sharp(count = nil) ⇒ NoteInScale

Raises note by semitones (adds sharps).

Examples:

note.sharp.pitch     # Up 1 semitone
note.sharp(2).pitch  # Up 2 semitones

Parameters:

  • count (Integer, nil) (defaults to: nil)

    number of semitones (default 1)

Returns:



1363
1364
1365
1366
# File 'lib/musa-dsl/music/scales.rb', line 1363

def sharp(count = nil)
  count ||= 1
  calculate_note_of_pitch(@pitch, count)
end

#up(interval_name_or_interval, natural_or_chromatic = nil, sign: nil) ⇒ NoteInScale

Navigates upward by interval.

Supports both natural (diatonic) and chromatic (semitone) intervals.

  • Numeric interval + :natural: Move by scale degrees
  • Symbol or numeric interval + :chromatic: Move by semitones or named interval

Examples:

Natural interval (scale degrees)

note.up(2, :natural)  # Up 2 scale degrees

Chromatic interval (semitones)

note.up(:P5)  # Up perfect fifth (7 semitones)
note.up(7)    # Up 7 semitones (if chromatic)

Parameters:

  • interval_name_or_interval (Symbol, Integer)

    interval

  • natural_or_chromatic (Symbol, nil) (defaults to: nil)

    :natural or :chromatic

  • sign (Integer) (defaults to: nil)

    direction multiplier (internal use)

Returns:



1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
# File 'lib/musa-dsl/music/scales.rb', line 1300

def up(interval_name_or_interval, natural_or_chromatic = nil, sign: nil)

  sign ||= 1

  if interval_name_or_interval.is_a?(Numeric)
    natural_or_chromatic ||= :natural
  else
    natural_or_chromatic = :chromatic
  end

  if natural_or_chromatic == :chromatic
    interval = if interval_name_or_interval.is_a?(Symbol)
                 @scale.kind.tuning.offset_of_interval(interval_name_or_interval)
               else
                 interval_name_or_interval
               end

    calculate_note_of_pitch(@pitch, sign * interval)
  else
    @scale[@grade + sign * interval_name_or_interval]
  end
end

#wide_gradeInteger

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns wide grade (grade + octave * grades_per_octave).

Examples:

note.wide_grade  # => 7 (second octave, first degree)

Returns:

  • (Integer)


1278
1279
1280
# File 'lib/musa-dsl/music/scales.rb', line 1278

def wide_grade
  @grade + @octave * @scale.kind.class.grades
end

#with_background(scale:, grade: nil, octave: nil, sharps: nil) ⇒ NoteInScale

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates a copy with background scale context.

Used internally when creating chromatic notes to remember their diatonic context.

Parameters:

  • scale (Scale)

    background diatonic scale

  • grade (Integer, nil) (defaults to: nil)

    background grade

  • octave (Integer, nil) (defaults to: nil)

    background octave

  • sharps (Integer, nil) (defaults to: nil)

    accidentals from background note

Returns:



1243
1244
1245
1246
1247
1248
1249
# File 'lib/musa-dsl/music/scales.rb', line 1243

def with_background(scale:, grade: nil, octave: nil, sharps: nil)
  NoteInScale.new(@scale, @grade, @octave, @pitch,
                  background_scale: scale,
                  background_grade: grade,
                  background_octave: octave,
                  background_sharps: sharps)
end