electro-music.com   Dedicated to experimental electro-acoustic
and electronic music
 
    Front Page  |  Articles  |  Radio
 |  Media  |  Forum  |  Links  |  Store
Forum with support of Syndicator RSS
 FAQFAQ   CalendarCalendar   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   LinksLinks GalleryGallery 
 RegisterRegister   ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in  Chat RoomChat Room 
 Forum index » DIY Hardware and Software » ChucK programming language
Anti-aliased oscillators
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 1 [11 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
Author Message
Frostburn



Joined: Dec 12, 2007
Posts: 213
Location: Finland
Audio files: 7

PostPosted: Fri Jan 11, 2008 5:08 pm    Post subject: Anti-aliased oscillators
Subject description: reducing frequency leaking
Reply with quote  Mark this post and the followings unread

I needed alias-free pulse and saw oscillators that could do FM so I made ones. They are float functions that are supposed to drive impulse generators. UGen is not extendable, right?
Just wanted to share. Feel free to ask any questions or point out any bugs.

Code:
fun float fun_saw(float t,float delta_t) {
    t-Math.floor(t) => t;
    //(0.0,0.5): from 0.0 to 1.0 (slope: 2.0)
    //[0.5,1.0): from -1.0 to 0.0 (slope: 2.0)
    if((t < 0.5) && (t+delta_t < 0.5)) return 2.0*t+delta_t;
    else if((t < 0.5) && (t+delta_t >= 0.5)) return (delta_t-1.0)*(2.0*t+delta_t-1.0)/delta_t;
    else if((t >= 0.5) && (t+delta_t < 0.5)) return (delta_t+1.0)*(2.0*t+delta_t-1.0)/delta_t;
    else if((t >= 0.5) && (t+delta_t >= 0.5)) return 2.0*t+delta_t-2.0;
    else return 0.0; //Should not happen
}   
fun float fun_pulse(float t,float delta_t,float width) {
    t-Math.floor(t) => t;
 
    //[0,width): 1.0
    //[width,1): -1.0
    if((t + delta_t <= 0) && (t >= width )) return width*2.0/delta_t-1.0; //If the pulse is very small we might miss it.
    else if((t < width) && (t + delta_t > 1.0)) return 1.0 - (1.0-width)*2.0/delta_t; //Same if it's very wide.
    else if(t + delta_t <= 0) return -1.0-2.0*t/delta_t;
    else if((t < width) && (t + delta_t < width) ) return 1.0;
    else if((t < width) && (t + delta_t >= width)) return 2.0*(width-t)/delta_t-1.0;
    else if((t >= width) && (t + delta_t < width)) return 1.0 - 2.0*(width-t)/delta_t;
    else if((t >= width) && (t + delta_t >= width) && (t + delta_t < 1.0) ) return -1.0;
    else if((t >= width) && (t + delta_t > 1.0)) return 1.0 + 2.0*(t-1.0)/delta_t;
    else return 0.0; //Should not happen
}


Listen the attached file to hear a demonstration.

I used simple integration to do the anti-aliasing. There may be better techniques available that reduce aliasing even more but I'm satisfied with the results so far.


anti_alias_osc.ck
 Description:
Listen to four frequency sweeps.
1st: SawOsc
2nd: Anti-aliased saw-wave
3rd: PulseOsc (width == 0.5) Same as SqrOsc.
4th: Anti-aliased pulse-wave

Download
 Filename:  anti_alias_osc.ck
 Filesize:  5.46 KB
 Downloaded:  21 Time(s)


_________________
To boldly go where no man has bothered to go before.
Back to top
View user's profile Send private message
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 6249
Location: The Hague, NL
G2 patch files: 3

PostPosted: Sat Jan 12, 2008 6:47 am    Post subject: Reply with quote  Mark this post and the followings unread

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;
    }

_________________
while(!machine.crash() ) <<<"all is well">>>;
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Frostburn



Joined: Dec 12, 2007
Posts: 213
Location: Finland
Audio files: 7

PostPosted: Sat Jan 12, 2008 7:11 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 6249
Location: The Hague, NL
G2 patch files: 3

PostPosted: Sat Jan 12, 2008 7:27 am    Post subject: Reply with quote  Mark this post and the followings unread

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! Smile

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!

_________________
while(!machine.crash() ) <<<"all is well">>>;
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 6249
Location: The Hague, NL
G2 patch files: 3

PostPosted: Sat Jan 12, 2008 7:37 am    Post subject: Reply with quote  Mark this post and the followings unread

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.

_________________
while(!machine.crash() ) <<<"all is well">>>;
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Frostburn



Joined: Dec 12, 2007
Posts: 213
Location: Finland
Audio files: 7

PostPosted: Sat Jan 12, 2008 7:45 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
Frostburn



Joined: Dec 12, 2007
Posts: 213
Location: Finland
Audio files: 7

PostPosted: Sat Jan 12, 2008 8:01 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
Kassen
Janitor
Janitor


Joined: Jul 06, 2004
Posts: 6249
Location: The Hague, NL
G2 patch files: 3

PostPosted: Sat Jan 12, 2008 8:08 am    Post subject: Reply with quote  Mark this post and the followings unread

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.

_________________
while(!machine.crash() ) <<<"all is well">>>;
Back to top
View user's profile Send private message Send e-mail Visit poster's website
Frostburn



Joined: Dec 12, 2007
Posts: 213
Location: Finland
Audio files: 7

PostPosted: Sun Jan 13, 2008 6:10 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
Frostburn



Joined: Dec 12, 2007
Posts: 213
Location: Finland
Audio files: 7

PostPosted: Sat Feb 16, 2008 6:55 am    Post subject: Reply with quote  Mark this post and the followings unread

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
View user's profile Send private message
kijjaz



Joined: Sep 20, 2004
Posts: 452
Location: bangkok, thailand
Audio files: 2

PostPosted: Sat Feb 16, 2008 9:45 am    Post subject: Reply with quote  Mark this post and the followings unread

Wow .. you guys are musically mathematical. I'm gonna study them soon -_-" .. very great job!
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger MSN Messenger
Display posts from previous:   
Post new topic   Reply to topic Moderators: Kassen
Page 1 of 1 [11 Posts]
View unread posts
View new posts in the last week
Mark the topic unread :: View previous topic :: View next topic
 Forum index » DIY Hardware and Software » ChucK programming language
Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum

Please support our site. If you click through and buy from
our affiliate partners, we earn a small commission.


Forum with support of Syndicator RSS
Powered by phpBB © 2001, 2005 phpBB Group
Copyright © 2003, 2004, 2005, 2006 and 2007 by electro-music.com