wavspa 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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