summaryrefslogtreecommitdiff
path: root/pysoundtouch/src
diff options
context:
space:
mode:
authoryo mama <pepper@scannerjammer.com>2015-06-19 16:24:27 -0400
committeryo mama <pepper@scannerjammer.com>2015-06-19 16:24:27 -0400
commit8adfb3bd99b4dcff2459756af090a640fd7a4b4a (patch)
treec1e6adddda335f4d36a98039ccc5ac867ae7296d /pysoundtouch/src
clone
Diffstat (limited to 'pysoundtouch/src')
-rw-r--r--pysoundtouch/src/WavFile.cpp745
-rw-r--r--pysoundtouch/src/WavFile.h250
-rw-r--r--pysoundtouch/src/pybpmdetect.cpp122
-rw-r--r--pysoundtouch/src/pybpmdetect.h41
-rw-r--r--pysoundtouch/src/pysoundtouch.cpp243
-rw-r--r--pysoundtouch/src/pysoundtouch.h61
-rw-r--r--pysoundtouch/src/soundtouchmodule.c27
-rw-r--r--pysoundtouch/src/soundtouchmodule.h17
8 files changed, 1506 insertions, 0 deletions
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 <stdio.h>
+#include <stdexcept>
+#include <string>
+#include <cstring>
+#include <assert.h>
+#include <limits.h>
+
+#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 <stdio.h>
+
+#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 <Python.h>
+
+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 <Python.h>
+#include <BPMDetect.h>
+
+#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 <Python.h>
+
+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 <Python.h>
+#include <SoundTouch.h>
+
+#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 <SoundTouch.h>
+#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 <Python.h>
+
+/* module accessible functions */
+extern "C" {
+ PyObject* py_soundtouch_new(PyObject* self, PyObject* args);
+ PyObject* py_bpmdetect_new(PyObject* self, PyObject* args);
+}
+
+#endif