zappa 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9392a71ab4fbaf850daa6ff3a3166f16ccbcb23d
4
- data.tar.gz: 33ea67e02179e8afeddf795844f306a43f5a1705
3
+ metadata.gz: d6e03448367486a910dea3a2c86dfc92df6b044e
4
+ data.tar.gz: 06bdff101d6c5e26d05c82998168fdae9937a962
5
5
  SHA512:
6
- metadata.gz: c262b0d9416d09bf225b10bbcbfce69e759de1b8740adeda12a588f3f0dbac1226f3be55c1995f8afcee4059999916e2c42ee2014da7e4dab56e07bb0ba953cc
7
- data.tar.gz: 5d64cdb5a2334858c12f53ac8e0459f26c7e213bea352a4ea635b3f1e5e8cd063ad7c466586e65dd2af9add0671a97d35faf02e7c95ea5173e2098181e20a2e5
6
+ metadata.gz: 205f8539cbe777d690ef19fd22d2418b881c97e4ac8232911132eb0e203da922dcba6082cea7c0f797a0e8dac37a173a1692e1ee0071e751e9bc64ebbe06e102
7
+ data.tar.gz: 848718e138c6248e49be8da334c948cfd0e97735a8bcbeda45d24043fe18bdb45d4e19236c06dcd27235a59e301c4fbaea50305dbefe588f10f5dc393fefddd5
data/Gemfile CHANGED
@@ -2,7 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in zappa.gemspec
4
4
  gemspec
5
-
6
- group :test do
7
- gem 'rspec'
8
- end
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # Zappa
2
+ [![Circle CI](https://circleci.com/gh/varunsrin/zappa/tree/dev.svg?style=svg)](https://circleci.com/gh/varunsrin/zappa/tree/dev)
3
+ [![Coverage Status](https://coveralls.io/repos/varunsrin/zappa/badge.svg?branch=dev)](https://coveralls.io/r/varunsrin/zappa?branch=dev)
2
4
 
3
5
  Zappa is a high level audio manipulation library for Ruby, inspired by pydub.
4
6
 
@@ -21,9 +23,10 @@ Or install it yourself as:
21
23
  $ gem install zappa
22
24
 
23
25
  ## Usage
26
+ At the core of Zappa is the Clip, an immutable audio unit.
24
27
 
25
- At the core of Zappa is the Clip, an immutable audio unit. Import a wav file
26
- into a clip:
28
+ ### Importing
29
+ Import a wav file into a clip:
27
30
 
28
31
  require 'zappa'
29
32
 
@@ -33,6 +36,19 @@ into a clip:
33
36
  The clip will create a safe copy of the wav before you can edit it. Remember,
34
37
  clips are immutable so any destructive operations return a new clip.
35
38
 
39
+
40
+ ### Generators
41
+
42
+ Alternatively, you can generate your own sounds from scratch:
43
+
44
+ generator = Zappa::Generator.new
45
+ clip = generator.generate('sine', 1000, 1)
46
+
47
+ This will create a 1000 Hz sine wave that is 1 second long.
48
+
49
+
50
+ ### Editing Clips
51
+
36
52
  You can slice clips into smaller chunks:
37
53
 
38
54
  slice_a = clip.slice(0, 1000) # clip containing 1st second
@@ -42,9 +58,20 @@ You can also join clips:
42
58
 
43
59
  joined_clip = slice_a + slice_b # clip containing 1st and 3rd seconds
44
60
 
61
+
62
+ ### Signal Processing
63
+
64
+ Amplify or attenuate clips with the following syntax:
65
+
66
+ louder_clip = joined_clip + 2
67
+ louder_clip = joined_clip.amplify(2)
68
+
69
+
70
+ ### Export
71
+
45
72
  Once you're done editing a clip, you can export it:
46
73
 
47
- joined_clip.export('output.wav')
74
+ louder_clip.export('output.wav')
48
75
 
49
76
  That's it for now. DSP tools are coming soon!
50
77
 
data/ROADMAP.md CHANGED
@@ -14,9 +14,6 @@
14
14
  0.3 - DSP - Basic
15
15
  ----------------
16
16
  - Amplify signals by DB
17
- - Calculate RMS
18
- - Calculate Max
19
- - Normalize
20
17
  - Phase Invert
21
18
 
22
19
 
@@ -60,3 +57,4 @@ Release - 1.0 Basic DSP Platform
60
57
  - DSP: Compressor
61
58
  - DSP: Remove Silence
62
59
  - DSP: Limiter
60
+ - DSP: Normalize
@@ -2,3 +2,5 @@ require 'zappa/version'
2
2
  require 'zappa/clip'
3
3
  require 'zappa/wave'
4
4
  require 'zappa/errors'
5
+ require 'zappa/processor'
6
+ require 'zappa/generator'
@@ -1,18 +1,19 @@
1
1
  require 'tempfile'
2
2
  require 'open3'
3
- require 'pry'
3
+ require 'zappa/processor'
4
4
 
5
5
  module Zappa
6
6
  class Clip
7
7
  attr_accessor :wav, :cache
8
8
 
9
- def initialize(wav=nil)
9
+ def initialize(wav = nil)
10
10
  if wav
11
11
  @wav = wav
12
12
  else
13
13
  @wav = Wave.new
14
14
  end
15
15
  @cache = nil
16
+ @processor = Processor.new
16
17
  end
17
18
 
18
19
  def from_file(path)
@@ -28,26 +29,49 @@ module Zappa
28
29
  end
29
30
  end
30
31
 
31
- def slice(from, to)
32
- slice_samples(ms_to_samples(from), ms_to_samples(to))
32
+ def slice(pos, len)
33
+ slice_samples(ms_to_samples(pos), ms_to_samples(len))
33
34
  end
34
35
 
35
- def slice_samples(from, to)
36
- fail 'invalid index' if from < 0 || to > @wav.sample_count
37
- fail 'negative range' if from >= to
38
- from *= @wav.frame_size
39
- to *= @wav.frame_size
40
- length = (to - from)
41
- slice = @wav.data.byteslice(from, length)
36
+ def slice_samples(pos, len)
37
+ fail 'invalid index' if pos < 0 || (pos + len) > @wav.sample_count
38
+ slice = @wav.samples[pos, len]
42
39
  clone(slice)
43
40
  end
44
41
 
45
42
  def +(other)
46
- fail 'format mismatch' unless @wav.format == other.wav.format
47
- w = Wave.new()
48
- w.format = @wav.format
49
- w.update_data(@wav.data + other.wav.data)
50
- Clip.new(w)
43
+ return amplify(other) if other.class == Fixnum
44
+
45
+ if other.class == Zappa::Clip
46
+ fail 'format mismatch' unless @wav.format == other.wav.format
47
+ w = Wave.new
48
+ w.format = @wav.format
49
+ samples = []
50
+ samples += @wav.samples if @wav.samples
51
+ samples += other.wav.samples if other.wav.samples
52
+ w.set_samples(samples)
53
+ return Clip.new(w)
54
+ end
55
+
56
+ fail "cannot add Zappa::Clip to #{other.class}"
57
+ end
58
+
59
+ # Processor Wrappers
60
+
61
+ def normalize(headroom)
62
+ clone(@processor.normalize(@wav.samples, headroom))
63
+ end
64
+
65
+ def compress(ratio = 2.0, threshold = - 20.0)
66
+ clone(@processor.compress(@wav.samples, ratio, threshold))
67
+ end
68
+
69
+ def amplify(db)
70
+ clone(@processor.amplify(@wav.samples, db))
71
+ end
72
+
73
+ def invert
74
+ clone(@processor.invert(@wav.samples))
51
75
  end
52
76
 
53
77
  private
@@ -58,7 +82,7 @@ module Zappa
58
82
 
59
83
  def persist_cache
60
84
  tmp = Tempfile.new('zappa')
61
- @cache = tmp.path
85
+ @cache = tmp.path
62
86
  File.write(@cache, @wav.pack)
63
87
  end
64
88
 
@@ -70,10 +94,10 @@ module Zappa
70
94
  destination
71
95
  end
72
96
 
73
- def clone(data = nil)
97
+ def clone(samples = nil)
74
98
  clone = Clip.new
75
99
  clone.wav = Marshal.load(Marshal.dump(@wav))
76
- clone.wav.update_data(data) if data
100
+ clone.wav.set_samples(samples) if samples
77
101
  clone
78
102
  end
79
103
  end
@@ -0,0 +1,56 @@
1
+ module Zappa
2
+ class Generator
3
+ attr_accessor :sample_rate, :channels, :bit_depth
4
+
5
+ def initialize(sample_rate = 44_100, channels = 2, bit_depth = 16)
6
+ @sample_rate = sample_rate
7
+ @channels = channels
8
+ @bit_depth = bit_depth
9
+ @max_amplitude = ((2**bit_depth) / 2) - 1
10
+ end
11
+
12
+ def generate(type, frequency, length)
13
+ types = %w(sine square sawtooth white_noise)
14
+ fail "Cannot generate #{type} wave" unless types.include?(type)
15
+
16
+ samples = []
17
+ wave_pos = 0.0
18
+ wave_delta = frequency.to_f / @sample_rate.to_f
19
+ num_samples = (length * @sample_rate).round
20
+
21
+ num_samples.times do |i|
22
+ wave_value = send(type, wave_pos)
23
+ abs_value = (wave_value * @max_amplitude).round
24
+ samples[i] = [abs_value] * @channels
25
+ wave_pos += wave_delta
26
+ wave_pos -= 1.0 if wave_pos >= 1.0
27
+ # TODO: - account for skips >= 2.0
28
+ end
29
+ clip_from_samples(samples)
30
+ end
31
+
32
+ private
33
+
34
+ def clip_from_samples(samples)
35
+ wave = Zappa::Wave.new
36
+ wave.set_samples(samples)
37
+ Zappa::Clip.new(wave)
38
+ end
39
+
40
+ def sine(position)
41
+ Math.sin(position * 2 * Math::PI)
42
+ end
43
+
44
+ def square(position)
45
+ position < 0.5 ? 1 : -1
46
+ end
47
+
48
+ def sawtooth(position)
49
+ 2 * (position - (0.5 + position).floor)
50
+ end
51
+
52
+ def white_noise(_position)
53
+ rand(-1.0..1.0)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,62 @@
1
+ module Zappa
2
+ class Processor
3
+ def normalize(samples, headroom)
4
+ fail 'headroom cannot be positive' if headroom > 0.0
5
+ curr_peak = max_sample(samples)
6
+ targ_peak = 32_768 * db_to_float(headroom) # calculate this constants
7
+ ratio = (targ_peak / curr_peak)
8
+ mul_samples(samples, ratio)
9
+ end
10
+
11
+ def amplify(samples, db) # fix order
12
+ mul_samples(samples, db_to_float(db))
13
+ end
14
+
15
+ def invert(samples)
16
+ mul_samples(samples, -1)
17
+ end
18
+
19
+ def compress(samples, ratio, threshold)
20
+ threshold_value = 32_768 * db_to_float(threshold) # calc this somehow
21
+ samples.each do |f|
22
+ f.map! do |s|
23
+ if s.abs > threshold_value
24
+ s += (threshold_value - s) / ratio if s > 0
25
+ s -= (s + threshold_value) / ratio if s < 0
26
+ end
27
+ s.round
28
+ end
29
+ end
30
+ samples
31
+ end
32
+
33
+ private
34
+
35
+ def mul_samples(samples, factor)
36
+ samples.map { |f| mul_frame(f, factor) }
37
+ end
38
+
39
+ def mul_frame(frame, factor)
40
+ frame.map { |s| clip((s * factor).round) }
41
+ end
42
+
43
+ def clip(value, max = 32_768)
44
+ return max if value > max
45
+ return -max if value < (-max)
46
+ value
47
+ end
48
+
49
+ def max_sample(samples)
50
+ curr_max = 0
51
+ samples.each do |f|
52
+ f.each { |s| curr_max = s.abs if s.abs > curr_max }
53
+ end
54
+ curr_max
55
+ end
56
+
57
+ # convert db values to floats
58
+ def db_to_float(db)
59
+ 10**(db / 20)
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,3 @@
1
1
  module Zappa
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -1,21 +1,22 @@
1
1
  require 'zappa/wave/format'
2
2
  require 'zappa/wave/riff_header'
3
- require 'zappa/wave/sub_chunk'
3
+ require 'zappa/wave/sub_chunk_header'
4
+ require 'zappa/wave/wave_data'
4
5
 
5
6
  # WAV Spec: http://soundfile.sapp.org/doc/WaveFormat/
6
7
 
7
8
  module Zappa
8
9
  class Wave
9
- attr_accessor :header, :format
10
+ attr_accessor :header, :format, :wave_data
10
11
 
11
12
  def initialize
12
13
  @header = RiffHeader.new
13
14
  @format = Format.new
14
- @wave_data = SubChunk.new
15
+ @wave_data = WaveData.new
15
16
  end
16
17
 
17
- def data
18
- @wave_data.data
18
+ def samples
19
+ @wave_data.samples
19
20
  end
20
21
 
21
22
  def data_size
@@ -31,44 +32,62 @@ module Zappa
31
32
  end
32
33
 
33
34
  def ==(other)
34
- other.data == data
35
- end
36
-
37
- def update_data(new_data)
38
- @wave_data.chunk_id = 'data'
39
- new_size = new_data.bytesize
40
- @header.chunk_size += (new_size - @wave_data.chunk_size)
41
- @wave_data.chunk_size = new_size
42
- @wave_data.data = new_data
35
+ other.wave_data == wave_data
43
36
  end
44
37
 
45
38
  def pack
46
- pack = @header.pack + @format.pack + @wave_data.pack
39
+ pack = @header.pack + @format.pack
40
+ pack += @wave_data.chunk_id
41
+ pack += [@wave_data.chunk_size].pack('V')
42
+ pack += pack_samples(@wave_data.samples)
43
+ pack
47
44
  end
48
45
 
49
46
  def unpack(source)
50
- begin
51
- file = File.open(path_to(source), 'rb')
52
- rescue
53
- fail 'Unable to open WAV file'
54
- else
55
- data_found = false
56
- @header = RiffHeader.new(file)
57
- @format = Format.new(file)
58
- while !data_found
59
- s = SubChunk.new(file)
60
- if s.chunk_id == 'data'
61
- @wave_data = s
62
- data_found = true
63
- end
47
+ file = File.open(path_to(source), 'rb')
48
+ rescue
49
+ raise 'Unable to open WAV file'
50
+ else
51
+ @header = RiffHeader.new(file)
52
+ @format = Format.new(file)
53
+ while sc_header = file.read(8)
54
+ s = SubChunkHeader.new(sc_header)
55
+ if s.chunk_id == 'data'
56
+ unpack_samples(file)
57
+ else
58
+ file.read(s.chunk_size)
64
59
  end
65
60
  end
66
61
  end
67
62
 
68
- def path_to(source)
63
+ def set_samples(samples)
64
+ samples_change = (samples.size - @wave_data.samples.size)
65
+ size_change = samples_change * @format.channels * 2
66
+ @header.chunk_size += size_change
67
+ @wave_data.set_samples(samples)
68
+ end
69
+
70
+ def path_to(source) # Private method?
69
71
  return source if source.class == String
70
72
  return source.path if source.class == File
71
73
  fail 'cannot unpack type: ' + source.class.to_s
72
74
  end
75
+
76
+ private
77
+
78
+ def pack_samples(samples)
79
+ pack_str = 's' * @format.channels
80
+ samples.map { |f| f.pack(pack_str) }.join
81
+ end
82
+
83
+ def unpack_samples(file)
84
+ samples = []
85
+ size = @format.bits_per_sample / 8
86
+ ch = @format.channels
87
+ while (frame_data = file.read(size * ch))
88
+ samples << frame_data.unpack('s' * ch)
89
+ end
90
+ @wave_data.set_samples(samples)
91
+ end
73
92
  end
74
93
  end
@@ -11,7 +11,7 @@ module Zappa
11
11
  @chunk_size = FMT_SIZE
12
12
  @audio_format = 1
13
13
  @channels = 2
14
- @sample_rate = 44100
14
+ @sample_rate = 44_100
15
15
  @byte_rate = 176_400
16
16
  @block_align = 4
17
17
  @bits_per_sample = 16
@@ -46,4 +46,4 @@ module Zappa
46
46
  @bits_per_sample = data.byteslice(14..15).unpack('v').first
47
47
  end
48
48
  end
49
- end
49
+ end
@@ -0,0 +1,24 @@
1
+ module Zappa
2
+ class SubChunkHeader
3
+ attr_accessor :chunk_id, :chunk_size
4
+
5
+ def initialize(data = nil)
6
+ @chunk_id = nil
7
+ @chunk_size = 0
8
+ unpack(data) if data
9
+ end
10
+
11
+ def pack
12
+ @chunk_id + [@chunk_size].pack('V')
13
+ end
14
+
15
+ def unpack(data)
16
+ @chunk_id = data.byteslice(0, 4)
17
+ @chunk_size = data.byteslice(4, 4).unpack('V').first
18
+ end
19
+
20
+ def ==(other)
21
+ other.chunk_size == @chunk_size && other.chunk_id = @chunk_id
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ module Zappa
2
+ class WaveData
3
+ attr_reader :samples, :chunk_id, :chunk_size
4
+
5
+ def initialize
6
+ @chunk_id = 'data'
7
+ @chunk_size = 0
8
+ @samples = []
9
+ end
10
+
11
+ def set_samples(samples)
12
+ @samples = samples
13
+ frame_size = samples[1].size
14
+ @chunk_size = @samples.size * frame_size * 2
15
+ end
16
+
17
+ def ==(other)
18
+ other.chunk_size == @chunk_size && other.chunk_id == @chunk_id
19
+ end
20
+ end
21
+ end
@@ -2,10 +2,10 @@ require 'spec_helper'
2
2
  require 'tempfile'
3
3
 
4
4
  WAV_IN = 'spec/audio/basic-5s.wav'
5
- WAV_IN_DATA_SIZE = 882000
5
+ WAV_IN_DATA_SIZE = 882_000
6
6
 
7
7
  describe Zappa::Clip do
8
- before :each do
8
+ before do
9
9
  subject.from_file(WAV_IN)
10
10
  end
11
11
 
@@ -44,10 +44,9 @@ describe Zappa::Clip do
44
44
  end
45
45
 
46
46
  it 'exports the clip correctly' do
47
- subject.from_file(WAV_IN)
48
47
  export_wav = Zappa::Wave.new
49
48
  export_wav.unpack(File.open(@tmp.path, 'rb'))
50
- expect(subject.wav).to eq(export_wav)
49
+ expect(subject.wav == export_wav).to eq(true)
51
50
  end
52
51
 
53
52
  it 'raises error for invalid path' do
@@ -59,19 +58,15 @@ describe Zappa::Clip do
59
58
 
60
59
  describe '#slice_samples' do
61
60
  before :each do
62
- @slice = subject.slice_samples(4, 8)
63
- end
64
-
65
- it 'fails if the beginning is larger than the end' do
66
- expect { subject.slice_samples(5,2) }.to raise_error(RuntimeError)
61
+ @slice = subject.slice_samples(4, 4)
67
62
  end
68
63
 
69
64
  it 'fails if the beginning is negative' do
70
- expect { subject.slice_samples(-1,2) }.to raise_error(RuntimeError)
65
+ expect { subject.slice_samples(-1, 2) }.to raise_error(RuntimeError)
71
66
  end
72
67
 
73
- it 'fails if the end is larger than the total size' do
74
- expect { subject.slice_samples(WAV_IN_DATA_SIZE,WAV_IN_DATA_SIZE+1) }
68
+ it 'fails if the length exceeds the wave\'s length' do
69
+ expect { subject.slice_samples(1, WAV_IN_DATA_SIZE) }
75
70
  .to raise_error(RuntimeError)
76
71
  end
77
72
 
@@ -101,15 +96,35 @@ describe Zappa::Clip do
101
96
  end
102
97
 
103
98
  describe '#+' do
104
- it 'combines the audio clips' do
105
- combined = subject + subject
106
- expect(combined.wav.data_size).to be(WAV_IN_DATA_SIZE * 2)
107
- end
108
-
109
- it 'fails if the wave formats are different' do
110
- sub_copy = Marshal.load(Marshal.dump(subject))
111
- sub_copy.wav.format.sample_rate = 22000
112
- expect { subject + sub_copy }.to raise_error(RuntimeError)
99
+ context 'concatenation' do
100
+ it 'adds two audio clips together' do
101
+ combined_clip = subject + subject
102
+ expect(combined_clip.wav.data_size).to be(WAV_IN_DATA_SIZE * 2)
103
+ end
104
+
105
+ it 'adds audio clip to empty clip' do
106
+ new_clip = Zappa::Clip.new
107
+ combined_clip = subject + new_clip
108
+ expect(combined_clip.wav.data_size).to be(WAV_IN_DATA_SIZE)
109
+ end
110
+
111
+ it 'fails if the wave formats are different' do
112
+ subject_copy = Marshal.load(Marshal.dump(subject))
113
+ subject_copy.wav.format.sample_rate = 22_000
114
+ expect { subject + subject_copy }.to raise_error(RuntimeError)
115
+ end
116
+
117
+ it 'fails if added to a non-wave object' do
118
+ non_wave = Object.new
119
+ expect { subject + non_wave }.to raise_error(RuntimeError)
120
+ end
121
+ end
122
+
123
+ context 'amplification' do
124
+ it 'amplifies clip when added to integer' do
125
+ expect(subject).to receive(:amplify).with(2)
126
+ subject + 2
127
+ end
113
128
  end
114
129
  end
115
130
  end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zappa::Generator do
4
+ let(:subject) { Zappa::Generator.new(44_100, 1, 16) }
5
+ let(:sine_path) { 'spec/audio/sine-1000hz.wav' }
6
+
7
+ describe '#generate' do
8
+ it 'raises an error for unknown types' do
9
+ expect { subject.generate('circle', 1000, 0.01) }
10
+ .to raise_error
11
+ end
12
+
13
+ it 'generates a 1000 Hz sine wave' do
14
+ file = File.open(sine_path, 'rb')
15
+ orig_wav = Zappa::Wave.new
16
+ orig_wav.unpack(file)
17
+
18
+ gen_clip = subject.generate('sine', 1000, 0.01)
19
+ expect(orig_wav).to eq(gen_clip.wav)
20
+ end
21
+
22
+ # generated sawtooth, square waves have slightly diff values
23
+ # from audacity generated waves. why?
24
+ pending 'generates a 1000 Hz sawtooth wave'
25
+ pending 'generates a 1000 Hz square wave'
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zappa::Processor do
4
+ let(:subject) { Zappa::Processor.new }
5
+ let(:samples) { [[0, -1], [24_000, -24_000]] }
6
+ let(:max_val) { 32_768 }
7
+ let(:min_val) { -32_768 }
8
+
9
+ describe '#amplify' do
10
+ let(:double_factor) { 6.020599913279623 } # double_factor db == 2x linear
11
+
12
+ before do
13
+ @amplified = subject.amplify(samples, double_factor)
14
+ end
15
+
16
+ it 'doubles sample values' do
17
+ expect(@amplified[0]).to eq(samples[0].collect { |s| s * 2 })
18
+ end
19
+
20
+ it 'does not let sample values go over the maximum value' do
21
+ expect(@amplified[1][0]).to eq(max_val)
22
+ end
23
+
24
+ it 'does not let sample values go under the minimum value' do
25
+ expect(@amplified[1][1]).to eq(min_val)
26
+ end
27
+ end
28
+
29
+ describe '#invert' do
30
+ it 'inverts all sample values' do
31
+ inverted = subject.invert(samples)
32
+ expect(inverted).to eq([[0, 1], [-24_000, 24_000]])
33
+ end
34
+ end
35
+
36
+ describe '#normalize' do
37
+ it 'normalizes all sample values' do
38
+ normalized = subject.normalize(samples, -0.1)
39
+ expect(normalized).to eq([[0, -1], [32_393, -32_393]])
40
+ end
41
+ end
42
+
43
+ describe '#compressor' do
44
+ before do
45
+ @compressed = subject.compress(samples, 4.0, -20.0)
46
+ end
47
+
48
+ it 'does not affect values below the threshold' do
49
+ expect(@compressed[0]).to eq([0, -1])
50
+ end
51
+
52
+ it 'affects values above the threshold according to the ratio' do
53
+ expect(@compressed[1]).to eq([18_819, -18_819])
54
+ end
55
+ end
56
+ end
@@ -1,9 +1,12 @@
1
1
  require 'bundler/setup'
2
2
  Bundler.setup
3
3
 
4
+ require 'coveralls'
5
+ Coveralls.wear!
6
+
4
7
  require 'zappa'
5
8
 
6
- RSpec.configure do |config|
9
+ RSpec.configure do |_config|
7
10
  # config
8
11
  end
9
12
 
@@ -8,12 +8,12 @@ describe Zappa::Format do
8
8
  it 'unpacks and packs each format chunk correctly' do
9
9
  src = File.read(wav_path)
10
10
  src_fmt = src.byteslice(src_offset, fmt_size)
11
-
11
+
12
12
  file = File.open(wav_path, 'rb')
13
13
  file.read(src_offset)
14
14
  fmt = Zappa::Format.new(file)
15
15
  pck_fmt = fmt.pack.force_encoding('UTF-8')
16
-
16
+
17
17
  expect(src_fmt).to eq(pck_fmt)
18
18
  end
19
19
  end
@@ -10,7 +10,7 @@ describe Zappa::RiffHeader do
10
10
  file = File.open(wav_path, 'rb')
11
11
  subject.unpack(file)
12
12
  pck_header = subject.pack
13
-
13
+
14
14
  expect(src_header).to eq(pck_header)
15
15
  end
16
- end
16
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zappa::SubChunkHeader do
4
+ OFFSET = 36
5
+
6
+ before :each do
7
+ file = File.read('spec/audio/basic-5s.wav')
8
+ @sc_header = file.byteslice(OFFSET, 8)
9
+ subject.unpack(@sc_header)
10
+ end
11
+
12
+ it 'unpacks subchunk data correctly' do
13
+ expect(subject.chunk_id).to eq('data')
14
+ expect(subject.chunk_size).to eq(882_000)
15
+ end
16
+
17
+ it 'packs subchunk data into a string' do
18
+ expect(subject.pack).to eq(@sc_header)
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zappa::WaveData do
4
+ before :each do
5
+ @samples = [[7, 5], [1, 3], [4, 4]]
6
+ @dummy_samples = [[1, 2], [3, 4]]
7
+ subject.set_samples(@samples)
8
+ end
9
+
10
+ describe 'set samples' do
11
+ before :each do
12
+ subject.set_samples(@dummy_samples)
13
+ end
14
+
15
+ it 'recalculates size correctly' do
16
+ expect(subject.chunk_size).to eq(8)
17
+ end
18
+
19
+ it 'replaces existing samples with new samples' do
20
+ expect(subject.samples).to eq(@dummy_samples)
21
+ end
22
+ end
23
+
24
+ describe 'equality' do
25
+ it 'is equal if the data is equal' do
26
+ d = Zappa::WaveData.new
27
+ d.set_samples(@samples)
28
+ expect(subject).to eq(d)
29
+ end
30
+
31
+ it 'is not equal if the data is not equal' do
32
+ d = Zappa::WaveData.new
33
+ d.set_samples(@dummy_samples)
34
+ expect(subject).not_to eq(d)
35
+ end
36
+ end
37
+ end
@@ -4,16 +4,20 @@ require 'tempfile'
4
4
  describe Zappa::Wave do
5
5
  let(:wav_path) { 'spec/audio/basic-5s.wav' }
6
6
  let(:empty_path) { 'does-not-exist.wav' }
7
- let(:wav_data_size) { 882000 }
8
- let(:wav_def_fmt) { { audio_format: 1,
9
- channels: 2,
10
- sample_rate: 44_100,
11
- byte_rate: 176_400,
12
- block_align: 4,
13
- bits_per_sample: 16 } }
14
- let(:wav_def_hdr) { { chunk_id: 'RIFF',
15
- chunk_size: 40,
16
- format: 'WAVE' } }
7
+ let(:wav_data_size) { 882_000 }
8
+ let(:wav_def_fmt) do
9
+ { audio_format: 1,
10
+ channels: 2,
11
+ sample_rate: 44_100,
12
+ byte_rate: 176_400,
13
+ block_align: 4,
14
+ bits_per_sample: 16 }
15
+ end
16
+ let(:wav_def_hdr) do
17
+ { chunk_id: 'RIFF',
18
+ chunk_size: 40,
19
+ format: 'WAVE' }
20
+ end
17
21
 
18
22
  before :each do
19
23
  @file = File.open(wav_path, 'rb')
@@ -26,44 +30,43 @@ describe Zappa::Wave do
26
30
  w = Zappa::Wave.new
27
31
  wav_def_hdr.each { |h| expect(h[1]).to eq(w.header.send(h[0])) }
28
32
  wav_def_fmt.each { |h| expect(h[1]).to eq(w.format.send(h[0])) }
29
- expect(w.data).to eq(nil)
30
33
  expect(w.data_size).to eq(0)
34
+ expect(w.samples).to eq([])
31
35
  end
32
36
  end
33
37
 
34
- describe '#update_data' do
35
- let (:slice_length) { 4 }
36
-
38
+ describe 'unpacks and packs wave data' do
37
39
  before :each do
38
- @new_data = @wav.data.byteslice(0, slice_length)
39
- @wav.update_data(@new_data)
40
+ @packed = @wav.pack
41
+ @file_data = File.read(wav_path)
40
42
  end
41
43
 
42
- it 'updates the wav data correctly' do
43
- expect(@wav.data).to eq(@new_data)
44
+ it 'does not alter the format' do
45
+ packed_fmt = @packed.byteslice(8, 8)
46
+ fmt = @file_data.byteslice(8, 8)
47
+ expect(packed_fmt).to eq(fmt)
44
48
  end
45
49
 
46
- it 'updates header data correctly' do
47
- expect(@wav.header.chunk_size).to eq(40)
48
- expect(@wav.data_size).to eq(slice_length)
50
+ it 'does not alter the wave data' do
51
+ packed_data = @packed.byteslice(16, wav_data_size - 16).force_encoding('UTF-8')
52
+ data = @file_data.byteslice(16, wav_data_size - 16)
53
+ expect(packed_data).to eq(data)
49
54
  end
50
55
  end
51
56
 
52
- describe '#pack' do
53
- it 'packs all sub-chunks into a string' do
54
- expect(@wav.pack.bytesize).to eq(@file.size)
57
+ describe '#set_samples' do
58
+ let (:samples) { [[3, 1], [3, 1]] }
59
+
60
+ before :each do
61
+ @wav.set_samples(samples)
55
62
  end
56
- end
57
63
 
58
- describe '#unpack' do
59
- it 'reads format headers correctly' do
60
- wav_def_fmt.each do |h|
61
- expect(h[1]).to eq(@wav.format.send(h[0]))
62
- end
64
+ it 'updates the header correctly' do
65
+ expect(@wav.header.chunk_size).to eq(44)
63
66
  end
64
67
 
65
- it 'reads data size correctly' do
66
- expect(@wav.data_size).to eq(wav_data_size)
68
+ it 'updates the wave data correctly' do
69
+ expect(@wav.samples).to eq(samples)
67
70
  end
68
71
  end
69
72
 
@@ -82,7 +85,7 @@ describe Zappa::Wave do
82
85
  end
83
86
 
84
87
  it 'is not equal to a wave with different data' do
85
- @new_wave.update_data('')
88
+ @new_wave.set_samples([3, 1])
86
89
  expect(@wav).not_to eq(@new_wave)
87
90
  end
88
91
  end
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency 'rake'
23
23
  spec.add_development_dependency 'rspec'
24
24
  spec.add_development_dependency 'pry'
25
+ spec.add_development_dependency 'coveralls'
25
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zappa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Varun Srinivasan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-20 00:00:00.000000000 Z
11
+ date: 2015-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: Zappa is a DSP toolbox for manipulating audio files.
70
84
  email:
71
85
  - varunsrin@gmail.com
@@ -85,17 +99,24 @@ files:
85
99
  - lib/zappa.rb
86
100
  - lib/zappa/clip.rb
87
101
  - lib/zappa/errors.rb
102
+ - lib/zappa/generator.rb
103
+ - lib/zappa/processor.rb
88
104
  - lib/zappa/version.rb
89
105
  - lib/zappa/wave.rb
90
106
  - lib/zappa/wave/format.rb
91
107
  - lib/zappa/wave/riff_header.rb
92
- - lib/zappa/wave/sub_chunk.rb
108
+ - lib/zappa/wave/sub_chunk_header.rb
109
+ - lib/zappa/wave/wave_data.rb
93
110
  - spec/audio/basic-5s.wav
111
+ - spec/audio/sine-1000hz.wav
94
112
  - spec/clip_spec.rb
113
+ - spec/generator_spec.rb
114
+ - spec/processor_spec.rb
95
115
  - spec/spec_helper.rb
96
116
  - spec/wave/format_spec.rb
97
117
  - spec/wave/riff_header_spec.rb
98
- - spec/wave/sub_chunk_spec.rb
118
+ - spec/wave/sub_chunk_header_spec.rb
119
+ - spec/wave/wave_data_spec.rb
99
120
  - spec/wave_spec.rb
100
121
  - zappa.gemspec
101
122
  homepage: ''
@@ -124,9 +145,13 @@ specification_version: 4
124
145
  summary: Ruby gem for manipulating audio files.
125
146
  test_files:
126
147
  - spec/audio/basic-5s.wav
148
+ - spec/audio/sine-1000hz.wav
127
149
  - spec/clip_spec.rb
150
+ - spec/generator_spec.rb
151
+ - spec/processor_spec.rb
128
152
  - spec/spec_helper.rb
129
153
  - spec/wave/format_spec.rb
130
154
  - spec/wave/riff_header_spec.rb
131
- - spec/wave/sub_chunk_spec.rb
155
+ - spec/wave/sub_chunk_header_spec.rb
156
+ - spec/wave/wave_data_spec.rb
132
157
  - spec/wave_spec.rb
@@ -1,29 +0,0 @@
1
- module Zappa
2
- class SubChunk
3
- attr_accessor :chunk_id, :chunk_size, :data
4
-
5
- def initialize(file = nil)
6
- if file.nil?
7
- @chunk_size = 0
8
- @data = nil
9
- else
10
- @chunk_id = file.read(4)
11
- @chunk_size = file.read(4).unpack('V').first
12
- @data = file.read(@chunk_size)
13
- end
14
- end
15
-
16
- def update(data)
17
- @chunk_size = data.bytesize
18
- @data = data
19
- end
20
-
21
- def pack
22
- @chunk_id + [@chunk_size].pack('V') + @data
23
- end
24
-
25
- def ==(other)
26
- other.data == data && other.chunk_id = chunk_id
27
- end
28
- end
29
- end
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Zappa::SubChunk do
4
- OFFSET = 36
5
-
6
- before do
7
- wav_path = 'spec/audio/basic-5s.wav'
8
- file = File.open(wav_path, 'rb')
9
- file.read(OFFSET)
10
- @pck = Zappa::SubChunk.new(file).pack
11
- @src = File.read(wav_path)
12
- end
13
-
14
- it 'unpacks and packs chunk_id correctly' do
15
- src_id = @src.byteslice(OFFSET, 4)
16
- pck_id = @pck.byteslice(0, 4)
17
- expect(src_id).to eq(pck_id)
18
- end
19
-
20
- it 'unpacks and packs data correctly' do
21
- src_size = @src.byteslice(OFFSET + 4, 4).unpack('V')
22
- pck_size = @pck.byteslice(4, 24).unpack('V')
23
- expect(src_size).to eq(pck_size)
24
-
25
- src_data = @src.byteslice(OFFSET + 8, src_size[0])
26
- pck_data = @pck.byteslice(8, pck_size[0]).force_encoding('UTF-8')
27
- expect(src_data).to eq(pck_data)
28
- end
29
- end