You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
685 lines
18 KiB
685 lines
18 KiB
/*
|
|
Rosegarden
|
|
A sequencer and musical notation editor.
|
|
|
|
This program is Copyright 2000-2008
|
|
Guillaume Laurent <glaurent@telegraph-road.org>,
|
|
Chris Cannam <cannam@all-day-breakfast.com>,
|
|
Richard Bown <bownie@bownie.com>
|
|
|
|
The moral right of the authors to claim authorship of this work
|
|
has been asserted.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the
|
|
License, or (at your option) any later version. See the file
|
|
COPYING included with this distribution for more information.
|
|
*/
|
|
|
|
#include "RIFFAudioFile.h"
|
|
#include "RealTime.h"
|
|
#include "Profiler.h"
|
|
|
|
using std::cout;
|
|
using std::cerr;
|
|
using std::endl;
|
|
|
|
//#define DEBUG_RIFF
|
|
|
|
|
|
namespace Rosegarden
|
|
{
|
|
|
|
RIFFAudioFile::RIFFAudioFile(unsigned int id,
|
|
const std::string &name,
|
|
const std::string &fileName):
|
|
AudioFile(id, name, fileName),
|
|
m_subFormat(PCM),
|
|
m_bytesPerSecond(0),
|
|
m_bytesPerFrame(0)
|
|
{}
|
|
|
|
RIFFAudioFile::RIFFAudioFile(const std::string &fileName,
|
|
unsigned int channels = 1,
|
|
unsigned int sampleRate = 48000,
|
|
unsigned int bytesPerSecond = 6000,
|
|
unsigned int bytesPerFrame = 2,
|
|
unsigned int bitsPerSample = 16):
|
|
AudioFile(0, "", fileName)
|
|
{
|
|
m_bitsPerSample = bitsPerSample;
|
|
m_sampleRate = sampleRate;
|
|
m_bytesPerSecond = bytesPerSecond;
|
|
m_bytesPerFrame = bytesPerFrame;
|
|
m_channels = channels;
|
|
|
|
if (bitsPerSample == 16)
|
|
m_subFormat = PCM;
|
|
else if (bitsPerSample == 32)
|
|
m_subFormat = FLOAT;
|
|
else
|
|
throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports 16 or 32-bit PCM or IEEE floating-point RIFF files for writing"));
|
|
|
|
}
|
|
|
|
RIFFAudioFile::~RIFFAudioFile()
|
|
{}
|
|
|
|
|
|
// Show some stats on this file
|
|
//
|
|
void
|
|
RIFFAudioFile::printStats()
|
|
{
|
|
cout << "filename : " << m_fileName << endl
|
|
<< "channels : " << m_channels << endl
|
|
<< "sample rate : " << m_sampleRate << endl
|
|
<< "bytes per second : " << m_bytesPerSecond << endl
|
|
<< "bits per sample : " << m_bitsPerSample << endl
|
|
<< "bytes per frame : " << m_bytesPerFrame << endl
|
|
<< "file length : " << m_fileSize << " bytes" << endl
|
|
<< endl;
|
|
}
|
|
|
|
bool
|
|
RIFFAudioFile::appendSamples(const std::string &buffer)
|
|
{
|
|
/*
|
|
if (m_outFile == 0 || m_type != WAV)
|
|
return false;
|
|
*/
|
|
|
|
// write out
|
|
putBytes(m_outFile, buffer);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RIFFAudioFile::appendSamples(const char *buf, unsigned int frames)
|
|
{
|
|
putBytes(m_outFile, buf, frames * m_bytesPerFrame);
|
|
return true;
|
|
}
|
|
|
|
// scan on from a descriptor position
|
|
bool
|
|
RIFFAudioFile::scanForward(std::ifstream *file, const RealTime &time)
|
|
{
|
|
// sanity
|
|
if (file == 0)
|
|
return false;
|
|
|
|
unsigned int totalSamples = m_sampleRate * time.sec +
|
|
( ( m_sampleRate * time.usec() ) / 1000000 );
|
|
unsigned int totalBytes = totalSamples * m_bytesPerFrame;
|
|
|
|
m_loseBuffer = true;
|
|
|
|
// do the seek
|
|
file->seekg(totalBytes, std::ios::cur);
|
|
|
|
if (file->eof())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RIFFAudioFile::scanForward(const RealTime &time)
|
|
{
|
|
if (*m_inFile)
|
|
return scanForward(m_inFile, time);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
RIFFAudioFile::scanTo(const RealTime &time)
|
|
{
|
|
if (*m_inFile)
|
|
return scanTo(m_inFile, time);
|
|
else
|
|
return false;
|
|
|
|
}
|
|
|
|
bool
|
|
RIFFAudioFile::scanTo(std::ifstream *file, const RealTime &time)
|
|
{
|
|
// sanity
|
|
if (file == 0)
|
|
return false;
|
|
|
|
// whatever we do here we invalidate the read buffer
|
|
//
|
|
m_loseBuffer = true;
|
|
|
|
file->clear();
|
|
|
|
// seek past header - don't hardcode this - use the file format
|
|
// spec to get header length and then scoot to that.
|
|
//
|
|
file->seekg(16, std::ios::beg);
|
|
|
|
unsigned int lengthOfFormat = 0;
|
|
|
|
try {
|
|
lengthOfFormat = getIntegerFromLittleEndian(getBytes(file, 4));
|
|
file->seekg(lengthOfFormat, std::ios::cur);
|
|
|
|
// check we've got data chunk start
|
|
std::string chunkName;
|
|
int chunkLength = 0;
|
|
|
|
while ((chunkName = getBytes(file, 4)) != "data") {
|
|
if (file->eof()) {
|
|
std::cerr << "RIFFAudioFile::scanTo(): failed to find data "
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
//#ifdef DEBUG_RIFF
|
|
std::cerr << "RIFFAudioFile::scanTo(): skipping chunk: "
|
|
<< chunkName << std::endl;
|
|
//#endif
|
|
chunkLength = getIntegerFromLittleEndian(getBytes(file, 4));
|
|
if (chunkLength < 0) {
|
|
std::cerr << "RIFFAudioFile::scanTo(): negative chunk length "
|
|
<< chunkLength << " for chunk " << chunkName << std::endl;
|
|
return false;
|
|
}
|
|
file->seekg(chunkLength, std::ios::cur);
|
|
}
|
|
|
|
// get the length of the data chunk, and scan past it as a side-effect
|
|
chunkLength = getIntegerFromLittleEndian(getBytes(file, 4));
|
|
#ifdef DEBUG_RIFF
|
|
|
|
std::cout << "RIFFAudioFile::scanTo() - data chunk size = "
|
|
<< chunkLength << std::endl;
|
|
#endif
|
|
|
|
} catch (BadSoundFileException s) {
|
|
#ifdef DEBUG_RIFF
|
|
std::cerr << "RIFFAudioFile::scanTo - EXCEPTION - \""
|
|
<< s.getMessage() << "\"" << std::endl;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
// Ok, we're past all the header information in the data chunk.
|
|
// Now, how much do we scan forward?
|
|
//
|
|
size_t totalFrames = RealTime::realTime2Frame(time, m_sampleRate);
|
|
|
|
unsigned int totalBytes = totalFrames * m_bytesPerFrame;
|
|
|
|
// When using seekg we have to keep an eye on the boundaries ourselves
|
|
//
|
|
if (totalBytes > m_fileSize - (lengthOfFormat + 16 + 8)) {
|
|
#ifdef DEBUG_RIFF
|
|
std::cerr << "RIFFAudioFile::scanTo() - attempting to move past end of "
|
|
<< "data block" << std::endl;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG_RIFF
|
|
std::cout << "RIFFAudioFile::scanTo - seeking to " << time
|
|
<< " (" << totalBytes << " bytes from current " << file->tellg()
|
|
<< ")" << std::endl;
|
|
#endif
|
|
|
|
file->seekg(totalBytes, std::ios::cur);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get a certain number of sample frames - a frame is a set
|
|
// of samples (all channels) for a given sample quanta.
|
|
//
|
|
// For example, getting one frame of 16-bit stereo will return
|
|
// four bytes of data (two per channel).
|
|
//
|
|
//
|
|
std::string
|
|
RIFFAudioFile::getSampleFrames(std::ifstream *file, unsigned int frames)
|
|
{
|
|
// sanity
|
|
if (file == 0)
|
|
return std::string("");
|
|
|
|
// Bytes per sample already takes into account the number
|
|
// of channels we're using
|
|
//
|
|
long totalBytes = frames * m_bytesPerFrame;
|
|
|
|
try {
|
|
return getBytes(file, totalBytes);
|
|
} catch (BadSoundFileException s) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
unsigned int
|
|
RIFFAudioFile::getSampleFrames(std::ifstream *file, char *buf,
|
|
unsigned int frames)
|
|
{
|
|
if (file == 0)
|
|
return 0;
|
|
try {
|
|
return getBytes(file, buf, frames * m_bytesPerFrame) / m_bytesPerFrame;
|
|
} catch (BadSoundFileException s) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
std::string
|
|
RIFFAudioFile::getSampleFrames(unsigned int frames)
|
|
{
|
|
if (*m_inFile) {
|
|
return getSampleFrames(m_inFile, frames);
|
|
} else {
|
|
return std::string("");
|
|
}
|
|
}
|
|
|
|
// Return a slice of frames over a time period
|
|
//
|
|
std::string
|
|
RIFFAudioFile::getSampleFrameSlice(std::ifstream *file, const RealTime &time)
|
|
{
|
|
// sanity
|
|
if (file == 0)
|
|
return std::string("");
|
|
|
|
long totalFrames = RealTime::realTime2Frame(time, m_sampleRate);
|
|
long totalBytes = totalFrames * m_bytesPerFrame;
|
|
|
|
try {
|
|
return getBytes(file, totalBytes);
|
|
} catch (BadSoundFileException s) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
std::string
|
|
RIFFAudioFile::getSampleFrameSlice(const RealTime &time)
|
|
{
|
|
if (*m_inFile) {
|
|
return getSampleFrameSlice(m_inFile, time);
|
|
} else {
|
|
return std::string("");
|
|
}
|
|
}
|
|
|
|
RealTime
|
|
RIFFAudioFile::getLength()
|
|
{
|
|
// Fixed header size = 44 but prove by getting it from the file too
|
|
//
|
|
unsigned int headerLength = 44;
|
|
|
|
if (m_inFile) {
|
|
m_inFile->seekg(16, std::ios::beg);
|
|
headerLength = getIntegerFromLittleEndian(getBytes(m_inFile, 4));
|
|
m_inFile->seekg(headerLength, std::ios::cur);
|
|
headerLength += (16 + 8);
|
|
}
|
|
|
|
if (!m_bytesPerFrame || !m_sampleRate) return RealTime::zeroTime;
|
|
|
|
double frames = (m_fileSize - headerLength) / m_bytesPerFrame;
|
|
double seconds = frames / ((double)m_sampleRate);
|
|
|
|
int secs = int(seconds);
|
|
int nsecs = int((seconds - secs) * 1000000000.0);
|
|
|
|
return RealTime(secs, nsecs);
|
|
}
|
|
|
|
|
|
// The RIFF file format chunk defines our internal meta data.
|
|
//
|
|
// Courtesy of:
|
|
// http://www.technology.niagarac.on.ca/courses/comp630/WavFileFormat.html
|
|
//
|
|
// 'The WAV file itself consists of three "chunks" of information:
|
|
// The RIFF chunk which identifies the file as a WAV file, The FORMAT
|
|
// chunk which identifies parameters such as sample rate and the DATA
|
|
// chunk which contains the actual data (samples).'
|
|
//
|
|
//
|
|
void
|
|
RIFFAudioFile::readFormatChunk()
|
|
{
|
|
if (m_inFile == 0)
|
|
return ;
|
|
|
|
m_loseBuffer = true;
|
|
|
|
// seek to beginning
|
|
m_inFile->seekg(0, std::ios::beg);
|
|
|
|
// get the header string
|
|
//
|
|
std::string hS = getBytes(36);
|
|
|
|
// Look for the RIFF identifier and bomb out if we don't find it
|
|
//
|
|
#if (__GNUC__ < 3)
|
|
|
|
if (hS.compare(AUDIO_RIFF_ID, 0, 4) != 0)
|
|
#else
|
|
|
|
if (hS.compare(0, 4, AUDIO_RIFF_ID) != 0)
|
|
#endif
|
|
|
|
{
|
|
#ifdef DEBUG_RIFF
|
|
std::cerr << "RIFFAudioFile::readFormatChunk - "
|
|
<< "can't find RIFF identifier\n";
|
|
#endif
|
|
|
|
throw(BadSoundFileException(m_fileName, "RIFFAudioFile::readFormatChunk - can't find RIFF identifier"));
|
|
}
|
|
|
|
// Look for the WAV identifier
|
|
//
|
|
#if (__GNUC__ < 3)
|
|
if (hS.compare(AUDIO_WAVE_ID, 8, 4) != 0)
|
|
#else
|
|
|
|
if (hS.compare(8, 4, AUDIO_WAVE_ID) != 0)
|
|
#endif
|
|
|
|
{
|
|
#ifdef DEBUG_RIFF
|
|
std::cerr << "Can't find WAV identifier\n";
|
|
#endif
|
|
|
|
throw(BadSoundFileException(m_fileName, "Can't find WAV identifier"));
|
|
}
|
|
|
|
// Look for the FORMAT identifier - note that this doesn't actually
|
|
// have to be in the first chunk we come across, but for the moment
|
|
// this is the only place we check for it because I'm lazy.
|
|
//
|
|
//
|
|
#if (__GNUC__ < 3)
|
|
if (hS.compare(AUDIO_FORMAT_ID, 12, 4) != 0)
|
|
#else
|
|
|
|
if (hS.compare(12, 4, AUDIO_FORMAT_ID) != 0)
|
|
#endif
|
|
|
|
{
|
|
#ifdef DEBUG_RIFF
|
|
std::cerr << "Can't find FORMAT identifier\n";
|
|
#endif
|
|
|
|
throw(BadSoundFileException(m_fileName, "Can't find FORMAT identifier"));
|
|
}
|
|
|
|
// Little endian conversion of length bytes into file length
|
|
// (add on eight for RIFF id and length field and compare to
|
|
// real file size).
|
|
//
|
|
unsigned int length = getIntegerFromLittleEndian(hS.substr(4, 4)) + 8;
|
|
|
|
if (length != m_fileSize) {
|
|
std::cerr << "WARNING: RIFFAudioFile: incorrect length ("
|
|
<< length << ", file size is " << m_fileSize << "), ignoring"
|
|
<< std::endl;
|
|
length = m_fileSize;
|
|
}
|
|
|
|
// Check the format length
|
|
//
|
|
unsigned int lengthOfFormat = getIntegerFromLittleEndian(hS.substr(16, 4));
|
|
|
|
// Make sure we step to the end of the format chunk ignoring the
|
|
// tail if it exists
|
|
//
|
|
if (lengthOfFormat > 0x10) {
|
|
#ifdef DEBUG_RIFF
|
|
std::cerr << "RIFFAudioFile::readFormatChunk - "
|
|
<< "extended Format Chunk (" << lengthOfFormat << ")"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
// ignore any overlapping bytes
|
|
m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur);
|
|
} else if (lengthOfFormat < 0x10) {
|
|
#ifdef DEBUG_RIFF
|
|
std::cerr << "RIFFAudioFile::readFormatChunk - "
|
|
<< "truncated Format Chunk (" << lengthOfFormat << ")"
|
|
<< std::endl;
|
|
#endif
|
|
|
|
m_inFile->seekg(lengthOfFormat - 0x10, std::ios::cur);
|
|
//throw(BadSoundFileException(m_fileName, "Format chunk too short"));
|
|
}
|
|
|
|
|
|
// Check sub format - we support PCM or IEEE floating point.
|
|
//
|
|
unsigned int subFormat = getIntegerFromLittleEndian(hS.substr(20, 2));
|
|
|
|
if (subFormat == 0x01) {
|
|
m_subFormat = PCM;
|
|
} else if (subFormat == 0x03) {
|
|
m_subFormat = FLOAT;
|
|
} else {
|
|
throw(BadSoundFileException(m_fileName, "Rosegarden currently only supports PCM or IEEE floating-point RIFF files"));
|
|
}
|
|
|
|
// We seem to have a good looking .WAV file - extract the
|
|
// sample information and populate this locally
|
|
//
|
|
unsigned int channelNumbers = getIntegerFromLittleEndian(hS.substr(22, 2));
|
|
|
|
switch (channelNumbers) {
|
|
case 0x01:
|
|
case 0x02:
|
|
m_channels = channelNumbers;
|
|
break;
|
|
|
|
default: {
|
|
throw(BadSoundFileException(m_fileName, "Unsupported number of channels"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Now the rest of the information
|
|
//
|
|
m_sampleRate = getIntegerFromLittleEndian(hS.substr(24, 4));
|
|
m_bytesPerSecond = getIntegerFromLittleEndian(hS.substr(28, 4));
|
|
m_bytesPerFrame = getIntegerFromLittleEndian(hS.substr(32, 2));
|
|
m_bitsPerSample = getIntegerFromLittleEndian(hS.substr(34, 2));
|
|
|
|
if (m_subFormat == PCM) {
|
|
if (m_bitsPerSample != 8 && m_bitsPerSample != 16 && m_bitsPerSample != 24) {
|
|
throw BadSoundFileException("Rosegarden currently only supports 8-, 16- or 24-bit PCM in RIFF files");
|
|
}
|
|
} else if (m_subFormat == FLOAT) {
|
|
if (m_bitsPerSample != 32) {
|
|
throw BadSoundFileException("Rosegarden currently only supports 32-bit floating-point in RIFF files");
|
|
}
|
|
}
|
|
|
|
// printStats();
|
|
|
|
}
|
|
|
|
// Write out the format chunk from our internal data
|
|
//
|
|
void
|
|
RIFFAudioFile::writeFormatChunk()
|
|
{
|
|
if (m_outFile == 0 || m_type != WAV)
|
|
return ;
|
|
|
|
std::string outString;
|
|
|
|
// RIFF type is all we support for the moment
|
|
outString += AUDIO_RIFF_ID;
|
|
|
|
// Now write the total length of the file minus these first 8 bytes.
|
|
// We won't know this until we've finished recording the file.
|
|
//
|
|
outString += "0000";
|
|
|
|
// WAV file is all we support
|
|
//
|
|
outString += AUDIO_WAVE_ID;
|
|
|
|
// Begin the format chunk
|
|
outString += AUDIO_FORMAT_ID;
|
|
|
|
// length
|
|
//cout << "LENGTH = " << getLittleEndianFromInteger(0x10, 4) << endl;
|
|
outString += getLittleEndianFromInteger(0x10, 4);
|
|
|
|
// 1 for PCM, 3 for float
|
|
if (m_subFormat == PCM) {
|
|
outString += getLittleEndianFromInteger(0x01, 2);
|
|
} else {
|
|
outString += getLittleEndianFromInteger(0x03, 2);
|
|
}
|
|
|
|
// channel
|
|
outString += getLittleEndianFromInteger(m_channels, 2);
|
|
|
|
// sample rate
|
|
outString += getLittleEndianFromInteger(m_sampleRate, 4);
|
|
|
|
// bytes per second
|
|
outString += getLittleEndianFromInteger(m_bytesPerSecond, 4);
|
|
|
|
// bytes per sample
|
|
outString += getLittleEndianFromInteger(m_bytesPerFrame, 2);
|
|
|
|
// bits per sample
|
|
outString += getLittleEndianFromInteger(m_bitsPerSample, 2);
|
|
|
|
// Now mark the beginning of the "data" chunk and leave the file
|
|
// open for writing.
|
|
outString += "data";
|
|
|
|
// length of data to follow - again needs to be written after
|
|
// we've completed the file.
|
|
//
|
|
outString += "0000";
|
|
|
|
// write out
|
|
//
|
|
putBytes(m_outFile, outString);
|
|
}
|
|
|
|
|
|
AudioFileType
|
|
RIFFAudioFile::identifySubType(const std::string &filename)
|
|
{
|
|
std::ifstream *testFile =
|
|
new std::ifstream(filename.c_str(), std::ios::in | std::ios::binary);
|
|
|
|
if (!(*testFile))
|
|
return UNKNOWN;
|
|
|
|
std::string hS;
|
|
unsigned int numberOfBytes = 36;
|
|
char *bytes = new char[numberOfBytes];
|
|
|
|
testFile->read(bytes, numberOfBytes);
|
|
for (unsigned int i = 0; i < numberOfBytes; i++)
|
|
hS += (unsigned char)bytes[i];
|
|
|
|
AudioFileType type = UNKNOWN;
|
|
|
|
// Test for BWF first because it's an extension of a plain WAV
|
|
//
|
|
#if (__GNUC__ < 3)
|
|
|
|
if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 &&
|
|
hS.compare(AUDIO_WAVE_ID, 8, 4) == 0 &&
|
|
hS.compare(AUDIO_BWF_ID, 12, 4) == 0)
|
|
#else
|
|
|
|
if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 &&
|
|
hS.compare(8, 4, AUDIO_WAVE_ID) == 0 &&
|
|
hS.compare(12, 4, AUDIO_BWF_ID) == 0)
|
|
#endif
|
|
|
|
{
|
|
type = BWF;
|
|
}
|
|
// Now for a WAV
|
|
#if (__GNUC__ < 3)
|
|
else if (hS.compare(AUDIO_RIFF_ID, 0, 4) == 0 &&
|
|
hS.compare(AUDIO_WAVE_ID, 8, 4) == 0)
|
|
#else
|
|
|
|
else if (hS.compare(0, 4, AUDIO_RIFF_ID) == 0 &&
|
|
hS.compare(8, 4, AUDIO_WAVE_ID) == 0)
|
|
#endif
|
|
|
|
{
|
|
type = WAV;
|
|
} else
|
|
type = UNKNOWN;
|
|
|
|
testFile->close();
|
|
delete [] bytes;
|
|
|
|
return type;
|
|
}
|
|
|
|
float
|
|
RIFFAudioFile::convertBytesToSample(const unsigned char *ubuf)
|
|
{
|
|
switch (getBitsPerSample()) {
|
|
|
|
case 8: {
|
|
// WAV stores 8-bit samples unsigned, other sizes signed.
|
|
return (float)(ubuf[0] - 128.0) / 128.0;
|
|
}
|
|
|
|
case 16: {
|
|
// Two's complement little-endian 16-bit integer.
|
|
// We convert endianness (if necessary) but assume 16-bit short.
|
|
unsigned char b2 = ubuf[0];
|
|
unsigned char b1 = ubuf[1];
|
|
unsigned int bits = (b1 << 8) + b2;
|
|
return (float)(short(bits)) / 32767.0;
|
|
}
|
|
|
|
case 24: {
|
|
// Two's complement little-endian 24-bit integer.
|
|
// Again, convert endianness but assume 32-bit int.
|
|
unsigned char b3 = ubuf[0];
|
|
unsigned char b2 = ubuf[1];
|
|
unsigned char b1 = ubuf[2];
|
|
// Rotate 8 bits too far in order to get the sign bit
|
|
// in the right place; this gives us a 32-bit value,
|
|
// hence the larger float divisor
|
|
unsigned int bits = (b1 << 24) + (b2 << 16) + (b3 << 8);
|
|
return (float)(int(bits)) / 2147483647.0;
|
|
}
|
|
|
|
case 32: {
|
|
// IEEE floating point
|
|
return *(float *)ubuf;
|
|
}
|
|
|
|
default:
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
}
|
|
|