music-transcription 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/music-transcription/model/link.rb +10 -2
- data/lib/music-transcription/model/note.rb +4 -11
- data/lib/music-transcription/model/pitch.rb +12 -43
- data/lib/music-transcription/version.rb +1 -1
- data/spec/model/note_spec.rb +16 -33
- data/spec/model/pitch_spec.rb +17 -32
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b53782c68a60ec01fd23022b00fa7ee67a76c6c
|
4
|
+
data.tar.gz: 22433554e9566a3841800f4ab757268160e2b5f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6fc8591be37735700427c607dac2cc71706287eb704e9179a4077c99a92d6a7f03c70ee3c9ded54115f5f0a54ad444640bc8cb85c206be849a0e32261fdc34c
|
7
|
+
data.tar.gz: 5c9bc76ed55f0f729fbf0caf384c7aaad4aa0e3f9f72961995d1bb3cf2f78604b0f3ffcc268bacb2f7eae055a63e9736f265e1377f0cbc8ece39bab2af20616a
|
@@ -18,8 +18,12 @@ class Link
|
|
18
18
|
self.class == other.class
|
19
19
|
end
|
20
20
|
|
21
|
+
def transpose diff
|
22
|
+
self.clone.transpose! diff
|
23
|
+
end
|
24
|
+
|
21
25
|
def transpose! diff
|
22
|
-
|
26
|
+
return self
|
23
27
|
end
|
24
28
|
|
25
29
|
def to_s; "="; end
|
@@ -37,8 +41,12 @@ class Link
|
|
37
41
|
self.class == other.class && @target_pitch == other.target_pitch
|
38
42
|
end
|
39
43
|
|
44
|
+
def transpose diff
|
45
|
+
self.clone.transpose! diff
|
46
|
+
end
|
47
|
+
|
40
48
|
def transpose! diff
|
41
|
-
@target_pitch
|
49
|
+
@target_pitch = @target_pitch.transpose(diff)
|
42
50
|
end
|
43
51
|
|
44
52
|
def to_s
|
@@ -43,17 +43,10 @@ class Note
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def transpose! diff
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@pitches = @pitches.map {|pitch| pitch + diff}
|
51
|
-
new_links = {}
|
52
|
-
@links.each_pair do |k,v|
|
53
|
-
v.transpose! diff
|
54
|
-
new_links[k + diff] = v
|
55
|
-
end
|
56
|
-
@links = new_links
|
46
|
+
@pitches = @pitches.map {|pitch| pitch.transpose(diff) }
|
47
|
+
@links = Hash[ @links.map do |k,v|
|
48
|
+
[ k.transpose(diff), v.transpose(diff) ]
|
49
|
+
end ]
|
57
50
|
return self
|
58
51
|
end
|
59
52
|
|
@@ -20,7 +20,7 @@ module Transcription
|
|
20
20
|
#
|
21
21
|
class Pitch
|
22
22
|
include Comparable
|
23
|
-
attr_reader :octave, :semitone
|
23
|
+
attr_reader :octave, :semitone, :total_semitone
|
24
24
|
|
25
25
|
#The default number of semitones per octave is 12, corresponding to
|
26
26
|
# the twelve-tone equal temperment tuning system.
|
@@ -33,6 +33,7 @@ class Pitch
|
|
33
33
|
@octave = octave
|
34
34
|
@semitone = semitone
|
35
35
|
normalize!
|
36
|
+
@total_semitone = @octave*SEMITONES_PER_OCTAVE + @semitone
|
36
37
|
end
|
37
38
|
|
38
39
|
# Return the pitch's frequency, which is determined by multiplying the base
|
@@ -43,40 +44,22 @@ class Pitch
|
|
43
44
|
return self.ratio() * BASE_FREQ
|
44
45
|
end
|
45
46
|
|
46
|
-
# Calculate the total semitone count. Converts octave to semitone count
|
47
|
-
# before adding to existing semitone count.
|
48
|
-
# @return [Fixnum] total semitone count
|
49
|
-
def total_semitone
|
50
|
-
return (@octave * SEMITONES_PER_OCTAVE) + @semitone
|
51
|
-
end
|
52
|
-
|
53
47
|
# Calculate the pitch ratio. Raises 2 to the power of the total semitone
|
54
48
|
# count divided by semitones-per-octave.
|
55
49
|
# @return [Float] ratio
|
56
50
|
def ratio
|
57
|
-
2.0**(
|
58
|
-
end
|
59
|
-
|
60
|
-
# Round to the nearest semitone.
|
61
|
-
def round
|
62
|
-
self.clone.round!
|
63
|
-
end
|
64
|
-
|
65
|
-
# Calculates the number of semitones which would represent the pitch's
|
66
|
-
# octave and semitone count
|
67
|
-
def total_semitone
|
68
|
-
return (@octave * SEMITONES_PER_OCTAVE) + @semitone
|
51
|
+
2.0**(@total_semitone.to_f / SEMITONES_PER_OCTAVE)
|
69
52
|
end
|
70
53
|
|
71
54
|
# Override default hash method.
|
72
55
|
def hash
|
73
|
-
return
|
56
|
+
return @total_semitone
|
74
57
|
end
|
75
58
|
|
76
59
|
# Compare pitch equality using total semitone
|
77
60
|
def ==(other)
|
78
61
|
return (self.class == other.class &&
|
79
|
-
|
62
|
+
@total_semitone == other.total_semitone)
|
80
63
|
end
|
81
64
|
|
82
65
|
def eql?(other)
|
@@ -86,29 +69,15 @@ class Pitch
|
|
86
69
|
# Compare pitches. A higher ratio or total semitone is considered larger.
|
87
70
|
# @param [Pitch] other The pitch object to compare.
|
88
71
|
def <=> (other)
|
89
|
-
|
72
|
+
@total_semitone <=> other.total_semitone
|
90
73
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
def + (other)
|
95
|
-
if other.is_a? Integer
|
96
|
-
return Pitch.new(octave: @octave, semitone: @semitone + other)
|
97
|
-
else
|
98
|
-
return Pitch.new(octave: (@octave + other.octave),
|
99
|
-
semitone: (@semitone + other.semitone))
|
100
|
-
end
|
74
|
+
|
75
|
+
def diff other
|
76
|
+
@total_semitone - other.total_semitone
|
101
77
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
def - (other)
|
106
|
-
if other.is_a? Integer
|
107
|
-
return Pitch.new(octave: @octave, semitone: @semitone - other)
|
108
|
-
else
|
109
|
-
return Pitch.new(octave: (@octave - other.octave),
|
110
|
-
semitone: (@semitone - other.semitone))
|
111
|
-
end
|
78
|
+
|
79
|
+
def transpose interval
|
80
|
+
Pitch.from_semitones @total_semitone + interval
|
112
81
|
end
|
113
82
|
|
114
83
|
# Produce an identical Pitch object.
|
data/spec/model/note_spec.rb
CHANGED
@@ -64,61 +64,44 @@ describe Note do
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
describe '#transpose
|
67
|
+
describe '#transpose' do
|
68
68
|
context 'given pitch diff' do
|
69
69
|
before(:all) do
|
70
|
-
@
|
71
|
-
@
|
72
|
-
@
|
70
|
+
@note1 = Note::quarter([C2,F2], links:{C2=>Link::Glissando.new(D2)})
|
71
|
+
@interval = 4
|
72
|
+
@note2 = @note1.transpose(@interval)
|
73
73
|
end
|
74
74
|
|
75
75
|
it 'should modifiy pitches by adding pitch diff' do
|
76
|
-
@
|
77
|
-
|
76
|
+
@note2.pitches.each_with_index do |p,i|
|
77
|
+
p.diff(@note1.pitches[i]).should eq(@interval)
|
78
|
+
end
|
78
79
|
end
|
79
80
|
|
80
81
|
it 'should also affect link targets' do
|
81
|
-
@
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
context 'given integer diff' do
|
87
|
-
it 'should transpose the given number of semitones' do
|
88
|
-
Note::quarter([C2]).transpose!(4).pitches[0].should eq(E2)
|
82
|
+
@note1.links.each do |k,v|
|
83
|
+
kt = k.transpose(@interval)
|
84
|
+
@note2.links.should have_key kt
|
85
|
+
@note2.links[kt].target_pitch.should eq(v.target_pitch.transpose(@interval))
|
86
|
+
end
|
89
87
|
end
|
90
88
|
end
|
91
89
|
|
92
90
|
context 'with links that have no target pitch' do
|
93
91
|
it 'should not raise error' do
|
94
92
|
n = Note::half([E2],links: {E2 => Link::Tie.new})
|
95
|
-
expect { n.transpose
|
93
|
+
expect { n.transpose(1) }.to_not raise_error
|
96
94
|
end
|
97
95
|
end
|
98
|
-
|
99
|
-
it 'should return self' do
|
100
|
-
n = Note::quarter
|
101
|
-
n.transpose!(0).should eq n
|
102
|
-
end
|
103
96
|
end
|
104
97
|
|
105
|
-
describe '#stretch
|
98
|
+
describe '#stretch' do
|
106
99
|
it 'should multiply note duration by ratio' do
|
107
|
-
note = Note::quarter
|
108
|
-
note.stretch!(2)
|
100
|
+
note = Note::quarter.stretch(2)
|
109
101
|
note.duration.should eq(Rational(1,2))
|
110
102
|
|
111
|
-
note = Note::quarter
|
112
|
-
note.stretch!(Rational(1,2))
|
103
|
+
note = Note::quarter.stretch(Rational(1,2))
|
113
104
|
note.duration.should eq(Rational(1,8))
|
114
|
-
note = Note::quarter
|
115
|
-
note.stretch!(2)
|
116
|
-
note.duration.should eq(Rational(1,2))
|
117
|
-
end
|
118
|
-
|
119
|
-
it 'should return self' do
|
120
|
-
note = Note::quarter
|
121
|
-
note.stretch!(1).should be note
|
122
105
|
end
|
123
106
|
end
|
124
107
|
|
data/spec/model/pitch_spec.rb
CHANGED
@@ -40,6 +40,23 @@ describe Pitch do
|
|
40
40
|
p.total_semitone.should be case_data[:total_semitone]
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
describe '#diff' do
|
45
|
+
it 'should return the difference between the given pitch, in semitones' do
|
46
|
+
C5.diff(C4).should eq(12)
|
47
|
+
C5.diff(D5).should eq(-2)
|
48
|
+
D5.diff(C5).should eq(2)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#transpose' do
|
53
|
+
it 'should add the given interval to total semitones' do
|
54
|
+
[0,1,2,5,12,13,-1,-2,-5,-12,-13].each do |interval|
|
55
|
+
pitch = Eb3.transpose(interval)
|
56
|
+
pitch.diff(Eb3).should eq(interval)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
43
60
|
|
44
61
|
describe '.from_ratio' do
|
45
62
|
it 'should return a Pitch with given ratio' do
|
@@ -90,38 +107,6 @@ describe Pitch do
|
|
90
107
|
p3.should be > p1
|
91
108
|
p2.should be > p1
|
92
109
|
end
|
93
|
-
|
94
|
-
it "should be addable and subtractable with other pitches" do
|
95
|
-
p1 = Pitch.new semitone: 1
|
96
|
-
p2 = Pitch.new semitone: 2
|
97
|
-
p3 = Pitch.new semitone: 3
|
98
|
-
|
99
|
-
(p1 + p2).should eq(Pitch.new semitone: 3)
|
100
|
-
(p1 + p3).should eq(Pitch.new semitone: 4)
|
101
|
-
(p2 + p3).should eq(Pitch.new semitone: 5)
|
102
|
-
|
103
|
-
(p1 - p2).should eq(Pitch.new semitone: -1)
|
104
|
-
(p1 - p3).should eq(Pitch.new semitone: -2)
|
105
|
-
(p2 - p3).should eq(Pitch.new semitone: -1)
|
106
|
-
(p3 - p2).should eq(Pitch.new semitone: 1)
|
107
|
-
(p3 - p1).should eq(Pitch.new semitone: 2)
|
108
|
-
end
|
109
|
-
|
110
|
-
it "should be addable and subtractable with integers" do
|
111
|
-
p1 = Pitch.new semitone: 1
|
112
|
-
p2 = Pitch.new semitone: 2
|
113
|
-
p3 = Pitch.new semitone: 3
|
114
|
-
|
115
|
-
(p1 + 2).should eq(Pitch.new semitone: 3)
|
116
|
-
(p1 + 3).should eq(Pitch.new semitone: 4)
|
117
|
-
(p2 + 3).should eq(Pitch.new semitone: 5)
|
118
|
-
|
119
|
-
(p1 - 2).should eq(Pitch.new semitone: -1)
|
120
|
-
(p1 - 3).should eq(Pitch.new semitone: -2)
|
121
|
-
(p2 - 3).should eq(Pitch.new semitone: -1)
|
122
|
-
(p3 - 2).should eq(Pitch.new semitone: 1)
|
123
|
-
(p3 - 1).should eq(Pitch.new semitone: 2)
|
124
|
-
end
|
125
110
|
|
126
111
|
it "should have freq of 440 for A4" do
|
127
112
|
a4 = Pitch.new octave: 4, semitone: 9
|