music-transcription 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.document +3 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -0
  5. data/ChangeLog.rdoc +4 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.rdoc +28 -0
  9. data/Rakefile +54 -0
  10. data/bin/transcribe +176 -0
  11. data/lib/music-transcription.rb +20 -0
  12. data/lib/music-transcription/arrangement.rb +31 -0
  13. data/lib/music-transcription/instrument_config.rb +38 -0
  14. data/lib/music-transcription/interval.rb +66 -0
  15. data/lib/music-transcription/link.rb +115 -0
  16. data/lib/music-transcription/note.rb +156 -0
  17. data/lib/music-transcription/part.rb +128 -0
  18. data/lib/music-transcription/pitch.rb +297 -0
  19. data/lib/music-transcription/pitch_constants.rb +204 -0
  20. data/lib/music-transcription/profile.rb +105 -0
  21. data/lib/music-transcription/program.rb +136 -0
  22. data/lib/music-transcription/score.rb +122 -0
  23. data/lib/music-transcription/tempo.rb +44 -0
  24. data/lib/music-transcription/transition.rb +71 -0
  25. data/lib/music-transcription/value_change.rb +85 -0
  26. data/lib/music-transcription/version.rb +7 -0
  27. data/music-transcription.gemspec +36 -0
  28. data/samples/arrangements/glissando_test.yml +71 -0
  29. data/samples/arrangements/hip.yml +952 -0
  30. data/samples/arrangements/instrument_test.yml +119 -0
  31. data/samples/arrangements/legato_test.yml +237 -0
  32. data/samples/arrangements/make_glissando_test.rb +27 -0
  33. data/samples/arrangements/make_hip.rb +75 -0
  34. data/samples/arrangements/make_instrument_test.rb +34 -0
  35. data/samples/arrangements/make_legato_test.rb +37 -0
  36. data/samples/arrangements/make_missed_connection.rb +72 -0
  37. data/samples/arrangements/make_portamento_test.rb +27 -0
  38. data/samples/arrangements/make_slur_test.rb +37 -0
  39. data/samples/arrangements/make_song1.rb +84 -0
  40. data/samples/arrangements/make_song2.rb +69 -0
  41. data/samples/arrangements/missed_connection.yml +481 -0
  42. data/samples/arrangements/portamento_test.yml +71 -0
  43. data/samples/arrangements/slur_test.yml +237 -0
  44. data/samples/arrangements/song1.yml +640 -0
  45. data/samples/arrangements/song2.yml +429 -0
  46. data/spec/instrument_config_spec.rb +47 -0
  47. data/spec/interval_spec.rb +38 -0
  48. data/spec/link_spec.rb +22 -0
  49. data/spec/musicality_spec.rb +7 -0
  50. data/spec/note_spec.rb +65 -0
  51. data/spec/part_spec.rb +87 -0
  52. data/spec/pitch_spec.rb +139 -0
  53. data/spec/profile_spec.rb +24 -0
  54. data/spec/program_spec.rb +55 -0
  55. data/spec/score_spec.rb +55 -0
  56. data/spec/spec_helper.rb +23 -0
  57. data/spec/transition_spec.rb +13 -0
  58. data/spec/value_change_spec.rb +19 -0
  59. metadata +239 -0
@@ -0,0 +1,38 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Interval do
4
+ before :all do
5
+ @pitch = C4
6
+ end
7
+
8
+ context '.new' do
9
+ it "should assign :pitch parameter given during construction" do
10
+ interval = Interval.new :pitch => @pitch
11
+ interval.pitch.should eq(@pitch)
12
+ end
13
+
14
+ it "should assign :link parameter if given during construction" do
15
+ link = Link.new(:relationship => Link::RELATIONSHIP_TIE, :target_pitch => @pitch)
16
+ interval = Interval.new :pitch => @pitch, :link => link
17
+ interval.link.should eq(link)
18
+ end
19
+ end
20
+
21
+ context '#pitch=' do
22
+ it "should assign pitch" do
23
+ interval = Interval.new :pitch => @pitch
24
+ interval.pitch = Gb4
25
+ interval.pitch.should eq Gb4
26
+ end
27
+ end
28
+
29
+ context '#link=' do
30
+ it "should assign link" do
31
+ link = Link.new(:relationship => Link::RELATIONSHIP_SLUR, :target_pitch => G2)
32
+ interval = Interval.new :pitch => @pitch
33
+ interval.link = link
34
+ interval.link.should eq(link)
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Link do
4
+ context '.new' do
5
+ its(:relationship) { should eq(Link::RELATIONSHIP_NONE) }
6
+ its(:target_pitch) { should eq(Pitch.new) }
7
+ end
8
+
9
+ it 'should assign the given relationship' do
10
+ Link::RELATIONSHIPS.each do |relationship|
11
+ link = Link.new(:relationship => relationship)
12
+ link.relationship.should eq(relationship)
13
+ end
14
+ end
15
+
16
+ it 'should assign the given target pitch' do
17
+ [A0, B0].each do |pitch|
18
+ link = Link.new(:target_pitch => pitch)
19
+ link.target_pitch.should eq(pitch)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Music::Transcription do
4
+ it "should have a VERSION constant" do
5
+ subject.const_get('VERSION').should_not be_empty
6
+ end
7
+ end
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Note do
4
+ before :all do
5
+ @pitch = C4
6
+ end
7
+
8
+ context '.new' do
9
+ it 'should assign :duration that is given during construction' do
10
+ note = Note.new :duration => 2
11
+ note.duration.should eq(2)
12
+ end
13
+
14
+ it "should assign :sustain, :attack, and :separation parameters if given during construction" do
15
+ note = Note.new :duration => 2, :sustain => 0.1, :attack => 0.2, :separation => 0.3
16
+ note.sustain.should eq(0.1)
17
+ note.attack.should eq(0.2)
18
+ note.separation.should eq(0.3)
19
+ end
20
+
21
+ it 'should have no intervals if not given' do
22
+ Note.new(:duration => 2).intervals.should be_empty
23
+ end
24
+
25
+ it 'should assign intervals when given' do
26
+ intervals = [
27
+ Interval.new(:pitch => C2),
28
+ Interval.new(:pitch => D2),
29
+ ]
30
+ Note.new(:duration => 2, :intervals => intervals).intervals.should eq(intervals)
31
+ end
32
+ end
33
+
34
+ context '#duration=' do
35
+ it 'should assign duration' do
36
+ note = Note.new :pitch => @pitch, :duration => 2
37
+ note.duration = 3
38
+ note.duration.should eq 3
39
+ end
40
+ end
41
+
42
+ context '#sustain=' do
43
+ it "should assign sustain" do
44
+ note = Note.new :pitch => @pitch, :duration => 2
45
+ note.sustain = 0.123
46
+ note.sustain.should eq 0.123
47
+ end
48
+ end
49
+
50
+ context '#attack=' do
51
+ it "should assign attack" do
52
+ note = Note.new :pitch => @pitch, :duration => 2
53
+ note.attack = 0.123
54
+ note.attack.should eq 0.123
55
+ end
56
+ end
57
+
58
+ context '#separation=' do
59
+ it "should assign separation" do
60
+ note = Note.new :pitch => @pitch, :duration => 2
61
+ note.separation = 0.123
62
+ note.separation.should eq 0.123
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,87 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Part do
4
+ context '.new' do
5
+ its(:offset) { should eq(0) }
6
+ its(:notes) { should be_empty }
7
+
8
+ it "should assign loudness_profile profile given during construction" do
9
+ loudness_profile = Profile.new(
10
+ :start_value => 0.5,
11
+ :value_changes => {
12
+ 1.0 => linear_change(1.0, 2.0)
13
+ }
14
+ )
15
+ part = Part.new :loudness_profile => loudness_profile
16
+ part.loudness_profile.should eq(loudness_profile)
17
+ end
18
+
19
+ it "should assign notes given during construction" do
20
+ notes = [
21
+ Note.new(
22
+ :duration => 0.25,
23
+ :intervals => [
24
+ Interval.new(:pitch => C1),
25
+ Interval.new(:pitch => D1),
26
+ ]
27
+ )
28
+ ]
29
+
30
+ part = Part.new :notes => notes
31
+ part.notes.should eq(notes)
32
+ end
33
+ end
34
+ end
35
+
36
+ describe PartFile do
37
+ describe '.new' do
38
+ before :all do
39
+ @part_hash = {
40
+ :notes => [
41
+ Note.new(
42
+ :duration => 0.25,
43
+ :intervals => [
44
+ Interval.new(:pitch => C1),
45
+ Interval.new(:pitch => D1) ]
46
+ ),
47
+ Note.new(
48
+ :duration => 0.125,
49
+ :intervals => [
50
+ Interval.new(:pitch => E2) ]
51
+ ),
52
+ ],
53
+ :loudness_profile => Profile.new(
54
+ :start_value => 0.6
55
+ )
56
+ }
57
+
58
+ @path = 'temp.yml'
59
+ end
60
+
61
+ context 'hash of part stored in YAML file' do
62
+ it 'should load part from file' do
63
+ File.open(@path, 'w') do |file|
64
+ file.write @part_hash.to_yaml
65
+ end
66
+ part = Part.new @part_hash
67
+ part_from_file = PartFile.new(:file_path => @path)
68
+ part_from_file.should eq part
69
+ end
70
+ end
71
+
72
+ context 'part stored in YAML file' do
73
+ it 'should load part from file' do
74
+ part = Part.new @part_hash
75
+ File.open(@path, 'w') do |file|
76
+ file.write part.to_yaml
77
+ end
78
+ part_from_file = PartFile.new(:file_path => @path)
79
+ part_from_file.should eq part
80
+ end
81
+ end
82
+
83
+ after :all do
84
+ File.delete @path
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,139 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Pitch do
4
+
5
+ before :each do
6
+ @cases =
7
+ [
8
+ { :octave => 1, :semitone => 0, :cent => 0, :ratio => 2.0, :total_cent => 1200 },
9
+ { :octave => 2, :semitone => 0, :cent => 0, :ratio => 4.0, :total_cent => 2400 },
10
+ { :octave => 1, :semitone => 6, :cent => 0, :ratio => 2.8284, :total_cent => 1800 },
11
+ { :octave => 2, :semitone => 6, :cent => 0, :ratio => 5.6569, :total_cent => 3000 },
12
+ { :octave => 1, :semitone => 6, :cent => 20, :ratio => 2.8613, :total_cent => 1820 },
13
+ { :octave => 2, :semitone => 6, :cent => 20, :ratio => 5.7226, :total_cent => 3020 },
14
+ { :octave => 3, :semitone => 7, :cent => 77, :ratio => 12.5316, :total_cent => 4377 },
15
+ { :octave => -1, :semitone => 0, :cent => 0, :ratio => 0.5, :total_cent => -1200 },
16
+ { :octave => -2, :semitone => 0, :cent => 0, :ratio => 0.25, :total_cent => -2400 },
17
+ { :octave => -2, :semitone => 7, :cent => 55, :ratio => 0.3867, :total_cent => -1645 },
18
+ { :octave => -1, :semitone => 9, :cent => 23, :ratio => 0.8521, :total_cent => -277 },
19
+ ]
20
+ end
21
+
22
+ it "should be constructible with no parameters (no error raised)" do
23
+ lambda { Pitch.new }.should_not raise_error
24
+ end
25
+
26
+ it "should be hash-makeable" do
27
+ obj = Pitch.new :octave => 4, :semitone => 3
28
+ obj.octave.should eq(4)
29
+ obj.semitone.should eq(3)
30
+ obj.cent.should eq(0)
31
+ end
32
+
33
+ it "should use default octave, semitone, and cent in none is given" do
34
+ p = Pitch.new
35
+ p.ratio.should be_within(0.01).of(1.0)
36
+ p.total_cent.should eq(0)
37
+ end
38
+
39
+ it "should use the octave, semitone, and cent given during construction" do
40
+ @cases.each do |case_data|
41
+ p = Pitch.new :octave => case_data[:octave], :semitone => case_data[:semitone], :cent => case_data[:cent]
42
+ p.ratio.should be_within(0.01).of case_data[:ratio]
43
+ p.total_cent.should be case_data[:total_cent]
44
+ end
45
+ end
46
+
47
+ it "should allow setting by ratio" do
48
+ @cases.each do |case_data|
49
+ p = Pitch.new
50
+ p.ratio = case_data[:ratio]
51
+
52
+ p.octave.should eq case_data[:octave]
53
+ p.semitone.should eq case_data[:semitone]
54
+ p.cent.should eq case_data[:cent]
55
+ p.total_cent.should eq case_data[:total_cent]
56
+ end
57
+ end
58
+
59
+ it "should setting by total_cent" do
60
+ @cases.each do |case_data|
61
+ p = Pitch.new
62
+ p.total_cent = case_data[:total_cent]
63
+
64
+ p.octave.should eq case_data[:octave]
65
+ p.semitone.should eq case_data[:semitone]
66
+ p.cent.should eq case_data[:cent]
67
+ p.total_cent.should eq case_data[:total_cent]
68
+ end
69
+ end
70
+
71
+ it "should be comparable to other pitches" do
72
+ p1 = Pitch.new :semitone => 1
73
+ p2 = Pitch.new :semitone => 2
74
+ p3 = Pitch.new :semitone => 3
75
+
76
+ p1.should eq(Pitch.new :semitone => 1)
77
+ p2.should eq(Pitch.new :semitone => 2)
78
+ p3.should eq(Pitch.new :semitone => 3)
79
+
80
+ p1.should be < p2
81
+ p1.should be < p3
82
+ p2.should be < p3
83
+ p3.should be > p2
84
+ p3.should be > p1
85
+ p2.should be > p1
86
+ end
87
+
88
+ it "should be addable and subtractable with other pitches" do
89
+ p1 = Pitch.new :semitone => 1
90
+ p2 = Pitch.new :semitone => 2
91
+ p3 = Pitch.new :semitone => 3
92
+
93
+ (p1 + p2).should eq(Pitch.new :semitone => 3)
94
+ (p1 + p3).should eq(Pitch.new :semitone => 4)
95
+ (p2 + p3).should eq(Pitch.new :semitone => 5)
96
+
97
+ (p1 - p2).should eq(Pitch.new :semitone => -1)
98
+ (p1 - p3).should eq(Pitch.new :semitone => -2)
99
+ (p2 - p3).should eq(Pitch.new :semitone => -1)
100
+ (p3 - p2).should eq(Pitch.new :semitone => 1)
101
+ (p3 - p1).should eq(Pitch.new :semitone => 2)
102
+ end
103
+
104
+ it "should have freq of 440 for A4" do
105
+ a4 = Pitch.new :octave => 4, :semitone => 9
106
+ a4.freq.should be_within(0.01).of(440.0)
107
+ end
108
+
109
+ context 'String#to_pitch' do
110
+ it 'should create a Pitch object that matches the musical note' do
111
+ {
112
+ "Ab2" => Pitch.new(:octave => 2, :semitone => 8),
113
+ "C0" => Pitch.new(:octave => 0, :semitone => 0),
114
+ "db4" => Pitch.new(:octave => 4, :semitone => 1),
115
+ "F#12" => Pitch.new(:octave => 12, :semitone => 6),
116
+ "E#7" => Pitch.new(:octave => 7, :semitone => 5),
117
+ "G9" => Pitch.new(:octave => 9, :semitone => 7),
118
+ "Bb10" => Pitch.new(:octave => 10, :semitone => 10),
119
+ }.each do |str, expected_pitch|
120
+ str.to_pitch.should eq(expected_pitch)
121
+ end
122
+ end
123
+ end
124
+
125
+ context '.make_from_freq' do
126
+ it 'should make a pitch whose freq is approximately the given freq' do
127
+ one_cent = Pitch.new(:cent => 1)
128
+ [1.0, 25.0, 200.0, 3500.0].each do |given_freq|
129
+ pitch = Pitch.make_from_freq given_freq
130
+ freq = pitch.freq
131
+ if freq > given_freq
132
+ (freq / given_freq).should be < one_cent.ratio
133
+ elsif freq < given_freq
134
+ (given_freq / freq).should be < one_cent.ratio
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Profile do
4
+
5
+ context '.new' do
6
+ it "should assign start value given during construction" do
7
+ p = Profile.new(:start_value => 0.2)
8
+ p.start_value.should eq(0.2)
9
+ end
10
+
11
+ it "should assign settings given during construction" do
12
+ p = Profile.new(
13
+ :start_value => 0.2,
14
+ :value_changes => {
15
+ 1.0 => ValueChange.new(:value => 0.5),
16
+ 1.5 => ValueChange.new(:value => 1.0)
17
+ }
18
+ )
19
+ p.value_changes[1.0].value.should eq(0.5)
20
+ p.value_changes[1.5].value.should eq(1.0)
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,55 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Program do
4
+
5
+ it "should assign the segments given during initialization" do
6
+ segments = [ 0.0...5.0, 0.0...4.0, 5.0...10.0 ]
7
+ program = Program.new :segments => segments
8
+ program.segments.should eq(segments.clone)
9
+ end
10
+
11
+ describe "#include?" do
12
+ it "should return true for any offset which would be encountered" do
13
+ segments = [ 0.0...5.0, 0.0...4.0, 5.0...10.0 ]
14
+ program = Program.new :segments => segments
15
+
16
+ [0.0, 4.0, 5.0, 9.999].each do |offset|
17
+ program.include?(offset).should be_true
18
+ end
19
+ end
20
+
21
+ it "should return false for any offset which would not be encountered" do
22
+ segments = [ 0.0...5.0, 0.0...4.0, 5.0...10.0 ]
23
+ program = Program.new :segments => segments
24
+
25
+ [-0.000001, 10.000001].each do |offset|
26
+ program.include?(offset).should be_false
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "#note_elapsed_at" do
32
+ before :each do
33
+ segments = [ 0.0...5.0, 0.0...4.0, 5.0..10.0 ]
34
+ @program = Program.new :segments => segments
35
+ end
36
+
37
+ it "should return 0.0 at program start" do
38
+ @program.note_elapsed_at(@program.start).should eq(0.0)
39
+ end
40
+
41
+ it "should return program length at program stop" do
42
+ @program.note_elapsed_at(@program.stop).should eq(@program.length)
43
+ end
44
+
45
+ it "should return correct note elapsed for any included offset" do
46
+ @program.note_elapsed_at(2.5).should eq(2.5)
47
+ @program.note_elapsed_at(5.5).should eq(9.5)
48
+ end
49
+
50
+ it "should raise error if offset is not included" do
51
+ lambda { @program.note_elapsed_at(-0.000001) }.should raise_error
52
+ lambda { @program.note_elapsed_at(10.000001) }.should raise_error
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Score do
4
+ before :each do
5
+ @parts = { "piano (LH)" => Samples::SAMPLE_PART }
6
+ @program = Program.new :segments => [0...0.75, 0...0.75]
7
+ end
8
+
9
+ describe '.new' do
10
+ context "no args given" do
11
+ let(:score) { Score.new }
12
+ subject { score }
13
+ its(:program) { should eq(Program.new) }
14
+ its(:parts) { should be_empty }
15
+ end
16
+
17
+ context 'args given' do
18
+ it "should assign parts given during construction" do
19
+ score = Score.new :program => @program, :parts => @parts
20
+ score.parts.should eq(@parts)
21
+ end
22
+
23
+ it "should assign program given during construction" do
24
+ score = Score.new :program => @program
25
+ score.program.should eq(@program)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ describe TempoScore do
32
+ before :each do
33
+ @parts = { "piano (LH)" => Samples::SAMPLE_PART }
34
+ @program = Program.new :segments => [0...0.75, 0...0.75]
35
+ @tempo_profile = Profile.new(
36
+ :start_value => tempo(120),
37
+ :value_changes => {
38
+ 0.5 => linear_change(tempo(60), 0.25)
39
+ }
40
+ )
41
+ end
42
+
43
+ describe '.new' do
44
+ it "should assign tempo profile given during construction" do
45
+ score = TempoScore.new :tempo_profile => @tempo_profile
46
+ score.tempo_profile.should eq(@tempo_profile)
47
+ end
48
+
49
+ it "should assign part and program given during construction" do
50
+ score = TempoScore.new :tempo_profile => @tempo_profile, :parts => @parts, :program => @program
51
+ score.parts.should eq(@parts)
52
+ score.program.should eq(@program)
53
+ end
54
+ end
55
+ end