package Emu10K1;

use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

require Exporter;
require DynaLoader;
require AutoLoader;

@ISA = qw(Exporter DynaLoader);
# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
@EXPORT = qw(
);
$VERSION = '0.01';

bootstrap Emu10K1 $VERSION;

# Preloaded methods go here.

my $maxhex = 0xffffffff;
my $maxposval = 0x7fffffff;
my $delay_factor = 48000*0x800;

sub getGprDelay {
  my $val = getGpr(@_);
  return defined $val ? $val/$delay_factor : undef;
}

sub getGprFrac {
  my $val = getGpr(@_);
  defined $val or return undef;
  return $val <= $maxposval ? $val/$maxposval : (($val-$maxhex)-1)/$maxposval;
}

sub setGprDelay {  
  my ($patch,$gpr,$value) = @_;
  return setGpr($patch,$gpr,int($value*48000*0x800));
}

sub setGprFrac {  
  my ($patch,$gpr,$value) = @_;
  return setGpr($patch,$gpr,int($value*$maxposval));
}

sub setGpr {
  my ($patch,$gpr,$value) = @_;
  for( $value )
  {
    # fractional value
    /^#?(-?[\d]*\.[\d]*)$/ and return setGprFrac($patch,$gpr,$1);
    # delay in seconds
    /^&(.*)$/ and return setGprDelay($patch,$gpr,$1);
  }
  # else assume int value
  return setGprInt($patch,$gpr,$value);
}

# loadPatchAs(FILE,PATCHNAME,LINE_NAMES [,"-"])
#    Use "-" to specify placement at tail of patch list.
# 
sub loadPatchAs {
  my ($file,$name,@lines) = @_;
  # parse line names into indices
  my @lineindex;
  my ($placement,$io) = (0,0);
  for( @lines )
  {
    /^-$/ and do { $placement=1; next; };
    my $i; 
    if( ($i = getInputLine($_)) >=0 ) {
      $io |= 2;
    } elsif( ($i = getOutputLine($_)) >=0 ) {
      $io |= 1;
    } elsif( ($i = getStereoInputLine($_)) >=0 ) {
      $io |= 2;
      push @lineindex,$i++;
    } elsif( ($i = getStereoOutputLine($_)) >=0 ) {
      $io |= 1;
      push @lineindex,$i++;
    } else {
      print STDERR "loadPatchAs($file,$name): illegal line name \"$_\"\n";
      return 0;
    }    
    push @lineindex,$i;
  }
  # check for validity
  unless( @lineindex ) {
    print STDERR "Emu10K1::loadPatchAs($file,$name): no lines specified\n";
    return 0;
  }
  # check for consistency
  if( $io == 3 ) {
    printf STDERR "Emu10K1::loadPatchAs($file,$name,%s): only input xor output lines should be specified\n",
                    join(",",@lines);
    return 0;
  }
  $io--;
  # call the xsub
  print join(" ",@lineindex),"\n";
  getdebug() and printf STDERR "Emu10K1::loadPatchAs($file,$name,%s): ",join(",",@lines);
  return attach_patch($file,$name,$io,$placement,@lineindex);
}

sub loadPatch {
  my ($file,@lines) = @_;
  return loadPatchAs($file,"",@lines);
}


# Autoload methods go after =cut, and are processed by the autosplit program.

1;
__END__
# Below is the stub of documentation for your module. You better edit it!

=head1 NAME

Emu10K1 - Perl extension for managing the emu10k1 DSP/driver

=head1 SYNOPSIS

use Emu10K1;

# loads patch, attaches to Pcm L (at start of patch chain)
Emu10K1::loadPatch("mypatch.bin","Pcm L");
# loads a 2-line patch, attaches to Pcm
Emu10K1::loadPatch("mypatch_2.bin","Pcm L","Pcm R");
# the above can also be written as
Emu10K1::loadPatch("mypatch_2.bin","Pcm");

# Use "-" to attach the patch at the END of the chain
Emu10K1::loadPatch("mypatch_2.bin","Pcm","-");

# loads patch and overrides the default patch name
Emu10K1::loadPatchAs("mypatch_2.bin","MyPatchName","Pcm");

# unload patch
Emu10K1::unloadPatch("MyPatchName");

# prints list of active patches (in "emu-dspmgr -u" format)
Emu10K1::printPatches("MyPatchName");

# sets value of a control GPR (32-bit integer)
Emu10K1::setGpr("MyPatchName","some_control",0x40000000); 
# if "." is present in the argument, it is interpreted as a fractional value
Emu10K1::setGpr("MyPatchName","some_control",0.5);
Emu10K1::setGpr("MyPatchName","some_control","1.0");
# "#" forces argument to be interpreted as a fractional value
Emu10K1::setGpr("MyPatchName","some_control","#1");
# "&" forces argument to be interpreted as a time interval 
Emu10K1::setGpr("MyPatchName","delay_cntrol","&0.05");   # 50 msec

# or use explicit calls for setting integer, frac and time values:
Emu10K1::setGprInt("MyPatchName","some_control",$myval);
Emu10K1::setGprFrac("MyPatchName","some_control",$myval);
Emu10K1::setGprDelay("MyPatchName","delay_control",$myval); 

# returns current value of control GPR, as 32-bit integer
$val = Emu10K1::getGpr("MyPatchName","some_control");
# or as fraction
$val = Emu10K1::getGprFrac("MyPatchName","some_control");
# or as time value
$val = Emu10K1::getGprDelay("MyPatchName","some_control");

# defers updates of control GPRs (see below)
Emu10K1::deferGprUpdates;
# loads deferred GPR values into DSP
Emu10K1::dspLoad;

# stops the DSP
Emu10K1::dspStop;
# starts the DSP
Emu10K1::dspStart;

# returns the error code for the last operartion (0 on success)
$errcode = Emu10K1::errcode;

# sets the debug level for status messages
Emu10K1::debug(1);  # enable messages
Emu10K1::debug(0);  # disable messages
>

=head1 DESCRIPTION

The Emu10K1 module manages the Linux emu10k1 driver. In functionality, it is
similar to the L<emu-dspmgr(1)> utility. See SYNOPSIS above for a list of
fuunctions.

Note that the setGpr() functions will normally update the DSP with the new GPR
value immediately. This is bad when setting many GPRs at once, since it
significantly slows down the proces and/or produces unpleasant pops and clicks
in the audio output. Instead, you can do a batch-update of multiple GPRs as
follows:

  Emu10K1::deferGprUpdates;
  Emu10K1::setGpr("MyPatchName","control_1",$val1);

  I<(more calls to setGpr())>

  Emu10K1::setGpr("MyPatchName","control_n",$valn);
  Emu10K1::dspLoad;

A call to deferGprUpdates() puts Emu10K1 into defferred-update mode. In this
mode, setGpr() calls do not update the DSP. A call to dspLoad() then updates the DSP
with all the deferred values at once, and exits deferred-update mode. 

=head1 SEE ALSO

perl(1), L<emu-dspmgr(1)>

=head1 BUGS

Add/remove route functions not yet implemented.

Device names "/dev/dsp" and "/dev/mixer" are hardcoded.

=head1 AUTHOR

Oleg Smirnov

=cut
