wavspa 0.1.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.
@@ -0,0 +1,59 @@
1
+ /*
2
+ * Wavelet transform library
3
+ *
4
+ * Copyright (C) 2016 Hiroshi Kuwagata <kgt9221@gmail.com>
5
+ */
6
+
7
+ /*
8
+ * $Id: walet.h 91 2016-07-04 03:05:03Z kgt $
9
+ */
10
+
11
+ #ifndef __WALET_H__
12
+ #define __WALET_H__
13
+
14
+ #include <stdio.h>
15
+ #include <stdlib.h>
16
+ #include <stdint.h>
17
+
18
+ #define WALET_LINEARSCALE_MODE 1
19
+ #define WALET_LOGSCALE_MODE 2
20
+
21
+ typedef struct __walet__ {
22
+ int flags;
23
+
24
+ double fq_s; // as "sampleing frequency"
25
+ double fq_l; // as "low side frequency"
26
+ double fq_h; // as "high side fequency"
27
+
28
+ double sigma;
29
+ double wk0;
30
+ double wk1;
31
+ double wk2;
32
+ int* ws; // as window size list
33
+ double** exp;
34
+
35
+ int width;
36
+ int mode;
37
+ double step;
38
+
39
+ double* smpl; // as "sample data"
40
+ int n; // as "number of sample size"
41
+
42
+ double* wt;
43
+ } walet_t;
44
+
45
+ int walet_new(walet_t** ptr);
46
+ int walet_destroy(walet_t* ptr);
47
+
48
+ int walet_set_sigma(walet_t* ptr, double sigma);
49
+ int walet_set_frequency(walet_t* ptr, double freq);
50
+ int walet_set_range(walet_t* ptr, double low, double high);
51
+ int walet_set_scale_mode(walet_t* ptr, int mode);
52
+ int walet_set_output_width(walet_t* ptr, int width);
53
+
54
+ int walet_put_in(walet_t* ptr, char* fmt, void* data, size_t size);
55
+ int walet_transform(walet_t* ptr, int pos);
56
+ int walet_calc_power(walet_t* ptr, double* dst);
57
+ int walet_calc_amplitude(walet_t* ptr, double* dst);
58
+
59
+ #endif /* !defined(__WALET_H__) */
data/lib/wav.rb ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+ #coding utf-8
3
+
4
+ #
5
+ # WAV file parser
6
+ #
7
+ # Copyright (C) 2014 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ class WavFile
11
+ class << self
12
+ alias :open :new
13
+ end
14
+
15
+ def initialize(file)
16
+ @io = File.open(file, "rb")
17
+
18
+ tmp = @io.read(12).unpack('a4Va4')
19
+
20
+ @magic = tmp[0]
21
+ @data_size = tmp[1]
22
+ @type = tmp[2]
23
+
24
+ if not (@magic == "RIFF" and @type == "WAVE")
25
+ raise('Illeagal magic number.')
26
+ end
27
+
28
+ if @data_size != (File.size(file) - 8)
29
+ raise('Data size is not match.')
30
+ end
31
+
32
+ tmp = @io.read(8).unpack('a4V')
33
+
34
+ @id = tmp[0]
35
+
36
+ if @id != 'fmt '
37
+ raise('ID "%s" is not supported.' % @id)
38
+ end
39
+
40
+ tmp = @io.read(tmp[1]).unpack('vvVVvv')
41
+
42
+ @format_id = tmp[0]
43
+ @channel_num = tmp[1]
44
+ @sample_rate = tmp[2]
45
+ @bytes_per_sec = tmp[3]
46
+ @block_size = tmp[4]
47
+ @sample_size = tmp[5]
48
+
49
+ loop {
50
+ tmp = @io.read(8).unpack('a4V')
51
+ break if tmp[0] == "data"
52
+
53
+ @io.seek(tmp[1], IO::SEEK_CUR)
54
+ }
55
+
56
+ @data_offset = @io.pos
57
+ @data_size = tmp[1]
58
+ end
59
+
60
+ attr_reader :data_size, :channel_num, :sample_rate, :sample_size,
61
+ :bytes_per_sec, :block_size
62
+
63
+ def read(n = nil)
64
+ if not n.nil?
65
+ @io.read(@block_size * n)
66
+ else
67
+ @io.read
68
+ end
69
+ end
70
+
71
+ def seek(pos)
72
+ @io.seek(@block_size * pos, IO::SEEK_SET)
73
+ end
74
+
75
+ def eof?
76
+ @io.eof?
77
+ end
78
+
79
+ def close
80
+ @io.close
81
+ end
82
+ end
83
+
84
+ if $0 == __FILE__
85
+ require 'pp'
86
+ pp WavFile.open(ARGV[0])
87
+ end
@@ -0,0 +1,74 @@
1
+ require "wavspa/version"
2
+
3
+ module WavSpectrumAnalyzer
4
+ module Common
5
+ def log_scaler(r)
6
+ return 1.0 - ((Math.log(r / @lo_freq) / @log_base) / @output_width)
7
+ end
8
+
9
+ def linear_scaler(r)
10
+ return 1.0 - ((r - @lo_freq) / @freq_width)
11
+ end
12
+
13
+ def fpos(r)
14
+ scale = (@logscale)? log_scaler(r): linear_scaler(r)
15
+ return (scale * @output_width).round
16
+ end
17
+
18
+ def draw_freq_line(fb)
19
+ #
20
+ # 低周波方向
21
+ #
22
+ frq = @basis_freq / 2
23
+ loop {
24
+ pos = fpos(frq)
25
+ break if pos > @output_width
26
+
27
+ fb.hline(pos, "#{frq.to_i}Hz")
28
+ frq /= 2
29
+ }
30
+
31
+ #
32
+ # 高周波方向
33
+ #
34
+ frq = @basis_freq;
35
+ loop {
36
+ pos = fpos(frq)
37
+ break if pos <= 0
38
+
39
+ fb.hline(pos, "#{frq.to_i}Hz")
40
+ frq *= 2
41
+ }
42
+ end
43
+
44
+ def time_str(tm)
45
+ m = tm / 60
46
+ s = tm % 60
47
+
48
+ if m.zero?
49
+ ret = %q{%d"} % s
50
+ else
51
+ ret = %q{%d'%02d"} % [m, s]
52
+ end
53
+
54
+ return ret
55
+ end
56
+
57
+ def draw_time_line(fb, wav, usize)
58
+ tc = 0
59
+ tm = 0
60
+ n = (wav.data_size / (wav.sample_size / 8)) / usize
61
+
62
+ fb.vline(0, time_str(tm))
63
+
64
+ n.times { |col|
65
+ tc += usize;
66
+ if tc >= wav.sample_rate
67
+ tm += 1
68
+ tc %= wav.sample_rate
69
+ fb.vline(col, time_str(tm)) if (tm % 10).zero?
70
+ end
71
+ }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module WavSpectrumAnalyzer
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,130 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # Spectrum analyzer for WAV file
6
+ #
7
+ # Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ require 'wav'
11
+ require 'png'
12
+
13
+ require 'wavspa/fft'
14
+ require 'wavspa/fb'
15
+
16
+ module WavSpectrumAnalyzer
17
+ module FFTApp
18
+ extend WavSpectrumAnalyzer::Common
19
+
20
+ class << self
21
+ def load_param(param)
22
+ @unit_time = param[:unit_time]
23
+ @fft_size = param[:fft_size]
24
+ @output_width = param[:output_width]
25
+ @logscale = (param[:scale_mode] == :LOGSCALE)
26
+ @win_func = param[:window_function]
27
+ @scale_mode = param[:scale_mode]
28
+ @col_step = param[:col_step]
29
+
30
+ @freq_range = param[:range]
31
+ @lo_freq = @freq_range[0]
32
+ @hi_freq = @freq_range[1]
33
+ @freq_width = (@hi_freq - @lo_freq)
34
+
35
+ @log_step = (@hi_freq / @lo_freq) ** (1.0 / @output_width)
36
+ @log_base = Math.log(@log_step)
37
+ @basis_freq = 440.0
38
+ end
39
+ private :load_param
40
+
41
+ def main(input, param, output)
42
+ load_param(param)
43
+
44
+ wav = WavFile.open(input)
45
+ fft = FFT.new("s%dle" % wav.sample_size, @fft_size)
46
+
47
+ fft.window = @win_func
48
+ fft.width = @output_width
49
+ fft.scale_mode = @scale_mode
50
+ fft.frequency = @freq_range.clone.unshift(wav.sample_rate)
51
+
52
+ line = []
53
+ rows = 0
54
+ usize = (wav.sample_rate / 100) * @unit_time
55
+ nblk = (wav.data_size / (wav.sample_size / 8)) / usize
56
+
57
+ fb = FrameBuffer.new(nblk,
58
+ @output_width,
59
+ :column_step => @col_step,
60
+ :margin_x => ($draw_freq_line)? 50:0,
61
+ :margin_y => ($draw_time_line)? 30:0)
62
+
63
+ if $verbose
64
+ STDERR.print <<~EOT
65
+ - input data
66
+ #{input}
67
+ data size: #{wav.data_size}
68
+ channel num: #{wav.channel_num}
69
+ sample rate: #{wav.sample_rate} Hz
70
+ sample size: #{wav.sample_size} bits
71
+ data rate: #{wav.bytes_per_sec} bytes/sec
72
+
73
+ - FFT parameter
74
+ FFT size: #{@fft_size} samples
75
+ unit time: #{@unit_time} cs
76
+ window func: #{@win_func}
77
+
78
+ - OUTPUT
79
+ width: #{fb.width}px
80
+ height: #{fb.height}px
81
+ freq range: #{@lo_freq} - #{@hi_freq}Hz
82
+ scale mode: #{@scale_mode}
83
+
84
+ EOT
85
+ end
86
+
87
+ if wav.channel_num >= 2
88
+ STDERR.print("error: multi chanel data is not supported " \
89
+ "(support only monoral data).\n")
90
+ exit(1)
91
+ end
92
+
93
+ until rows >= nblk
94
+ STDERR.printf("\rtransform #{rows + 1}/#{nblk}", rows) if $verbose
95
+
96
+ fft << wav.read(usize)
97
+
98
+ fft.spectrum.unpack("d*").each {|x|
99
+ begin
100
+ x = (x * 3.5).round
101
+ x = 0 if x < 0
102
+ x = 255 if x > 255
103
+ rescue
104
+ x = 0
105
+ end
106
+
107
+ line.unshift(x / 3, x, x / 2)
108
+ }
109
+
110
+ fb.put_column(rows, line)
111
+ line.clear
112
+
113
+ rows += 1
114
+ end
115
+
116
+ STDERR.printf(" ... done\n") if $verbose
117
+
118
+ STDERR.printf("write to #{output} ... ") if $verbose
119
+
120
+ draw_freq_line(fb) if $draw_freq_line
121
+ draw_time_line(fb, wav, usize) if $draw_time_line
122
+
123
+ png = PNG.encode(fb.width, fb.height, fb.to_s, :pixel_format => :RGB)
124
+ IO.binwrite(output, png)
125
+
126
+ STDERR.printf("done\n") if $verbose
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,54 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # Spectrum analyzer for WAV file
6
+ #
7
+ # Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ module WavSpectrumAnalyzer
11
+ module FFTApp
12
+ PRESET_TABLE = {
13
+ "default" => {
14
+ :unit_time => 10,
15
+ :fft_size => 16384,
16
+ :output_width => 240,
17
+ :window_function => :FLAT_TOP,
18
+ :scale_mode => :LOGSCALE,
19
+ :range => [200, 8000],
20
+ :col_step => 1,
21
+ },
22
+
23
+ "32k" => {
24
+ :unit_time => 5,
25
+ :fft_size => 32768,
26
+ :output_width => 360,
27
+ :window_function => :FLAT_TOP,
28
+ :scale_mode => :LOGSCALE,
29
+ :range => [200, 16000],
30
+ :col_step => 1,
31
+ },
32
+
33
+ "cd" => {
34
+ :unit_time => 5,
35
+ :fft_size => 32768,
36
+ :output_width => 480,
37
+ :window_function => :FLAT_TOP,
38
+ :scale_mode => :LOGSCALE,
39
+ :range => [50, 22000],
40
+ :col_step => 1,
41
+ },
42
+
43
+ "highreso" => {
44
+ :unit_time => 5,
45
+ :fft_size => 131072,
46
+ :window_function => :FLAT_TOP,
47
+ :output_width => 640,
48
+ :scale_mode => :LOGSCALE,
49
+ :range => [200, 32000],
50
+ :col_step => 1,
51
+ },
52
+ }
53
+ end
54
+ end
@@ -0,0 +1,139 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # Spectrum analyzer for WAV file
6
+ #
7
+ # Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ require 'wav'
11
+ require 'png'
12
+
13
+ require 'wavspa/wavelet'
14
+ require 'wavspa/fb'
15
+
16
+ module WavSpectrumAnalyzer
17
+ module WaveLetApp
18
+ extend WavSpectrumAnalyzer::Common
19
+
20
+ class << self
21
+ def load_param(param)
22
+ @unit_time = param[:unit_time]
23
+ @sigma = param[:sigma]
24
+ @ceil = param[:ceil]
25
+ @floor = param[:floor]
26
+ @tran_mode = param[:transform_mode]
27
+ @output_width = param[:output_width]
28
+ @logscale = (param[:scale_mode] == :LOGSCALE)
29
+ @scale_mode = param[:scale_mode]
30
+ @col_step = param[:col_step]
31
+
32
+ @freq_range = param[:range]
33
+ @lo_freq = @freq_range[0]
34
+ @hi_freq = @freq_range[1]
35
+ @freq_width = (@hi_freq - @lo_freq)
36
+
37
+ @log_step = (@hi_freq / @lo_freq) ** (1.0 / @output_width)
38
+ @log_base = Math.log(@log_step)
39
+ @basis_freq = 440.0
40
+ end
41
+ private :load_param
42
+
43
+ def main(input, param, output)
44
+ load_param(param)
45
+
46
+ wav = WavFile.open(input)
47
+ wl = Wavelet.new
48
+
49
+ wl.frequency = wav.sample_rate
50
+ wl.sigma = @sigma
51
+ wl.range = (@lo_freq .. @hi_freq)
52
+ wl.scale_mode = @scale_mode
53
+ wl.width = @output_width
54
+
55
+ wl.put_in("s%dle" % wav.sample_size, wav.read)
56
+
57
+ line = []
58
+ rows = 0
59
+
60
+ usize = (wav.sample_rate * @unit_time) / 1000
61
+ nblk = (wav.data_size / wav.block_size) / usize
62
+
63
+ fb = FrameBuffer.new(nblk,
64
+ @output_width,
65
+ :column_step => @col_step,
66
+ :margin_x => ($draw_freq_line)? 50:0,
67
+ :margin_y => ($draw_time_line)? 30:0)
68
+
69
+ if $verbose
70
+ STDERR.print <<~EOT
71
+ - input data
72
+ #{input}
73
+ data size: #{wav.data_size}
74
+ channel num: #{wav.channel_num}
75
+ sample rate: #{wav.sample_rate} Hz
76
+ sample size: #{wav.sample_size} bits
77
+ data rate: #{wav.bytes_per_sec} bytes/sec
78
+
79
+ - FFT parameter
80
+ sigma: #{@sigma}
81
+ unit time: #{@unit_time} ms
82
+ ceil: #{@ceil}
83
+ floor: #{@floor}
84
+ mode : #{@tran_mode}
85
+
86
+ - OUTPUT
87
+ width: #{fb.width}px
88
+ height: #{fb.height}px
89
+ freq range: #{@lo_freq} - #{@hi_freq}Hz
90
+ scale mode: #{@scale_mode}
91
+
92
+ EOT
93
+ end
94
+
95
+ if wav.channel_num >= 2
96
+ STDERR.print("error: multi chanel data is not supported " \
97
+ "(support only monoral data).\n")
98
+ exit(1)
99
+ end
100
+
101
+ until rows >= nblk
102
+ STDERR.printf("\rtransform #{rows + 1}/#{nblk}", rows) if $verbose
103
+ wl.transform(rows * usize)
104
+
105
+ result = (@transform_mode == :POWER)? wl.power: wl.amplitude
106
+
107
+ result.unpack("d*").each {|x|
108
+ if x > @ceil
109
+ x = 255
110
+ elsif x < @floor
111
+ x = 0
112
+ else
113
+ x = ((255 * (x - @floor)) / (@ceil - @floor)).floor
114
+ end
115
+
116
+ line.unshift(x / 3, x, x / 2)
117
+ }
118
+
119
+ fb.put_column(rows, line)
120
+ line.clear
121
+
122
+ rows += 1
123
+ end
124
+
125
+ STDERR.printf(" ... done\n") if $verbose
126
+
127
+ STDERR.printf("write to #{output} ... ") if $verbose
128
+
129
+ draw_freq_line(fb) if $draw_freq_line
130
+ draw_time_line(fb, wav, usize) if $draw_time_line
131
+
132
+ png = PNG.encode(fb.width, fb.height, fb.to_s, :pixel_format => :RGB)
133
+ IO.binwrite(output, png)
134
+
135
+ STDERR.printf("done\n") if $verbose
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,38 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ #
5
+ # Spectrum analyzer for WAV file
6
+ #
7
+ # Copyright (C) 2019 Hiroshi Kuwagata <kgt9221@gmail.com>
8
+ #
9
+
10
+ module WavSpectrumAnalyzer
11
+ module WaveLetApp
12
+ PRESET_TABLE = {
13
+ "default" => {
14
+ :sigma => 24.0,
15
+ :unit_time => 10 * 10,
16
+ :scale_mode => :LOGSCALE,
17
+ :transform_mode => :POWER,
18
+ :output_width => 240,
19
+ :range => [200.0, 8000.0],
20
+ :ceil => -10,
21
+ :floor => -90,
22
+ :col_step => 1,
23
+ },
24
+
25
+ "cd" => {
26
+ :sigma => 24.0,
27
+ :unit_time => 5 * 10,
28
+ :scale_mode => :LOGSCALE,
29
+ :transform_mode => :POWER,
30
+ :output_width => 480,
31
+ :range => [50.0, 22050.0],
32
+ :ceil => -10,
33
+ :floor => -90,
34
+ :col_step => 1,
35
+ },
36
+ }
37
+ end
38
+ end
data/wavspa.gemspec ADDED
@@ -0,0 +1,47 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "wavspa/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "wavspa"
8
+ spec.version = WavSpectrumAnalyzer::VERSION
9
+ spec.authors = ["Hirosho Kuwagata"]
10
+ spec.email = ["kgt9221@gmail.com"]
11
+
12
+ spec.summary = %q{spectrum analyzer for wav file.}
13
+ spec.description = %q{spectrum analyzer for wav file.}
14
+ spec.homepage = "https://github.com/kwgt/wavspa"
15
+ spec.license = "MIT"
16
+
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against " \
21
+ "public gem pushes."
22
+ end
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been
26
+ # added into git.
27
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
+ `git ls-files -z`.split("\x0").reject { |f|
29
+ f.match(%r{^(test|spec|features)/})
30
+ }
31
+ end
32
+
33
+ spec.bindir = "bin"
34
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["none"]
36
+ spec.extensions = %w[
37
+ ext/wavspa/fft/extconf.rb
38
+ ext/wavspa/wavelet/extconf.rb
39
+ ext/wavspa/fb/extconf.rb
40
+ ]
41
+
42
+ spec.required_ruby_version = ">= 2.4.0"
43
+
44
+ spec.add_development_dependency "bundler", "~> 2.0"
45
+ spec.add_development_dependency "rake", "~> 10.0"
46
+ spec.add_dependency "libpng-ruby", "~> 0.5.2"
47
+ end