musical_score 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +25 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +7 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +45 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/codeclimate-config.patch +1191 -0
  15. data/lib/musical_score/attribute/attribute.rb +69 -0
  16. data/lib/musical_score/attribute/clef.rb +44 -0
  17. data/lib/musical_score/attribute/key.rb +74 -0
  18. data/lib/musical_score/attribute/time.rb +43 -0
  19. data/lib/musical_score/const.rb +16 -0
  20. data/lib/musical_score/element_base.rb +17 -0
  21. data/lib/musical_score/errors.rb +3 -0
  22. data/lib/musical_score/io/importer.rb +22 -0
  23. data/lib/musical_score/location.rb +13 -0
  24. data/lib/musical_score/measures.rb +42 -0
  25. data/lib/musical_score/note/lyric.rb +43 -0
  26. data/lib/musical_score/note/notation/notation.rb +105 -0
  27. data/lib/musical_score/note/notation/tie.rb +22 -0
  28. data/lib/musical_score/note/notation/tuplet.rb +22 -0
  29. data/lib/musical_score/note/note.rb +141 -0
  30. data/lib/musical_score/note/pitch.rb +140 -0
  31. data/lib/musical_score/note/time_modification.rb +33 -0
  32. data/lib/musical_score/note/type.rb +29 -0
  33. data/lib/musical_score/notes.rb +37 -0
  34. data/lib/musical_score/part/measure.rb +49 -0
  35. data/lib/musical_score/part/part.rb +45 -0
  36. data/lib/musical_score/score/identification/creator.rb +29 -0
  37. data/lib/musical_score/score/identification/encoding.rb +66 -0
  38. data/lib/musical_score/score/identification/identification.rb +47 -0
  39. data/lib/musical_score/score/part/part.rb +37 -0
  40. data/lib/musical_score/score/score.rb +126 -0
  41. data/lib/musical_score/version.rb +4 -0
  42. data/lib/musical_score.rb +12 -0
  43. data/musical_score.gemspec +42 -0
  44. metadata +184 -0
@@ -0,0 +1,69 @@
1
+ require 'contracts'
2
+ Dir[File.expand_path('../', __FILE__) << '/**/*.rb'].each do |file|
3
+ # require file except myself
4
+ if(file != __FILE__)
5
+ require file
6
+ end
7
+ end
8
+ module MusicalScore
9
+ module Attribute
10
+ class Attribute < MusicalScore::ElementBase
11
+ include Contracts
12
+ attr_accessor :divisions, :key, :time, :instruments, :clef
13
+
14
+ # initialize the attibute
15
+ #
16
+ Contract KeywordArgs[
17
+ :divisions => Num,
18
+ :clef => Optional[MusicalScore::Attribute::Clef],
19
+ :key => Optional[MusicalScore::Attribute::Key],
20
+ :time => Optional[MusicalScore::Attribute::Time],
21
+ :instruments => Optional[String],
22
+ ] => Any
23
+ def initialize(
24
+ divisions:,
25
+ clef: MusicalScore::Attribute::Clef.new(:G),
26
+ key: MusicalScore::Attribute::Key.new(0, :major),
27
+ time: MusicalScore::Attribute::Time.new(4, 4),
28
+ instruments: 'Piano',
29
+ **rest_args
30
+ )
31
+ @divisions = divisions
32
+ @clef = clef
33
+ @key = key
34
+ @time = time
35
+ @instruments = instruments
36
+ end
37
+
38
+ Contract REXML::Element => MusicalScore::Attribute::Attribute
39
+ def self.create_by_xml(xml_doc)
40
+ divisions = xml_doc.elements["//divisions"].text.to_i
41
+ clef_doc = xml_doc.elements["//clef"]
42
+ time_doc = xml_doc.elements["//time"]
43
+ key_doc = xml_doc.elements["//key"]
44
+
45
+ clef = clef_doc ? MusicalScore::Attribute::Clef.create_by_xml(clef_doc) : nil
46
+ time = time_doc ? MusicalScore::Attribute::Time.create_by_xml(time_doc) : nil
47
+ key = key_doc ? MusicalScore::Attribute::Key.create_by_xml(key_doc) : nil
48
+
49
+ attributes = MusicalScore::Attribute::Attribute.new(divisions: divisions, clef: clef, time: time)
50
+ return attributes
51
+ end
52
+
53
+ def export_xml
54
+ attribute_element = REXML::Element.new('attributes')
55
+ divisions_element = REXML::Element.new('divisions').add_text(@divisions.to_s)
56
+ key_element = @key.export_xml
57
+ time_element = @time.export_xml
58
+ clef_element = @clef.export_xml
59
+
60
+ attribute_element.add_element(divisions_element)
61
+ attribute_element.add_element(key_element)
62
+ attribute_element.add_element(time_element)
63
+ attribute_element.add_element(clef_element)
64
+
65
+ return attribute_element
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,44 @@
1
+ require "contracts"
2
+ module MusicalScore
3
+ module Attribute
4
+ class Clef < MusicalScore::ElementBase
5
+ include Contracts
6
+ @@sign = %i(G F C percussion TAB jianpu none)
7
+ attr_reader :sign, :line, :clef_octave_change
8
+
9
+ # constructor
10
+ #
11
+ # @param sign The sign of clef, such as treble, bass. It is described by the @@sign symbols
12
+ # @param line The number of line from the bottom of the staff, which the sign note is defined at the line.
13
+ # @param clef_octave_change The number of clef changes, which is written either an octave higher or lower than sounding pitch
14
+ Contract Enum[*@@sign], Pos, Num => Any
15
+ def initialize(sign, line = 0, clef_octave_change = 0)
16
+ @sign = sign.to_sym
17
+ @line = line
18
+ @clef_octave_change = clef_octave_change
19
+ end
20
+
21
+ Contract REXML::Element => MusicalScore::Attribute::Clef
22
+ def self.create_by_xml(xml_doc)
23
+ sign = xml_doc.elements["sign"].text.to_sym
24
+ line = xml_doc.elements["line"] ? xml_doc.elements["line"].text.to_i : 0
25
+ clef_octave_change = xml_doc.elements["clef-octave-change"] ? xml_doc.elements["clef-octave-change"].text.to_i : 0
26
+ clef = MusicalScore::Attribute::Clef.new(sign, line, clef_octave_change)
27
+ return clef
28
+ end
29
+
30
+ def export_xml
31
+ clef_element = REXML::Element.new('clef')
32
+ sign_element = REXML::Element.new('sign').add_text(@sign.to_s)
33
+ line_element = @line != 0 ? REXML::Element.new('line').add_text(@line.to_s) : nil
34
+ clef_octave_change_element = @clef_octave_change != 0 ? REXML::Element.new('clef-octave-change').add_text(@clef_octave_change.to_s) : nil
35
+
36
+ clef_element.add_element(sign_element)
37
+ clef_element.add_element(line_element) if line_element
38
+ clef_element.add_element(clef_octave_change_element) if clef_octave_change_element
39
+
40
+ return clef_element
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,74 @@
1
+ require "contracts"
2
+ module MusicalScore
3
+ module Attribute
4
+ class Key < MusicalScore::ElementBase
5
+ include Contracts
6
+ @@mode = %i(major :minor)
7
+ @@circle_of_fifths = [0, 7, 2, 9, 4, 11, 6, 1, 8, 3, 10, 5]
8
+
9
+ attr_reader :fifths, :mode
10
+ # constructor
11
+ #
12
+ # @param fifths The number of sharps (positive) or flats (negative)
13
+ # @param mode major or minor
14
+ Contract Enum[*-NUMBER_OF_FIFTHS..NUMBER_OF_FIFTHS], Enum[*@@mode] => Any
15
+ def initialize(fifths, mode)
16
+ @fifths = fifths
17
+ @mode = mode.to_sym
18
+ end
19
+
20
+ # detect tonic in major scale and minor scale, and pithes that has sharp or flat
21
+ #
22
+ Contract None => HashOf[Any, Any]
23
+ def tonic_key_and_altered_pitches
24
+ if @fifths >= 0
25
+ pitch_number = @@circle_of_fifths[@fifths]
26
+ major_pitch = MusicalScore::Note::Pitch.new_note_sharp(pitch_number)
27
+
28
+ minor_number = (pitch_number + RELATED_KEY_SLIDE_NUMBER) % NUMBER_OF_NOTES
29
+ minor_pitch = MusicalScore::Note::Pitch.new_note_sharp(minor_number)
30
+
31
+ altered_pitches = Array.new
32
+ @fifths.times do |i|
33
+ count = (i + SHARP_START_INDEX) % NUMBER_OF_NOTES
34
+ altered_pitches.push(MusicalScore::Note::Pitch.new_note_sharp(@@circle_of_fifths[count]))
35
+ end
36
+ return { :major_pitch => major_pitch, :minor_pitch => minor_pitch, :altered_pitches => altered_pitches }
37
+ else
38
+ reversed = @@circle_of_fifths.reverse
39
+ fif = @fifths.abs
40
+ pitch_number = reversed[fif-1]
41
+ major_pitch = MusicalScore::Note::Pitch.new_note_flat(pitch_number)
42
+
43
+ minor_number = (pitch_number + RELATED_KEY_SLIDE_NUMBER) % NUMBER_OF_NOTES
44
+ minor_pitch = MusicalScore::Note::Pitch.new_note_flat(minor_number)
45
+
46
+ altered_pitches = Array.new
47
+ fif.times do |i|
48
+ count = (i + FLAT_START_INDEX) % NUMBER_OF_NOTES
49
+ altered_pitches.push(MusicalScore::Note::Pitch.new_note_flat(reversed[count]))
50
+ end
51
+ return { :major_pitch => major_pitch, :minor_pitch => minor_pitch, :altered_pitches => altered_pitches }
52
+ end
53
+ end
54
+
55
+ Contract REXML::Element => MusicalScore::Attribute::Key
56
+ def self.create_by_xml(xml_doc)
57
+ fifths = xml_doc.elements["fifths"].text.to_i
58
+ mode = xml_doc.elements["mode"].text.to_sym
59
+ return MusicalScore::Attribute::Key.new(fifths, mode)
60
+ end
61
+
62
+ def export_xml
63
+ key_element = REXML::Element.new('key')
64
+ fifths_element = REXML::Element.new('fifths').add_text(@fifths.to_s)
65
+ mode_element = REXML::Element.new('mode').add_text(@mode.to_s)
66
+
67
+ key_element.add_element(fifths_element)
68
+ key_element.add_element(mode_element)
69
+
70
+ return key_element
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,43 @@
1
+ require 'contracts'
2
+ module MusicalScore
3
+ module Attribute
4
+ class Time < MusicalScore::ElementBase
5
+ include Contracts
6
+ attr_reader :beats, :beat_type
7
+
8
+ # constructor
9
+ #
10
+ Contract Num, Num => Any
11
+ def initialize(beats, beat_type)
12
+ @beats = beats
13
+ @beat_type = beat_type
14
+ end
15
+
16
+ # @return [String] describe the time object in a fraction style
17
+ def to_s
18
+ return "%d/%d" % [@beats, @beat_type]
19
+ end
20
+
21
+ Contract REXML::Element => MusicalScore::Attribute::Time
22
+ def self.create_by_xml(xml_doc)
23
+ beats = xml_doc.elements["beats"].text.to_i
24
+ beat_type = xml_doc.elements["beat-type"].text.to_i
25
+ return MusicalScore::Attribute::Time.new(beats, beat_type)
26
+ end
27
+
28
+ def export_xml
29
+ time = REXML::Element.new('time')
30
+ beats = REXML::Element.new('beats')
31
+ beat_type = REXML::Element.new('beat-type')
32
+
33
+ beats.add_text(@beats.to_s)
34
+ beat_type.add_text(@beat_type.to_s)
35
+
36
+ time.add_element(beats)
37
+ time.add_element(beat_type)
38
+
39
+ return time
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ module MusicalScore
2
+ SHARP = :sharp
3
+ FLAT = :flat
4
+
5
+ NUMBER_OF_NOTES = 12
6
+ NUMBER_OF_FIFTHS = 7
7
+ SHARP_START_INDEX = 11
8
+ FLAT_START_INDEX = 6
9
+
10
+ RELATED_KEY_SLIDE_NUMBER = 9
11
+
12
+ AVAILABLE_NUMBERS_OF_ALTER = [-2, -1, 0, 1, 2]
13
+ TYPE_START_STOP = %i(start stop)
14
+ TYPE_START_STOP_CONTINUE = %i(start stop continue)
15
+ TYPE_CREATOR = %i(composer lyricist arranger)
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'contracts'
2
+ require 'rexml/document'
3
+
4
+ module MusicalScore
5
+ class ElementBase
6
+ include Contracts
7
+
8
+ Contract Or[REXML::Document, REXML::Element] => Any
9
+ def self.create_by_xml(element)
10
+ raise "Called abstract method: create_by_xml"
11
+ end
12
+
13
+ def export_xml
14
+ raise "Called abstract method: export_xml"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module MusicalScore
2
+ class InvalidFileType < StandardError; end
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'musical_score'
2
+ require 'rexml/document'
3
+ module MusicalScore
4
+ module IO
5
+ def import(file)
6
+ extname = File.extname(file)
7
+ case extname
8
+ when ".xml"
9
+ return import_xml(file)
10
+ else
11
+ raise MusicalScore::InvalidFileType
12
+ end
13
+ end
14
+ def import_xml(file_path)
15
+ doc = REXML::Document.new(File.new(file_path))
16
+ score = MusicalScore::Score::Score.create_by_xml(doc, file_path)
17
+ return score
18
+ end
19
+
20
+ module_function :import, :import_xml
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ require 'contracts'
2
+
3
+ module MusicalScore
4
+ class Location
5
+ attr_reader :measure_number, :location
6
+ include Contracts
7
+ Contract Nat, Rational => Any
8
+ def initialize(measure_number, location)
9
+ @measure_number = measure_number
10
+ @location = location
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ require 'contracts'
2
+ require 'musical_score/part/measure'
3
+ module MusicalScore
4
+ class Measures < MusicalScore::ElementBase
5
+ include Contracts
6
+ include Enumerable
7
+ attr_reader :measures
8
+
9
+ Contract ArrayOf[MusicalScore::Part::Measure] => Any
10
+ def initialize(measures)
11
+ @measures = measures
12
+ end
13
+
14
+ def [](index)
15
+ return @measures[index]
16
+ end
17
+
18
+ def each
19
+ @measures.each do |measure|
20
+ yield measure
21
+ end
22
+ end
23
+
24
+ def all_notes
25
+ result = Array.new
26
+ @measures.each do |measure|
27
+ result.concat(measure)
28
+ end
29
+ return result
30
+ end
31
+
32
+ def set_location
33
+ current_location = Rational(0)
34
+ @measures.each do |measure|
35
+ number = measure.number
36
+ measure.notes.set_location(current_location, number)
37
+ current_location += measure.notes.duration
38
+ measure.length = measure.notes.duration
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ require 'contracts'
2
+ module MusicalScore
3
+ module Note
4
+ class Lyric < MusicalScore::ElementBase
5
+ include Contracts
6
+ @@syllabic = %i(single begin end middle)
7
+ attr_accessor :text, :syllabic, :is_extend
8
+
9
+ # constructor
10
+ #
11
+ # @param text
12
+ # @param syllabic
13
+ # @param is_extend
14
+ Contract String, Maybe[Enum[*@@syllabic]], Bool => Any
15
+ def initialize(text, syllabic, is_extend = false)
16
+ @text = text
17
+ @syllabic = syllabic
18
+ @is_extend = is_extend
19
+ end
20
+ Contract REXML::Element => MusicalScore::Note::Lyric
21
+ def self.create_by_xml(xml_doc)
22
+ syllabic = xml_doc.elements["syllabic"] ? xml_doc.elements["syllabic"].text.to_sym : nil
23
+ text = xml_doc.elements["text"].text
24
+ is_extend = xml_doc.elements["extend"] ? true : false
25
+ return MusicalScore::Note::Lyric.new(text, syllabic, is_extend)
26
+ end
27
+
28
+ def export_xml(number)
29
+ lyric_element = REXML::Element.new('lyric')
30
+ lyric_element.add_attribute('number', number.to_s)
31
+ text_element = REXML::Element.new('text').add_text(@text.to_s)
32
+
33
+ if (@syllabic)
34
+ syllabic_element = REXML::Element.new('syllabic').add_text(@syllabic.to_s)
35
+ lyric_element.add_element(syllabic_element)
36
+ end
37
+ lyric_element.add_element(text_element)
38
+
39
+ return lyric_element
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,105 @@
1
+ require 'contracts'
2
+ Dir[File.expand_path('../', __FILE__) << '/**/*.rb'].each do |file|
3
+ # require file except myself
4
+ if(file != __FILE__)
5
+ require file
6
+ end
7
+ end
8
+ module MusicalScore
9
+ module Note
10
+ module Notation
11
+ class Notation < MusicalScore::ElementBase
12
+ include Contracts
13
+ @@articulation = %i(
14
+ accent
15
+ breath_mark
16
+ caesura
17
+ detached_legato
18
+ doit
19
+ falloff
20
+ plop
21
+ scoop
22
+ spiccato
23
+ staccatissimo
24
+ staccato
25
+ stress
26
+ strong_accent
27
+ tenuto
28
+ unstress
29
+ )
30
+ @@dynamics = %i(
31
+ f
32
+ ff
33
+ fff
34
+ ffff
35
+ fffff
36
+ fp
37
+ fz
38
+ mf
39
+ mp
40
+ p
41
+ pp
42
+ ppp
43
+ pppp
44
+ ppppp
45
+ rf
46
+ rfz
47
+ sf
48
+ sffz
49
+ sfpp
50
+ sfz
51
+ )
52
+ attr_accessor :articulation, :dynamics, :tie, :tuplet
53
+ Contract KeywordArgs[
54
+ :articulation => Maybe[Enum[*@@articulation]],
55
+ :dynamics => Maybe[Enum[*@@dynamics]],
56
+ :tie => Maybe[MusicalScore::Note::Notation::Tie],
57
+ :tuplet => Maybe[MusicalScore::Note::Notation::Tuplet],
58
+ ] => Any
59
+ def initialize(
60
+ articulation: nil,
61
+ dynamics: nil,
62
+ tie: nil,
63
+ tuplet: nil,
64
+ **rest_args
65
+ )
66
+ @articulation = articulation
67
+ @dynamics = dynamics
68
+ @tie = tie
69
+ @tuplet = tuplet
70
+ end
71
+
72
+ Contract REXML::Element => MusicalScore::Note::Notation::Notation
73
+ def self.create_by_xml(xml_doc)
74
+ articulation = xml_doc.elements["articulations"] ? xml_doc.elements["articulations"].elements[1].name.to_sym : nil
75
+ dynamics = xml_doc.elements["dynamics"] ? xml_doc.elements["dynamics"].elements[1].name.to_sym : nil
76
+ tie_arg = xml_doc.elements["tied"] ? xml_doc.elements["tied"].attributes["type"].to_sym : nil
77
+ tie = tie_arg ? MusicalScore::Note::Notation::Tie.new(tie_arg) : nil
78
+ tuplet_arg = xml_doc.elements["tuplet"] ? xml_doc.elements["tuplet"].attributes["type"].to_sym : nil
79
+ tuplet = tuplet_arg ? MusicalScore::Note::Notation::Tuplet.new(tuplet_arg) : nil
80
+ return MusicalScore::Note::Notation::Notation.new(articulation: articulation, dynamics: dynamics, tie: tie, tuplet: tuplet)
81
+ end
82
+
83
+ def export_xml
84
+ notations_element = REXML::Element.new('notations')
85
+ articulation_element = REXML::Element.new('articulations')
86
+ dynamics_element = REXML::Element.new('dynamics')
87
+
88
+ if (@articulation)
89
+ articulation_element.add_element(REXML::Element.new(@articulation.to_s))
90
+ notations_element.add_element(articulation_element)
91
+ end
92
+ if (@dynamics)
93
+ dynamics_element.add_element(REXML::Element.new(@dynamics.to_s))
94
+ notations_element.add_element(dynamics_element)
95
+ end
96
+
97
+ notations_element.add_element(@tie.export_xml) if @tie
98
+ notations_element.add_element(@tuplet.export_xml) if @tuplet
99
+
100
+ return notations_element
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,22 @@
1
+ require 'contracts'
2
+ module MusicalScore
3
+ module Note
4
+ module Notation
5
+ class Tie < MusicalScore::ElementBase
6
+ include Contracts
7
+ attr_accessor :type
8
+ Contract Enum[*TYPE_START_STOP_CONTINUE] => Any
9
+ def initialize(type)
10
+ @type = type.to_sym
11
+ end
12
+
13
+ def export_xml
14
+ tie_element = REXML::Element.new('tied')
15
+ tie_element.add_attribute('type',@type.to_s)
16
+
17
+ return tie_element
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ require 'contracts'
2
+ module MusicalScore
3
+ module Note
4
+ module Notation
5
+ class Tuplet < MusicalScore::ElementBase
6
+ include Contracts
7
+ attr_accessor :type
8
+ Contract Enum[*TYPE_START_STOP] => Any
9
+ def initialize(type)
10
+ @type = type
11
+ end
12
+
13
+ def export_xml
14
+ tuplet_element = REXML::Element.new('tuplet')
15
+ tuplet_element.add_attribute('type', @type.to_s)
16
+
17
+ return tuplet_element
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end