| Author |
Message |
Frostburn

Joined: Dec 12, 2007 Posts: 231 Location: Finland
Audio files: 7
|
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 6365 Location: The Hague, NL
G2 patch files: 3
|
Posted: Sat Jan 12, 2008 6:47 am Post subject:
|
 |
|
Cool! It's really a marked improvement!
I had a go as well. My strategy should never alias as it doesn't allow for harmonics over the nequist but as the harmonics drop out the volume (and to a larger degree the preceived volume) fluctuates.
on the positive side; it's less code and should allow for a slower controllrate in the loop at the expense of stepping.
Maybe others have yet more ideas? It'd be cool to have a few strategies at hand to use depending on the situation.
| Code: |
(second /samp ) => float srate;
srate/ 2 => float nequist;
BlitSaw saw => dac;
4::second => dur sweep_length;
500 => float originalfreq;
6000 => float finalfreq;
now + sweep_length => time later;
originalfreq => saw.freq;
(finalfreq - originalfreq) / (sweep_length / samp) => float delta;
while(now < later)
{
saw.freq() + delta => saw.freq;
(( nequist - saw.freq() ) / saw.freq() - 1) $ int => saw.harmonics;
samp => now;
} |
_________________ Modern technology offers an endless field day to any deviant strains in our personalities. --J.G.Ballard |
|
|
Back to top
|
|
 |
Frostburn

Joined: Dec 12, 2007 Posts: 231 Location: Finland
Audio files: 7
|
Posted: Sat Jan 12, 2008 7:11 am Post subject:
|
 |
|
It wasn't long that I realized that we can do legimate integration instead of piecewise, that causes problems on high frequencies. This results in much more general functions (that still have problems at very high frequencies, because simple averaging integration doesn't eliminate all the frequencies above nyquist. Maybe sinc-anti-aliasing would do the trick better but that would require implementation of the Sine-integral(t) function):
| Code: |
fun float fun_aa_saw(float t,float delta_t) {
t-Math.floor(t+0.5) => float t1;
t+delta_t-Math.floor(t+delta_t+0.5) => float t2;
return (t2*t2-t1*t1)/delta_t;
}
fun float fun_aa_pulse(float t,float delta_t,float width) {
float t1;
if(t-Math.floor(t) < width) t+Math.floor(t)*(2.0*width-2.0) => t1;
else -t+Math.floor(t+1.0)*2.0*width => t1;
delta_t +=> t;
float t2;
if(t-Math.floor(t) < width) t+Math.floor(t)*(2.0*width-2.0) => t2;
else -t+Math.floor(t+1.0)*2.0*width => t2;
return (t2-t1)/delta_t;
}
fun float fun_aa_sin(float t,float delta_t) {
//Integrate sin(t*2*pi) over (t,t+delta_t) and divide by |delta_t| to get the average
return 0.5/(pi*delta_t)*(Math.cos(t*2.0*pi)-Math.cos((t+delta_t)*2.0*pi));
}
fun float fun_aa_tri(float t, float delta_t) {
t-Math.floor(t+0.25) => float t1;
t+delta_t-Math.floor(t+delta_t+0.25) => float t2;
if(t1 < 0.25) 2.0*t1*t1 => t1;
else -2.0*t1*t1 + t1*2.0 - 0.25 => t1;
if(t2 < 0.25) 2.0*t2*t2 => t2;
else -2.0*t2*t2 + t2*2.0 - 0.25 => t2;
return (t2-t1)/delta_t;
}
|
EDIT:
Kassen, you don't need to call:
(( nequist - saw.freq() ) / saw.freq() - 1) $ int => saw.harmonics;
Chuck does this automatically when you call:
0 => saw.harmonics;
EDIT2:
Oh and by the way. If you're like me and like to experiment with different mathematical functions just to hear how they sound like, you can define them in an integral form to get auto-anti-aliased versions of them:
| Code: |
fun float my_oscillator(float t, float delta_t){
return (my_functions_integral(t+delta_t)-my_functions_integral(t))/delta_t;
}
|
Where fun float my_functions_integral(float t) is any float function which's derivate you want to hear.
EDIT3: Removed Std.fabs() from the functions to fix a bug using it caused at negative frequencies. _________________ To boldly go where no man has bothered to go before. Last edited by Frostburn on Sun Jan 13, 2008 6:17 am; edited 2 times in total |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 6365 Location: The Hague, NL
G2 patch files: 3
|
Posted: Sat Jan 12, 2008 7:27 am Post subject:
|
 |
|
Yeah, these aren't easy problems. I just tried my strategy with the BlitSquare and got some odd artefacts at one stage of the sweep, strangely not even that high in the spectrum. (weird)
I do believer SndBuf comes with a mode for proper sin(x)/x anti-aliassing so we may end up with oldfashioned tables yet!
I wonder how much CPU could be saved by implementing some of your ideas as "proper" Ugens, SawOsc's performance here wasn't really to write home about and the Blit ones need manual managing of the harmonics which is cool for filter-like trickery but less then convenient for sweeps. I could see "a market" for proper and convenient anti-aliased sweping oscs for regular synthesis use.
Cool stuff! _________________ Modern technology offers an endless field day to any deviant strains in our personalities. --J.G.Ballard |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 6365 Location: The Hague, NL
G2 patch files: 3
|
Posted: Sat Jan 12, 2008 7:37 am Post subject:
|
 |
|
| Frostburn wrote: |
EDIT:
Kassen, you don't need to call:
(( nequist - saw.freq() ) / saw.freq() - 1) $ int => saw.harmonics;
Chuck does this automatically when you call:
0 => saw.harmonics; |
Coolness!
Now BlitSquare sounds good as well.
They come with .phase() as well, but I'm not sure how much modulation they can take and still remain ertifact-free. I'm not sure it's even possible to produce no-artifact deep modulations in the digital domain. _________________ Modern technology offers an endless field day to any deviant strains in our personalities. --J.G.Ballard |
|
|
Back to top
|
|
 |
Frostburn

Joined: Dec 12, 2007 Posts: 231 Location: Finland
Audio files: 7
|
Posted: Sat Jan 12, 2008 7:45 am Post subject:
|
 |
|
| Kassen wrote: |
Yeah, these aren't easy problems. I just tried my strategy with the BlitSquare and got some odd artefacts at one stage of the sweep, strangely not even that high in the spectrum. (weird)
|
I haven't looked at BlitSquare's source code but it looks like judging from it's output that it uses an IIR filter to generate it's harmonics. This causes it to perform poorly when modulated, which is why I wanted to make proper band-limited fm-able version of it.
| Kassen wrote: |
I wonder how much CPU could be saved by implementing some of your ideas as "proper" Ugens, SawOsc's performance here wasn't really to write home about and the Blit ones need manual managing of the harmonics which is cool for filter-like trickery but less then convenient for sweeps. I could see "a market" for proper and convenient anti-aliased sweping oscs for regular synthesis use.
|
I do believe that they can be greatly optimized CPUwise. And having them as legimate UGens would come handy.
I can try implementing them at ChucK source level but I have never used C++... so let's not get our hopes up yet. Of course we could ask Ge nicely. :)
I think we'll also have to see if any higher quality anti-aliasing mehods would yield analytic solutions for the basic oscillators. If they do we could make almost sublime square-tunes with ChucK. Mmm... High fidelity chip-music :) _________________ To boldly go where no man has bothered to go before. |
|
|
Back to top
|
|
 |
Frostburn

Joined: Dec 12, 2007 Posts: 231 Location: Finland
Audio files: 7
|
Posted: Sat Jan 12, 2008 8:01 am Post subject:
|
 |
|
| Kassen wrote: |
I'm not sure it's even possible to produce no-artifact deep modulations in the digital domain. |
Completely no-artifact is a no no for most of the cases because for most FM sweeps there doesn't exist an analytical representation of it, but I guess one could get pretty good results with sub-sampling ie. doing everything with float functions (avoiding look-up table oscillators) and rendering at ~240kHz sample rate and then down sampling with high-quality anti-alias.
Even better results could be achieved if one was a numerical math genius and could find numeric approximations for absolutely everything... but I don't think a human ear could tell the difference anymore. _________________ To boldly go where no man has bothered to go before. |
|
|
Back to top
|
|
 |
Kassen
Janitor


Joined: Jul 06, 2004 Posts: 6365 Location: The Hague, NL
G2 patch files: 3
|
Posted: Sat Jan 12, 2008 8:08 am Post subject:
|
 |
|
The good news is that the framework for Ugens is quite well structured, I mean; I don't speak C++ either but I still managed to fix Envelope when it had a bug; it's not that hard.
Not so good is that Ge has been bussy teaching. If I wanted to implement a Ugen I think I'd start by looking at how the others are done, then ask questions to our dev-list and hope to reach somebody like Dan Trueman. Some people (like him) have successfully implemented Ugens so they must know stuff.
I'm not sure how one merges successfull fixes or Ugens into the officialk source; Envelope was in there quite fast yet the ASIO fix still isn't in there. Maybe Ge wants to check stuff himself and he's on a Mac which may make Win-only issues like ASIO slower?
Ge is one of the nicest people I've had the pleasure of corresponding with but he's very bussy these days so hoping for another friendly hand might be better right now. Once I'm back home and armed with GCC again I'd be happy to join in and see where we get stuck; it's all structured and we can just copy and paste bits from Ugens that are already there. I think it's doable. for the DSP side we could ask for help from some of EM's dsp experts as well, I think we have all we need. _________________ Modern technology offers an endless field day to any deviant strains in our personalities. --J.G.Ballard |
|
|
Back to top
|
|
 |
Frostburn

Joined: Dec 12, 2007 Posts: 231 Location: Finland
Audio files: 7
|
Posted: Sun Jan 13, 2008 6:10 am Post subject:
|
 |
|
I fixed a couple of bugs and made the Anti-aliased oscillators more like UGens. The triangle one now has controllable width and sync mode 3 modifies the width according to input.
EDIT: Implemented sync mode 0. I misunderstood what was meant by it.
| Code: |
//Classes do not extend properly if they're not defined before the main loop. Most likely a bug.
class AaTri{
//Anti-aliasing variable width Triangle oscillator
//by: Pyry Pakkanen ( 2008 )
Gain in => blackhole;
Gain out;
220.0 => float _freq;
0.0 => float _phase;
0.5 => float _width;
0 => int _sync;
false => int alive;
fun float freq(float f){ return f => _freq; }
fun float freq(){ return _freq; }
fun float phase(float p){ return p => _phase; }
fun float phase(){ return _phase; } //Not constrained to (0.0,1.0)
fun float width(float w){ return Math.max(0.0,Math.min(1.0,w)) => _width; }
fun float width(){ return _width; }
fun int sync(int s){ return s => _sync; }
fun int sync(){ return _sync; }
fun void run(){
true => alive;
samp/second => float delta;
_phase => float current_phase;
delta*_freq => float delta_phase;
current_phase + delta_phase => float next_phase;
float t1;
float t2;
Impulse i => out;
while(alive){
//Variable width Triangle Oscillator
//Phases:
//(-width*0.5,width*0.5) from -1.0 to 1.0; slope 2.0/width;
//[width*0.5,1.0 - width*0.5] from 1.0 to -1.0; slope 2.0/(width-1.0);
current_phase-Math.floor(current_phase+0.5*_width) => t1;
next_phase-Math.floor(next_phase+0.5*_width) => t2;
if(delta_phase != 0.0){
if(_width == 1.0) (t2*t2-t1*t1)/delta_phase => i.next; //Rising saw-wave from 0.0 to 1.0 and then from -1.0 to 0.0
else if(_width == 0.0) ((t1-0.5)*(t1-0.5)-(t2-0.5)*(t2-0.5))/delta_phase => i.next; //Declining saw-wave from 1.0 to -1.0
else{
if(t1 < 0.5*_width) t1*t1/_width => t1;
else (t1-0.5)*(t1-0.5)/(_width-1.0) + 0.25 => t1;
if(t2 < 0.5*_width) t2*t2/_width => t2;
else (t2-0.5)*(t2-0.5)/(_width-1.0) + 0.25 => t2;
(t2-t1)/delta_phase => i.next;
}
}
else if(t1 < 0.5*_width) t1*2.0/_width => i.next;
else 1.0+_width/(1.0 - _width) + t1*2.0/(_width-1.0) => i.next;
samp => now;
next_phase => current_phase;
if(_sync == 0){ in.last() => freq; delta*_freq => delta_phase +=> _phase => next_phase; } //Frequency modulation without the _freq offset
else if(_sync == 1) { in.last() => phase => next_phase;
next_phase - current_phase => delta_phase; } //Phase modulation
else if(_sync == 2) delta*( _freq + in.last() ) => delta_phase +=> _phase => next_phase; //Frequency modulation
else if(_sync == 3) { in.last() => width; delta*_freq => delta_phase +=> _phase => next_phase; } //Width modulation
}
}
fun void kill(){ false => alive; }
}
class AaPulse extends AaTri {
//Override:
fun void run(){
true => alive;
samp/second => float delta;
_phase => float current_phase;
delta*_freq => float delta_phase;
current_phase + delta_phase => float next_phase;
float t;
float t1;
float t2;
Impulse i => out;
while(alive){
//Variable width Pulse Oscillator
//Phases:
//(0.0,width) 1.0;
//[width,1.0] -1.0;
if(delta_phase != 0.0){
current_phase => t;
if(t-Math.floor(t) < _width) t+Math.floor(t)*(2.0*_width-2.0) => t1;
else -t+Math.floor(t+1.0)*2.0*_width => t1;
next_phase => t;
if(t-Math.floor(t) < _width) t+Math.floor(t)*(2.0*_width-2.0) => t2;
else -t+Math.floor(t+1.0)*2.0*_width => t2;
(t2-t1)/delta_phase => i.next;
}
else if(current_phase-Math.floor(current_phase) < _width) 1.0 => i.next;
else -1.0 => i.next;
samp => now;
next_phase => current_phase;
if(_sync == 0){ in.last() => freq; delta*_freq => delta_phase +=> _phase => next_phase; } //Frequency modulation without the _freq offset
else if(_sync == 1) { in.last() => phase => next_phase;
next_phase - current_phase => delta_phase; } //Phase modulation
else if(_sync == 2) delta*( _freq + in.last() ) => delta_phase +=> _phase => next_phase; //Frequency modulation
else if(_sync == 3) { in.last() => width; delta*_freq => delta_phase +=> _phase => next_phase; } //Width modulation
}
}
}
//---------Main Loop----------
SinOsc m; //The modulator
AaTri at;
0.25 => at.width;
1 => at.sync; //Phase modulation
m => at.in; 20.0 => m.gain; 4.0 => m.freq;
at.out => dac;
spork~at.run(); //You'll have to start the oscillator manually
second => now;
at.kill(); //This kills the sporked loop
m =< at.in;
at.out =< dac;
second => now;
AaTri as;
1.0 => as.width; //Rising saw-wave
2 => as.sync; //FM
m => as.in; 10.0*as.freq() => m.gain; as.freq()*5.0 => m.freq;
as.out => dac;
spork~as.run();
now + 2::second => time later;
while(now < later){
m.gain()*0.99 => m.gain;
m.freq()*0.99 => m.freq;
10::ms => now;
}
as.kill();
m =< as.in;
as.out =< dac;
second => now;
AaPulse ap;
3 => ap.sync; //Width modulation
m => Gain offset => ap.in;
Step half => offset; 0.5 => half.next;
0.5 => m.gain; 3.0 => m.freq;
ap.out => dac;
spork~ap.run();
second => now;
ap.kill();
m =< offset =< ap.in;
ap.out =< dac;
second => now;
|
_________________ To boldly go where no man has bothered to go before. |
|
|
Back to top
|
|
 |
Frostburn

Joined: Dec 12, 2007 Posts: 231 Location: Finland
Audio files: 7
|
Posted: Sat Feb 16, 2008 6:55 am Post subject:
|
 |
|
Figured out a way to make an anti-aliased saw wave without Impulses.
A square wave can then be made by adding together two saw waves separated 180 degrees in phase (0.5 => phase) and inverting the other (-1.0 => gain).
| Code: | SawOsc s => Gain saw_integral => OneZero deriv => Gain AaSaw;
1.0 => deriv.b0;
-1.0 => deriv.b1;
3 => saw_integral.op;
0.5 => s.gain; //Get sweep from -0.5 to 0.5
s => Gain dummy => saw_integral; // make x*x
0 => s.sync;
Step freq => s;
4 => AaSaw.op;
freq => AaSaw;
second/samp => deriv.gain;
//220.0 => freq.next;
AaSaw => dac;// => WvOut w => blackhole;
//"foo.wav" => w.wavFilename;
0.0 => float t;
for(now + 10::second => time later; now < later; 10::ms => now){
t*220.0 => freq.next;
10::ms/second +=> t;
}
//w.closeFile(); |
_________________ To boldly go where no man has bothered to go before. |
|
|
Back to top
|
|
 |
kijjaz

Joined: Sep 20, 2004 Posts: 465 Location: bangkok, thailand
Audio files: 2
|
Posted: Sat Feb 16, 2008 9:45 am Post subject:
|
 |
|
| Wow .. you guys are musically mathematical. I'm gonna study them soon -_-" .. very great job! |
|
|
Back to top
|
|
 |
|