music-transcription 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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