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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1b214156bf7a967baf0ab368060f8cef89ec8b3b
4
- data.tar.gz: 3b2943f248a6c8d2657fa8640fe328742e185a1b
3
+ metadata.gz: 1acc7f11dab076c36e450cf7a1d4ec1c96aed13f
4
+ data.tar.gz: 8a47f61e2797d48ea428914328ba32e0f772b630
5
5
  SHA512:
6
- metadata.gz: 9b6c9336bcb1a83d00cdde450e26e16026526d66c6fe3189c647cd98adbedd7197293942291fc92b68ae38750d42f87d4110d363626af2ed9e52cbb9ad944a41
7
- data.tar.gz: 67e7947c7dac37ba5f30c9f6cc534f31ed46bdd9c46a82861b17ac052838b4d80723e1ca98e2c3127de8e0bf6edd8b40fa9ee1b1881b4982af613c85a547041d
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
- # NOT YET IMPLEMENTED # my_file.pitches # list of extracted pitches
46
- # NOT YET IMPLEMENTED # my_file.beats # where one would tap their foot
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
- * add end of file as optional onset for slicing purposes
92
- * implement relative timing in offset
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
@@ -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 Base
7
- class FileNotFound < Exception; end
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
 
@@ -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
@@ -22,37 +22,50 @@ module Aubio
22
22
  return enum_for(:each) unless block_given?
23
23
 
24
24
  total_frames_counter = 0
25
- tmp_read = FFI::MemoryPointer.new(:int)
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, tmp_read)
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
- read = tmp_read.read_int
47
- total_frames_counter += read
48
- if(read != @hop_size) then
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.del_aubio_source(@source)
51
- Api.del_fvec(@sample_buffer)
52
- Api.del_fvec(@out_fvec)
63
+ Api.del_aubio_onset(@onset)
64
+ Api.del_fvec(@sample_buffer)
65
+ Api.del_fvec(@out_fvec)
53
66
 
54
- break
55
- end
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
@@ -1,3 +1,3 @@
1
1
  module Aubio
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
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.0
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: