b4e has asked for the wisdom of the Perl Monks concerning the following question:

hey.
i'm currently trying to program a virtual synth using perl.
the program should be able to perform realtime manipulations like 'filter cutoff' etc.
i'm fairly ok with the mathematical aspect but can't find a way to implement it in perl.
i had a look at the cpan audio modules, but they either didn't work (like pdl::audio) or didn't help.
at the moment i'm using Win32::Sound to load the data into the soundcard buffer and then play it.
basically like this :
sub sound { my $WAV = new Win32::Sound::WaveOut(10000, 8, 1); my $length = $input2->get(); $length = $length*2; my $data2 = ""; my $counter = 0; for my $i (1..$length) #foreach my $loop (1..$length) { my $v2 = sin($counter/2*3.14) * 128 + 128; $data2 .= pack("c", $v2); $counter += ($add * 10)/10000; $WAV->Load($data2); # get it $WAV->Write(); # hear it $WAV->Unload(); # drop it &new_add; #get new $add value from a scale } }
however, this does NOT work...
is there any other way of sending data to the soundcard or maybe a totally different approach to this in perl ?
thanks, b.

Replies are listed 'Best First'.
Re: building a "realtime" software synth
by Joost (Canon) on Jul 20, 2004 at 15:04 UTC
    What exactly does not work?

    I'm having very nice results with Audio::Play for playing sounds.

    As for real time audio manipulation, I think you'd have to use PDL for anything complicated because of performance issues.

    Also, (shameless plug here): Have you tried Audio::LADSPA? It uses ladspa plugins to do most of the real processing work, so it should be loads faster than pure perl. I'm not sure if any of the plugins can be compiled for win32, but it might be worth trying.

    From the docs for Audio::LADSPA::Network

    use Audio::LADSPA::Network; use Audio::LADSPA::Plugin::Play; my $net = Audio::LADSPA::Network->new(); my $sine = $net->add_plugin( label => 'sine_fcac' ); my $delay = $net->add_plugin( label => 'delay_5s' ); my $play = $net->add_plugin('Audio::LADSPA::Plugin::Play'); $net->connect($sine,'Output',$delay,'Input'); $net->connect($delay,'Output',$play,'Input'); $sine->set('Frequency (Hz)' => 440); # set freq $sine->set(Amplitude => 1); # set amp $delay->set('Delay (Seconds)' => 1); # 1 sec delay $delay->set('Dry/Wet Balance' => 0.2); # balance - 0.2 for ( 0 .. 100 ) { $net->run(100); } $sine->set(Amplitude => 0); #just delay from now for ( 0 .. 500 ) { $net->run(100); }
    There are plugins available that do filtering, delays, reverbs, oscillators etc. For instance in the CMT library

    Good luck
    Joost.

      thanks for your quick reply.
      i've already tried to use Audio::LADSPA, but for some reason it does not work on my machine...
      maybe i was a bit too vague in my original question.
      what i want is that everytime a new sample is generated, ie every time
      my $v2 = sin($counter/2*3.14) * 128 + 128; $data2 .= pack("c", $v2);
      generates new data, this data should be played by the soundcard.
      this way, by including additional manipulations on $v2 using variables send to the subroutine by moving a scale-widget, the sound should change.
      i also would like to program the filters, fx, etc myself (that is, if i am able to do so...)
        Hmmm. I know for a fact that Audio::Play (should you decide to use it) uses floats for sample data and most pcm output hardware uses 16-bit words per sample. Your code assumes its 8-bits.

        It's also probably more efficient to generate an @array of data first (say 100 samples) and then pack() it into a string and write it (Audio::Play can do this for you):

        #!/usr/bin/perl -w use strict; use Audio::Play; use Audio::Data; my $player = Audio::Play->new(); while (1) { my $audio = Audio::Data->new( rate => 44100 ); my @samples = map { sin ( 2 * 3.14 * $_ / 100 ) / 8 } 0 .. 99; # +generate 100 samples as perl numbers $audio->data(@samples); # will pack() samples into floats $player->play($audio); }
        This works perfectly on my machine.

        Update:

        To clarify: you really don't want to calculate and then play each sample seperately. It's incredibly inefficient so even most C - based softsynths don't do that.

        What you want is a low latency between dragging a slider, and hearing the sound change. IMHO checking the slider state once for every 100 samples is enough:

        Say you're running at 44100 Hz (normal CD sample rate), then playing 100 samples takes 100/44100 =~ 0.0023 seconds. That's probably fast enough for anything except really exact keyboard input, provided you don't have to sync your ouput with audio streams generated from outside your machine.

        So, what you'd do is (pseudocode):

        while (1) { check_for_input(); update_parameters_for_new_input(); #possibly using heavy math generate_100_samples_as_fast_as_possible(); push_samples_into_soundcard_buffer(); }
        Hope this clarifies.
        Joost.
MIDI?
by chanio (Priest) on Jul 20, 2004 at 22:30 UTC
    Isn't MIDI good for all these?

    It is one of the benefits of using Win32. In other platforms, you wouldn't have it that easy.

    You might try something like Buzz Machines. It is more than any other previous tracker-sampler, etc. It works with modules that might be added, created, etc.

    .{\('v')/}
    _`(___)' __________________________
      i might include MIDI functions like SYNC (EXT) or an external start/stop function later but i want to build the synth so that it does create the sounds from scratch...

      is there any way to get the Audio::play module as a ppd file ???

      the buzz thing looks interesting, i'll have a look at that... thanks, b.