ffi-sndfile 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +230 -0
- data/lib/ffi-sndfile.rb +5 -0
- data/lib/sndfile/commands.rb +99 -0
- data/lib/sndfile/constants.rb +264 -0
- data/lib/sndfile/error.rb +80 -0
- data/lib/sndfile/format.rb +124 -0
- data/lib/sndfile/formats.rb +60 -0
- data/lib/sndfile/info.rb +27 -0
- data/lib/sndfile/library_version.rb +52 -0
- data/lib/sndfile/native/enums.rb +64 -0
- data/lib/sndfile/native/functions.rb +176 -0
- data/lib/sndfile/native/handle.rb +23 -0
- data/lib/sndfile/native/structs.rb +194 -0
- data/lib/sndfile/native.rb +28 -0
- data/lib/sndfile/sound_file.rb +289 -0
- data/lib/sndfile/strings.rb +74 -0
- data/lib/sndfile/version.rb +5 -0
- data/lib/sndfile/virtual_io.rb +86 -0
- data/lib/sndfile.rb +44 -0
- metadata +80 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b113eec6ca95e787da990c8492074e91536d69f0d88c0f14ce54b36b48191080
|
|
4
|
+
data.tar.gz: dd944287fbcb0def4f3194ec136b3c4e9c007819e7e3970994bf444c7fc0b8b2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0fd357282d36ca986a2301099b2f1966c337c042f265e10571fc4d6bf47e4830818bbf6fc363bd45d27a65329368ab6f0edf6e538ae2f9fdb80b539bdc582025
|
|
7
|
+
data.tar.gz: cf60f0f27122780304cbd27e59d941ccacc7b0917459590622af232762ec7de852036cb922989bb9c1f98b5724169880610120c70f9d2ac74f04bd7fd8233151
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2026 Roel van Dijk
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# ffi-sndfile
|
|
2
|
+
|
|
3
|
+
Ruby FFI bindings for [libsndfile](https://libsndfile.github.io/libsndfile/), the library for reading and writing sound files: WAV, AIFF, FLAC, Ogg/Vorbis, Opus, MP3, CAF and many more.
|
|
4
|
+
|
|
5
|
+
See [CHANGELOG.md](CHANGELOG.md) for what changed.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### Requirements
|
|
10
|
+
|
|
11
|
+
- Ruby 3.2 or newer
|
|
12
|
+
- libsndfile 1.0.28 or newer, including its development files:
|
|
13
|
+
- Ubuntu / Debian: `sudo apt-get install libsndfile1-dev`
|
|
14
|
+
- Fedora: `sudo dnf install libsndfile-devel`
|
|
15
|
+
- Arch Linux: `sudo pacman -S libsndfile`
|
|
16
|
+
- macOS (Homebrew): `brew install libsndfile`
|
|
17
|
+
|
|
18
|
+
Check your version with `pkg-config --modversion sndfile`. With libsndfile older than 1.0.28, `require "sndfile"` raises a `LoadError` that names the version it found. MP3 support (the `:mpeg` format and `bitrate_mode`) needs libsndfile 1.1.0 or newer, built with MPEG support.
|
|
19
|
+
|
|
20
|
+
### Gem Installation
|
|
21
|
+
|
|
22
|
+
Add to your Gemfile:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem "ffi-sndfile"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
And run:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then load it with `require "sndfile"` (or `require "ffi-sndfile"`; Bundler's auto-require uses that one). The library lives in the `SndFile` module.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Reading a Sound File
|
|
39
|
+
|
|
40
|
+
`SndFile.open` (the same as `SndFile::SoundFile.open`) opens a file. With a block, it closes the file afterwards (also when the block raises) and returns the block's value:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
require "sndfile"
|
|
44
|
+
|
|
45
|
+
SndFile.open("song.flac") do |file|
|
|
46
|
+
file.samplerate # => 44100
|
|
47
|
+
file.channels # => 2
|
|
48
|
+
file.frames # => 9_261_000
|
|
49
|
+
file.duration # => 210.0 (seconds)
|
|
50
|
+
file.format # => #<SndFile::Format flac/pcm_16>
|
|
51
|
+
|
|
52
|
+
# Read in buffers of 4096 frames
|
|
53
|
+
file.each_buffer(4096) do |samples|
|
|
54
|
+
# samples is a flat Array, channels interleaved: [left, right, left, right, ...]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Samples are floats from -1.0 to 1.0 by default. Pass `type:` to get another sample type; libsndfile converts between it and the file's encoding:
|
|
60
|
+
|
|
61
|
+
| `type:` | Values |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `:float` (default) | Floats, -1.0 to 1.0 |
|
|
64
|
+
| `:double` | Floats, -1.0 to 1.0 |
|
|
65
|
+
| `:short` | 16 bit integers, -32768 to 32767 |
|
|
66
|
+
| `:int` | 32 bit integers |
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
SndFile.open("take.wav") do |file|
|
|
70
|
+
file.read(1024) # up to 1024 frames as floats ([] at the end of the file)
|
|
71
|
+
file.read(1024, type: :short) # the next 1024 frames as 16 bit integers
|
|
72
|
+
file.seek(0) # back to the first frame; also seek(n, :cur) and seek(-n, :end)
|
|
73
|
+
file.read # everything up to the end of the file
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Without a block, `open` returns the `SndFile::SoundFile`; call `close` when you're done. The file is also closed when it is garbage collected, but don't rely on that when writing: closing is what writes the final header.
|
|
78
|
+
|
|
79
|
+
### Writing a Sound File
|
|
80
|
+
|
|
81
|
+
Writing needs a sample rate, a channel count and a format. The format can come from the file name's extension:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
samples = Array.new(44_100) { |i| 0.5 * Math.sin(2 * Math::PI * 440 * i / 44_100) } # one second of A4
|
|
85
|
+
|
|
86
|
+
SndFile.open("a4.wav", :write, samplerate: 44_100, channels: 1) do |file|
|
|
87
|
+
file.write(samples) # => 44100 (frames written)
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or give it with `format:` (a major format such as `:wav` or `:flac`) and optionally `subtype:` (the encoding) and `endian:`:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
SndFile.open("a4.aiff", :write, format: :aiff, subtype: :pcm_24, samplerate: 48_000, channels: 2) do |file|
|
|
95
|
+
file << [0.1, -0.1] << [0.2, -0.2] # two stereo frames
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Without `subtype:`, common formats use a sensible default: `:pcm_16` for WAV, AIFF, FLAC and the like, `:vorbis` for Ogg and `:mpeg_layer_iii` for MP3. An unknown name or a combination libsndfile can't write (such as `format: :wav, subtype: :vorbis`) raises `ArgumentError`. Open a file with `:rdwr` to read and write it, for example to append.
|
|
100
|
+
|
|
101
|
+
### Ogg/Vorbis with Vorbis Comments
|
|
102
|
+
|
|
103
|
+
String metadata (title, artist, ...) is written in the format's own way: as Vorbis comments in Ogg and FLAC files, as an INFO chunk in WAV files, and so on. Most formats need the strings, and encoder settings such as the quality, before the first audio is written, so pass them to `open` or set them first thing:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
SndFile.open("song.ogg", :write, samplerate: 44_100, channels: 2,
|
|
107
|
+
strings: { title: "Heart of Gold", artist: "Neil Young" }) do |file|
|
|
108
|
+
file.strings.album = "Harvest"
|
|
109
|
+
file.strings[:date] = "1972"
|
|
110
|
+
file.vbr_quality = 0.6 # 0.0 (smallest file) to 1.0 (best quality)
|
|
111
|
+
|
|
112
|
+
file.write(samples)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
SndFile.open("song.ogg") do |file|
|
|
116
|
+
file.format # => #<SndFile::Format ogg/vorbis>
|
|
117
|
+
file.strings.title # => "Heart of Gold"
|
|
118
|
+
file.strings.to_h # => {title: "Heart of Gold", artist: "Neil Young", date: "1972", album: "Harvest"}
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The strings are `title`, `copyright`, `software`, `artist`, `comment`, `date`, `album`, `license`, `tracknumber` (also `track_number`) and `genre`. They are read back as UTF-8. Setting a string after audio was written, or one the format doesn't support, raises `SndFile::SndFileError`.
|
|
123
|
+
|
|
124
|
+
Use `format: :ogg, subtype: :opus` (or a `.opus` file name) for Ogg/Opus.
|
|
125
|
+
|
|
126
|
+
### Files in Memory and Other IO Objects
|
|
127
|
+
|
|
128
|
+
Instead of a path, you can pass an IO-like object, such as a `StringIO`, a `File` or a socket. libsndfile reads and writes it through `#read`, `#write`, `#seek` and `#pos`. A format is needed for writing, since there is no file name to take it from:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
require "stringio"
|
|
132
|
+
|
|
133
|
+
io = StringIO.new(+"".b)
|
|
134
|
+
SndFile.open(io, :write, format: :ogg, samplerate: 44_100, channels: 1) { |file| file.write(samples) }
|
|
135
|
+
io.string # => "OggS\x00\x02..."
|
|
136
|
+
|
|
137
|
+
io.rewind
|
|
138
|
+
SndFile.open(io) { |file| file.frames } # => 44100
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Exceptions raised by the IO object are raised again by the `SndFile` call that caused them.
|
|
142
|
+
|
|
143
|
+
### Other Settings
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
file.compression_level = 0.8 # FLAC, Ogg, MP3: 0.0 (fastest) to 1.0 (smallest)
|
|
147
|
+
file.bitrate_mode = :variable # MP3: :constant, :average or :variable
|
|
148
|
+
file.normalize_float = false # read and write floats unscaled (default: -1.0..1.0)
|
|
149
|
+
file.signal_max # the largest absolute sample value
|
|
150
|
+
file.update_header_now # make a file being written readable already
|
|
151
|
+
file.truncate(1000) # cut the file to 1000 frames
|
|
152
|
+
file.byterate # current bytes per second, or nil
|
|
153
|
+
file.log_info # libsndfile's log of opening the file
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Every other libsndfile command is available through `file.command`, which takes an `SFC_*` constant or its name as a symbol and passes the data on, for example a `SndFile::Native::BroadcastInfo` struct for the Broadcast WAV `bext` chunk:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
bext = SndFile::Native::BroadcastInfo.new
|
|
160
|
+
bext.set_string(:description, "Field recording")
|
|
161
|
+
|
|
162
|
+
SndFile.open("field.wav", :write, samplerate: 48_000, channels: 2) do |file|
|
|
163
|
+
file.command(:set_broadcast_info, bext) # => 1 (SF_TRUE); before any audio is written
|
|
164
|
+
file.write(samples)
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Formats
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
SndFile::Format.new(:ogg, :vorbis).to_i # => 2097248 (SF_FORMAT_OGG | SF_FORMAT_VORBIS)
|
|
172
|
+
SndFile::Format.new(0x170002) # => #<SndFile::Format flac/pcm_16>
|
|
173
|
+
SndFile::Format.new(:flac).valid?(samplerate: 44_100, channels: 2) # => true
|
|
174
|
+
|
|
175
|
+
SndFile.major_formats.map(&:name) # => ["AIFF (Apple/SGI)", "AU (Sun/NeXT)", ...]
|
|
176
|
+
SndFile.subtypes.map(&:name) # => ["Signed 8 bit PCM", "Signed 16 bit PCM", ...]
|
|
177
|
+
SndFile.simple_formats # common combinations, each with its format, name and extension
|
|
178
|
+
SndFile::Formats.describe(:vorbis) # => #<data SndFile::FormatDescription format=..., name="Vorbis", extension=nil>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The lists come from the loaded libsndfile, so they show what this installation supports.
|
|
182
|
+
|
|
183
|
+
### Errors
|
|
184
|
+
|
|
185
|
+
Every libsndfile error is raised as a subclass of `SndFile::SndFileError`, so `rescue SndFile::SndFileError` catches them all. The message is libsndfile's own error text, and `#code` is libsndfile's error number.
|
|
186
|
+
|
|
187
|
+
| Class | libsndfile error |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `SndFile::UnrecognisedFormatError` | `SF_ERR_UNRECOGNISED_FORMAT`: not a sound file, or a format libsndfile can't read |
|
|
190
|
+
| `SndFile::SystemError` | `SF_ERR_SYSTEM`: an operating system error, such as a missing file |
|
|
191
|
+
| `SndFile::MalformedFileError` | `SF_ERR_MALFORMED_FILE`: the file is damaged |
|
|
192
|
+
| `SndFile::UnsupportedEncodingError` | `SF_ERR_UNSUPPORTED_ENCODING`: an encoding libsndfile can't decode |
|
|
193
|
+
| `SndFile::SndFileError` | any of libsndfile's other, internal errors |
|
|
194
|
+
| `SndFile::ClosedFileError` | the `SoundFile` was used after it was closed |
|
|
195
|
+
|
|
196
|
+
### libsndfile Version
|
|
197
|
+
|
|
198
|
+
`SndFile.version` describes the loaded libsndfile library:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
SndFile.version.to_s # => "libsndfile-1.2.2"
|
|
202
|
+
SndFile.version.number # => "1.2.2"
|
|
203
|
+
SndFile.version >= "1.1.0" # => true
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Threads
|
|
207
|
+
|
|
208
|
+
libsndfile calls that read or write a file (`open`, `read`, `write`, `seek`, `close`, ...) let other Ruby threads keep running while they wait on the disk.
|
|
209
|
+
|
|
210
|
+
### Low-level libsndfile Functions
|
|
211
|
+
|
|
212
|
+
Every function in `sndfile.h` is attached to `SndFile::Native`, an `FFI::Library`, for anything `SndFile::SoundFile` doesn't cover. Every struct is there too (`Native::Info` for `SF_INFO`, `Native::Instrument`, `Native::CartInfo`, `Native::ChunkInfo`, ...), and every constant is in `SndFile` (`SndFile::SF_FORMAT_OGG`, `SndFile::SFC_GET_LOG_INFO`, ...). Parameters for modes, string types, commands and seek origins also take symbols:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
info = SndFile::Native::Info.new
|
|
216
|
+
sndfile = SndFile::Native.sf_open("take.wav", :read, info)
|
|
217
|
+
info[:frames] # => 1000
|
|
218
|
+
SndFile::Native.sf_get_string(sndfile, :title) # => "Old Man"
|
|
219
|
+
SndFile::Native.sf_close(sndfile) # => 0
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`sf_open`, `sf_open_fd` and `sf_open_virtual` return a handle that closes itself when it is garbage collected, unless you close it with `Native.sf_close` first; they raise a `SndFile::SndFileError` instead of returning `NULL`. Otherwise these functions return libsndfile's error codes instead of raising, and their API follows libsndfile rather than this gem.
|
|
223
|
+
|
|
224
|
+
## Development
|
|
225
|
+
|
|
226
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up the project, run the specs and which Ruby versions changes need to support.
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT License. See [LICENSE.txt](LICENSE.txt).
|
data/lib/ffi-sndfile.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
# Methods for SoundFile that wrap libsndfile's sf_command. #command gives
|
|
5
|
+
# access to every SFC_* command; the other methods cover common ones.
|
|
6
|
+
#
|
|
7
|
+
# Encoder settings (#vbr_quality=, #compression_level=, #bitrate_mode=)
|
|
8
|
+
# must be set before the first audio data is written.
|
|
9
|
+
module Commands
|
|
10
|
+
# Runs an sf_command and returns its integer result. +command+ is an
|
|
11
|
+
# SFC_* constant or its symbol (e.g. :get_log_info); +data+ is a pointer
|
|
12
|
+
# (or FFI::Struct) of +size+ bytes, as the command expects.
|
|
13
|
+
def command(command, data = nil, size = data.respond_to?(:size) ? data.size : 0)
|
|
14
|
+
Native.sf_command(handle, command, data, size)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# libsndfile's log of what it did while opening the file.
|
|
18
|
+
def log_info
|
|
19
|
+
buffer = FFI::MemoryPointer.new(:char, 4096)
|
|
20
|
+
command(SFC_GET_LOG_INFO, buffer, buffer.size)
|
|
21
|
+
buffer.read_string
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Sets the encoding quality for variable bit rate formats such as Ogg
|
|
25
|
+
# Vorbis, Opus, FLAC and MP3: 0.0 (lowest) to 1.0 (highest).
|
|
26
|
+
def vbr_quality=(quality)
|
|
27
|
+
set_double(SFC_SET_VBR_ENCODING_QUALITY, quality, "VBR encoding quality")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Sets the compression level for formats such as FLAC, Ogg and MP3:
|
|
31
|
+
# 0.0 (least compression) to 1.0 (most compression).
|
|
32
|
+
def compression_level=(level)
|
|
33
|
+
set_double(SFC_SET_COMPRESSION_LEVEL, level, "compression level")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The bitrate mode (:constant, :average or :variable), for MPEG files.
|
|
37
|
+
# Returns nil when the format has none.
|
|
38
|
+
def bitrate_mode
|
|
39
|
+
mode = command(SFC_GET_BITRATE_MODE)
|
|
40
|
+
Native.enum_type(:sf_bitrate_mode)[mode] unless mode.negative?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def bitrate_mode=(mode)
|
|
44
|
+
value = FFI::MemoryPointer.new(:int)
|
|
45
|
+
value.write_int(Native.enum_type(:sf_bitrate_mode).to_native(mode, nil))
|
|
46
|
+
check_command(SFC_SET_BITRATE_MODE, value, value.size, "bitrate mode #{mode.inspect}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Whether floating point data is normalized to -1.0..1.0 when reading
|
|
50
|
+
# and writing (libsndfile's default is true).
|
|
51
|
+
def normalize_float?
|
|
52
|
+
command(SFC_GET_NORM_FLOAT) == SF_TRUE
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def normalize_float=(normalize)
|
|
56
|
+
command(SFC_SET_NORM_FLOAT, nil, normalize ? SF_TRUE : SF_FALSE)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Writes the file header now, so the file is valid while it is being
|
|
60
|
+
# written.
|
|
61
|
+
def update_header_now
|
|
62
|
+
command(SFC_UPDATE_HEADER_NOW)
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Truncates the file to +frames+ frames.
|
|
67
|
+
def truncate(frames)
|
|
68
|
+
value = FFI::MemoryPointer.new(:int64)
|
|
69
|
+
value.write_int64(frames)
|
|
70
|
+
return self if command(SFC_FILE_TRUNCATE, value).zero?
|
|
71
|
+
|
|
72
|
+
raise SndFileError, "Cannot truncate #{inspect} to #{frames} frames"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The largest absolute sample value in the file (SFC_CALC_SIGNAL_MAX),
|
|
76
|
+
# or with normalized: true, as a fraction of the format's full scale.
|
|
77
|
+
def signal_max(normalized: false)
|
|
78
|
+
value = FFI::MemoryPointer.new(:double)
|
|
79
|
+
command(normalized ? SFC_CALC_NORM_SIGNAL_MAX : SFC_CALC_SIGNAL_MAX, value)
|
|
80
|
+
Error.check_sndfile(handle)
|
|
81
|
+
value.read_double
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def set_double(command_id, number, description)
|
|
87
|
+
value = FFI::MemoryPointer.new(:double)
|
|
88
|
+
value.write_double(Float(number))
|
|
89
|
+
check_command(command_id, value, value.size, "#{description} #{number}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Runs a SET command that returns SF_TRUE on success.
|
|
93
|
+
def check_command(command_id, data, size, description)
|
|
94
|
+
return if command(command_id, data, size) == SF_TRUE
|
|
95
|
+
|
|
96
|
+
raise SndFileError, "Cannot set #{description} for #{format} files"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The constants from sndfile.h, in the order of that header, with its comments.
|
|
4
|
+
module SndFile
|
|
5
|
+
# The following file types can be read and written. A file type would
|
|
6
|
+
# consist of a major type (ie SF_FORMAT_WAV) bitwise ORed with a minor type
|
|
7
|
+
# (ie SF_FORMAT_PCM). SF_FORMAT_TYPEMASK and SF_FORMAT_SUBMASK can be used
|
|
8
|
+
# to separate the major and minor file types.
|
|
9
|
+
|
|
10
|
+
# Major formats.
|
|
11
|
+
SF_FORMAT_WAV = 0x010000
|
|
12
|
+
SF_FORMAT_AIFF = 0x020000
|
|
13
|
+
SF_FORMAT_AU = 0x030000
|
|
14
|
+
SF_FORMAT_RAW = 0x040000
|
|
15
|
+
SF_FORMAT_PAF = 0x050000
|
|
16
|
+
SF_FORMAT_SVX = 0x060000
|
|
17
|
+
SF_FORMAT_NIST = 0x070000
|
|
18
|
+
SF_FORMAT_VOC = 0x080000
|
|
19
|
+
SF_FORMAT_IRCAM = 0x0A0000
|
|
20
|
+
SF_FORMAT_W64 = 0x0B0000
|
|
21
|
+
SF_FORMAT_MAT4 = 0x0C0000
|
|
22
|
+
SF_FORMAT_MAT5 = 0x0D0000
|
|
23
|
+
SF_FORMAT_PVF = 0x0E0000
|
|
24
|
+
SF_FORMAT_XI = 0x0F0000
|
|
25
|
+
SF_FORMAT_HTK = 0x100000
|
|
26
|
+
SF_FORMAT_SDS = 0x110000
|
|
27
|
+
SF_FORMAT_AVR = 0x120000
|
|
28
|
+
SF_FORMAT_WAVEX = 0x130000
|
|
29
|
+
SF_FORMAT_SD2 = 0x160000
|
|
30
|
+
SF_FORMAT_FLAC = 0x170000
|
|
31
|
+
SF_FORMAT_CAF = 0x180000
|
|
32
|
+
SF_FORMAT_WVE = 0x190000
|
|
33
|
+
SF_FORMAT_OGG = 0x200000
|
|
34
|
+
SF_FORMAT_MPC2K = 0x210000
|
|
35
|
+
SF_FORMAT_RF64 = 0x220000
|
|
36
|
+
SF_FORMAT_MPEG = 0x230000
|
|
37
|
+
|
|
38
|
+
# Subtypes from here on.
|
|
39
|
+
SF_FORMAT_PCM_S8 = 0x0001
|
|
40
|
+
SF_FORMAT_PCM_16 = 0x0002
|
|
41
|
+
SF_FORMAT_PCM_24 = 0x0003
|
|
42
|
+
SF_FORMAT_PCM_32 = 0x0004
|
|
43
|
+
SF_FORMAT_PCM_U8 = 0x0005
|
|
44
|
+
SF_FORMAT_FLOAT = 0x0006
|
|
45
|
+
SF_FORMAT_DOUBLE = 0x0007
|
|
46
|
+
SF_FORMAT_ULAW = 0x0010
|
|
47
|
+
SF_FORMAT_ALAW = 0x0011
|
|
48
|
+
SF_FORMAT_IMA_ADPCM = 0x0012
|
|
49
|
+
SF_FORMAT_MS_ADPCM = 0x0013
|
|
50
|
+
SF_FORMAT_GSM610 = 0x0020
|
|
51
|
+
SF_FORMAT_VOX_ADPCM = 0x0021
|
|
52
|
+
SF_FORMAT_NMS_ADPCM_16 = 0x0022
|
|
53
|
+
SF_FORMAT_NMS_ADPCM_24 = 0x0023
|
|
54
|
+
SF_FORMAT_NMS_ADPCM_32 = 0x0024
|
|
55
|
+
SF_FORMAT_G721_32 = 0x0030
|
|
56
|
+
SF_FORMAT_G723_24 = 0x0031
|
|
57
|
+
SF_FORMAT_G723_40 = 0x0032
|
|
58
|
+
SF_FORMAT_DWVW_12 = 0x0040
|
|
59
|
+
SF_FORMAT_DWVW_16 = 0x0041
|
|
60
|
+
SF_FORMAT_DWVW_24 = 0x0042
|
|
61
|
+
SF_FORMAT_DWVW_N = 0x0043
|
|
62
|
+
SF_FORMAT_DPCM_8 = 0x0050
|
|
63
|
+
SF_FORMAT_DPCM_16 = 0x0051
|
|
64
|
+
SF_FORMAT_VORBIS = 0x0060
|
|
65
|
+
SF_FORMAT_OPUS = 0x0064
|
|
66
|
+
SF_FORMAT_ALAC_16 = 0x0070
|
|
67
|
+
SF_FORMAT_ALAC_20 = 0x0071
|
|
68
|
+
SF_FORMAT_ALAC_24 = 0x0072
|
|
69
|
+
SF_FORMAT_ALAC_32 = 0x0073
|
|
70
|
+
SF_FORMAT_MPEG_LAYER_I = 0x0080
|
|
71
|
+
SF_FORMAT_MPEG_LAYER_II = 0x0081
|
|
72
|
+
SF_FORMAT_MPEG_LAYER_III = 0x0082
|
|
73
|
+
|
|
74
|
+
# Endian-ness options.
|
|
75
|
+
SF_ENDIAN_FILE = 0x00000000
|
|
76
|
+
SF_ENDIAN_LITTLE = 0x10000000
|
|
77
|
+
SF_ENDIAN_BIG = 0x20000000
|
|
78
|
+
SF_ENDIAN_CPU = 0x30000000
|
|
79
|
+
|
|
80
|
+
SF_FORMAT_SUBMASK = 0x0000FFFF
|
|
81
|
+
SF_FORMAT_TYPEMASK = 0x0FFF0000
|
|
82
|
+
SF_FORMAT_ENDMASK = 0x30000000
|
|
83
|
+
|
|
84
|
+
# The following are the valid command numbers for the sf_command()
|
|
85
|
+
# interface. The use of these commands is documented in the file
|
|
86
|
+
# command.html in the doc directory of the source code distribution.
|
|
87
|
+
SFC_GET_LIB_VERSION = 0x1000
|
|
88
|
+
SFC_GET_LOG_INFO = 0x1001
|
|
89
|
+
SFC_GET_CURRENT_SF_INFO = 0x1002
|
|
90
|
+
SFC_GET_NORM_DOUBLE = 0x1010
|
|
91
|
+
SFC_GET_NORM_FLOAT = 0x1011
|
|
92
|
+
SFC_SET_NORM_DOUBLE = 0x1012
|
|
93
|
+
SFC_SET_NORM_FLOAT = 0x1013
|
|
94
|
+
SFC_SET_SCALE_FLOAT_INT_READ = 0x1014
|
|
95
|
+
SFC_SET_SCALE_INT_FLOAT_WRITE = 0x1015
|
|
96
|
+
SFC_GET_SIMPLE_FORMAT_COUNT = 0x1020
|
|
97
|
+
SFC_GET_SIMPLE_FORMAT = 0x1021
|
|
98
|
+
SFC_GET_FORMAT_INFO = 0x1028
|
|
99
|
+
SFC_GET_FORMAT_MAJOR_COUNT = 0x1030
|
|
100
|
+
SFC_GET_FORMAT_MAJOR = 0x1031
|
|
101
|
+
SFC_GET_FORMAT_SUBTYPE_COUNT = 0x1032
|
|
102
|
+
SFC_GET_FORMAT_SUBTYPE = 0x1033
|
|
103
|
+
SFC_CALC_SIGNAL_MAX = 0x1040
|
|
104
|
+
SFC_CALC_NORM_SIGNAL_MAX = 0x1041
|
|
105
|
+
SFC_CALC_MAX_ALL_CHANNELS = 0x1042
|
|
106
|
+
SFC_CALC_NORM_MAX_ALL_CHANNELS = 0x1043
|
|
107
|
+
SFC_GET_SIGNAL_MAX = 0x1044
|
|
108
|
+
SFC_GET_MAX_ALL_CHANNELS = 0x1045
|
|
109
|
+
SFC_SET_ADD_PEAK_CHUNK = 0x1050
|
|
110
|
+
SFC_UPDATE_HEADER_NOW = 0x1060
|
|
111
|
+
SFC_SET_UPDATE_HEADER_AUTO = 0x1061
|
|
112
|
+
SFC_FILE_TRUNCATE = 0x1080
|
|
113
|
+
SFC_SET_RAW_START_OFFSET = 0x1090
|
|
114
|
+
SFC_SET_DITHER_ON_WRITE = 0x10A0
|
|
115
|
+
SFC_SET_DITHER_ON_READ = 0x10A1
|
|
116
|
+
SFC_GET_DITHER_INFO_COUNT = 0x10A2
|
|
117
|
+
SFC_GET_DITHER_INFO = 0x10A3
|
|
118
|
+
SFC_GET_EMBED_FILE_INFO = 0x10B0
|
|
119
|
+
SFC_SET_CLIPPING = 0x10C0
|
|
120
|
+
SFC_GET_CLIPPING = 0x10C1
|
|
121
|
+
SFC_GET_CUE_COUNT = 0x10CD
|
|
122
|
+
SFC_GET_CUE = 0x10CE
|
|
123
|
+
SFC_SET_CUE = 0x10CF
|
|
124
|
+
SFC_GET_INSTRUMENT = 0x10D0
|
|
125
|
+
SFC_SET_INSTRUMENT = 0x10D1
|
|
126
|
+
SFC_GET_LOOP_INFO = 0x10E0
|
|
127
|
+
SFC_GET_BROADCAST_INFO = 0x10F0
|
|
128
|
+
SFC_SET_BROADCAST_INFO = 0x10F1
|
|
129
|
+
SFC_GET_CHANNEL_MAP_INFO = 0x1100
|
|
130
|
+
SFC_SET_CHANNEL_MAP_INFO = 0x1101
|
|
131
|
+
SFC_RAW_DATA_NEEDS_ENDSWAP = 0x1110
|
|
132
|
+
# Support for Wavex Ambisonics Format
|
|
133
|
+
SFC_WAVEX_SET_AMBISONIC = 0x1200
|
|
134
|
+
SFC_WAVEX_GET_AMBISONIC = 0x1201
|
|
135
|
+
# RF64 files can be set so that on-close, writable files that have less
|
|
136
|
+
# than 4GB of data in them are converted to RIFF/WAV, as per EBU
|
|
137
|
+
# recommendations.
|
|
138
|
+
SFC_RF64_AUTO_DOWNGRADE = 0x1210
|
|
139
|
+
|
|
140
|
+
SFC_SET_VBR_ENCODING_QUALITY = 0x1300
|
|
141
|
+
SFC_SET_COMPRESSION_LEVEL = 0x1301
|
|
142
|
+
|
|
143
|
+
# Ogg format commands
|
|
144
|
+
SFC_SET_OGG_PAGE_LATENCY_MS = 0x1302
|
|
145
|
+
SFC_SET_OGG_PAGE_LATENCY = 0x1303
|
|
146
|
+
SFC_GET_OGG_STREAM_SERIALNO = 0x1306
|
|
147
|
+
|
|
148
|
+
SFC_GET_BITRATE_MODE = 0x1304
|
|
149
|
+
SFC_SET_BITRATE_MODE = 0x1305
|
|
150
|
+
|
|
151
|
+
# Cart Chunk support
|
|
152
|
+
SFC_SET_CART_INFO = 0x1400
|
|
153
|
+
SFC_GET_CART_INFO = 0x1401
|
|
154
|
+
|
|
155
|
+
# Opus files original samplerate metadata
|
|
156
|
+
SFC_SET_ORIGINAL_SAMPLERATE = 0x1500
|
|
157
|
+
SFC_GET_ORIGINAL_SAMPLERATE = 0x1501
|
|
158
|
+
|
|
159
|
+
# Following commands for testing only.
|
|
160
|
+
SFC_TEST_IEEE_FLOAT_REPLACE = 0x6001
|
|
161
|
+
|
|
162
|
+
# These SFC_SET_ADD_* values are deprecated and will disappear at some
|
|
163
|
+
# time in the future. They currently do nothing and will continue to do
|
|
164
|
+
# nothing.
|
|
165
|
+
SFC_SET_ADD_HEADER_PAD_CHUNK = 0x1051
|
|
166
|
+
SFC_SET_ADD_DITHER_ON_WRITE = 0x1070
|
|
167
|
+
SFC_SET_ADD_DITHER_ON_READ = 0x1071
|
|
168
|
+
|
|
169
|
+
# String types that can be set and read from files. Not all file types
|
|
170
|
+
# support this and even the file types which support one, may not support
|
|
171
|
+
# all string types.
|
|
172
|
+
SF_STR_TITLE = 0x01
|
|
173
|
+
SF_STR_COPYRIGHT = 0x02
|
|
174
|
+
SF_STR_SOFTWARE = 0x03
|
|
175
|
+
SF_STR_ARTIST = 0x04
|
|
176
|
+
SF_STR_COMMENT = 0x05
|
|
177
|
+
SF_STR_DATE = 0x06
|
|
178
|
+
SF_STR_ALBUM = 0x07
|
|
179
|
+
SF_STR_LICENSE = 0x08
|
|
180
|
+
SF_STR_TRACKNUMBER = 0x09
|
|
181
|
+
SF_STR_GENRE = 0x10
|
|
182
|
+
|
|
183
|
+
# Use the following as the start and end index when doing metadata
|
|
184
|
+
# transcoding.
|
|
185
|
+
SF_STR_FIRST = SF_STR_TITLE
|
|
186
|
+
SF_STR_LAST = SF_STR_GENRE
|
|
187
|
+
|
|
188
|
+
# True and false
|
|
189
|
+
SF_FALSE = 0
|
|
190
|
+
SF_TRUE = 1
|
|
191
|
+
|
|
192
|
+
# Modes for opening files.
|
|
193
|
+
SFM_READ = 0x10
|
|
194
|
+
SFM_WRITE = 0x20
|
|
195
|
+
SFM_RDWR = 0x30
|
|
196
|
+
|
|
197
|
+
SF_AMBISONIC_NONE = 0x40
|
|
198
|
+
SF_AMBISONIC_B_FORMAT = 0x41
|
|
199
|
+
|
|
200
|
+
# Public error values. These are guaranteed to remain unchanged for the
|
|
201
|
+
# duration of the library major version number.
|
|
202
|
+
SF_ERR_NO_ERROR = 0
|
|
203
|
+
SF_ERR_UNRECOGNISED_FORMAT = 1
|
|
204
|
+
SF_ERR_SYSTEM = 2
|
|
205
|
+
SF_ERR_MALFORMED_FILE = 3
|
|
206
|
+
SF_ERR_UNSUPPORTED_ENCODING = 4
|
|
207
|
+
|
|
208
|
+
# Channel map values (used with SFC_SET/GET_CHANNEL_MAP).
|
|
209
|
+
SF_CHANNEL_MAP_INVALID = 0
|
|
210
|
+
SF_CHANNEL_MAP_MONO = 1
|
|
211
|
+
SF_CHANNEL_MAP_LEFT = 2
|
|
212
|
+
SF_CHANNEL_MAP_RIGHT = 3
|
|
213
|
+
SF_CHANNEL_MAP_CENTER = 4
|
|
214
|
+
SF_CHANNEL_MAP_FRONT_LEFT = 5
|
|
215
|
+
SF_CHANNEL_MAP_FRONT_RIGHT = 6
|
|
216
|
+
SF_CHANNEL_MAP_FRONT_CENTER = 7
|
|
217
|
+
SF_CHANNEL_MAP_REAR_CENTER = 8
|
|
218
|
+
SF_CHANNEL_MAP_REAR_LEFT = 9
|
|
219
|
+
SF_CHANNEL_MAP_REAR_RIGHT = 10
|
|
220
|
+
SF_CHANNEL_MAP_LFE = 11
|
|
221
|
+
SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER = 12
|
|
222
|
+
SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER = 13
|
|
223
|
+
SF_CHANNEL_MAP_SIDE_LEFT = 14
|
|
224
|
+
SF_CHANNEL_MAP_SIDE_RIGHT = 15
|
|
225
|
+
SF_CHANNEL_MAP_TOP_CENTER = 16
|
|
226
|
+
SF_CHANNEL_MAP_TOP_FRONT_LEFT = 17
|
|
227
|
+
SF_CHANNEL_MAP_TOP_FRONT_RIGHT = 18
|
|
228
|
+
SF_CHANNEL_MAP_TOP_FRONT_CENTER = 19
|
|
229
|
+
SF_CHANNEL_MAP_TOP_REAR_LEFT = 20
|
|
230
|
+
SF_CHANNEL_MAP_TOP_REAR_RIGHT = 21
|
|
231
|
+
SF_CHANNEL_MAP_TOP_REAR_CENTER = 22
|
|
232
|
+
SF_CHANNEL_MAP_AMBISONIC_B_W = 23
|
|
233
|
+
SF_CHANNEL_MAP_AMBISONIC_B_X = 24
|
|
234
|
+
SF_CHANNEL_MAP_AMBISONIC_B_Y = 25
|
|
235
|
+
SF_CHANNEL_MAP_AMBISONIC_B_Z = 26
|
|
236
|
+
SF_CHANNEL_MAP_MAX = 27
|
|
237
|
+
|
|
238
|
+
# Bitrate mode values (for use with SFC_GET/SET_BITRATE_MODE)
|
|
239
|
+
SF_BITRATE_MODE_CONSTANT = 0
|
|
240
|
+
SF_BITRATE_MODE_AVERAGE = 1
|
|
241
|
+
SF_BITRATE_MODE_VARIABLE = 2
|
|
242
|
+
|
|
243
|
+
# Enums and typedefs for adding dither on read and write.
|
|
244
|
+
# Reserved for future implementation.
|
|
245
|
+
SFD_DEFAULT_LEVEL = 0
|
|
246
|
+
SFD_CUSTOM_LEVEL = 0x40000000
|
|
247
|
+
SFD_NO_DITHER = 500
|
|
248
|
+
SFD_WHITE = 501
|
|
249
|
+
SFD_TRIANGULAR_PDF = 502
|
|
250
|
+
|
|
251
|
+
# The loop mode field in SF_INSTRUMENT will be one of the following.
|
|
252
|
+
SF_LOOP_NONE = 800
|
|
253
|
+
SF_LOOP_FORWARD = 801
|
|
254
|
+
SF_LOOP_BACKWARD = 802
|
|
255
|
+
SF_LOOP_ALTERNATING = 803
|
|
256
|
+
|
|
257
|
+
# sf_seek () uses the same values for whence (SEEK_SET, SEEK_CUR and
|
|
258
|
+
# SEEK_END) as stdio.h function fseek ().
|
|
259
|
+
SF_SEEK_SET = 0
|
|
260
|
+
SF_SEEK_CUR = 1
|
|
261
|
+
SF_SEEK_END = 2
|
|
262
|
+
|
|
263
|
+
SF_COUNT_MAX = (2**63) - 1
|
|
264
|
+
end
|