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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Exception classes for libsndfile errors.
|
|
4
|
+
module SndFile
|
|
5
|
+
# Base class of every error this gem raises for libsndfile. #code is
|
|
6
|
+
# libsndfile's error number (see sf_error), or nil when there is none.
|
|
7
|
+
class SndFileError < StandardError
|
|
8
|
+
attr_reader :code
|
|
9
|
+
|
|
10
|
+
def initialize(message = nil, code: nil)
|
|
11
|
+
super(message)
|
|
12
|
+
@code = code
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class UnrecognisedFormatError < SndFileError; end
|
|
17
|
+
class SystemError < SndFileError; end
|
|
18
|
+
class MalformedFileError < SndFileError; end
|
|
19
|
+
class UnsupportedEncodingError < SndFileError; end
|
|
20
|
+
|
|
21
|
+
# Raised when a SoundFile is used after it was closed.
|
|
22
|
+
class ClosedFileError < SndFileError; end
|
|
23
|
+
|
|
24
|
+
# Maps libsndfile's error numbers to exception classes.
|
|
25
|
+
module Error
|
|
26
|
+
# libsndfile's public error numbers. It also has many private numbers
|
|
27
|
+
# (which may change between versions); those raise a plain SndFileError.
|
|
28
|
+
ERROR_CLASSES = {
|
|
29
|
+
SF_ERR_UNRECOGNISED_FORMAT => UnrecognisedFormatError,
|
|
30
|
+
SF_ERR_SYSTEM => SystemError,
|
|
31
|
+
SF_ERR_MALFORMED_FILE => MalformedFileError,
|
|
32
|
+
SF_ERR_UNSUPPORTED_ENCODING => UnsupportedEncodingError
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# Raises the matching SndFileError for a non-zero libsndfile error
|
|
36
|
+
# number. The message is the file's error text (sf_strerror) when
|
|
37
|
+
# +sndfile+ is given, otherwise libsndfile's text for the number.
|
|
38
|
+
def self.check_error(error_number, sndfile = nil)
|
|
39
|
+
return unless error_number.is_a?(Integer) && error_number != SF_ERR_NO_ERROR
|
|
40
|
+
|
|
41
|
+
raise_error(error_number, sndfile ? file_message(sndfile) : nil)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Raises when libsndfile has recorded an error for +sndfile+ (sf_error).
|
|
45
|
+
def self.check_sndfile(sndfile)
|
|
46
|
+
check_error(Native.sf_error(sndfile), sndfile)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Raises the error of a failed sf_open, sf_open_fd or sf_open_virtual,
|
|
50
|
+
# which libsndfile keeps in global state (sf_error(NULL)). That state is
|
|
51
|
+
# not cleared, so only call this right after the failure.
|
|
52
|
+
def self.raise_open_error
|
|
53
|
+
error_number = Native.sf_error(nil)
|
|
54
|
+
error_number = SF_ERR_SYSTEM if error_number == SF_ERR_NO_ERROR
|
|
55
|
+
raise_error(error_number, file_message(nil))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.raise_error(error_number, message)
|
|
59
|
+
message ||= number_message(error_number) || "libsndfile error #{error_number}"
|
|
60
|
+
raise ERROR_CLASSES.fetch(error_number, SndFileError).new(message, code: error_number)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# sf_strerror's text, unless it says there is no error. It is more
|
|
64
|
+
# specific than sf_error_number's, e.g. it names the missing file.
|
|
65
|
+
def self.file_message(sndfile)
|
|
66
|
+
message = Native.sf_strerror(sndfile)
|
|
67
|
+
message unless message.nil? || message.empty? || message == number_message(SF_ERR_NO_ERROR)
|
|
68
|
+
rescue StandardError
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.number_message(error_number)
|
|
73
|
+
message = Native.sf_error_number(error_number)
|
|
74
|
+
message unless message.nil? || message.empty?
|
|
75
|
+
rescue StandardError
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
private_class_method :raise_error, :file_message, :number_message
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
# A libsndfile format: a major format (the container, such as :ogg), a
|
|
5
|
+
# subtype (the encoding, such as :vorbis) and an endianness. libsndfile
|
|
6
|
+
# ORs these together into one integer, which #to_i returns.
|
|
7
|
+
class Format
|
|
8
|
+
# The subtype used when only a major format is given.
|
|
9
|
+
DEFAULT_SUBTYPES = {
|
|
10
|
+
wav: :pcm_16, wavex: :pcm_16, aiff: :pcm_16, au: :pcm_16, raw: :pcm_16,
|
|
11
|
+
w64: :pcm_16, caf: :pcm_16, rf64: :pcm_16, flac: :pcm_16,
|
|
12
|
+
ogg: :vorbis, mpeg: :mpeg_layer_iii
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
# Formats for common file name extensions, used by Format.for_path.
|
|
16
|
+
EXTENSIONS = {
|
|
17
|
+
"wav" => [:wav], "aif" => [:aiff], "aiff" => [:aiff], "aifc" => [:aiff],
|
|
18
|
+
"au" => [:au], "snd" => [:au], "raw" => [:raw], "w64" => [:w64],
|
|
19
|
+
"caf" => [:caf], "rf64" => [:rf64], "flac" => [:flac],
|
|
20
|
+
"ogg" => %i[ogg vorbis], "oga" => %i[ogg vorbis], "opus" => %i[ogg opus],
|
|
21
|
+
"mp3" => %i[mpeg mpeg_layer_iii]
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# Converts +value+ (a Format, an Integer, a major format Symbol or an
|
|
25
|
+
# Array such as [:ogg, :vorbis]) to a Format.
|
|
26
|
+
def self.coerce(value, subtype = nil, endian = nil)
|
|
27
|
+
case value
|
|
28
|
+
when Format then value
|
|
29
|
+
when Integer then new(value)
|
|
30
|
+
when Symbol, String then new(value, subtype, endian || :file)
|
|
31
|
+
when Array then new(*value)
|
|
32
|
+
else raise ArgumentError, "Expected a Format, Integer, Symbol or Array, got #{value.class}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The format for a file name's extension, or nil for unknown extensions.
|
|
37
|
+
def self.for_path(path)
|
|
38
|
+
extension = ::File.extname(path.to_s).delete_prefix(".").downcase
|
|
39
|
+
EXTENSIONS[extension]&.then { |args| new(*args) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Builds a format from a major format and subtype (symbols or integer
|
|
43
|
+
# constants), or from a complete format integer.
|
|
44
|
+
def initialize(major, subtype = nil, endian = :file)
|
|
45
|
+
@to_i = if major.is_a?(Integer) && subtype.nil?
|
|
46
|
+
major
|
|
47
|
+
else
|
|
48
|
+
major_code = code(:sf_major_format, major)
|
|
49
|
+
subtype ||= default_subtype(major_code)
|
|
50
|
+
major_code | code(:sf_subtype, subtype) | code(:sf_endian, endian)
|
|
51
|
+
end
|
|
52
|
+
freeze
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attr_reader :to_i
|
|
56
|
+
alias to_int to_i
|
|
57
|
+
|
|
58
|
+
# The major format, as a symbol such as :wav (or the integer if unknown).
|
|
59
|
+
def major
|
|
60
|
+
symbol(:sf_major_format, to_i & SF_FORMAT_TYPEMASK)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The subtype, as a symbol such as :pcm_16 (or the integer if unknown).
|
|
64
|
+
def subtype
|
|
65
|
+
symbol(:sf_subtype, to_i & SF_FORMAT_SUBMASK)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The endianness: :file, :little, :big or :cpu.
|
|
69
|
+
def endian
|
|
70
|
+
symbol(:sf_endian, to_i & SF_FORMAT_ENDMASK)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Whether libsndfile can write this format with the given sample rate
|
|
74
|
+
# and channel count (sf_format_check).
|
|
75
|
+
def valid?(samplerate:, channels:)
|
|
76
|
+
info = Native::Info.new
|
|
77
|
+
info[:format] = to_i
|
|
78
|
+
info[:samplerate] = samplerate
|
|
79
|
+
info[:channels] = channels
|
|
80
|
+
Native.sf_format_check(info) == SF_TRUE
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ==(other)
|
|
84
|
+
other.is_a?(Format) && to_i == other.to_i
|
|
85
|
+
end
|
|
86
|
+
alias eql? ==
|
|
87
|
+
|
|
88
|
+
def hash
|
|
89
|
+
[self.class, to_i].hash
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# "ogg/vorbis", "wav/pcm_16/big", or just "flac" for a format without a
|
|
93
|
+
# subtype (as in the Formats listings).
|
|
94
|
+
def to_s
|
|
95
|
+
parts = [major, subtype].reject { |part| part.equal?(0) }
|
|
96
|
+
parts << endian unless endian == :file
|
|
97
|
+
parts.join("/")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def inspect
|
|
101
|
+
"#<#{self.class.name} #{self}>"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def code(enum_name, value)
|
|
107
|
+
return value if value.is_a?(Integer)
|
|
108
|
+
|
|
109
|
+
enum = Native.enum_type(enum_name)
|
|
110
|
+
enum[value.to_s.downcase.to_sym] or
|
|
111
|
+
raise ArgumentError, "Unknown #{enum_name.to_s.delete_prefix("sf_").tr("_", " ")}: #{value.inspect}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def default_subtype(major_code)
|
|
115
|
+
DEFAULT_SUBTYPES.fetch(Native.enum_type(:sf_major_format)[major_code]) do
|
|
116
|
+
raise ArgumentError, "A subtype is needed for #{symbol(:sf_major_format, major_code).inspect} files"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def symbol(enum_name, value)
|
|
121
|
+
Native.enum_type(enum_name)[value] || value
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
# A format libsndfile supports, as listed by SndFile.major_formats,
|
|
5
|
+
# SndFile.subtypes and SndFile.simple_formats: its Format, a description
|
|
6
|
+
# and (for major and simple formats) the usual file extension.
|
|
7
|
+
FormatDescription = Data.define(:format, :name, :extension)
|
|
8
|
+
|
|
9
|
+
# Lists the formats the loaded libsndfile supports, using its
|
|
10
|
+
# SFC_GET_FORMAT_* commands.
|
|
11
|
+
module Formats
|
|
12
|
+
# The major formats (containers), e.g. "WAV (Microsoft)".
|
|
13
|
+
def self.major_formats
|
|
14
|
+
list(SFC_GET_FORMAT_MAJOR_COUNT, SFC_GET_FORMAT_MAJOR)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The subtypes (encodings), e.g. "Signed 16 bit PCM".
|
|
18
|
+
def self.subtypes
|
|
19
|
+
list(SFC_GET_FORMAT_SUBTYPE_COUNT, SFC_GET_FORMAT_SUBTYPE)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Common combinations of major format and subtype.
|
|
23
|
+
def self.simple_formats
|
|
24
|
+
list(SFC_GET_SIMPLE_FORMAT_COUNT, SFC_GET_SIMPLE_FORMAT)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The description of one format (SFC_GET_FORMAT_INFO), or nil when
|
|
28
|
+
# libsndfile doesn't know it. Takes a major format or a subtype alone,
|
|
29
|
+
# as an integer or anything Format.coerce accepts.
|
|
30
|
+
def self.describe(format)
|
|
31
|
+
code = format.is_a?(Symbol) ? symbol_code(format) : Format.coerce(format).to_i
|
|
32
|
+
info = Native::FormatInfo.new
|
|
33
|
+
info[:format] = code
|
|
34
|
+
return unless Native.sf_command(nil, SFC_GET_FORMAT_INFO, info, info.size).zero?
|
|
35
|
+
|
|
36
|
+
description(info)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.list(count_command, item_command)
|
|
40
|
+
count = FFI::MemoryPointer.new(:int)
|
|
41
|
+
Native.sf_command(nil, count_command, count, count.size)
|
|
42
|
+
Array.new(count.read_int) do |index|
|
|
43
|
+
info = Native::FormatInfo.new
|
|
44
|
+
info[:format] = index
|
|
45
|
+
Native.sf_command(nil, item_command, info, info.size)
|
|
46
|
+
description(info)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.description(info)
|
|
51
|
+
FormatDescription.new(format: Format.new(info[:format]), name: info[:name], extension: info[:extension])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.symbol_code(symbol)
|
|
55
|
+
Native.enum_type(:sf_major_format)[symbol] || Native.enum_type(:sf_subtype)[symbol] or
|
|
56
|
+
raise ArgumentError, "Unknown format #{symbol.inspect}"
|
|
57
|
+
end
|
|
58
|
+
private_class_method :list, :description, :symbol_code
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/sndfile/info.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
# What libsndfile knows about an open sound file (its SF_INFO): the number
|
|
5
|
+
# of frames, sample rate, channel count, Format, number of sections and
|
|
6
|
+
# whether it is seekable.
|
|
7
|
+
Info = Data.define(:frames, :samplerate, :channels, :format, :sections, :seekable) do
|
|
8
|
+
# Builds an Info from a Native::Info struct.
|
|
9
|
+
def self.from_native(struct)
|
|
10
|
+
new(frames: struct[:frames],
|
|
11
|
+
samplerate: struct[:samplerate],
|
|
12
|
+
channels: struct[:channels],
|
|
13
|
+
format: Format.new(struct[:format]),
|
|
14
|
+
sections: struct[:sections],
|
|
15
|
+
seekable: struct[:seekable] == SF_TRUE)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def seekable?
|
|
19
|
+
seekable
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The length in seconds, or nil when the sample rate is unknown.
|
|
23
|
+
def duration
|
|
24
|
+
frames.fdiv(samplerate) if samplerate.positive?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
# The version of the loaded libsndfile library (sf_version_string), as
|
|
5
|
+
# returned by SndFile.version.
|
|
6
|
+
class LibraryVersion
|
|
7
|
+
include Comparable
|
|
8
|
+
|
|
9
|
+
# The oldest libsndfile with every function these bindings attach.
|
|
10
|
+
# Some sf_command commands are newer: the MPEG formats and
|
|
11
|
+
# SFC_GET/SET_BITRATE_MODE need 1.1.0.
|
|
12
|
+
MINIMUM = "1.0.28"
|
|
13
|
+
|
|
14
|
+
attr_reader :string, :major, :minor, :patch
|
|
15
|
+
|
|
16
|
+
# Raises LoadError when the loaded libsndfile is older than MINIMUM.
|
|
17
|
+
def self.ensure_supported!
|
|
18
|
+
version = new
|
|
19
|
+
return version if version >= MINIMUM
|
|
20
|
+
|
|
21
|
+
raise LoadError, "ffi-sndfile needs libsndfile #{MINIMUM} or newer, but found #{version.number}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Parses a version string such as "libsndfile-1.2.2" (the default is the
|
|
25
|
+
# loaded library's).
|
|
26
|
+
def initialize(string = Native.sf_version_string)
|
|
27
|
+
@string = string
|
|
28
|
+
match = string.to_s.match(/(\d+)\.(\d+)\.(\d+)/)
|
|
29
|
+
raise ArgumentError, "Cannot parse libsndfile version #{string.inspect}" unless match
|
|
30
|
+
|
|
31
|
+
@major, @minor, @patch = match.captures.map(&:to_i)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# "major.minor.patch", e.g. "1.2.2".
|
|
35
|
+
def number
|
|
36
|
+
[major, minor, patch].join(".")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def <=>(other)
|
|
40
|
+
other = other.number if other.is_a?(LibraryVersion)
|
|
41
|
+
Gem::Version.new(number) <=> Gem::Version.new(other.to_s)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_s
|
|
45
|
+
@string
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def inspect
|
|
49
|
+
"#<#{self.class.name} #{number}>"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
# FFI enum types for the libsndfile constants in SndFile (see
|
|
5
|
+
# constants.rb). Functions that take these values are attached with the
|
|
6
|
+
# enum type, so they accept symbols such as :read or :title as well as the
|
|
7
|
+
# integer constants. Each symbol is the constant's name without its prefix,
|
|
8
|
+
# in lower case: SFC_SET_VBR_ENCODING_QUALITY is :set_vbr_encoding_quality.
|
|
9
|
+
module Native
|
|
10
|
+
# Defines an enum named +name+ from the constants +prefix+<suffix>.
|
|
11
|
+
def self.constant_enum(name, prefix, suffixes)
|
|
12
|
+
values = suffixes.flat_map do |suffix|
|
|
13
|
+
[suffix.downcase.to_sym, SndFile.const_get(:"#{prefix}#{suffix}")]
|
|
14
|
+
end
|
|
15
|
+
enum name, values
|
|
16
|
+
end
|
|
17
|
+
private_class_method :constant_enum
|
|
18
|
+
|
|
19
|
+
constant_enum :sf_major_format, "SF_FORMAT_", %w[
|
|
20
|
+
WAV AIFF AU RAW PAF SVX NIST VOC IRCAM W64 MAT4 MAT5 PVF XI HTK SDS AVR
|
|
21
|
+
WAVEX SD2 FLAC CAF WVE OGG MPC2K RF64 MPEG
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
constant_enum :sf_subtype, "SF_FORMAT_", %w[
|
|
25
|
+
PCM_S8 PCM_16 PCM_24 PCM_32 PCM_U8 FLOAT DOUBLE ULAW ALAW IMA_ADPCM
|
|
26
|
+
MS_ADPCM GSM610 VOX_ADPCM NMS_ADPCM_16 NMS_ADPCM_24 NMS_ADPCM_32 G721_32
|
|
27
|
+
G723_24 G723_40 DWVW_12 DWVW_16 DWVW_24 DWVW_N DPCM_8 DPCM_16 VORBIS OPUS
|
|
28
|
+
ALAC_16 ALAC_20 ALAC_24 ALAC_32 MPEG_LAYER_I MPEG_LAYER_II MPEG_LAYER_III
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
constant_enum :sf_endian, "SF_ENDIAN_", %w[FILE LITTLE BIG CPU]
|
|
32
|
+
|
|
33
|
+
commands = SndFile.constants.grep(/\ASFC_/).map { |name| name.to_s.delete_prefix("SFC_") }
|
|
34
|
+
constant_enum :sf_command, "SFC_", commands
|
|
35
|
+
|
|
36
|
+
constant_enum :sf_str, "SF_STR_", %w[
|
|
37
|
+
TITLE COPYRIGHT SOFTWARE ARTIST COMMENT DATE ALBUM LICENSE TRACKNUMBER GENRE
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
constant_enum :sf_mode, "SFM_", %w[READ WRITE RDWR]
|
|
41
|
+
|
|
42
|
+
constant_enum :sf_ambisonic, "SF_AMBISONIC_", %w[NONE B_FORMAT]
|
|
43
|
+
|
|
44
|
+
constant_enum :sf_error, "SF_ERR_", %w[
|
|
45
|
+
NO_ERROR UNRECOGNISED_FORMAT SYSTEM MALFORMED_FILE UNSUPPORTED_ENCODING
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
constant_enum :sf_channel_map, "SF_CHANNEL_MAP_", %w[
|
|
49
|
+
INVALID MONO LEFT RIGHT CENTER FRONT_LEFT FRONT_RIGHT FRONT_CENTER
|
|
50
|
+
REAR_CENTER REAR_LEFT REAR_RIGHT LFE FRONT_LEFT_OF_CENTER
|
|
51
|
+
FRONT_RIGHT_OF_CENTER SIDE_LEFT SIDE_RIGHT TOP_CENTER TOP_FRONT_LEFT
|
|
52
|
+
TOP_FRONT_RIGHT TOP_FRONT_CENTER TOP_REAR_LEFT TOP_REAR_RIGHT
|
|
53
|
+
TOP_REAR_CENTER AMBISONIC_B_W AMBISONIC_B_X AMBISONIC_B_Y AMBISONIC_B_Z
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
constant_enum :sf_bitrate_mode, "SF_BITRATE_MODE_", %w[CONSTANT AVERAGE VARIABLE]
|
|
57
|
+
|
|
58
|
+
constant_enum :sf_dither, "SFD_", %w[NO_DITHER WHITE TRIANGULAR_PDF]
|
|
59
|
+
|
|
60
|
+
constant_enum :sf_loop_mode, "SF_LOOP_", %w[NONE FORWARD BACKWARD ALTERNATING]
|
|
61
|
+
|
|
62
|
+
constant_enum :sf_seek_whence, "SF_SEEK_", %w[SET CUR END]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
# attach_function declarations for every function in sndfile.h.
|
|
5
|
+
module Native
|
|
6
|
+
# These `attach_function` declarations are in the order of sndfile.h, with
|
|
7
|
+
# its comments. Parameters that take SFM_*, SF_STR_*, SFC_* or SF_SEEK_*
|
|
8
|
+
# values use the FFI enums from enums.rb, so they accept symbols such as
|
|
9
|
+
# :read or :title as well as the integer constants.
|
|
10
|
+
# sf_open, sf_open_fd and sf_open_virtual return a Handle, which closes
|
|
11
|
+
# itself when it is garbage collected, and raise a SndFileError instead of
|
|
12
|
+
# returning NULL.
|
|
13
|
+
# Calls that read or write the file are attached with `blocking: true` so
|
|
14
|
+
# they release Ruby's GVL; pass them an FFI::MemoryPointer, never a Ruby
|
|
15
|
+
# string's memory.
|
|
16
|
+
|
|
17
|
+
# Open the specified file for read, write or both. On error, this will
|
|
18
|
+
# return a NULL pointer. To find the error number, pass a NULL SNDFILE
|
|
19
|
+
# to sf_strerror ().
|
|
20
|
+
# All calls to sf_open() should be matched with a call to sf_close().
|
|
21
|
+
attach_function :sf_open, [:string, :sf_mode, Info.by_ref], Handle, blocking: true
|
|
22
|
+
|
|
23
|
+
# Use the existing file descriptor to create a SNDFILE object. If close_desc
|
|
24
|
+
# is TRUE, the file descriptor will be closed when sf_close() is called. If
|
|
25
|
+
# it is FALSE, the descriptor will not be closed.
|
|
26
|
+
# When passed a descriptor like this, the library will assume that the start
|
|
27
|
+
# of file header is at the current file offset. This allows sound files within
|
|
28
|
+
# larger container files to be read and/or written.
|
|
29
|
+
attach_function :sf_open_fd, [:int, :sf_mode, Info.by_ref, :int], Handle, blocking: true
|
|
30
|
+
|
|
31
|
+
attach_function :sf_open_virtual, [VirtualIO.by_ref, :sf_mode, Info.by_ref, :pointer], Handle
|
|
32
|
+
|
|
33
|
+
# sf_error () returns a error number which can be translated to a text
|
|
34
|
+
# string using sf_error_number().
|
|
35
|
+
attach_function :sf_error, [:sndfile], :int
|
|
36
|
+
|
|
37
|
+
# sf_strerror () returns to the caller a pointer to the current error message for
|
|
38
|
+
# the given SNDFILE.
|
|
39
|
+
attach_function :sf_strerror, [:sndfile], :string
|
|
40
|
+
|
|
41
|
+
# sf_error_number () allows the retrieval of the error string for each internal
|
|
42
|
+
# error number.
|
|
43
|
+
attach_function :sf_error_number, [:int], :string
|
|
44
|
+
|
|
45
|
+
# The following two error functions are deprecated but they will remain in the
|
|
46
|
+
# library for the foreseeable future. The function sf_strerror() should be used
|
|
47
|
+
# in their place.
|
|
48
|
+
attach_function :sf_perror, [:sndfile], :int
|
|
49
|
+
attach_function :sf_error_str, %i[sndfile pointer size_t], :int
|
|
50
|
+
|
|
51
|
+
# Allow the caller to retrieve information from or change aspects of the
|
|
52
|
+
# library behaviour.
|
|
53
|
+
attach_function :sf_command, %i[sndfile sf_command pointer int], :int
|
|
54
|
+
|
|
55
|
+
# Return TRUE if fields of the SF_INFO struct are a valid combination of values.
|
|
56
|
+
attach_function :sf_format_check, [Info.by_ref], :int
|
|
57
|
+
|
|
58
|
+
# Seek within the waveform data chunk of the SNDFILE.
|
|
59
|
+
# An offset of zero with whence set to SEEK_SET will position the
|
|
60
|
+
# read / write pointer to the first data sample.
|
|
61
|
+
# On success sf_seek returns the current position in (multi-channel)
|
|
62
|
+
# samples from the start of the file.
|
|
63
|
+
# On error all of these functions return -1.
|
|
64
|
+
attach_function :sf_seek, %i[sndfile sf_count_t sf_seek_whence], :sf_count_t, blocking: true
|
|
65
|
+
|
|
66
|
+
# Functions for retrieving and setting string data within sound files.
|
|
67
|
+
# Not all file types support this features; AIFF and WAV do.
|
|
68
|
+
# On error, sf_set_string() returns non-zero while sf_get_string()
|
|
69
|
+
# returns NULL.
|
|
70
|
+
attach_function :sf_set_string, %i[sndfile sf_str string], :int
|
|
71
|
+
attach_function :sf_get_string, %i[sndfile sf_str], :string
|
|
72
|
+
|
|
73
|
+
# Return the library version string.
|
|
74
|
+
# (sf_version_string is attached in native.rb, before the version check)
|
|
75
|
+
|
|
76
|
+
# Return the current byterate at this point in the file. The byte rate in this
|
|
77
|
+
# case is the number of bytes per second of audio data.
|
|
78
|
+
# Returns -1 for unknown.
|
|
79
|
+
attach_function :sf_current_byterate, [:sndfile], :int
|
|
80
|
+
|
|
81
|
+
# Functions for reading/writing the waveform data of a sound file.
|
|
82
|
+
attach_function :sf_read_raw, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
83
|
+
attach_function :sf_write_raw, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
84
|
+
|
|
85
|
+
# Functions for reading and writing the data chunk in terms of frames.
|
|
86
|
+
# The number of items actually read/written = frames * number of channels.
|
|
87
|
+
# All of these read/write function return number of frames read/written.
|
|
88
|
+
attach_function :sf_readf_short, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
89
|
+
attach_function :sf_writef_short, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
90
|
+
|
|
91
|
+
attach_function :sf_readf_int, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
92
|
+
attach_function :sf_writef_int, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
93
|
+
|
|
94
|
+
attach_function :sf_readf_float, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
95
|
+
attach_function :sf_writef_float, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
96
|
+
|
|
97
|
+
attach_function :sf_readf_double, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
98
|
+
attach_function :sf_writef_double, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
99
|
+
|
|
100
|
+
# Functions for reading and writing the data chunk in terms of items.
|
|
101
|
+
# Otherwise similar to above.
|
|
102
|
+
# All of these read/write function return number of items read/written.
|
|
103
|
+
attach_function :sf_read_short, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
104
|
+
attach_function :sf_write_short, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
105
|
+
|
|
106
|
+
attach_function :sf_read_int, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
107
|
+
attach_function :sf_write_int, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
108
|
+
|
|
109
|
+
attach_function :sf_read_float, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
110
|
+
attach_function :sf_write_float, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
111
|
+
|
|
112
|
+
attach_function :sf_read_double, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
113
|
+
attach_function :sf_write_double, %i[sndfile pointer sf_count_t], :sf_count_t, blocking: true
|
|
114
|
+
|
|
115
|
+
# Close the SNDFILE and clean up all memory allocations associated with this
|
|
116
|
+
# file.
|
|
117
|
+
# Returns 0 on success, or an error number.
|
|
118
|
+
# (Attached as sf_close_pointer. Native.sf_close also stops a Handle from
|
|
119
|
+
# being closed a second time when it is garbage collected.)
|
|
120
|
+
attach_function :sf_close_pointer, :sf_close, [:sndfile], :int, blocking: true
|
|
121
|
+
|
|
122
|
+
def self.sf_close(sndfile)
|
|
123
|
+
sndfile.autorelease = false if sndfile.is_a?(Handle)
|
|
124
|
+
sf_close_pointer(sndfile)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# If the file is opened SFM_WRITE or SFM_RDWR, call fsync() on the file
|
|
128
|
+
# to force the writing of data to disk. If the file is opened SFM_READ
|
|
129
|
+
# no action is taken.
|
|
130
|
+
attach_function :sf_write_sync, [:sndfile], :void, blocking: true
|
|
131
|
+
|
|
132
|
+
# The function sf_wchar_open() is Windows Only!
|
|
133
|
+
# Open a file passing in a Windows Unicode filename. Otherwise, this is
|
|
134
|
+
# the same as sf_open().
|
|
135
|
+
attach_function :sf_wchar_open, [:pointer, :sf_mode, Info.by_ref], Handle, blocking: true if FFI::Platform.windows?
|
|
136
|
+
|
|
137
|
+
# Getting and setting of chunks from within a sound file.
|
|
138
|
+
#
|
|
139
|
+
# These functions allow the getting and setting of chunks within a sound file
|
|
140
|
+
# (for those formats which allow it).
|
|
141
|
+
|
|
142
|
+
# Set the specified chunk info (must be done before any audio data is written
|
|
143
|
+
# to the file). This will fail for format specific reserved chunks.
|
|
144
|
+
# The chunk_info->data pointer must be valid until the file is closed.
|
|
145
|
+
# Returns SF_ERR_NO_ERROR on success or non-zero on failure.
|
|
146
|
+
attach_function :sf_set_chunk, [:sndfile, ChunkInfo.by_ref], :int
|
|
147
|
+
|
|
148
|
+
# Get an iterator for all chunks matching chunk_info.
|
|
149
|
+
# If chunk_info is NULL, an iterator to all chunks in the SNDFILE* handle
|
|
150
|
+
# is returned.
|
|
151
|
+
# If no matching chunks are found in the sndfile, NULL is returned.
|
|
152
|
+
# The memory for the iterator belongs to the SNDFILE* handle and is freed when
|
|
153
|
+
# sf_close() is called.
|
|
154
|
+
attach_function :sf_get_chunk_iterator, [:sndfile, ChunkInfo.by_ref], :pointer
|
|
155
|
+
|
|
156
|
+
# Iterate through chunks by incrementing the iterator.
|
|
157
|
+
# If iterator points to the last chunk, this will free all resources
|
|
158
|
+
# associated with iterator and return NULL.
|
|
159
|
+
attach_function :sf_next_chunk_iterator, [:pointer], :pointer
|
|
160
|
+
|
|
161
|
+
# Get the size of the specified chunk.
|
|
162
|
+
# If the specified chunk exists, the size will be returned in the
|
|
163
|
+
# datalen field of the SF_CHUNK_INFO struct.
|
|
164
|
+
# The function will return SF_ERR_NO_ERROR on success or non-zero on
|
|
165
|
+
# failure.
|
|
166
|
+
attach_function :sf_get_chunk_size, [:pointer, ChunkInfo.by_ref], :int
|
|
167
|
+
|
|
168
|
+
# Get the specified chunk data.
|
|
169
|
+
# If the specified chunk exists, up to chunk_info->datalen bytes of
|
|
170
|
+
# the chunk data will be copied into the chunk_info->data buffer
|
|
171
|
+
# (allocated by the caller).
|
|
172
|
+
# The function will return SF_ERR_NO_ERROR on success or non-zero on
|
|
173
|
+
# failure.
|
|
174
|
+
attach_function :sf_get_chunk_data, [:pointer, ChunkInfo.by_ref], :int
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SndFile
|
|
4
|
+
module Native
|
|
5
|
+
# An owned SNDFILE* handle, as returned by Native.sf_open, sf_open_fd and
|
|
6
|
+
# sf_open_virtual. It is closed with sf_close when it is garbage
|
|
7
|
+
# collected, unless it was closed before (see SoundFile#close).
|
|
8
|
+
class Handle < FFI::AutoPointer
|
|
9
|
+
def self.release(ptr)
|
|
10
|
+
Native.sf_close_pointer(ptr)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Called by FFI to wrap the pointer that the sf_open functions return.
|
|
14
|
+
# They return NULL when the file can't be opened; this raises the
|
|
15
|
+
# matching SndFileError with libsndfile's message instead.
|
|
16
|
+
def self.from_native(ptr, ctx)
|
|
17
|
+
Error.raise_open_error if ptr.null?
|
|
18
|
+
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|