guitar_pro_parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in guitar_pro_parser.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Alexander Borovykh
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # GuitarProParser
2
+
3
+ It is a gem for Ruby that allows to read Guitar Pro files.
4
+ Now it supports Guitar Pro 4 and 5 files. Version 3 should work but is not tested at all.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'guitar_pro_parser'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install guitar_pro_parser
19
+
20
+ ## Usage
21
+
22
+ Require the library:
23
+
24
+ require 'guitar_pro_parser'
25
+
26
+ Read the file:
27
+
28
+ song = GuitarProParser.read_file('path_to_file')
29
+
30
+ Now you can access song's properties like that:
31
+
32
+ # Get some attributes of the song
33
+ puts song.title
34
+ puts song.artist
35
+ puts song.bpm
36
+
37
+ # Or read notes
38
+ song.tracks.each do |track|
39
+ track.bars.each do |bar|
40
+ puts "There are #{bar.voices[:lead].count} beats in lead voice"
41
+ puts "There are #{bar.voices[:bass].count} beats in bass voice" unless bar.voices[:bass].nil?
42
+
43
+ bar.voice[:lead] do |beat|
44
+ beat.strings.each do |string, note|
45
+ puts "Play #{string} string on the #{note.fret} fret."
46
+ puts "Note's duration is #{note.duration}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ If you don't need any information about beats, notes and other music stuff you can read headers only:
53
+
54
+ song = GuitarProParser.read_headers('path_to_file')
55
+
56
+ # You'll have title, subtitle, artist, etc.
57
+ song.title # => 'Title'
58
+
59
+ # But no notes
60
+ song.tracks.first.bars # => []
61
+
62
+ All available methods and attributes could be found in the source code. :)
63
+
64
+ TODO: Write documentation.
65
+
66
+ ## Contributing
67
+
68
+ 1. Fork it
69
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
71
+ 4. Push to the branch (`git push origin my-new-feature`)
72
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'guitar_pro_parser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "guitar_pro_parser"
8
+ spec.version = GuitarProParser::VERSION
9
+ spec.authors = ["Alexander Borovykh"]
10
+ spec.email = ["immaculate.pine@gmail.com"]
11
+ spec.description = %q{Gem for reading Guitar Pro files}
12
+ spec.summary = %q{It is a gem that allows to read Guitar Pro files. Now it supports Guitar Pro 4 and 5 files. Version 3 should work but is not tested at all.}
13
+ spec.homepage = "https://github.com/ImmaculatePine/guitar_pro_parser"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+
25
+ end
@@ -0,0 +1,29 @@
1
+ module GuitarProParser
2
+
3
+ # This class represents bars as containers of notes.
4
+ #
5
+ # == Attributes
6
+ #
7
+ # All attributes are read-only
8
+ #
9
+ # * +voices+ (hash) Voices of this bar.
10
+ # Guitar Pro 5 files has :lead and :bass voices.
11
+ # Guitar Pro 4 and less files has only :lead voice.
12
+ #
13
+ #
14
+ class Bar
15
+
16
+ attr_accessor :voices
17
+
18
+ def initialize
19
+ @voices = {lead: [], bass: []}
20
+ end
21
+
22
+ # Returns selected beat of selected voice
23
+ def get_beat(number, voice = :lead)
24
+ @voices.fetch(voice).fetch(number)
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,74 @@
1
+ module GuitarProParser
2
+
3
+ # This class represents settings of bars (measures).
4
+ #
5
+ # == Attributes
6
+ #
7
+ # *+new_time_signature+* (hash) Info about new time signature in format
8
+ # { numerator: 4, denominator: 4, beam_eight_notes_by_values: [2, 2, 2, 2] }
9
+ # or nil if doesn't present
10
+ #
11
+ # * +has_start_of_repeat+ (boolean) Is there start of repeat symbol?
12
+ # * +has_end_of_repeat+ (boolean) Is there end of repeat symbol?
13
+ # * +repeats_count+ (integer) Denominator of time signature if @has_end_of_repeat presents
14
+ # * +alternate_endings+ (array) Contains numbers of all alternate endings in Guitar Pro 5
15
+ # or the largest number of alternate endings in Guitar Pro 4 or less
16
+ #
17
+ # * +marker+ (hash) Info about marker in format:
18
+ # { name: "Marker name", color: [255, 0, 0] }
19
+ # or nil if doesn't present
20
+ #
21
+ # * +new_key_signature+ (hash) Info about new key signature in format:
22
+ # { key: 0, scale: :major }
23
+ # or nil if doesn't present.
24
+ # Key is encoded like this: # TODO: Convert to more readable format
25
+ # -2 Bb (bb)
26
+ # -1 F (b)
27
+ # 0 C
28
+ # 1 G (#)
29
+ # 2 D (##)
30
+ # Scale can be :major or :minor
31
+ #
32
+ # * +triplet_feel+ (symbol) Can be :no_triplet_feel, :triplet_8th or :triplet_16th
33
+ # * +double_bar+ (boolean) Is this bar double?
34
+ #
35
+ class BarSettings
36
+
37
+ attr_accessor :new_time_signature,
38
+ :has_start_of_repeat,
39
+ :has_end_of_repeat,
40
+ :repeats_count,
41
+ :alternate_endings,
42
+ :marker,
43
+ :new_key_signature,
44
+ :triplet_feel,
45
+ :double_bar
46
+
47
+ def initialize
48
+ # Initialize attributes by default values
49
+ @new_time_signature = nil
50
+ @has_start_of_repeat = false
51
+ @has_end_of_repeat = false
52
+ @repeats_count = 0
53
+ @alternate_endings = []
54
+ @marker = nil
55
+ @new_key_signature = nil
56
+ @triplet_feel = :no_triplet_feel
57
+ @double_bar = false
58
+ end
59
+
60
+ def set_new_key_signature(key, scale)
61
+ @new_key_signature = { key: key, scale: scale }
62
+ end
63
+
64
+ def set_new_time_signature(numerator, denominator, beam_eight_notes_by_values)
65
+ @new_time_signature = { numerator: numerator, denominator: denominator, beam_eight_notes_by_values: beam_eight_notes_by_values }
66
+ end
67
+
68
+ def set_marker(name, color)
69
+ @marker = { name: name, color: color }
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,42 @@
1
+ module GuitarProParser
2
+
3
+ class Beat
4
+
5
+ attr_accessor :dotted,
6
+ :mix_table,
7
+ :rest,
8
+ :duration,
9
+ :tuplet,
10
+ :chord_diagram,
11
+ :text,
12
+ :effects,
13
+ :strings,
14
+ :transpose
15
+
16
+ def initialize
17
+ # Initialize attributes by default values
18
+ @dotted = false
19
+ @mix_table = nil
20
+ @rest = nil
21
+
22
+ @duration = :eighth
23
+ @tuplet = nil
24
+ @chord_diagram = nil
25
+ @text = nil
26
+ @effects = {}
27
+ @strings = {}
28
+
29
+ @transpose = nil
30
+ end
31
+
32
+ def has_effect?(effect)
33
+ @effects.include?(effect)
34
+ end
35
+
36
+ def add_effect(effect)
37
+ @effects[effect] = nil
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,25 @@
1
+ module GuitarProParser
2
+
3
+ class Channel
4
+
5
+ attr_accessor :instrument,
6
+ :volume,
7
+ :pan,
8
+ :chorus,
9
+ :reverb,
10
+ :phaser,
11
+ :tremolo
12
+
13
+ def initialize
14
+ @instrument = 0
15
+ @volume = 13
16
+ @pan = 8
17
+ @chorus = 0
18
+ @reverb = 0
19
+ @phaser = 0
20
+ @tremolo = 0
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,14 @@
1
+ module GuitarProParser
2
+
3
+ class ChordDiagram
4
+
5
+ attr_accessor :name, :start_fret, :frets
6
+
7
+ def initialize
8
+ @name = ''
9
+ @start_fret = 0
10
+ @frets = []
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,73 @@
1
+ module GuitarProHelper
2
+
3
+ NOTES = %w(C C# D D# E F F# G G# A A# B)
4
+ VOICES = [:lead, :bass]
5
+ FINGERS = [:thumb, :index, :middle, :ring, :pinky]
6
+ BEND_TYPES = [:none, :bend, :bend_and_release, :bend_release_bend, :prebend, :prebend_and_release,
7
+ :tremolo_dip, :tremolo_dive, :tremolo_release_up, :tremolo_inverted_dip, :tremolo_return, :tremolo_release_down]
8
+ BEND_VIBRATO_TYPES = [:none, :fast, :average, :slow]
9
+ MUSICAL_DIRECTIONS = [:coda, :double_coda, :segno, :segno_segno, :fine, :da_capo,
10
+ :da_capo_al_coda, :da_capo_al_double_coda, :da_capo_al_fine,
11
+ :da_segno, :da_segno_al_coda, :da_segno_al_double_coda,
12
+ :da_segno_al_fine, :da_segno_segno, :da_segno_segno_al_coda,
13
+ :da_segno_segno_al_double_coda, :da_segno_segno_al_fine,
14
+ :da_coda, :da_double_coda]
15
+ TRIPLET_FEEL = [:no_triplet_feel, :triplet_8th, :triplet_16th]
16
+ REST_TYPES = { '0' => :empty_beat,
17
+ '2' => :rest }
18
+ DURATIONS = { '-2' => :whole,
19
+ '-1' => :half,
20
+ '0' => :quarter,
21
+ '1' => :eighth,
22
+ '2' => :sixteens,
23
+ '3' => :thirty_second,
24
+ '4' => :sixty_fourth }
25
+ STRING_EFFECTS = [:tremolo_bar, :tapping, :slapping, :popping]
26
+ STROKE_EFFECT_SPEEDS = [:none, 128, 64, 32, 16, 8, 4]
27
+ STROKE_DIRECTIONS = [:none, :up, :down]
28
+ NOTE_TYPES = [:normal, :tie, :dead]
29
+ NOTE_DYNAMICS = %w(ppp pp p mp mf f ff fff)
30
+ GRACE_NOTE_TRANSITION_TYPES = [:none, :slide, :bend, :hammer]
31
+ GRACE_NOTE_DURATIONS = { '3' => 16, '2' => 32, '1' => 64 }
32
+ TREMOLO_PICKING_SPEEDS = { '3' => 32, '2' => 16, '1' => 8 }
33
+
34
+ SLIDE_TYPES = [:no_slide, :shift_slide, :legato_slide, :slide_out_and_downwards, :slide_out_and_upwards, :slide_in_from_below, :slide_in_from_above]
35
+ MAP_SLIDE_TYPES_GP5 = { '0'=>0, '1'=>1, '2'=>2, '4'=>3, '8'=>4, '16'=>5, '32'=>6 }
36
+ MAP_SLIDE_TYPES_GP4 = { '-2'=>0, '-1'=>1, '0'=>2, '1'=>3, '2'=>4, '3'=>5, '4'=>6 }
37
+
38
+ HARMONIC_TYPES = [:none, :natural, :artificial, :tapped, :pinch, :semi]
39
+ TRILL_PERIODS = [4, 8, 16]
40
+
41
+ # Macros to create boolean instance variables' getters like this:
42
+ # attr_boolean :complete
43
+ # generates
44
+ # complete?
45
+ # method that returns @complete instance variable
46
+ def attr_boolean(*variables)
47
+ variables.each do |variable|
48
+ define_method("#{variable}?") do
49
+ instance_variable_get("@#{variable}")
50
+ end
51
+ end
52
+ end
53
+
54
+ # Converts note's digit representation to its string equivalent:
55
+ # 0 for C0, 1 for C#0, etc.
56
+ def GuitarProHelper.digit_to_note(digit)
57
+ note_index = 0
58
+ octave = 0
59
+ digit.times do |i|
60
+ note_index = note_index + 1
61
+ if note_index == NOTES.count
62
+ note_index = 0
63
+ octave = octave + 1
64
+ end
65
+ end
66
+
67
+ "#{NOTES.fetch(note_index)}#{octave.to_s}"
68
+ end
69
+
70
+
71
+ # TODO: Create helper to convert number of increments of .1dB to float for equalizers
72
+
73
+ end
@@ -0,0 +1,110 @@
1
+ module GuitarProParser
2
+
3
+ class InputStream
4
+
5
+ INTEGER_LENGTH = 4
6
+ SHORT_INTEGER_LENGTH = 2
7
+ BYTE_LENGTH = 1
8
+
9
+ attr_accessor :offset
10
+
11
+ def initialize file_path
12
+ @file_path = file_path
13
+ @offset = 0
14
+ end
15
+
16
+ # Reads unsigned integer (4 bytes)
17
+ def read_integer
18
+ value = IO.binread(@file_path, INTEGER_LENGTH, @offset).unpack('i')[0]
19
+ skip_integer
20
+ value
21
+ end
22
+
23
+ # Reads signed integer (4 bytes)
24
+ def read_signed_integer
25
+ value = IO.binread(@file_path, INTEGER_LENGTH, @offset).unpack('I')[0]
26
+ skip_integer
27
+ value
28
+ end
29
+
30
+ # Reads unsigned short integer (2 bytes)
31
+ def read_short_integer
32
+ value = IO.binread(@file_path, SHORT_INTEGER_LENGTH, @offset).unpack('S_')[0]
33
+ skip_short_integer
34
+ value
35
+ end
36
+
37
+ # Reads signed short integer (2 bytes)
38
+ def read_signed_short_integer
39
+ value = IO.binread(@file_path, SHORT_INTEGER_LENGTH, @offset).unpack('s_')[0]
40
+ skip_short_integer
41
+ value
42
+ end
43
+
44
+ # Reads unsigned byte (8 bits) (0..255)
45
+ def read_byte
46
+ value = IO.binread(@file_path, BYTE_LENGTH, @offset).unpack('C')[0]
47
+ skip_byte
48
+ value
49
+ end
50
+
51
+ # Reads signed byte (8 bits) (-127..127)
52
+ def read_signed_byte
53
+ value = IO.binread(@file_path, BYTE_LENGTH, @offset).unpack('c')[0]
54
+ skip_byte
55
+ value
56
+ end
57
+
58
+ # Reads signed byte as boolean (8 bit)
59
+ def read_boolean
60
+ !read_byte.zero?
61
+ end
62
+
63
+ # Reads string with specified length
64
+ def read_string(length)
65
+ value = IO.binread(@file_path, length, @offset)
66
+ increment_offset(length)
67
+ value
68
+ end
69
+
70
+ # Reads byte as 8-bit bitmask
71
+ def read_bitmask
72
+ bits = []
73
+ value = read_byte
74
+ value.to_s(2).each_char { |bit| bits << !bit.to_i.zero? }
75
+ bits.reverse!
76
+ bits << false while bits.count < 8
77
+ bits
78
+ end
79
+
80
+ # Reads data chunk from file.
81
+ #
82
+ # Chunk's format is:
83
+ # 4 bytes - field length (including string length byte that follows this value)
84
+ # 1 byte - string length (N)
85
+ # N bytes - string
86
+ def read_chunk
87
+ skip_integer
88
+ string_length = read_byte
89
+ read_string string_length
90
+ end
91
+
92
+ def increment_offset delta
93
+ @offset = @offset + delta
94
+ end
95
+
96
+ def skip_integer
97
+ increment_offset(INTEGER_LENGTH)
98
+ end
99
+
100
+ def skip_short_integer
101
+ increment_offset(SHORT_INTEGER_LENGTH)
102
+ end
103
+
104
+ def skip_byte
105
+ increment_offset(BYTE_LENGTH)
106
+ end
107
+
108
+ end
109
+
110
+ end