midi_lyrics 0.0.10 → 0.0.11
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.
- checksums.yaml +4 -4
- data/lib/midi_lyrics.rb +68 -22
- data/lib/midi_lyrics/version.rb +1 -1
- data/midi_lyrics.gemspec +1 -1
- data/spec/fixtures/60_tempo.mid +0 -0
- data/spec/fixtures/60_tempo.nwc +0 -0
- data/spec/fixtures/90_tempo.mid +0 -0
- data/spec/fixtures/90_tempo.nwc +0 -0
- data/spec/fixtures/changing_tempo.mid +0 -0
- data/spec/fixtures/changing_tempo.nwc +0 -0
- data/spec/midi_lyrics_spec.rb +63 -7
- metadata +19 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f698931d0fee61f9ab69c4139d59528ae8c7f2a0
|
4
|
+
data.tar.gz: ac95cb42cff2ee743b5d5b0de43fa14b5c2bece8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 736dc598ca49e75a2abf1816c652b4d6795cab42e5a2d7cf7913e49398e9f9cf57e10b1732447244aa61638b1503c0afa0f243dd2131a53d46deb88fe87e67f6
|
7
|
+
data.tar.gz: 51cd277418c0dff15160daad3ba2b56a1c0e3111695c215baeccd93cb6d3fc9d38fd0b1b9705f74c59e3f2269350d6f06b25cfbda44474aac11c192f1f5e730b
|
data/lib/midi_lyrics.rb
CHANGED
@@ -4,28 +4,50 @@ require "midilib"
|
|
4
4
|
module MidiLyrics
|
5
5
|
class FileNotFound < StandardError; end
|
6
6
|
|
7
|
-
class
|
8
|
-
attr_accessor :
|
9
|
-
attr_writer :sequence
|
7
|
+
class Tempo
|
8
|
+
attr_accessor :start, :duration, :tempo
|
10
9
|
|
11
10
|
def initialize(fields = {})
|
12
|
-
self.
|
13
|
-
self.
|
14
|
-
self.duration_in_pulses = fields[:duration_in_pulses]
|
15
|
-
self.text = fields[:text]
|
16
|
-
self.sequence = fields[:sequence]
|
11
|
+
self.start = fields[:start].to_f
|
12
|
+
self.tempo = fields[:tempo].to_f
|
17
13
|
end
|
14
|
+
end
|
18
15
|
|
19
|
-
|
20
|
-
|
16
|
+
class TempoCalculator
|
17
|
+
attr_accessor :tempo_track
|
18
|
+
attr_accessor :sequence
|
19
|
+
|
20
|
+
def initialize(fields = {})
|
21
|
+
self.tempo_track = fields[:tempo_track]
|
22
|
+
self.sequence = fields[:sequence]
|
21
23
|
end
|
22
24
|
|
23
|
-
def
|
24
|
-
|
25
|
+
def calculate(pulses)
|
26
|
+
pulses = pulses.to_f
|
27
|
+
value = 0.0
|
28
|
+
last_tempo = @tempo_track.first
|
29
|
+
@tempo_track.each do |t|
|
30
|
+
if t.start < pulses
|
31
|
+
value += (((t.start - last_tempo.start) / sequence.ppqn.to_f / ::MIDI::Tempo.mpq_to_bpm(last_tempo.tempo)) * 60.0)
|
32
|
+
last_tempo = t
|
33
|
+
end
|
34
|
+
end
|
35
|
+
if last_tempo.start < pulses
|
36
|
+
value += (((pulses - last_tempo.start) / sequence.ppqn.to_f / ::MIDI::Tempo.mpq_to_bpm(last_tempo.tempo)) * 60.0)
|
37
|
+
end
|
38
|
+
value
|
25
39
|
end
|
40
|
+
end
|
26
41
|
|
27
|
-
|
28
|
-
|
42
|
+
class LyricSyllable
|
43
|
+
attr_accessor :text, :start_in_pulses, :start2_in_pulses, :duration_in_pulses
|
44
|
+
attr_accessor :start, :start2, :duration
|
45
|
+
|
46
|
+
def initialize(fields = {})
|
47
|
+
self.start_in_pulses = fields[:start_in_pulses]
|
48
|
+
self.start2_in_pulses = fields[:start2_in_pulses]
|
49
|
+
self.duration_in_pulses = fields[:duration_in_pulses]
|
50
|
+
self.text = fields[:text]
|
29
51
|
end
|
30
52
|
|
31
53
|
def end_in_pulses
|
@@ -48,11 +70,6 @@ module MidiLyrics
|
|
48
70
|
duration: duration
|
49
71
|
}
|
50
72
|
end
|
51
|
-
|
52
|
-
private
|
53
|
-
def format_time(time)
|
54
|
-
@sequence.pulses_to_seconds(time.to_f).round(3)
|
55
|
-
end
|
56
73
|
end
|
57
74
|
|
58
75
|
class Parser
|
@@ -70,6 +87,7 @@ module MidiLyrics
|
|
70
87
|
|
71
88
|
def extract
|
72
89
|
read_sequence_from_file
|
90
|
+
load_tempo_track
|
73
91
|
load_tracks
|
74
92
|
calculate_durations
|
75
93
|
load_lyrics
|
@@ -78,22 +96,41 @@ module MidiLyrics
|
|
78
96
|
remove_lines_trailing_spaces
|
79
97
|
fix_durations
|
80
98
|
remove_repeating unless repeating
|
99
|
+
calculate_seconds
|
81
100
|
@lyrics.collect(&:as_json)
|
82
101
|
end
|
83
102
|
|
84
103
|
private
|
85
104
|
def read_sequence_from_file
|
86
105
|
@sequence = ::MIDI::Sequence.new()
|
87
|
-
File.open(file, "rb") do |
|
106
|
+
File.open(file, "rb") do |file|
|
88
107
|
@sequence.read(file)
|
89
108
|
end
|
90
109
|
@sequence
|
91
110
|
end
|
92
111
|
|
112
|
+
def load_tempo_track
|
113
|
+
@tempo_track = []
|
114
|
+
|
115
|
+
@sequence.tracks[0].each do |event|
|
116
|
+
if event.kind_of?(::MIDI::Tempo)
|
117
|
+
@tempo_track << Tempo.new(
|
118
|
+
start: event.time_from_start,
|
119
|
+
tempo: event.data
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if @tempo_track.size == 0
|
125
|
+
@tempo_track << Tempo.new(start: 0, tempo: ::MIDI::Tempo.bpm_to_mpq(120))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
93
129
|
def load_tracks
|
94
130
|
@lyrics_track = ::MIDI::Track.new(@sequence)
|
95
131
|
@noteon_track = ::MIDI::Track.new(@sequence)
|
96
|
-
|
132
|
+
|
133
|
+
@sequence.tracks[1].each do |event|
|
97
134
|
if event.kind_of?(::MIDI::MetaEvent) && event.meta_type == ::MIDI::META_LYRIC
|
98
135
|
@lyrics_track.events << event
|
99
136
|
end
|
@@ -125,7 +162,6 @@ module MidiLyrics
|
|
125
162
|
[heading_space, letters, trailing_space].each do |text|
|
126
163
|
unless text.nil?
|
127
164
|
@lyrics << LyricSyllable.new(
|
128
|
-
sequence: @sequence,
|
129
165
|
start_in_pulses: event.time_from_start,
|
130
166
|
duration_in_pulses: @durations[event.time_from_start],
|
131
167
|
text: text
|
@@ -199,5 +235,15 @@ module MidiLyrics
|
|
199
235
|
end
|
200
236
|
end
|
201
237
|
end
|
238
|
+
|
239
|
+
def calculate_seconds
|
240
|
+
tempo_calculator = TempoCalculator.new(tempo_track: @tempo_track, sequence: @sequence)
|
241
|
+
|
242
|
+
@lyrics.each do |l|
|
243
|
+
l.start = tempo_calculator.calculate(l.start_in_pulses).round(3)
|
244
|
+
l.start2 = tempo_calculator.calculate(l.start2_in_pulses).round(3)
|
245
|
+
l.duration = (tempo_calculator.calculate(l.start_in_pulses + l.duration_in_pulses) - l.start).round(3)
|
246
|
+
end
|
247
|
+
end
|
202
248
|
end
|
203
249
|
end
|
data/lib/midi_lyrics/version.rb
CHANGED
data/midi_lyrics.gemspec
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/spec/midi_lyrics_spec.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe MidiLyrics do
|
4
|
-
|
4
|
+
FLOAT_PRECISION_ERROR = 0.001
|
5
5
|
HALF_NOTE_DURATION = 0.875
|
6
|
+
QUARTER_NOTE_DURATION = 0.417
|
7
|
+
QUARTER_NOTE_DURATION_90 = 0.555
|
8
|
+
QUARTER_NOTE_DURATION_60 = 0.833
|
6
9
|
|
7
10
|
it "returns an array" do
|
8
11
|
expect(MidiLyrics::Parser.new("spec/fixtures/one_note_one_syllable.mid").extract).to be_kind_of(Array)
|
@@ -31,7 +34,7 @@ describe MidiLyrics do
|
|
31
34
|
expect(
|
32
35
|
MidiLyrics::Parser.new("spec/fixtures/two_notes_one_syllable.mid").extract
|
33
36
|
).to eq([
|
34
|
-
{ text: "Test", start: 0, start2: 0.0, duration: 0.5 + QUARTER_NOTE_DURATION },
|
37
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: 0.5 + QUARTER_NOTE_DURATION },
|
35
38
|
{ text: "\r\n", start: 0.5 + QUARTER_NOTE_DURATION, start2: 0.0, duration: 0.0 }
|
36
39
|
])
|
37
40
|
end
|
@@ -40,7 +43,7 @@ describe MidiLyrics do
|
|
40
43
|
expect(
|
41
44
|
MidiLyrics::Parser.new("spec/fixtures/two_notes_two_syllables.mid").extract
|
42
45
|
).to eq([
|
43
|
-
{ text: "Test", start: 0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
46
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
44
47
|
{ text: "ing", start: 0.5, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
45
48
|
{ text: "\r\n", start: 0.5 + QUARTER_NOTE_DURATION, start2: 0.0, duration: 0.0 }
|
46
49
|
])
|
@@ -50,7 +53,7 @@ describe MidiLyrics do
|
|
50
53
|
expect(
|
51
54
|
MidiLyrics::Parser.new("spec/fixtures/two_notes_two_syllables_dash.mid").extract
|
52
55
|
).to eq([
|
53
|
-
{ text: "Test", start: 0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
56
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
54
57
|
{ text: "ing", start: 0.5, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
55
58
|
{ text: "\r\n", start: 0.5 + QUARTER_NOTE_DURATION, start2: 0.0, duration: 0.0 }
|
56
59
|
])
|
@@ -60,7 +63,7 @@ describe MidiLyrics do
|
|
60
63
|
expect(
|
61
64
|
MidiLyrics::Parser.new("spec/fixtures/two_notes_three_syllables.mid").extract
|
62
65
|
).to eq([
|
63
|
-
{ text: "Hello, test", start: 0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
66
|
+
{ text: "Hello, test", start: 0.0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
64
67
|
{ text: "ing", start: 0.5, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
65
68
|
{ text: "\r\n", start: 0.5 + QUARTER_NOTE_DURATION, start2: 0.0, duration: 0.0 }
|
66
69
|
])
|
@@ -70,7 +73,7 @@ describe MidiLyrics do
|
|
70
73
|
expect(
|
71
74
|
MidiLyrics::Parser.new("spec/fixtures/three_notes_two_syllables.mid").extract
|
72
75
|
).to eq([
|
73
|
-
{ text: "Test", start: 0, start2: 0.0, duration: 0.5 + QUARTER_NOTE_DURATION },
|
76
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: 0.5 + QUARTER_NOTE_DURATION },
|
74
77
|
{ text: "ing", start: 1, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
75
78
|
{ text: "\r\n", start: 1 + QUARTER_NOTE_DURATION, start2: 0.0, duration: 0.0 }
|
76
79
|
])
|
@@ -80,7 +83,7 @@ describe MidiLyrics do
|
|
80
83
|
expect(
|
81
84
|
MidiLyrics::Parser.new("spec/fixtures/three_notes_two_syllables_with_pause.mid").extract
|
82
85
|
).to eq([
|
83
|
-
{ text: "Test", start: 0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
86
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
84
87
|
{ text: "ing", start: 1, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
85
88
|
{ text: "\r\n", start: 1 + QUARTER_NOTE_DURATION, start2: 0.0, duration: 0.0 }
|
86
89
|
])
|
@@ -189,6 +192,40 @@ describe MidiLyrics do
|
|
189
192
|
MidiLyrics::Parser.new("spec/fixtures/complete_example.mid", repeating: true).extract
|
190
193
|
).to eq(parsed_complete_example)
|
191
194
|
end
|
195
|
+
|
196
|
+
it "parses 60_tempo.mid correctly" do
|
197
|
+
expect(
|
198
|
+
MidiLyrics::Parser.new("spec/fixtures/60_tempo.mid").extract
|
199
|
+
).to eq([
|
200
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: QUARTER_NOTE_DURATION_60 },
|
201
|
+
{ text: "ing", start: 1, start2: 0.0, duration: QUARTER_NOTE_DURATION_60 },
|
202
|
+
{ text: "\r\n", start: 1 + QUARTER_NOTE_DURATION_60, start2: 0.0, duration: 0.0 }
|
203
|
+
])
|
204
|
+
end
|
205
|
+
|
206
|
+
it "parses 90_tempo.mid correctly" do
|
207
|
+
expect(
|
208
|
+
MidiLyrics::Parser.new("spec/fixtures/90_tempo.mid").extract
|
209
|
+
).to eq([
|
210
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: QUARTER_NOTE_DURATION_90 + FLOAT_PRECISION_ERROR },
|
211
|
+
{ text: "ing", start: 0.667, start2: 0.0, duration: QUARTER_NOTE_DURATION_90 },
|
212
|
+
{ text: "\r\n", start: 1.222, start2: 0.0, duration: 0.0 }
|
213
|
+
])
|
214
|
+
end
|
215
|
+
|
216
|
+
it "parses changing_tempo.mid correctly" do
|
217
|
+
expect(
|
218
|
+
MidiLyrics::Parser.new("spec/fixtures/changing_tempo.mid").extract
|
219
|
+
).to eq([
|
220
|
+
{ text: "Test", start: 0.0, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
221
|
+
{ text: "ing", start: 0.5, start2: 0.0, duration: QUARTER_NOTE_DURATION },
|
222
|
+
{ text: " ", start: 0.5 + QUARTER_NOTE_DURATION, start2: 0.0, duration: 0.0 },
|
223
|
+
{ text: "One", start: 1.0, start2: 0.0, duration: QUARTER_NOTE_DURATION_60 },
|
224
|
+
{ text: " ", start: 1.0 + QUARTER_NOTE_DURATION_60, start2: 0.0, duration: 0.0 },
|
225
|
+
{ text: "Two", start: 2.0, start2: 0.0, duration: QUARTER_NOTE_DURATION_60 },
|
226
|
+
{ text: "\r\n", start: 2.0 + QUARTER_NOTE_DURATION_60, start2: 0.0, duration: 0.0 }
|
227
|
+
])
|
228
|
+
end
|
192
229
|
end
|
193
230
|
|
194
231
|
context "error handling" do
|
@@ -196,4 +233,23 @@ describe MidiLyrics do
|
|
196
233
|
expect { MidiLyrics::Parser.new("test.mid").extract }.to raise_error(MidiLyrics::FileNotFound)
|
197
234
|
end
|
198
235
|
end
|
236
|
+
|
237
|
+
describe MidiLyrics::TempoCalculator do
|
238
|
+
it "calculates simple tempo" do
|
239
|
+
tempo_calculator = MidiLyrics::TempoCalculator.new(
|
240
|
+
tempo_track: [
|
241
|
+
MidiLyrics::Tempo.new(start: 0, tempo: ::MIDI::Tempo.bpm_to_mpq(1)),
|
242
|
+
MidiLyrics::Tempo.new(start: 10, tempo: ::MIDI::Tempo.bpm_to_mpq(2)),
|
243
|
+
MidiLyrics::Tempo.new(start: 20, tempo: ::MIDI::Tempo.bpm_to_mpq(1))
|
244
|
+
],
|
245
|
+
sequence: double(ppqn: 1)
|
246
|
+
)
|
247
|
+
tempo_calculator.calculate(0).should == 0
|
248
|
+
tempo_calculator.calculate(1).should == 60.0
|
249
|
+
tempo_calculator.calculate(10).should == 600.0
|
250
|
+
tempo_calculator.calculate(11).should == 630.0
|
251
|
+
tempo_calculator.calculate(20).should == 900.0
|
252
|
+
tempo_calculator.calculate(21).should == 960.0
|
253
|
+
end
|
254
|
+
end
|
199
255
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: midi_lyrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mateus Del Bianco
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -56,16 +56,16 @@ dependencies:
|
|
56
56
|
name: midilib
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.0.
|
61
|
+
version: 2.0.4
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - '='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 2.0.
|
68
|
+
version: 2.0.4
|
69
69
|
description: MIDI Lyrics extractor
|
70
70
|
email:
|
71
71
|
- mateus@delbianco.net.br
|
@@ -85,6 +85,12 @@ files:
|
|
85
85
|
- lib/midi_lyrics.rb
|
86
86
|
- lib/midi_lyrics/version.rb
|
87
87
|
- midi_lyrics.gemspec
|
88
|
+
- spec/fixtures/60_tempo.mid
|
89
|
+
- spec/fixtures/60_tempo.nwc
|
90
|
+
- spec/fixtures/90_tempo.mid
|
91
|
+
- spec/fixtures/90_tempo.nwc
|
92
|
+
- spec/fixtures/changing_tempo.mid
|
93
|
+
- spec/fixtures/changing_tempo.nwc
|
88
94
|
- spec/fixtures/complete_example.mid
|
89
95
|
- spec/fixtures/complete_example.nwc
|
90
96
|
- spec/fixtures/one_note_one_syllable.mid
|
@@ -131,11 +137,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
137
|
version: '0'
|
132
138
|
requirements: []
|
133
139
|
rubyforge_project:
|
134
|
-
rubygems_version: 2.0.
|
140
|
+
rubygems_version: 2.0.6
|
135
141
|
signing_key:
|
136
142
|
specification_version: 4
|
137
143
|
summary: Extracts lyrics with timing from MIDI files
|
138
144
|
test_files:
|
145
|
+
- spec/fixtures/60_tempo.mid
|
146
|
+
- spec/fixtures/60_tempo.nwc
|
147
|
+
- spec/fixtures/90_tempo.mid
|
148
|
+
- spec/fixtures/90_tempo.nwc
|
149
|
+
- spec/fixtures/changing_tempo.mid
|
150
|
+
- spec/fixtures/changing_tempo.nwc
|
139
151
|
- spec/fixtures/complete_example.mid
|
140
152
|
- spec/fixtures/complete_example.nwc
|
141
153
|
- spec/fixtures/one_note_one_syllable.mid
|