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 +6 -0
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/ext/extconf.rb +11 -0
- data/ext/snd_analytics.c +48 -0
- data/ext/snd_analytics.h +7 -0
- data/ext/tsunami_ext.c +51 -0
- data/lib/tsunami.rb +17 -59
- data/tsunami.gemspec +7 -6
- metadata +12 -23
- data/lib/tsunami/bucket.rb +0 -39
data/.gitignore
CHANGED
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.
|
1
|
+
0.4.0
|
data/ext/extconf.rb
ADDED
@@ -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'
|
data/ext/snd_analytics.c
ADDED
@@ -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
|
+
}
|
data/ext/snd_analytics.h
ADDED
data/ext/tsunami_ext.c
ADDED
@@ -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
|
+
}
|
data/lib/tsunami.rb
CHANGED
@@ -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 '
|
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
|
-
|
12
|
-
end
|
8
|
+
raise Errno::ENOENT.new(audio_file) unless File.exist? audio_file
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
:width => options[:width] || 100,
|
17
|
-
:height => options[:height] || 50
|
18
|
-
}
|
10
|
+
@file_name = audio_file
|
11
|
+
end
|
19
12
|
|
20
|
-
|
21
|
-
|
22
|
-
|
13
|
+
def create_waveform(image_file, width, height)
|
14
|
+
self.width = width
|
15
|
+
self.height = height
|
23
16
|
|
24
|
-
|
25
|
-
end
|
17
|
+
values = TsunamiC.get_stats(@file_name, width)
|
26
18
|
|
27
|
-
|
28
|
-
create_buckets versions
|
29
|
-
fill_buckets
|
19
|
+
canvas = draw_waveform(values)
|
30
20
|
|
31
|
-
|
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
|
38
|
-
gc = build_graph
|
24
|
+
def draw_waveform(values)
|
25
|
+
gc = build_graph(values)
|
39
26
|
|
40
|
-
canvas = Magick::Image.new(
|
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
|
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 =
|
39
|
+
mid = height / 2
|
78
40
|
|
79
|
-
|
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
|
data/tsunami.gemspec
CHANGED
@@ -5,13 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{tsunami}
|
8
|
-
s.version = "0.
|
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-
|
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
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
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-
|
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: &
|
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: *
|
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
|
data/lib/tsunami/bucket.rb
DELETED
@@ -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
|