aubio 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: