wtf_chord 0.3.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +10 -0
- data/README.md +22 -0
- data/lib/wtf_chord.rb +5 -0
- data/lib/wtf_chord/chord.rb +43 -9
- data/lib/wtf_chord/cli.rb +13 -5
- data/lib/wtf_chord/collectors.rb +10 -0
- data/lib/wtf_chord/collectors/generic.rb +33 -0
- data/lib/wtf_chord/collectors/guitar.rb +116 -0
- data/lib/wtf_chord/collectors/piano.rb +39 -0
- data/lib/wtf_chord/complexity_counter.rb +1 -1
- data/lib/wtf_chord/fingering.rb +24 -1
- data/lib/wtf_chord/formatter.rb +2 -0
- data/lib/wtf_chord/formatters/base.rb +7 -1
- data/lib/wtf_chord/formatters/piano.rb +21 -0
- data/lib/wtf_chord/formatters/piano1.rb +46 -0
- data/lib/wtf_chord/fretboard.rb +12 -2
- data/lib/wtf_chord/guitar_string.rb +18 -6
- data/lib/wtf_chord/helpers/roman_numbers_helper.rb +3 -1
- data/lib/wtf_chord/instrument_pitch.rb +53 -0
- data/lib/wtf_chord/keyboard.rb +21 -0
- data/lib/wtf_chord/keyboard_presentation.rb +90 -0
- data/lib/wtf_chord/piano_key.rb +19 -0
- data/lib/wtf_chord/pitch.rb +4 -0
- data/lib/wtf_chord/scale.rb +22 -6
- data/lib/wtf_chord/version.rb +1 -1
- data/wtf_chord.gemspec +2 -2
- metadata +18 -22
- data/lib/wtf_chord/fingerings_generator.rb +0 -92
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: d79d3b4f0a6ef8088987819387a46e7e1b4fd4e03614f131dba94ea7cc19e6c8
         | 
| 4 | 
            +
              data.tar.gz: e7122f195b69586a37c6a570bc0e3b64cf5f4ca8f1e357fc2504be86dfc0d0bc
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d2defd3bdae6c97b0eeb1e899edc1204a53c61a124b6832ac5748c1083fae7714cf322ba53e0000fc36fd217bbd85c0393833fada192cc02df76a2aadd4661b1
         | 
| 7 | 
            +
              data.tar.gz: 18d8cf19045668207068d5854108292cc045f35da1e9063b7a3d869c78d2d78a98cad034730b8f950c465223128acb30d754a2b84136926a3358484f8575beeb
         | 
    
        data/CHANGELOG.md
    ADDED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -91,6 +91,28 @@ You can get simpler output, using the `--output` option: | |
| 91 91 | 
             
                [ 10 8 7 7 6 10 ]
         | 
| 92 92 | 
             
                [ 10 8 7 7 10 10 ]
         | 
| 93 93 |  | 
| 94 | 
            +
            Starting with 0.7.0, we have the very nice piano formatting:
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                $ wtfchord Fdim -n 2 --output=piano
         | 
| 97 | 
            +
                F1 - G♯1 - B1
         | 
| 98 | 
            +
                ┌─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┐
         | 
| 99 | 
            +
                │ │ ││ │ │ │ ││█││ │ │
         | 
| 100 | 
            +
                │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ │
         | 
| 101 | 
            +
                │  │  │  │▐▌│  │  │▐▌│
         | 
| 102 | 
            +
                └──┴──┴──┴──┴──┴──┴──┘
         | 
| 103 | 
            +
                 ↑
         | 
| 104 | 
            +
                 Ⅰ
         | 
| 105 | 
            +
             | 
| 106 | 
            +
             | 
| 107 | 
            +
                G♯1 - B1 - F2
         | 
| 108 | 
            +
                ┌─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┬─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┐
         | 
| 109 | 
            +
                │ │ ││ │ │ │ ││█││ │ │ │ ││ │ │ │ ││ ││ │ │
         | 
| 110 | 
            +
                │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ │
         | 
| 111 | 
            +
                │  │  │  │  │  │  │▐▌│  │  │  │▐▌│  │  │  │
         | 
| 112 | 
            +
                └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
         | 
| 113 | 
            +
                 ↑                    ↑
         | 
| 114 | 
            +
                 Ⅰ                    Ⅱ
         | 
| 115 | 
            +
             | 
| 94 116 | 
             
            ## Development
         | 
| 95 117 |  | 
| 96 118 | 
             
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec wtf_chord` to use the gem in this directory, ignoring other installed copies of this gem.
         | 
    
        data/lib/wtf_chord.rb
    CHANGED
    
    | @@ -1,8 +1,13 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require "wtf_chord/scale"
         | 
| 2 4 | 
             
            require "wtf_chord/rules"
         | 
| 3 5 | 
             
            require "wtf_chord/version"
         | 
| 4 6 |  | 
| 5 7 | 
             
            module WTFChord
         | 
| 8 | 
            +
              autoload :Fretboard, "wtf_chord/fretboard"
         | 
| 9 | 
            +
              autoload :Keyboard, "wtf_chord/keyboard"
         | 
| 10 | 
            +
             | 
| 6 11 | 
             
              DEFAULTS = {
         | 
| 7 12 | 
             
                :rules_file => File.expand_path("../wtf_chord.yml", __FILE__)
         | 
| 8 13 | 
             
              }.freeze
         | 
    
        data/lib/wtf_chord/chord.rb
    CHANGED
    
    | @@ -1,28 +1,62 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'wtf_chord/fingering'
         | 
| 2 | 
            -
            require 'wtf_chord/ | 
| 4 | 
            +
            require 'wtf_chord/collectors'
         | 
| 3 5 |  | 
| 4 6 | 
             
            module WTFChord
         | 
| 5 7 | 
             
              class Chord
         | 
| 6 | 
            -
                 | 
| 8 | 
            +
                BASS_MATCH = /(?<=[\\\/])[A-H][b#]?(?=$)/
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :pitch, :steps, :notes, :name, :bass
         | 
| 7 11 |  | 
| 8 12 | 
             
                def initialize(note, name)
         | 
| 9 13 | 
             
                  @pitch = note.is_a?(Pitch) ? note : WTFChord.note(note)
         | 
| 10 | 
            -
                  @name  = "#{@pitch.key}#{name}" | 
| 14 | 
            +
                  @name  = "#{@pitch.key}#{name}"
         | 
| 11 15 | 
             
                  @steps = Array(WTFChord.rules[name])
         | 
| 12 16 | 
             
                  @notes = @steps.map { |dist| (@pitch + dist).note }.tap(&:uniq!)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  BASS_MATCH.match(@name) do |m|
         | 
| 19 | 
            +
                    @bass = WTFChord.note(m[0]).note
         | 
| 20 | 
            +
                  end
         | 
| 13 21 | 
             
                end
         | 
| 14 22 |  | 
| 15 | 
            -
                def  | 
| 16 | 
            -
                   | 
| 23 | 
            +
                def fingerings(limit = nil, collector: Collectors::Guitar)
         | 
| 24 | 
            +
                  collector.new(self).call[0, limit || 5]
         | 
| 17 25 | 
             
                end
         | 
| 18 26 |  | 
| 19 | 
            -
                def  | 
| 20 | 
            -
                   | 
| 21 | 
            -
                  FingeringsGenerator.new(self).call[0, limit]
         | 
| 27 | 
            +
                def tone
         | 
| 28 | 
            +
                  bass || original_bass
         | 
| 22 29 | 
             
                end
         | 
| 23 30 |  | 
| 24 31 | 
             
                def third_tone
         | 
| 25 | 
            -
                  @third_tone ||= @notes[@steps. | 
| 32 | 
            +
                  @third_tone ||= @notes[@steps.size > 3 ? 2 : -1]
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def bass?
         | 
| 36 | 
            +
                  !!@bass
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def size
         | 
| 40 | 
            +
                  bass? ? [*@notes, bass].uniq.size : @notes.size
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def original_bass
         | 
| 44 | 
            +
                  @notes[0]
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def === other
         | 
| 48 | 
            +
                  case other
         | 
| 49 | 
            +
                  when InstrumentPitch
         | 
| 50 | 
            +
                    self === other.note
         | 
| 51 | 
            +
                  when Note
         | 
| 52 | 
            +
                    notes.include?(other) || (bass? && bass == other)
         | 
| 53 | 
            +
                  else
         | 
| 54 | 
            +
                    super
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def inspect
         | 
| 59 | 
            +
                  "#{name} (#{@notes.map(&:key) * ' - '})"
         | 
| 26 60 | 
             
                end
         | 
| 27 61 | 
             
              end
         | 
| 28 62 | 
             
            end
         | 
    
        data/lib/wtf_chord/cli.rb
    CHANGED
    
    | @@ -7,14 +7,22 @@ module WTFChord | |
| 7 7 | 
             
                include Methadone::CLILogging
         | 
| 8 8 |  | 
| 9 9 | 
             
                main do |name|
         | 
| 10 | 
            -
                  chord | 
| 11 | 
            -
                   | 
| 12 | 
            -
                  formatter  = WTFChord::Formatter.new(options['output'], options['rates'])
         | 
| 10 | 
            +
                  chord     = WTFChord.chord(name)
         | 
| 11 | 
            +
                  formatter = WTFChord::Formatter.new(options['output'], options['rates'])
         | 
| 13 12 |  | 
| 14 13 | 
             
                  debug { "Output using formatter: #{formatter.formatter.to_s}\n" }
         | 
| 15 14 |  | 
| 16 | 
            -
                   | 
| 17 | 
            -
             | 
| 15 | 
            +
                  options['amount'] = 1 if options['output'] == 'piano1'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  collector =
         | 
| 18 | 
            +
                    case formatter.name when 'piano', 'piano1'
         | 
| 19 | 
            +
                      Collectors::Piano
         | 
| 20 | 
            +
                    else
         | 
| 21 | 
            +
                      Collectors::Guitar
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  fingerings = chord.fingerings(options['amount'], collector: collector)
         | 
| 25 | 
            +
                  puts formatter[*fingerings].uniq * formatter.separator
         | 
| 18 26 | 
             
                end
         | 
| 19 27 |  | 
| 20 28 | 
             
                version VERSION
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WTFChord
         | 
| 4 | 
            +
              class Chord
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              module Collectors
         | 
| 8 | 
            +
                class Generic < DelegateClass(Chord)
         | 
| 9 | 
            +
                  singleton_class.attr_accessor :max_dist
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def fingerings
         | 
| 12 | 
            +
                    @fingerings ||= []
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def call
         | 
| 16 | 
            +
                    reset!
         | 
| 17 | 
            +
                    collect!
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def reset!
         | 
| 21 | 
            +
                    fingerings.clear
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def collect!
         | 
| 25 | 
            +
                    raise NotImplementedError
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def max_dist
         | 
| 29 | 
            +
                    self.class.max_dist
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,116 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WTFChord
         | 
| 4 | 
            +
              module Collectors
         | 
| 5 | 
            +
                class Guitar < Generic
         | 
| 6 | 
            +
                  self.max_dist = 5
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  MAX_FRET = 7
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def collect!
         | 
| 11 | 
            +
                    (0...MAX_FRET).each do |from|
         | 
| 12 | 
            +
                      to = from + max_dist
         | 
| 13 | 
            +
                      generate(from...to) do |variant|
         | 
| 14 | 
            +
                        fingerings << set_bass(variant) if filter_variant(variant)
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    fingerings.uniq!
         | 
| 19 | 
            +
                    fingerings.sort_by!(&:complexity)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  private
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def generate(fret_range)
         | 
| 25 | 
            +
                    combinations = WTFChord.guitar.strings.map.with_index do |s, index|
         | 
| 26 | 
            +
                      fret_range.
         | 
| 27 | 
            +
                        select { |dist| pitch = s.original + dist; has_note?(pitch) }.
         | 
| 28 | 
            +
                        tap    { |frets| frets << nil if frets.size == 0 }
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    combinations.inject(&:product).each do |fingers|
         | 
| 32 | 
            +
                      fingers.flatten!
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      Fingering.new(WTFChord.guitar, fingers) do |variant|
         | 
| 35 | 
            +
                        adjust(variant)
         | 
| 36 | 
            +
                        yield(variant)
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def filter_variant(variant)
         | 
| 42 | 
            +
                    used_strings = variant.used_strings
         | 
| 43 | 
            +
                    used_notes = used_strings.map(&:note)
         | 
| 44 | 
            +
                    tones_count = notes.map { |n| used_notes.count(n) }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    !fingerings.include?(variant) &&
         | 
| 47 | 
            +
                    basetone?(used_strings[0]) &&
         | 
| 48 | 
            +
                    (
         | 
| 49 | 
            +
                      (third?(used_strings[1]) || third?(used_strings[2])) ||
         | 
| 50 | 
            +
                      (last?(used_strings[1]) || last?(used_strings[2]))
         | 
| 51 | 
            +
                    ) &&
         | 
| 52 | 
            +
                    all_notes?(used_notes) &&
         | 
| 53 | 
            +
                    used_notes.each_cons(2).none? { |(l, r)| l == r } &&
         | 
| 54 | 
            +
                    tones_count.all? { |n| tones_count[0] >= n || tones_count[notes.index(third_tone)] >= n } &&
         | 
| 55 | 
            +
                    variant.complexity <= 2.25
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def adjust(fingering)
         | 
| 59 | 
            +
                    while (string = fingering.used_strings[0]) && !basetone?(string)
         | 
| 60 | 
            +
                      string.dead if !string.dead?
         | 
| 61 | 
            +
                      break if fingering.used_strings.size == 4
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def set_bass(variant)
         | 
| 66 | 
            +
                    if bass?
         | 
| 67 | 
            +
                      variant.find_used_string_for(original_bass) do |idx, bass_string|
         | 
| 68 | 
            +
                        try_set_bass_on(variant, idx) or begin
         | 
| 69 | 
            +
                          4.times do |i|
         | 
| 70 | 
            +
                            next if i == idx
         | 
| 71 | 
            +
                            if try_set_bass_on(variant, i)
         | 
| 72 | 
            +
                              bass_string.dead
         | 
| 73 | 
            +
                              break
         | 
| 74 | 
            +
                            end
         | 
| 75 | 
            +
                          end
         | 
| 76 | 
            +
                        end
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    variant
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  def try_set_bass_on(variant, idx)
         | 
| 84 | 
            +
                    bass_string = variant.strings[idx].dup
         | 
| 85 | 
            +
                    distance = bass_string.distance_to(bass)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    if distance >= 0 && distance < 5 && (distance - variant.min_fret) > -3
         | 
| 88 | 
            +
                      variant.extra_complexity += 0.5 if distance < variant.min_fret
         | 
| 89 | 
            +
                      variant.strings[idx].hold_on(distance)
         | 
| 90 | 
            +
                      true
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def all_notes?(used_notes)
         | 
| 95 | 
            +
                    notes.all? { |n| used_notes.include?(n) }
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  def has_note?(value)
         | 
| 99 | 
            +
                    value = value.note if !value.is_a?(Note) && value.respond_to?(:note)
         | 
| 100 | 
            +
                    notes.include?(value)
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  def basetone?(string)
         | 
| 104 | 
            +
                    string && string.note == notes[0]
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  def third?(string)
         | 
| 108 | 
            +
                    string && (string.note == third_tone)
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def last?(string)
         | 
| 112 | 
            +
                    string && (string.note == notes[-1])
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module WTFChord
         | 
| 4 | 
            +
              module Collectors
         | 
| 5 | 
            +
                class Piano < Generic
         | 
| 6 | 
            +
                  self.max_dist = 12
         | 
| 7 | 
            +
                  MAX_OCTAVE = 5
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  CheckDist = -> ((a, b)) { (a.to_i - b.to_i).abs <= max_dist }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  FilterProc = -> (actual_size, notes) {
         | 
| 12 | 
            +
                    notes.uniq(&:key).size == actual_size && notes.sort.each_cons(2).all?(&CheckDist)
         | 
| 13 | 
            +
                  }.curry(2).freeze
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  Distances = -> (notes) {
         | 
| 16 | 
            +
                    notes.each_cons(2).map { |(a, b)| (a.to_i - b.to_i).abs }
         | 
| 17 | 
            +
                  }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  Sorting = -> (tone, notes) {
         | 
| 20 | 
            +
                    notes = notes.sort
         | 
| 21 | 
            +
                    mod = (notes[0].note.position - tone.position).abs.next
         | 
| 22 | 
            +
                    score = Distances[notes[1..-1]].sum
         | 
| 23 | 
            +
                    mod + score
         | 
| 24 | 
            +
                  }.curry(2).freeze
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def collect!
         | 
| 27 | 
            +
                    @keyboard = Keyboard.new(1..MAX_OCTAVE)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    @fingerings = @keyboard.keys.
         | 
| 30 | 
            +
                      grep(__getobj__).
         | 
| 31 | 
            +
                      combination(size).
         | 
| 32 | 
            +
                      filter(&FilterProc[size])
         | 
| 33 | 
            +
                    @fingerings.uniq! { |x| x.map(&:key) }
         | 
| 34 | 
            +
                    @fingerings.sort_by!(&Sorting[tone])
         | 
| 35 | 
            +
                    @fingerings
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
    
        data/lib/wtf_chord/fingering.rb
    CHANGED
    
    | @@ -4,15 +4,18 @@ require 'wtf_chord/formatter' | |
| 4 4 |  | 
| 5 5 | 
             
            module WTFChord
         | 
| 6 6 | 
             
              class Fingering < Fretboard
         | 
| 7 | 
            +
                attr_accessor :extra_complexity
         | 
| 8 | 
            +
             | 
| 7 9 | 
             
                def initialize(guitar, fingers = nil)
         | 
| 8 10 | 
             
                  @capo      = guitar.capo
         | 
| 9 11 | 
             
                  @strings   = guitar.strings.map(&:dup)
         | 
| 12 | 
            +
                  @extra_complexity = 0.0
         | 
| 10 13 | 
             
                  set_fingers(fingers) if fingers.is_a?(Array)
         | 
| 11 14 | 
             
                  yield(self) if block_given?
         | 
| 12 15 | 
             
                end
         | 
| 13 16 |  | 
| 14 17 | 
             
                def code
         | 
| 15 | 
            -
                   | 
| 18 | 
            +
                  strings.map(&:code).pack("c*")
         | 
| 16 19 | 
             
                end
         | 
| 17 20 |  | 
| 18 21 | 
             
                def == other
         | 
| @@ -23,6 +26,18 @@ module WTFChord | |
| 23 26 | 
             
                  end
         | 
| 24 27 | 
             
                end
         | 
| 25 28 |  | 
| 29 | 
            +
                def eql?(other)
         | 
| 30 | 
            +
                  super || self == other
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def hash
         | 
| 34 | 
            +
                  strings.map(&:code).hash
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def used_keys
         | 
| 38 | 
            +
                  used_strings.map(&:note).map(&:key).uniq.join
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 26 41 | 
             
                def complexity
         | 
| 27 42 | 
             
                  complexity_counter.rate
         | 
| 28 43 | 
             
                end
         | 
| @@ -43,6 +58,14 @@ module WTFChord | |
| 43 58 | 
             
                  @strings.reject(&:dead?)
         | 
| 44 59 | 
             
                end
         | 
| 45 60 |  | 
| 61 | 
            +
                def find_used_string_for(note)
         | 
| 62 | 
            +
                  used = used_strings
         | 
| 63 | 
            +
                  if idx = used.index(note)
         | 
| 64 | 
            +
                    string = used[idx]
         | 
| 65 | 
            +
                    yield(strings.index(string), string)
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 46 69 | 
             
                def min_fret
         | 
| 47 70 | 
             
                  holded_strings.min.fret
         | 
| 48 71 | 
             
                end
         | 
    
        data/lib/wtf_chord/formatter.rb
    CHANGED
    
    
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'forwardable'
         | 
| 2 4 |  | 
| 3 5 | 
             
            module WTFChord
         | 
| @@ -9,7 +11,7 @@ module WTFChord | |
| 9 11 | 
             
                  def_delegators :@fret, :strings
         | 
| 10 12 |  | 
| 11 13 | 
             
                  def self.separator
         | 
| 12 | 
            -
                    "\n" | 
| 14 | 
            +
                    "\n"
         | 
| 13 15 | 
             
                  end
         | 
| 14 16 |  | 
| 15 17 | 
             
                  def self.to_proc
         | 
| @@ -31,6 +33,10 @@ module WTFChord | |
| 31 33 | 
             
                  def draw
         | 
| 32 34 | 
             
                  end
         | 
| 33 35 |  | 
| 36 | 
            +
                  def actual_strings
         | 
| 37 | 
            +
                    strings.reject(&:dead?)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 34 40 | 
             
                  def with_rates?
         | 
| 35 41 | 
             
                    !!@with_rates
         | 
| 36 42 | 
             
                  end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "wtf_chord/formatters/piano1"
         | 
| 4 | 
            +
            require "wtf_chord/keyboard"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module WTFChord
         | 
| 7 | 
            +
              module Formatters
         | 
| 8 | 
            +
                class Piano < Piano1
         | 
| 9 | 
            +
                  def draw
         | 
| 10 | 
            +
                    [
         | 
| 11 | 
            +
                      pressed_keys.map(&:to_s).join(" - "),
         | 
| 12 | 
            +
                      keyboard_presentation
         | 
| 13 | 
            +
                    ].join("\n")
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def keyboard_presentation
         | 
| 17 | 
            +
                    KeyboardPresentation.press(*pressed_keys)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "wtf_chord/keyboard_presentation"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module WTFChord
         | 
| 6 | 
            +
              module Formatters
         | 
| 7 | 
            +
                class Piano1
         | 
| 8 | 
            +
                  def self.separator
         | 
| 9 | 
            +
                    "\n"
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def self.to_proc
         | 
| 13 | 
            +
                    proc { |keys| new(keys).draw }
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def self.with_rates(*)
         | 
| 17 | 
            +
                    yield(self)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  attr_reader :pressed_keys
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def initialize(keys)
         | 
| 23 | 
            +
                    @pressed_keys = keys
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def draw
         | 
| 27 | 
            +
                    [
         | 
| 28 | 
            +
                      unique_notes.sort.join(" - "),
         | 
| 29 | 
            +
                      keyboard_presentation
         | 
| 30 | 
            +
                    ].join("\n\n")
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def actual_notes
         | 
| 34 | 
            +
                    actual_strings.map(&:note)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def unique_notes
         | 
| 38 | 
            +
                    actual_notes.uniq(&:position)
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def keyboard_presentation
         | 
| 42 | 
            +
                    KeyboardPresentation.press(*unique_notes.map(&:position))
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
    
        data/lib/wtf_chord/fretboard.rb
    CHANGED
    
    | @@ -1,14 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'wtf_chord/guitar_string'
         | 
| 2 4 |  | 
| 3 5 | 
             
            module WTFChord
         | 
| 4 6 | 
             
              class Fretboard
         | 
| 5 | 
            -
                DEAD = "×" | 
| 7 | 
            +
                DEAD = "×"
         | 
| 6 8 |  | 
| 7 9 | 
             
                attr_reader :strings, :capo
         | 
| 8 10 |  | 
| 9 11 | 
             
                def initialize(*strings)
         | 
| 10 12 | 
             
                  @capo    = 0
         | 
| 11 | 
            -
                  @strings = strings.map | 
| 13 | 
            +
                  @strings = strings.map { |string| GuitarString.new(string, @capo) }
         | 
| 12 14 | 
             
                end
         | 
| 13 15 |  | 
| 14 16 | 
             
                def [] idx
         | 
| @@ -23,6 +25,14 @@ module WTFChord | |
| 23 25 | 
             
                  self
         | 
| 24 26 | 
             
                end
         | 
| 25 27 |  | 
| 28 | 
            +
                def with_capo(capo)
         | 
| 29 | 
            +
                  capo_was = @capo
         | 
| 30 | 
            +
                  set_capo(capo)
         | 
| 31 | 
            +
                  yield
         | 
| 32 | 
            +
                ensure
         | 
| 33 | 
            +
                  set_capo(capo_was)
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 26 36 | 
             
                def fingers
         | 
| 27 37 | 
             
                  @strings.map { |string| string.dead? ? DEAD : string.fret  }
         | 
| 28 38 | 
             
                end
         | 
| @@ -1,14 +1,19 @@ | |
| 1 | 
            -
             | 
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'wtf_chord/instrument_pitch'
         | 
| 2 4 |  | 
| 3 5 | 
             
            module WTFChord
         | 
| 4 | 
            -
              class GuitarString <  | 
| 6 | 
            +
              class GuitarString < InstrumentPitch
         | 
| 5 7 | 
             
                attr_reader :capo, :fret, :original
         | 
| 6 8 |  | 
| 7 9 | 
             
                def initialize(note, capo = 0)
         | 
| 8 | 
            -
                  @original = WTFChord.note(note)
         | 
| 9 10 | 
             
                  @fret = nil
         | 
| 10 11 | 
             
                  @capo = capo
         | 
| 11 | 
            -
                  super( | 
| 12 | 
            +
                  super(note)
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def init_pitch
         | 
| 16 | 
            +
                  @original + capo
         | 
| 12 17 | 
             
                end
         | 
| 13 18 |  | 
| 14 19 | 
             
                def initialize_dup(other)
         | 
| @@ -48,11 +53,18 @@ module WTFChord | |
| 48 53 | 
             
                  dead? ? -1 : to_i
         | 
| 49 54 | 
             
                end
         | 
| 50 55 |  | 
| 56 | 
            +
                def distance_to(pitch)
         | 
| 57 | 
            +
                  pos = 0
         | 
| 58 | 
            +
                  opened = dup.open
         | 
| 59 | 
            +
                  pos += 1 while (opened + pos) != pitch
         | 
| 60 | 
            +
                  pos
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 51 63 | 
             
                def <=> other
         | 
| 52 64 | 
             
                  return if dead?
         | 
| 53 65 | 
             
                  case other
         | 
| 54 | 
            -
                  when Integer | 
| 55 | 
            -
                  when GuitarString | 
| 66 | 
            +
                  when Integer       then @fret <=> other
         | 
| 67 | 
            +
                  when GuitarString  then @fret <=> other.fret
         | 
| 56 68 | 
             
                  end
         | 
| 57 69 | 
             
                end
         | 
| 58 70 |  | 
| @@ -1,6 +1,8 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module WTFChord
         | 
| 2 4 | 
             
              module RomanNumbersHelper
         | 
| 3 | 
            -
                ROMAN_SYMBOLS ||=  | 
| 5 | 
            +
                ROMAN_SYMBOLS ||= ("\u2160".."\u216B").to_a.unshift(nil).freeze
         | 
| 4 6 |  | 
| 5 7 | 
             
                def romanize(number)
         | 
| 6 8 | 
             
                  ROMAN_SYMBOLS[number]
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'wtf_chord/pitch'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module WTFChord
         | 
| 6 | 
            +
              class InstrumentPitch < DelegateClass(Pitch)
         | 
| 7 | 
            +
                attr_reader :interval, :original
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(note)
         | 
| 10 | 
            +
                  @original = WTFChord.note(note)
         | 
| 11 | 
            +
                  super(init_pitch)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def init_pitch
         | 
| 15 | 
            +
                  @original
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def initialize_dup(other)
         | 
| 19 | 
            +
                  super
         | 
| 20 | 
            +
                  __setobj__(@original.dup)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def dead
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def dead?
         | 
| 27 | 
            +
                  raise NotImplementedError
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def holded?
         | 
| 31 | 
            +
                  raise NotImplementedError
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def distance_to(pitch)
         | 
| 35 | 
            +
                  pos = 0
         | 
| 36 | 
            +
                  pos += 1 while (original + pos) != pitch
         | 
| 37 | 
            +
                  pos
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def code
         | 
| 41 | 
            +
                  to_i
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def <=> other
         | 
| 45 | 
            +
                  case other
         | 
| 46 | 
            +
                  when Integer
         | 
| 47 | 
            +
                    to_i <=> other
         | 
| 48 | 
            +
                  when InstrumentPitch
         | 
| 49 | 
            +
                    to_i <=> other.to_i
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "wtf_chord/piano_key"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module WTFChord
         | 
| 6 | 
            +
              class Keyboard
         | 
| 7 | 
            +
                attr_reader :keys
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                KEYS = %w(C C# D D# E F F# G G# A Bb B)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(octaves = 1..5)
         | 
| 12 | 
            +
                  @keys = octaves.to_a.product(KEYS).map { |(o, k)| PianoKey.new("#{k}#{o}") }
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def [] idx
         | 
| 16 | 
            +
                  case idx
         | 
| 17 | 
            +
                  when 1..@keys.size then @keys[~-idx]
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'wtf_chord/helpers/roman_numbers_helper'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module WTFChord
         | 
| 6 | 
            +
              class KeyboardPresentation
         | 
| 7 | 
            +
                FRAME = <<~EOF
         | 
| 8 | 
            +
                ┌─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┐┬
         | 
| 9 | 
            +
                │ │ ││ │ │ │ ││ ││ │ ││
         | 
| 10 | 
            +
                │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ ││
         | 
| 11 | 
            +
                │  │  │  │  │  │  │  ││
         | 
| 12 | 
            +
                └──┴──┴──┴──┴──┴──┴──┘┴
         | 
| 13 | 
            +
                 ↑                    |
         | 
| 14 | 
            +
                 %s                    |
         | 
| 15 | 
            +
                                      |
         | 
| 16 | 
            +
                EOF
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                include RomanNumbersHelper
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                KeyStruct = Struct.new(:index) do
         | 
| 21 | 
            +
                  singleton_class.attr_accessor :mark
         | 
| 22 | 
            +
                  singleton_class.alias_method :[], :new
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def call(frame)
         | 
| 25 | 
            +
                    frame[index, mark.length] = mark
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  private def mark
         | 
| 29 | 
            +
                    self.class.mark
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                Key = -> (mark) { Class.new(KeyStruct).tap { |c| c.mark = mark } }
         | 
| 34 | 
            +
                W, B = Key["▐▌"], Key["█"]
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                KEYS = [W[73], B[27], W[76], B[30], W[79], W[82], B[36], W[85], B[39], W[88], B[42], W[91]].
         | 
| 37 | 
            +
                  map.with_index(1).to_h.invert.freeze
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def self.press(*positions)
         | 
| 40 | 
            +
                  if positions.all? { |v| Integer === v }
         | 
| 41 | 
            +
                    new.press(*positions)
         | 
| 42 | 
            +
                  else
         | 
| 43 | 
            +
                    positions.uniq(&:key).group_by(&:octave).inject(nil) do |a, (octave, strings)|
         | 
| 44 | 
            +
                      pos = strings.map { |s| s.note.position }
         | 
| 45 | 
            +
                      kb = new(octave).press(*pos)
         | 
| 46 | 
            +
                      a.nil? ? kb : a + kb
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                attr_reader :frame
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def initialize(octave = 1)
         | 
| 54 | 
            +
                  @octave = octave
         | 
| 55 | 
            +
                  @frame = FRAME.dup % romanize(octave)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def +@
         | 
| 59 | 
            +
                  shift!
         | 
| 60 | 
            +
                  self
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def +(other)
         | 
| 64 | 
            +
                  pop!
         | 
| 65 | 
            +
                  @frame.replace(@frame.lines(chomp: true).zip((+other).frame.lines).map(&:join).join)
         | 
| 66 | 
            +
                  self
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def press(*positions)
         | 
| 70 | 
            +
                  positions.each { |pos| KEYS.fetch(pos).(@frame) }
         | 
| 71 | 
            +
                  self
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def to_s
         | 
| 75 | 
            +
                  finalize!
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                def finalize!
         | 
| 79 | 
            +
                  @frame.gsub("|", " ").gsub(/[^\s](?=\n)/, "")
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def pop!
         | 
| 83 | 
            +
                  @frame.replace(@frame.lines.each { |line| line[-3, 1] = "" }.join)
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def shift!
         | 
| 87 | 
            +
                  @frame.replace(@frame.lines.each { |line| line[0, 1] = "" }.join)
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "wtf_chord/instrument_pitch"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module WTFChord
         | 
| 6 | 
            +
              class PianoKey < InstrumentPitch
         | 
| 7 | 
            +
                def pressed?
         | 
| 8 | 
            +
                  !!@pressed
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def dead?
         | 
| 12 | 
            +
                  !pressed?
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def press!
         | 
| 16 | 
            +
                  @pressed = true
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
    
        data/lib/wtf_chord/pitch.rb
    CHANGED
    
    
    
        data/lib/wtf_chord/scale.rb
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'wtf_chord/note'
         | 
| 2 4 | 
             
            require 'wtf_chord/pitch'
         | 
| 3 5 | 
             
            require 'wtf_chord/fretboard'
         | 
| @@ -30,9 +32,9 @@ module WTFChord | |
| 30 32 |  | 
| 31 33 | 
             
              private_constant :ScaleArray
         | 
| 32 34 |  | 
| 33 | 
            -
              FLAT    ||= "b" | 
| 34 | 
            -
              SHARP   ||= "#" | 
| 35 | 
            -
              SIGNS   ||= { FLAT => "\u266D" | 
| 35 | 
            +
              FLAT    ||= "b"
         | 
| 36 | 
            +
              SHARP   ||= "#"
         | 
| 37 | 
            +
              SIGNS   ||= { FLAT => "\u266D", SHARP => "\u266F" }.freeze
         | 
| 36 38 |  | 
| 37 39 | 
             
              DIATONIC = {
         | 
| 38 40 | 
             
                "C" => "Do",
         | 
| @@ -43,7 +45,7 @@ module WTFChord | |
| 43 45 | 
             
                "A" => "La",
         | 
| 44 46 | 
             
                "B" => "Si",
         | 
| 45 47 | 
             
                "H" => "Si"
         | 
| 46 | 
            -
              } | 
| 48 | 
            +
              }
         | 
| 47 49 |  | 
| 48 50 | 
             
              SCALE ||= begin
         | 
| 49 51 | 
             
                chromatic_scale = %W(
         | 
| @@ -60,8 +62,22 @@ module WTFChord | |
| 60 62 | 
             
                  Bb|A#
         | 
| 61 63 | 
             
                  B|H
         | 
| 62 64 | 
             
                )
         | 
| 63 | 
            -
                ScaleArray.build(*chromatic_scale) | 
| 65 | 
            +
                ScaleArray.build(*chromatic_scale)
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              def self.guitar=(guitar)
         | 
| 69 | 
            +
                Thread.current[:wtf_guitar] = guitar
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def self.guitar
         | 
| 73 | 
            +
                Thread.current[:wtf_guitar] ||= Fretboard.new(*%w(E2 A2 D3 G3 B3 E4))
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              def self.piano=(piano)
         | 
| 77 | 
            +
                Thread.current[:wtf_piano] = piano
         | 
| 64 78 | 
             
              end
         | 
| 65 79 |  | 
| 66 | 
            -
               | 
| 80 | 
            +
              def self.piano
         | 
| 81 | 
            +
                Thread.current[:wtf_piano] ||= Keyboard.new
         | 
| 82 | 
            +
              end
         | 
| 67 83 | 
             
            end
         | 
    
        data/lib/wtf_chord/version.rb
    CHANGED
    
    
    
        data/wtf_chord.gemspec
    CHANGED
    
    | @@ -22,7 +22,7 @@ Gem::Specification.new do |spec| | |
| 22 22 |  | 
| 23 23 | 
             
              spec.add_runtime_dependency "methadone"
         | 
| 24 24 |  | 
| 25 | 
            -
              spec.add_development_dependency "bundler" | 
| 26 | 
            -
              spec.add_development_dependency "rake" | 
| 25 | 
            +
              spec.add_development_dependency "bundler"
         | 
| 26 | 
            +
              spec.add_development_dependency "rake"
         | 
| 27 27 | 
             
              spec.add_development_dependency "rspec", ">= 3.0", "< 4"
         | 
| 28 28 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: wtf_chord
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.7.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Anton
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-03-29 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: methadone
         | 
| @@ -30,40 +30,28 @@ dependencies: | |
| 30 30 | 
             
                requirements:
         | 
| 31 31 | 
             
                - - ">="
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            -
                    version: ' | 
| 34 | 
            -
                - - "<"
         | 
| 35 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 36 | 
            -
                    version: '2'
         | 
| 33 | 
            +
                    version: '0'
         | 
| 37 34 | 
             
              type: :development
         | 
| 38 35 | 
             
              prerelease: false
         | 
| 39 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 40 37 | 
             
                requirements:
         | 
| 41 38 | 
             
                - - ">="
         | 
| 42 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 43 | 
            -
                    version: ' | 
| 44 | 
            -
                - - "<"
         | 
| 45 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 46 | 
            -
                    version: '2'
         | 
| 40 | 
            +
                    version: '0'
         | 
| 47 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 48 42 | 
             
              name: rake
         | 
| 49 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 50 44 | 
             
                requirements:
         | 
| 51 45 | 
             
                - - ">="
         | 
| 52 46 | 
             
                  - !ruby/object:Gem::Version
         | 
| 53 | 
            -
                    version: ' | 
| 54 | 
            -
                - - "<"
         | 
| 55 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 56 | 
            -
                    version: '12'
         | 
| 47 | 
            +
                    version: '0'
         | 
| 57 48 | 
             
              type: :development
         | 
| 58 49 | 
             
              prerelease: false
         | 
| 59 50 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 60 51 | 
             
                requirements:
         | 
| 61 52 | 
             
                - - ">="
         | 
| 62 53 | 
             
                  - !ruby/object:Gem::Version
         | 
| 63 | 
            -
                    version: ' | 
| 64 | 
            -
                - - "<"
         | 
| 65 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 66 | 
            -
                    version: '12'
         | 
| 54 | 
            +
                    version: '0'
         | 
| 67 55 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 68 56 | 
             
              name: rspec
         | 
| 69 57 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -95,6 +83,7 @@ files: | |
| 95 83 | 
             
            - ".gitignore"
         | 
| 96 84 | 
             
            - ".rspec"
         | 
| 97 85 | 
             
            - ".travis.yml"
         | 
| 86 | 
            +
            - CHANGELOG.md
         | 
| 98 87 | 
             
            - Gemfile
         | 
| 99 88 | 
             
            - README.md
         | 
| 100 89 | 
             
            - Rakefile
         | 
| @@ -105,17 +94,26 @@ files: | |
| 105 94 | 
             
            - lib/wtf_chord.yml
         | 
| 106 95 | 
             
            - lib/wtf_chord/chord.rb
         | 
| 107 96 | 
             
            - lib/wtf_chord/cli.rb
         | 
| 97 | 
            +
            - lib/wtf_chord/collectors.rb
         | 
| 98 | 
            +
            - lib/wtf_chord/collectors/generic.rb
         | 
| 99 | 
            +
            - lib/wtf_chord/collectors/guitar.rb
         | 
| 100 | 
            +
            - lib/wtf_chord/collectors/piano.rb
         | 
| 108 101 | 
             
            - lib/wtf_chord/complexity_counter.rb
         | 
| 109 102 | 
             
            - lib/wtf_chord/fingering.rb
         | 
| 110 | 
            -
            - lib/wtf_chord/fingerings_generator.rb
         | 
| 111 103 | 
             
            - lib/wtf_chord/formatter.rb
         | 
| 112 104 | 
             
            - lib/wtf_chord/formatters/base.rb
         | 
| 113 105 | 
             
            - lib/wtf_chord/formatters/default.rb
         | 
| 106 | 
            +
            - lib/wtf_chord/formatters/piano.rb
         | 
| 107 | 
            +
            - lib/wtf_chord/formatters/piano1.rb
         | 
| 114 108 | 
             
            - lib/wtf_chord/formatters/simple.rb
         | 
| 115 109 | 
             
            - lib/wtf_chord/fretboard.rb
         | 
| 116 110 | 
             
            - lib/wtf_chord/guitar_string.rb
         | 
| 117 111 | 
             
            - lib/wtf_chord/helpers/roman_numbers_helper.rb
         | 
| 112 | 
            +
            - lib/wtf_chord/instrument_pitch.rb
         | 
| 113 | 
            +
            - lib/wtf_chord/keyboard.rb
         | 
| 114 | 
            +
            - lib/wtf_chord/keyboard_presentation.rb
         | 
| 118 115 | 
             
            - lib/wtf_chord/note.rb
         | 
| 116 | 
            +
            - lib/wtf_chord/piano_key.rb
         | 
| 119 117 | 
             
            - lib/wtf_chord/pitch.rb
         | 
| 120 118 | 
             
            - lib/wtf_chord/rules.rb
         | 
| 121 119 | 
             
            - lib/wtf_chord/scale.rb
         | 
| @@ -139,10 +137,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 139 137 | 
             
                - !ruby/object:Gem::Version
         | 
| 140 138 | 
             
                  version: '0'
         | 
| 141 139 | 
             
            requirements: []
         | 
| 142 | 
            -
             | 
| 143 | 
            -
            rubygems_version: 2.6.2
         | 
| 140 | 
            +
            rubygems_version: 3.0.3
         | 
| 144 141 | 
             
            signing_key: 
         | 
| 145 142 | 
             
            specification_version: 4
         | 
| 146 143 | 
             
            summary: "‘WTF Chord?’ is the Ruby guitar chords generator library."
         | 
| 147 144 | 
             
            test_files: []
         | 
| 148 | 
            -
            has_rdoc: 
         | 
| @@ -1,92 +0,0 @@ | |
| 1 | 
            -
            module WTFChord
         | 
| 2 | 
            -
              class Chord
         | 
| 3 | 
            -
              end
         | 
| 4 | 
            -
             | 
| 5 | 
            -
              class FingeringsGenerator < DelegateClass(Chord)
         | 
| 6 | 
            -
                MAX_DIST = 5
         | 
| 7 | 
            -
                MAX_FRET = 7
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                def fingerings
         | 
| 10 | 
            -
                  @fingerings ||= []
         | 
| 11 | 
            -
                end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                def call
         | 
| 14 | 
            -
                  fingerings.clear
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                  (0...MAX_FRET).each do |from|
         | 
| 17 | 
            -
                    to = from + MAX_DIST
         | 
| 18 | 
            -
                    generate(from...to) do |variant|
         | 
| 19 | 
            -
                      fingerings << variant if filter_variant(variant)
         | 
| 20 | 
            -
                    end
         | 
| 21 | 
            -
                  end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                  fingerings.sort_by!(&:complexity)
         | 
| 24 | 
            -
                end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                private
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                def generate(fret_range)
         | 
| 29 | 
            -
                  combinations = GUITAR.strings.map.with_index do |s, index|
         | 
| 30 | 
            -
                    fret_range.select do |dist|
         | 
| 31 | 
            -
                      pitch = s.original + dist
         | 
| 32 | 
            -
                      has_note?(pitch)
         | 
| 33 | 
            -
                    end.tap do |frets|
         | 
| 34 | 
            -
                      frets << nil if frets.size == 0
         | 
| 35 | 
            -
                    end
         | 
| 36 | 
            -
                  end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  combinations.inject(&:product).each do |fingers|
         | 
| 39 | 
            -
                    fingers.flatten!
         | 
| 40 | 
            -
                    Fingering.new(GUITAR, fingers) do |variant|
         | 
| 41 | 
            -
                      adjust(variant)
         | 
| 42 | 
            -
                      yield(variant)
         | 
| 43 | 
            -
                    end
         | 
| 44 | 
            -
                  end
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                def filter_variant(variant)
         | 
| 48 | 
            -
                  used_strings = variant.used_strings
         | 
| 49 | 
            -
                  used_notes = used_strings.map(&:note)
         | 
| 50 | 
            -
                  tones_count = notes.map { |n| used_notes.count(n) }
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                  !fingerings.include?(variant) &&
         | 
| 53 | 
            -
                  basetone?(used_strings[0]) &&
         | 
| 54 | 
            -
                  (
         | 
| 55 | 
            -
                    (third?(used_strings[1]) || third?(used_strings[2])) ||
         | 
| 56 | 
            -
                    (last?(used_strings[1]) || last?(used_strings[2]))
         | 
| 57 | 
            -
                  ) &&
         | 
| 58 | 
            -
                  all_notes?(used_notes) &&
         | 
| 59 | 
            -
                  used_notes.each_cons(2).none? { |(l, r)| l == r } &&
         | 
| 60 | 
            -
                  tones_count.all? { |n| tones_count[0] >= n || tones_count[notes.index(third_tone)] >= n } &&
         | 
| 61 | 
            -
                  variant.complexity <= 2.25
         | 
| 62 | 
            -
                end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                def adjust(fingering)
         | 
| 65 | 
            -
                  while (string = fingering.used_strings[0]) && !basetone?(string)
         | 
| 66 | 
            -
                    string.dead if !string.dead?
         | 
| 67 | 
            -
                    break if fingering.used_strings.size == 4
         | 
| 68 | 
            -
                  end
         | 
| 69 | 
            -
                end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                def all_notes?(used_notes)
         | 
| 72 | 
            -
                  notes.all? { |n| used_notes.include?(n) }
         | 
| 73 | 
            -
                end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                def has_note?(value)
         | 
| 76 | 
            -
                  value = value.note if !value.is_a?(Note) && value.respond_to?(:note)
         | 
| 77 | 
            -
                  notes.include?(value)
         | 
| 78 | 
            -
                end
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                def basetone?(string)
         | 
| 81 | 
            -
                  string && string.note == notes[0]
         | 
| 82 | 
            -
                end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                def third?(string)
         | 
| 85 | 
            -
                  string && (string.note == third_tone)
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                def last?(string)
         | 
| 89 | 
            -
                  string && (string.note == notes[-1])
         | 
| 90 | 
            -
                end
         | 
| 91 | 
            -
              end
         | 
| 92 | 
            -
            end
         |