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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c9b84cff07242cecae62df47efb69be45145f9e
4
- data.tar.gz: d7338244525a74d8b421a4f7c0ffe12870abb597
3
+ metadata.gz: f698931d0fee61f9ab69c4139d59528ae8c7f2a0
4
+ data.tar.gz: ac95cb42cff2ee743b5d5b0de43fa14b5c2bece8
5
5
  SHA512:
6
- metadata.gz: cdc89a38e654ff98188a167736b4ce91fb9dfa911589bbcaf81f4e34a956a0f6ee148cd1aba5bc828ab3a6c3785fd37b3428761be631ddee5ac661b4dd53cc5d
7
- data.tar.gz: f05d68e09fff457e6d1fae7759a17ec26f743a52cfdd6c948f5e2aa2ab978bd14cf8cf9dbfd40d670def40ea2f117ba70063a2b41412a3e6d61100b3f33f560a
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 LyricSyllable
8
- attr_accessor :text, :start_in_pulses, :start2_in_pulses, :duration_in_pulses
9
- attr_writer :sequence
7
+ class Tempo
8
+ attr_accessor :start, :duration, :tempo
10
9
 
11
10
  def initialize(fields = {})
12
- self.start_in_pulses = fields[:start_in_pulses]
13
- self.start2_in_pulses = fields[:start2_in_pulses]
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
- def start
20
- format_time(start_in_pulses)
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 start2
24
- format_time(start2_in_pulses)
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
- def duration
28
- format_time(duration_in_pulses)
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 | file |
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
- @sequence.tracks[1].each do | event |
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
@@ -1,3 +1,3 @@
1
1
  module MidiLyrics
2
- VERSION = "0.0.10"
2
+ VERSION = "0.0.11"
3
3
  end
data/midi_lyrics.gemspec CHANGED
@@ -22,5 +22,5 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "rspec"
24
24
 
25
- spec.add_dependency("midilib", ["~> 2.0.0"])
25
+ spec.add_dependency("midilib", ["= 2.0.4"])
26
26
  end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,8 +1,11 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe MidiLyrics do
4
- QUARTER_NOTE_DURATION = 0.417
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.10
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-08-02 00:00:00.000000000 Z
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.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.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.3
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