From: Stefan F. <st...@sf...> - 2010-03-25 15:46:49
|
Hi folks, I am currently rewriting my RC-Filters ... and I ran into some "core"-trouble ... Digital IIR-filters are (at low cutoff-frequencies) numericaly unstable. You can test this by using lmms standard-lowpass and esp. the standard-highpass and sweep it at very low frequencies. You even can trigger that unstable behaviour better (or more precisely make it extremely worse...) by exporting such a track to 192kHz, 8time oversampled (this is just because of the fact, that the relative cutoff-frequency gets even lower by this...). If only considering a one-pole-no-zero-filter (passive RC) the only effect is that you can not reach your desired cutoff-frequency. If it's a one-pole-one-zero/one-pole-two-zeros (lmms-standard-filters: lowpass, highpass, bandpass-csg/cpzg) this renders the low-end of frequencies unusable, because the filter will produce all sorts of nasty output and/or even will clip or saturate wildly. For a two-pole/two-zeros-approach (typical active RC-filter) it becomes even worse... The standard-way arround these problems is (if a DSP can handle such numbers) to use 64bit-double-precision floats (or better) for the filter-coefficients as well as for the samples in the filter-history. While it's trivial to change the filter-coeffs to doubles (as modern machines do all fp-stuff in doubles and just cast to floats afterwards this should even give a little speed-boost) in ./includes/basic_filters.h, I wonder what would be needed to do the same for the internal sample-format... I see that ./include/lmms_basics.h defines this: > typedef float sample_t; // standard sample-type But if I just change it, I get a bunch of compiler-errors for "sample_buffer.cpp" due to hard-coded floats instead of sampe_t's ... Just in case I fix that, what other "happy" side-effects would the change of sample_t from float to double imply? regards, Stefan |
From: Tobias D. <tob...@gm...> - 2010-04-01 21:40:41
|
Hi, Am Donnerstag, 25. März 2010 16:46:35 schrieb Stefan Fendt: > While it's trivial to change the filter-coeffs to doubles (as modern > machines do all fp-stuff in doubles and just cast to floats afterwards > this should even give a little speed-boost) in > ./includes/basic_filters.h, I wonder what would be needed to do the same > for the internal sample-format... > > I see that ./include/lmms_basics.h defines this: > > typedef float sample_t; // standard sample-type > > But if I just change it, I get a bunch of compiler-errors for > "sample_buffer.cpp" due to hard-coded floats instead of sampe_t's ... > > Just in case I fix that, what other "happy" side-effects would the > change of sample_t from float to double imply? We discussed about that 2 years ago or so. AFAIR there was no clear result so we left things as they were. Changing internal processing sample format to double definitely would be nice if it does not introduce performance regressions (whichI fear due to double data rate and thus less CPU cache efficiency). I'd like to see you providing a nice patch for that and make some benchmarks and/or send the patch to this list, so other people can test it. If the benchmark results convince, we'll apply the patch. Toby |
From: Jonathan A. <eag...@gm...> - 2010-04-01 22:30:34
|
toby with the processing power we have now a days it shouldnt hit performance badly. especially since cache size and speeds are getting quicker On Thu, Apr 1, 2010 at 11:40 PM, Tobias Doerffel <tob...@gm...>wrote: > Hi, > > Am Donnerstag, 25. März 2010 16:46:35 schrieb Stefan Fendt: > > While it's trivial to change the filter-coeffs to doubles (as modern > > machines do all fp-stuff in doubles and just cast to floats afterwards > > this should even give a little speed-boost) in > > ./includes/basic_filters.h, I wonder what would be needed to do the same > > for the internal sample-format... > > > > I see that ./include/lmms_basics.h defines this: > > > typedef float sample_t; // standard sample-type > > > > But if I just change it, I get a bunch of compiler-errors for > > "sample_buffer.cpp" due to hard-coded floats instead of sampe_t's ... > > > > Just in case I fix that, what other "happy" side-effects would the > > change of sample_t from float to double imply? > We discussed about that 2 years ago or so. AFAIR there was no clear result > so > we left things as they were. Changing internal processing sample format to > double definitely would be nice if it does not introduce performance > regressions (whichI fear due to double data rate and thus less CPU cache > efficiency). I'd like to see you providing a nice patch for that and make > some > benchmarks and/or send the patch to this list, so other people can test it. > If > the benchmark results convince, we'll apply the patch. > > Toby > > > ------------------------------------------------------------------------------ > Download Intel® Parallel Studio Eval > Try the new software tools for yourself. Speed compiling, find bugs > proactively, and fine-tune applications for parallel performance. > See why Intel Parallel Studio got high marks during beta. > http://p.sf.net/sfu/intel-sw-dev > _______________________________________________ > LMMS-devel mailing list > LMM...@li... > https://lists.sourceforge.net/lists/listinfo/lmms-devel > > -- Jonathan Aquilina |
From: Tobias D. <tob...@gm...> - 2010-04-06 20:35:55
|
Hi, Am Dienstag, 6. April 2010 21:52:41 schrieben Sie: > 1. Chose a sane but fixed output-sampling-rate (44.1 kHz, 48 kHz, 96 > kHz ... any of these will do. I would rather not go far beyond 96 > kHz... Why? Given alias-"free" generators no one even can hear > 22.05 kHz tones...) When doing post-processing of render output, samplerates up to 192 KHz IMHO can be helpful - even if most people won't need it. > 2. For "low quality" (==real-time output) I would just run everything > on that rate. Generators, filters, everything. That's whats already done, if I didn't understand you wrong. > 3. For "high quality" (==rendering output) I would just run the > generators (== Oscillators) on a /significantly/ higher rate. I > would _not_ opt for alias-free waveform-generators To be honest, there's a checkbox but currently it does not have any impact yet.. so don't let you get confused by that.. ;-) > (BLIP or > something alike, as these are limited to only a small subset of > the useful operations) but for a very high sampling rate (~16x > oversampled) and down-sample immediately after the generators via > stacked Sinc-Filters (to make this halfway fast at least). So we > would get virtually alias-"free" sampled output on a sane > sampling-rate. This is what I would feed into the filters and > effects. So their output will be the same than with the > real-time-preview (just without the alias). Why downsample before filters and effects? Usually you want to process audio data at highest samplerate until the end and downsample afterwards. Exceptions are made for LADSPA plugins that are known/were reported to go crazy with higher samplerates. > By doing so, I bet that most (if not all) stability problems > ("stability" in terms of audio-quality) of lmms will be gone... Hm.. probably true for filters but not in general. However I'm not completely familiar with all the higher DSP math behind filters etc. so correct me if I'm wrong. > [*] I personally regard a stable rendering quality which as closely as > possible resembles the real-time output (but of course without the > alias) as being essential for the acceptance of lmms against other > solutions, such as MusE or commercial products. Just because if one > presses the big render-knob he/she wants to know it sounds the same ... > just better (that is without the aliasing) than before... Do you know how other solutions handle this issue? Do they all downsample immediately after the generators? Toby |
From: Stefan F. <st...@sf...> - 2010-04-06 21:23:01
|
Tobias Doerffel schrieb: > Hi, > > Am Dienstag, 6. April 2010 21:52:41 schrieben Sie: > >> 1. Chose a sane but fixed output-sampling-rate (44.1 kHz, 48 kHz, 96 >> kHz ... any of these will do. I would rather not go far beyond 96 >> kHz... Why? Given alias-"free" generators no one even can hear >> 22.05 kHz tones...) >> > When doing post-processing of render output, samplerates up to 192 KHz IMHO > can be helpful - even if most people won't need it. > I never encountered this and I can't see a reason for it either, currently. So, for what operations would this be beneficial? Due to the fact that we are no bats, only sample-rates up to (maybe even this is too high... hmm,...) 96kHz make some sense at all. And only if post-processing of some sort is involved. For listening the finished product 44.1 kHz is definitely enough (16-bit maybe a little bit too less, but the sampling-rate is OK for that...). > That's whats already done, if I didn't understand you wrong. > :) no, you don't ... This is already happening all the day... :) >> would _not_ opt for alias-free waveform-generators >> > To be honest, there's a checkbox but currently it does not have any impact > yet.. so don't let you get confused by that.. ;-) > Oh, good... :) > Why downsample before filters and effects? Usually you want to process audio > data at highest samplerate until the end and downsample afterwards. Exceptions > are made for LADSPA plugins that are known/were reported to go crazy with > higher samplerates. > The reason is just, that going to the ends of the spectrum (either lowest or highest end, doesn't matter...) the coefficients of any DSP-Algo may get unstable due to very low or very high values needed to coope with the higher sampling-rate. While there exist many algorithms (filter+FX) which do not suffer too much from that problem so they are usable on a quite large range of sample-rates, some of the more interesting ones are difficult to be tuned for stable operation on such a large scale of supported sampling-rates. While this is strongly related to numerical precision it is something which can be solved. But it is difficult to do (and to do it "fast" enough for real-time) and most of the time the gain in quality is so marginal that it's just not worth the effort... > Hm.. probably true for filters but not in general. However I'm not completely > familiar with all the higher DSP math behind filters etc. so correct me if I'm > wrong. > Well, for anything which has at least two poles (so, yes, mainly for IIR-digital filters :)), this becomes "interesting"... It doesn't apply on such alike as reverbs or FIR-Filters. My point just was that even with these the gain in quality is marginal (if present at all) and the computational load is reduced in either ways... >> [*] I personally regard a stable rendering quality which as closely as >> possible resembles the real-time output (but of course without the >> alias) as being essential for the acceptance of lmms against other >> solutions, such as MusE or commercial products. Just because if one >> presses the big render-knob he/she wants to know it sounds the same ... >> just better (that is without the aliasing) than before... >> > Do you know how other solutions handle this issue? Do they all downsample > immediately after the generators? > At least the products I know the internals of, do it this way: Either they generate the waveforms in a band-limited-manner directly on f_s or they generate the waveforms with a very high sampling-rate (power of two to f_s) and down-sample to f_s immediately afterwards, so all following DSP is done on that lower rate... all the best, Stefan |
From: Stefan F. <st...@sf...> - 2010-04-07 17:53:02
|
Maybe this is of use for someone... // Oversampled "naive" Oscillator to suppress audible aliasing. This concept // will work also with a wavetable-based oscillator. It does _not_ deliver alias // *free*-output, but the alias is sufficiently suppressed to be unaudible at // 32-times oversampling and above. // // Usually one would use a FIR-Filter for this, but an IIR-Filter works also // and gives the output some "analogue" touch-up... ;-) Despite that the IIR is // faster and we are not interested in having something with zero-phase-shift // anyways... // // This is proof of concept-code, so do not expect any beauty... // // (C)2010 S.Fendt, released into the public domain in the hope it will be usefull // for someone... #include <iostream> #include <fstream> #include <cmath> #include <cstdlib> class lowpassfilter { double a0,a1,a2,b1,b2; double x0,x1,x2,y1,y2,y0; public: lowpassfilter( float rel_frequency ) { double fx = cos( M_PIl*rel_frequency ); double fy = sin( M_PIl*rel_frequency ); double sD = sqrt( pow( (-1.0) - fx, 2.0 ) + pow( -fy, 2.0 ) )/ sqrt( pow( (+1.0) - fx, 2.0 ) + pow( -fy, 2.0 ) ); double sR = sD/fabs( pow(sD, 2.0) - 1.0 )*2.0; if( sR > 10000 ) sR=10000; double alpha = asin( fy / sR ); double px = 0; double py = 0; if( fx >= 0 ) { double sX = fx + sR*cos( asin( fy / sR ) ); px = cos(M_PI-alpha*0.45)*sR+sX; py = sin(M_PI-alpha*0.45)*sR; } else { double sX = fx - sR*cos( asin( fy / sR ) ); px = cos(alpha*0.45)*sR+sX; py = sin(alpha*0.45)*sR; } a0 = ( px*px + py*py - 2.0 * px + 1 )/4.0; a1 = +2.0 * a0; a2 = +1.0 * a0; b1 = +2.0 * px; b2 = -( px*px + py*py ); x0 = x1 = x2 = y0 = y1 = y2 = 0.0; } inline double run( double in ) { x2 = x1; x1 = x0; x0 = in; y2 = y1; y1 = y0; y0 = x0*a0 + x1*a1 + x2*a2 + y1*b1 + y2*b2; return( y0 ); } }; class oscillator { float phase; float frequency; float samplerate; int oversampling; int pos; float out; lowpassfilter* lp0; lowpassfilter* lp1; lowpassfilter* lp2; lowpassfilter* lp3; public: oscillator(float samplerate) { this->samplerate = samplerate; this->phase = 0; this->frequency = 8000; this->oversampling = 256; // 64times oversampling runs >10times faster // than realtime on my system (E5400@2.7GHz) // and is free of audible aliasing... out = 0; lp0 = new lowpassfilter( 0.66/(float)oversampling ); lp1 = new lowpassfilter( 0.66/(float)oversampling ); lp2 = new lowpassfilter( 0.66/(float)oversampling ); lp3 = new lowpassfilter( 0.66/(float)oversampling ); } ~oscillator() { } float run() { const float alpha = 0.125f/(float)oversampling; for(int n=0; n<oversampling; n++) { phase += frequency/(samplerate*(float)oversampling); phase = phase>1.0f? phase-1.0f:phase; if(1) { if(phase<0.125) out = lp3->run(lp2->run(lp1->run(lp0->run( +0.5 )))); else out = lp3->run(lp2->run(lp1->run(lp0->run( -0.5 )))); } else { out = lp3->run(lp2->run(lp1->run(lp0->run( (phase-0.5)*2.0 )))); } } return( out ); } void set_frequency( float frequency ) { this->frequency = frequency; } }; int main() { std::fstream fileout( "outfile.dat", std::ios::out|std::ios::binary ); float samplerate = 44100; union { float f; char c[4]; } sample; oscillator m_oscillator( samplerate ); float f0 = 100; float f1 = 8000; for(int n=0; n<(int)(samplerate*10); n++ ) { m_oscillator.set_frequency( f0*((float)n/(samplerate*10)) + f1 * (1.0-(float)n/(samplerate*10)) ); sample.f = m_oscillator.run(); fileout.write( &sample.c[0], 4 ); } fileout.close(); } |
From: Paul G. <dr...@gm...> - 2010-04-09 14:21:15
|
This is an interesting thread. Let's keep it going. I wonder what the best solution is. How about if the existing core layout was not a limiting factor? How would we do it with a blank canvas? Paul On Apr 7, 2010 1:53 PM, "Stefan Fendt" <st...@sf...> wrote: Maybe this is of use for someone... // Oversampled "naive" Oscillator to suppress audible aliasing. This concept // will work also with a wavetable-based oscillator. It does _not_ deliver alias // *free*-output, but the alias is sufficiently suppressed to be unaudible at // 32-times oversampling and above. // // Usually one would use a FIR-Filter for this, but an IIR-Filter works also // and gives the output some "analogue" touch-up... ;-) Despite that the IIR is // faster and we are not interested in having something with zero-phase-shift // anyways... // // This is proof of concept-code, so do not expect any beauty... // // (C)2010 S.Fendt, released into the public domain in the hope it will be usefull // for someone... #include <iostream> #include <fstream> #include <cmath> #include <cstdlib> class lowpassfilter { double a0,a1,a2,b1,b2; double x0,x1,x2,y1,y2,y0; public: lowpassfilter( float rel_frequency ) { double fx = cos( M_PIl*rel_frequency ); double fy = sin( M_PIl*rel_frequency ); double sD = sqrt( pow( (-1.0) - fx, 2.0 ) + pow( -fy, 2.0 ) )/ sqrt( pow( (+1.0) - fx, 2.0 ) + pow( -fy, 2.0 ) ); double sR = sD/fabs( pow(sD, 2.0) - 1.0 )*2.0; if( sR > 10000 ) sR=10000; double alpha = asin( fy / sR ); double px = 0; double py = 0; if( fx >= 0 ) { double sX = fx + sR*cos( asin( fy / sR ) ); px = cos(M_PI-alpha*0.45)*sR+sX; py = sin(M_PI-alpha*0.45)*sR; } else { double sX = fx - sR*cos( asin( fy / sR ) ); px = cos(alpha*0.45)*sR+sX; py = sin(alpha*0.45)*sR; } a0 = ( px*px + py*py - 2.0 * px + 1 )/4.0; a1 = +2.0 * a0; a2 = +1.0 * a0; b1 = +2.0 * px; b2 = -( px*px + py*py ); x0 = x1 = x2 = y0 = y1 = y2 = 0.0; } inline double run( double in ) { x2 = x1; x1 = x0; x0 = in; y2 = y1; y1 = y0; y0 = x0*a0 + x1*a1 + x2*a2 + y1*b1 + y2*b2; return( y0 ); } }; class oscillator { float phase; float frequency; float samplerate; int oversampling; int pos; float out; lowpassfilter* lp0; lowpassfilter* lp1; lowpassfilter* lp2; lowpassfilter* lp3; public: oscillator(float samplerate) { this->samplerate = samplerate; this->phase = 0; this->frequency = 8000; this->oversampling = 256; // 64times oversampling runs >10times faster // than realtime on my system (E5400@2.7GHz) // and is free of audible aliasing... out = 0; lp0 = new lowpassfilter( 0.66/(float)oversampling ); lp1 = new lowpassfilter( 0.66/(float)oversampling ); lp2 = new lowpassfilter( 0.66/(float)oversampling ); lp3 = new lowpassfilter( 0.66/(float)oversampling ); } ~oscillator() { } float run() { const float alpha = 0.125f/(float)oversampling; for(int n=0; n<oversampling; n++) { phase += frequency/(samplerate*(float)oversampling); phase = phase>1.0f? phase-1.0f:phase; if(1) { if(phase<0.125) out = lp3->run(lp2->run(lp1->run(lp0->run( +0.5 )))); else out = lp3->run(lp2->run(lp1->run(lp0->run( -0.5 )))); } else { out = lp3->run(lp2->run(lp1->run(lp0->run( (phase-0.5)*2.0 )))); } } return( out ); } void set_frequency( float frequency ) { this->frequency = frequency; } }; int main() { std::fstream fileout( "outfile.dat", std::ios::out|std::ios::binary ); float samplerate = 44100; union { float f; char c[4]; } sample; oscillator m_oscillator( samplerate ); float f0 = 100; float f1 = 8000; for(int n=0; n<(int)(samplerate*10); n++ ) { m_oscillator.set_frequency( f0*((float)n/(samplerate*10)) + f1 * (1.0-(float)n/(samplerate*10)) ); sample.f = m_oscillator.run(); fileout.write( &sample.c[0], 4 ); } fileout.close(); } ------------------------------------------------------------------------------ Download Intel... |
From: Stefan F. <st...@sf...> - 2010-04-10 12:56:06
|
Hello Paul, > This is an interesting thread. Let's keep it going. I wonder what the > best solution is. How about if the existing core layout was not a > limiting factor? How would we do it with a blank canvas? > Well I'm not absolutely sure, as all approaches have their respective pros and cons... 1. Naive generators + massive oversampling ("Brute-Force") * Pros: o the only solution which can under some conditions sound absolutely faithful if "true analog" sound is required o the only solution which can under some conditions handle virtually all types of modulations correctly: - ring-modulation - frequency-modulation - hard-/soft-sync (BTW: phase-modulation is just FM with reduced modulation-factors and AM is just ring-modulation with reduced modulation-factors) o very simple to design * Cons: o under real-time-conditions sounds very, very awful due to massive under-sampling of the generators o good sound quality is only achievable if the generators are run on extraordinary high sample-rates which is unfeasible for real-time-processing o if the high sample-rate is hold through the whole rendering-path some FX- and or filter algorithms will get unstable and or produce a result which sounds considerably different from that of the real-time-preview, making it very hard for an artist to decide how the final product will sound... o white/pink/whatever noise as a source can not be handled correctly. 2. Sort of "clever" generators ("somehow"(TM) band-limited) * Pros: o Usually real-time capable with good to superb sound-quality if not modulated (too much). o Noise-sources can be handled correctly. That is: the amplitude of the noise remains the same in preview and HQ-rendering. o Oversampling (for a HQ-Mode) still is possible, but due to the fact that there will never be as much aliasing as with naive generators (even if using full blown modulations) a moderate oversampling only for the generators (2x or 4x) will be sufficient to eliminate nearly all problems arising through modulations. On more powerful machines this can be done in real-time, too... * Cons o Some algorithms are quite complicated to understand o Some algorithms need some heuristics to work o The more easy to understand algorithms (wave-table-based) use more memory (even more than "naive" wave-table-oscillators) o Higher order modulations can produce some amount of alias-frequencies again if not oversampled: - ring-modulation - frequency-modulation - syncing oscillators Remark: If the band-limit of the generator is not set to the Nyquist but somewhat lower, it nonetheless sounds good without oversampling, even if the result is not identical to that of a real analog synthesizers and or that which can be achieved by brute-forcing. The main advantage is that the real-time- and the high-quality-mode are identical so the artist can hear in advance what he/she will get for mastering. I would most probably opt for the later case, using a band-limited wave-table-based approach. This would on the one hand side result in a larger memory-footprint (compared to naive generators) but it would on the other hand side allow for extremely simple and fast algorithms for the waveform-generation. Most probably I would not use "real" oversampling... Most probably I would just run the engine (internally) on 96kHz using generators band-limited to 18-20kHz. If these are modulated then, one will (with the exception of hard-sync[*]) get only a spectrum reaching up to 36-40kHz. Depending on the sound-card used, this either can be down-sampled by a factor of 2 or directly output... Stefan [*] if using hard-sync harmonics higher than the Nyquist may be generated but as it's a "fold-over" at 48kHz then, it would take some quite high pitched generator to make it audible again and at least it would sound as in the preview... |