mb-sound 0.0.0.usegit

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.
@@ -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: []