aubio 0.2.0 → 0.2.1
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/README.md +16 -8
- data/lib/aubio.rb +67 -2
- data/lib/aubio/beats.rb +75 -0
- data/lib/aubio/onsets.rb +25 -12
- data/lib/aubio/pitches.rb +75 -0
- data/lib/aubio/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1acc7f11dab076c36e450cf7a1d4ec1c96aed13f
|
4
|
+
data.tar.gz: 8a47f61e2797d48ea428914328ba32e0f772b630
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf26ded45b899acf39ec1b82e6a373506df549e18f5acc441265e9d914943053988d09fb4c5ce648e184c18c99830ff0192fdbd8e2b9de72f99295dd850d0620
|
7
|
+
data.tar.gz: fb09d46d4a1e70f2a6a5d73b8e5a6f69934b8dc3fc9e9e3a555576c3826290ce853d776fcad1f962f0b48b582fcbc78b33b9a6a23e727b2a3907be267ba2afd3
|
data/README.md
CHANGED
@@ -42,8 +42,8 @@ From there you can access the following:
|
|
42
42
|
|
43
43
|
```
|
44
44
|
my_file.onsets # list of extracted onsets
|
45
|
-
|
46
|
-
|
45
|
+
my_file.pitches # list of extracted pitches
|
46
|
+
my_file.beats # where one would tap their foot
|
47
47
|
# NOT YET IMPLEMENTED # my_file.notes # list of onsets with pitches
|
48
48
|
# NOT YET IMPLEMENTED # my_file.silences # list of silent regions
|
49
49
|
# NOT YET IMPLEMENTED # my_file.mel_frequency_cepstral_coefficients # list of mfccs
|
@@ -51,15 +51,25 @@ my_file.onsets # list of extracted onsets
|
|
51
51
|
|
52
52
|
All of these are Ruby `Enumerator`s which means they'll respond to `.next`, and so on. If you want the whole list all at once call `.to_a` on it.
|
53
53
|
|
54
|
+
### Avoiding memory leaks
|
55
|
+
|
56
|
+
The underlying C library allocates some memory so to clean this up you'll need to run
|
57
|
+
|
58
|
+
my_file.close
|
59
|
+
|
60
|
+
to free it up.
|
61
|
+
|
54
62
|
## Data format
|
55
63
|
|
56
64
|
Each "event" that `aubio` describes is represented as a `Hash` like so:
|
57
65
|
|
58
66
|
```
|
59
|
-
my_file.onsets.first #=> {s: 0.0, ms: 0.0}
|
67
|
+
my_file.onsets.first #=> {s: 0.0, ms: 0.0, start: 1, end: 0}
|
60
68
|
```
|
61
69
|
|
62
|
-
`s` and `ms` refer to seconds and milliseconds respectively.
|
70
|
+
`s` and `ms` refer to seconds and milliseconds respectively.
|
71
|
+
|
72
|
+
`start: 1` and `end: 1` are special events that describe the start and the end of the audio file respectively. Whilst the `start` event at `0.0s` is usually an onset, the `end` is a convenience added by the Ruby wrapper to allow for easier slicing of sound files into samples.
|
63
73
|
|
64
74
|
### Still to implement
|
65
75
|
|
@@ -88,10 +98,8 @@ Aubio.open("/path/to/audio/file", sample_rate: 44100)
|
|
88
98
|
## Bugs / Still to do
|
89
99
|
|
90
100
|
* better tests
|
91
|
-
*
|
92
|
-
*
|
93
|
-
* use `Offsets` class as a basis to implement the other functions
|
94
|
-
* implement class level methods for "bpm"
|
101
|
+
* use `Onsets` class as a basis to implement the other functions
|
102
|
+
* improve accuracy of bpm - seems to be consistently too fast on things I've tried
|
95
103
|
* look into streaming results for live inputs
|
96
104
|
|
97
105
|
## Development
|
data/lib/aubio.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
require_relative "aubio/version"
|
2
2
|
require_relative "aubio/api"
|
3
3
|
require_relative "aubio/onsets"
|
4
|
+
require_relative "aubio/pitches"
|
5
|
+
require_relative "aubio/beats"
|
4
6
|
|
5
7
|
module Aubio
|
6
|
-
class
|
7
|
-
|
8
|
+
class AubioException < Exception; end
|
9
|
+
class FileNotFound < AubioException; end
|
10
|
+
class AlreadyClosed < AubioException; end
|
11
|
+
class InvalidAudioInput < AubioException; end
|
8
12
|
|
13
|
+
class Base
|
9
14
|
def initialize(path, params)
|
10
15
|
raise FileNotFound unless File.file?(path)
|
11
16
|
|
@@ -14,11 +19,71 @@ module Aubio
|
|
14
19
|
|
15
20
|
@source = Api.new_aubio_source(path, sample_rate, hop_size)
|
16
21
|
@params = params
|
22
|
+
|
23
|
+
check_for_valid_audio_source(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def close
|
27
|
+
Api.del_aubio_source(@source)
|
28
|
+
@is_closed = true
|
17
29
|
end
|
18
30
|
|
19
31
|
def onsets
|
32
|
+
check_for_closed
|
33
|
+
|
20
34
|
Onsets.new(@source, @params).each
|
21
35
|
end
|
36
|
+
|
37
|
+
def pitches
|
38
|
+
check_for_closed
|
39
|
+
|
40
|
+
Pitches.new(@source, @params).each
|
41
|
+
end
|
42
|
+
|
43
|
+
def beats
|
44
|
+
check_for_closed
|
45
|
+
|
46
|
+
Beats.new(@source, @params).each
|
47
|
+
end
|
48
|
+
|
49
|
+
def bpm
|
50
|
+
check_for_closed
|
51
|
+
|
52
|
+
beat_locations = Beats.new(@source, @params).each.to_a
|
53
|
+
beat_periods = beat_locations.each_cons(2).map {|a,b| b[:s] - a[:s] }
|
54
|
+
|
55
|
+
# use interquartile median to discourage outliers
|
56
|
+
s = beat_periods.length
|
57
|
+
qrt_lower_idx = (s/4.0).floor
|
58
|
+
qrt_upper_idx = qrt_lower_idx * 3
|
59
|
+
interquartile_beat_periods = beat_periods[qrt_lower_idx..qrt_upper_idx]
|
60
|
+
|
61
|
+
# Calculate median
|
62
|
+
iqs = interquartile_beat_periods.length
|
63
|
+
|
64
|
+
# debug
|
65
|
+
interquartile_beat_periods.sort.each {|x| puts x }
|
66
|
+
|
67
|
+
iq_median_beat_period = interquartile_beat_periods.sort[(iqs/2.0).floor() - 1]
|
68
|
+
60.0 / iq_median_beat_period
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def check_for_closed
|
73
|
+
raise AlreadyClosed if @is_closed
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_for_valid_audio_source(path)
|
77
|
+
begin
|
78
|
+
@source.read_pointer
|
79
|
+
rescue FFI::NullPointerError
|
80
|
+
raise InvalidAudioInput.new(%Q{
|
81
|
+
|
82
|
+
Couldn't read file at #{path}
|
83
|
+
Did you install aubio with libsndfile support?
|
84
|
+
})
|
85
|
+
end
|
86
|
+
end
|
22
87
|
end
|
23
88
|
end
|
24
89
|
|
data/lib/aubio/beats.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
module Aubio
|
2
|
+
class Beats
|
3
|
+
|
4
|
+
def initialize(aubio_source, params)
|
5
|
+
# TODO: cleanup param dups
|
6
|
+
@sample_rate = params[:sample_rate] || 44100
|
7
|
+
@window_size = params[:window_size] || 1024
|
8
|
+
@hop_size = params[:hop_size] || 512
|
9
|
+
|
10
|
+
@source = aubio_source
|
11
|
+
@tempo = Api.new_aubio_tempo('specdiff', @window_size, @hop_size, @sample_rate)
|
12
|
+
|
13
|
+
# create output for source
|
14
|
+
@sample_buffer = Api.new_fvec(@hop_size)
|
15
|
+
# create output for beat
|
16
|
+
@out_fvec = Api.new_fvec(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
return enum_for(:each) unless block_given?
|
21
|
+
|
22
|
+
total_frames_counter = 0
|
23
|
+
read_buffer = FFI::MemoryPointer.new(:int)
|
24
|
+
|
25
|
+
loop do
|
26
|
+
# Perform tempo calculation
|
27
|
+
Api.aubio_source_do(@source, @sample_buffer, read_buffer)
|
28
|
+
Api.aubio_tempo_do(@tempo, @sample_buffer, @out_fvec)
|
29
|
+
|
30
|
+
# Retrieve result
|
31
|
+
is_beat = Api.fvec_get_sample(@out_fvec, 0)
|
32
|
+
no_of_bytes_read = read_buffer.read_int
|
33
|
+
total_frames_counter += no_of_bytes_read
|
34
|
+
|
35
|
+
if is_beat > 0.0
|
36
|
+
tempo_seconds = Api.aubio_tempo_get_last_s(@tempo)
|
37
|
+
tempo_milliseconds = Api.aubio_tempo_get_last_ms(@tempo)
|
38
|
+
tempo_confidence = Api.aubio_tempo_get_confidence(@tempo)
|
39
|
+
|
40
|
+
output = {
|
41
|
+
:confidence => tempo_confidence,
|
42
|
+
:s => tempo_seconds,
|
43
|
+
:ms => tempo_milliseconds,
|
44
|
+
:start => (tempo_seconds == 0.0 ? 1 : 0),
|
45
|
+
:end => 0
|
46
|
+
}
|
47
|
+
yield output
|
48
|
+
end
|
49
|
+
|
50
|
+
if no_of_bytes_read != @hop_size
|
51
|
+
# there's no more audio to look at
|
52
|
+
|
53
|
+
# Let's output one last tempo to mark the end of the file
|
54
|
+
total_time = total_frames_counter.to_f / @sample_rate.to_f
|
55
|
+
output = {
|
56
|
+
:confidence => 1.0,
|
57
|
+
:s => total_time,
|
58
|
+
:ms => total_time/1000.0,
|
59
|
+
:start => 0,
|
60
|
+
:end => 1
|
61
|
+
}
|
62
|
+
yield output
|
63
|
+
|
64
|
+
# clean up
|
65
|
+
Api.del_aubio_tempo(@tempo)
|
66
|
+
Api.del_fvec(@sample_buffer)
|
67
|
+
Api.del_fvec(@out_fvec)
|
68
|
+
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/aubio/onsets.rb
CHANGED
@@ -22,37 +22,50 @@ module Aubio
|
|
22
22
|
return enum_for(:each) unless block_given?
|
23
23
|
|
24
24
|
total_frames_counter = 0
|
25
|
-
|
25
|
+
read_buffer = FFI::MemoryPointer.new(:int)
|
26
26
|
|
27
27
|
loop do
|
28
28
|
# Perform onset calculation
|
29
|
-
Api.aubio_source_do(@source, @sample_buffer,
|
29
|
+
Api.aubio_source_do(@source, @sample_buffer, read_buffer)
|
30
30
|
Api.aubio_onset_do(@onset, @sample_buffer, @out_fvec)
|
31
31
|
|
32
32
|
# Retrieve result
|
33
33
|
onset_new_peak = Api.fvec_get_sample(@out_fvec, 0)
|
34
|
+
no_of_bytes_read = read_buffer.read_int
|
35
|
+
total_frames_counter += no_of_bytes_read
|
34
36
|
|
35
37
|
if onset_new_peak > 0.0
|
36
38
|
onset_seconds = Api.aubio_onset_get_last_s(@onset)
|
37
39
|
onset_milliseconds = Api.aubio_onset_get_last_ms(@onset)
|
38
|
-
# TODO: implement relative here
|
39
40
|
output = {
|
40
41
|
:s => onset_seconds,
|
41
|
-
:ms => onset_milliseconds
|
42
|
+
:ms => onset_milliseconds,
|
43
|
+
:start => (onset_seconds == 0.0 ? 1 : 0),
|
44
|
+
:end => 0
|
42
45
|
}
|
43
46
|
yield output
|
44
47
|
end
|
45
48
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
if no_of_bytes_read != @hop_size
|
50
|
+
# there's no more audio to look at
|
51
|
+
|
52
|
+
# Let's output one last onset to mark the end of the file
|
53
|
+
total_time = total_frames_counter.to_f / @sample_rate.to_f
|
54
|
+
output = {
|
55
|
+
:s => total_time,
|
56
|
+
:ms => total_time/1000.0,
|
57
|
+
:start => 0,
|
58
|
+
:end => 1
|
59
|
+
}
|
60
|
+
yield output
|
61
|
+
|
49
62
|
# clean up
|
50
|
-
Api.
|
51
|
-
|
52
|
-
|
63
|
+
Api.del_aubio_onset(@onset)
|
64
|
+
Api.del_fvec(@sample_buffer)
|
65
|
+
Api.del_fvec(@out_fvec)
|
53
66
|
|
54
|
-
|
55
|
-
|
67
|
+
break
|
68
|
+
end
|
56
69
|
end
|
57
70
|
end
|
58
71
|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Aubio
|
2
|
+
class Pitches
|
3
|
+
|
4
|
+
def initialize(aubio_source, params)
|
5
|
+
# TODO: cleanup param dups
|
6
|
+
@sample_rate = params[:sample_rate] || 44100
|
7
|
+
@window_size = params[:window_size] || 1024
|
8
|
+
@hop_size = params[:hop_size] || 512
|
9
|
+
|
10
|
+
@source = aubio_source
|
11
|
+
@pitch = Api.new_aubio_pitch('yin', @window_size, @hop_size, @sample_rate)
|
12
|
+
Api.aubio_pitch_set_unit(@pitch, 'midi')
|
13
|
+
Api.aubio_pitch_set_tolerance(@pitch, 0.8)
|
14
|
+
|
15
|
+
# create output for source
|
16
|
+
@sample_buffer = Api.new_fvec(@hop_size)
|
17
|
+
# create output for pitch and beat
|
18
|
+
@out_fvec = Api.new_fvec(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
def each
|
22
|
+
return enum_for(:each) unless block_given?
|
23
|
+
|
24
|
+
total_frames_counter = 0
|
25
|
+
read_buffer = FFI::MemoryPointer.new(:int)
|
26
|
+
last_pitch = 0
|
27
|
+
|
28
|
+
loop do
|
29
|
+
# Perform pitch calculation
|
30
|
+
Api.aubio_source_do(@source, @sample_buffer, read_buffer)
|
31
|
+
Api.aubio_pitch_do(@pitch, @sample_buffer, @out_fvec)
|
32
|
+
|
33
|
+
# Retrieve result
|
34
|
+
pitch = Api.fvec_get_sample(@out_fvec, 0)
|
35
|
+
confidence = Api.aubio_pitch_get_confidence(@pitch)
|
36
|
+
no_of_bytes_read = read_buffer.read_int
|
37
|
+
total_frames_counter += no_of_bytes_read
|
38
|
+
|
39
|
+
if (last_pitch - pitch).abs >= 1 and confidence > 0.9
|
40
|
+
output = {
|
41
|
+
:pitch => pitch,
|
42
|
+
:confidence => confidence,
|
43
|
+
:start => (total_frames_counter == 0 ? 1 : 0),
|
44
|
+
:end => 0
|
45
|
+
}
|
46
|
+
yield output
|
47
|
+
end
|
48
|
+
|
49
|
+
last_pitch = pitch
|
50
|
+
|
51
|
+
if no_of_bytes_read != @hop_size
|
52
|
+
# there's no more audio to look at
|
53
|
+
|
54
|
+
# Let's output one last pitch to mark the end of the file
|
55
|
+
total_time = total_frames_counter.to_f / @sample_rate.to_f
|
56
|
+
output = {
|
57
|
+
:pitch => pitch,
|
58
|
+
:confidence => confidence,
|
59
|
+
:start => 0,
|
60
|
+
:end => 1
|
61
|
+
}
|
62
|
+
yield output
|
63
|
+
|
64
|
+
# clean up
|
65
|
+
Api.del_aubio_pitch(@pitch)
|
66
|
+
Api.del_fvec(@sample_buffer)
|
67
|
+
Api.del_fvec(@out_fvec)
|
68
|
+
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/aubio/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aubio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Riley
|
@@ -89,7 +89,9 @@ files:
|
|
89
89
|
- bin/setup
|
90
90
|
- lib/aubio.rb
|
91
91
|
- lib/aubio/api.rb
|
92
|
+
- lib/aubio/beats.rb
|
92
93
|
- lib/aubio/onsets.rb
|
94
|
+
- lib/aubio/pitches.rb
|
93
95
|
- lib/aubio/version.rb
|
94
96
|
homepage: https://github.com/xavriley/ruby-aubio
|
95
97
|
licenses:
|