// ----
// ---- file : WavIO.tks
// ---- author : Bastian Spiegel <bs@tkscript.de>
// ---- license: (c) 2009-2010 by Bastian Spiegel.
// ---- Distributed under terms of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL). See
// ---- http://www.gnu.org/licenses/licenses.html#LGPL or COPYING for further information.
// ----
// ---- info : written based on the WAV documentation at
// ---- <http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html>
// ---- and <http://web.archive.org/web/20080113195252/http://www.borg.com/~jglatt/tech/wave.htm>
// --- resp. <http://home.roadrunner.com/~jgglatt/tech/wave.htm>
// ----
// ---- created: 22Sep2009
// ---- changed: 26Dec2009, 16Jul2010, 17Jul2010, 19Jul2010
// ----
// ----
// ----
module MWavIO;
class WavIO_FMT {
// 18 bytes (0x12)
short format_tag; // 1 for integer PCM, 3 for float. also see FMT_xxx defines below.
short num_ch;
int sample_rate;
int avg_bytes_per_sec; // mainly for compressed formats?
short block_align;
short sample_bits; // 8, 16, 32, ..
short ext_size; // 18 bytes for Akai, see hexdumps below
// (note) the strange first-word-aligned-right coding style is reserved for "C" structs written in stone .)
}
class WavIO_SMPL_LOOP { // (note) not to be confused with the 'loop' smpl sampler_data extension (Akai?)
// 24 bytes (0x18)
int identifier;
int type; // 0=normal, 1=alternating, 2=backward, 3..31 reserved, 32-? manufacturer defined
int start; // sample offset (_not_ byteoffset!)
int end; // sample offset (_not_ byteoffset!)
int fraction;
int playcount;
}
class WavIO_SMPL {
// 36 + (num_sample_loops*24) + sampler_data bytes
int manufacturer; // high-byte denotes number of used low-bytes.
// 0x01000047 is Akai, for example. see <http://www.midi.org/techspecs/manid.php>
int product;
int sample_period;
int midi_unity_note;
int midi_pitch_fraction;
int smpte_format;
int smpte_offset;
int num_sample_loops;
int sampler_data; // #extended bytes (after this chunk)
WavIO_SMPL_LOOP loops[]; // WavIO_SMPL_LOOP data
// extended sampler_data: manufacturer=0x47 (Akai), sampler_data=0x12, offset=0x5E, actual #bytes until next chunk: (0x88-0x5e)=0x2A
//
// A: (MXR woodblock.wav, 4310 samples, 16bit, mono, 0 loops)
// 000058 6c 6f ......lo
// 000060 6f 70 00 00 00 00 fe ff op....þÿ
// 000068 ff ff c7 10 00 00 00 00 ÿÿÇ.....
// 000070 00 00 00 00 00 00 02 00 ........
// 000078 3c 00 00 00 01 00 00 01 <.......
// 000080 ff ff ff ff d6 10 00 00 ÿÿÿÿÖ...
//
// B: (MW Spike Bass.wav, 25121 samples, 16bit, mono, 0 loops)
// 000058 00 00 12 00 00 00 6c 6f ......lo
// 000060 6f 70 00 00 00 00 ff ff op....ÿÿ
// 000068 ff ff 12 62 00 00 00 00 ÿÿ.b....
// 000070 00 00 00 00 00 00 02 00 ........
// 000078 2d 00 00 00 00 00 00 01 -.......
// 000080 00 00 00 00 21 62 00 00 ....!b..
//
// C: (Morning G1 Loop.wav, 392306 samples, 16bit, mono, 1 loop)
// 000058 00 00 12 00 00 00 6c 6f ......lo
// 000060 6f 70 00 00 00 00 ef 18 op....ï.
// 000068 04 00 61 fc 05 00 00 00 ..aü....
// 000070 00 00 00 00 00 00 02 00 ........
// 000078 2b 00 00 00 02 00 00 01 +.......
// 000080 00 00 00 00 71 fc 05 00 ....qü..
//
// Extended Akai 'loop' sampler_data:
//
// offset size info
// 0 4 'loop'
// 4 4 <unknown>
// (00 00 00 00)
// 8 4 <unknown>
// (fe ff ff ff)
// (seems to be a 2s complement integer)
// 12 2 <unknown>
// (c7 10)
// 14 4 <unknown>
// (00 00 00 00)
// 18 6 <unknown>
// (00 00 00 00 00 00)
// 24 2 <unknown>
// (02 00)
// 26 2 MIDI base note?
// (3c 00)
// 28 2 <unknown>
// (00 00)
// 30 2 <unknown>
// (01 00)
// 32 2 <unknown>
// (00 01)
// 34 4 <unknown>
// (ff ff ff ff)
// (seems to be a 2s complement integer)
// 38 2 <unknown>
// (D6 10)
// 40 2 <unknown>
// (00 00)
// 42 (length)
}
class WavIO {
define int MAGIC_RIFF = 0x46464952; // 'RIFF'
define int MAGIC_WAVE = 0x45564157; // 'WAVE'
define int MAGIC_FMT = 0x20746d66; // 'fmt '
define int MAGIC_FACT = 0x74636166; // 'fact'
define int MAGIC_SMPL = 0x6c706d73; // 'smpl'
define int MAGIC_LOOP = 0x706f6f6c; // 'loop'
define int MAGIC_DATA = 0x61746164; // 'data'
// (note) unsupported chunks: 'wavl', 'cue ', 'inst', 'note', 'list', 'plst', 'labl', 'ltxt'
define int FMT_PCM = 0x0001;
define int FMT_MS_ADPCM = 0x0002;
define int FMT_IEEE_FLOAT = 0x0003;
define int FMT_IBM_CVSD = 0x0005;
define int FMT_ALAW = 0x0006;
define int FMT_MULAW = 0x0007;
define int FMT_OKI_ADPCM = 0x0010;
define int FMT_DVI_IMA_ADPCM = 0x0011;
define int FMT_MEDIASPACE_ADPCM = 0x0012;
define int FMT_SIERRA_ADPCM = 0x0013;
define int FMT_G723_ADPCM = 0x0014;
define int FMT_DIGISTD = 0x0015;
define int FMT_DIGIFIX = 0x0016;
define int FMT_DIALOGIC_OKI_ADPCM = 0x0017;
define int FMT_YAMAHA_ADPCM = 0x0020;
define int FMT_SONARC = 0x0021;
define int FMT_DSPGROUP_TRUESPEECH = 0x0022;
define int FMT_ECHOSC1 = 0x0023;
define int FMT_AUDIOFILE_AF36 = 0x0024;
define int FMT_APTX = 0x0025;
define int FMT_AUDIOFILE_AF10 = 0x0026;
define int FMT_DOLBY_AC2 = 0x0030;
define int FMT_GSM610 = 0x0031;
define int FMT_ANTEX_ADPCME = 0x0033;
define int FMT_CONTROL_RES_VQLPC = 0x0034;
define int FMT_CONTROL_RES_VQLPC = 0x0035;
define int FMT_DIGIADPCM = 0x0036;
define int FMT_CONTROL_RES_CR10 = 0x0037;
define int FMT_NMS_VBXADPCM = 0x0038;
define int FMT_CS_IMAADPCM = 0x0039;
define int FMT_G721_ADPCM = 0x0040;
define int FMT_MPEG = 0x0050;
define int FMT_XBOX_ADPCM = 0x0069;
define int FMT_CREATIVE_ADPCM = 0x0200;
define int FMT_CREATIVE_FASTSPEECH8 = 0x0202;
define int FMT_CREATIVE_FASTSPEECH10 = 0x0203;
define int FMT_FM_TOWNS_SND = 0x0300;
define int FMT_OLIGSM = 0x1000;
define int FMT_OLIADPCM = 0x1001;
define int FMT_OLICELP = 0x1002;
define int FMT_OLISBC = 0x1003;
define int FMT_OLIOPR = 0x1004;
define exception Fail : UncriticalError;
public static WavIO_FMT *fmt; // reset to null before loading a file
public static WavIO_SMPL *smpl; // reset to null before loading a file, may contain extended sampler (loop) information after loading.
protected static GetChunkNameFromInt(int _i) : String {
// _i is little-endian 32bit integer
char d = (_i >> 24) & 255;
char c = (_i >> 16) & 255;
char b = (_i >> 8) & 255;
char a = (_i ) & 255;
String t;
t.realloc(5);
t[0] = a;
t[1] = b;
t[2] = c;
t[3] = d;
t[4] = 0;
t.fixLength(); // xxx hackish..
return t;
}
protected static GetFourCCHexStringFromInt(int _i) : String {
// _i is little-endian 32bit integer
char d = (_i >> 24) & 255;
char c = (_i >> 16) & 255;
char b = (_i >> 8) & 255;
char a = (_i ) & 255;
Integer io;
String t;
t.empty();
io = a;
t.append(io.printf("%02x "));
io = b;
t.append(io.printf("%02x "));
io = c;
t.append(io.printf("%02x "));
io = d;
t.append(io.printf("%02x"));
return t;
}
protected static ReadDATA(Stream ifs,
int chunkSz,
FloatArray d
) {
if!([FMT_PCM, FMT_IEEE_FLOAT].contains(fmt.format_tag))
{
throw Fail("LoadStream: unsupported format (have="+GetChunkNameFromInt(fmt.format_tag)+
" expected "+GetChunkNameFromInt(FMT_PCM)+" or "+GetChunkNameFromInt(FMT_IEEE_FLOAT)+")"
);
}
if!([8, 16, 24, 32].contains(fmt.sample_bits))
{
throw Fail("LoadStream: unsupported #bits (have="+fmt.sample_bits+" expect=8, 16, 24, or 32)");
}
if!([1, 2].contains(fmt.num_ch))
{
throw Fail("LoadStream: unsupported #channels (have="+fmt.num_ch+" expect=[1, 2])");
}
int smpLen = chunkSz / fmt.block_align;
d.alloc(smpLen * fmt.num_ch);
float s;
float smp;
int smpi;
d.numElements = 0;
// Load sampledata and expand to -1..1 32bit floating point
float t = 0.0f;
if(32 == fmt.sample_bits)
{
if(FMT_IEEE_FLOAT != fmt.format_tag)
{
throw Fail("LoadStream: cannot handle 32 bit integer sample format");
}
// Load 32-bit IEEE float sample
// compile loop(smpLen)
// {
// loop(fmt.num_ch)
// {
// smp = ifs.f32;
// d.add(smp);
// }
// }
d.loadFromStreamFloat32(ifs, smpLen*fmt.num_ch);
}
else if(24 == fmt.sample_bits)
{
// Load 24-bit sample (signed integers)
// t = ((1<<(32-1))-1);
// compile loop(smpLen)
// {
// loop(fmt.num_ch)
// {
// int a = ifs.i8; // read bits 0..7
// int b = ifs.i8; // read bits 8..15
// int c = ifs.i8; // read bits 16..23
// smpi = ((c&255)<<24) | ((b&255)<<16) | ((a&255)<<8);
// smp = (float(smpi)) / t;
// d.add(smp);
// }
// }
d.loadFromStreamSigned24(ifs, smpLen*fmt.num_ch);
}
else if(16 == fmt.sample_bits)
{
// Load 16-bit sample (signed integers)
// s = 1.0f / 32767.0f;
// compile loop(smpLen)
// {
// loop(fmt.num_ch)
// {
// smp = ifs.i16 * s;
// d.add(smp);
// }
// }
d.loadFromStreamSigned16(ifs, smpLen*fmt.num_ch);
}
else
{
// Load 8-bit sample (unsigned integers)
// compile loop(smpLen)
// {
// loop(fmt.num_ch)
// {
// smpi = ifs.i8;
// smp = ((smpi-128)<<24) / float((1<<31)-1);
// d.add(smp);
// }
// }
d.loadFromStreamUnsigned8(ifs, smpLen*fmt.num_ch);
}
return true;
}
protected static ReadSMPL(Stream ifs, int chunkSz, WavIO_SMPL smpl, StSample _sampleHint) {
int startOff = ifs.offset;
smpl.manufacturer = ifs.i32;
smpl. product = ifs.i32;
smpl. sample_period = ifs.i32;
smpl. midi_unity_note = ifs.i32;
smpl. midi_pitch_fraction = ifs.i32;
smpl. smpte_format = ifs.i32;
smpl. smpte_offset = ifs.i32;
smpl. num_sample_loops = ifs.i32;
smpl. sampler_data = ifs.i32;
trace "xxx ReadSMPL: sampleHint="+#(_sampleHint);
if(null != _sampleHint)
{
StWaveform wf <= _sampleHint.waveform;
trace "xxx ReadSMPL: wfHint="+#(wf);
if(null != wf)
{
wf.sampleRate = smpl.sample_period;
int midiNote = smpl.midi_unity_note;
if(midiNote < 0)
midiNote = 60;
else if(midiNote > 100)
midiNote = 60;
wf.baseFrequency = ( ((440.0f/32.0f)*exp( ((midiNote-9.0)/12.0)*ln(2.0) )) );
trace "xxx WavIO::ReadSMPL: wf.baseFrequency="+wf.baseFrequency;
// (todo) convert pitch fraction..
}
}
trace "[dbg] smpl.manufacturer = " + GetFourCCHexStringFromInt(smpl.manufacturer) ;
trace "[dbg] smpl. product = " + GetFourCCHexStringFromInt(smpl.product) ;
trace "[dbg] smpl. sample_period = " + smpl. sample_period ;
trace "[dbg] smpl. midi_unity_note = " + smpl. midi_unity_note ;
trace "[dbg] smpl. midi_pitch_fraction = " + smpl. midi_pitch_fraction ;
trace "[dbg] smpl. smpte_format = " + smpl. smpte_format ;
trace "[dbg] smpl. smpte_offset = " + smpl. smpte_offset ;
trace "[dbg] smpl. num_sample_loops = " + smpl. num_sample_loops ;
trace "[dbg] smpl. sampler_data = " + smpl.sampler_data ;
// Read loops, if any
if(smpl.num_sample_loops > 0)
{
IntArray *sampleLoops;
// Initialize sample loops
if(null != _sampleHint)
{
_sampleHint.freeSampleLoops();
sampleLoops <= _sampleHint.getOrCreateSampleLoops();
}
int bytesLeft = chunkSz - (ifs.offset - startOff);
int expectLeft = (24 * smpl.num_sample_loops) + smpl.sampler_data;
if(bytesLeft == expectLeft)
{
Array<WavIO_SMPL_LOOP> a;
a <= smpl.loops;
a.alloc(smpl.num_sample_loops);
trace "[dbg] WavIO::ReadSMPL: found "+smpl.num_sample_loops+" loops.";
int loopIdx = 0;
loop(smpl.num_sample_loops)
{
WavIO_SMPL_LOOP lp <= a.nextFree;
lp.identifier = GetFourCCHexStringFromInt(ifs.i32);
lp. type = ifs.i32;
lp. start = ifs.i32;
lp. end = ifs.i32;
lp. fraction = ifs.i32;
lp. playcount = ifs.i32;
// Add new sample loop
if(null != _sampleHint)
{
// offset
sampleLoops.add(lp.start);
// len
if(lp.end > lp.start)
{
sampleLoops.add(lp.end - lp.start);
}
else
{
sampleLoops.add(lp.start - lp.end);
}
// #repeats
sampleLoops.add(lp.playcount & 65535);
}
trace "[dbg] WavIO::ReadSMPL: loop info:";
trace " loop["+loopIdx+"].identifier = " + GetFourCCHexStringFromInt(lp.identifier);
trace " loop["+loopIdx+"]. type = " + lp. type;
trace " loop["+loopIdx+"]. start = " + lp. start;
trace " loop["+loopIdx+"]. end = " + lp. end;
trace " loop["+loopIdx+"]. fraction = " + lp. fraction;
trace " loop["+loopIdx+"]. playcount = " + lp.playcount;
loopIdx++;
}
// Verify that sample/loop offsets/lengths do not cross waveform boundaries
if(null != _sampleHint)
{
_sampleHint.verifySampleAreas();
}
}
else
{
throw Fail("WavIO::ReadSMPL: expected "+expectLeft+" bytes for sample loops and sampler_data, bytesLeft="+bytesLeft);
}
}
// Skip extended sampler attributes
if(smpl.sampler_data > 0)
{
trace "[...] WavIO::ReadSMPL: skipping "+smpl.sampler_data+" opaque sampler_data bytes at offset="+ifs.offset+".";
ifs.seek(smpl.sampler_data, SEEK_CUR);
}
}
protected static ReadFMT(Stream ifs, int chunkSz) {
fmt.format_tag = ifs.i16;
fmt. num_ch = ifs.i16;
fmt.sample_rate = ifs.i32;
fmt. avg_bytes_per_sec = ifs.i32;
fmt. block_align = ifs.i16;
fmt.sample_bits = ifs.i16;
trace "[...] WavIO::ReadFMT: "
"\n\t format_tag = "+fmt. format_tag +
"\n\t num_ch = "+fmt. num_ch +
"\n\t sample_rate = "+fmt. sample_rate +
"\n\t avg_bytes_per_sec = "+fmt. avg_bytes_per_sec +
"\n\t block_align = "+fmt. block_align +
"\n\t sample_bits = "+fmt. sample_bits ;
if(chunkSz >= 18)
{
fmt.ext_size = ifs.i16;
if(fmt.ext_size > 0)
{
trace "[dbg] WavIO::ReadFMT: found extension, size="+fmt.ext_size+" bytes";
// (note) the ext_size seems to be wrong / too small in some wavs
}
}
}
public static LoadStream(Stream ifs,
FloatArray d,
Integer _retSampleRate,
Integer _retNumCh,
Object _retFileInfo,
StSample _sampleHint
) : boolean {
return = false;
fmt <= null;
smpl <= null;
ifs.byteOrder = YAC_LITTLE_ENDIAN;
return = false;
String retFileInfo; retFileInfo.empty();
retFileInfo.append("WAV");
int magicRIFF = ifs.i32;
if(magicRIFF == MAGIC_RIFF)
{
int totalSizeMinus8 = ifs.i32;
int magicWAVE = ifs.i32;
if(magicWAVE == MAGIC_WAVE)
{
int factNumSamples = -1;
while(!ifs.eof())
{
int chunkId = ifs.i32;
int chunkSz = ifs.i32;
if(0 == chunkSz)
{
throw Fail("LoadStream: size of chunk "+GetFourCCHexStringFromInt(chunkId)+" is 0");
}
int chunkOff = ifs.offset;
int chunkEnd = chunkOff + chunkSz;
////trace "[dbg] WavIO: found chunkId="+GetFourCCHexStringFromInt(chunkId)+"('"+GetChunkNameFromInt(chunkId)+"'), size="+chunkSz+" at offset="+(ifs.offset-8)+"/"+ifs.size;
switch(chunkId)
{
case 0:
throw Fail("LoadStream: no more chunks");
default: // Unknown chunk
trace "[~~~] WavIO: skipping unknown chunkId="+GetFourCCHexStringFromInt(chunkId)+" ('"+GetChunkNameFromInt(chunkId)+"'), size="+chunkSz+" at offset="+(ifs.offset-8);
break;
case MAGIC_FMT:
// Read format chunk
trace "[...] WavIO: found 'fmt ' chunk at offset="+(chunkOff-8)+" size="+chunkSz;
if(null != fmt)
{
trace "[~~~] WavIO: found more than one format chunk! overwriting previously read data..";
}
fmt <= new WavIO_FMT;
try
{
ReadFMT(ifs, chunkSz);
_retNumCh = fmt.num_ch;
_retSampleRate = fmt.sample_rate;
retFileInfo.append(", "+fmt.sample_bits+" bit");
retFileInfo.append(", "+((2==fmt.num_ch)?"stereo":"mono"));
retFileInfo.append(", "+fmt.sample_rate+" Hz");
}
catch(UncriticalError e) throw e;
break;
case MAGIC_FACT:
// Read fact chunk (mainly for compressed data)
trace "[...] WavIO: found 'fact' chunk at offset="+(chunkOff-8)+", size="+chunkSz;
if(-1 != factNumSamples)
{
trace "[~~~] WavIO: found more than one FACT chunk! overwriting previously read data..";
}
factNumSamples = ifs.i32;
trace "[...] WavIO::ReadFACT: numSamples="+factNumSamples;
break;
case MAGIC_SMPL:
// Read sampler chunk
trace "[...] WavIO: found 'smpl' chunk at offset="+(chunkOff-8)+", size="+chunkSz;
if(null != smpl)
{
trace "[~~~] WavIO: found more than one sampler chunk! overwriting previously read data..";
}
smpl <= new WavIO_SMPL;
try
{
ReadSMPL(ifs, chunkSz, smpl, _sampleHint);
retFileInfo.append(", "+(smpl.num_sample_loops)+" loops");
}
catch(UncriticalError e) throw e;
break;
case MAGIC_DATA:
// Read data chunk
if(null == fmt)
{
throw Fail("LoadStream: found DATA chunk before FMT chunk");
}
trace "[...] WavIO: found 'data' chunk at offset="+(chunkOff-8)+", size="+chunkSz;
try
{
return = ReadDATA(ifs, chunkSz, d);
retFileInfo.append(", "+(d.numElements/fmt.num_ch)+" samples");
}
catch(UncriticalError e) throw e;
break;
}
// Skip any unread bytes of last chunk
if(chunkEnd > ifs.offset)
{
trace "[~~~] WavIO: skipping "+(chunkEnd-ifs.offset)+" unread bytes of chunk '"+GetChunkNameFromInt(chunkId)+"' at offset="+ifs.offset;
ifs.seek(chunkEnd - ifs.offset, SEEK_CUR);
}
else if(ifs.offset > chunkEnd)
{
throw Fail("LoadStream: FMT: read position ("+ifs.offset+") exceeds chunk end ("+chunkEnd+")");
}
// Next chunk
}
}
else throw Fail("LoadStream: wrong WAVE magic (have='"+GetChunkNameFromInt(magicWAVE)+"', expect='"+GetChunkNameFromInt(MAGIC_WAVE)+"')");
}
else throw Fail("LoadStream: wrong RIFF magic (have='"+GetChunkNameFromInt(magicRIFF)+"', expect='"+ GetChunkNameFromInt(MAGIC_RIFF)+"')");
if(null != _retFileInfo)
{
_retFileInfo = retFileInfo;
}
}
public static LoadLocal(String _filename,
FloatArray d,
Integer _retSampleRate,
Integer _retNumCh,
Object _retFileInfo,
StSample _sampleHint
) : boolean {
return = false;
trace "[dbg] WavIO::LoadLocal: fileName=\""+_filename+"\".";
File f;
if(f.openLocal(_filename, IOS_IN))
{
try return = LoadStream(f, d, _retSampleRate, _retNumCh, _retFileInfo, _sampleHint);
catch(UncriticalError e) throw e;
finally f.close();
}
else throw Fail("LoadLocal: failed to open file \""+_filename+"\".");
}
public static SaveStream(Stream ofs, FloatArray sam, int _sampleRate, _numCh) : boolean {
explain "Save float waveform to 16bit .wav file";
return = false;
int smpLen = sam.numElements;
ofs.byteOrder = YAC_LITTLE_ENDIAN;
ofs.i32 = MAGIC_RIFF;
ofs.i32 = 44 + (smpLen * 2) - 8; // *2 for 16bit
ofs.i32 = MAGIC_WAVE;
ofs.i32 = MAGIC_FMT;
ofs.i32 = 16;
ofs.i16 = FMT_PCM;
ofs.i16 = _numCh;
ofs.i32 = _sampleRate;
ofs.i32 = (_numCh * _sampleRate); // bytes per second
ofs.i16 = (_numCh * 2); // block align
ofs.i16 = 16;
ofs.i32 = MAGIC_DATA;
ofs.i32 = smpLen * 2; // *2 for 16bit
int sOff = 0;
float s= 32767.0;
compile loop(smpLen)
{
float v= sam[sOff++];
if(v>1) v=1; else if(v<-1) v=-1;
ofs.i16 = v * s;
}
return true;
}
public static SaveLocal(String _filename, FloatArray d, int _sampleRate, _numCh) : boolean {
return = false;
File f;
if(f.openLocal(_filename, IOS_OUT))
{
try return = SaveStream(f, d, _sampleRate, _numCh);
catch(UncriticalError e) throw e;
finally f.close();
}
else throw Fail("SaveLocal: failed to open file \""+_filename+"\".");
}
}
// (note) also see testwavio_simple.tkp
function Test() {
FloatArray sam;
boolean b;
Integer sampleRate, numCh;
// Load
try {
b = WavIO.LoadLocal("samples/housyriff_C_126.wav", sam, sampleRate, numCh, null, null);
if(b)
{
trace "[...] loaded sample len="+(sam.numElements / numCh)+" #ch="+numCh+" rate="+sampleRate;
}
trace "b="+b;
}
catch(WavIO::Fail e) {
trace "[---] caught "+e.fullName+": \""+e.message+"\".";
}
// Save
try {
b = WavIO.SaveLocal("samples/t.wav", sam, sampleRate, numCh);
if(b)
{
trace "[...] saved sample";
}
trace "b="+b;
}
catch(WavIO::Fail e) {
trace "[---] caught "+e.fullName+": \""+e.message+"\".";
}
}
//Test();