Merge pull request #645 from diizy/master

BandLimitedWave: provide wavetables as pre-generated "bin" files, these ...
This commit is contained in:
Tobias Doerffel
2014-04-25 23:30:46 +02:00
8 changed files with 228 additions and 99 deletions

View File

@@ -4,6 +4,7 @@ ADD_SUBDIRECTORY(presets)
ADD_SUBDIRECTORY(projects)
ADD_SUBDIRECTORY(samples)
ADD_SUBDIRECTORY(themes)
ADD_SUBDIRECTORY(wavetables)
IF(LMMS_BUILD_LINUX)
INSTALL(FILES themes/default/icon.png DESTINATION "${DATA_DIR}/pixmaps" RENAME lmms.png)

View File

@@ -0,0 +1,2 @@
FILE(GLOB WAVETABLES *.bin)
INSTALL(FILES ${WAVETABLES} DESTINATION "${LMMS_DATA_DIR}/wavetables")

BIN
data/wavetables/moog.bin Normal file

Binary file not shown.

BIN
data/wavetables/saw.bin Normal file

Binary file not shown.

BIN
data/wavetables/sqr.bin Normal file

Binary file not shown.

BIN
data/wavetables/tri.bin Normal file

Binary file not shown.

View File

@@ -26,6 +26,11 @@
#ifndef BANDLIMITEDWAVE_H
#define BANDLIMITEDWAVE_H
#include <QString>
#include <QDataStream>
#include <QFile>
#include "config_mgr.h"
#include "interpolation.h"
#include "lmms_basics.h"
#include "lmms_math.h"
@@ -50,26 +55,54 @@ const int TLENS[MAXTBL+1] = { 2 << 0, 3 << 0, 2 << 1, 3 << 1,
typedef struct
{
public:
inline sample_t sampleAt( int _table, int _ph )
inline sample_t sampleAt( int table, int ph )
{
if( _table % 2 == 0 )
{ return m_data[ TLENS[ _table ] + _ph ]; }
if( table % 2 == 0 )
{ return m_data[ TLENS[ table ] + ph ]; }
else
{ return m_data3[ TLENS[ _table ] + _ph ]; }
{ return m_data3[ TLENS[ table ] + ph ]; }
}
inline void setSampleAt( int _table, int _ph, sample_t _sample )
inline void setSampleAt( int table, int ph, sample_t sample )
{
if( _table % 2 == 0 )
{ m_data[ TLENS[ _table ] + _ph ] = _sample; }
if( table % 2 == 0 )
{ m_data[ TLENS[ table ] + ph ] = sample; }
else
{ m_data3[ TLENS[ _table ] + _ph ] = _sample; }
{ m_data3[ TLENS[ table ] + ph ] = sample; }
}
private:
sample_t m_data [ MIPMAPSIZE ];
sample_t m_data3 [ MIPMAPSIZE3 ];
} WaveMipMap;
QDataStream& operator<< ( QDataStream &out, WaveMipMap &waveMipMap )
{
for( int tbl = 0; tbl <= MAXTBL; tbl++ )
{
for( int i = 0; i < TLENS[tbl]; i++ )
{
out << waveMipMap.sampleAt( tbl, i );
}
}
return out;
}
QDataStream& operator>> ( QDataStream &in, WaveMipMap &waveMipMap )
{
sample_t sample;
for( int tbl = 0; tbl <= MAXTBL; tbl++ )
{
for( int i = 0; i < TLENS[tbl]; i++ )
{
in >> sample;
waveMipMap.setSampleAt( tbl, i, sample );
}
}
return in;
}
class BandLimitedWave
{
public:
@@ -88,22 +121,22 @@ public:
/*! \brief This method converts frequency to wavelength. The oscillate function takes wavelength as argument so
* use this to convert your note frequency to wavelength before using it.
*/
static inline float freqToLen( float _f )
static inline float freqToLen( float f )
{
return freqToLen( _f, engine::mixer()->processingSampleRate() );
return freqToLen( f, engine::mixer()->processingSampleRate() );
}
/*! \brief This method converts frequency to wavelength, but you can use any custom sample rate with it.
*/
static inline float freqToLen( float _f, sample_rate_t _sr )
static inline float freqToLen( float f, sample_rate_t sr )
{
return static_cast<float>( _sr ) / _f;
return static_cast<float>( sr ) / f;
}
/*! \brief This method converts phase delta to wavelength. It assumes a phase scale of 0 to 1. */
static inline float pdToLen( float _pd )
static inline float pdToLen( float pd )
{
return 1.0f / _pd;
return 1.0f / pd;
}
/*! \brief This method provides interpolated samples of bandlimited waveforms.
@@ -189,8 +222,10 @@ public:
static void generateWaves();
static bool s_wavesGenerated;
static WaveMipMap s_waveforms [NumBLWaveforms];
static QString s_wavetableDir;
};

View File

@@ -28,6 +28,7 @@
WaveMipMap BandLimitedWave::s_waveforms[4] = { };
bool BandLimitedWave::s_wavesGenerated = false;
QString BandLimitedWave::s_wavetableDir = "";
void BandLimitedWave::generateWaves()
{
@@ -36,117 +37,207 @@ void BandLimitedWave::generateWaves()
int i;
// set wavetable directory
s_wavetableDir = configManager::inst()->dataDir() + "wavetables/";
// set wavetable files
QFile saw_file( s_wavetableDir + "saw.bin" );
QFile sqr_file( s_wavetableDir + "sqr.bin" );
QFile tri_file( s_wavetableDir + "tri.bin" );
QFile moog_file( s_wavetableDir + "moog.bin" );
// saw wave - BLSaw
for( i = 0; i <= MAXTBL; i++ )
// check for file and use it if exists
if( saw_file.exists() )
{
const int len = TLENS[i];
//const double om = 1.0 / len;
double max = 0.0;
for( int ph = 0; ph < len; ph++ )
saw_file.open( QIODevice::ReadOnly );
QDataStream in( &saw_file );
in >> s_waveforms[ BandLimitedWave::BLSaw ];
saw_file.close();
}
else
{
for( i = 0; i <= MAXTBL; i++ )
{
int harm = 1;
double s = 0.0f;
double hlen;
do
const int len = TLENS[i];
//const double om = 1.0 / len;
double max = 0.0;
for( int ph = 0; ph < len; ph++ )
{
hlen = static_cast<double>( len ) / static_cast<double>( harm );
const double amp = -1.0 / static_cast<double>( harm );
//const double a2 = cos( om * harm * F_2PI );
s += amp * /*a2 **/sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
harm++;
} while( hlen > 2.0 );
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
max = qMax( max, qAbs( s ) );
}
// normalize
for( int ph = 0; ph < len; ph++ )
{
sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max;
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
int harm = 1;
double s = 0.0f;
double hlen;
do
{
hlen = static_cast<double>( len ) / static_cast<double>( harm );
const double amp = -1.0 / static_cast<double>( harm );
//const double a2 = cos( om * harm * F_2PI );
s += amp * /*a2 **/sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
harm++;
} while( hlen > 2.0 );
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
max = qMax( max, qAbs( s ) );
}
// normalize
for( int ph = 0; ph < len; ph++ )
{
sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max;
s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s );
}
}
}
// square wave - BLSquare
for( i = 0; i <= MAXTBL; i++ )
// check for file and use it if exists
if( sqr_file.exists() )
{
const int len = TLENS[i];
//const double om = 1.0 / len;
double max = 0.0;
for( int ph = 0; ph < len; ph++ )
sqr_file.open( QIODevice::ReadOnly );
QDataStream in( &sqr_file );
in >> s_waveforms[ BandLimitedWave::BLSquare ];
sqr_file.close();
}
else
{
for( i = 0; i <= MAXTBL; i++ )
{
int harm = 1;
double s = 0.0f;
double hlen;
do
const int len = TLENS[i];
//const double om = 1.0 / len;
double max = 0.0;
for( int ph = 0; ph < len; ph++ )
{
hlen = static_cast<double>( len ) / static_cast<double>( harm );
const double amp = 1.0 / static_cast<double>( harm );
//const double a2 = cos( om * harm * F_2PI );
s += amp * /*a2 **/ sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
harm += 2;
} while( hlen > 2.0 );
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
max = qMax( max, qAbs( s ) );
}
// normalize
for( int ph = 0; ph < len; ph++ )
{
sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max;
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
int harm = 1;
double s = 0.0f;
double hlen;
do
{
hlen = static_cast<double>( len ) / static_cast<double>( harm );
const double amp = 1.0 / static_cast<double>( harm );
//const double a2 = cos( om * harm * F_2PI );
s += amp * /*a2 **/ sin( static_cast<double>( ph * harm ) / static_cast<double>( len ) * F_2PI );
harm += 2;
} while( hlen > 2.0 );
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
max = qMax( max, qAbs( s ) );
}
// normalize
for( int ph = 0; ph < len; ph++ )
{
sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max;
s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s );
}
}
}
// triangle wave - BLTriangle
for( i = 0; i <= MAXTBL; i++ )
if( tri_file.exists() )
{
const int len = TLENS[i];
//const double om = 1.0 / len;
double max = 0.0;
for( int ph = 0; ph < len; ph++ )
tri_file.open( QIODevice::ReadOnly );
QDataStream in( &tri_file );
in >> s_waveforms[ BandLimitedWave::BLTriangle ];
tri_file.close();
}
else
{
for( i = 0; i <= MAXTBL; i++ )
{
int harm = 1;
double s = 0.0f;
double hlen;
do
const int len = TLENS[i];
//const double om = 1.0 / len;
double max = 0.0;
for( int ph = 0; ph < len; ph++ )
{
hlen = static_cast<double>( len ) / static_cast<double>( harm );
const double amp = 1.0 / static_cast<double>( harm * harm );
//const double a2 = cos( om * harm * F_2PI );
s += amp * /*a2 **/ sin( ( static_cast<double>( ph * harm ) / static_cast<double>( len ) +
( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI );
harm += 2;
} while( hlen > 2.0 );
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
max = qMax( max, qAbs( s ) );
}
// normalize
for( int ph = 0; ph < len; ph++ )
{
sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max;
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
int harm = 1;
double s = 0.0f;
double hlen;
do
{
hlen = static_cast<double>( len ) / static_cast<double>( harm );
const double amp = 1.0 / static_cast<double>( harm * harm );
//const double a2 = cos( om * harm * F_2PI );
s += amp * /*a2 **/ sin( ( static_cast<double>( ph * harm ) / static_cast<double>( len ) +
( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI );
harm += 2;
} while( hlen > 2.0 );
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
max = qMax( max, qAbs( s ) );
}
// normalize
for( int ph = 0; ph < len; ph++ )
{
sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max;
s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s );
}
}
}
// moog saw wave - BLMoog
// basically, just add in triangle + 270-phase saw
for( i = 0; i <= MAXTBL; i++ )
if( moog_file.exists() )
{
const int len = TLENS[i];
for( int ph = 0; ph < len; ph++ )
moog_file.open( QIODevice::ReadOnly );
QDataStream in( &moog_file );
in >> s_waveforms[ BandLimitedWave::BLMoog ];
moog_file.close();
}
else
{
for( i = 0; i <= MAXTBL; i++ )
{
const int sawph = ( ph + static_cast<int>( len * 0.75 ) ) % len;
const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph );
const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph );
s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f );
const int len = TLENS[i];
for( int ph = 0; ph < len; ph++ )
{
const int sawph = ( ph + static_cast<int>( len * 0.75 ) ) % len;
const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph );
const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph );
s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f );
}
}
}
// set the generated flag so we don't load/generate them again needlessly
s_wavesGenerated = true;
// generate files, serialize mipmaps as QDataStreams and save them on disk
//
// normally these are now provided with LMMS as pre-generated so we don't have to do this,
// but I'm leaving the code here in case it's needed in the future
// (maybe we add more waveforms or change the generation code or mipmap format, etc.)
/*
// if you want to generate the files, you need to set the filenames and paths here -
// can't use the usual wavetable directory here as it can require permissions on
// some systems...
QFile sawfile( "path-to-wavetables/saw.bin" );
QFile sqrfile( "path-to-wavetables/sqr.bin" );
QFile trifile( "path-to-wavetables/tri.bin" );
QFile moogfile( "path-to-wavetables/moog.bin" );
sawfile.open( QIODevice::WriteOnly );
QDataStream sawout( &sawfile );
sawout << s_waveforms[ BandLimitedWave::BLSaw ];
sawfile.close();
sqrfile.open( QIODevice::WriteOnly );
QDataStream sqrout( &sqrfile );
sqrout << s_waveforms[ BandLimitedWave::BLSquare ];
sqrfile.close();
trifile.open( QIODevice::WriteOnly );
QDataStream triout( &trifile );
triout << s_waveforms[ BandLimitedWave::BLTriangle ];
trifile.close();
moogfile.open( QIODevice::WriteOnly );
QDataStream moogout( &moogfile );
moogout << s_waveforms[ BandLimitedWave::BLMoog ];
moogfile.close();
*/
}