From 8adfb3bd99b4dcff2459756af090a640fd7a4b4a Mon Sep 17 00:00:00 2001 From: yo mama Date: Fri, 19 Jun 2015 16:24:27 -0400 Subject: clone --- pysoundtouch/src/WavFile.cpp | 745 ++++++++++++++++++++++++++++++++++++ pysoundtouch/src/WavFile.h | 250 ++++++++++++ pysoundtouch/src/pybpmdetect.cpp | 122 ++++++ pysoundtouch/src/pybpmdetect.h | 41 ++ pysoundtouch/src/pysoundtouch.cpp | 243 ++++++++++++ pysoundtouch/src/pysoundtouch.h | 61 +++ pysoundtouch/src/soundtouchmodule.c | 27 ++ pysoundtouch/src/soundtouchmodule.h | 17 + 8 files changed, 1506 insertions(+) create mode 100644 pysoundtouch/src/WavFile.cpp create mode 100644 pysoundtouch/src/WavFile.h create mode 100644 pysoundtouch/src/pybpmdetect.cpp create mode 100644 pysoundtouch/src/pybpmdetect.h create mode 100644 pysoundtouch/src/pysoundtouch.cpp create mode 100644 pysoundtouch/src/pysoundtouch.h create mode 100644 pysoundtouch/src/soundtouchmodule.c create mode 100644 pysoundtouch/src/soundtouchmodule.h (limited to 'pysoundtouch/src') diff --git a/pysoundtouch/src/WavFile.cpp b/pysoundtouch/src/WavFile.cpp new file mode 100644 index 0000000..47af5a2 --- /dev/null +++ b/pysoundtouch/src/WavFile.cpp @@ -0,0 +1,745 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Classes for easy reading & writing of WAV sound files. +/// +/// For big-endian CPU, define _BIG_ENDIAN_ during compile-time to correctly +/// parse the WAV files with such processors. +/// +/// Admittingly, more complete WAV reader routines may exist in public domain, +/// but the reason for 'yet another' one is that those generic WAV reader +/// libraries are exhaustingly large and cumbersome! Wanted to have something +/// simpler here, i.e. something that's not already larger than rest of the +/// SoundTouch/SoundStretch program... +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date$ +// File revision : $Revision: 4 $ +// +// $Id$ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include "WavFile.h" + +using namespace std; + +static const char riffStr[] = "RIFF"; +static const char waveStr[] = "WAVE"; +static const char fmtStr[] = "fmt "; +static const char dataStr[] = "data"; + + +////////////////////////////////////////////////////////////////////////////// +// +// Helper functions for swapping byte order to correctly read/write WAV files +// with big-endian CPU's: Define compile-time definition _BIG_ENDIAN_ to +// turn-on the conversion if it appears necessary. +// +// For example, Intel x86 is little-endian and doesn't require conversion, +// while PowerPC of Mac's and many other RISC cpu's are big-endian. + +#ifdef BYTE_ORDER + // In gcc compiler detect the byte order automatically + #if BYTE_ORDER == BIG_ENDIAN + // big-endian platform. + #define _BIG_ENDIAN_ + #endif +#endif + +#ifdef _BIG_ENDIAN_ + // big-endian CPU, swap bytes in 16 & 32 bit words + + // helper-function to swap byte-order of 32bit integer + static inline void _swap32(unsigned int &dwData) + { + dwData = ((dwData >> 24) & 0x000000FF) | + ((dwData >> 8) & 0x0000FF00) | + ((dwData << 8) & 0x00FF0000) | + ((dwData << 24) & 0xFF000000); + } + + // helper-function to swap byte-order of 16bit integer + static inline void _swap16(unsigned short &wData) + { + wData = ((wData >> 8) & 0x00FF) | + ((wData << 8) & 0xFF00); + } + + // helper-function to swap byte-order of buffer of 16bit integers + static inline void _swap16Buffer(unsigned short *pData, unsigned int dwNumWords) + { + unsigned long i; + + for (i = 0; i < dwNumWords; i ++) + { + _swap16(pData[i]); + } + } + +#else // BIG_ENDIAN + // little-endian CPU, WAV file is ok as such + + // dummy helper-function + static inline void _swap32(unsigned int &dwData) + { + // do nothing + } + + // dummy helper-function + static inline void _swap16(unsigned short &wData) + { + // do nothing + } + + // dummy helper-function + static inline void _swap16Buffer(unsigned short *pData, unsigned int dwNumBytes) + { + // do nothing + } + +#endif // BIG_ENDIAN + + +////////////////////////////////////////////////////////////////////////////// +// +// Class WavInFile +// + +WavInFile::WavInFile(const char *fileName) +{ + // Try to open the file for reading + fptr = fopen(fileName, "rb"); + if (fptr == NULL) + { + // didn't succeed + string msg = "Error : Unable to open file \""; + msg += fileName; + msg += "\" for reading."; + throw runtime_error(msg); + } + + init(); +} + + +WavInFile::WavInFile(FILE *file) +{ + // Try to open the file for reading + fptr = file; + if (!file) + { + // didn't succeed + string msg = "Error : Unable to access input stream for reading"; + throw runtime_error(msg); + } + + init(); +} + + +/// Init the WAV file stream +void WavInFile::init() +{ + int hdrsOk; + + // assume file stream is already open + assert(fptr); + + // Read the file headers + hdrsOk = readWavHeaders(); + if (hdrsOk != 0) + { + // Something didn't match in the wav file headers + string msg = "Input file is corrupt or not a WAV file"; + throw runtime_error(msg); + } + + if (header.format.fixed != 1) + { + string msg = "Input file uses unsupported encoding."; + throw runtime_error(msg); + } + + dataRead = 0; +} + + + +WavInFile::~WavInFile() +{ + if (fptr) fclose(fptr); + fptr = NULL; +} + + + +void WavInFile::rewind() +{ + int hdrsOk; + + fseek(fptr, 0, SEEK_SET); + hdrsOk = readWavHeaders(); + assert(hdrsOk == 0); + dataRead = 0; +} + + +int WavInFile::checkCharTags() const +{ + // header.format.fmt should equal to 'fmt ' + if (memcmp(fmtStr, header.format.fmt, 4) != 0) return -1; + // header.data.data_field should equal to 'data' + if (memcmp(dataStr, header.data.data_field, 4) != 0) return -1; + + return 0; +} + + +int WavInFile::read(char *buffer, int maxElems) +{ + int numBytes; + uint afterDataRead; + + // ensure it's 8 bit format + if (header.format.bits_per_sample != 8) + { + throw runtime_error("Error: WavInFile::read(char*, int) works only with 8bit samples."); + } + assert(sizeof(char) == 1); + + numBytes = maxElems; + afterDataRead = dataRead + numBytes; + if (afterDataRead > header.data.data_len) + { + // Don't read more samples than are marked available in header + numBytes = (int)header.data.data_len - (int)dataRead; + assert(numBytes >= 0); + } + + assert(buffer); + numBytes = fread(buffer, 1, numBytes, fptr); + dataRead += numBytes; + + return numBytes; +} + + +int WavInFile::read(short *buffer, int maxElems) +{ + unsigned int afterDataRead; + int numBytes; + int numElems; + + assert(buffer); + if (header.format.bits_per_sample == 8) + { + // 8 bit format + char *temp = new char[maxElems]; + int i; + + numElems = read(temp, maxElems); + // convert from 8 to 16 bit + for (i = 0; i < numElems; i ++) + { + buffer[i] = temp[i] << 8; + } + delete[] temp; + } + else + { + // 16 bit format + assert(header.format.bits_per_sample == 16); + assert(sizeof(short) == 2); + + numBytes = maxElems * 2; + afterDataRead = dataRead + numBytes; + if (afterDataRead > header.data.data_len) + { + // Don't read more samples than are marked available in header + numBytes = (int)header.data.data_len - (int)dataRead; + assert(numBytes >= 0); + } + + numBytes = fread(buffer, 1, numBytes, fptr); + dataRead += numBytes; + numElems = numBytes / 2; + + // 16bit samples, swap byte order if necessary + _swap16Buffer((unsigned short *)buffer, numElems); + } + + return numElems; +} + + + +int WavInFile::read(float *buffer, int maxElems) +{ + short *temp = new short[maxElems]; + int num; + int i; + double fscale; + + num = read(temp, maxElems); + + fscale = 1.0 / 32768.0; + // convert to floats, scale to range [-1..+1[ + for (i = 0; i < num; i ++) + { + buffer[i] = (float)(fscale * (double)temp[i]); + } + + delete[] temp; + + return num; +} + + +int WavInFile::eof() const +{ + // return true if all data has been read or file eof has reached + return (dataRead == header.data.data_len || feof(fptr)); +} + + + +// test if character code is between a white space ' ' and little 'z' +static int isAlpha(char c) +{ + return (c >= ' ' && c <= 'z') ? 1 : 0; +} + + +// test if all characters are between a white space ' ' and little 'z' +static int isAlphaStr(const char *str) +{ + char c; + + c = str[0]; + while (c) + { + if (isAlpha(c) == 0) return 0; + str ++; + c = str[0]; + } + + return 1; +} + + +int WavInFile::readRIFFBlock() +{ + if (fread(&(header.riff), sizeof(WavRiff), 1, fptr) != 1) return -1; + + // swap 32bit data byte order if necessary + _swap32((unsigned int &)header.riff.package_len); + + // header.riff.riff_char should equal to 'RIFF'); + if (memcmp(riffStr, header.riff.riff_char, 4) != 0) return -1; + // header.riff.wave should equal to 'WAVE' + if (memcmp(waveStr, header.riff.wave, 4) != 0) return -1; + + return 0; +} + + + + +int WavInFile::readHeaderBlock() +{ + char label[5]; + string sLabel; + + // lead label string + if (fread(label, 1, 4, fptr) !=4) return -1; + label[4] = 0; + + if (isAlphaStr(label) == 0) return -1; // not a valid label + + // Decode blocks according to their label + if (strcmp(label, fmtStr) == 0) + { + int nLen, nDump; + + // 'fmt ' block + memcpy(header.format.fmt, fmtStr, 4); + + // read length of the format field + if (fread(&nLen, sizeof(int), 1, fptr) != 1) return -1; + // swap byte order if necessary + _swap32((unsigned int &)nLen); // int format_len; + header.format.format_len = nLen; + + // calculate how much length differs from expected + nDump = nLen - ((int)sizeof(header.format) - 8); + + // if format_len is larger than expected, read only as much data as we've space for + if (nDump > 0) + { + nLen = sizeof(header.format) - 8; + } + + // read data + if (fread(&(header.format.fixed), nLen, 1, fptr) != 1) return -1; + + // swap byte order if necessary + _swap16((unsigned short &)header.format.fixed); // short int fixed; + _swap16((unsigned short &)header.format.channel_number); // short int channel_number; + _swap32((unsigned int &)header.format.sample_rate); // int sample_rate; + _swap32((unsigned int &)header.format.byte_rate); // int byte_rate; + _swap16((unsigned short &)header.format.byte_per_sample); // short int byte_per_sample; + _swap16((unsigned short &)header.format.bits_per_sample); // short int bits_per_sample; + + // if format_len is larger than expected, skip the extra data + if (nDump > 0) + { + fseek(fptr, nDump, SEEK_CUR); + } + + return 0; + } + else if (strcmp(label, dataStr) == 0) + { + // 'data' block + memcpy(header.data.data_field, dataStr, 4); + if (fread(&(header.data.data_len), sizeof(uint), 1, fptr) != 1) return -1; + + // swap byte order if necessary + _swap32((unsigned int &)header.data.data_len); + + return 1; + } + else + { + uint len, i; + uint temp; + // unknown block + + // read length + if (fread(&len, sizeof(len), 1, fptr) != 1) return -1; + // scan through the block + for (i = 0; i < len; i ++) + { + if (fread(&temp, 1, 1, fptr) != 1) return -1; + if (feof(fptr)) return -1; // unexpected eof + } + } + return 0; +} + + +int WavInFile::readWavHeaders() +{ + int res; + + memset(&header, 0, sizeof(header)); + + res = readRIFFBlock(); + if (res) return 1; + // read header blocks until data block is found + do + { + // read header blocks + res = readHeaderBlock(); + if (res < 0) return 1; // error in file structure + } while (res == 0); + // check that all required tags are legal + return checkCharTags(); +} + + +uint WavInFile::getNumChannels() const +{ + return header.format.channel_number; +} + + +uint WavInFile::getNumBits() const +{ + return header.format.bits_per_sample; +} + + +uint WavInFile::getBytesPerSample() const +{ + return getNumChannels() * getNumBits() / 8; +} + + +uint WavInFile::getSampleRate() const +{ + return header.format.sample_rate; +} + + + +uint WavInFile::getDataSizeInBytes() const +{ + return header.data.data_len; +} + + +uint WavInFile::getNumSamples() const +{ + if (header.format.byte_per_sample == 0) return 0; + return header.data.data_len / (unsigned short)header.format.byte_per_sample; +} + + +uint WavInFile::getLengthMS() const +{ + uint numSamples; + uint sampleRate; + + numSamples = getNumSamples(); + sampleRate = getSampleRate(); + + assert(numSamples < UINT_MAX / 1000); + return (1000 * numSamples / sampleRate); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Class WavOutFile +// + +WavOutFile::WavOutFile(const char *fileName, int sampleRate, int bits, int channels) +{ + bytesWritten = 0; + fptr = fopen(fileName, "wb"); + if (fptr == NULL) + { + string msg = "Error : Unable to open file \""; + msg += fileName; + msg += "\" for writing."; + //pmsg = msg.c_str; + throw runtime_error(msg); + } + + fillInHeader(sampleRate, bits, channels); + writeHeader(); +} + + +WavOutFile::WavOutFile(FILE *file, int sampleRate, int bits, int channels) +{ + bytesWritten = 0; + fptr = file; + if (fptr == NULL) + { + string msg = "Error : Unable to access output file stream."; + throw runtime_error(msg); + } + + fillInHeader(sampleRate, bits, channels); + writeHeader(); +} + + + +WavOutFile::~WavOutFile() +{ + finishHeader(); + if (fptr) fclose(fptr); + fptr = NULL; +} + + + +void WavOutFile::fillInHeader(uint sampleRate, uint bits, uint channels) +{ + // fill in the 'riff' part.. + + // copy string 'RIFF' to riff_char + memcpy(&(header.riff.riff_char), riffStr, 4); + // package_len unknown so far + header.riff.package_len = 0; + // copy string 'WAVE' to wave + memcpy(&(header.riff.wave), waveStr, 4); + + + // fill in the 'format' part.. + + // copy string 'fmt ' to fmt + memcpy(&(header.format.fmt), fmtStr, 4); + + header.format.format_len = 0x10; + header.format.fixed = 1; + header.format.channel_number = (short)channels; + header.format.sample_rate = (int)sampleRate; + header.format.bits_per_sample = (short)bits; + header.format.byte_per_sample = (short)(bits * channels / 8); + header.format.byte_rate = header.format.byte_per_sample * (int)sampleRate; + header.format.sample_rate = (int)sampleRate; + + // fill in the 'data' part.. + + // copy string 'data' to data_field + memcpy(&(header.data.data_field), dataStr, 4); + // data_len unknown so far + header.data.data_len = 0; +} + + +void WavOutFile::finishHeader() +{ + // supplement the file length into the header structure + header.riff.package_len = bytesWritten + 36; + header.data.data_len = bytesWritten; + + writeHeader(); +} + + + +void WavOutFile::writeHeader() +{ + WavHeader hdrTemp; + int res; + + // swap byte order if necessary + hdrTemp = header; + _swap32((unsigned int &)hdrTemp.riff.package_len); + _swap32((unsigned int &)hdrTemp.format.format_len); + _swap16((unsigned short &)hdrTemp.format.fixed); + _swap16((unsigned short &)hdrTemp.format.channel_number); + _swap32((unsigned int &)hdrTemp.format.sample_rate); + _swap32((unsigned int &)hdrTemp.format.byte_rate); + _swap16((unsigned short &)hdrTemp.format.byte_per_sample); + _swap16((unsigned short &)hdrTemp.format.bits_per_sample); + _swap32((unsigned int &)hdrTemp.data.data_len); + + // write the supplemented header in the beginning of the file + fseek(fptr, 0, SEEK_SET); + res = fwrite(&hdrTemp, sizeof(hdrTemp), 1, fptr); + if (res != 1) + { + throw runtime_error("Error while writing to a wav file."); + } + + // jump back to the end of the file + fseek(fptr, 0, SEEK_END); +} + + + +void WavOutFile::write(const char *buffer, int numElems) +{ + int res; + + if (header.format.bits_per_sample != 8) + { + throw runtime_error("Error: WavOutFile::write(const char*, int) accepts only 8bit samples."); + } + assert(sizeof(char) == 1); + + res = fwrite(buffer, 1, numElems, fptr); + if (res != numElems) + { + throw runtime_error("Error while writing to a wav file."); + } + + bytesWritten += numElems; +} + + +void WavOutFile::write(const short *buffer, int numElems) +{ + int res; + + // 16 bit samples + if (numElems < 1) return; // nothing to do + + if (header.format.bits_per_sample == 8) + { + int i; + char *temp = new char[numElems]; + // convert from 16bit format to 8bit format + for (i = 0; i < numElems; i ++) + { + temp[i] = buffer[i] >> 8; + } + // write in 8bit format + write(temp, numElems); + delete[] temp; + } + else + { + // 16bit format + unsigned short *pTemp = new unsigned short[numElems]; + + assert(header.format.bits_per_sample == 16); + + // allocate temp buffer to swap byte order if necessary + memcpy(pTemp, buffer, numElems * 2); + _swap16Buffer(pTemp, numElems); + + res = fwrite(pTemp, 2, numElems, fptr); + + delete[] pTemp; + + if (res != numElems) + { + throw runtime_error("Error while writing to a wav file."); + } + bytesWritten += 2 * numElems; + } +} + + +void WavOutFile::write(const float *buffer, int numElems) +{ + int i; + short *temp = new short[numElems]; + int iTemp; + + // convert to 16 bit integer + for (i = 0; i < numElems; i ++) + { + // convert to integer + iTemp = (int)(32768.0f * buffer[i]); + + // saturate + if (iTemp < -32768) iTemp = -32768; + if (iTemp > 32767) iTemp = 32767; + temp[i] = (short)iTemp; + } + + write(temp, numElems); + + delete[] temp; +} diff --git a/pysoundtouch/src/WavFile.h b/pysoundtouch/src/WavFile.h new file mode 100644 index 0000000..bf9b3bd --- /dev/null +++ b/pysoundtouch/src/WavFile.h @@ -0,0 +1,250 @@ +//////////////////////////////////////////////////////////////////////////////// +/// +/// Classes for easy reading & writing of WAV sound files. +/// +/// For big-endian CPU, define BIG_ENDIAN during compile-time to correctly +/// parse the WAV files with such processors. +/// +/// Admittingly, more complete WAV reader routines may exist in public domain, but +/// the reason for 'yet another' one is that those generic WAV reader libraries are +/// exhaustingly large and cumbersome! Wanted to have something simpler here, i.e. +/// something that's not already larger than rest of the SoundTouch/SoundStretch program... +/// +/// Author : Copyright (c) Olli Parviainen +/// Author e-mail : oparviai 'at' iki.fi +/// SoundTouch WWW: http://www.surina.net/soundtouch +/// +//////////////////////////////////////////////////////////////////////////////// +// +// Last changed : $Date$ +// File revision : $Revision: 4 $ +// +// $Id$ +// +//////////////////////////////////////////////////////////////////////////////// +// +// License : +// +// SoundTouch audio processing library +// Copyright (c) Olli Parviainen +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef WAVFILE_H +#define WAVFILE_H + +#include + +#ifndef uint +typedef unsigned int uint; +#endif + + +/// WAV audio file 'riff' section header +typedef struct +{ + char riff_char[4]; + int package_len; + char wave[4]; +} WavRiff; + +/// WAV audio file 'format' section header +typedef struct +{ + char fmt[4]; + int format_len; + short fixed; + short channel_number; + int sample_rate; + int byte_rate; + short byte_per_sample; + short bits_per_sample; +} WavFormat; + +/// WAV audio file 'data' section header +typedef struct +{ + char data_field[4]; + uint data_len; +} WavData; + + +/// WAV audio file header +typedef struct +{ + WavRiff riff; + WavFormat format; + WavData data; +} WavHeader; + + +/// Class for reading WAV audio files. +class WavInFile +{ +private: + /// File pointer. + FILE *fptr; + + /// Counter of how many bytes of sample data have been read from the file. + uint dataRead; + + /// WAV header information + WavHeader header; + + /// Init the WAV file stream + void init(); + + /// Read WAV file headers. + /// \return zero if all ok, nonzero if file format is invalid. + int readWavHeaders(); + + /// Checks WAV file header tags. + /// \return zero if all ok, nonzero if file format is invalid. + int checkCharTags() const; + + /// Reads a single WAV file header block. + /// \return zero if all ok, nonzero if file format is invalid. + int readHeaderBlock(); + + /// Reads WAV file 'riff' block + int readRIFFBlock(); + +public: + /// Constructor: Opens the given WAV file. If the file can't be opened, + /// throws 'runtime_error' exception. + WavInFile(const char *filename); + + WavInFile(FILE *file); + + /// Destructor: Closes the file. + ~WavInFile(); + + /// Rewind to beginning of the file + void rewind(); + + /// Get sample rate. + uint getSampleRate() const; + + /// Get number of bits per sample, i.e. 8 or 16. + uint getNumBits() const; + + /// Get sample data size in bytes. Ahem, this should return same information as + /// 'getBytesPerSample'... + uint getDataSizeInBytes() const; + + /// Get total number of samples in file. + uint getNumSamples() const; + + /// Get number of bytes per audio sample (e.g. 16bit stereo = 4 bytes/sample) + uint getBytesPerSample() const; + + /// Get number of audio channels in the file (1=mono, 2=stereo) + uint getNumChannels() const; + + /// Get the audio file length in milliseconds + uint getLengthMS() const; + + /// Reads audio samples from the WAV file. This routine works only for 8 bit samples. + /// Reads given number of elements from the file or if end-of-file reached, as many + /// elements as are left in the file. + /// + /// \return Number of 8-bit integers read from the file. + int read(char *buffer, int maxElems); + + /// Reads audio samples from the WAV file to 16 bit integer format. Reads given number + /// of elements from the file or if end-of-file reached, as many elements as are + /// left in the file. + /// + /// \return Number of 16-bit integers read from the file. + int read(short *buffer, ///< Pointer to buffer where to read data. + int maxElems ///< Size of 'buffer' array (number of array elements). + ); + + /// Reads audio samples from the WAV file to floating point format, converting + /// sample values to range [-1,1[. Reads given number of elements from the file + /// or if end-of-file reached, as many elements as are left in the file. + /// + /// \return Number of elements read from the file. + int read(float *buffer, ///< Pointer to buffer where to read data. + int maxElems ///< Size of 'buffer' array (number of array elements). + ); + + /// Check end-of-file. + /// + /// \return Nonzero if end-of-file reached. + int eof() const; +}; + + + +/// Class for writing WAV audio files. +class WavOutFile +{ +private: + /// Pointer to the WAV file + FILE *fptr; + + /// WAV file header data. + WavHeader header; + + /// Counter of how many bytes have been written to the file so far. + int bytesWritten; + + /// Fills in WAV file header information. + void fillInHeader(const uint sampleRate, const uint bits, const uint channels); + + /// Finishes the WAV file header by supplementing information of amount of + /// data written to file etc + void finishHeader(); + + /// Writes the WAV file header. + void writeHeader(); + +public: + /// Constructor: Creates a new WAV file. Throws a 'runtime_error' exception + /// if file creation fails. + WavOutFile(const char *fileName, ///< Filename + int sampleRate, ///< Sample rate (e.g. 44100 etc) + int bits, ///< Bits per sample (8 or 16 bits) + int channels ///< Number of channels (1=mono, 2=stereo) + ); + + WavOutFile(FILE *file, int sampleRate, int bits, int channels); + + /// Destructor: Finalizes & closes the WAV file. + ~WavOutFile(); + + /// Write data to WAV file. This function works only with 8bit samples. + /// Throws a 'runtime_error' exception if writing to file fails. + void write(const char *buffer, ///< Pointer to sample data buffer. + int numElems ///< How many array items are to be written to file. + ); + + /// Write data to WAV file. Throws a 'runtime_error' exception if writing to + /// file fails. + void write(const short *buffer, ///< Pointer to sample data buffer. + int numElems ///< How many array items are to be written to file. + ); + + /// Write data to WAV file in floating point format, saturating sample values to range + /// [-1..+1[. Throws a 'runtime_error' exception if writing to file fails. + void write(const float *buffer, ///< Pointer to sample data buffer. + int numElems ///< How many array items are to be written to file. + ); +}; + +#endif diff --git a/pysoundtouch/src/pybpmdetect.cpp b/pysoundtouch/src/pybpmdetect.cpp new file mode 100644 index 0000000..03a673f --- /dev/null +++ b/pysoundtouch/src/pybpmdetect.cpp @@ -0,0 +1,122 @@ +/* + * python interface to soundtouch (the open-source audio processing library) + * + * The structure of this code was based on pymad-0.5.4 + * This is a C++ file. + */ + +#include + +extern "C" { + #include "soundtouchmodule.h" +} +#include "pybpmdetect.h" + +#if PY_VERSION_HEX < 0x01060000 +#define PyObject_DEL(op) PyMem_DEL((op)) +#endif + +PyTypeObject py_bpmdetect_t = { + PyObject_HEAD_INIT(&PyType_Type) + 0, + "BPMDetect", + sizeof(py_bpmdetect), + 0, + /* standard methods */ + (destructor) py_bpmdetect_dealloc, + (printfunc) 0, + (getattrfunc) py_bpmdetect_getattr, + (setattrfunc) 0, + (cmpfunc) 0, + (reprfunc) 0, + /* type categories */ + 0, /* as number */ + 0, /* as sequence */ + 0, /* as mapping */ + 0, /* hash */ + 0, /* binary */ + 0, /* repr */ + 0, /* getattro */ + 0, /* setattro */ + 0, /* as buffer */ + 0, /* tp_flags */ + NULL +}; + +// Constructor +PyObject* py_bpmdetect_new(PyObject* self, PyObject* args) { + py_bpmdetect* ps = NULL; + uint sampleRate, channels; + + // Needs to be constructed with sampling rate and number of channels + if (!PyArg_ParseTuple(args, "II:Soundtouch", &sampleRate, &channels)) { + PyErr_SetString(PyExc_RuntimeError, "Requires sampling rate and number of channels (sample size must be 2)"); + return NULL; + } + + // Create the object + ps = PyObject_NEW(py_bpmdetect, &py_bpmdetect_t); + ps->bpmdetect = new soundtouch::BPMDetect((int) channels, (int) sampleRate); + ps->channels = (int) channels; + + return (PyObject*) ps; +} + +// Deallocate the BPMDetect object +static void py_bpmdetect_dealloc(PyObject* self, PyObject* args) { + py_bpmdetect* ps = PY_BPMDETECT(self); + + if (ps->bpmdetect) { + delete ps->bpmdetect; + ps->bpmdetect = NULL; + } + + PyObject_DEL(self); +} + +// Read in a number of samples for beat detection +static PyObject* py_bpmdetect_put_samples(PyObject* self, PyObject* args) { + py_bpmdetect* ps = PY_BPMDETECT(self); + int buflen; + char* transfer; + + // Needs to be called with a string of samples + if (!PyArg_ParseTuple(args, "s#", &transfer, &buflen)) { + PyErr_SetString(PyExc_TypeError, "invalid argument"); + return NULL; + } + + // Move all into our char-short union + for (int ii = 0; ii < buflen; ii++) + ps->buffer.chars[ii] = transfer[ii]; + + // Input them into the BMP detector + ps->bpmdetect->inputSamples(ps->buffer.shorts, (uint) buflen / (2 * ps->channels)); + + Py_INCREF(Py_None); + return Py_None; +} + +// Perform the beat detection algorithm +// return the beats per minute +static PyObject* py_bpmdetect_get_bpm(PyObject* self, PyObject* args) { + py_bpmdetect* ps = PY_BPMDETECT(self); + + // Return the BPM of the input samples + float bpm = ps->bpmdetect->getBpm(); + + return PyFloat_FromDouble(bpm); +} + +/* housekeeping */ + +static PyMethodDef bpmdetect_methods[] = { + { "put_samples", py_bpmdetect_put_samples, METH_VARARGS, "" }, + { "get_bpm", py_bpmdetect_get_bpm, METH_VARARGS, "" }, + { NULL, 0, 0, NULL } +}; + +// Extract information from the bpmdetect object +static PyObject* py_bpmdetect_getattr(PyObject* self, char* name) { + return Py_FindMethod(bpmdetect_methods, self, name); +} diff --git a/pysoundtouch/src/pybpmdetect.h b/pysoundtouch/src/pybpmdetect.h new file mode 100644 index 0000000..54438d0 --- /dev/null +++ b/pysoundtouch/src/pybpmdetect.h @@ -0,0 +1,41 @@ +/* + * python interface to soundtouch (the open-source audio processing library) + * BPM Detection + */ + +#ifndef __PY_BMPDETECT_H__ +#define __PY_BMPDETECT_H__ + +#include +#include + +#define BUFFER_SIZE 44100 + +// Definition of the bpmdetect object +typedef struct { + PyObject_HEAD + soundtouch::BPMDetect* bpmdetect; + int channels; // 1 or 2 + union { + char chars[BUFFER_SIZE]; + short shorts[BUFFER_SIZE/2]; + } buffer; +} py_bpmdetect; /* Soundtouch */ + +#define PY_BPMDETECT(x) ((py_bpmdetect *) x) + +extern PyTypeObject py_bpmdetect_t; + +// Deallocate the BPMDetect object +static void py_bpmdetect_dealloc(PyObject* self, PyObject* args); + +// Read in a number of samples for beat detection +static PyObject* py_bpmdetect_put_samples(PyObject* self, PyObject* args); + +// Perform the beat detection algorithm +static PyObject* py_bpmdetect_get_bpm(PyObject* self, PyObject* args); + +// Extract information from the bpmdetect object +static PyObject* py_bpmdetect_getattr(PyObject* self, char* name); + +#endif diff --git a/pysoundtouch/src/pysoundtouch.cpp b/pysoundtouch/src/pysoundtouch.cpp new file mode 100644 index 0000000..ddc832b --- /dev/null +++ b/pysoundtouch/src/pysoundtouch.cpp @@ -0,0 +1,243 @@ +/* + * python interface to soundtouch (the open-source audio processing library) + * + * The structure of this code was based on pymad-0.5.4 + * This is a C++ file. + */ + +#include + +extern "C" { + #include "soundtouchmodule.h" +} +#include "pysoundtouch.h" + +#if PY_VERSION_HEX < 0x01060000 +#define PyObject_DEL(op) PyMem_DEL((op)) +#endif + +/* For debugging: +#include "WavFile.h" +WavOutFile* inputs = new WavOutFile("/Users/jrising/inputs.wav", 44100, 16, 1); +WavOutFile* outputs = new WavOutFile("/Users/jrising/outputs.wav", 44100, 16, 1);*/ + +PyTypeObject py_soundtouch_t = { + PyObject_HEAD_INIT(&PyType_Type) + 0, + "Soundtouch", + sizeof(py_soundtouch), + 0, + /* standard methods */ + (destructor) py_soundtouch_dealloc, + (printfunc) 0, + (getattrfunc) py_soundtouch_getattr, + (setattrfunc) 0, + (cmpfunc) 0, + (reprfunc) 0, + /* type categories */ + 0, /* as number */ + 0, /* as sequence */ + 0, /* as mapping */ + 0, /* hash */ + 0, /* binary */ + 0, /* repr */ + 0, /* getattro */ + 0, /* setattro */ + 0, /* as buffer */ + 0, /* tp_flags */ + NULL +}; + +// Constructor +PyObject* py_soundtouch_new(PyObject* self, PyObject* args) { + py_soundtouch* ps = NULL; + uint sampleRate, channels; + + // Needs to be given the sampling rate and number of channels + if (!PyArg_ParseTuple(args, "II:Soundtouch", &sampleRate, &channels)) { + PyErr_SetString(PyExc_RuntimeError, "Requires sampling rate and number of channels (sample size must be 2)"); + return NULL; + } + + // Create the object + ps = PyObject_NEW(py_soundtouch, &py_soundtouch_t); + ps->soundtouch = new soundtouch::SoundTouch(); + ps->channels = (int) channels; + ps->soundtouch->setSampleRate(sampleRate); + ps->soundtouch->setChannels(channels); + + return (PyObject*) ps; +} + +// Deallocate the SoundTouch object +static void py_soundtouch_dealloc(PyObject* self, PyObject* args) { + py_soundtouch* ps = PY_SOUNDTOUCH(self); + + if (ps->soundtouch) { + delete ps->soundtouch; + ps->soundtouch = NULL; + } + + PyObject_DEL(self); +} + +// Set the rate of playback +static PyObject* py_soundtouch_set_rate(PyObject* self, PyObject* args) { + float rate; + + // Given the rate as a fraction + if (!PyArg_ParseTuple(args, "f", &rate) || rate < 0) { + PyErr_SetString(PyExc_TypeError, "invalid argument"); + return NULL; + } + + PY_SOUNDTOUCH(self)->soundtouch->setRate(rate); + + Py_INCREF(Py_None); + return Py_None; +} + +// Set the tempo of playback +static PyObject* py_soundtouch_set_tempo(PyObject* self, PyObject* args) { + float tempo; + + // Given the tempo as a ratio + if (!PyArg_ParseTuple(args, "f", &tempo) || tempo < 0) { + PyErr_SetString(PyExc_TypeError, "invalid argument"); + return NULL; + } + + PY_SOUNDTOUCH(self)->soundtouch->setTempo(tempo); + + Py_INCREF(Py_None); + return Py_None; +} + +// Shift all pitches +static PyObject* py_soundtouch_set_pitch(PyObject* self, PyObject* args) { + float pitch; + + // Given the pitch adjustment as a fraction + if (!PyArg_ParseTuple(args, "f", &pitch) || pitch < 0) { + PyErr_SetString(PyExc_TypeError, "invalid argument"); + return NULL; + } + + PY_SOUNDTOUCH(self)->soundtouch->setPitch(pitch); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject* py_soundtouch_set_pitch_shift(PyObject* self, PyObject* args) { + float pitch; + + // Given the pitch adjustment in half-steps + if (!PyArg_ParseTuple(args, "f", &pitch) || pitch < -12 || pitch > 12) { + PyErr_SetString(PyExc_TypeError, "invalid argument: pitch must be between -12 and 12"); + return NULL; + } + + PY_SOUNDTOUCH(self)->soundtouch->setPitchSemiTones(pitch); + + Py_INCREF(Py_None); + return Py_None; +} + +// Flush all samples to the output +static PyObject* py_soundtouch_flush(PyObject* self, PyObject* args) { + PY_SOUNDTOUCH(self)->soundtouch->flush(); + + // For debugging: + //delete inputs; + //delete outputs; + + Py_INCREF(Py_None); + return Py_None; +} + +// Clear all buffers +static PyObject* py_soundtouch_clear(PyObject* self, PyObject* args) { + PY_SOUNDTOUCH(self)->soundtouch->clear(); + + Py_INCREF(Py_None); + return Py_None; +} + +// Add new samples +static PyObject* py_soundtouch_put_samples(PyObject* self, PyObject* args) { + py_soundtouch* ps = PY_SOUNDTOUCH(self); + int buflen; + char* transfer; + + // Given a string of samples to add + if (!PyArg_ParseTuple(args, "s#", &transfer, &buflen)) { + PyErr_SetString(PyExc_TypeError, "invalid argument"); + return NULL; + } + + // Move them into our char-short union + for (int ii = 0; ii < buflen; ii++) + ps->buffer.chars[ii] = transfer[ii]; + + // Add them + ps->soundtouch->putSamples(ps->buffer.shorts, (uint) buflen / (2 * ps->channels)); + + // For debugging: + //inputs->write(ps->buffer.shorts, buflen / 2); + + Py_INCREF(Py_None); + return Py_None; +} + +// Return the shifted samples +static PyObject* py_soundtouch_get_samples(PyObject* self, PyObject* args) { + py_soundtouch* ps = PY_SOUNDTOUCH(self); + uint maxSamples; + + // Given the number of samples to request + if (!PyArg_ParseTuple(args, "|I", &maxSamples)) { + PyErr_SetString(PyExc_TypeError, "invalid argument"); + return NULL; + } + + // Move them into our char-short union + uint received = ps->soundtouch->receiveSamples(ps->buffer.shorts, maxSamples); + + // For debugging: + //outputs->write(ps->buffer.shorts, received * ps->channels); + + return PyString_FromStringAndSize(ps->buffer.chars, received * 2 * ps->channels); +} + +// Return how many samples are available for output +static PyObject* py_soundtouch_ready_count(PyObject* self, PyObject* args) { + return PyInt_FromLong(PY_SOUNDTOUCH(self)->soundtouch->numSamples()); +} + +// Return how many samples will be available given the current data +static PyObject* py_soundtouch_waiting_count(PyObject* self, PyObject* args) { + return PyInt_FromLong(PY_SOUNDTOUCH(self)->soundtouch->numUnprocessedSamples()); +} + +/* housekeeping */ + +static PyMethodDef soundtouch_methods[] = { + { "set_rate", py_soundtouch_set_rate, METH_VARARGS, "" }, + { "set_tempo", py_soundtouch_set_tempo, METH_VARARGS, "" }, + { "set_pitch", py_soundtouch_set_pitch, METH_VARARGS, "" }, + { "set_pitch_shift", py_soundtouch_set_pitch_shift, METH_VARARGS, "" }, + { "flush", py_soundtouch_flush, METH_VARARGS, "" }, + { "clear", py_soundtouch_clear, METH_VARARGS, "" }, + { "put_samples", py_soundtouch_put_samples, METH_VARARGS, "" }, + { "get_samples", py_soundtouch_get_samples, METH_VARARGS, "" }, + { "ready_count", py_soundtouch_ready_count, METH_VARARGS, "" }, + { "waiting_count", py_soundtouch_waiting_count, METH_VARARGS, "" }, + { NULL, 0, 0, NULL } +}; + +// Get additional attributes from the SoundTouch object +static PyObject* py_soundtouch_getattr(PyObject* self, char* name) { + // TODO: add soundtouch.getSetting here? Add a setattr with soundtouch.setSetting? + return Py_FindMethod(soundtouch_methods, self, name); +} diff --git a/pysoundtouch/src/pysoundtouch.h b/pysoundtouch/src/pysoundtouch.h new file mode 100644 index 0000000..e9790af --- /dev/null +++ b/pysoundtouch/src/pysoundtouch.h @@ -0,0 +1,61 @@ +/* + * python interface to soundtouch (the open-source audio processing library) + * Pitch, tempo, and rate shifting + */ + +#ifndef __PY_SOUNDTOUCH_H__ +#define __PY_SOUNDTOUCH_H__ + +#define __cplusplus + +#include +#include + +#define BUFFER_SIZE 44100 + +// Definition of the soundtouch object +typedef struct { + PyObject_HEAD + int channels; // 1 or 2 + soundtouch::SoundTouch* soundtouch; + union { + char chars[BUFFER_SIZE]; + short shorts[BUFFER_SIZE/2]; + } buffer; +} py_soundtouch; /* Soundtouch */ + +#define PY_SOUNDTOUCH(x) ((py_soundtouch *) x) + +extern PyTypeObject py_soundtouch_t; + +// Deallocate the SoundTouch object +static void py_soundtouch_dealloc(PyObject* self, PyObject* args); + +// Adjust attributes of samples that have been entered +static PyObject* py_soundtouch_set_rate(PyObject* self, PyObject* args); +static PyObject* py_soundtouch_set_tempo(PyObject* self, PyObject* args); +static PyObject* py_soundtouch_set_pitch(PyObject* self, PyObject* args); +static PyObject* py_soundtouch_set_pitch_shift(PyObject* self, PyObject* args); + +// Move all waiting samples to the output +static PyObject* py_soundtouch_flush(PyObject* self, PyObject* args); + +// Clear the buffer of samples +static PyObject* py_soundtouch_clear(PyObject* self, PyObject* args); + +// Add new samples to be processed +static PyObject* py_soundtouch_put_samples(PyObject* self, PyObject* args); + +// Extract processed samples from the output +static PyObject* py_soundtouch_get_samples(PyObject* self, PyObject* args); + +// Return how many samples are available for output +static PyObject* py_soundtouch_ready_count(PyObject* self, PyObject* args); + +// Return how many samples will be available given the current data +static PyObject* py_soundtouch_waiting_count(PyObject* self, PyObject* args); + +// Get additional attributes from the SoundTouch object +static PyObject* py_soundtouch_getattr(PyObject* self, char* name); + +#endif diff --git a/pysoundtouch/src/soundtouchmodule.c b/pysoundtouch/src/soundtouchmodule.c new file mode 100644 index 0000000..361af6a --- /dev/null +++ b/pysoundtouch/src/soundtouchmodule.c @@ -0,0 +1,27 @@ +/* + * python interface to soundtouch (the open-source audio processing library) + * Expose BMP detection and Shifting to python + */ + +#include +#include "soundtouchmodule.h" + +static PyMethodDef soundtouch_methods[] = { + { "SoundTouch", py_soundtouch_new, METH_VARARGS, "" }, + { "BPMDetect", py_bpmdetect_new, METH_VARARGS, "" }, + { NULL, 0, 0, NULL } +}; + +PyMODINIT_FUNC +initsoundtouch(void) { + PyObject *module, *dict; + + module = Py_InitModule("soundtouch", soundtouch_methods); + dict = PyModule_GetDict(module); + + PyDict_SetItemString(dict, "__version__", + PyString_FromString(VERSION)); + + if (PyErr_Occurred()) + PyErr_SetString(PyExc_ImportError, "soundtouch: init failed"); +} diff --git a/pysoundtouch/src/soundtouchmodule.h b/pysoundtouch/src/soundtouchmodule.h new file mode 100644 index 0000000..6f533eb --- /dev/null +++ b/pysoundtouch/src/soundtouchmodule.h @@ -0,0 +1,17 @@ +/* + * python interface to soundtouch (the open-source audio processing library) + * Expose BMP detection and Shifting to python + */ + +#ifndef __SOUNDTOUCH_MODULE_H__ +#define __SOUNDTOUCH_MODULE_H__ + +#include + +/* module accessible functions */ +extern "C" { + PyObject* py_soundtouch_new(PyObject* self, PyObject* args); + PyObject* py_bpmdetect_new(PyObject* self, PyObject* args); +} + +#endif -- cgit v1.2.3-70-g09d2