mb-sound 0.0.0.usegit

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 388a5a05c26d3be71704db135288a50cd39113ef9c5bc17355264e2435c5e50a
4
+ data.tar.gz: e259e5d393d9e3db20cfe94e4207aa1a6fdba04c0b7f5637c0763acffffa2c92
5
+ SHA512:
6
+ metadata.gz: 47fcab2c1ec6720df606fc889697b967ae302bdd5278b42ae7f1dab672e7e4aaff4ce77aeaf1385920ba8ed55f7f1cac0861e2f43704798a2dd4863e326b52f9
7
+ data.tar.gz: 93438d5b5496feb8d050c4512c9fdc9e0cb02a9c6bb87ca2228ae9ee1e60c6552a9b82503286c00b1bd71769f6cfb8b65f465aa8f2db1b6139cd20f6ec315be2
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .*.swp
10
+ .*.swo
11
+ /*.flac
12
+ /*.wav
13
+ /spec/examples.txt
@@ -0,0 +1 @@
1
+ mb-rb-sound
@@ -0,0 +1 @@
1
+ 2.7.1
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
3
+
4
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
5
+
6
+ # Math gems
7
+ gem 'cmath'
8
+ gem 'numo-narray'
9
+
10
+ # Interactive command line gems
11
+ gem 'pry'
12
+ gem 'pry-byebug'
13
+ gem 'pry-doc'
14
+ gem 'word_wrap'
15
+
16
+ # Testing gems
17
+ gem 'rspec'
@@ -0,0 +1,48 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ byebug (11.1.3)
5
+ cmath (1.0.0)
6
+ coderay (1.1.3)
7
+ diff-lcs (1.4.4)
8
+ method_source (1.0.0)
9
+ numo-narray (0.9.1.8)
10
+ pry (0.13.1)
11
+ coderay (~> 1.1)
12
+ method_source (~> 1.0)
13
+ pry-byebug (3.9.0)
14
+ byebug (~> 11.0)
15
+ pry (~> 0.13.0)
16
+ pry-doc (1.1.0)
17
+ pry (~> 0.11)
18
+ yard (~> 0.9.11)
19
+ rspec (3.9.0)
20
+ rspec-core (~> 3.9.0)
21
+ rspec-expectations (~> 3.9.0)
22
+ rspec-mocks (~> 3.9.0)
23
+ rspec-core (3.9.2)
24
+ rspec-support (~> 3.9.3)
25
+ rspec-expectations (3.9.2)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.9.0)
28
+ rspec-mocks (3.9.1)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.9.0)
31
+ rspec-support (3.9.3)
32
+ word_wrap (1.0.0)
33
+ yard (0.9.25)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ cmath
40
+ numo-narray
41
+ pry
42
+ pry-byebug
43
+ pry-doc
44
+ rspec
45
+ word_wrap
46
+
47
+ BUNDLED WITH
48
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020, Mike Bourgeous
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,131 @@
1
+ # mb-sound
2
+
3
+ A library of simple Ruby tools for processing sound. This is a companion
4
+ library to an [educational video series I'm making about sound][0].
5
+
6
+ You'll find simple functions for loading and saving audio files, playing and
7
+ recording sound in realtime (on Linux, and only for really simple algorithms).
8
+
9
+ This is written in Ruby for speed of prototyping, convenience of the Ruby
10
+ command line, and for the sake of novelty. Another reason is that, if we can
11
+ write sound algorithms that work fast enough in Ruby, then they'll definitely
12
+ be fast enough when ported to e.g. C, Rust, GLSL, etc.
13
+
14
+ More information may be added here as the video series progresses. For now,
15
+ the most interesting things you'll want to know about using this library are
16
+ installing the dependencies, and launching the interactive command line.
17
+
18
+ ## Installation and usage
19
+
20
+ You can either tinker within this project, or use it as a Git-sourced Gem in
21
+ your own projects.
22
+
23
+ If you don't already have a recent version of Ruby installed, and a Ruby version
24
+ manager of your choosing, I highly recommend using [RVM](https://rvm.io). You
25
+ can find installation instructions for RVM at https://rvm.io.
26
+
27
+ ### Using the project by itself
28
+
29
+ After getting RVM installed, you'll want to clone this repository, install
30
+ Ruby, and install the Gems needed by this code.
31
+
32
+ I also recommend making a separate projects directory just for this video
33
+ series.
34
+
35
+ This assumes basic familiarity with the Linux/macOS/WSL command line, or enough
36
+ independent knowledge to make this work on your operating system of choice.
37
+ I'll provide an overly detailed Linux example here:
38
+
39
+ ```bash
40
+ # Make a project directory (substitute your own preferred paths)
41
+ cd ~/projects
42
+ mkdir sound_code_series
43
+ cd sound_code_series
44
+
45
+ # Install Ruby
46
+ # (disable-binary is needed on Ubuntu 20.04 to fix "/usr/bin/mkdir not found"
47
+ # error in the binary package of 2.7.1)
48
+ rvm install --disable-binary 2.7.1
49
+
50
+ # Clone the repo
51
+ git clone git@github.com:mike-bourgeous/mb-sound.git
52
+ cd mb-sound
53
+
54
+ # Install Gem dependencies
55
+ cd mb-sound
56
+ gem install bundler
57
+ bundle install
58
+ ```
59
+
60
+ Now that everything's installed, you are ready to start playing with sound:
61
+
62
+ ```bash
63
+ # Launch the interactive command line
64
+ bin/pry.rb
65
+ ```
66
+
67
+ See the Examples section for some things to try.
68
+
69
+
70
+ ### Using the project as a dependency
71
+
72
+ If you're already familiar with Ruby and Gems, then you can add this repo as a
73
+ dependency to a new project's Gemfile.
74
+
75
+ *TODO: add gemspec so this actually works*
76
+
77
+ ```ruby
78
+ # your-project/Gemfile
79
+ gem 'mb-sound', git: 'git@github.com:mike-bourgeous/mb-sound.git'
80
+ ```
81
+
82
+ ## Examples
83
+
84
+ TODO: Examples will be added as the library progresses.
85
+
86
+ ## Testing
87
+
88
+ You can run the integrated test suite with `rspec`.
89
+
90
+ ## Contributing
91
+
92
+ Since this library is meant to accompany a video series, most new features will
93
+ be targeted at what's covered in episodes as they are released. If you think of
94
+ something cool to add that relates to the video series, then please open a pull
95
+ request.
96
+
97
+ Pull requests are also welcome if you want to add or improve support for new
98
+ platforms.
99
+
100
+ ## License
101
+
102
+ This project is released under a 2-clause BSD license. See the LICENSE file.
103
+
104
+ Note, however, that if you use the FFT routines from FFTW, your app may be
105
+ subject to the GPL.
106
+
107
+ ## Standing on the shoulders of giants
108
+
109
+ ### Dependencies
110
+ This code uses some really cool other projects either directly or indirectly:
111
+
112
+ - FFMPEG
113
+ - Numo gems
114
+ - Pry interactive console for Ruby
115
+ - sndfile
116
+
117
+ ### References
118
+
119
+ There are lots of excellent resources out there for learning sound and signal
120
+ processing:
121
+
122
+ - I've created a [playlist with some cool videos by others][1]
123
+ - [Circles, sines, and signals][2] is a great interactive demonstration of
124
+ waves and Fourier transforms
125
+ - Online [books by Julius O. Smith][3] (I recommend buying the print versions)
126
+
127
+
128
+ [0]: https://www.youtube.com/playlist?list=PLpRqC8LaADXnwve3e8gI239eDNRO3Nhya
129
+ [1]: https://www.youtube.com/playlist?list=PLpRqC8LaADXlYhKRTwSpdW3ineaQnM9zK
130
+ [2]: https://jackschaedler.github.io/circles-sines-signals/
131
+ [3]: https://ccrma.stanford.edu/~jos/#books
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ require 'benchmark'
7
+
8
+ require 'io/console'
9
+
10
+ Bundler.require
11
+
12
+ $LOAD_PATH << File.expand_path('../lib', __dir__)
13
+
14
+ require 'mb/sound'
15
+
16
+ puts WordWrap.ww(<<-EOF, IO.console.winsize[1], true)
17
+
18
+ \e[33;1mWelcome to the interactive sound environment!\e[0m
19
+
20
+ If you're new to Pry, check out https://pry.github.io/.
21
+
22
+ \e[3mSome things to try:\e[0m
23
+
24
+ \e[1mls \e[32mMB::Sound\e[0m (that's \e[1mls\e[0m for "list") to get a list of the easiest to
25
+ use sound functions.
26
+
27
+ EOF
28
+
29
+ Pry.pry(
30
+ prompt: Pry::Prompt.new(:mb_sound, "The interactive sound environment's default prompt", [
31
+ _pry_a = -> (obj, nest, pry) {
32
+ "\1\e[36m\2#{File.basename($0)}\1\e[0m\2 \1\e[32m\2#{obj}\1\e[0;2m\2(#{nest}) > \1\e[0m\2"
33
+ },
34
+ -> (obj, nest, pry) {
35
+ ' ' * _pry_a.call(obj, nest, pry).gsub(/(\x01|\x02|\e\[[0-9;]*[A-Za-z])/, '').length
36
+ }
37
+ ])
38
+ )
@@ -0,0 +1,25 @@
1
+ require 'cmath'
2
+
3
+ require_relative 'sound/version'
4
+
5
+ module MB
6
+ # Convenience functions for making quick work of sound.
7
+ #
8
+ # Top-level namespace for the mb-sound library.
9
+ module Sound
10
+ # Converts a Ruby Array of any nesting depth to a Numo::NArray with a
11
+ # matching number of dimensions. All nested arrays at a particular depth
12
+ # should have the same size (that is, all positions should be filled).
13
+ #
14
+ # Chained subscripts on the Array become comma-separated subscripts on the
15
+ # NArray, so array[1][2] would become narray[1, 2].
16
+ def self.array_to_narray(array)
17
+ return array if array.is_a?(Numo::NArray)
18
+ narray = Numo::NArray[array]
19
+ narray.reshape(*narray.shape[1..-1])
20
+ end
21
+ end
22
+ end
23
+
24
+ require_relative 'sound/io_input'
25
+ require_relative 'sound/ffmpeg_input'
@@ -0,0 +1,109 @@
1
+ require 'shellwords'
2
+
3
+ require_relative 'io_input'
4
+
5
+ module MB
6
+ module Sound
7
+ # An input stream type that uses FFMPEG to parse an audio stream from most
8
+ # file formats.
9
+ class FFMPEGInput < IOInput
10
+ attr_reader :filename, :rate, :channels, :frames, :info
11
+
12
+ # Uses ffprobe to get information about the specified file. Pass an
13
+ # unescaped filename (no shell backslashes, quotes, etc).
14
+ #
15
+ # Returns an array of Hashes, one Hash for each audio stream in the file.
16
+ def self.parse_info(filename)
17
+ fnesc = filename.shellescape
18
+
19
+ raw_info = `ffprobe -loglevel 8 -show_streams -select_streams a #{fnesc}`
20
+
21
+ streams = raw_info.each_line.chunk_while { |b, a|
22
+ a != "[STREAM]\n" && b != "[/STREAM]\n"
23
+ }.select { |chunk|
24
+ chunk[0] == "[STREAM]\n"
25
+ }.map { |chunk|
26
+ # Filter out start/end tags and any line that isn't a key/value pair
27
+ chunk.reject { |l|
28
+ l.start_with?('[') || !l.include?('=')
29
+ }.map { |l|
30
+ kv = l.split('=', 2).map(&:strip)
31
+
32
+ kv[0] = kv[0].to_sym
33
+
34
+ case kv[1]
35
+ when /\A[0-9]+\z/
36
+ kv[1] = kv[1].to_i
37
+
38
+ when /\A[0-9]+\.[0-9]+\z/
39
+ # FIXME: this parses channel_layout: "7.1" as float
40
+ kv[1] = kv[1].to_f
41
+
42
+ when %r{\A[0-9]+/0\z}
43
+ # Prevent division by zero
44
+ kv[1] = 0
45
+
46
+ when %r{\A[0-9]+/[0-9]+\z}
47
+ kv[1] = kv[1].to_r
48
+ end
49
+
50
+ kv
51
+ }.to_h
52
+ }
53
+ end
54
+
55
+ # Initializes an input stream that will use the `ffmpeg` command to read
56
+ # the specified audio stream (first stream if unspecified) from the given
57
+ # file.
58
+ #
59
+ # +filename+ - The filename to read.
60
+ # +stream_id+ - The number of audio stream to read in multi-stream files (0
61
+ # is the default).
62
+ # +resample+ - If an integer, asks ffmpeg to resample to that rate.
63
+ # +channels+ - If not nil, asks ffmpeg to convert the number of channels.
64
+ def initialize(filename, stream_id: 0, resample: nil, channels: nil)
65
+ raise "File is not readable" unless File.readable?(filename)
66
+ @filename = filename
67
+ @fnesc = filename.shellescape
68
+
69
+ raise "Stream ID must be an integer" unless stream_id.is_a?(Integer)
70
+ @stream_id = stream_id
71
+
72
+ # First get info for all streams from ffprobe
73
+ @info = FFMPEGInput.parse_info(@filename)
74
+
75
+ if channels
76
+ raise "Channel count must be an integer greater than 0" unless channels.is_a?(Integer) && channels > 0
77
+ @channels = channels
78
+ else
79
+ @channels = @info[stream_id][:channels]
80
+ raise "Missing channels from stream info" unless @channels
81
+ end
82
+
83
+ if resample
84
+ raise "Sampling rate must be an integer greater than 0" unless resample.is_a?(Integer) && resample > 0
85
+ @rate = resample
86
+ else
87
+ @rate = @info[stream_id][:sample_rate]
88
+ end
89
+
90
+ start = @info[stream_id][:start_time]
91
+ start = 0 unless start.is_a?(Numeric)
92
+ duration = @info[stream_id][:duration]
93
+ @frames = (start + duration * @rate).ceil
94
+
95
+ resample_opt = resample ? "-ar '#{@rate}'" : ''
96
+ channels_opt = channels ? "-ac '#{@channels}' -af 'aresample=matrix_encoding=dplii'" : ''
97
+ @pipe = IO.popen(["sh", "-c", "ffmpeg -nostdin -loglevel 8 -i #{@fnesc} #{resample_opt} #{channels_opt} -f f32le -"], "r")
98
+
99
+ super(@pipe, @channels)
100
+ end
101
+
102
+ # Closes the input pipe to arecord, which should cause arecord to exit.
103
+ def close
104
+ @pipe.close if @pipe && !@pipe.closed?
105
+ @pipe = nil
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,34 @@
1
+ module MB
2
+ module Sound
3
+ # A process_stream-compatible input stream that reads 32-bit little-endian
4
+ # floats from a byte-wise IO stream (e.g. STDIN).
5
+ class IOInput
6
+ attr_reader :channels
7
+
8
+ # Initializes an IO-reading audio input stream for the given I/O object and
9
+ # number of channels.
10
+ def initialize(io, channels)
11
+ raise 'IO must respond to :read' unless io.respond_to?(:read)
12
+ raise 'Channels must be an int >= 1' unless channels.is_a?(Integer) && channels >= 1
13
+
14
+ @io = io
15
+ @channels = channels
16
+ @frame_bytes = channels * 4
17
+ end
18
+
19
+ # Reads +frames+ frames of raw 32-bit floats for +@channels+ channels from
20
+ # the IO given to the constructor. Returns an array of @channels NArrays.
21
+ def read(frames)
22
+ bytes = @io.read(frames * @frame_bytes)
23
+ return [ Numo::SFloat[] ] * @channels if bytes.nil? # end of file
24
+
25
+ raise 'Bytes read was not a multiple of frame size' unless bytes.size % @frame_bytes == 0
26
+
27
+ frames_read = bytes.size / @frame_bytes
28
+
29
+ # TODO: each_slice + transpose is probably slow; do something faster
30
+ bytes.unpack('e*').each_slice(@channels).to_a.transpose.map { |c| Sound.array_to_narray(c) }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module MB
2
+ module Sound
3
+ VERSION = "0.0.0.usegit"
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'lib/mb/sound/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "mb-sound"
5
+ spec.version = MB::Sound::VERSION
6
+ spec.authors = ["Mike Bourgeous"]
7
+ spec.email = ["mike@mikebourgeous.com"]
8
+
9
+ spec.summary = %q{A library of simple Ruby tools for processing sound.}
10
+ spec.description = %q{
11
+ A library of simple Ruby tools for processing sound. This is a companion library
12
+ to an educational video series about sound.
13
+ }
14
+ spec.homepage = "https://github.com/mike-bourgeous/mb-sound"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.1")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/mike-bourgeous/mb-sound"
19
+ #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|sounds)/}) }
25
+ end
26
+ spec.bindir = "bin"
27
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mb-sound
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0.usegit
5
+ platform: ruby
6
+ authors:
7
+ - Mike Bourgeous
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: "\n A library of simple Ruby tools for processing sound. This is a
14
+ companion library\n to an educational video series about sound.\n "
15
+ email:
16
+ - mike@mikebourgeous.com
17
+ executables:
18
+ - prysound.rb
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".gitignore"
23
+ - ".ruby-gemset"
24
+ - ".ruby-version"
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE
28
+ - README.md
29
+ - Rakefile
30
+ - bin/prysound.rb
31
+ - lib/mb/sound.rb
32
+ - lib/mb/sound/ffmpeg_input.rb
33
+ - lib/mb/sound/io_input.rb
34
+ - lib/mb/sound/version.rb
35
+ - mb-sound.gemspec
36
+ homepage: https://github.com/mike-bourgeous/mb-sound
37
+ licenses: []
38
+ metadata:
39
+ homepage_uri: https://github.com/mike-bourgeous/mb-sound
40
+ source_code_uri: https://github.com/mike-bourgeous/mb-sound
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 2.7.1
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.1
55
+ requirements: []
56
+ rubygems_version: 3.1.2
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: A library of simple Ruby tools for processing sound.
60
+ test_files: []