zappa 0.1.0 → 0.2.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: 7451348ebf69ebf1dcfcccdb63e9a4f1b639aa46
4
- data.tar.gz: cde7d8e96634e6e974cffd61b7d657e0ec6411e3
3
+ metadata.gz: 9392a71ab4fbaf850daa6ff3a3166f16ccbcb23d
4
+ data.tar.gz: 33ea67e02179e8afeddf795844f306a43f5a1705
5
5
  SHA512:
6
- metadata.gz: e776a7938072269a5cc3065b915c3c92317989ee05a4595abbaa7b3b4b862c5239309010a717c3c14182bd7124a152d9ee429a689f9ba57370e807d2230fcf4e
7
- data.tar.gz: c356ed13032cf939aed42c4ea63a68cd73c7ed621dbde65213dae121551a930d0ca57462d1b1f54f1871f530a20444962d0474ff713f209280f042972e648a10
6
+ metadata.gz: c262b0d9416d09bf225b10bbcbfce69e759de1b8740adeda12a588f3f0dbac1226f3be55c1995f8afcee4059999916e2c42ee2014da7e4dab56e07bb0ba953cc
7
+ data.tar.gz: 5d64cdb5a2334858c12f53ac8e0459f26c7e213bea352a4ea635b3f1e5e8cd063ad7c466586e65dd2af9add0671a97d35faf02e7c95ea5173e2098181e20a2e5
data/README.md CHANGED
@@ -22,24 +22,31 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- You can open a wave file:
25
+ At the core of Zappa is the Clip, an immutable audio unit. Import a wav file
26
+ into a clip:
26
27
 
27
- ```
28
- include 'zappa'
29
- s = Segment.from_file('this_is_a_song.wav')
30
- ```
28
+ require 'zappa'
29
+
30
+ clip = Zappa::Clip.new
31
+ clip.from_file('this_is_a_song.wav')
31
32
 
32
- and then read any of its properties:
33
+ The clip will create a safe copy of the wav before you can edit it. Remember,
34
+ clips are immutable so any destructive operations return a new clip.
33
35
 
34
- ```
35
- puts s.format
36
- ```
36
+ You can slice clips into smaller chunks:
37
37
 
38
- and save it to a different location:
38
+ slice_a = clip.slice(0, 1000) # clip containing 1st second
39
+ slice_b = clip.slice(1000, 2000) # clip containing 3rd second
39
40
 
40
- ```
41
- s.to_file('output.wav')
42
- ```
41
+ You can also join clips:
42
+
43
+ joined_clip = slice_a + slice_b # clip containing 1st and 3rd seconds
44
+
45
+ Once you're done editing a clip, you can export it:
46
+
47
+ joined_clip.export('output.wav')
48
+
49
+ That's it for now. DSP tools are coming soon!
43
50
 
44
51
 
45
52
  ## Contributing
data/ROADMAP.md CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  0.2 - Slicing
9
9
  -------------
10
- - Slice segments into smaller parts (by time or samples)
11
- - Join segments
10
+ - Slice clips into smaller parts (by time or samples)
11
+ - Join clips
12
12
 
13
13
 
14
14
  0.3 - DSP - Basic
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task default: :spec
7
+ task test: :spec
data/circle.yml ADDED
@@ -0,0 +1,3 @@
1
+ dependencies:
2
+ pre:
3
+ - sudo apt-get install ffmpeg
data/lib/zappa/clip.rb ADDED
@@ -0,0 +1,80 @@
1
+ require 'tempfile'
2
+ require 'open3'
3
+ require 'pry'
4
+
5
+ module Zappa
6
+ class Clip
7
+ attr_accessor :wav, :cache
8
+
9
+ def initialize(wav=nil)
10
+ if wav
11
+ @wav = wav
12
+ else
13
+ @wav = Wave.new
14
+ end
15
+ @cache = nil
16
+ end
17
+
18
+ def from_file(path)
19
+ @wav.unpack(path)
20
+ persist_cache
21
+ end
22
+
23
+ def export(path)
24
+ persist_cache if @cache.nil?
25
+ cmd = 'ffmpeg -i ' + @cache + ' -y -f wav ' + path
26
+ Open3.popen3(cmd) do |_stdin, _stdout, _stderr, wait_thr|
27
+ fail 'Cannot export to' + path unless wait_thr.value.success?
28
+ end
29
+ end
30
+
31
+ def slice(from, to)
32
+ slice_samples(ms_to_samples(from), ms_to_samples(to))
33
+ end
34
+
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)
42
+ clone(slice)
43
+ end
44
+
45
+ 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)
51
+ end
52
+
53
+ private
54
+
55
+ def ms_to_samples(ms)
56
+ (ms * @wav.format.sample_rate / 1000).round
57
+ end
58
+
59
+ def persist_cache
60
+ tmp = Tempfile.new('zappa')
61
+ @cache = tmp.path
62
+ File.write(@cache, @wav.pack)
63
+ end
64
+
65
+ def ffmpeg_wav_export(source, destination)
66
+ cmd = 'ffmpeg -i ' + source + ' -vn -y -f wav ' + destination
67
+ Open3.popen3(cmd) do |_stdin, _stdout, _stderr, wait_thr|
68
+ fail 'Cannot open file ' + path unless wait_thr.value.success?
69
+ end
70
+ destination
71
+ end
72
+
73
+ def clone(data = nil)
74
+ clone = Clip.new
75
+ clone.wav = Marshal.load(Marshal.dump(@wav))
76
+ clone.wav.update_data(data) if data
77
+ clone
78
+ end
79
+ end
80
+ end
data/lib/zappa/errors.rb CHANGED
@@ -1,7 +1,4 @@
1
1
  module Zappa
2
- class FileFormatError < StandardError
3
- end
4
-
5
2
  class FileError < StandardError
6
3
  end
7
4
  end
data/lib/zappa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Zappa
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,49 @@
1
+ module Zappa
2
+ class Format
3
+ attr_accessor :name, :audio_format, :bits_per_sample, :block_align,
4
+ :byte_rate, :channels, :sample_rate, :chunk_size, :unknown
5
+
6
+ FMT_SIZE = 16
7
+
8
+ def initialize(file = nil)
9
+ if file.nil?
10
+ @chunk_id = 'fmt '
11
+ @chunk_size = FMT_SIZE
12
+ @audio_format = 1
13
+ @channels = 2
14
+ @sample_rate = 44100
15
+ @byte_rate = 176_400
16
+ @block_align = 4
17
+ @bits_per_sample = 16
18
+ else
19
+ @chunk_id = file.read(4)
20
+ @chunk_size = file.read(4).unpack('V').first
21
+ unpack(file.read(@chunk_size))
22
+ end
23
+ end
24
+
25
+ def ==(other)
26
+ pack == other.pack
27
+ end
28
+
29
+ def pack
30
+ fmt = @chunk_id
31
+ fmt += [@chunk_size].pack('V')
32
+ fmt += [@audio_format].pack('v')
33
+ fmt += [@channels].pack('v')
34
+ fmt += [@sample_rate].pack('V')
35
+ fmt += [@byte_rate].pack('V')
36
+ fmt += [@block_align].pack('v')
37
+ fmt + [@bits_per_sample].pack('v')
38
+ end
39
+
40
+ def unpack(data)
41
+ @audio_format = data.byteslice(0..1).unpack('v').first
42
+ @channels = data.byteslice(2..3).unpack('v').first
43
+ @sample_rate = data.byteslice(4..7).unpack('V').first
44
+ @byte_rate = data.byteslice(8..11).unpack('V').first
45
+ @block_align = data.byteslice(12..13).unpack('v').first
46
+ @bits_per_sample = data.byteslice(14..15).unpack('v').first
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ module Zappa
2
+ class RiffHeader
3
+ attr_reader :chunk_id, :format
4
+ attr_accessor :chunk_size
5
+
6
+ def initialize(file = nil)
7
+ if file.nil?
8
+ @chunk_id = 'RIFF'
9
+ @chunk_size = 40
10
+ @format = 'WAVE'
11
+ else
12
+ unpack(file)
13
+ end
14
+ end
15
+
16
+ def pack
17
+ @chunk_id + [@chunk_size].pack('V') + @format
18
+ end
19
+
20
+ def unpack(file)
21
+ @chunk_id = file.read(4)
22
+ @chunk_size = file.read(4).unpack('V').first
23
+ @format = file.read(4)
24
+ fail 'ID is not RIFF' unless @chunk_id == 'RIFF'
25
+ fail 'Format is not WAVE' unless @format == 'WAVE'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
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
data/lib/zappa/wave.rb CHANGED
@@ -1,103 +1,74 @@
1
- # http://soundfile.sapp.org/doc/WaveFormat/
1
+ require 'zappa/wave/format'
2
+ require 'zappa/wave/riff_header'
3
+ require 'zappa/wave/sub_chunk'
4
+
5
+ # WAV Spec: http://soundfile.sapp.org/doc/WaveFormat/
2
6
 
3
7
  module Zappa
4
8
  class Wave
5
- attr_accessor :format, :data, :data_size, :file_path
6
- SUBCHUNKS = %q('fmt', 'data')
7
- KNOWN_FMT_SIZE = 16
9
+ attr_accessor :header, :format
8
10
 
9
- def initialize(path)
10
- @header = {}
11
- @format = {}
12
- @data = {}
11
+ def initialize
12
+ @header = RiffHeader.new
13
+ @format = Format.new
14
+ @wave_data = SubChunk.new
15
+ end
13
16
 
14
- begin
15
- @file = File.new(path, 'rb')
16
- rescue
17
- raise FileError.new('Could not find ' + path)
18
- else
19
- @file_path = @file.path
20
- unpack_wav
21
- end
17
+ def data
18
+ @wave_data.data
22
19
  end
23
20
 
24
- def update
25
- raw_file = pack_riff_header + pack_fmt + pack_data
26
- File.write(@file_path, raw_file)
21
+ def data_size
22
+ @wave_data.chunk_size
27
23
  end
28
24
 
29
- def pack_riff_header
30
- hdr = ''
31
- hdr += @header[:chunk_id]
32
- hdr += [@header[:chunk_size]].pack('V')
33
- hdr += @header[:format]
25
+ def frame_size
26
+ @format.bits_per_sample * @format.channels / 8
34
27
  end
35
28
 
36
- def pack_fmt
37
- fmt = 'fmt '
38
- fmt += [16].pack('V')
39
- fmt += [@format[:audio_format]].pack('v')
40
- fmt += [@format[:channels]].pack('v')
41
- fmt += [@format[:sample_rate]].pack('V')
42
- fmt += [@format[:byte_rate]].pack('V')
43
- fmt += [@format[:block_align]].pack('v')
44
- fmt += [@format[:bits_per_sample]].pack('v')
29
+ def sample_count
30
+ data_size / frame_size
45
31
  end
46
32
 
47
- def pack_data
48
- data = 'data'
49
- data += [@data[:size]].pack('V')
50
- data += @data[:data]
33
+ def ==(other)
34
+ other.data == data
51
35
  end
52
36
 
53
- def unpack_wav
54
- unpack_riff_header
55
- while @data[:data].nil?
56
- unpack_subchunk
57
- end
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
58
43
  end
59
44
 
60
- def unpack_riff_header
61
- @header[:chunk_id] = @file.read(4)
62
- @header[:chunk_size] = @file.read(4).unpack('V').first
63
- @header[:format] = @file.read(4)
64
- raise FileFormatError.new('Format is not WAVE') unless @header[:format] == 'WAVE'
65
- raise FileFormatError.new('ID is not RIFF') unless @header[:chunk_id] == 'RIFF'
45
+ def pack
46
+ pack = @header.pack + @format.pack + @wave_data.pack
66
47
  end
67
48
 
68
- def unpack_subchunk
69
- id = @file.read(4).strip
70
- if SUBCHUNKS.include?(id)
71
- send('unpack_' + id)
49
+ def unpack(source)
50
+ begin
51
+ file = File.open(path_to(source), 'rb')
52
+ rescue
53
+ fail 'Unable to open WAV file'
72
54
  else
73
- unpack_unknown
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
64
+ end
74
65
  end
75
66
  end
76
67
 
77
- def unpack_fmt
78
- size = @file.read(4).unpack('V').first
79
- @format[:audio_format] = @file.read(2).unpack('v').first
80
- @format[:channels] = @file.read(2).unpack('v').first
81
- @format[:sample_rate] = @file.read(4).unpack('V').first
82
- @format[:byte_rate] = @file.read(4).unpack('V').first
83
- @format[:block_align] = @file.read(2).unpack('v').first
84
- @format[:bits_per_sample] = @file.read(2).unpack('v').first
85
- unread = size - KNOWN_FMT_SIZE
86
- @format[:unknown] = @file.read(unread) if unread > 0
87
- end
88
-
89
- def unpack_data
90
- @data[:size] = @file.read(4).unpack('V').first
91
- @data[:data] = @file.read(@data[:size])
92
- end
93
-
94
- def unpack_unknown
95
- size = @file.read(4).unpack('V').first
96
- @file.read(size)
97
- end
98
-
99
- def ==(other)
100
- other.format == format && other.data == data
68
+ def path_to(source)
69
+ return source if source.class == String
70
+ return source.path if source.class == File
71
+ fail 'cannot unpack type: ' + source.class.to_s
101
72
  end
102
73
  end
103
74
  end
data/lib/zappa.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  require 'zappa/version'
2
- require 'zappa/segment'
2
+ require 'zappa/clip'
3
3
  require 'zappa/wave'
4
4
  require 'zappa/errors'
data/spec/clip_spec.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ WAV_IN = 'spec/audio/basic-5s.wav'
5
+ WAV_IN_DATA_SIZE = 882000
6
+
7
+ describe Zappa::Clip do
8
+ before :each do
9
+ subject.from_file(WAV_IN)
10
+ end
11
+
12
+ describe '#from_file' do
13
+ it 'makes a safe wav copy of the file' do
14
+ orig_file = File.open(WAV_IN, 'rb')
15
+ orig_wav = Zappa::Wave.new
16
+ orig_wav.unpack(orig_file)
17
+ cached_wav = Zappa::Wave.new
18
+ cached_wav.unpack(subject.cache)
19
+ expect(orig_wav).to eq(cached_wav)
20
+ end
21
+
22
+ it 'has a path value' do
23
+ expect(subject.cache.nil?).to eq(false)
24
+ end
25
+
26
+ it 'raises error if file does not exist' do
27
+ c = Zappa::Clip.new
28
+ expect { c.from_file('some_foo') }.to raise_error(RuntimeError)
29
+ end
30
+
31
+ pending 'raises error if ffmpeg is not installed'
32
+ end
33
+
34
+ describe '#export' do
35
+ before do
36
+ @tmp = Tempfile.new('zappa-spec')
37
+ subject.cache = nil
38
+ subject.export(@tmp.path)
39
+ end
40
+
41
+ it 'persisted the file' do
42
+ expect(subject.cache.nil?).to eq(false)
43
+ # expect the data of cache matches data in object
44
+ end
45
+
46
+ it 'exports the clip correctly' do
47
+ subject.from_file(WAV_IN)
48
+ export_wav = Zappa::Wave.new
49
+ export_wav.unpack(File.open(@tmp.path, 'rb'))
50
+ expect(subject.wav).to eq(export_wav)
51
+ end
52
+
53
+ it 'raises error for invalid path' do
54
+ expect { subject.export('some:foo') }.to raise_error(RuntimeError)
55
+ end
56
+
57
+ pending 'raises error if ffmpeg is not installed'
58
+ end
59
+
60
+ describe '#slice_samples' do
61
+ 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)
67
+ end
68
+
69
+ it 'fails if the beginning is negative' do
70
+ expect { subject.slice_samples(-1,2) }.to raise_error(RuntimeError)
71
+ end
72
+
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) }
75
+ .to raise_error(RuntimeError)
76
+ end
77
+
78
+ it 'slices the wave by sample range' do
79
+ expect(@slice.wav.data_size).to eq(16)
80
+ end
81
+
82
+ it 'invalidates the cache' do
83
+ expect(@slice.cache).to eq(nil)
84
+ end
85
+ end
86
+
87
+ describe '#slice' do
88
+ before :each do
89
+ @slice = subject.slice(0, 4)
90
+ end
91
+
92
+ it 'slices the wav by ms range' do
93
+ samples_in_ms = (4 * 44.1).round
94
+ total_bytes = samples_in_ms * 4
95
+ expect(@slice.wav.data_size).to eq(total_bytes)
96
+ end
97
+
98
+ it 'invalidates the cache' do
99
+ expect(@slice.cache).to eq(nil)
100
+ end
101
+ end
102
+
103
+ 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)
113
+ end
114
+ end
115
+ end
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,7 @@ RSpec.configure do |config|
7
7
  # config
8
8
  end
9
9
 
10
- def wav_data(encoded_wav)
11
- encoded_wav.split('dataPu').last
10
+ def slice_and_unpack(item, offset, size, enc)
11
+ item = item.byteslice(offset, size)
12
+ item = item.unpack(enc) if enc
12
13
  end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zappa::Format do
4
+ let(:src_offset) { 12 }
5
+ let(:fmt_size) { 24 }
6
+ let(:wav_path) { 'spec/audio/basic-5s.wav' }
7
+
8
+ it 'unpacks and packs each format chunk correctly' do
9
+ src = File.read(wav_path)
10
+ src_fmt = src.byteslice(src_offset, fmt_size)
11
+
12
+ file = File.open(wav_path, 'rb')
13
+ file.read(src_offset)
14
+ fmt = Zappa::Format.new(file)
15
+ pck_fmt = fmt.pack.force_encoding('UTF-8')
16
+
17
+ expect(src_fmt).to eq(pck_fmt)
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zappa::RiffHeader do
4
+ let(:wav_path) { 'spec/audio/basic-5s.wav' }
5
+
6
+ it 'unpacks and packs header correctly' do
7
+ src = File.read(wav_path)
8
+ src_header = src.byteslice(0..11)
9
+
10
+ file = File.open(wav_path, 'rb')
11
+ subject.unpack(file)
12
+ pck_header = subject.pack
13
+
14
+ expect(src_header).to eq(pck_header)
15
+ end
16
+ end
@@ -0,0 +1,29 @@
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
data/spec/wave_spec.rb CHANGED
@@ -1,32 +1,99 @@
1
1
  require 'spec_helper'
2
2
  require 'tempfile'
3
3
 
4
- WAV_IN = 'spec/audio/basic-5s.wav'
5
- WAV_EX = 'does-not-exist.wav'
6
-
7
4
  describe Zappa::Wave do
8
- it 'reads format headers correctly' do
9
- fmt_header = { 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
- w = Zappa::Wave.new(WAV_IN)
16
- expect(w.format).to eq(fmt_header)
5
+ let(:wav_path) { 'spec/audio/basic-5s.wav' }
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' } }
17
+
18
+ before :each do
19
+ @file = File.open(wav_path, 'rb')
20
+ @wav = Zappa::Wave.new
21
+ @wav.unpack(@file)
17
22
  end
18
23
 
19
- it 'updates file without altering it' do
20
- orig = Zappa::Wave.new(WAV_IN)
21
- orig.update
22
- current = Zappa::Wave.new(WAV_IN)
23
- expect(orig).to eq(current)
24
+ describe '#initialize' do
25
+ it 'has a default header, format chunks and empty wave chunk' do
26
+ w = Zappa::Wave.new
27
+ wav_def_hdr.each { |h| expect(h[1]).to eq(w.header.send(h[0])) }
28
+ wav_def_fmt.each { |h| expect(h[1]).to eq(w.format.send(h[0])) }
29
+ expect(w.data).to eq(nil)
30
+ expect(w.data_size).to eq(0)
31
+ end
24
32
  end
25
33
 
26
- it 'raises error for incorrect path' do
27
- expect { Zappa::Wave.new(WAV_EX) }.to raise_error(Zappa::FileError)
34
+ describe '#update_data' do
35
+ let (:slice_length) { 4 }
36
+
37
+ before :each do
38
+ @new_data = @wav.data.byteslice(0, slice_length)
39
+ @wav.update_data(@new_data)
40
+ end
41
+
42
+ it 'updates the wav data correctly' do
43
+ expect(@wav.data).to eq(@new_data)
44
+ end
45
+
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)
49
+ end
28
50
  end
29
51
 
30
- pending 'handles wave files with unknown subchunks'
31
- pending 'handles wave files with optional tmp data'
52
+ describe '#pack' do
53
+ it 'packs all sub-chunks into a string' do
54
+ expect(@wav.pack.bytesize).to eq(@file.size)
55
+ end
56
+ end
57
+
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
63
+ end
64
+
65
+ it 'reads data size correctly' do
66
+ expect(@wav.data_size).to eq(wav_data_size)
67
+ end
68
+ end
69
+
70
+ describe '#==' do
71
+ before :each do
72
+ @new_wave = Marshal.load(Marshal.dump(@wav))
73
+ end
74
+
75
+ it 'is equal to a wave with identical data' do
76
+ expect(@wav).to eq(@new_wave)
77
+ end
78
+
79
+ it 'is equal to a wave with different fmt data' do
80
+ @new_wave.format.bits_per_sample = 2
81
+ expect(@wav).to eq(@new_wave)
82
+ end
83
+
84
+ it 'is not equal to a wave with different data' do
85
+ @new_wave.update_data('')
86
+ expect(@wav).not_to eq(@new_wave)
87
+ end
88
+ end
89
+
90
+ describe '#path_to' do
91
+ it 'returns input, if provided a file' do
92
+ expect(subject.path_to(@file)).to eq(@file.path)
93
+ end
94
+
95
+ it 'returns the file at path, if provided a path' do
96
+ expect(subject.path_to(wav_path)).to eq(wav_path)
97
+ end
98
+ end
32
99
  end
data/zappa.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ['Varun Srinivasan']
10
10
  spec.email = ['varunsrin@gmail.com']
11
11
  spec.summary = 'Ruby gem for manipulating audio files.'
12
- spec.description = 'Write a longer description. Optional.'
12
+ spec.description = 'Zappa is a DSP toolbox for manipulating audio files.'
13
13
  spec.homepage = ''
14
14
  spec.license = 'MIT'
15
15
 
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.1.0
4
+ version: 0.2.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-17 00:00:00.000000000 Z
11
+ date: 2015-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,7 +66,7 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
- description: Write a longer description. Optional.
69
+ description: Zappa is a DSP toolbox for manipulating audio files.
70
70
  email:
71
71
  - varunsrin@gmail.com
72
72
  executables: []
@@ -81,14 +81,21 @@ files:
81
81
  - README.md
82
82
  - ROADMAP.md
83
83
  - Rakefile
84
+ - circle.yml
84
85
  - lib/zappa.rb
86
+ - lib/zappa/clip.rb
85
87
  - lib/zappa/errors.rb
86
- - lib/zappa/segment.rb
87
88
  - lib/zappa/version.rb
88
89
  - lib/zappa/wave.rb
90
+ - lib/zappa/wave/format.rb
91
+ - lib/zappa/wave/riff_header.rb
92
+ - lib/zappa/wave/sub_chunk.rb
89
93
  - spec/audio/basic-5s.wav
90
- - spec/segment_spec.rb
94
+ - spec/clip_spec.rb
91
95
  - spec/spec_helper.rb
96
+ - spec/wave/format_spec.rb
97
+ - spec/wave/riff_header_spec.rb
98
+ - spec/wave/sub_chunk_spec.rb
92
99
  - spec/wave_spec.rb
93
100
  - zappa.gemspec
94
101
  homepage: ''
@@ -117,6 +124,9 @@ specification_version: 4
117
124
  summary: Ruby gem for manipulating audio files.
118
125
  test_files:
119
126
  - spec/audio/basic-5s.wav
120
- - spec/segment_spec.rb
127
+ - spec/clip_spec.rb
121
128
  - spec/spec_helper.rb
129
+ - spec/wave/format_spec.rb
130
+ - spec/wave/riff_header_spec.rb
131
+ - spec/wave/sub_chunk_spec.rb
122
132
  - spec/wave_spec.rb
data/lib/zappa/segment.rb DELETED
@@ -1,44 +0,0 @@
1
- require 'tempfile'
2
- require 'open3'
3
- require 'pry'
4
-
5
- module Zappa
6
- class Segment
7
- attr_reader :source
8
-
9
- def initialize(path = nil)
10
- if path
11
- from_file(path)
12
- else
13
- @source = nil
14
- end
15
- end
16
-
17
- def from_file(path)
18
- @source = Wave.new(safe_copy(path))
19
- end
20
-
21
- def to_file(path)
22
- raise FileError.new('No data in Segment') if @source.nil?
23
- cmd = 'ffmpeg -i ' + @source.file_path + ' -y -f wav ' + path
24
- Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
25
- raise ('Cannot export to' + path ) unless wait_thr.value.success?
26
- end
27
- end
28
-
29
- def ==(other)
30
- source == other.source
31
- end
32
-
33
- private
34
-
35
- def safe_copy(path)
36
- tmp = Tempfile.new('zappa')
37
- cmd = 'ffmpeg -i ' + path + ' -vn -y -f wav ' + tmp.path
38
- Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
39
- raise ('Cannot open file ' + path ) unless wait_thr.value.success?
40
- end
41
- tmp.path
42
- end
43
- end
44
- end
data/spec/segment_spec.rb DELETED
@@ -1,43 +0,0 @@
1
- require 'spec_helper'
2
- require 'tempfile'
3
-
4
- WAV_IN = 'spec/audio/basic-5s.wav'
5
-
6
- describe Zappa::Segment do
7
- before do
8
- subject.from_file(WAV_IN)
9
- end
10
-
11
- describe '#from_file' do
12
- it 'makes a safe copy of the source wav file' do
13
- expect(Zappa::Wave.new(WAV_IN))
14
- .to eq(Zappa::Wave.new(subject.source.file_path))
15
- end
16
-
17
- it 'raises error if file does not exist' do
18
- expect { Zappa::Segment.new('some_foo') }.to raise_error(RuntimeError)
19
- end
20
-
21
- pending 'raises error if ffmpeg is not installed'
22
- pending 'only permits wav files'
23
- end
24
-
25
- describe '#to_file' do
26
- it 'exports the segment to a wav file' do
27
- tmp = Tempfile.new('zappa-spec')
28
- subject.to_file(tmp.path)
29
- expect(Zappa::Wave.new(WAV_IN)).to eq(Zappa::Wave.new(tmp.path))
30
- end
31
-
32
- it 'raises error if segment is empty' do
33
- w = Zappa::Segment.new
34
- expect { w.to_file('foo.wav') }.to raise_error(Zappa::FileError)
35
- end
36
-
37
- it 'raises error for invalid path' do
38
- expect { subject.to_file('some:foo') }.to raise_error(RuntimeError)
39
- end
40
-
41
- pending 'raises error if ffmpeg is not installed'
42
- end
43
- end