<commit_before>require 'guitar_pro_parser/io/input_stream'
require 'guitar_pro_parser/guitar_pro_helper'

module GuitarProParser

  class Reader

    def initialize(song, file_path, headers_only)
      @song = song
      @file_path = file_path

      @input = InputStream.new(file_path)

      read_version
      read_info
      read_notices
      @song.triplet_feel = @input.read_boolean if @version < 5.0
      read_lyrics if @version >= 4.0

      if @version >= 5.0
        @song.master_volume = @input.read_integer 
        @input.skip_integer
      end

      11.times { |n| @song.equalizer[n] = @input.read_byte } if @version >= 5.0
      read_page_setup if @version >= 5.0
      read_tempo
      read_key
      @song.octave = @input.read_byte if @version >= 4.0
      read_channels
      read_musical_directions if @version >= 5.0
      @song.master_reverb = @input.read_integer if @version >= 5.0

      bars_count = @input.read_integer
      tracks_count = @input.read_integer

      if headers_only
        bars_count.times { @song.add_bar_settings }
        tracks_count.times { @song.add_track }
        return
      end

      bars_count.times { read_bars_settings }
      tracks_count.times { read_track }
      @input.skip_byte if @version >= 5.0
      
      @song.bars_settings.each do |bar_settings| 
        @song.tracks.each do |track|
          bar = Bar.new

          voices_count = @version >= 5.0 ? 2 : 1

          voices_count.times do |voice_number|
            voice = GuitarProHelper::VOICES.fetch(voice_number)
            beats_count = @input.read_integer
            beats_count.times { read_beat(track, bar, voice) }
          end

          track.bars << bar

          # Padding
          @input.skip_byte if @version >= 5.0
        end
      end
    end

    private

    def read_version
      length = @input.read_byte
      version_string = @input.read_string length
      # TODO: Raise exception for unsupported or wrong versions here
      @version = GuitarProHelper::VERSIONS.fetch(version_string)
      @song.version = @version

      # Skip first 31 bytes that are reserved for version data
      @input.offset = 31
    end

    def read_info
      @song.title = @input.read_chunk
      @song.subtitle = @input.read_chunk
      @song.artist = @input.read_chunk
      @song.album = @input.read_chunk
      @song.lyricist = @input.read_chunk if @version >= 5.0
      @song.composer = @input.read_chunk
      @song.copyright = @input.read_chunk
      @song.transcriber = @input.read_chunk
      @song.instructions = @input.read_chunk
    end

    def read_notices
      notices = []

      notices_count = @input.read_integer
      notices_count.times { notices << @input.read_chunk }
      
      @song.notices = notices.join('/n')
    end

    # > 4.0 only
    def read_lyrics
      @song.lyrics_track = @input.read_integer

      5.times do 
        start_bar = @input.read_integer
        length = @input.read_integer
        lyrics_text = @input.read_string length
        @song.lyrics << {text: lyrics_text, bar: start_bar}
      end
    end

    #  >= 5.0 only
    def read_page_setup
      @song.page_setup.page_format_length = @input.read_integer
      @song.page_setup.page_format_width = @input.read_integer
      @song.page_setup.left_margin = @input.read_integer
      @song.page_setup.right_margin = @input.read_integer
      @song.page_setup.top_margin = @input.read_integer
      @song.page_setup.bottom_margin = @input.read_integer
      @song.page_setup.score_size = @input.read_integer

      # The enabled header/footer fields bitmask declares which fields are displayed:
      # Bit 0 (LSB):  Title field
      # Bit 1:    Subtitle field
      # Bit 2:    Artist field
      # Bit 3:    Album field
      # Bit 4:    Words (Lyricist) field
      # Bit 5:    Music (Composer) field
      # Bit 6:    Words & Music field
      # Bit 7:    Copyright field
      # Bit 8:    Page Number (field)
      # Bits 9 - 15:  Unused (set to 0)
      bits = @input.read_bitmask
      @song.page_setup.displayed_fields << :title if bits[0]
      @song.page_setup.displayed_fields << :subtitle if bits[1]
      @song.page_setup.displayed_fields << :artist if bits[2]
      @song.page_setup.displayed_fields << :album if bits[3]
      @song.page_setup.displayed_fields << :lyrics_author if bits[4]
      @song.page_setup.displayed_fields << :music_author if bits[5]
      @song.page_setup.displayed_fields << :lyrics_and_music_author if bits[6]
      @song.page_setup.displayed_fields << :copyright if bits[7]

      bits = @input.read_bitmask
      @song.page_setup.displayed_fields << :page_number if bits[0]

      @song.page_setup.title = @input.read_chunk
      @song.page_setup.subtitle = @input.read_chunk
      @song.page_setup.artist = @input.read_chunk
      @song.page_setup.album = @input.read_chunk
      @song.page_setup.lyrics_author = @input.read_chunk
      @song.page_setup.music_author = @input.read_chunk
      @song.page_setup.lyrics_and_music_author = @input.read_chunk
      @song.page_setup.copyright_line_1 = @input.read_chunk
      @song.page_setup.copyright_line_2 = @input.read_chunk
      @song.page_setup.page_number = @input.read_chunk
    end

    def read_tempo
      @song.tempo = @input.read_chunk if @version >= 5.0
      @song.bpm = @input.read_integer
      @input.skip_byte if @version >= 5.0
    end

    def read_key
      if @version >= 4.0
        @song.key = @input.read_byte
        @input.increment_offset(3)
      else
        @song.key = @input.read_integer
      end
    end
<commit_msg>More fixes for GTP v5.0.<commit_after>require 'guitar_pro_parser/io/input_stream'
require 'guitar_pro_parser/guitar_pro_helper'

module GuitarProParser

  class Reader

    def initialize(song, file_path, headers_only)
      @song = song
      @file_path = file_path

      @input = InputStream.new(file_path)

      read_version
      read_info
      read_notices
      @song.triplet_feel = @input.read_boolean if @version < 5.0
      read_lyrics if @version >= 4.0

      if @version > 5.0
        @song.master_volume = @input.read_integer 
        @input.skip_integer
        11.times { |n| @song.equalizer[n] = @input.read_byte }
      end
      
      read_page_setup if @version >= 5.0
      read_tempo
      read_key
      @song.octave = @input.read_byte if @version >= 4.0
      read_channels
      read_musical_directions if @version >= 5.0
      @song.master_reverb = @input.read_integer if @version >= 5.0

      bars_count = @input.read_integer
      tracks_count = @input.read_integer

      if headers_only
        bars_count.times { @song.add_bar_settings }
        tracks_count.times { @song.add_track }
        return
      end

      bars_count.times { read_bars_settings }
      tracks_count.times { read_track }
      @input.skip_byte if @version >= 5.0
      
      @song.bars_settings.each do |bar_settings| 
        @song.tracks.each do |track|
          bar = Bar.new

          voices_count = @version >= 5.0 ? 2 : 1

          voices_count.times do |voice_number|
            voice = GuitarProHelper::VOICES.fetch(voice_number)
            beats_count = @input.read_integer
            beats_count.times { read_beat(track, bar, voice) }
          end

          track.bars << bar

          # Padding
          @input.skip_byte if @version >= 5.0
        end
      end
    end

    private

    def read_version
      length = @input.read_byte
      version_string = @input.read_string length
      # TODO: Raise exception for unsupported or wrong versions here
      @version = GuitarProHelper::VERSIONS.fetch(version_string)
      @song.version = @version

      # Skip first 31 bytes that are reserved for version data
      @input.offset = 31
    end

    def read_info
      @song.title = @input.read_chunk
      @song.subtitle = @input.read_chunk
      @song.artist = @input.read_chunk
      @song.album = @input.read_chunk
      @song.lyricist = @input.read_chunk if @version >= 5.0
      @song.composer = @input.read_chunk
      @song.copyright = @input.read_chunk
      @song.transcriber = @input.read_chunk
      @song.instructions = @input.read_chunk
    end

    def read_notices
      notices = []

      notices_count = @input.read_integer
      notices_count.times { notices << @input.read_chunk }
      
      @song.notices = notices.join('/n')
    end

    # > 4.0 only
    def read_lyrics
      @song.lyrics_track = @input.read_integer

      5.times do 
        start_bar = @input.read_integer
        length = @input.read_integer
        lyrics_text = @input.read_string length
        @song.lyrics << {text: lyrics_text, bar: start_bar}
      end
    end

    #  >= 5.0 only
    def read_page_setup
      @song.page_setup.page_format_length = @input.read_integer
      @song.page_setup.page_format_width = @input.read_integer
      @song.page_setup.left_margin = @input.read_integer
      @song.page_setup.right_margin = @input.read_integer
      @song.page_setup.top_margin = @input.read_integer
      @song.page_setup.bottom_margin = @input.read_integer
      @song.page_setup.score_size = @input.read_integer

      # The enabled header/footer fields bitmask declares which fields are displayed:
      # Bit 0 (LSB):  Title field
      # Bit 1:    Subtitle field
      # Bit 2:    Artist field
      # Bit 3:    Album field
      # Bit 4:    Words (Lyricist) field
      # Bit 5:    Music (Composer) field
      # Bit 6:    Words & Music field
      # Bit 7:    Copyright field
      # Bit 8:    Page Number (field)
      # Bits 9 - 15:  Unused (set to 0)
      bits = @input.read_bitmask
      @song.page_setup.displayed_fields << :title if bits[0]
      @song.page_setup.displayed_fields << :subtitle if bits[1]
      @song.page_setup.displayed_fields << :artist if bits[2]
      @song.page_setup.displayed_fields << :album if bits[3]
      @song.page_setup.displayed_fields << :lyrics_author if bits[4]
      @song.page_setup.displayed_fields << :music_author if bits[5]
      @song.page_setup.displayed_fields << :lyrics_and_music_author if bits[6]
      @song.page_setup.displayed_fields << :copyright if bits[7]

      bits = @input.read_bitmask
      @song.page_setup.displayed_fields << :page_number if bits[0]

      @song.page_setup.title = @input.read_chunk
      @song.page_setup.subtitle = @input.read_chunk
      @song.page_setup.artist = @input.read_chunk
      @song.page_setup.album = @input.read_chunk
      @song.page_setup.lyrics_author = @input.read_chunk
      @song.page_setup.music_author = @input.read_chunk
      @song.page_setup.lyrics_and_music_author = @input.read_chunk
      @song.page_setup.copyright_line_1 = @input.read_chunk
      @song.page_setup.copyright_line_2 = @input.read_chunk
      @song.page_setup.page_number = @input.read_chunk
    end

    def read_tempo
      @song.tempo = @input.read_chunk if @version >= 5.0
      @song.bpm = @input.read_integer
      @input.skip_byte if @version > 5.0
    end

    def read_key
      if @version >= 4.0
        @song.key = @input.read_byte
        @input.increment_offset(3)
      else
        @song.key = @input.read_integer
      end
    end

{'fraction_non_alphanumeric': 0.09673951988534575, 'fraction_numerical': 0.012182013615191688, 'mean_word_length': 2.463089330024814, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 34, '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': '909743', 'n_tokens_mistral': 3883, 'n_tokens_neox': 3620, 'n_words': 751}