guitar_pro_parser 0.0.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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +7 -0
- data/guitar_pro_parser.gemspec +25 -0
- data/lib/guitar_pro_parser/bar.rb +29 -0
- data/lib/guitar_pro_parser/bar_settings.rb +74 -0
- data/lib/guitar_pro_parser/beat.rb +42 -0
- data/lib/guitar_pro_parser/channel.rb +25 -0
- data/lib/guitar_pro_parser/chord_diagram.rb +14 -0
- data/lib/guitar_pro_parser/guitar_pro_helper.rb +73 -0
- data/lib/guitar_pro_parser/io/input_stream.rb +110 -0
- data/lib/guitar_pro_parser/io/reader.rb +759 -0
- data/lib/guitar_pro_parser/note.rb +71 -0
- data/lib/guitar_pro_parser/page_setup.rb +34 -0
- data/lib/guitar_pro_parser/song.rb +113 -0
- data/lib/guitar_pro_parser/track.rb +125 -0
- data/lib/guitar_pro_parser/version.rb +3 -0
- data/lib/guitar_pro_parser.rb +25 -0
- data/spec/lib/guitar_pro_parser/bar_settings_spec.rb +176 -0
- data/spec/lib/guitar_pro_parser/beat_spec.rb +79 -0
- data/spec/lib/guitar_pro_parser/channel_spec.rb +44 -0
- data/spec/lib/guitar_pro_parser/guitar_pro_helper_spec.rb +11 -0
- data/spec/lib/guitar_pro_parser/io/input_stream_spec.rb +101 -0
- data/spec/lib/guitar_pro_parser/note_spec.rb +55 -0
- data/spec/lib/guitar_pro_parser/page_setup_spec.rb +26 -0
- data/spec/lib/guitar_pro_parser/song_spec.rb +121 -0
- data/spec/lib/guitar_pro_parser/track_spec.rb +211 -0
- data/spec/lib/guitar_pro_parser_spec.rb +51 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/tabs/tab.gp4 +0 -0
- data/spec/tabs/tab.gp5 +0 -0
- data/spec/tabs/test_musical_directions.gp5 +0 -0
- metadata +144 -0
@@ -0,0 +1,759 @@
|
|
1
|
+
require 'guitar_pro_parser/io/input_stream'
|
2
|
+
require 'guitar_pro_parser/guitar_pro_helper'
|
3
|
+
|
4
|
+
module GuitarProParser
|
5
|
+
|
6
|
+
class Reader
|
7
|
+
|
8
|
+
def initialize(song, file_path, headers_only)
|
9
|
+
@song = song
|
10
|
+
@file_path = file_path
|
11
|
+
|
12
|
+
@input = InputStream.new(file_path)
|
13
|
+
|
14
|
+
read_version
|
15
|
+
read_info
|
16
|
+
read_notices
|
17
|
+
@song.triplet_feel = @input.read_boolean if @version < 5.0
|
18
|
+
read_lyrics if @version >= 4.0
|
19
|
+
|
20
|
+
if @version >= 5.0
|
21
|
+
@song.master_volume = @input.read_integer
|
22
|
+
@input.skip_integer
|
23
|
+
end
|
24
|
+
|
25
|
+
11.times { |n| @song.equalizer[n] = @input.read_byte } if @version >= 5.0
|
26
|
+
read_page_setup if @version >= 5.0
|
27
|
+
read_tempo
|
28
|
+
read_key
|
29
|
+
@song.octave = @input.read_byte if @version >= 4.0
|
30
|
+
read_channels
|
31
|
+
read_musical_directions if @version >= 5.0
|
32
|
+
@song.master_reverb = @input.read_integer if @version >= 5.0
|
33
|
+
|
34
|
+
bars_count = @input.read_integer
|
35
|
+
tracks_count = @input.read_integer
|
36
|
+
|
37
|
+
if headers_only
|
38
|
+
bars_count.times { @song.add_bar_settings }
|
39
|
+
tracks_count.times { @song.add_track }
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
bars_count.times { read_bars_settings }
|
44
|
+
tracks_count.times { read_track }
|
45
|
+
@input.skip_byte if @version >= 5.0
|
46
|
+
|
47
|
+
@song.bars_settings.each do |bar_settings|
|
48
|
+
@song.tracks.each do |track|
|
49
|
+
bar = Bar.new
|
50
|
+
|
51
|
+
voices_count = @version >= 5.0 ? 2 : 1
|
52
|
+
|
53
|
+
voices_count.times do |voice_number|
|
54
|
+
voice = GuitarProHelper::VOICES.fetch(voice_number)
|
55
|
+
beats_count = @input.read_integer
|
56
|
+
beats_count.times { read_beat(track, bar, voice) }
|
57
|
+
end
|
58
|
+
|
59
|
+
track.bars << bar
|
60
|
+
|
61
|
+
# Padding
|
62
|
+
@input.skip_byte if @version >= 5.0
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def read_version
|
70
|
+
length = @input.read_byte
|
71
|
+
version_string = @input.read_string length
|
72
|
+
# TODO: Change a way to get value from string
|
73
|
+
version_string['FICHIER GUITAR PRO v'] = ''
|
74
|
+
@version = version_string.to_f
|
75
|
+
@song.version = @version
|
76
|
+
|
77
|
+
# Skip first 31 bytes that are reserved for version data
|
78
|
+
@input.offset = 31
|
79
|
+
end
|
80
|
+
|
81
|
+
def read_info
|
82
|
+
@song.title = @input.read_chunk
|
83
|
+
@song.subtitle = @input.read_chunk
|
84
|
+
@song.artist = @input.read_chunk
|
85
|
+
@song.album = @input.read_chunk
|
86
|
+
@song.lyricist = @input.read_chunk if @version > 5.0
|
87
|
+
@song.composer = @input.read_chunk
|
88
|
+
@song.copyright = @input.read_chunk
|
89
|
+
@song.transcriber = @input.read_chunk
|
90
|
+
@song.instructions = @input.read_chunk
|
91
|
+
end
|
92
|
+
|
93
|
+
def read_notices
|
94
|
+
notices = []
|
95
|
+
|
96
|
+
notices_count = @input.read_integer
|
97
|
+
notices_count.times { notices << @input.read_chunk }
|
98
|
+
|
99
|
+
@song.notices = notices.join('/n')
|
100
|
+
end
|
101
|
+
|
102
|
+
# > 4.0 only
|
103
|
+
def read_lyrics
|
104
|
+
@song.lyrics_track = @input.read_integer
|
105
|
+
|
106
|
+
5.times do
|
107
|
+
start_bar = @input.read_integer
|
108
|
+
length = @input.read_integer
|
109
|
+
lyrics_text = @input.read_string length
|
110
|
+
@song.lyrics << {text: lyrics_text, bar: start_bar}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# >= 5.0 only
|
115
|
+
def read_page_setup
|
116
|
+
@song.page_setup.page_format_length = @input.read_integer
|
117
|
+
@song.page_setup.page_format_width = @input.read_integer
|
118
|
+
@song.page_setup.left_margin = @input.read_integer
|
119
|
+
@song.page_setup.right_margin = @input.read_integer
|
120
|
+
@song.page_setup.top_margin = @input.read_integer
|
121
|
+
@song.page_setup.bottom_margin = @input.read_integer
|
122
|
+
@song.page_setup.score_size = @input.read_integer
|
123
|
+
|
124
|
+
# The enabled header/footer fields bitmask declares which fields are displayed:
|
125
|
+
# Bit 0 (LSB): Title field
|
126
|
+
# Bit 1: Subtitle field
|
127
|
+
# Bit 2: Artist field
|
128
|
+
# Bit 3: Album field
|
129
|
+
# Bit 4: Words (Lyricist) field
|
130
|
+
# Bit 5: Music (Composer) field
|
131
|
+
# Bit 6: Words & Music field
|
132
|
+
# Bit 7: Copyright field
|
133
|
+
# Bit 8: Page Number (field)
|
134
|
+
# Bits 9 - 15: Unused (set to 0)
|
135
|
+
bits = @input.read_bitmask
|
136
|
+
@song.page_setup.displayed_fields << :title if bits[0]
|
137
|
+
@song.page_setup.displayed_fields << :subtitle if bits[1]
|
138
|
+
@song.page_setup.displayed_fields << :artist if bits[2]
|
139
|
+
@song.page_setup.displayed_fields << :album if bits[3]
|
140
|
+
@song.page_setup.displayed_fields << :lyrics_author if bits[4]
|
141
|
+
@song.page_setup.displayed_fields << :music_author if bits[5]
|
142
|
+
@song.page_setup.displayed_fields << :lyrics_and_music_author if bits[6]
|
143
|
+
@song.page_setup.displayed_fields << :copyright if bits[7]
|
144
|
+
|
145
|
+
bits = @input.read_bitmask
|
146
|
+
@song.page_setup.displayed_fields << :page_number if bits[0]
|
147
|
+
|
148
|
+
@song.page_setup.title = @input.read_chunk
|
149
|
+
@song.page_setup.subtitle = @input.read_chunk
|
150
|
+
@song.page_setup.artist = @input.read_chunk
|
151
|
+
@song.page_setup.album = @input.read_chunk
|
152
|
+
@song.page_setup.lyrics_author = @input.read_chunk
|
153
|
+
@song.page_setup.music_author = @input.read_chunk
|
154
|
+
@song.page_setup.lyrics_and_music_author = @input.read_chunk
|
155
|
+
@song.page_setup.copyright_line_1 = @input.read_chunk
|
156
|
+
@song.page_setup.copyright_line_2 = @input.read_chunk
|
157
|
+
@song.page_setup.page_number = @input.read_chunk
|
158
|
+
end
|
159
|
+
|
160
|
+
def read_tempo
|
161
|
+
@song.tempo = @input.read_chunk if @version >= 5.0
|
162
|
+
@song.bpm = @input.read_integer
|
163
|
+
@input.skip_byte if @version >= 5.0
|
164
|
+
end
|
165
|
+
|
166
|
+
def read_key
|
167
|
+
if @version >= 4.0
|
168
|
+
@song.key = @input.read_byte
|
169
|
+
@input.increment_offset(3)
|
170
|
+
else
|
171
|
+
@song.key = @input.read_integer
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def read_channels
|
176
|
+
4.times do
|
177
|
+
@song.channels << []
|
178
|
+
16.times do
|
179
|
+
channel = Channel.new
|
180
|
+
channel.instrument = @input.read_integer
|
181
|
+
channel.volume = @input.read_byte
|
182
|
+
channel.pan = @input.read_byte
|
183
|
+
channel.chorus = @input.read_byte
|
184
|
+
channel.reverb = @input.read_byte
|
185
|
+
channel.phaser = @input.read_byte
|
186
|
+
channel.tremolo = @input.read_byte
|
187
|
+
@song.channels.last << channel
|
188
|
+
@input.skip_short_integer # Padding
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# >= 5.0 only
|
194
|
+
def read_musical_directions
|
195
|
+
GuitarProHelper::MUSICAL_DIRECTIONS.each do |musical_direction|
|
196
|
+
value = @input.read_short_integer
|
197
|
+
value = nil if value == 0xFFFF
|
198
|
+
@song.musical_directions[musical_direction] = value
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def read_bars_settings
|
203
|
+
bars_settings = @song.add_bar_settings
|
204
|
+
|
205
|
+
bits = @input.read_bitmask
|
206
|
+
has_new_time_signature_numerator = bits[0]
|
207
|
+
has_new_time_signature_denominator = bits[1]
|
208
|
+
bars_settings.has_start_of_repeat = bits[2]
|
209
|
+
bars_settings.has_end_of_repeat = bits[3]
|
210
|
+
has_alternate_endings = bits[4]
|
211
|
+
has_marker = bits[5]
|
212
|
+
has_new_key_signature = bits[6]
|
213
|
+
bars_settings.double_bar = bits[7]
|
214
|
+
|
215
|
+
time_signature_numerator = @input.read_byte if has_new_time_signature_numerator
|
216
|
+
time_signature_denominator = @input.read_byte if has_new_time_signature_denominator
|
217
|
+
beam_eight_notes_by_values = []
|
218
|
+
|
219
|
+
if bars_settings.has_end_of_repeat
|
220
|
+
bars_settings.repeats_count = @input.read_byte
|
221
|
+
|
222
|
+
# Version 5 of the format has slightly different counting for repeats
|
223
|
+
bars_settings.repeats_count = bars_settings.repeats_count - 1 if @version >= 5.0
|
224
|
+
end
|
225
|
+
|
226
|
+
# Read number of alternate ending and marker
|
227
|
+
# Their order differs depending on Guitar Pro version
|
228
|
+
if @version < 5.0
|
229
|
+
read_alternate_endings(bars_settings) if has_alternate_endings
|
230
|
+
read_marker(bars_settings) if has_marker
|
231
|
+
else
|
232
|
+
read_marker(bars_settings) if has_marker
|
233
|
+
read_alternate_endings(bars_settings) if has_alternate_endings
|
234
|
+
end
|
235
|
+
|
236
|
+
# Read new key signature if it changed
|
237
|
+
if has_new_key_signature
|
238
|
+
key = @input.read_byte
|
239
|
+
scale = @input.read_boolean ? :minor : :major
|
240
|
+
bars_settings.set_new_key_signature(key, scale)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Read specific Guitar Pro 5 data
|
244
|
+
if @version >= 5.0
|
245
|
+
# Read beaming 8th notes by values if there is new time signature
|
246
|
+
if has_new_time_signature_numerator || has_new_time_signature_denominator
|
247
|
+
4.times { beam_eight_notes_by_values << @input.read_byte }
|
248
|
+
end
|
249
|
+
|
250
|
+
# If a GP5 file doesn't define an alternate ending here, ignore a byte of padding
|
251
|
+
@input.skip_byte unless has_alternate_endings
|
252
|
+
|
253
|
+
# Read triplet feel
|
254
|
+
bars_settings.triplet_feel = GuitarProHelper::TRIPLET_FEEL[@input.read_byte]
|
255
|
+
@song.triplet_feel = true if bars_settings.triplet_feel
|
256
|
+
|
257
|
+
# Skip byte of padding
|
258
|
+
@input.skip_byte
|
259
|
+
end
|
260
|
+
|
261
|
+
bars_settings.set_new_time_signature(time_signature_numerator, time_signature_denominator, beam_eight_notes_by_values) if has_new_time_signature_numerator || has_new_time_signature_denominator
|
262
|
+
end
|
263
|
+
|
264
|
+
def read_alternate_endings(bars_settings)
|
265
|
+
# In Guitar Pro 5 values is bitmask for creating array of alternate endings
|
266
|
+
# Bit 0 - alt. ending #1
|
267
|
+
# Bit 1 - alt. ending #2
|
268
|
+
# ...
|
269
|
+
# Bit 7 - alt. ending #8
|
270
|
+
#
|
271
|
+
# In Guitar Pro 4 and less value is a digit.
|
272
|
+
if (@version >= 5.0)
|
273
|
+
bits = @input.read_bitmask
|
274
|
+
bits.count.times { |i| bars_settings.alternate_endings << (i+1) if bits[i] }
|
275
|
+
else
|
276
|
+
bars_settings.alternate_endings << @input.read_byte
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def read_marker(bars_settings)
|
281
|
+
name = @input.read_chunk
|
282
|
+
color = []
|
283
|
+
3.times { color << @input.read_byte }
|
284
|
+
bars_settings.set_marker(name, color)
|
285
|
+
@input.skip_byte
|
286
|
+
end
|
287
|
+
|
288
|
+
def read_track
|
289
|
+
track = @song.add_track
|
290
|
+
|
291
|
+
# Bit 0 (LSB): Drums track
|
292
|
+
# Bit 1: 12 stringed guitar track
|
293
|
+
# Bit 2: Banjo track
|
294
|
+
# Bit 3: Blank bit
|
295
|
+
# Bit 4: Marked for solo playback
|
296
|
+
# Bit 5: Marked for muted playback
|
297
|
+
# Bit 6: Use RSE playback (track instrument option)
|
298
|
+
# Bit 7: Indicate tuning on the score (track properties)
|
299
|
+
bits = @input.read_bitmask
|
300
|
+
track.drums = bits[0]
|
301
|
+
track.twelve_stringed_guitar = bits[1]
|
302
|
+
track.banjo = bits[2]
|
303
|
+
track.solo_playback = bits[4]
|
304
|
+
track.mute_playback = bits[5]
|
305
|
+
track.rse_playback = bits[6]
|
306
|
+
track.indicate_tuning = bits[7]
|
307
|
+
|
308
|
+
track_name_field_length = 41
|
309
|
+
length = @input.read_byte
|
310
|
+
track.name = @input.read_string length
|
311
|
+
@input.increment_offset (track_name_field_length - length - 1)
|
312
|
+
|
313
|
+
strings_count = @input.read_integer
|
314
|
+
track.strings.clear
|
315
|
+
strings_count.times { track.strings << (GuitarProHelper.digit_to_note(@input.read_integer)) }
|
316
|
+
# Skip padding if there are less than 7 strings
|
317
|
+
(7 - strings_count).times { @input.skip_integer }
|
318
|
+
|
319
|
+
track.midi_port = @input.read_integer
|
320
|
+
track.midi_channel = @input.read_integer
|
321
|
+
track.midi_channel_for_effects = @input.read_integer
|
322
|
+
track.frets_count = @input.read_integer
|
323
|
+
track.capo = @input.read_integer
|
324
|
+
|
325
|
+
track.color.clear
|
326
|
+
3.times { track.color << @input.read_byte }
|
327
|
+
@input.skip_byte
|
328
|
+
|
329
|
+
if @version > 5.0
|
330
|
+
# The track properties 1 bitmask declares various options in track properties:
|
331
|
+
# Bit 0 (LSB): Unknown (something to do with tablature notation being enabled)
|
332
|
+
# Bit 1: Unknown
|
333
|
+
# Bit 2: Diagrams/chords below the standard notation
|
334
|
+
# Bit 3: Show rhythm with tab
|
335
|
+
# Bit 4: Force horizontal beams
|
336
|
+
# Bit 5: Force channels 11 to 16
|
337
|
+
# Bit 6: Diagrams list on top of the score
|
338
|
+
# Bit 7 (MSB): Diagrams in the score
|
339
|
+
bits = @input.read_bitmask
|
340
|
+
track.diagrams_below_the_standard_notation = bits[2]
|
341
|
+
track.show_rythm_with_tab = bits[3]
|
342
|
+
track.force_horizontal_beams = bits[4]
|
343
|
+
track.force_channels_11_to_16 = bits[5]
|
344
|
+
track.diagrams_list_on_top_of_score = bits[6]
|
345
|
+
track.diagrams_in_the_score = bits[7]
|
346
|
+
|
347
|
+
# The track properties 2 bitmask declares various options in track properties/instrument:
|
348
|
+
# Bit 0 (LSB): Unknown
|
349
|
+
# Bit 1: Auto-Let Ring
|
350
|
+
# Bit 2: Auto Brush
|
351
|
+
# Bit 3: Extend rhythmic inside the tab
|
352
|
+
# Bits 4-7: Unknown
|
353
|
+
bits = @input.read_bitmask
|
354
|
+
track.auto_let_ring = bits[1]
|
355
|
+
track.auto_brush = bits[2]
|
356
|
+
track.extend_rhytmic_inside_the_tab = bits[3]
|
357
|
+
|
358
|
+
@input.skip_byte
|
359
|
+
track.midi_bank = @input.read_byte
|
360
|
+
track.human_playing = @input.read_byte
|
361
|
+
track.auto_accentuation = @input.read_byte
|
362
|
+
@input.increment_offset 31
|
363
|
+
track.sound_bank = @input.read_byte
|
364
|
+
@input.increment_offset 7
|
365
|
+
|
366
|
+
track.equalizer.clear
|
367
|
+
4.times { track.equalizer << @input.read_byte }
|
368
|
+
|
369
|
+
track.instrument_effect_1 = @input.read_chunk
|
370
|
+
track.instrument_effect_2 = @input.read_chunk
|
371
|
+
end
|
372
|
+
|
373
|
+
@input.increment_offset 45 if @version == 5.0
|
374
|
+
end
|
375
|
+
|
376
|
+
def read_beat(track, bar, voice)
|
377
|
+
beat = Beat.new
|
378
|
+
bar.voices.fetch(voice) << beat
|
379
|
+
|
380
|
+
# The beat bitmask declares which parameters are defined for the beat:
|
381
|
+
# Bit 0 (LSB): Dotted note
|
382
|
+
# Bit 1: Chord diagram present
|
383
|
+
# Bit 2: Text present
|
384
|
+
# Bit 3: Beat effects present
|
385
|
+
# Bit 4: Mix table change present
|
386
|
+
# Bit 5: This beat is an N-tuplet
|
387
|
+
# Bit 6: Is a rest beat
|
388
|
+
# Bit 7 (MSB): Unused (set to 0)
|
389
|
+
bits = @input.read_bitmask
|
390
|
+
beat.dotted = bits[0]
|
391
|
+
has_chord_diagram = bits[1]
|
392
|
+
has_text = bits[2]
|
393
|
+
has_effects = bits[3]
|
394
|
+
has_mix_table_change = bits[4]
|
395
|
+
is_tuplet = bits[5]
|
396
|
+
is_rest = bits[6]
|
397
|
+
|
398
|
+
beat.rest = GuitarProHelper::REST_TYPES.fetch(@input.read_byte.to_s) if is_rest
|
399
|
+
beat.duration = GuitarProHelper::DURATIONS.fetch(@input.read_signed_byte.to_s)
|
400
|
+
beat.tuplet = @input.read_integer if is_tuplet
|
401
|
+
read_chord_diagram(track) if has_chord_diagram
|
402
|
+
beat.text = @input.read_chunk if has_text
|
403
|
+
read_beat_effects(beat) if has_effects
|
404
|
+
read_mix_table(beat) if has_mix_table_change
|
405
|
+
|
406
|
+
strings_bitmask = @input.read_bitmask
|
407
|
+
used_strings = []
|
408
|
+
(0..6).to_a.reverse.each { |i| used_strings << strings_bitmask[i] }
|
409
|
+
7.times do |i|
|
410
|
+
if used_strings[i]
|
411
|
+
note = Note.new
|
412
|
+
read_note(note)
|
413
|
+
beat.strings["#{i+1}"] = note
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Transponse data in Guitar Pro 5
|
418
|
+
if @version >= 5.0
|
419
|
+
# Bits 0-3: Unknown/unused
|
420
|
+
# Bit 4: 8va (up one octave)
|
421
|
+
# Bit 5: 8vb (down one octave)
|
422
|
+
# Bit 6: 15ma (up two octaves)
|
423
|
+
# Bit 7: Unknown/unused
|
424
|
+
bits1 = @input.read_bitmask
|
425
|
+
|
426
|
+
# Bit 8: 15mb (down two octaves)
|
427
|
+
# Bits 9-10: Unknown/unused
|
428
|
+
# Bit 11: An extra unknown data byte follows this bitmask
|
429
|
+
# Bits 12-15: Unknown/unused
|
430
|
+
bits2 = @input.read_bitmask
|
431
|
+
|
432
|
+
beat.transpose = '8va' if bits1[4]
|
433
|
+
beat.transpose = '8vb' if bits1[5]
|
434
|
+
beat.transpose = '15ma' if bits1[6]
|
435
|
+
beat.transpose = '15mb' if bits2[0]
|
436
|
+
|
437
|
+
# Unknown data
|
438
|
+
@input.skip_byte if bits2[3]
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def read_chord_diagram(track)
|
443
|
+
beat.chord_diagram = ChordDiagram.new
|
444
|
+
|
445
|
+
format = @input.read_byte
|
446
|
+
|
447
|
+
if format == 0 # Guitar Pro 3 format
|
448
|
+
beat.chord_diagram.name = @input.read_chunk
|
449
|
+
beat.chord_diagram.start_fret = @input.read_integer
|
450
|
+
unless beat.chord_diagram.start_fret.zero?
|
451
|
+
track.strings.count.times { beat.chord_diagram.frets << @input.read_integer }
|
452
|
+
end
|
453
|
+
else # Guitar Pro 4 format
|
454
|
+
@input.increment_offset(105) # TODO: Write reading logic here
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
def read_beat_effects(beat)
|
459
|
+
# The beat effects bitmasks declare which parameters are defined for the beat:
|
460
|
+
# Byte 1
|
461
|
+
# Bit 0: Vibrato
|
462
|
+
# Bit 1: Wide vibrato
|
463
|
+
# Bit 2: Natural harmonic
|
464
|
+
# Bit 3: Artificial harmonic
|
465
|
+
# Bit 4: Fade in
|
466
|
+
# Bit 5: String effect
|
467
|
+
# Bit 6: Stroke effect
|
468
|
+
# Bit 7: Unused (set to 0)
|
469
|
+
bits = @input.read_bitmask
|
470
|
+
beat.add_effect(:vibrato) if bits[0]
|
471
|
+
beat.add_effect(:wide_vibrato) if bits[1]
|
472
|
+
beat.add_effect(:natural_harmonic) if bits[2]
|
473
|
+
beat.add_effect(:artificial_harmonic) if bits[3]
|
474
|
+
beat.add_effect(:fade_in) if bits[4]
|
475
|
+
beat.add_effect(:string_effect) if bits[5]
|
476
|
+
beat.add_effect(:stroke_effect) if bits[6]
|
477
|
+
|
478
|
+
# Byte 2 (extended beat effects, only if the major file version is >= 4):
|
479
|
+
# Bit 0: Rasguedo
|
480
|
+
# Bit 1: Pickstroke
|
481
|
+
# Bit 2: Tremolo bar
|
482
|
+
# Bits 3-7: Unused (set to 0)
|
483
|
+
if @version >= 4.0
|
484
|
+
bits = @input.read_bitmask
|
485
|
+
beat.add_effect(:rasguedo) if bits[0]
|
486
|
+
beat.add_effect(:pickstroke) if bits[1]
|
487
|
+
beat.add_effect(:tremolo_bar) if bits[2]
|
488
|
+
end
|
489
|
+
|
490
|
+
if beat.has_effect? :string_effect
|
491
|
+
beat.effects[:string_effect] = GuitarProHelper::STRING_EFFECTS.fetch(@input.read_byte)
|
492
|
+
# Skip a value applied to the string effect in old Guitar Pro versions
|
493
|
+
@input.read_integer if @version < 4.0
|
494
|
+
end
|
495
|
+
|
496
|
+
beat.effects[:tremolo_bar] = read_bend if beat.has_effect? :tremolo_bar
|
497
|
+
|
498
|
+
if beat.has_effect? :stroke_effect
|
499
|
+
upstroke = GuitarProHelper::STROKE_EFFECT_SPEEDS.fetch(@input.read_byte)
|
500
|
+
downstroke = GuitarProHelper::STROKE_EFFECT_SPEEDS.fetch(@input.read_byte)
|
501
|
+
beat.effects[:stroke_effect] = { upstroke_speed: upstroke, downstroke_speed: downstroke }
|
502
|
+
end
|
503
|
+
|
504
|
+
beat.effects[:pickstroke] = GuitarProHelper::STROKE_DIRECTIONS.fetch(@input.read_byte) if beat.has_effect? :pickstroke
|
505
|
+
end
|
506
|
+
|
507
|
+
def read_bend
|
508
|
+
type = GuitarProHelper::BEND_TYPES.fetch(@input.read_byte)
|
509
|
+
height = @input.read_integer
|
510
|
+
points_coint = @input.read_integer
|
511
|
+
result = { type: type, height: height, points: [] }
|
512
|
+
points_coint.times do
|
513
|
+
time = @input.read_integer
|
514
|
+
pitch_alteration = @input.read_integer
|
515
|
+
vibrato_type = GuitarProHelper::BEND_VIBRATO_TYPES.fetch(@input.read_byte)
|
516
|
+
result[:points] << { time: time, pitch_alteration: pitch_alteration, vibrato_type: vibrato_type }
|
517
|
+
end
|
518
|
+
|
519
|
+
result
|
520
|
+
end
|
521
|
+
|
522
|
+
# TODO: It seems that this method is incorrect. Test it.
|
523
|
+
def read_mix_table(beat)
|
524
|
+
beat.mix_table = {}
|
525
|
+
|
526
|
+
instrument = @input.read_signed_byte
|
527
|
+
beat.mix_table[:instrument] = instrument unless instrument == -1
|
528
|
+
|
529
|
+
if @version >= 5.0
|
530
|
+
# RSE related 4 digit numbers (-1 if RSE is disabled)
|
531
|
+
3.times { @input.skip_integer }
|
532
|
+
@input.skip_integer # Padding
|
533
|
+
end
|
534
|
+
|
535
|
+
volume = @input.read_signed_byte
|
536
|
+
pan = @input.read_signed_byte
|
537
|
+
chorus = @input.read_signed_byte
|
538
|
+
reverb = @input.read_signed_byte
|
539
|
+
phaser = @input.read_signed_byte
|
540
|
+
tremolo = @input.read_signed_byte
|
541
|
+
|
542
|
+
tempo_string = ''
|
543
|
+
tempo_string = @input.read_chunk if @version >= 5.0
|
544
|
+
tempo = @input.read_integer
|
545
|
+
|
546
|
+
beat.mix_table[:volume] = { value: volume, transition: @input.read_byte } if volume >= 0
|
547
|
+
beat.mix_table[:pan] = { value: pan, transition: @input.read_byte } if pan >= 0
|
548
|
+
beat.mix_table[:chorus] = { value: chorus, transition: @input.read_byte } if chorus >= 0
|
549
|
+
beat.mix_table[:reverb] = { value: reverb, transition: @input.read_byte } if reverb >= 0
|
550
|
+
beat.mix_table[:phaser] = { value: phaser, transition: @input.read_byte } if phaser >= 0
|
551
|
+
beat.mix_table[:tremolo] = { value: tremolo, transition: @input.read_byte } if tremolo >= 0
|
552
|
+
|
553
|
+
if tempo >= 0
|
554
|
+
beat.mix_table[:tempo] = { value: tempo, transition: @input.read_byte, text: tempo_string }
|
555
|
+
beat.mix_table[:tempo][:hidden_text] = @input.read_byte if @version > 5.0
|
556
|
+
end
|
557
|
+
|
558
|
+
# The mix table change applied tracks bitmask declares which mix change events apply to all tracks (set = all tracks, reset = current track only):
|
559
|
+
# Bit 0 (LSB): Volume change
|
560
|
+
# Bit 1: Pan change
|
561
|
+
# Bit 2: Chorus change
|
562
|
+
# Bit 3: Reverb change
|
563
|
+
# Bit 4: Phaser change
|
564
|
+
# Bit 5: Tremolo change
|
565
|
+
# Bits 6-7: Unused
|
566
|
+
if @version >= 4.0
|
567
|
+
bits = @input.read_bitmask
|
568
|
+
apply_hash = { true => :all, false => :current }
|
569
|
+
beat.mix_table[:volume][:apply_to] = apply_hash[bits[0]] unless beat.mix_table[:volume].nil?
|
570
|
+
beat.mix_table[:pan][:apply_to] = apply_hash[bits[1]] unless beat.mix_table[:pan].nil?
|
571
|
+
beat.mix_table[:chorus][:apply_to] = apply_hash[bits[2]] unless beat.mix_table[:chorus].nil?
|
572
|
+
beat.mix_table[:reverb][:apply_to] = apply_hash[bits[3]] unless beat.mix_table[:reverb].nil?
|
573
|
+
beat.mix_table[:phaser][:apply_to] = apply_hash[bits[4]] unless beat.mix_table[:phaser].nil?
|
574
|
+
beat.mix_table[:tremolo][:apply_to] = apply_hash[bits[5]] unless beat.mix_table[:tremolo].nil?
|
575
|
+
end
|
576
|
+
|
577
|
+
# Padding
|
578
|
+
@input.skip_byte if @version >= 5.0
|
579
|
+
|
580
|
+
if @version > 5.0
|
581
|
+
rse_effect_2 = @input.read_chunk
|
582
|
+
rse_effect_1 = @input.read_chunk
|
583
|
+
|
584
|
+
beat.mix_table[:rse_effect_2] = rse_effect_2 unless rse_effect_2.empty?
|
585
|
+
beat.mix_table[:rse_effect_1] = rse_effect_1 unless rse_effect_1.empty?
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
def read_note(note)
|
590
|
+
# The note bitmask declares which parameters are defined for the note:
|
591
|
+
# Bit 0 (LSB): Time-independent duration
|
592
|
+
# Bit 1: Heavy Accentuated note
|
593
|
+
# Bit 2: Ghost note
|
594
|
+
# Bit 3: Note effects present
|
595
|
+
# Bit 4: Note dynamic
|
596
|
+
# Bit 5: Note type
|
597
|
+
# Bit 6: Accentuated note
|
598
|
+
# Bit 7: Right/Left hand fingering
|
599
|
+
bits = @input.read_bitmask
|
600
|
+
|
601
|
+
# TODO: Find in Guitar Pro how to enable this feature
|
602
|
+
note.time_independent_duration = bits[0]
|
603
|
+
|
604
|
+
# Guitar Pro 5 files has 'Heavy accentuated' (bit 1) and 'Accentuated' (bit 6) states.
|
605
|
+
# Guitar Pro 4 and less have only 'Accentuated' (bit 6) state.
|
606
|
+
# So the only supported option for note is just 'Accentuated'.
|
607
|
+
note.accentuated = bits[1] || bits[6]
|
608
|
+
|
609
|
+
note.ghost = bits[2]
|
610
|
+
|
611
|
+
has_effects = bits[3]
|
612
|
+
has_dynamic = bits[4]
|
613
|
+
has_type = bits[5]
|
614
|
+
has_fingering = bits[7]
|
615
|
+
|
616
|
+
note.type = GuitarProHelper::NOTE_TYPES.fetch(@input.read_byte - 1) if has_type
|
617
|
+
|
618
|
+
# Ignore time-independed duration data for Guitar Pro 4 and less
|
619
|
+
@input.skip_short_integer if @version < 5.0 && note.time_independent_duration
|
620
|
+
|
621
|
+
note.dynamic = GuitarProHelper::NOTE_DYNAMICS.fetch(@input.read_byte - 1) if has_dynamic
|
622
|
+
note.fret = @input.read_byte
|
623
|
+
|
624
|
+
if has_fingering
|
625
|
+
left_finger = @input.read_byte
|
626
|
+
right_finger = @input.read_byte
|
627
|
+
|
628
|
+
note.add_left_hand_finger(FINGERS.fetch(left_finger)) unless left_finger == -1
|
629
|
+
note.add_right_hand_finger(FINGERS.fetch(right_finger)) unless right_finger == -1
|
630
|
+
end
|
631
|
+
|
632
|
+
# Ignore time-independed duration data for Guitar Pro 5
|
633
|
+
@input.increment_offset 8 if @version >= 5.0 && note.time_independent_duration
|
634
|
+
|
635
|
+
# Skip padding
|
636
|
+
@input.skip_byte if @version >= 5.0
|
637
|
+
|
638
|
+
if has_effects
|
639
|
+
# The note effect 1 bitmask declares which effects are defined for the note:
|
640
|
+
# Bit 0 (LSB): Bend present
|
641
|
+
# Bit 1: Hammer on/Pull off from the current note
|
642
|
+
# Bit 2: Slide from the current note (GP3 format version)
|
643
|
+
# Bit 3: Let ring
|
644
|
+
# Bit 4: Grace note
|
645
|
+
# Bits 5-7: Unused (set to 0)
|
646
|
+
bits = @input.read_bitmask
|
647
|
+
has_bend = bits[0]
|
648
|
+
hammer_or_pull = bits[1]
|
649
|
+
has_slide = bits[2]
|
650
|
+
let_ring = bits[3]
|
651
|
+
has_grace = bits[4]
|
652
|
+
|
653
|
+
note.hammer_or_pull = hammer_or_pull
|
654
|
+
note.let_ring = let_ring
|
655
|
+
|
656
|
+
has_tremolo = false
|
657
|
+
has_slide = false
|
658
|
+
has_harmonic = false
|
659
|
+
has_trill = false
|
660
|
+
|
661
|
+
if @version >= 4.0
|
662
|
+
# The note effect 2 bitmask declares more effects for the note:
|
663
|
+
# Bit 0 (LSB): Note played staccato
|
664
|
+
# Bit 1: Palm Mute
|
665
|
+
# Bit 2: Tremolo Picking
|
666
|
+
# Bit 3: Slide from the current note
|
667
|
+
# Bit 4: Harmonic note
|
668
|
+
# Bit 5: Trill
|
669
|
+
# Bit 6: Vibrato
|
670
|
+
# Bit 7 (MSB): Unused (set to 0)
|
671
|
+
bits = @input.read_bitmask
|
672
|
+
staccato = bits[0]
|
673
|
+
palm_mute = bits[1]
|
674
|
+
has_tremolo = bits[2]
|
675
|
+
has_slide = bits[3]
|
676
|
+
has_harmonic = bits[4]
|
677
|
+
has_trill = bits[5]
|
678
|
+
vibrato = bits[6]
|
679
|
+
|
680
|
+
note.staccato = staccato
|
681
|
+
note.palm_mute = palm_mute
|
682
|
+
note.vibrato = vibrato
|
683
|
+
end
|
684
|
+
|
685
|
+
note.bend = read_bend if has_bend
|
686
|
+
|
687
|
+
read_grace(note) if has_grace
|
688
|
+
note.add_tremolo(GuitarProHelper::TREMOLO_PICKING_SPEEDS.fetch(@input.read_byte.to_s)) if has_tremolo
|
689
|
+
|
690
|
+
if has_slide
|
691
|
+
value = @input.read_byte.to_s
|
692
|
+
# TODO: Check if there is difference between GP4 and GP5
|
693
|
+
note.slide = GuitarProHelper::SLIDE_TYPES.fetch(GuitarProHelper::MAP_SLIDE_TYPES_GP5.fetch(value))
|
694
|
+
end
|
695
|
+
|
696
|
+
read_harmonic(note) if has_harmonic
|
697
|
+
|
698
|
+
if has_trill
|
699
|
+
fret = @input.read_byte
|
700
|
+
period = GuitarProHelper::TRILL_PERIODS.fetch(@input.read_byte)
|
701
|
+
note.add_trill(fret, period)
|
702
|
+
end
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
def read_grace(note)
|
707
|
+
fret = @input.read_byte
|
708
|
+
dynamic = GuitarProHelper::NOTE_DYNAMICS.fetch(@input.read_byte - 1)
|
709
|
+
transition = GuitarProHelper::GRACE_NOTE_TRANSITION_TYPES.fetch(@input.read_byte)
|
710
|
+
duration = GuitarProHelper::GRACE_NOTE_DURATIONS.fetch(@input.read_byte.to_s)
|
711
|
+
dead = false
|
712
|
+
position = :before_the_beat
|
713
|
+
|
714
|
+
if @version >= 5.0
|
715
|
+
bits = @input.read_bitmask
|
716
|
+
dead = bits[0]
|
717
|
+
position = :on_the_beat if bits[1]
|
718
|
+
end
|
719
|
+
|
720
|
+
note.add_grace(fret, dynamic, transition, duration, dead, position)
|
721
|
+
end
|
722
|
+
|
723
|
+
def read_harmonic(note)
|
724
|
+
type = nil
|
725
|
+
harmonic_type_index = @input.read_byte
|
726
|
+
if @version >= 5.0
|
727
|
+
type = GuitarProHelper::HARMONIC_TYPES.fetch(harmonic_type_index)
|
728
|
+
else
|
729
|
+
map_of_gp4_harmonics = { '0' => 0,
|
730
|
+
'1' => 1,
|
731
|
+
'15' => 2,
|
732
|
+
'17' => 2,
|
733
|
+
'22' => 2,
|
734
|
+
'3' => 3,
|
735
|
+
'4' => 4,
|
736
|
+
'5' => 5 }
|
737
|
+
type = GuitarProHelper::HARMONIC_TYPES.fetch(map_of_gp4_harmonics[harmonic_type_index.to_s])
|
738
|
+
end
|
739
|
+
|
740
|
+
note.add_harmonic(type)
|
741
|
+
|
742
|
+
# Guitar Pro 5 has additional data about artificial and tapped harmonics
|
743
|
+
# But Guitar Pro 4 and less have not this data so we'll skip it now
|
744
|
+
if @version >= 5.0
|
745
|
+
case type
|
746
|
+
when :artificial
|
747
|
+
# Note (1 byte), note type (1 byte) and harmonic octave (1 byte)
|
748
|
+
# E.g. C# 15ma
|
749
|
+
@input.increment_offset(3)
|
750
|
+
when :tapped
|
751
|
+
# Tapped fret (1 byte)
|
752
|
+
@input.skip_byte
|
753
|
+
end
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
end
|
758
|
+
|
759
|
+
end
|