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.
@@ -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
@@ -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