pedrozath/coltrane

View on GitHub
lib/coltrane/theory/progression.rb

Summary

Maintainability
A
45 mins
Test Coverage
A
90%
# frozen_string_literal: true

module Coltrane
  module Theory
    # Allows the creation of chord progressions using standard notations.
    # Ex: Progression.new('I-IV-V', key: 'Am')
    class Progression
      extend NotableProgressions
      include Changes
      attr_reader :scale, :chords, :notation

      def self.find(*chords)
        chords
          .yield_self { |chords|
            next chords if chords[0].is_a?(Chord)
            chords.map {|c| Chord.new(name: c) }
          }
          .yield_self { |chords|
            NoteSet[*chords.map(&:root_note)]
            .yield_self { |root_notes|
              Scale.having_notes(*root_notes).strict_scales
            }
            .reduce([]) { |memo, scale|
              memo + [Progression.new(chords: chords, scale: scale)]
            }
          }
          .yield_self { |progressions| ProgressionSet.new(*progressions) }
      end

      def initialize(notation = nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
        if notation.nil? && chords.nil? && roman_chords.nil? || key.nil? && scale.nil?
          raise WrongKeywordsError,
            '[chords:, [scale: || key:]] '\
            '[roman_chords:, [scale: || key:]] '\
            '[notation:, [scale: || key:]] '\
        end

        @scale  = scale || Key[key]
        @chords =
          if !chords.nil?
            chords
          elsif !roman_chords.nil?
            roman_chords.map(&:chord)
          elsif !notation.nil?
            @notation = notation
            notation.split('-').map { |c| RomanChord.new(c, scale: @scale).chord }
          end
      end

      def interval_sequence
        @interval_sequence ||= IntervalSequence(notes: @root_notes)
      end

      def root_notes
        @root_notes ||= @chords.map(&:root_note)
      end

      def roman_chords
        @roman_chords ||= chords.map do |c|
          RomanChord.new(chord: c, scale: scale)
        end
      end

      def notation
        roman_chords.map(&:notation).join('-')
      end

      def notes
        NoteSet[*chords.map(&:notes).map(&:notes).flatten]
      end

      def notes_out
        notes - scale.notes
      end

      def notes_out_size
        notes_out.size
      end
    end
  end
end