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.
Files changed (36) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +72 -0
  6. data/Rakefile +7 -0
  7. data/guitar_pro_parser.gemspec +25 -0
  8. data/lib/guitar_pro_parser/bar.rb +29 -0
  9. data/lib/guitar_pro_parser/bar_settings.rb +74 -0
  10. data/lib/guitar_pro_parser/beat.rb +42 -0
  11. data/lib/guitar_pro_parser/channel.rb +25 -0
  12. data/lib/guitar_pro_parser/chord_diagram.rb +14 -0
  13. data/lib/guitar_pro_parser/guitar_pro_helper.rb +73 -0
  14. data/lib/guitar_pro_parser/io/input_stream.rb +110 -0
  15. data/lib/guitar_pro_parser/io/reader.rb +759 -0
  16. data/lib/guitar_pro_parser/note.rb +71 -0
  17. data/lib/guitar_pro_parser/page_setup.rb +34 -0
  18. data/lib/guitar_pro_parser/song.rb +113 -0
  19. data/lib/guitar_pro_parser/track.rb +125 -0
  20. data/lib/guitar_pro_parser/version.rb +3 -0
  21. data/lib/guitar_pro_parser.rb +25 -0
  22. data/spec/lib/guitar_pro_parser/bar_settings_spec.rb +176 -0
  23. data/spec/lib/guitar_pro_parser/beat_spec.rb +79 -0
  24. data/spec/lib/guitar_pro_parser/channel_spec.rb +44 -0
  25. data/spec/lib/guitar_pro_parser/guitar_pro_helper_spec.rb +11 -0
  26. data/spec/lib/guitar_pro_parser/io/input_stream_spec.rb +101 -0
  27. data/spec/lib/guitar_pro_parser/note_spec.rb +55 -0
  28. data/spec/lib/guitar_pro_parser/page_setup_spec.rb +26 -0
  29. data/spec/lib/guitar_pro_parser/song_spec.rb +121 -0
  30. data/spec/lib/guitar_pro_parser/track_spec.rb +211 -0
  31. data/spec/lib/guitar_pro_parser_spec.rb +51 -0
  32. data/spec/spec_helper.rb +23 -0
  33. data/spec/tabs/tab.gp4 +0 -0
  34. data/spec/tabs/tab.gp5 +0 -0
  35. data/spec/tabs/test_musical_directions.gp5 +0 -0
  36. 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