cowboy 0.0.1 → 0.0.2
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 +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
|