tsunami 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -19,8 +19,14 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+ *.log
22
23
  lib/*.mp3
23
24
  lib/*.wav
24
25
  lib/*.ogg
25
26
  lib/*.png
26
27
  lib/test.rb
28
+ ext/*.wav
29
+ ext/test.rb
30
+ ext/*.o
31
+ ext/*.bundle
32
+ ext/Makefile
data/Rakefile CHANGED
@@ -11,7 +11,6 @@ begin
11
11
  gem.homepage = "http://github.com/zmack/tsunami"
12
12
  gem.authors = ["Andrei Bocan"]
13
13
  gem.add_dependency "rmagick", ">= 0"
14
- gem.add_dependency "ruby-audio", ">= 1.0.0"
15
14
  gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
16
15
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
16
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
@@ -0,0 +1,11 @@
1
+ require 'mkmf'
2
+
3
+ $CFLAGS = `pkg-config --cflags sndfile`.chop
4
+ $LDFLAGS = `pkg-config --libs sndfile`.chop
5
+
6
+ unless find_library 'sndfile', 'sf_open'
7
+ raise 'You need to install libsndfile (http://www.mega-nerd.com/libsndfile/)'
8
+ exit
9
+ end
10
+
11
+ create_makefile 'tsunami_ext'
@@ -0,0 +1,48 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+ #include <sndfile.h>
4
+ #include "snd_analytics.h"
5
+
6
+ const FRAMES_IN_BUFFER = 2000;
7
+
8
+ TsunamiRange *get_stats(char *path, short sample_count) {
9
+ SNDFILE *file;
10
+ SF_INFO info;
11
+ float *buffer;
12
+ TsunamiRange *samples;
13
+ unsigned int offset = 0, frames_per_sample;
14
+ short i, buffer_size, read_frames, sample_index = 0;
15
+
16
+ file = sf_open(path, SFM_READ, &info);
17
+ buffer_size = FRAMES_IN_BUFFER * info.channels * sizeof(float);
18
+
19
+ frames_per_sample = info.frames / sample_count;
20
+
21
+ buffer = (float *)malloc(buffer_size);
22
+
23
+ samples = (TsunamiRange *)malloc(sample_count * sizeof(TsunamiRange));
24
+
25
+ for( i = 0; i < sample_count; i++ ) {
26
+ samples[i].min = 1;
27
+ samples[i].max = -1;
28
+ }
29
+
30
+ while ( read_frames = sf_readf_float(file, buffer, FRAMES_IN_BUFFER) ) {
31
+ for( i = 0; i < read_frames; i += info.channels ) {
32
+ sample_index = (i + offset) / frames_per_sample;
33
+
34
+ if (samples[sample_index].min > buffer[i]) {
35
+ samples[sample_index].min = buffer[i];
36
+ }
37
+
38
+ if (samples[sample_index].max < buffer[i]) {
39
+ samples[sample_index].max = buffer[i];
40
+ }
41
+ }
42
+ offset += read_frames;
43
+ }
44
+
45
+ free(buffer);
46
+ sf_close(file);
47
+ return samples;
48
+ }
@@ -0,0 +1,7 @@
1
+
2
+ typedef struct {
3
+ float min;
4
+ float max;
5
+ } TsunamiRange;
6
+
7
+ TsunamiRange *get_stats(char *path, short sample_count);
@@ -0,0 +1,51 @@
1
+ /*Include the Ruby headers and goodies*/
2
+ #include "ruby.h"
3
+ #include "snd_analytics.h"
4
+
5
+ // Defining a space for information and references about the module to be stored internally
6
+ VALUE MyTest = Qnil;
7
+
8
+ // Prototype for the initialization method - Ruby calls this, not you
9
+ void Init_tsunami_ext();
10
+
11
+ // Prototype for our method 'test1' - methods are prefixed by 'method_' here
12
+ VALUE method_get_stats(VALUE self, VALUE path, VALUE samples);
13
+
14
+ // The initialization method for this module
15
+ void Init_tsunami_ext() {
16
+ MyTest = rb_define_module("TsunamiC");
17
+ rb_define_singleton_method(MyTest, "get_stats", method_get_stats, 2);
18
+ }
19
+
20
+ VALUE min_max_hash(float min, float max) {
21
+ VALUE min_symb = ID2SYM(rb_intern("min"));
22
+ VALUE max_symb = ID2SYM(rb_intern("max"));
23
+
24
+ VALUE hash = rb_hash_new();
25
+ rb_hash_aset(hash, min_symb, rb_float_new(min));
26
+ rb_hash_aset(hash, max_symb, rb_float_new(max));
27
+
28
+ return hash;
29
+ }
30
+
31
+ // Our 'test1' method.. it simply returns a value of '10' for now.
32
+ VALUE method_get_stats(VALUE self, VALUE rb_path, VALUE rb_samples) {
33
+ TsunamiRange *values;
34
+ VALUE array, hash;
35
+
36
+ int i;
37
+ char *path = STR2CSTR(rb_path);
38
+ int samples = FIX2INT(rb_samples);
39
+
40
+ array = rb_ary_new();
41
+
42
+ values = get_stats(path, samples);
43
+
44
+ for(i = 0; i < samples; i++) {
45
+ rb_ary_push(array, min_max_hash(values[i].min, values[i].max));
46
+ }
47
+
48
+ free(values);
49
+
50
+ return array;
51
+ }
@@ -1,82 +1,44 @@
1
- #this class was inspired by rjp's awesome http://github.com/rjp/cdcover
2
- require 'rubygems'
3
1
  require 'RMagick'
4
- require 'ruby-audio'
5
- require 'buffer_ext.rb'
6
- require 'tsunami/bucket'
2
+ require 'tsunami_ext'
7
3
 
8
4
  class Tsunami
5
+ attr_accessor :width, :height
9
6
 
10
7
  def initialize audio_file
11
- @audio_file = RubyAudio::Sound.new audio_file
12
- end
8
+ raise Errno::ENOENT.new(audio_file) unless File.exist? audio_file
13
9
 
14
- def create_waveform(image_file, options = {})
15
- dimensions = {
16
- :width => options[:width] || 100,
17
- :height => options[:height] || 50
18
- }
10
+ @file_name = audio_file
11
+ end
19
12
 
20
- versions = {
21
- image_file => dimensions
22
- }
13
+ def create_waveform(image_file, width, height)
14
+ self.width = width
15
+ self.height = height
23
16
 
24
- create_waveforms(versions)
25
- end
17
+ values = TsunamiC.get_stats(@file_name, width)
26
18
 
27
- def create_waveforms(versions)
28
- create_buckets versions
29
- fill_buckets
19
+ canvas = draw_waveform(values)
30
20
 
31
- @buckets.each do |key, bucket|
32
- canvas = draw_waveform_from_bucket(bucket)
33
- canvas.write(key.to_s)
34
- end
21
+ canvas.write(image_file)
35
22
  end
36
23
 
37
- def draw_waveform_from_bucket(bucket)
38
- gc = build_graph bucket
24
+ def draw_waveform(values)
25
+ gc = build_graph(values)
39
26
 
40
- canvas = Magick::Image.new(bucket.width, bucket.height) { self.background_color = 'transparent' }
27
+ canvas = Magick::Image.new(width, height) { self.background_color = 'transparent' }
41
28
 
42
29
  gc.draw(canvas)
43
30
 
44
31
  canvas
45
32
  end
46
33
 
47
- def create_buckets(versions)
48
- @buckets = {}
49
- versions.each_key do |key|
50
- @buckets[key] = Bucket.new(total_frames, versions[key][:width], versions[key][:height])
51
- end
52
- end
53
-
54
- def fill_buckets
55
- @audio_file.seek(0,0)
56
- frames = @audio_file.read("float", total_frames)
57
- frame_index = 0
58
-
59
- frames.each do |channel_frames|
60
- if @audio_file.info.channels > 1
61
- frame = channel_frames[0]
62
- else
63
- frame = channel_frames
64
- end
65
-
66
- @buckets.values.each { |b| b.set(frame_index, frame) }
67
-
68
- frame_index += 1
69
- end
70
- end
71
-
72
- def build_graph bucket
34
+ def build_graph(values)
73
35
  gc = Magick::Draw.new
74
36
  gc.stroke('red')
75
37
  gc.stroke_width(1)
76
38
 
77
- mid = bucket.height / 2
39
+ mid = height / 2
78
40
 
79
- bucket.each_value_with_index do |values, i|
41
+ values.each_with_index do |values, i|
80
42
  low = values[:min]
81
43
  high = values[:max]
82
44
 
@@ -89,8 +51,4 @@ class Tsunami
89
51
  return gc
90
52
  end
91
53
 
92
- def total_frames
93
- @total_frames ||= @audio_file.info.frames
94
- end
95
-
96
54
  end
@@ -5,13 +5,14 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{tsunami}
8
- s.version = "0.3.1"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andrei Bocan"]
12
- s.date = %q{2010-04-19}
12
+ s.date = %q{2010-05-06}
13
13
  s.description = %q{For reals, it draws waveforms, man}
14
14
  s.email = %q{zmaxor@gmail.com}
15
+ s.extensions = ["ext/extconf.rb"]
15
16
  s.extra_rdoc_files = [
16
17
  "LICENSE",
17
18
  "README.rdoc"
@@ -23,9 +24,12 @@ Gem::Specification.new do |s|
23
24
  "README.rdoc",
24
25
  "Rakefile",
25
26
  "VERSION",
27
+ "ext/extconf.rb",
28
+ "ext/snd_analytics.c",
29
+ "ext/snd_analytics.h",
30
+ "ext/tsunami_ext.c",
26
31
  "lib/buffer_ext.rb",
27
32
  "lib/tsunami.rb",
28
- "lib/tsunami/bucket.rb",
29
33
  "test/helper.rb",
30
34
  "test/test_tsunami.rb",
31
35
  "tsunami.gemspec"
@@ -46,16 +50,13 @@ Gem::Specification.new do |s|
46
50
 
47
51
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
52
  s.add_runtime_dependency(%q<rmagick>, [">= 0"])
49
- s.add_runtime_dependency(%q<ruby-audio>, [">= 1.0.0"])
50
53
  s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
51
54
  else
52
55
  s.add_dependency(%q<rmagick>, [">= 0"])
53
- s.add_dependency(%q<ruby-audio>, [">= 1.0.0"])
54
56
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
55
57
  end
56
58
  else
57
59
  s.add_dependency(%q<rmagick>, [">= 0"])
58
- s.add_dependency(%q<ruby-audio>, [">= 1.0.0"])
59
60
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
60
61
  end
61
62
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 3
8
- - 1
9
- version: 0.3.1
7
+ - 4
8
+ - 0
9
+ version: 0.4.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Andrei Bocan
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-19 00:00:00 +03:00
17
+ date: 2010-05-06 00:00:00 +03:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -29,24 +29,10 @@ dependencies:
29
29
  version: "0"
30
30
  type: :runtime
31
31
  version_requirements: *id001
32
- - !ruby/object:Gem::Dependency
33
- name: ruby-audio
34
- prerelease: false
35
- requirement: &id002 !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- segments:
40
- - 1
41
- - 0
42
- - 0
43
- version: 1.0.0
44
- type: :runtime
45
- version_requirements: *id002
46
32
  - !ruby/object:Gem::Dependency
47
33
  name: thoughtbot-shoulda
48
34
  prerelease: false
49
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ requirement: &id002 !ruby/object:Gem::Requirement
50
36
  requirements:
51
37
  - - ">="
52
38
  - !ruby/object:Gem::Version
@@ -54,13 +40,13 @@ dependencies:
54
40
  - 0
55
41
  version: "0"
56
42
  type: :development
57
- version_requirements: *id003
43
+ version_requirements: *id002
58
44
  description: For reals, it draws waveforms, man
59
45
  email: zmaxor@gmail.com
60
46
  executables: []
61
47
 
62
- extensions: []
63
-
48
+ extensions:
49
+ - ext/extconf.rb
64
50
  extra_rdoc_files:
65
51
  - LICENSE
66
52
  - README.rdoc
@@ -71,9 +57,12 @@ files:
71
57
  - README.rdoc
72
58
  - Rakefile
73
59
  - VERSION
60
+ - ext/extconf.rb
61
+ - ext/snd_analytics.c
62
+ - ext/snd_analytics.h
63
+ - ext/tsunami_ext.c
74
64
  - lib/buffer_ext.rb
75
65
  - lib/tsunami.rb
76
- - lib/tsunami/bucket.rb
77
66
  - test/helper.rb
78
67
  - test/test_tsunami.rb
79
68
  - tsunami.gemspec
@@ -1,39 +0,0 @@
1
- require 'forwardable'
2
-
3
- class Tsunami
4
- class Bucket
5
- extend ::Forwardable
6
-
7
- def_delegator :@values, :each, :each_value
8
- def_delegator :@values, :each_with_index, :each_value_with_index
9
-
10
- attr_reader :width, :height, :frames
11
-
12
- def initialize(frames, width, height)
13
- @values = []
14
- @frames = frames
15
- @width = width
16
- @height = height
17
- end
18
-
19
- def set(frame, value)
20
- frame_values = values(frame)
21
-
22
- frame_values[:min] = value if value < frame_values[:min]
23
-
24
- frame_values[:max] = value if value > frame_values[:max]
25
- end
26
-
27
- def frames_per_pixel
28
- @frames_per_pixel ||= frames / @width
29
- end
30
-
31
- def index(frame)
32
- frame / frames_per_pixel
33
- end
34
-
35
- def values(frame)
36
- @values[index(frame)] ||= { :max => -1, :min => 1 }
37
- end
38
- end
39
- end