music-transcription 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/bin/transcribe CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'micro-optparse'
4
- require 'pry'
5
4
  require 'spcore'
6
5
  require 'wavefile'
7
6
  require 'music-transcription'
@@ -1,5 +1,3 @@
1
- require 'hashmake'
2
-
3
1
  # basic core classes
4
2
  require 'music-transcription/version'
5
3
 
@@ -7,7 +5,7 @@ require 'music-transcription/version'
7
5
  require 'music-transcription/pitch'
8
6
  require 'music-transcription/pitch_constants'
9
7
  require 'music-transcription/link'
10
- require 'music-transcription/interval'
8
+ require 'music-transcription/accent'
11
9
  require 'music-transcription/note'
12
10
  require 'music-transcription/transition'
13
11
  require 'music-transcription/value_change'
@@ -16,5 +14,3 @@ require 'music-transcription/part'
16
14
  require 'music-transcription/program'
17
15
  require 'music-transcription/tempo'
18
16
  require 'music-transcription/score'
19
- require 'music-transcription/instrument_config'
20
- require 'music-transcription/arrangement'
@@ -0,0 +1,43 @@
1
+ module Music
2
+ module Transcription
3
+
4
+ # Defines a note accent (stacatto, tenuto, etc.)
5
+ #
6
+ # @author James Tunnell
7
+ #
8
+ class Accent
9
+ def ==(other)
10
+ self.class == other.class
11
+ end
12
+
13
+ def clone
14
+ self.class.new
15
+ end
16
+
17
+ class Stacatto < Accent
18
+ def to_s
19
+ return "."
20
+ end
21
+ end
22
+
23
+ class Stacattissimo < Accent
24
+ def to_s
25
+ return "'"
26
+ end
27
+ end
28
+
29
+ class Martellato < Accent
30
+ def to_s
31
+ return "^"
32
+ end
33
+ end
34
+
35
+ class Tenuto < Accent
36
+ def to_s
37
+ return "_"
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ module Music
2
+ module Transcription
3
+ class ValueNotPositiveError < RuntimeError;
4
+ class ValueOutOfRangeError < RuntimeError;
5
+ end
@@ -8,107 +8,64 @@ module Transcription
8
8
  # @!attribute [rw] target_pitch
9
9
  # @return [Pitch] The pitch of the note which is being connected to.
10
10
  #
11
- # @!attribute [rw] relationship
12
- # @return [Symbol] The relationship between the current note and a consecutive
13
- # note. Valid values are RELATIONSHIP_NONE, RELATIONSHIP_TIE,
14
- # RELATIONSHIP_SLUR, RELATIONSHIP_LEGATO, RELATIONSHIP_GLISSANDO,
15
- # and RELATIONSHIP_PORTAMENTO.
16
- #
17
11
  class Link
18
- include Hashmake::HashMakeable
19
-
20
- # no relationship with the following note
21
- RELATIONSHIP_NONE = :none
22
- # tie to the following note
23
- RELATIONSHIP_TIE = :tie
24
- # play notes continuously and don't rearticulate
25
- RELATIONSHIP_SLUR = :slur
26
- # play notes continuously and do rearticulate
27
- RELATIONSHIP_LEGATO = :legato
28
- # play an uninterrupted slide through a series of consecutive tones to the next note.
29
- RELATIONSHIP_GLISSANDO = :glissando
30
- # play an uninterrupted glide to the next note.
31
- RELATIONSHIP_PORTAMENTO = :portamento
12
+ attr_accessor :target_pitch
32
13
 
33
- # a list of valid note relationships
34
- RELATIONSHIPS = [
35
- RELATIONSHIP_NONE,
36
- RELATIONSHIP_TIE,
37
- RELATIONSHIP_SLUR,
38
- RELATIONSHIP_LEGATO,
39
- RELATIONSHIP_GLISSANDO,
40
- RELATIONSHIP_PORTAMENTO
41
- ]
42
-
43
- # hashed-arg specs (for hash-makeable idiom)
44
- ARG_SPECS = {
45
- :target_pitch => arg_spec(:reqd => false, :type => Pitch, :default => ->(){ Pitch.new }),
46
- :relationship => arg_spec(:reqd => false, :type => Symbol, :default => RELATIONSHIP_NONE, :validator => ->(a){ RELATIONSHIPS.include?(a)}),
47
- }
14
+ def initialize target_pitch
15
+ @target_pitch = target_pitch
16
+ end
48
17
 
49
- attr_reader :target_pitch, :relationship
50
-
51
- # A new instance of Link.
52
- # @param [Hash] args Hashed arguments. See ARG_SPECS for details about valid keys.
53
- def initialize args={}
54
- hash_make args
18
+ def ==(other)
19
+ self.class == other.class && self.target_pitch == other.target_pitch
55
20
  end
56
21
 
57
- # Produce an identical Link object.
58
22
  def clone
59
- Link.new(:target_pitch => @target_pitch.clone, :relationship => @relationship)
23
+ self.class.new @target_pitch.clone
60
24
  end
61
25
 
62
- # Compare equality of two Link objects.
63
- def ==(other)
64
- return (@target_pitch == other.target_pitch) && (@relationship == other.relationship)
26
+ def to_s
27
+ return @target_pitch
65
28
  end
66
-
67
- # Set the pitch of the note being connected to.
68
- # @param [Pitch] target_pitch The pitch of the note being connected to.
69
- # @raise [ArgumentError] if target_pitch is not a Pitch.
70
- def target_pitch= target_pitch
71
- ARG_SPECS[:target_pitch].validate_value target_pitch
72
- @target_pitch = target_pitch
29
+
30
+ class Slur < Link
31
+ def initialize target_pitch
32
+ super(target_pitch)
33
+ end
34
+
35
+ def to_s
36
+ return "=" + super()
37
+ end
73
38
  end
74
-
75
- # Set the note relationship.
76
- # @param [Symbol] relationship The relationship of the note to the following
77
- # note (if applicable). Valid relationship are given by the
78
- # RELATIONSHIPS constant.
79
- # @raise [ArgumentError] if relationship is not a valid relationship.
80
- def relationship= relationship
81
- ARG_SPECS[:relationship].validate_value relationship
82
- @relationship = relationship
39
+
40
+ class Legato < Link
41
+ def initialize target_pitch
42
+ super(target_pitch)
43
+ end
44
+
45
+ def to_s
46
+ return "-" + super()
47
+ end
83
48
  end
84
49
 
85
- end
86
-
87
- module_function
88
-
89
- # helper method to create a Link object with GLISSANDO relationship.
90
- def glissando pitch
91
- Link.new(:target_pitch => pitch, :relationship => Link::RELATIONSHIP_GLISSANDO)
92
- end
93
-
94
- # helper method to create a Link object with LEGATO relationship.
95
- def legato pitch
96
- Link.new(:target_pitch => pitch, :relationship => Link::RELATIONSHIP_LEGATO)
97
- end
98
-
99
- # helper method to create a Link object with PORTAMENTO relationship.
100
- def portamento pitch
101
- Link.new(:target_pitch => pitch, :relationship => Link::RELATIONSHIP_PORTAMENTO)
102
- end
103
-
104
- # helper method to create a Link object with SLUR relationship.
105
- def slur pitch
106
- Link.new(:target_pitch => pitch, :relationship => Link::RELATIONSHIP_SLUR)
107
- end
108
-
109
- # helper method to create a Link object with TIE relationship.
110
- def tie pitch
111
- Link.new(:target_pitch => pitch, :relationship => Link::RELATIONSHIP_TIE)
50
+ class Glissando < Link
51
+ def initialize target_pitch
52
+ super(target_pitch)
53
+ end
54
+
55
+ def to_s
56
+ return "~" + super()
57
+ end
58
+ end
59
+
60
+ class Portamento < Link
61
+ def initialize target_pitch
62
+ super(target_pitch)
63
+ end
64
+
65
+ def to_s
66
+ return "/" + super()
67
+ end
68
+ end
112
69
  end
113
70
 
114
71
  end
@@ -1,156 +1,112 @@
1
1
  module Music
2
2
  module Transcription
3
3
 
4
- # Abstraction of a musical note. The note can contain multiple intervals
5
- # (at different pitches). Each interval can also contain a link to an interval
6
- # in a following note. Contains values for attack, sustain, and separation,
7
- # which will be used to form the envelope profile for the note.
4
+ require 'set'
5
+
6
+ # Abstraction of a musical note. The note can contain zero or more pitches,
7
+ # with links to a pitches in a following note. The note also has an accent,
8
+ # which must be one of Note::ACCENTS.
8
9
  #
9
10
  # @author James Tunnell
10
11
  #
11
12
  # @!attribute [rw] duration
12
13
  # @return [Numeric] The duration (in, say note length or time), greater than 0.0.
13
14
  #
14
- # @!attribute [rw] intervals
15
- # @return [Numeric] The intervals that define which pitches are part of the
16
- # note and can link to intervals in a following note.
17
- #
18
- # @!attribute [rw] attack
19
- # @return [Numeric] The amount of attack, from 0.0 (less) to 1.0 (more).
20
- # Attack controls how quickly a note's loudness increases
21
- # at the start.
22
- #
23
- # @!attribute [rw] sustain
24
- # @return [Numeric] The amount of sustain, from 0.0 (less) to 1.0 (more).
25
- # Sustain controls how much the note's loudness is
26
- # sustained after the attack.
15
+ # @!attribute [r] pitches
16
+ # @return [Set] The pitches that are part of the note and can link to
17
+ # pitches in a following note.
27
18
  #
28
- # @!attribute [rw] separation
29
- # @return [Numeric] Shift the note release towards or away the beginning
30
- # of the note. From 0.0 (towards end of the note) to
31
- # 1.0 (towards beginning of the note).
19
+ # @!attribute [r] links
20
+ # @return [Hash] Maps pitches in the current note to pitches in the following
21
+ # note, by some link class, like Link::Slur.
22
+ #
23
+ # @!attribute [rw] accent
24
+ # @return [Accent] The accent type, which must be one of Note::ACCENTS.
32
25
  #
33
26
  class Note
34
- include Hashmake::HashMakeable
35
- attr_reader :duration, :intervals, :sustain, :attack, :separation
27
+ attr_reader :duration, :pitches, :links
28
+ attr_accessor :accent
36
29
 
37
- # hashed-arg specs (for hash-makeable idiom)
38
- ARG_SPECS = {
39
- :duration => arg_spec(:type => Numeric, :reqd => true, :validator => ->(a){ a > 0 } ),
40
- :intervals => arg_spec_array(:type => Interval, :reqd => false),
41
- :sustain => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5),
42
- :attack => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5),
43
- :separation => arg_spec(:type => Numeric, :reqd => false, :validator => ->(a){ a.between?(0.0,1.0)}, :default => 0.5),
44
- }
45
-
46
30
  # A new instance of Note.
47
- # @param [Hash] args Hashed arguments. See Note::ARG_SPECS for details.
48
- def initialize args={}
49
- hash_make args
31
+ def initialize duration, pitches = [], links: {}, accent: nil
32
+ self.duration = duration
33
+ @pitches = Set.new(pitches)
34
+ @links = links
35
+ self.accent = accent
50
36
  end
51
37
 
52
38
  # Compare the equality of another Note object.
53
39
  def == other
54
40
  return (@duration == other.duration) &&
55
- (@intervals == other.intervals) &&
56
- (@sustain == other.sustain) &&
57
- (@attack == other.attack) &&
58
- (@separation == other.separation)
41
+ (self.pitches == other.pitches) &&
42
+ (@links == other.links) &&
43
+ (@accent == other.accent)
59
44
  end
60
45
 
61
46
  # Set the note duration.
62
47
  # @param [Numeric] duration The duration to use.
63
48
  # @raise [ArgumentError] if duration is not greater than 0.
64
49
  def duration= duration
65
- ARG_SPECS[:duration].validate_value duration
50
+ raise ValueNotPositiveError if duration <= 0
66
51
  @duration = duration
67
- end
68
-
69
- # Set the note sustain.
70
- # @param [Numeric] sustain The sustain of the note.
71
- # @raise [ArgumentError] if sustain is not a Numeric.
72
- # @raise [RangeError] if sustain is outside the range 0.0..1.0.
73
- def sustain= sustain
74
- ARG_SPECS[:sustain].validate_value sustain
75
- @sustain = sustain
76
- end
77
-
78
- # Set the note attack.
79
- # @param [Numeric] attack The attack of the note.
80
- # @raise [ArgumentError] if attack is not a Numeric.
81
- # @raise [RangeError] if attack is outside the range 0.0..1.0.
82
- def attack= attack
83
- ARG_SPECS[:attack].validate_value attack
84
- @attack = attack
85
- end
86
-
87
- # Set the note separation.
88
- # @param [Numeric] separation The separation of the note.
89
- # @raise [ArgumentError] if separation is not a Numeric.
90
- # @raise [RangeError] if separation is outside the range 0.0..1.0.
91
- def separation= separation
92
- ARG_SPECS[:separation].validate_value separation
93
- @separation = separation
94
52
  end
95
53
 
96
54
  # Produce an identical Note object.
97
55
  def clone
98
56
  Marshal.load(Marshal.dump(self))
99
57
  end
100
-
101
- # Remove any duplicate intervals (occuring on the same pitch), removing
102
- # all but the last occurance. Remove any duplicate links (links to the
103
- # same interval), removing all but the last occurance.
104
- def remove_duplicates
105
- # in case of duplicate notes
106
- intervals_to_remove = Set.new
107
- for i in (0...@intervals.count).entries.reverse
108
- @intervals.each_index do |j|
109
- if j < i
110
- if @intervals[i].pitch == @intervals[j].pitch
111
- intervals_to_remove.add @intervals[j]
112
- end
113
- end
114
- end
115
- end
116
- @intervals.delete_if { |interval| intervals_to_remove.include? interval}
117
-
118
- # in case of duplicate links
119
- for i in (0...@intervals.count).entries.reverse
120
- @intervals.each_index do |j|
121
- if j < i
122
- if @intervals[i].linked? && @intervals[j].linked? && @intervals[i].link.target_pitch == @intervals[j].link.target_pitch
123
- @intervals[j].link = Link.new
124
- end
125
- end
126
- end
127
- end
58
+
59
+ def transpose_pitches_only pitch_diff
60
+ self.clone.transpose_pitches! pitch_diff, transpose_link
128
61
  end
129
62
 
130
- def transpose pitch_diff
131
- self.clone.transpose! pitch_diff
63
+ def transpose_pitches_only! pitch_diff
64
+ @pitches = @pitches.map {|pitch| pitch + pitch_diff}
65
+ new_links = {}
66
+ @links.each_pair do |k,v|
67
+ new_links[k + pitch_diff] = v
68
+ end
69
+ @links = new_links
70
+ return self
71
+ end
72
+
73
+ def transpose_pitches_and_links pitch_diff
74
+ self.clone.transpose_pitches_and_links! pitch_diff
132
75
  end
133
76
 
134
- def transpose! pitch_diff
135
- @intervals.each do |interval|
136
- interval.pitch += pitch_diff
137
- interval.link.target_pitch += pitch_diff
77
+ def transpose_pitches_and_links! pitch_diff
78
+ @pitches = @pitches.map {|pitch| pitch + pitch_diff}
79
+ new_links = {}
80
+ @links.each_pair do |k,v|
81
+ v.target_pitch += pitch_diff
82
+ new_links[k + pitch_diff] = v
138
83
  end
84
+ @links = new_links
139
85
  return self
140
86
  end
141
87
 
142
88
  def to_s
143
- "#{duration}:#{intervals.map{|i| i.pitch}.inspect}"
89
+ output = @duration.to_s
90
+ if @pitches.any?
91
+ output += "@"
92
+ @pitches[0...-1].each do |pitch|
93
+ output += pitch.to_s
94
+ if @links.has_key? pitch
95
+ output += @links[pitch].to_s
96
+ end
97
+ output += ","
98
+ end
99
+
100
+ last_pitch = @pitches[-1]
101
+ output += last_pitch.to_s
102
+ if @links.has_key? last_pitch
103
+ output += @links[last_pitch].to_s
104
+ end
105
+ end
106
+
107
+ return output
144
108
  end
145
109
  end
146
110
 
147
- module_function
148
-
149
- def note duration, intervals = [], other_args = {}
150
- Note.new(
151
- { :duration => duration, :intervals => intervals }.merge other_args
152
- )
153
- end
154
-
155
111
  end
156
112
  end