cowboy 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.md +34 -2
- data/Rakefile +38 -0
- data/cowboy.gemspec +5 -3
- data/ext/cowboy/cowboy.c +10 -10
- data/ext/cowboy/cowboy.h +1 -0
- data/ext/cowboy/cowboy_array.c +69 -0
- data/ext/cowboy/cowboy_array.h +7 -0
- data/ext/cowboy/cowboy_complex.c +51 -24
- data/ext/cowboy/cowboy_complex.h +4 -3
- data/lib/cowboy.rb +2 -1
- data/lib/cowboy/fft.rb +5 -1
- data/lib/cowboy/version.rb +1 -1
- data/test/test_array.rb +27 -0
- data/test/test_enumerable.rb +26 -0
- data/test/test_helper.rb +5 -0
- data/test/test_string.rb +22 -0
- metadata +17 -6
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
# Cowboy
|
2
2
|
|
3
|
-
|
3
|
+
This is a Ruby wrapper for [FFTW3](http://fftw.org). It currently only
|
4
|
+
supports 1-dimension transforms, but I'll be adding support for
|
5
|
+
multiple dimensions soon.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
9
|
+
### Prerequisites
|
10
|
+
|
11
|
+
[FFTW3](http://fftw.org) is required. You can generally get it from
|
12
|
+
your OS's package manager (e.g. `sudo port install fftw3`).
|
13
|
+
|
14
|
+
### Gem installation
|
15
|
+
|
7
16
|
Add this line to your application's Gemfile:
|
8
17
|
|
9
18
|
gem 'cowboy'
|
@@ -18,7 +27,30 @@ Or install it yourself as:
|
|
18
27
|
|
19
28
|
## Usage
|
20
29
|
|
21
|
-
|
30
|
+
After installing, you can call `Cowboy::fft_1d` with a 1d array of
|
31
|
+
real numbers. This will return an array of complex numbers. Note that
|
32
|
+
your input array will be blanked out.
|
33
|
+
|
34
|
+
### Windowing
|
35
|
+
|
36
|
+
`Cowboy::fft` will do some windowing before calling `fft_1d`. This
|
37
|
+
currently behaves somewhat unintelligently and discards points around
|
38
|
+
the end of the array (for window size N, it discards N/2 from the
|
39
|
+
beginning and N/2 from the end) to allow for a full window at every
|
40
|
+
point.
|
41
|
+
|
42
|
+
The default is to use a
|
43
|
+
[Hamming Window](http://en.wikipedia.org/wiki/Window_function#Hamming_window)
|
44
|
+
with a window size of 29. This is configurable as the second and third
|
45
|
+
arguments to `fft`. (e.g. `Cowboy::fft(my_array, my_windowing_func,
|
46
|
+
12)`)
|
47
|
+
|
48
|
+
### Shifting
|
49
|
+
|
50
|
+
`Cowboy::Frequencies` is a class that, initialized an array of complex
|
51
|
+
numbers (ideally the output of a call to `fft`), will shift them to
|
52
|
+
their correct order (e.g. `[-nyquist, +nyquist]`). There is also a
|
53
|
+
function `buckets` that will give you the frequency buckets in order.
|
22
54
|
|
23
55
|
## Contributing
|
24
56
|
|
data/Rakefile
CHANGED
@@ -1,2 +1,40 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/clean'
|
6
|
+
|
7
|
+
NAME = 'cowboy'
|
8
|
+
|
9
|
+
# rule to build the extension: this says
|
10
|
+
# that the extension should be rebuilt
|
11
|
+
# after any change to the files in ext
|
12
|
+
file "lib/#{NAME}/#{NAME}.bundle" =>
|
13
|
+
Dir.glob("ext/#{NAME}/*{.rb,.c}") do
|
14
|
+
Dir.chdir("ext/#{NAME}") do
|
15
|
+
# this does essentially the same thing
|
16
|
+
# as what RubyGems does
|
17
|
+
ruby "extconf.rb"
|
18
|
+
sh "make"
|
19
|
+
end
|
20
|
+
cp "ext/#{NAME}/#{NAME}.bundle", "lib/#{NAME}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# make the :test task depend on the shared
|
24
|
+
# object, so it will be built automatically
|
25
|
+
# before running the tests
|
26
|
+
task :test => "lib/#{NAME}/#{NAME}.bundle"
|
27
|
+
|
28
|
+
# use 'rake clean' and 'rake clobber' to
|
29
|
+
# easily delete generated files
|
30
|
+
CLEAN.include('ext/**/*{.o,.log,.so,.bundle}')
|
31
|
+
CLEAN.include('ext/**/Makefile')
|
32
|
+
CLOBBER.include('lib/**/*{.so,.bundle}')
|
33
|
+
|
34
|
+
# the same as before
|
35
|
+
Rake::TestTask.new do |t|
|
36
|
+
t.libs << 'test'
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Run tests"
|
40
|
+
task :default => :test
|
data/cowboy.gemspec
CHANGED
@@ -4,9 +4,11 @@ require File.expand_path('../lib/cowboy/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Alejandro Ciniglio"]
|
6
6
|
gem.email = ["mail@alejandrociniglio.com"]
|
7
|
-
gem.description = %q{
|
8
|
-
|
9
|
-
gem.
|
7
|
+
gem.description = %q{Cowboy is a wrapper for fftw
|
8
|
+
(C fourier transform library)}
|
9
|
+
gem.summary = %q{Cowboy allows you to access
|
10
|
+
blazing fast FFTs from within Ruby}
|
11
|
+
gem.homepage = "https://github.com/ciniglio/cowboy"
|
10
12
|
|
11
13
|
gem.files = `git ls-files`.split($\)
|
12
14
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
data/ext/cowboy/cowboy.c
CHANGED
@@ -2,34 +2,34 @@
|
|
2
2
|
|
3
3
|
VALUE mCowboy;
|
4
4
|
|
5
|
-
VALUE fft_1d(VALUE m, VALUE
|
5
|
+
VALUE fft_1d(VALUE m, VALUE v) {
|
6
6
|
fftw_complex *in, *out;
|
7
7
|
fftw_plan fp;
|
8
|
-
|
8
|
+
int n;
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
if (n == 0){
|
14
|
-
rb_raise(rb_eException, "Can't use blank array");
|
10
|
+
n = (int) size_of_val(v);
|
11
|
+
if (n == 0) {
|
12
|
+
rb_raise(rb_eException, "Can't use empty set of samples");
|
15
13
|
}
|
14
|
+
|
16
15
|
in = allocate_fftw_complex(n);
|
17
16
|
out = allocate_fftw_complex(n);
|
18
17
|
fp = fftw_plan_dft_1d(n, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
|
19
18
|
|
20
|
-
|
19
|
+
cast_val_to_complex(in, v);
|
21
20
|
|
22
21
|
fftw_execute(fp);
|
23
22
|
free(in);
|
24
23
|
fftw_destroy_plan(fp);
|
25
24
|
|
26
|
-
return
|
25
|
+
return ca_wrap_struct_class(out, n);
|
27
26
|
}
|
28
27
|
|
29
|
-
void Init_cowboy(){
|
28
|
+
void Init_cowboy() {
|
30
29
|
mCowboy = rb_define_module("Cowboy");
|
31
30
|
|
32
31
|
Init_cowboy_complex();
|
32
|
+
Init_cowboy_array();
|
33
33
|
|
34
34
|
rb_define_module_function(mCowboy, "fft_1d", fft_1d, 1);
|
35
35
|
}
|
data/ext/cowboy/cowboy.h
CHANGED
@@ -0,0 +1,69 @@
|
|
1
|
+
#include "cowboy.h"
|
2
|
+
#include <ruby.h>
|
3
|
+
|
4
|
+
VALUE cCowboyArray;
|
5
|
+
|
6
|
+
typedef struct cowboy_array {
|
7
|
+
long N;
|
8
|
+
fftw_complex* fc;
|
9
|
+
} CowboyArray;
|
10
|
+
|
11
|
+
static void ca_free(void * ca){
|
12
|
+
free(((CowboyArray *)ca)->fc);
|
13
|
+
free(ca);
|
14
|
+
}
|
15
|
+
|
16
|
+
VALUE array_index(VALUE self, VALUE index){
|
17
|
+
fftw_complex * fc;
|
18
|
+
CowboyArray * ca;
|
19
|
+
long i = NUM2LONG(index);
|
20
|
+
Data_Get_Struct(self, CowboyArray, ca);
|
21
|
+
if (i >= ca->N){
|
22
|
+
return Qnil;
|
23
|
+
}
|
24
|
+
return c_to_rb_complex(ca->fc[i][0],
|
25
|
+
ca->fc[i][1]);
|
26
|
+
}
|
27
|
+
|
28
|
+
VALUE array_size(VALUE self){
|
29
|
+
fftw_complex * fc;
|
30
|
+
CowboyArray * ca;
|
31
|
+
Data_Get_Struct(self, CowboyArray, ca);
|
32
|
+
return LONG2NUM(ca->N);
|
33
|
+
}
|
34
|
+
|
35
|
+
VALUE array_each(VALUE self){
|
36
|
+
int i;
|
37
|
+
fftw_complex * fc;
|
38
|
+
CowboyArray * ca;
|
39
|
+
Data_Get_Struct(self, CowboyArray, ca);
|
40
|
+
|
41
|
+
if (!rb_block_given_p())
|
42
|
+
rb_raise(rb_eArgError, "Expected Block");
|
43
|
+
|
44
|
+
for(i = 0; i < ca->N; i++)
|
45
|
+
rb_yield(c_to_rb_complex(ca->fc[i][0],
|
46
|
+
ca->fc[i][1]));
|
47
|
+
return self;
|
48
|
+
}
|
49
|
+
|
50
|
+
VALUE ca_wrap_struct_class(fftw_complex *fc, long N){
|
51
|
+
CowboyArray * ca;
|
52
|
+
ca = ALLOC(CowboyArray);
|
53
|
+
ca->N = N;
|
54
|
+
ca->fc = fc;
|
55
|
+
return Data_Wrap_Struct(cCowboyArray, 0, free, ca);
|
56
|
+
}
|
57
|
+
|
58
|
+
VALUE ca_not_implemented(VALUE self){
|
59
|
+
rb_notimplement();
|
60
|
+
}
|
61
|
+
|
62
|
+
void Init_cowboy_array(){
|
63
|
+
cCowboyArray = rb_define_class_under(mCowboy, "CowboyArray", rb_cObject);
|
64
|
+
rb_define_method(cCowboyArray, "initialize", ca_not_implemented, 0);
|
65
|
+
rb_define_method(cCowboyArray, "size", array_size, 0);
|
66
|
+
rb_define_method(cCowboyArray, "[]", array_index, 1);
|
67
|
+
rb_define_method(cCowboyArray, "each", array_each, 0);
|
68
|
+
rb_include_module(cCowboyArray, rb_mEnumerable);
|
69
|
+
}
|
data/ext/cowboy/cowboy_complex.c
CHANGED
@@ -1,11 +1,48 @@
|
|
1
1
|
#include "cowboy.h"
|
2
2
|
#include <ruby.h>
|
3
|
+
#include <stdlib.h>
|
3
4
|
|
4
5
|
long size_of_ary(VALUE nums){
|
5
6
|
Check_Type(nums, T_ARRAY);
|
6
7
|
return RARRAY_LEN(nums);
|
7
8
|
}
|
8
9
|
|
10
|
+
long size_of_str(VALUE str){
|
11
|
+
Check_Type(str, T_STRING);
|
12
|
+
return RSTRING_LEN(RSTRING(str)) / 8;
|
13
|
+
}
|
14
|
+
|
15
|
+
long size_of_val(VALUE v){
|
16
|
+
if (TYPE(v) == T_STRING) {
|
17
|
+
return size_of_str(v);
|
18
|
+
} else if (TYPE(v) == T_ARRAY) {
|
19
|
+
return size_of_ary(v);
|
20
|
+
} else {
|
21
|
+
rb_raise(rb_eNotImpError, "Needs a string or array");
|
22
|
+
return 0;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
void cast_string_to_complex(fftw_complex * fc, VALUE str){
|
27
|
+
unsigned char * s;
|
28
|
+
long len;
|
29
|
+
int i, j, a;
|
30
|
+
double d;
|
31
|
+
void *p;
|
32
|
+
double t = 2.3;
|
33
|
+
|
34
|
+
p = &d;
|
35
|
+
|
36
|
+
s = RSTRING_PTR(RSTRING(str));
|
37
|
+
len = RSTRING_LEN(RSTRING(str));
|
38
|
+
|
39
|
+
for(i = 0, j = 0; i < len; i+=8, j++){
|
40
|
+
d = *(double *) &s[i];
|
41
|
+
fc[j][0] = d;
|
42
|
+
fc[j][1] = 0;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
9
46
|
void cast_nums_to_complex(fftw_complex * fc, VALUE nums){
|
10
47
|
long len;
|
11
48
|
int i;
|
@@ -13,10 +50,20 @@ void cast_nums_to_complex(fftw_complex * fc, VALUE nums){
|
|
13
50
|
Check_Type(nums, T_ARRAY);
|
14
51
|
i = 0;
|
15
52
|
|
16
|
-
|
17
|
-
|
18
|
-
fc[
|
19
|
-
|
53
|
+
for(len = 0; len < size_of_ary(nums); len++){
|
54
|
+
n = rb_ary_entry(nums, len);
|
55
|
+
fc[len][0] = NUM2DBL(n);
|
56
|
+
fc[len][1] = 0;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
void cast_val_to_complex(fftw_complex * fc, VALUE v){
|
61
|
+
if (TYPE(v) == T_STRING) {
|
62
|
+
cast_string_to_complex(fc, v);
|
63
|
+
} else if (TYPE(v) == T_ARRAY) {
|
64
|
+
cast_nums_to_complex(fc, v);
|
65
|
+
} else {
|
66
|
+
rb_raise(rb_eNotImpError, "Needs a string or array");
|
20
67
|
}
|
21
68
|
}
|
22
69
|
|
@@ -32,25 +79,5 @@ VALUE c_to_rb_complex(double r, double i){
|
|
32
79
|
DBL2NUM(r), DBL2NUM(i));
|
33
80
|
}
|
34
81
|
|
35
|
-
VALUE complex_to_real_nums(fftw_complex *fc, long N){
|
36
|
-
VALUE ar = rb_ary_new();
|
37
|
-
int i;
|
38
|
-
for(i = 0; i < N; i++){
|
39
|
-
rb_ary_push(ar, c_to_rb_complex(fc[i][0],
|
40
|
-
fc[i][1]));
|
41
|
-
}
|
42
|
-
return ar;
|
43
|
-
}
|
44
|
-
|
45
|
-
VALUE test_back_and_from_complex(VALUE m, VALUE nums){
|
46
|
-
Check_Type(nums, T_ARRAY);
|
47
|
-
fftw_complex * fc;
|
48
|
-
fc = allocate_fftw_complex(size_of_ary(nums));
|
49
|
-
cast_nums_to_complex(fc, nums);
|
50
|
-
return complex_to_real_nums(fc,
|
51
|
-
size_of_ary(nums));
|
52
|
-
}
|
53
|
-
|
54
82
|
void Init_cowboy_complex(){
|
55
|
-
rb_define_module_function(mCowboy, "test_complex", test_back_and_from_complex, 1);
|
56
83
|
}
|
data/ext/cowboy/cowboy_complex.h
CHANGED
@@ -5,10 +5,11 @@
|
|
5
5
|
|
6
6
|
void Init_cowboy_complex();
|
7
7
|
|
8
|
-
|
8
|
+
long size_of_val(VALUE v);
|
9
|
+
void cast_val_to_complex(fftw_complex * fc, VALUE v);
|
10
|
+
|
9
11
|
fftw_complex * allocate_fftw_complex(long n);
|
10
|
-
VALUE complex_to_real_nums(fftw_complex * fc, long N);
|
11
|
-
long size_of_ary(VALUE nums);
|
12
12
|
|
13
|
+
VALUE c_to_rb_complex(double r, double i);
|
13
14
|
|
14
15
|
#endif
|
data/lib/cowboy.rb
CHANGED
data/lib/cowboy/fft.rb
CHANGED
@@ -16,13 +16,17 @@ module Cowboy
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def buckets(sample_rate=nil)
|
19
|
+
return @buckets if @buckets
|
19
20
|
sample_rate ||= @freq.size
|
20
21
|
nyquist = sample_rate/2.0
|
22
|
+
sum = 0
|
21
23
|
a = []
|
22
24
|
n = @freq.size
|
23
25
|
for i in (0...n)
|
24
|
-
a << -nyquist +
|
26
|
+
a << -nyquist + sum
|
27
|
+
sum += (2 * nyquist/n)
|
25
28
|
end
|
29
|
+
@buckets = a
|
26
30
|
return a
|
27
31
|
end
|
28
32
|
end
|
data/lib/cowboy/version.rb
CHANGED
data/test/test_array.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative './test_helper.rb'
|
2
|
+
|
3
|
+
require 'cowboy'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
class TestArray < Test::Unit::TestCase
|
7
|
+
def test_version
|
8
|
+
assert_not_nil Cowboy::VERSION
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_ffd_1d_4
|
12
|
+
a = [1,2,3,4]
|
13
|
+
c = Cowboy::fft_1d a
|
14
|
+
assert_equal c[0], 10
|
15
|
+
assert_equal c[1], Complex(-2,2)
|
16
|
+
assert_equal c[2], Complex(-2,0)
|
17
|
+
assert_equal c[3], Complex(-2,-2)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_ffd_1d_8
|
21
|
+
a = [1,2,1,2,1,2,1,2]
|
22
|
+
c = Cowboy::fft_1d a
|
23
|
+
assert_equal c[0], 12
|
24
|
+
assert_equal c[1], 0
|
25
|
+
assert_equal c[4], -4
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative './test_helper.rb'
|
2
|
+
|
3
|
+
class TestEnumerable < Test::Unit::TestCase
|
4
|
+
def test_size
|
5
|
+
a = (0...10000).to_a
|
6
|
+
c = Cowboy::fft_1d a
|
7
|
+
assert_equal c.size, a.size
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_each
|
11
|
+
a = (0...4).to_a
|
12
|
+
c = Cowboy::fft_1d a
|
13
|
+
assert_equal (c.respond_to? :each), true
|
14
|
+
assert_block do
|
15
|
+
c.each do |i|
|
16
|
+
assert_not_nil i
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_map
|
22
|
+
a = (0...10).to_a
|
23
|
+
c = Cowboy::fft_1d a
|
24
|
+
assert_equal (c.respond_to? :map), true
|
25
|
+
end
|
26
|
+
end
|
data/test/test_helper.rb
ADDED
data/test/test_string.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative './test_helper.rb'
|
2
|
+
require 'cowboy'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class TestString < Test::Unit::TestCase
|
6
|
+
def test_ffd_1d_4
|
7
|
+
a = [1,2,3,4].pack('D*')
|
8
|
+
c = Cowboy::fft_1d a
|
9
|
+
assert_equal c[0], 10
|
10
|
+
assert_equal c[1], Complex(-2,2)
|
11
|
+
assert_equal c[2], Complex(-2,0)
|
12
|
+
assert_equal c[3], Complex(-2,-2)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_ffd_1d_8
|
16
|
+
a = [1,2,1,2,1,2,1,2].pack('D*')
|
17
|
+
c = Cowboy::fft_1d a
|
18
|
+
assert_equal c[0], 12
|
19
|
+
assert_equal c[1], 0
|
20
|
+
assert_equal c[4], -4
|
21
|
+
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cowboy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-06 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description:
|
14
|
+
description: ! "Cowboy is a wrapper for fftw\n (C fourier
|
15
|
+
transform library)"
|
15
16
|
email:
|
16
17
|
- mail@alejandrociniglio.com
|
17
18
|
executables: []
|
@@ -27,6 +28,8 @@ files:
|
|
27
28
|
- cowboy.gemspec
|
28
29
|
- ext/cowboy/cowboy.c
|
29
30
|
- ext/cowboy/cowboy.h
|
31
|
+
- ext/cowboy/cowboy_array.c
|
32
|
+
- ext/cowboy/cowboy_array.h
|
30
33
|
- ext/cowboy/cowboy_complex.c
|
31
34
|
- ext/cowboy/cowboy_complex.h
|
32
35
|
- ext/cowboy/extconf.rb
|
@@ -34,7 +37,11 @@ files:
|
|
34
37
|
- lib/cowboy/fft.rb
|
35
38
|
- lib/cowboy/hamming.rb
|
36
39
|
- lib/cowboy/version.rb
|
37
|
-
|
40
|
+
- test/test_array.rb
|
41
|
+
- test/test_enumerable.rb
|
42
|
+
- test/test_helper.rb
|
43
|
+
- test/test_string.rb
|
44
|
+
homepage: https://github.com/ciniglio/cowboy
|
38
45
|
licenses: []
|
39
46
|
post_install_message:
|
40
47
|
rdoc_options: []
|
@@ -57,5 +64,9 @@ rubyforge_project:
|
|
57
64
|
rubygems_version: 1.8.24
|
58
65
|
signing_key:
|
59
66
|
specification_version: 3
|
60
|
-
summary:
|
61
|
-
test_files:
|
67
|
+
summary: Cowboy allows you to access blazing fast FFTs from within Ruby
|
68
|
+
test_files:
|
69
|
+
- test/test_array.rb
|
70
|
+
- test/test_enumerable.rb
|
71
|
+
- test/test_helper.rb
|
72
|
+
- test/test_string.rb
|