diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index e385d2f13..58af25ce0 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -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) diff --git a/data/wavetables/CMakeLists.txt b/data/wavetables/CMakeLists.txt new file mode 100644 index 000000000..9089171cc --- /dev/null +++ b/data/wavetables/CMakeLists.txt @@ -0,0 +1,2 @@ +FILE(GLOB WAVETABLES *.bin) +INSTALL(FILES ${WAVETABLES} DESTINATION "${LMMS_DATA_DIR}/wavetables") diff --git a/data/wavetables/moog.bin b/data/wavetables/moog.bin new file mode 100644 index 000000000..4dda32052 Binary files /dev/null and b/data/wavetables/moog.bin differ diff --git a/data/wavetables/saw.bin b/data/wavetables/saw.bin new file mode 100644 index 000000000..57b6a1323 Binary files /dev/null and b/data/wavetables/saw.bin differ diff --git a/data/wavetables/sqr.bin b/data/wavetables/sqr.bin new file mode 100644 index 000000000..731194915 Binary files /dev/null and b/data/wavetables/sqr.bin differ diff --git a/data/wavetables/tri.bin b/data/wavetables/tri.bin new file mode 100644 index 000000000..c2f87eef1 Binary files /dev/null and b/data/wavetables/tri.bin differ diff --git a/include/BandLimitedWave.h b/include/BandLimitedWave.h index 855ff4b90..06f320db9 100644 --- a/include/BandLimitedWave.h +++ b/include/BandLimitedWave.h @@ -26,6 +26,11 @@ #ifndef BANDLIMITEDWAVE_H #define BANDLIMITEDWAVE_H +#include +#include +#include + +#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( _sr ) / _f; + return static_cast( 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; }; diff --git a/src/core/BandLimitedWave.cpp b/src/core/BandLimitedWave.cpp index cf2215c8e..8d95e0426 100644 --- a/src/core/BandLimitedWave.cpp +++ b/src/core/BandLimitedWave.cpp @@ -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( len ) / static_cast( harm ); - const double amp = -1.0 / static_cast( harm ); - //const double a2 = cos( om * harm * F_2PI ); - s += amp * /*a2 **/sin( static_cast( ph * harm ) / static_cast( 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( len ) / static_cast( harm ); + const double amp = -1.0 / static_cast( harm ); + //const double a2 = cos( om * harm * F_2PI ); + s += amp * /*a2 **/sin( static_cast( ph * harm ) / static_cast( 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( len ) / static_cast( harm ); - const double amp = 1.0 / static_cast( harm ); - //const double a2 = cos( om * harm * F_2PI ); - s += amp * /*a2 **/ sin( static_cast( ph * harm ) / static_cast( 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( len ) / static_cast( harm ); + const double amp = 1.0 / static_cast( harm ); + //const double a2 = cos( om * harm * F_2PI ); + s += amp * /*a2 **/ sin( static_cast( ph * harm ) / static_cast( 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( len ) / static_cast( harm ); - const double amp = 1.0 / static_cast( harm * harm ); - //const double a2 = cos( om * harm * F_2PI ); - s += amp * /*a2 **/ sin( ( static_cast( ph * harm ) / static_cast( 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( len ) / static_cast( harm ); + const double amp = 1.0 / static_cast( harm * harm ); + //const double a2 = cos( om * harm * F_2PI ); + s += amp * /*a2 **/ sin( ( static_cast( ph * harm ) / static_cast( 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( 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( 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(); + +*/ + }