GTP 0.0.1 → 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c95f11dd3b8984c35d4a5689256f044d63efd63b
4
+ data.tar.gz: 2f350da3f47500d24a2a9467b6f9d004b9dc6a44
5
+ SHA512:
6
+ metadata.gz: f5b9655918545ae36cf5c75948c70e89030226cf52638b1d1905051a1c5adfaba7ab98cee2322a76856e40a7d9f8047a936ef97a214adddb96f2a7a15ff3f680
7
+ data.tar.gz: 14165f6937a8825c3bea4fb3be97a6d660101ed45eeb6d349a72b3999cbb9ca1813e92a543cf69f2c0ff1c6b9e48fbf9379b28f80a4ac64448a1601affc56a91
data/GTP.gemspec CHANGED
@@ -20,4 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency 'pry'
24
+ spec.add_development_dependency 'json'
23
25
  end
data/lib/GTP.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require "GTP/version"
2
2
  require "GTP/gp4"
3
+ require "GTP/measure"
4
+ require "GTP/io/reader"
3
5
 
4
6
  module GTP
5
-
7
+ def configure(&block)
8
+ block.call self
9
+ end
6
10
  end
data/lib/GTP/gp4.rb CHANGED
@@ -1,117 +1,198 @@
1
+ require "json"
2
+
1
3
  module GTP
2
4
  class GP4
5
+ FIELDS = %w(title subtitle artist album author copyright tab instruction notice triplet_feel)
6
+
7
+ attr_accessor :file, :version, :offset, :lyrics, :tempo, :key, :octave, :num_measures, :num_tracks, :measures
8
+
9
+ attr_accessor *FIELDS
3
10
 
4
- attr_accessor :file,
5
- :version,
6
- :title,
7
- :subtitle,
8
- :artist,
9
- :album,
10
- :author,
11
- :copyright,
12
- :tab,
13
- :instruction,
14
- :notice,
15
- :triplet_feel,
16
- :offset
17
11
  attr_reader :file_path
18
12
 
19
13
  INTEGER_SIZE = 4
20
14
 
21
15
  def initialize(tab_path)
16
+ @reader = Reader.new tab_path
22
17
  @file = File.open(tab_path, "r")
23
- @offset = 31
24
18
  end
25
19
 
26
- def read_string
27
-
28
- self.offset = self.offset+INTEGER_SIZE
20
+ def call
21
+ parse_version
22
+ parse_info
23
+ parse_lyrics
24
+ end
29
25
 
30
- length = IO.binread(self.file, 1, self.offset).bytes.to_a[0].to_i
26
+ def fix_header header
31
27
 
32
- self.offset = self.offset + 1
28
+ counter = 0
33
29
 
34
- string = IO.binread(self.file, length, self.offset)
30
+ new_header = header.reverse
35
31
 
36
- self.offset = self.offset + length
32
+ while 8 > new_header.size
33
+ new_header << "0"
34
+ end
37
35
 
38
- return string
36
+ return new_header
39
37
  end
40
38
 
41
39
  def parse_version
42
-
43
- size = IO.binread(self.file, 1).bytes.to_a[0].to_i
44
-
45
- self.version = IO.binread(self.file, size, 1).strip
40
+ self.version = @reader.read_string
46
41
  end
47
42
 
48
43
  def parse_info
49
- parse_title
50
- parse_subtitle
51
- parse_artist
52
- parse_album
53
- parse_author
54
- parse_copyright
55
- parse_tab
56
- parse_instruction
57
- parse_notice
58
- parse_triplet_feel
44
+ FIELDS.each do |field|
45
+ self.public_send "parse_#{field}"
46
+ end
59
47
  end
60
48
 
61
- def parse_title
62
- self.title = read_string
49
+ FIELDS.each do |field|
50
+ define_method "parse_#{field}" do
51
+ self.public_send "#{field}=", @reader.read_default_string
52
+ end
63
53
  end
64
54
 
65
- def parse_subtitle
66
- self.subtitle = read_string
67
- end
55
+ def parse_notice
56
+ linesCount = @reader.read_integer
68
57
 
69
- def parse_artist
70
- self.artist = read_string
71
- end
58
+ notice = ""
72
59
 
73
- def parse_album
74
- self.album = read_string
75
- end
60
+ linesCount.times do
61
+ notice << @reader.read_default_string << "\n"
62
+ end
76
63
 
77
- def parse_author
78
- self.author = read_string
64
+ self.notice = notice
79
65
  end
80
66
 
81
- def parse_copyright
82
- self.copyright = read_string
67
+ def parse_triplet_feel
68
+ self.triplet_feel = @reader.read_byte.to_s
83
69
  end
84
70
 
85
- def parse_tab
86
- self.tab = read_string
71
+ def parse_lyrics
72
+ track = @reader.read_integer
73
+ self.lyrics = []
74
+
75
+ 5.times do
76
+
77
+ bar = @reader.read_integer
78
+ content = @reader.read_int_string
79
+
80
+ tuple = {}
81
+ tuple.store(bar, content.gsub("\r\n", "\n"))
82
+
83
+ self.lyrics.push(tuple)
84
+ end
87
85
  end
88
86
 
89
- def parse_instruction
90
- self.instruction = read_string
87
+ def parse_tempo
88
+ self.tempo = @reader.read_integer.to_i
91
89
  end
92
90
 
93
- def parse_notice
94
- lines = IO.binread(self.file, INTEGER_SIZE, self.offset).unpack("L")[0]
91
+ def parse_key
92
+ self.key = @reader.read_integer
93
+ end
95
94
 
96
- self.offset = self.offset+INTEGER_SIZE
95
+ def parse_octave
96
+ self.octave = @reader.read_byte
97
+ end
97
98
 
98
- notice = ""
99
+ def parse_midi_channels
100
+ @reader.increment_offset (12 * 16 * 4) # TODO
101
+ end
99
102
 
100
- for i in 1..lines-1
101
- notice << read_string << "\n"
102
- end
103
+ def parse_number_of_measures
104
+ self.num_measures = @reader.read_integer.to_i
105
+ end
103
106
 
104
- notice << read_string
105
-
106
- self.notice = notice
107
+ def parse_number_of_tracks
108
+ self.num_tracks = @reader.read_integer
107
109
  end
108
110
 
109
- def parse_triplet_feel
110
- self.triplet_feel = IO.binread(self.file, 1, self.offset).bytes.to_a[0].to_s
111
+ def parse_measures
112
+
113
+ self.measures = []
114
+
115
+ for n in 1..self.num_measures do
116
+
117
+ header = fix_header read_byte.to_s(2)
118
+ numerator = nil
119
+ denominator = nil
120
+ begin_repeat = nil
121
+ end_repeat = nil
122
+ marker_name = nil
123
+ marker_color = nil
124
+ tonality = nil
125
+ double_bar = nil
126
+
127
+ if header[0] == "1"
128
+ numerator = read_byte
129
+ end
130
+
131
+ if header[1] == "1"
132
+ denominator = read_byte
133
+ end
134
+
135
+ if header[2] == "1"
136
+ begin_repeat = true
137
+ end
138
+
139
+ if header[3] == "1"
140
+ end_repeat = read_byte
141
+ end
142
+
143
+ if header[4] == "1"
144
+ num_alt_ending = read_byte
145
+ end
146
+
147
+ if header[5] == "1"
148
+ marker_name = @reader.read_string
149
+ marker_color = read_integer
150
+ end
151
+
152
+ if header[6] == "1"
153
+ tonality = read_byte
154
+ @reader.increment_offset 1
155
+ end
156
+
157
+ if header[7] == "1"
158
+ double_bar = true
159
+ end
160
+
161
+ measure = Measure.new
162
+ measure.numerator = numerator
163
+ measure.denominator = denominator
164
+ measure.begin_repeat = begin_repeat
165
+ measure.end_repeat = end_repeat
166
+ measure.num_alt_ending = num_alt_ending
167
+ measure.marker_name = marker_name
168
+ measure.marker_color = marker_color
169
+ measure.tonality = tonality
170
+ measure.double_bar = double_bar
171
+
172
+ self.measures.push(measure)
173
+ end
111
174
  end
112
175
 
113
176
  def to_json
114
-
177
+ {
178
+
179
+ "score" => {
180
+
181
+ "info" => {
182
+ "version" => self.version,
183
+ "title" => self.title,
184
+ "subtitle" => self.subtitle,
185
+ "artist" => self.artist,
186
+ "album" => self.album,
187
+ "author" => self.author,
188
+ "copyright" => self.copyright
189
+ },
190
+ "tempo" => self.tempo,
191
+ "key" => self.key,
192
+ "num_track" => self.num_tracks,
193
+ "num_measures" => self.num_measures
194
+ }
195
+ }.to_json
115
196
  end
116
197
  end
117
- end
198
+ end
@@ -0,0 +1,78 @@
1
+ module GTP
2
+ class Reader
3
+
4
+ attr_accessor :offset, :file
5
+ INTEGER_SIZE = 4
6
+
7
+ def initialize(file)
8
+ @file = File.open(file, "r")
9
+ @offset = 0
10
+ end
11
+
12
+ def read_next_chunk_with_offset(size, offset)
13
+ increment_offset size
14
+ IO.binread(self.file, size, offset)
15
+ end
16
+
17
+ def read_integer
18
+ result = IO.binread(self.file, INTEGER_SIZE, self.offset).unpack('L').first
19
+ increment_offset INTEGER_SIZE
20
+ result
21
+ end
22
+
23
+ def increment_offset(delta)
24
+ self.offset = self.offset + delta
25
+ end
26
+
27
+ def read_default_string(amount_to_read=1)
28
+ skip_useless_bytes
29
+ read_next_string amount_to_read
30
+ end
31
+
32
+ def read_string
33
+ value = read_next_string
34
+ skip_bytes value.length
35
+ value
36
+ end
37
+
38
+ def read_first_string_and_unpack
39
+ read_next_string.unpack("L")
40
+ end
41
+
42
+ def read_int_string
43
+ read_next_string INTEGER_SIZE
44
+ end
45
+
46
+ def read_byte
47
+
48
+ result = IO.binread(self.file, 1, self.offset).bytes.to_a
49
+
50
+ increment_offset 1
51
+
52
+ raise "Byte is greather than expected" if result.size > 1
53
+
54
+ result.first.to_i
55
+ end
56
+
57
+ def skip_integer
58
+ increment_offset INTEGER_SIZE
59
+ end
60
+
61
+ protected
62
+ def skip_useless_bytes
63
+ increment_offset INTEGER_SIZE
64
+ end
65
+
66
+ def skip_bytes(size)
67
+ increment_offset 30-size
68
+ end
69
+
70
+ def read_next_string(amount_to_read=1)
71
+ size = IO.binread(self.file, amount_to_read, self.offset).bytes.to_a.first.to_i
72
+ increment_offset amount_to_read
73
+ value = IO.binread(self.file, size, self.offset).strip
74
+ increment_offset size
75
+ value
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,18 @@
1
+ module GTP
2
+ class Measure
3
+ FIELDS = %w(double_bar tonality marker_name marker_color num_alt_ending end_repeat begin_repeat denominator numerator)
4
+ attr_accessor *FIELDS
5
+
6
+ def ==(o)
7
+ o.class == self.class && o.state == state
8
+ end
9
+
10
+ protected
11
+ def state
12
+ info = Array.new
13
+ FIELDS.each do |f|
14
+ info.push(f)
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/GTP/version.rb CHANGED
@@ -1,3 +1,6 @@
1
1
  module GTP
2
- VERSION = "0.0.1"
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ PATCH = 1
5
+ VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
3
6
  end
@@ -1,53 +1,119 @@
1
1
  require "test_helper"
2
2
 
3
- describe GTP::GP4 do
3
+ module GTP
4
+ describe GP4 do
4
5
 
5
- it "must read the file version" do
6
- parser = GTP::GP4.new "test/tabs/test.gp4"
6
+ it "must read the file version" do
7
+ parser = GP4.new "test/tabs/test.gp4"
8
+ parser.parse_version
7
9
 
8
- parser.parse_version
10
+ # p parser.version
11
+ parser.version.must_match %r/FICHIER GUITAR PRO v4./
12
+ end
9
13
 
10
- parser.version.must_match %r/FICHIER GUITAR PRO v4./
11
- end
14
+ let(:parser) { GP4.new "test/tabs/test.gp4" }
12
15
 
13
- it "must read the tablature info" do
14
- parser = GTP::GP4.new "test/tabs/test.gp4"
15
-
16
- parser.parse_info
17
-
18
- parser.title.must_equal "Title"
19
- parser.subtitle.must_equal "Subtitle"
20
- parser.artist.must_equal "Artist"
21
- parser.album.must_equal "Album"
22
- parser.author.must_equal "Author"
23
- parser.copyright.must_equal "Copyright"
24
- parser.tab.must_equal "Tab"
25
- parser.instruction.must_equal "Instruction"
26
- parser.notice.must_equal "N line 1\nN line 2\nN line 3\nN line 4"
27
- parser.triplet_feel.must_match "0"
28
- end
16
+ before { parser.call }
29
17
 
30
- it "must read the tablature lyrics" do
31
-
32
- end
18
+ describe 'tablature info' do
19
+ it "must read the tablature info" do
20
+ parser.title.must_equal "Title"
21
+ parser.subtitle.must_equal "Subtitle"
22
+ parser.artist.must_equal "Artist"
23
+ parser.album.must_equal "Album"
24
+ parser.author.must_equal "Author"
25
+ parser.copyright.must_equal "Copyright"
26
+ parser.tab.must_equal "Tab"
27
+ parser.instruction.must_equal "Instruction"
28
+ parser.notice.must_equal "N line 1\nN line 2\nN line 3\nN line 4\n"
29
+ parser.triplet_feel.must_match "0"
30
+ end
31
+ end
33
32
 
34
- it "must read the tablature extra info" do
35
-
36
- end
33
+ describe "Lyrics" do
34
+ it "must read the tablature lyrics" do
35
+ parser.lyrics.must_equal [
36
+ {1=>"1111\n1111\n1111\n1111"},
37
+ {2=>"2222\n2222\n2222\n2222"},
38
+ {1=>"3333\n3333\n3333"},
39
+ {2=>"4444\n4444"},
40
+ {1=>"5555"}
41
+ ]
42
+ end
43
+ end
37
44
 
38
- it "must read the tablature measures" do
39
-
40
- end
45
+ describe "Other information" do
46
+ it "about the piece" do
47
+ parser.parse_tempo
48
+ parser.parse_key
49
+ parser.parse_octave
50
+ parser.parse_midi_channels # <----- PARSE ME PLEASE!!!
51
+ parser.parse_number_of_measures
52
+ parser.parse_number_of_tracks
41
53
 
42
- it "must read the tablature tracks" do
43
-
44
- end
54
+ parser.tempo.must_equal 120
55
+ parser.key.must_equal 1
56
+ parser.octave.must_equal 0
57
+ # parser.midi_channels
58
+ parser.num_measures.must_equal 2
59
+ parser.num_tracks.must_equal 1
60
+ end
61
+ end
45
62
 
46
- it "must read the tablature song" do
47
-
48
- end
63
+ describe "Serialization" do
64
+ it "must serialize as json" do
65
+ parser.to_json.must_equal "{\"score\":{\"info\":{\"version\":\"FICHIER GUITAR PRO v4.06\",\"title\":\"Title\",\"subtitle\":\"Subtitle\",\"artist\":\"Artist\",\"album\":\"Album\",\"author\":\"Author\",\"copyright\":\"Copyright\"},\"tempo\":null,\"key\":null,\"num_track\":null,\"num_measures\":null}}"
66
+ end
67
+ end
68
+
69
+ # describe "Measures" do
70
+ # first_measure = Measure.new
71
+ # second_measure = Measure.new
72
+
73
+ # before do
74
+ # parser.parse_version
75
+ # parser.parse_info
76
+ # parser.parse_lyrics
77
+ # parser.parse_tempo
78
+ # parser.parse_key
79
+ # parser.parse_octave
80
+ # parser.parse_midi_channels
81
+ # parser.parse_number_of_measures
82
+ # parser.parse_number_of_tracks
83
+
84
+ # first_measure.numerator = 4
85
+ # first_measure.denominator = 4
86
+ # first_measure.begin_repeat = true
87
+ # first_measure.end_repeat = nil
88
+ # first_measure.num_alt_ending = nil
89
+ # first_measure.marker_name = nil
90
+ # first_measure.marker_color = nil
91
+ # first_measure.tonality = 1
92
+ # first_measure.double_bar = nil
93
+
94
+ # second_measure.numerator = 7
95
+ # second_measure.denominator = 8
96
+ # second_measure.begin_repeat = nil
97
+ # second_measure.end_repeat = nil
98
+ # second_measure.num_alt_ending = nil
99
+ # second_measure.marker_name = nil
100
+ # second_measure.marker_color = nil
101
+ # second_measure.tonality = nil
102
+ # second_measure.double_bar = nil
103
+
104
+ # end
105
+
106
+ # it "must get the tab measures" do
107
+ # parser.parse_measures
108
+
109
+ # expected_measures = Array.new
110
+ # expected_measures.push(first_measure)
111
+ # expected_measures.push(second_measure)
112
+
113
+ # # require "pry"; binding.pry
49
114
 
50
- it "must read the tablature chord diagrams" do
51
-
115
+ # parser.measures.must_equal expected_measures
116
+ # end
117
+ # end
52
118
  end
53
- end
119
+ end
@@ -0,0 +1,21 @@
1
+ require "test_helper"
2
+
3
+ module GTP
4
+ describe Measure do
5
+
6
+ it "must setup its attributes properly" do
7
+ # measures = Measure.new "11100010"
8
+
9
+ # measures.header.must_equal
10
+ # measures.double_bar.must_equal false
11
+ # measures.tonality.must_equal
12
+ # measures.marker.must_equal false
13
+ # measures.num_alt_ending.must_equal false
14
+ # measures.end_repeat.must_equal false
15
+ # measures.begin_repeat.must_equal true
16
+ # measures.den_key.must_equal 4
17
+ # measures.num_key.must_equal 4
18
+
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,57 +1,78 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: GTP
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 0
8
- - 1
9
- version: 0.0.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
10
5
  platform: ruby
11
- authors:
6
+ authors:
12
7
  - Thiago Rocha
13
8
  autorequire:
14
9
  bindir: bin
15
10
  cert_chain: []
16
-
17
- date: 2013-06-30 00:00:00 -03:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
11
+ date: 2013-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
21
14
  name: bundler
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- requirements:
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
25
17
  - - ~>
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 1
29
- - 3
30
- version: "1.3"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
31
20
  type: :development
32
- version_requirements: *id001
33
- - !ruby/object:Gem::Dependency
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
34
28
  name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
35
49
  prerelease: false
36
- requirement: &id002 !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- segments:
41
- - 0
42
- version: "0"
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
43
62
  type: :development
44
- version_requirements: *id002
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
45
69
  description: A Guitar Pro file parser
46
- email:
70
+ email:
47
71
  - kimo@kimo.io
48
72
  executables: []
49
-
50
73
  extensions: []
51
-
52
74
  extra_rdoc_files: []
53
-
54
- files:
75
+ files:
55
76
  - .gitignore
56
77
  - GTP.gemspec
57
78
  - Gemfile
@@ -60,46 +81,44 @@ files:
60
81
  - Rakefile
61
82
  - lib/GTP.rb
62
83
  - lib/GTP/gp4.rb
84
+ - lib/GTP/io/reader.rb
85
+ - lib/GTP/measure.rb
63
86
  - lib/GTP/version.rb
64
87
  - test/lib/GTP/gp4_test.rb
88
+ - test/lib/GTP/measure_test.rb
65
89
  - test/lib/GTP/version_test.rb
66
90
  - test/tabs/test.gp3
67
91
  - test/tabs/test.gp4
68
92
  - test/tabs/test.gp5
69
93
  - test/tabs/test2.gp4
70
94
  - test/test_helper.rb
71
- has_rdoc: true
72
- homepage: ""
73
- licenses:
95
+ homepage: ''
96
+ licenses:
74
97
  - MIT
98
+ metadata: {}
75
99
  post_install_message:
76
100
  rdoc_options: []
77
-
78
- require_paths:
101
+ require_paths:
79
102
  - lib
80
- required_ruby_version: !ruby/object:Gem::Requirement
81
- requirements:
82
- - - ">="
83
- - !ruby/object:Gem::Version
84
- segments:
85
- - 0
86
- version: "0"
87
- required_rubygems_version: !ruby/object:Gem::Requirement
88
- requirements:
89
- - - ">="
90
- - !ruby/object:Gem::Version
91
- segments:
92
- - 0
93
- version: "0"
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
94
113
  requirements: []
95
-
96
114
  rubyforge_project:
97
- rubygems_version: 1.3.6
115
+ rubygems_version: 2.0.3
98
116
  signing_key:
99
- specification_version: 3
117
+ specification_version: 4
100
118
  summary: It parses gtp files
101
- test_files:
119
+ test_files:
102
120
  - test/lib/GTP/gp4_test.rb
121
+ - test/lib/GTP/measure_test.rb
103
122
  - test/lib/GTP/version_test.rb
104
123
  - test/tabs/test.gp3
105
124
  - test/tabs/test.gp4