Module: Cyclotone::Harmony
- Defined in:
- lib/cyclotone/harmony.rb
Constant Summary collapse
- NOTE_OFFSETS =
{ "c" => 0, "cs" => 1, "db" => 1, "d" => 2, "ds" => 3, "eb" => 3, "e" => 4, "f" => 5, "fs" => 6, "gb" => 6, "g" => 7, "gs" => 8, "ab" => 8, "a" => 9, "as" => 10, "bb" => 10, "b" => 11 }.freeze
- SCALES =
{ major: [0, 2, 4, 5, 7, 9, 11], minor: [0, 2, 3, 5, 7, 8, 10], dorian: [0, 2, 3, 5, 7, 9, 10], phrygian: [0, 1, 3, 5, 7, 8, 10], lydian: [0, 2, 4, 6, 7, 9, 11], mixolydian: [0, 2, 4, 5, 7, 9, 10], locrian: [0, 1, 3, 5, 6, 8, 10], harmonic_minor: [0, 2, 3, 5, 7, 8, 11], melodic_minor: [0, 2, 3, 5, 7, 9, 11], whole_tone: [0, 2, 4, 6, 8, 10], chromatic: (0..11).to_a, pentatonic_major: [0, 2, 4, 7, 9], pentatonic_minor: [0, 3, 5, 7, 10], blues: [0, 3, 5, 6, 7, 10], egyptian: [0, 2, 5, 7, 10], hirajoshi: [0, 2, 3, 7, 8], iwato: [0, 1, 5, 6, 10], enigmatic: [0, 1, 4, 6, 8, 10, 11], neapolitan_major: [0, 1, 3, 5, 7, 9, 11], neapolitan_minor: [0, 1, 3, 5, 7, 8, 11] }.freeze
- CHORDS =
{ major: [0, 4, 7], minor: [0, 3, 7], diminished: [0, 3, 6], augmented: [0, 4, 8], sus2: [0, 2, 7], sus4: [0, 5, 7], major7: [0, 4, 7, 11], minor7: [0, 3, 7, 10], dominant7: [0, 4, 7, 10] }.freeze
Class Method Summary collapse
- .arpeggiate(pattern, mode: :up) ⇒ Object
- .chord(name, root: 0, inversion: 0, voicing: nil, drop2: false, octave_spread: 0) ⇒ Object
- .note_number(value) ⇒ Object
- .scale(name, pattern, root: 0) ⇒ Object
Class Method Details
.arpeggiate(pattern, mode: :up) ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/cyclotone/harmony.rb', line 70 def arpeggiate(pattern, mode: :up) Pattern.ensure_pattern(pattern).flat_map_events do |event| notes = extract_notes(event.value) next [event] if notes.empty? next [] if event.part.duration.zero? ordered = order_notes(notes, mode) segment_length = event.part.duration / ordered.length ordered.each_with_index.map do |note, index| part = TimeSpan.new( event.part.start + (segment_length * index), event.part.start + (segment_length * (index + 1)) ) Event.new(whole: part, part: part, value: arpeggiated_value(event.value, note)) end end end |
.chord(name, root: 0, inversion: 0, voicing: nil, drop2: false, octave_spread: 0) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/cyclotone/harmony.rb', line 57 def chord(name, root: 0, inversion: 0, voicing: nil, drop2: false, octave_spread: 0) root_note = note_number(root) intervals = voicing || CHORDS.fetch(name.to_sym) { raise ArgumentError, "unknown chord #{name}" } notes = Array(intervals).map do |interval| root_note + interval end notes = invert_notes(notes, inversion.to_i) notes = spread_octaves(notes, octave_spread.to_i) notes = drop_second_voice(notes) if drop2 Pattern.pure(notes) end |
.note_number(value) ⇒ Object
90 91 92 93 94 95 96 97 98 99 |
# File 'lib/cyclotone/harmony.rb', line 90 def note_number(value) return value.to_i if value.is_a?(Numeric) normalized = value.to_s.strip.downcase match = normalized.match(/\A([a-g](?:s|b)?)(-?\d+)\z/) return normalized.to_i if match.nil? && normalized.match?(/\A-?\d+\z/) raise ArgumentError, "invalid note #{value.inspect}" if match.nil? NOTE_OFFSETS.fetch(match[1]) + ((match[2].to_i + 1) * 12) end |
.scale(name, pattern, root: 0) ⇒ Object
48 49 50 51 52 53 54 55 |
# File 'lib/cyclotone/harmony.rb', line 48 def scale(name, pattern, root: 0) intervals = SCALES.fetch(name.to_sym) { raise ArgumentError, "unknown scale #{name}" } root_note = note_number(root) Pattern.ensure_pattern(pattern).fmap do |value| apply_scale(intervals, root_note, value) end end |