1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|
---
--- @classmod sndfile
---
-- module table
local sndfile = {}
local bit = require "bit"
local ffi = require "ffi"
ffi.cdef[[
typedef struct SNDFILE_tag SNDFILE;
typedef int64_t sf_count_t;
typedef enum SF_FORMAT { /* Major formats. */
SF_FORMAT_WAV = 0x010000, /* Microsoft WAV format (little endian default). */
SF_FORMAT_AIFF = 0x020000, /* Apple/SGI AIFF format (big endian). */
SF_FORMAT_AU = 0x030000, /* Sun/NeXT AU format (big endian). */
SF_FORMAT_RAW = 0x040000, /* RAW PCM data. */
SF_FORMAT_PAF = 0x050000, /* Ensoniq PARIS file format. */
SF_FORMAT_SVX = 0x060000, /* Amiga IFF / SVX8 / SV16 format. */
SF_FORMAT_NIST = 0x070000, /* Sphere NIST format. */
SF_FORMAT_VOC = 0x080000, /* VOC files. */
SF_FORMAT_IRCAM = 0x0A0000, /* Berkeley/IRCAM/CARL */
SF_FORMAT_W64 = 0x0B0000, /* Sonic Foundry's 64 bit RIFF/WAV */
SF_FORMAT_MAT4 = 0x0C0000, /* Matlab (tm) V4.2 / GNU Octave 2.0 */
SF_FORMAT_MAT5 = 0x0D0000, /* Matlab (tm) V5.0 / GNU Octave 2.1 */
SF_FORMAT_PVF = 0x0E0000, /* Portable Voice Format */
SF_FORMAT_XI = 0x0F0000, /* Fasttracker 2 Extended Instrument */
SF_FORMAT_HTK = 0x100000, /* HMM Tool Kit format */
SF_FORMAT_SDS = 0x110000, /* Midi Sample Dump Standard */
SF_FORMAT_AVR = 0x120000, /* Audio Visual Research */
SF_FORMAT_WAVEX = 0x130000, /* MS WAVE with WAVEFORMATEX */
SF_FORMAT_SD2 = 0x160000, /* Sound Designer 2 */
SF_FORMAT_FLAC = 0x170000, /* FLAC lossless file format */
SF_FORMAT_CAF = 0x180000, /* Core Audio File format */
SF_FORMAT_WVE = 0x190000, /* Psion WVE format */
SF_FORMAT_OGG = 0x200000, /* Xiph OGG container */
SF_FORMAT_MPC2K = 0x210000, /* Akai MPC 2000 sampler */
SF_FORMAT_RF64 = 0x220000, /* RF64 WAV file */
/* Subtypes from here on. */
SF_FORMAT_PCM_S8 = 0x0001, /* Signed 8 bit data */
SF_FORMAT_PCM_16 = 0x0002, /* Signed 16 bit data */
SF_FORMAT_PCM_24 = 0x0003, /* Signed 24 bit data */
SF_FORMAT_PCM_32 = 0x0004, /* Signed 32 bit data */
SF_FORMAT_PCM_U8 = 0x0005, /* Unsigned 8 bit data (WAV and RAW only) */
SF_FORMAT_FLOAT = 0x0006, /* 32 bit float data */
SF_FORMAT_DOUBLE = 0x0007, /* 64 bit float data */
SF_FORMAT_ULAW = 0x0010, /* U-Law encoded. */
SF_FORMAT_ALAW = 0x0011, /* A-Law encoded. */
SF_FORMAT_IMA_ADPCM = 0x0012, /* IMA ADPCM. */
SF_FORMAT_MS_ADPCM = 0x0013, /* Microsoft ADPCM. */
SF_FORMAT_GSM610 = 0x0020, /* GSM 6.10 encoding. */
SF_FORMAT_VOX_ADPCM = 0x0021, /* OKI / Dialogix ADPCM */
SF_FORMAT_G721_32 = 0x0030, /* 32kbs G721 ADPCM encoding. */
SF_FORMAT_G723_24 = 0x0031, /* 24kbs G723 ADPCM encoding. */
SF_FORMAT_G723_40 = 0x0032, /* 40kbs G723 ADPCM encoding. */
SF_FORMAT_DWVW_12 = 0x0040, /* 12 bit Delta Width Variable Word encoding. */
SF_FORMAT_DWVW_16 = 0x0041, /* 16 bit Delta Width Variable Word encoding. */
SF_FORMAT_DWVW_24 = 0x0042, /* 24 bit Delta Width Variable Word encoding. */
SF_FORMAT_DWVW_N = 0x0043, /* N bit Delta Width Variable Word encoding. */
SF_FORMAT_DPCM_8 = 0x0050, /* 8 bit differential PCM (XI only) */
SF_FORMAT_DPCM_16 = 0x0051, /* 16 bit differential PCM (XI only) */
SF_FORMAT_VORBIS = 0x0060, /* Xiph Vorbis encoding. */
/* Endian-ness options. */
SF_ENDIAN_FILE = 0x00000000, /* Default file endian-ness. */
SF_ENDIAN_LITTLE = 0x10000000, /* Force little endian-ness. */
SF_ENDIAN_BIG = 0x20000000, /* Force big endian-ness. */
SF_ENDIAN_CPU = 0x30000000, /* Force CPU endian-ness. */
SF_FORMAT_SUBMASK = 0x0000FFFF,
SF_FORMAT_TYPEMASK = 0x0FFF0000,
SF_FORMAT_ENDMASK = 0x30000000
} SF_FORMAT;
typedef struct SF_INFO {
sf_count_t frames; /* Used to be called samples. Changed to avoid confusion. */
int samplerate;
int channels;
int format;
int sections;
int seekable;
} SF_INFO;
typedef struct {
int format;
const char *name;
const char *extension;
} SF_FORMAT_INFO;
/* The enum was not named in the sndfile.h */
typedef enum SF_MODE {
/* Modes for opening files. */
SFM_READ = 0x10,
SFM_WRITE = 0x20,
SFM_RDWR = 0x30
} SF_MODE;
/* These values come from stdio.h */
typedef enum SF_SEEK {
SEEK_SET = 0,
SEEK_CUR = 1,
SEEK_END = 2
} SF_SEEK;
SNDFILE* sf_open(const char *path, int mode, SF_INFO *sfinfo);
sf_count_t sf_seek(SNDFILE *sndfile, sf_count_t frames, int whence);
const char* sf_strerror(SNDFILE *sndfile);
int sf_command(SNDFILE *sndfile, int command, void *data, int datasize);
sf_count_t sf_read_double(SNDFILE *sndfile, double *ptr, sf_count_t items);
sf_count_t sf_readf_double(SNDFILE *sndfile, double *ptr, sf_count_t frames);
sf_count_t sf_write_double(SNDFILE *sndfile, const double *ptr, sf_count_t items);
sf_count_t sf_writef_double(SNDFILE *sndfile, const double *ptr, sf_count_t frames);
int sf_close(SNDFILE *sndfile);
]]
local lib = ffi.load("sndfile")
sndfile.frame_type = ffi.typeof("double[?]")
-- This can be reused in sndfile:read() and sndfile:write()
-- to avoid allocations.
local double_buffer = sndfile.frame_type(1)
-- NOTE: Constants are also in ffi.C
sndfile.SF_FORMAT = ffi.typeof("SF_FORMAT")
-- FIXME: Maybe fall back to sf_command(SFC_GET_SIMPLE_FORMAT)
-- for unknown extensions. The simple formats are not always
-- ideal.
function sndfile.guess_format(filename)
local ext2format = {
raw = ffi.C.SF_FORMAT_RAW + ffi.C.SF_FORMAT_DOUBLE,
wav = ffi.C.SF_FORMAT_WAV + ffi.C.SF_FORMAT_FLOAT,
xi = ffi.C.SF_FORMAT_XI + ffi.C.SF_FORMAT_DPCM_16,
flac = ffi.C.SF_FORMAT_FLAC + ffi.C.SF_FORMAT_PCM_24,
ogg = ffi.C.SF_FORMAT_OGG + ffi.C.SF_FORMAT_DOUBLE
}
local ext = filename:match("%.(.+)$")
-- assume raw format for missing extensions
local format = ext2format[ext and ext:lower() or "raw"]
if not format then error("Unknown extension \""..ext.."\"") end
return format
end
function sndfile:new(path, mode, samplerate, channels, format)
local obj = {}
local info = ffi.new("SF_INFO[1]")
if mode == "SFM_WRITE" or
(format and bit.band(format, ffi.C.SF_FORMAT_TYPEMASK) == ffi.C.SF_FORMAT_RAW) then
info[0].samplerate = samplerate or 44100
info[0].channels = channels or 1
info[0].format = format or sndfile.guess_format(path)
end
obj.handle = lib.sf_open(path, ffi.new("SF_MODE", mode), info)
if obj.handle == nil then
error(ffi.string(lib.sf_strerror(nil)))
end
obj.handle = ffi.gc(obj.handle, lib.sf_close)
obj.info = info[0]
return setmetatable(obj, {__index = self})
end
function sndfile:seek(frames, whence)
whence = whence and ffi.new("SF_SEEK", whence) or ffi.C.SEEK_SET
return lib.sf_seek(self.handle, frames, whence)
end
-- TODO: Maybe support reading multiple samples at once.
function sndfile:read()
return lib.sf_read_double(self.handle, double_buffer, 1) == 1 and
tonumber(double_buffer[0]) or nil
end
function sndfile:readf(frame)
return lib.sf_readf_double(self.handle, frame, 1) == 1
end
-- TODO: Maybe support writing multiple samples at once
function sndfile:write(sample)
double_buffer[0] = sample
return lib.sf_write_double(self.handle, double_buffer, 1)
end
function sndfile:writef(frame)
return lib.sf_writef_double(self.handle, frame, 1)
end
function sndfile:close()
if self.handle then
-- NOTE: Finalizer must be removed to avoid a
-- double-close here and later by the garbage collector.
lib.sf_close(ffi.gc(self.handle, nil))
self.handle = nil
end
end
return sndfile
|