/* * BandLimitedWave.h - helper functions for band-limited * waveform generation * * Copyright (c) 2014 Vesa Kivimäki * * This file is part of LMMS - http://lmms.io * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program 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 * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "BandLimitedWave.h" WaveMipMap BandLimitedWave::s_waveforms[4] = { }; bool BandLimitedWave::s_wavesGenerated = false; QString BandLimitedWave::s_wavetableDir = ""; 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; } void BandLimitedWave::generateWaves() { // don't generate if they already exist if( s_wavesGenerated ) return; 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 // check for file and use it if exists if( saw_file.exists() ) { saw_file.open( QIODevice::ReadOnly ); QDataStream in( &saw_file ); in >> s_waveforms[ BandLimitedWave::BLSaw ]; saw_file.close(); } else { for( i = 0; i <= MAXTBL; i++ ) { const int len = TLENS[i]; //const double om = 1.0 / len; double max = 0.0; for( int ph = 0; ph < len; ph++ ) { 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 // check for file and use it if exists if( sqr_file.exists() ) { sqr_file.open( QIODevice::ReadOnly ); QDataStream in( &sqr_file ); in >> s_waveforms[ BandLimitedWave::BLSquare ]; sqr_file.close(); } else { for( i = 0; i <= MAXTBL; i++ ) { const int len = TLENS[i]; //const double om = 1.0 / len; double max = 0.0; for( int ph = 0; ph < len; ph++ ) { 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 if( tri_file.exists() ) { tri_file.open( QIODevice::ReadOnly ); QDataStream in( &tri_file ); in >> s_waveforms[ BandLimitedWave::BLTriangle ]; tri_file.close(); } else { for( i = 0; i <= MAXTBL; i++ ) { const int len = TLENS[i]; //const double om = 1.0 / len; double max = 0.0; for( int ph = 0; ph < len; ph++ ) { 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 if( moog_file.exists() ) { 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 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(); */ }