tsunami 0.3.1 → 0.4.0

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.
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