<commit_before>module MusicTheory
  class Note
    attr_reader :diatonic_index, :accidentals, :octave

    def initialize(diatonic_index, accidentals=0, octave=nil)
      @diatonic_index = diatonic_index
      @accidentals = accidentals
      @octave = octave
    end

    def diatonic_s
      ('A'.ord + (diatonic_index + 2) % 7).chr
    end

    def accidental_s
      if accidentals < 0
        'b' * -accidentals
      else
        '#' * accidentals
      end
    end

    def to_s(opts = {})
      result = "#{diatonic_s}#{accidental_s}"
      result += "#{octave}" if octave
      result
    end

    def inspect(opts = {})
      to_s(opts)
    end

    def sharp(n=1)
      n = n.to_i
      Note.new(diatonic_index, accidentals + n, octave)
    end

    def flat(n=1)
      sharp(-n)
    end

    def chromatic_index
      basis = Interval.semitone_basis(diatonic_index + 1)
      basis + accidentals
    end

    def with_octave(octave)
      Note.new(diatonic_index, accidentals, octave)
    end

    def without_octave
      with_octave(nil)
    end

    def ==(note)
      return false unless note.is_a? Note
      return false unless note.diatonic_index == self.diatonic_index && note.accidentals == self.accidentals
      if note.octave && self.octave
        return false unless note.octave == self.octave
      end
      true
    end

    def eql?(other)
      self == other && other.octave == self.octave
    end

    def hash
      [diatonic_index, accidentals, octave].hash
    end

    # these make the algebra with intervals work out nicely
    def +@
      self
    end

    def -@
      self
    end

    def +(interval)
      interval = Scale.major[interval.to_i] if interval.is_a? Numeric

      idx = (diatonic_index + interval.diatonic_offset) % 7
      oct = (diatonic_index + interval.diatonic_offset) / 7
      result = Note.new(idx, accidentals, octave ? octave + oct : nil)

      if interval.specific?
        accidental_offset = chromatic_index - (result.chromatic_index + oct * 12) + interval.semitones
        result = result.sharp(accidental_offset)
      end

      result
    end

    def -(n)
      if n.is_a? Note
        n.measure_to self
      else
        self + -n
      end
    end

    def measure_to(note)
      if note.octave && self.octave
        oct = note.octave - self.octave
      else
        oct = note.diatonic_index < self.diatonic_index ? 1 : 0
      end
      c_idx = oct * 12 + note.chromatic_index - self.chromatic_index
      d_idx = oct * 7 + note.diatonic_index - self.diatonic_index<commit_msg>Handle note comparison to scales and chords<commit_after>module MusicTheory
  class Note
    attr_reader :diatonic_index, :accidentals, :octave

    def initialize(diatonic_index, accidentals=0, octave=nil)
      @diatonic_index = diatonic_index
      @accidentals = accidentals
      @octave = octave
    end

    def diatonic_s
      ('A'.ord + (diatonic_index + 2) % 7).chr
    end

    def accidental_s
      if accidentals < 0
        'b' * -accidentals
      else
        '#' * accidentals
      end
    end

    def to_s(opts = {})
      result = "#{diatonic_s}#{accidental_s}"
      result += "#{octave}" if octave
      result
    end

    def inspect(opts = {})
      to_s(opts)
    end

    def sharp(n=1)
      n = n.to_i
      Note.new(diatonic_index, accidentals + n, octave)
    end

    def flat(n=1)
      sharp(-n)
    end

    def chromatic_index
      basis = Interval.semitone_basis(diatonic_index + 1)
      basis + accidentals
    end

    def with_octave(octave)
      Note.new(diatonic_index, accidentals, octave)
    end

    def without_octave
      with_octave(nil)
    end

    def ==(note)
      return false unless note.is_a? Note
      return false unless note.diatonic_index == self.diatonic_index && note.accidentals == self.accidentals
      if note.octave && self.octave
        return false unless note.octave == self.octave
      end
      true
    end

    def eql?(other)
      self == other && other.octave == self.octave
    end

    def hash
      [diatonic_index, accidentals, octave].hash
    end

    # these make the algebra with intervals work out nicely
    def +@
      self
    end

    def -@
      self
    end

    def +(interval)
      interval = Scale.major[interval.to_i] if interval.is_a? Numeric

      idx = (diatonic_index + interval.diatonic_offset) % 7
      oct = (diatonic_index + interval.diatonic_offset) / 7
      result = Note.new(idx, accidentals, octave ? octave + oct : nil)

      if interval.specific?
        accidental_offset = chromatic_index - (result.chromatic_index + oct * 12) + interval.semitones
        result = result.sharp(accidental_offset)
      end

      result
    end

    def -(n)
      if n.respond_to?(:root)
        self - n.root
      elsif n.is_a? Note
        n.measure_to self
      else
        self + -n
      end
    end

    def measure_to(note)
      if note.octave && self.octave
        oct = note.octave - self.octave
      else
        oct = note.diatonic_index < self.diatonic_index ? 1 : 0
      end
      c_idx = oct * 12 + note.chromatic_index - self.chromatic_index
      d_idx = oct * 7 + note.diatonic_index - self.diatonic_index
{'fraction_non_alphanumeric': 0.08856868395773294, 'fraction_numerical': 0.006147934678194044, 'mean_word_length': 2.1342564720048163, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4086902', 'n_tokens_mistral': 1804, 'n_tokens_neox': 1727, 'n_words': 423}