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