<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl -w

# by alexatslabdotorg
# http://yaxu.org/

# feedback.pl - a Perl editor for live coding
#
# Copyright (C) Alex McLean 2004
#
# 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; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

use strict;

my $text = '';
if (@ARGV) {
    my $file = shift @ARGV;
    open(FH, "&lt;$file")
      or die "couldn't open '$file': $!\n";
    $text = join('', &lt;FH&gt;);
    close FH;
}

my $editor = Editor-&gt;new(text =&gt; $text);
$editor-&gt;run;

##

package Editor;

# (c) 2004 Alex McLean

use strict;
use Curses;
use Time::HiRes qw/sleep/;

use constant MAX_COLUMN =&gt; 79;
use constant X =&gt; 1;
use constant Y =&gt; 0;

##

sub new {
    my ($pkg, %p) = @_;
    my $self = bless(\%p, $_[0]);
    $self-&gt;init();
    return($self)
}

##

sub init {
    my $self = shift;
    $self-&gt;init_curses;

    $self-&gt;{sandbox} = Sandbox-&gt;_new({code =&gt; $self-&gt;{code}});
    $self-&gt;init_code;
    $self-&gt;{cursor} = [0, 0];
    $self-&gt;{yscroll} = 0;
    $self-&gt;{xscroll} = 0;
    $self-&gt;{modification} = 0;
    $self-&gt;redraw;
    $self-&gt;exec_thread;
    $self-&gt;{sandbox}-&gt;reload(1);
    refresh();
}

##

sub exec_thread {
    my $self = shift;
    if (not $self-&gt;{exec_thread}) {
        $self-&gt;{exec_thread} =
          $self-&gt;{sandbox}-&gt;new_thread;
    }
}

##

sub init_code {
    my $self = shift;
    my $code = $self-&gt;{sandbox}-&gt;code;
    
    if ($self-&gt;{text}) {
        push(@$code, split("\n", $self-&gt;{text}));
        delete $self-&gt;{text};
    } else {
        push(@$code, (
                      'sub bang {',
                      '    my $self = shift;',
                      #'',
                      #'    # Type something interesting for me to do here',
                      #'',
                      #'    $self-&gt;code-&gt;[3] .= ".";',
                      #'    $self-&gt;modified;',
                      '}',
                     )
            );
    }
    $self-&gt;{code} = $code;
}

##

sub redraw {
    my $self = shift;

    #clear;
    my $code = $self-&gt;{code};

    for (my $c = $self-&gt;{yscroll}; $c &lt; scalar(@$code); ++$c) {
        addstr($c - $self-&gt;{yscroll}, 0, $code-&gt;[$c] 
               . (' ' x (MAX_COLUMN - length($code-&gt;[$c])))
              );
    }

    move(@{$self-&gt;{cursor}});
}

##

sub init_curses {
    initscr();
    start_color();
    cbreak();
    noecho();
    nonl();
    #idlok(1);
    scrollok(1);
    keypad(1);
    nodelay(1);
    move(0, 0);
}

##

sub run {
    my $self = shift;
    while (1) {
        sleep(0.1);
        while ((my $ch = getch()) ne ERR) {
            my $ord = ord($ch);
            if (length($ch) == 1) {
                if ($ord &gt;= 1 and $ord &lt;= 26) {
                    $ch = ('ctrl_' . chr($ord + ord('a') - 1));
                } else {
                    $self-&gt;add($ch);
                }
            }
            # this should really be a hash lookup by now
            if ($ch eq ' ') {
                $ch = 'space';
            } elsif ($ch eq KEY_LEFT) {
                $ch = 'left';
            } elsif ($ch eq KEY_RIGHT) {
                $ch = 'right';
            } elsif ($ch eq KEY_UP) {
                $ch = 'up';
            } elsif ($ch eq KEY_DOWN) {
                $ch = 'down';
            } elsif ($ch eq KEY_ENTER) {
                $ch = 'enter';
            } elsif ($ch eq KEY_BACKSPACE) {
                $ch = 'backspace';
            } elsif ($ord == 9) {
                $ch = 'tab';
            } elsif ($ch eq 'ctrl_d') {
                $ch = 'delete';
            } elsif ($ch eq 'ctrl_b') {
                $ch = 'left';
            } elsif ($ch eq 'ctrl_f') {
                $ch = 'right';
            } elsif ($ch eq 'ctrl_p') {
                $ch = 'up';
            } elsif ($ch eq 'ctrl_n') {
                $ch = 'down';
            } elsif ($ch eq '274') {
                $ch = 'f10';
            }
            my $func = "key__$ch";
            if ($self-&gt;can($func)) {
                $self-&gt;$func();
            }
        }
        if ($self-&gt;{sandbox}-&gt;modification &gt; $self-&gt;{modification}) {
            $self-&gt;{modification} = $self-&gt;{sandbox}-&gt;modification;
            if ($ENV{SELFMODIFY}) {
                $self-&gt;{sandbox}-&gt;reload(1);
            }
            $self-&gt;redraw;
        }
    
        refresh;
    }
}

##

sub key__ctrl_r {
    my $self = shift;
    my $filename = $self-&gt;archive_filename;
    my $code = join("\n", @{$self-&gt;{code}});
    open(FN, "&gt;$filename")
      or warn "couldn't write to $filename";
    print FN $code;
    close FN;
    system("echo '" . scalar(localtime) . "' | ci -l -q $filename 2&gt;&amp;1");
}

sub archive_filename {
    my $self = shift;
    unless ($self-&gt;{filename}) {
        my($sec, $min, $hour, $day, $month, $year) = localtime(time);
        $month += 1;
        $year += 1900;
        my $date = sprintf("%04d-%02d-%02d", $year, $month, $day);
        my $dir = "/yaxu/archive/$date/";
        mkdir $dir unless -d $dir;
        mkdir "$dir/RCS" unless -d "$dir/RCS";
        chdir($dir);
        my $file = "$hour:$min:$sec-$$";
    
        $self-&gt;{filename} = $file;
    }
    return($self-&gt;{filename});
}

sub key__ctrl_x {
    my $self = shift;
    $self-&gt;{sandbox}-&gt;reload(1);
}

##

sub current_y {
    my $self = shift;
    return $self-&gt;{cursor}-&gt;[Y] + $self-&gt;{yscroll};
}

##

sub current_line {
    my $self = shift;

    my $y = $self-&gt;current_y;

    if (not defined $self-&gt;{code}-&gt;[$y]) {
        $self-&gt;{code}-&gt;[$y] = '';
    }
    return(\$self-&gt;{code}-&gt;[$y]);
}

##

sub add {
    my ($self, $ch) = @_;
    my $cursor = $self-&gt;{cursor};
    if ($cursor-&gt;[X] &lt; (MAX_COLUMN - 1)) {
        my $line = $self-&gt;current_line;
    
        if (length($$line) &lt; $cursor-&gt;[X]) {
            $$line .= (' ' x ($cursor-&gt;[X] - length($$line)));
        }
        substr($$line, $cursor-&gt;[X], 0) = $ch;
    
        insstr($ch);
        $self-&gt;key__right;
    }
}

##

sub key__backspace {
    my $self = shift;
    my $cursor = $self-&gt;{cursor};
    my $line = $self-&gt;current_line;
    
    if ($cursor-&gt;[X] &gt; 0) {
        --$cursor-&gt;[X];
        move(@$cursor);
        delch();
        if (length($$line) &gt; $cursor-&gt;[X]) {
            substr($$line, $cursor-&gt;[X], 1) = '';
        }
    } else {
        my $y = $self-&gt;current_y;
        if ($y &gt; 0) {
            if (length($self-&gt;{code}-&gt;[$y - 1]) + length($$line) 
                &lt;= MAX_COLUMN
               ) {
                $cursor-&gt;[Y]--;
                $cursor-&gt;[X] = length($self-&gt;{code}-&gt;[$y - 1]);
                $self-&gt;{code}-&gt;[$y - 1] .= $$line;

                # perl can't splice shared arrays!
                #splice(@{$self-&gt;{code}}, $y, 1);
                my @code = @{$self-&gt;{code}};
                my @head = @code[0 .. $y - 1];
                my @tail = @code[$y+1 .. $#code];
                @{$self-&gt;{code}} = (@head, @tail);
        
                $self-&gt;redraw();
            }
        }
    }
}

##

sub key__delete {
    my $self = shift;
    my $cursor = $self-&gt;{cursor};
    my $line = $self-&gt;current_line;
    
    if ($cursor-&gt;[X] &lt; length($$line) ) {
        delch();
        substr($$line, $cursor-&gt;[X], 1) = '';
    } else {
        my $y = $self-&gt;current_y;
        if ($y &lt; scalar(@{$self-&gt;{code}})) {
            if (length($self-&gt;{code}-&gt;[$y + 1]) + length($$line) 
                &lt;= MAX_COLUMN
               ) {
                $$line .= $self-&gt;{code}-&gt;[$y + 1];

                # perl can't splice shared arrays!
                #splice(@{$self-&gt;{code}}, $y + 1, 1);
                my @code = @{$self-&gt;{code}};
                my @head = @code[0 .. $y];
                my @tail = @code[$y+2 .. $#code];
                @{$self-&gt;{code}} = (@head, @tail);
        
                $self-&gt;redraw();
            }
        }
    }
}

##

sub key__ctrl_a {
    my $self = shift;
    my $cursor = $self-&gt;{cursor};
    $cursor-&gt;[X] = 0;
    move(@$cursor);
}

##

sub key__ctrl_e {
    my $self = shift;
    my $cursor = $self-&gt;{cursor};
    $cursor-&gt;[X] = length($self-&gt;{code}-&gt;[$self-&gt;current_y] or '');
    move(@$cursor);
}

##

sub key__ctrl_k {
    my $self = shift;
    my $cursor = $self-&gt;{cursor};

    clrtoeol();
    my $line = $self-&gt;current_line;
    if (length($$line) == 0) {
        deleteln();
        my $y = $self-&gt;current_y;
    
        # perl can't splice shared arrays!
        #splice(@{$self-&gt;{code}}, $y, 1);
        my @code = @{$self-&gt;{code}};
        my @head = @code[0 .. $y - 1];
        my @tail = @code[$y + 1 .. $#code];
        @{$self-&gt;{code}} = (@head, @tail);
    } elsif (length($$line) &gt; $cursor-&gt;[X]) {
        $$line = substr($$line, 0, $cursor-&gt;[X])
    }
}

##

sub key__ctrl_l {
    my $self = shift;

    clear();
    $self-&gt;redraw();
    refresh();
}

##

sub key__ctrl_m {
    my $self = shift;
    my $cursor = $self-&gt;{cursor};
    my $line = $self-&gt;current_line;
    my $cut = '';
    if ($cursor-&gt;[X] &lt; length($$line)) {
        $cut = substr($$line, $cursor-&gt;[X]);
        substr($$line, $cursor-&gt;[X]) = '';
    }
    
    $cursor-&gt;[Y]++;
    $cursor-&gt;[X] = 0;

    my $y = $self-&gt;current_y;

    # perl can't splice shared arrays!
    #splice(@{$self-&gt;{code}}, $y, 0, $cut);
    my @code = @{$self-&gt;{code}};
    my @head = @code[0 .. $y - 1];
    my @tail = @code[$y .. $#code];
    @{$self-&gt;{code}} = (@head, $cut, @tail);

    $self-&gt;redraw();
}

##

sub key__up { 
    my $self = shift;
    my $cursor = $self-&gt;{cursor};

    if ($cursor-&gt;[Y] &gt; 0) {
        --$cursor-&gt;[Y];
    } else {
        if ($self-&gt;{yscroll}) {
            scrl(-1);
            $self-&gt;{yscroll}--;
            $self-&gt;redraw();
        }
    }

    my $max = length($self-&gt;{code}-&gt;[$self-&gt;current_y] or '');
    $cursor-&gt;[X] = $max if $cursor-&gt;[X] &gt; $max;

    move(@$cursor);
}

##

sub key__down { 
    my $self = shift;
    my $cursor = $self-&gt;{cursor};

    if ($self-&gt;current_y &lt; scalar(@{$self-&gt;{code}})) {
        my ($maxy, $maxx);
        getmaxyx(stdscr(), $maxy, $maxx);
        if ($cursor-&gt;[Y] &gt;= $maxy -1) {
            scrl(1);
            $self-&gt;{yscroll}++;
            $self-&gt;redraw();
        } else {
            ++$cursor-&gt;[Y] if $cursor-&gt;[Y] &lt; (MAX_COLUMN - 1);
        
            my $max = length($self-&gt;{code}-&gt;[$self-&gt;current_y] or '');
            $cursor-&gt;[X] = $max if $cursor-&gt;[X] &gt; $max;
            move(@$cursor);
        }
    }
}

##

sub key__left { 
    my $self = shift;
    my $cursor = $self-&gt;{cursor};
    if ($cursor-&gt;[X] &gt; 0) {
        --$cursor-&gt;[X] 
    } elsif ($cursor-&gt;[Y] &gt; 0) {
        $cursor-&gt;[Y]--;
        $cursor-&gt;[X] = length($ {$self-&gt;current_line});
    }
    move(@$cursor);
}

##

sub key__right { 
    my $self = shift;
    my $cursor = $self-&gt;{cursor};
    ++$cursor-&gt;[X];
    if ($cursor-&gt;[X] &gt; length($ {$self-&gt;current_line})) {
        $cursor-&gt;[X] = 0;
        $cursor-&gt;[Y]++;
    }
    move(@$cursor);
}

##

sub key__f10 {
    my $self = shift;
    $self-&gt;{shot} ||= 0;
    my $code = join("\n", @{$self-&gt;{code}});
    my $fn = "/yaxu/shots/$$-" . $self-&gt;{shot}++;
    open(FH, "&gt;$fn")
      or die "couldn't open shotfile";
    print(FH $code);
    print(FH "\n");
    addstr(10, 10, "saved $fn");
    close(FH);
}

##

sub key__tab {
    my $self = shift;
    
    my $current_line = $self-&gt;current_line;
    
    # do something clever here
    
    my $insert = $self-&gt;{cursor}-&gt;[X] % 4;
    if ($insert == 0) {
        $insert = 4;
    }
    for (my $c = 0; $c &lt; $insert; ++$c) {
        $self-&gt;add(' ');
    }
}

##

sub crash {
    endwin();
    die(shift);
}

##

1;


package Sandbox;

# (c) Alex McLean 2004/02/29

use strict;

use threads;
use threads::shared;

use Time::HiRes qw/ sleep time /;

use Audio::Beep;

use vars qw/$use_osc/;

BEGIN {
    eval {
        require Audio::OSC::Client;
        $use_osc = 1;
    };
    if ($@) {
        $use_osc = 0;
    }
}

##

sub _new {
    my ($pkg, $p) = @_;
    $p ||= {};
    my $self = bless($p, $pkg);
    $self-&gt;_init();
    return($self)
}

##

sub _init {
    my $self = shift;
    
    # variables shared with the running program
    my $reload : shared;
    my $modification : shared;
    
    $self-&gt;{reload} = \$reload;
    $self-&gt;{modification} = \$modification;
    $self-&gt;{bangs} = 0;
    $self-&gt;{buffer_time} = 0.05;
    $self-&gt;bpm(140 * 4);
    $modification = 1;
    
    if ($use_osc) {
        $self-&gt;_init_osc;
    }
    else {
        die;
    }
    
    $self-&gt;init if $self-&gt;can('init');
}

##

sub _init_osc {
    my $self = shift;
    my $osc = 
      Audio::OSC::Client-&gt;new(Host =&gt; 'localhost', Port =&gt; 57120)
          or die "failed to connect to SuperCollider\n";
    $self-&gt;{osc} = $osc;
}

##

sub new_thread {
    my $self = shift;
    
    if ($ENV{SPREAD}) {
        threads-&gt;create(sub {$self-&gt;spread_event_loop} );
    } else {
        # internal clock
        threads-&gt;create(sub {$self-&gt;event_loop} );
    }
}

##

sub event_loop {
    my $self = shift;
    close(STDERR);
    open(STDERR, '&gt;sandbox.err');
    close(STDOUT);
    close(STDIN);
    my $sleep;
    my $start = time();
    my $bpm = $self-&gt;bpm;
    my $interval = 60 / $bpm;
    $self-&gt;_do_bang;
    my $bangs = 1;
    while (1) {
        my $new = $self-&gt;bpm;
        if ($new and $new != $bpm) {
            $start = $start + ($bangs * $interval);
            $bpm = $new;
            $interval = 60 / $bpm;
            $bangs = 0;
        }
    
        # what time the new bang should be - start time plus number of
        # ticks so far * amount of time per bang
        my $seconds_in = ($start
                          + ($bangs * $interval)
                         );
        # how long til the new bang?
        $sleep = ($seconds_in - time());
        if ($sleep &gt; $interval) {
            # Oops, haven't had this tick yet, sleep until it's time
            $sleep -= $interval;
        } else {
            $self-&gt;{now} = $seconds_in;
            $self-&gt;_do_bang;
            $self-&gt;{bangs} = $bangs++;
        }
        sleep($sleep) unless $sleep &lt;= 0;
    }
}

##

sub spread_event_loop {
    require YAML;
    require Spread::Session;
    
    my $self = shift;
    
    my $start_mod = 4;

    my ($start, $point, $ticks_per_minute, $ticks, @changes);
    
    close(STDERR);
    open(STDERR, '&gt;sandbox.err');
    close(STDOUT);
    close(STDIN);

    my $spread = $self-&gt;{spread} = 
      Spread::Session-&gt;new(MESSAGE_CALLBACK =&gt; 
                           sub {
                               my ($command, $p) = YAML::Load($_[0]-&gt;{BODY});
                               if ($command eq 'ticks_per_minute') {
                                   push(@changes, $p);
                                   @changes = 
                                     sort{$a-&gt;{ticks} &lt;=&gt; $b-&gt;{ticks}}
                                       @changes;
                               } else {
                                   my $func = "on__$command";
                                   if ($self-&gt;can($func)) {
                                       $self-&gt;$func($_[0], $p);
                                   }
                               }
                           },
                          )
          or die;
    
    $spread-&gt;publish('#tm#localhost', YAML::Dump('new_listener'));

    while (not @changes) {
        $spread-&gt;poll();
        $spread-&gt;receive();
    }
    $spread-&gt;subscribe('ticks_per_minute');
    $spread-&gt;subscribe('share');

    my $change = shift @changes;
    ($start, $ticks_per_minute, $ticks) = ($change-&gt;{start},
                                           $change-&gt;{ticks_per_minute},
                                           $change-&gt;{ticks},
                                          );
    my $time = time();
    if ($start &gt;= $time) {
        sleep($start - $time);
    }
    
    $point = $start;
    
    my $started = 0;
    while (not $started) {
        if (($ticks % $start_mod) == 0) {
            $started = 1;
        }
    
        $ticks++;
        my $tick_seconds = ($ticks / $ticks_per_minute) * 60;
        $point = $tick_seconds + $start;
        my $sleep = $point - time();
        sleep($sleep) if $sleep &gt; 0;
    }
    
    my $bangs_per_tick = ($self-&gt;{bangs_per_tick} ||= 4);
    my $bpm = $bangs_per_tick * $ticks_per_minute;
    my $bangs = 0;
    my $bangs_since_change = 0;
    $start = $point;

    while (1) {
        $self-&gt;{now} = $point;
        $self-&gt;_do_bang();
    
        if ($spread-&gt;poll()) {
            $spread-&gt;receive(1);
        }
    
        # --
        # sleep till next bang
    
        $self-&gt;{bangs} = $bangs++;
        $bangs_since_change++;
    
        # Recalculate from the start each time so we don't collect errors
        my $bang_seconds = ($bangs_since_change / $bpm) * 60;

        $point = $bang_seconds + $start;    

        my $sleep = $point - time();
        sleep($sleep) if $sleep &gt; 0;
    
        if (($bangs % $bangs_per_tick) == 0) {
            $self-&gt;{ticks} = ++$ticks;
            if (@changes and ($changes[0]-&gt;{ticks} &lt;= $ticks)) {
                my $change = shift @changes;
                if ($ticks != $change-&gt;{ticks}) {
                    warn("processed a ticks_per_minute change " 
                         . ($ticks - $change-&gt;{ticks}) 
                         . " ticks too late.\n"
                        );
                }

                $start = $point;
                $ticks_per_minute = $change-&gt;{ticks_per_minute};
                $ticks = 0;
                $bpm = $bangs_per_tick * $ticks_per_minute;
                $bangs_since_change = 0;
            }
        }
    }
}

##

sub say {
    my ($self, $command, $p) = @_;
    
    $self-&gt;{spread}-&gt;publish('share', YAML::Dump($command, $p));
}

##

sub send_bpm {
    my ($self, $ticks_per_minute) = @_;
    
    $self-&gt;{spread}-&gt;publish('ticks_per_minute', 
                             YAML::Dump('ticks_per_minute', 
                                        {ticks_per_minute =&gt; $ticks_per_minute,
                                         ticks =&gt; $self-&gt;{ticks} + 2,
                                        }
                                       )
                            );
}

##

sub _do_bang {
    my $self = shift;
    if (__PACKAGE__-&gt;can('bang')) {
        eval {
            $self-&gt;bang();
        };
        if ($@) {
            print(STDERR $@);
            $self-&gt;reload(1);
        }
    }
    if ($self-&gt;reload) {
        if ($self-&gt;interpret) {
            $self-&gt;reload(0);
        }
    }
}

##

sub code {
    my $self = shift;
    if (not $self-&gt;{code}) {
        my @code;
        share(@code);
        $self-&gt;{code} = (\@code);
    }
    return($self-&gt;{code});
}

##

sub regex {
    my ($self, $regex, $replacement) = @_;
    
    return unless defined $replacement;
    
    my $code = join("\n", @{$self-&gt;code});
    
    $code =~ s/$regex/$replacement/s;
    
    @{$self-&gt;code} = split("\n", $code);
}

##

sub modified {
    my $self = shift;
    $ {$self-&gt;{modification}}++;
}

##

sub play {
    my $self = shift;
    my ($num, $gain, $pan, $formfreq, $bwfreq, $ts, $offset, $crackle, $browndel, $env);
    
    if (ref($_[0])) {
        ($num, $gain, $pan, $formfreq, $bwfreq, 
         $ts, $offset, $crackle, $browndel, $env) = 
           map {$_[0]-&gt;{$_}}
             qw{num gain pan formfreq bwfreq ts offset crackle browndel env};
    } else {
        ($num, $formfreq, $bwfreq, 
         $ts, $offset, $pan, $gain, $crackle, $browndel, $env) = @_;
    }
    
    $formfreq ||= 10;
    $bwfreq ||= 0;
    
    $pan = 0.5 if not defined $pan;

    $offset ||= 0;
    
    $num ||= 160;
    $browndel ||= 0;
    
    if (not defined $env) {
        $env = 1;
    }
    
    my $osc = $self-&gt;{osc};

    $gain ||= 100;
    my ($lgain, $rgain) = ($gain, $gain);
    if ($pan &gt; .5) {
        $lgain *= (1 - (($pan - .5) * 2));
    } elsif ($pan &lt; 0.5) {
        $rgain *= ($pan * 2);
    }
    $crackle ||= 10;
    $ts ||= 20;
    
    $osc-&gt;send(['#bundle', 
                $self-&gt;{now} + $self-&gt;{buffer_time} + $offset,
                ['/play',
                 's', 'on',
                 'i', $num, 
                 'i', $formfreq,
                 'i', $bwfreq,
                 'i', $ts,
                 'i', $lgain,
                 'i', $rgain,
                 'i', $crackle,
                 'i', $browndel,
                 'i', $env      
                ]
               ]
              );
}

##

sub trigger {
    my $self = shift;
    my ($sample, $gain, $offset, $pan, $crackle, $noise, $ts, $browndel, $rate, $env);
    
    if (ref($_[0])) {
        ($sample, $gain, $offset, $pan, $crackle, $noise, $ts, $browndel, $rate, $env) = 
          map {$_[0]-&gt;{$_}}
            qw{ sample gain offset pan crackle noise ts browndel rate env };
    } else {
        ($sample, $gain, $offset, $pan, $crackle, $noise, $ts, $browndel, $rate, $env) = @_;
    }
    
    $pan = 0.5 if not defined $pan;

    $offset ||= 0;

    $noise ||= 0;
    $browndel ||= 0;
    $rate ||= 100;
    $env ||= 0;
    
    return unless $sample;
    
    my $osc = $self-&gt;{osc};

    $gain ||= 100;
    my ($lgain, $rgain) = ($gain, $gain);
    if ($pan &gt; .5) {
        $lgain *= (1 - (($pan - .5) * 2));
    } elsif ($pan &lt; 0.5) {
        $rgain *= ($pan * 2);
    }
    $crackle ||= 10;
    $ts ||= 20;
    
    if ($sample !~ m,^/,) {
        $sample = '/yaxu/samples/' . $sample; 
    }

    if ($sample =~ m,^(.+?/)(\d+)$,) {
        my ($dir, $number) = ($1, $2);
        my $samples = $self-&gt;{sample_cache}-&gt;{$sample};
        if (not $samples) {
            opendir(DIR, $dir)
              or return;
            $samples = 
              $self-&gt;{sample_cache}-&gt;{$sample} =
                [grep {/\.[Ww][Aa][Vv]$/} 
                 readdir(DIR)
                ];

            closedir(DIR);
        }
        return unless @$samples;
        $sample = $dir . $samples-&gt;[$number % @$samples];
    }

    $osc-&gt;send(['#bundle',
                $self-&gt;{now} + $self-&gt;{buffer_time} + $offset,
                ['/trigger',
                 's', 'on',
                 's', $sample, 
                 'i', $lgain,
                 'i', $rgain,
                 'i', $crackle,
                 'i', $noise,
                 'i', $ts,
                 'i', $browndel,
                 'i', $rate,
                 'i', $env
                ]
               ]
              );
}

##

sub interpret {
    my $self = shift;
    my $result;
    my $code = join("\n", @{$self-&gt;code});
    no warnings 'redefine';
    
    eval("package Sandbox::Test; $code");
    if ($@) {
        print(STDERR $@);
    } elsif (not Sandbox::Test-&gt;can('bang')) {
        eval("test failed - no bang!");
    } else {
        eval("package Sandbox; $code");
        $result = 1;
    }
    
    if ($self-&gt;can('on_interpret')) {
        $self-&gt;on_interpret;
    }
    return($result);
}

##

sub reload {
    return(@_ &gt; 1 ? ($ {$_[0]-&gt;{reload}} = $_[1]) : $ {$_[0]-&gt;{reload}});
}

##

sub modification {
    return(@_ &gt; 1 
           ? ($ {$_[0]-&gt;{modification}} = $_[1]) 
           : $ {$_[0]-&gt;{modification}}
          );
}

##

sub bpm {
    my $self = shift;
    if (@_) {
        $self-&gt;{bpm} = shift;
    }
    return($self-&gt;{bpm});
}

##

sub sin {
    my $self = shift;
    my $div = shift;
    $div ||= 1;
    ((CORE::sin($self-&gt;{bangs} / $div) + 1) / 2)
}

##

1;
</pre></body></html>