zappa 0.2.0 → 0.3.0

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