258 lines
8.4 KiB
C++
258 lines
8.4 KiB
C++
|
// SampledSound class implementation
|
||
|
//
|
||
|
// A sound as an array of multi-channel samples
|
||
|
//
|
||
|
// QATSH Copyright 2009 Jean-Philippe MEURET <jpmeuret@free.fr>
|
||
|
|
||
|
#include "SampledSound.h"
|
||
|
|
||
|
#include <iostream>
|
||
|
#include <limits>
|
||
|
#include <cstring>
|
||
|
|
||
|
#include <sndfile.hh>
|
||
|
|
||
|
|
||
|
SampledSound::SampledSound(int nChannels, int nFrames, int nSamplingRate,
|
||
|
double** adSamples)
|
||
|
: _nChannels(nChannels), _nFrames(nFrames), _nSamplingRate(nSamplingRate), _adSamples(0)
|
||
|
{
|
||
|
if (_nChannels)
|
||
|
{
|
||
|
_adSamples = new double*[_nChannels];
|
||
|
if (_nFrames && _adSamples)
|
||
|
{
|
||
|
for (int nChanInd = 0; nChanInd < _nChannels; nChanInd++)
|
||
|
{
|
||
|
_adSamples[nChanInd] = new double[_nFrames];
|
||
|
if (adSamples && _adSamples[nChanInd])
|
||
|
memcpy(_adSamples[nChanInd], adSamples[nChanInd], nFrames*sizeof(double));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SampledSound::~SampledSound()
|
||
|
{
|
||
|
freeSamples();
|
||
|
}
|
||
|
|
||
|
void SampledSound::freeSamples()
|
||
|
{
|
||
|
if (_adSamples)
|
||
|
{
|
||
|
for (int nChanInd = 0; nChanInd < _nChannels; nChanInd++)
|
||
|
delete [] _adSamples[nChanInd];
|
||
|
delete [] _adSamples;
|
||
|
_adSamples = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int SampledSound::samplingRate() const
|
||
|
{
|
||
|
return _nSamplingRate;
|
||
|
}
|
||
|
|
||
|
int SampledSound::nbChannels() const
|
||
|
{
|
||
|
return _nChannels;
|
||
|
}
|
||
|
|
||
|
int SampledSound::nbFrames() const
|
||
|
{
|
||
|
return _nFrames;
|
||
|
}
|
||
|
|
||
|
|
||
|
double** SampledSound::samples()
|
||
|
{
|
||
|
return _adSamples;
|
||
|
}
|
||
|
|
||
|
double* SampledSound::channel(int nChanInd)
|
||
|
{
|
||
|
return _adSamples[nChanInd];
|
||
|
}
|
||
|
|
||
|
|
||
|
bool SampledSound::load(const char* pszFileName)
|
||
|
{
|
||
|
std::cout << "SampledSound : Loading sound file '" << pszFileName << "' ..." << std::endl;
|
||
|
|
||
|
// Open sound file.
|
||
|
SndfileHandle sndFileHdle(pszFileName, SFM_READ);
|
||
|
|
||
|
// Check if all right till now.
|
||
|
if (!sndFileHdle)
|
||
|
{
|
||
|
std::cerr << "SampledSound : Failed to open sound file '" << pszFileName
|
||
|
<< "' : " << sndFileHdle.strError() << std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check for number of frames : as we load the whole sample into memory,
|
||
|
// we support only up to numeric_limits<int>::max() frames (samples per channel).
|
||
|
if (sndFileHdle.frames() > std::numeric_limits<int>::max())
|
||
|
{
|
||
|
std::cerr << "SampledSound : Sound file '" << pszFileName
|
||
|
<< "' too big (can't support more than " << std::numeric_limits<int>::max()
|
||
|
<< " samples)" << std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Save sample properties
|
||
|
_nFrames = (int)sndFileHdle.frames();
|
||
|
_nChannels = sndFileHdle.channels();
|
||
|
_nSamplingRate = sndFileHdle.samplerate();
|
||
|
|
||
|
// Load the sample into memory.
|
||
|
// 1) Allocate the sample arrays (1 per channel).
|
||
|
freeSamples();
|
||
|
_adSamples = new double*[_nChannels];
|
||
|
for (int nChanInd = 0; nChanInd < _nChannels; nChanInd++)
|
||
|
_adSamples[nChanInd] = new double[_nFrames];
|
||
|
|
||
|
|
||
|
// 2) Allocate a buffer for IO
|
||
|
const int knFramesToRead = 2048;
|
||
|
double* aBuffer = new double[_nChannels*knFramesToRead];
|
||
|
|
||
|
// 3) Read the file and store the samples into the arrays.
|
||
|
sf_count_t nReadSamples = 0;
|
||
|
sf_count_t nReadFrames;
|
||
|
int nSampInd;
|
||
|
int nCurrFrameInd = 0;
|
||
|
while ((nReadFrames = sndFileHdle.readf(aBuffer, knFramesToRead)))
|
||
|
{
|
||
|
nSampInd = 0;
|
||
|
while (nReadFrames--)
|
||
|
{
|
||
|
for (int nChanInd = 0; nChanInd < _nChannels; nChanInd++)
|
||
|
_adSamples[nChanInd][nCurrFrameInd] = aBuffer[nSampInd++];
|
||
|
nCurrFrameInd++;
|
||
|
}
|
||
|
nReadSamples += nSampInd;
|
||
|
}
|
||
|
delete [] aBuffer;
|
||
|
|
||
|
std::cout << "... done (" << nReadSamples << " samples x channels read)" << std::endl;
|
||
|
|
||
|
return nReadSamples == _nFrames * _nChannels;
|
||
|
}
|
||
|
|
||
|
//======================================================================
|
||
|
// File extension to libsndfile::SF_FORMAT_* major type conversion table
|
||
|
// Note: Was setup for libsndfile 1.0.17 (more formats may be supported now).
|
||
|
struct SSndFileMajorFormatDesc
|
||
|
{
|
||
|
const char* pszFileExt;
|
||
|
int nSndFileMajorFormat;
|
||
|
};
|
||
|
|
||
|
static SSndFileMajorFormatDesc KSndFileMajorType[] =
|
||
|
{
|
||
|
{ ".wav", SF_FORMAT_WAV }, // Microsoft WAV format (little endian).
|
||
|
{ ".aif", SF_FORMAT_AIFF }, // Apple/SGI AIFF format (big endian).
|
||
|
{ ".aifc", SF_FORMAT_AIFF }, // Apple/SGI AIFF format (big endian).
|
||
|
{ ".aiff", SF_FORMAT_AIFF }, // Apple/SGI AIFF format (big endian).
|
||
|
{ ".au", SF_FORMAT_AU }, // Sun/NeXT AU format (big endian).
|
||
|
{ ".snd", SF_FORMAT_AU }, // Sun/NeXT AU format (big endian).
|
||
|
{ ".raw", SF_FORMAT_RAW }, // RAW PCM data.
|
||
|
{ ".pcm", SF_FORMAT_RAW }, // RAW PCM data.
|
||
|
{ ".voc", SF_FORMAT_VOC }, // VOC files.
|
||
|
{ ".caf", SF_FORMAT_CAF }, // Apple Core Audio File format
|
||
|
{ ".w64", SF_FORMAT_W64 }, // Sonic Foundry's 64 bit RIFF/WAV
|
||
|
{ ".sf", SF_FORMAT_IRCAM }, // Berkeley/IRCAM/CARL
|
||
|
{ ".paf", SF_FORMAT_PAF }, // Ensoniq PARIS file format.
|
||
|
{ ".iff", SF_FORMAT_SVX }, // Amiga IFF / SVX8 / SV16 format.
|
||
|
{ ".svx", SF_FORMAT_SVX }, // Amiga IFF / SVX8 / SV16 format.
|
||
|
{ ".nist", SF_FORMAT_NIST }, // Sphere NIST format.
|
||
|
{ ".sph", SF_FORMAT_NIST }, // Sphere NIST format.
|
||
|
{ ".flac", SF_FORMAT_FLAC }, // FLAC lossless file format
|
||
|
// { ".", SF_FORMAT_MAT4 }, // Matlab (tm) V4.2 / GNU Octave 2.0
|
||
|
// { ".", SF_FORMAT_MAT5 }, // Matlab (tm) V5.0 / GNU Octave 2.1
|
||
|
// { ".", SF_FORMAT_PVF }, // Portable Voice Format
|
||
|
// { ".", SF_FORMAT_XI }, // Fasttracker 2 Extended Instrument
|
||
|
// { ".", SF_FORMAT_HTK }, // HMM Tool Kit format
|
||
|
// { ".", SF_FORMAT_SDS }, // Midi Sample Dump Standard
|
||
|
// { ".", SF_FORMAT_AVR }, // Audio Visual Research
|
||
|
// { ".", SF_FORMAT_WAVEX }, // MS WAVE with WAVEFORMATEX
|
||
|
// { ".", SF_FORMAT_SD2 }, // Sound Designer 2
|
||
|
{ 0, 0 } // End of data marker; don't remove.
|
||
|
};
|
||
|
|
||
|
// ESampleFormat to libsndfile::SF_FORMAT_* subtype conversion table
|
||
|
static int KSndFileSubType[SampledSound::eSampFormatNumber] =
|
||
|
{
|
||
|
SF_FORMAT_PCM_16, // Signed 16 bit data
|
||
|
SF_FORMAT_PCM_24, // Signed 24 bit data
|
||
|
SF_FORMAT_PCM_32, // Signed 32 bit data
|
||
|
|
||
|
SF_FORMAT_FLOAT, // 32 bit float data
|
||
|
SF_FORMAT_DOUBLE // 64 bit float data
|
||
|
};
|
||
|
|
||
|
// Build a libsndfile format from the file extension (major format)
|
||
|
// and the sample format (format subtype) (see libsndfile API).
|
||
|
static int SndfileFormat(const char* pszFileExt, SampledSound::ESampleFormat eSampFormat)
|
||
|
{
|
||
|
SSndFileMajorFormatDesc* pFormaDesc = KSndFileMajorType;
|
||
|
while (pFormaDesc->pszFileExt && strcmp(pFormaDesc->pszFileExt, pszFileExt))
|
||
|
pFormaDesc++;
|
||
|
std::cout << "SampledSound : SndfileFormat(" << pszFileExt << ") : Major="
|
||
|
<< std::hex << pFormaDesc->nSndFileMajorFormat
|
||
|
<< ", SubType=" << KSndFileSubType[eSampFormat] << std::dec << std::endl;
|
||
|
return pFormaDesc->nSndFileMajorFormat | KSndFileSubType[eSampFormat];
|
||
|
}
|
||
|
|
||
|
// Store samples to the given file using given sample format.
|
||
|
bool SampledSound::store(const char* pszFileName, ESampleFormat eSampFormat) const
|
||
|
{
|
||
|
std::cout << "SampledSound : Storing sound file '" << pszFileName << "' ..." << std::endl;
|
||
|
|
||
|
// Determine sample output format and check if it is supported.
|
||
|
const int format = SndfileFormat(strrchr(pszFileName, '.'), eSampFormat);
|
||
|
if (!SndfileHandle::formatCheck(format, _nChannels, _nSamplingRate))
|
||
|
{
|
||
|
std::cerr << "SampledSound : Unsupported output format for '" << pszFileName
|
||
|
<< "'" << std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Try and open sound file.
|
||
|
SndfileHandle sndFileHdle(pszFileName, SFM_WRITE, format, _nChannels, _nSamplingRate);
|
||
|
if (!sndFileHdle)
|
||
|
{
|
||
|
std::cerr << "SampledSound : Failed to open sound file '" << pszFileName
|
||
|
<< "' : " << sndFileHdle.strError() << std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Write samples to file.
|
||
|
const int nFrameBlockLen = 2048; // Nb of frames (1 frame = nbChannels() samples)
|
||
|
double* aFrameBlock = new double[nFrameBlockLen*_nChannels];
|
||
|
int nCurrFrameInd = 0; // Index in _adSamples[*] of the frame to write
|
||
|
while (nCurrFrameInd < _nFrames) // For each possible frame block :
|
||
|
{
|
||
|
// Copy samples to the frame block
|
||
|
int nSampInd = 0; // Index in aFrameBlock of the sample to copy from _adSamples
|
||
|
while (nSampInd < nFrameBlockLen*_nChannels && nCurrFrameInd < _nFrames)
|
||
|
{
|
||
|
for (int nChanInd = 0; nChanInd < _nChannels; nChanInd++)
|
||
|
aFrameBlock[nSampInd++] = _adSamples[nChanInd][nCurrFrameInd];
|
||
|
nCurrFrameInd++;
|
||
|
}
|
||
|
|
||
|
// Write the frame block.
|
||
|
if (sndFileHdle.writef(aFrameBlock, nSampInd / _nChannels) < nSampInd / _nChannels)
|
||
|
{
|
||
|
std::cerr << "SampledSound : Failed to write frame block in '" << pszFileName
|
||
|
<< "' : " << sndFileHdle.strError() << std::endl;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
delete [] aFrameBlock;
|
||
|
|
||
|
return true;
|
||
|
}
|