#!/usr/bin/perl
# 
# Convert Ensoniq EPS instrument files to Akai S3000 programs/samples.
# Copyright (c) 1997, Hiroyuki Ohsaki.
# All rights reserved.
# 
# $Id: eps2akai,v 1.4 1998/07/11 16:45:41 oosaki Exp ohsaki $
# 

# 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., 675 Mass Ave, Cambridge, MA 02139, USA.

use Synth::AkaiSample;
use Synth::EpsSample;
use File::Basename;
use Getopt::Std;
use strict;

my $EPS_KEY_OFFSET = 21;
my %RATE_TABLE = (0  => 48000, 1  => 44100, 2  => 31250);

my %SAMPLE_NAME = ();
my %SAMPLE_NAME_COUNT = ();

sub convert_common {
    my %common = @_;

    (name      => $common{name},
     midi_prog => $common{midi_prog} - 1,
     midi_chan => $common{midi_chan},
     low_key   => $common{low_key},
     high_key  => $common{high_key});
}

sub convert_program_name {
    my($name, $count) = @_;

    $name =~ s/[^0-9 A-Za-z\#.+-]/ /g;
    $name =~ s/ +/ /g;
    $name = substr($name, 0, 10) if (length($name) > 10);
    sprintf '%-10s-%1d', $name, $count;
}

sub convert_sample_name {
    my($name, $count) = @_;

    $name =~ s/[^0-9 A-Za-z\#.+-]/ /g;
    $name =~ s/ +/ /g;
    $name = substr($name, 0, 12) if (length($name) > 12);

    # if sample name is already used, add '-2', '-3', ... at the end
    # of the sample name.
    if (!$SAMPLE_NAME{$count}) {
	$SAMPLE_NAME_COUNT{$name}++;
	if ($SAMPLE_NAME_COUNT{$name} >= 2) {
	    $_ = '-' . $SAMPLE_NAME_COUNT{$name};
	    $SAMPLE_NAME{$count} = substr($name, 0, 12 - length($_)) . $_;
	} else {
	    $SAMPLE_NAME{$count} = $name;
	}
    }
    $SAMPLE_NAME{$count};
}

sub convert_wave {
    my($eps, $akai, %wave) = @_;

    $wave{name} = convert_sample_name($wave{name}, $wave{number});
    $wave{rate} = $RATE_TABLE{$wave{rate}} ||
	int(exp(-log($wave{rate})) * 208333 / exp(-log(3)) + 0.5);
    $wave{loop_times} = 0;
    my $dataref = $eps->read_wave_data($wave{number});

    if ($wave{loop_mode} == 2) { # uni-directional loop
	$wave{loop_times} = 9999;
    }
    if ($wave{loop_mode} == 3) { # bi-directional loop
	my $length = $wave{loop_end} - $wave{loop_start};
	my $head   = substr($$dataref, 0, $wave{loop_start} << 1);
	my $loop   = substr($$dataref, $wave{loop_start} << 1, $length << 1);
	my $loop_r = pack('v*', reverse(unpack('v*', $loop)));
	my $tail   = substr($$dataref, $wave{loop_end} << 1);
	$$dataref  = $head . $loop . $loop_r . $tail;
	$wave{size}      += $length;
	$wave{end}       += $length;
	$wave{loop_end}  += $length;
	$wave{loop_times} = 9999;
    }

    # transpose pitch if sample rate is other than 44100 Hz.
    my $tune = 12 * log($wave{rate} / 44100) / log(2);
    
    # change the order of byte allocation.
    $$dataref = pack('v*', unpack('n*', $$dataref));

    (name       => $wave{name},
     size       => $wave{size},
     rate       => $wave{rate},
     key        => $wave{key},
     tune       => $tune,
     start      => $wave{start},
     end        => $wave{end},
     loop_start => $wave{loop_end}, # not mistake
     loop_len   => $wave{loop_end} - $wave{loop_start},
     loop_times => $wave{loop_times},
     data       => $dataref,
     );
}

sub convert_keygroup {
    my($eps, $wave_no, %layer) = @_;

    my(%wave) = $eps->read_wave_header($wave_no);
    return () unless (exists $wave{name});

    (
     # keygroup common
     tune       => $wave{tune} * 1 / 128, # why?

     # keygroup zone
     name       => convert_sample_name($wave{name}, ($wave{copy} == 0)
				       ? $wave{number} : $wave{copy}), 
     low_vel    => $layer{low_vel},
     high_vel   => $layer{high_vel},
     vol_offset => ($wave{vol} - 127) >> 2,
     pan_offset => $wave{pan_frac} * 50 / 127,	# [-127:127] -> [50:-50]
     
     # more keygroup common
     pitch      => ($layer{pitch} == 1),
     );
}
     
sub convert_rate {
    my %wave = @_;
    
    $wave{size}       = $wave{size} >> 1;
    $wave{rate}       = $wave{rate} >> 1;
    $wave{tune}       -= 12;
    $wave{start}      = $wave{start} >> 1;
    $wave{end}        = $wave{end} >> 1;
    $wave{loop_start} = $wave{loop_start} >> 1;
    $wave{loop_len}   = $wave{loop_len} >> 1;

    # convert sample rate to half of the orginal rate
    my $dataref = $wave{data};
    my $newdata = '';
    for (0 .. $wave{size} - 1) {
	$newdata .= substr($$dataref, $_ << 2, 2);
    }
    $$dataref = '';
    $wave{data} = \$newdata;

    %wave;
}

sub convert_layer {
    my($eps, $akai, %layer) = @_;

    # create AKAI keygroup for each zone
    my $last_wave = 0;
    my %keygroup;
    my $key;
    for $key (0 .. 87) {
	my $current_wave = $layer{keymap}[$key];
	if ($current_wave != $last_wave) {
	    if ($last_wave != 0) {
		# write previous keygroup
		$keygroup{high_key} = $EPS_KEY_OFFSET + ($key - 1);
		$akai->write_keygroup(%keygroup);
	    }
	    if ($current_wave != 0) {
		# create a new keygroup
		%keygroup = convert_keygroup($eps, $current_wave, %layer);
		if (!exists $keygroup{name}) {
		    warn;
		    $last_wave = 0;
		    next;
		}
		$keygroup{low_key} = $EPS_KEY_OFFSET + $key;
	    }
	    $last_wave = $current_wave;
	}
    }
    if ($last_wave != 0) {	
	# write last keygroup
	$keygroup{high_key} = $EPS_KEY_OFFSET + (88 - 1);
	$akai->write_keygroup(%keygroup);
    }
}

sub usage {
    my $prog = basename($0);
    die <<EOF;
usage: $prog [-vl] [-d dir] file...
 -v       verbose mode
 -l       lower samle rate if instrument file > 4MB
 -d dir   create S3000 programs/samples in dir
EOF
}

getopts('vld:') || usage;
@ARGV || usage;

my $destdir = $::opt_d || '.';

my $file;
for $file (@ARGV) {
    # load EPS instrument
    my $eps = new Synth::EpsSample;
    print "-------- $file --------\n" if $::opt_v;
    $eps->load($file) || warn "$file: $!\n";
    
    # convert four patches
    my $n;
    for $n (0 .. 3) {
	my $akai = new Synth::AkaiSample;

	# convert common part
	my(%common) = $eps->read_common;
	my(%akai_common) = convert_common(%common);
	$akai_common{midi_prog} = $n;
	$akai->write_common(%akai_common);

	# convert EPS layers to AKAI keygroups
	my $layer;
	for $layer (0 .. 7) {
	    next unless ($common{patch_select}[$n] & (1 << $layer));
	    my %layer;
 	    if ($eps->read_layer_address($layer)) {
		%layer = $eps->read_layer($layer);
 	    } else {
 		%layer = $eps->read_layer(0);
 	    }
	    $eps->dump_layer(%layer) if $::opt_v;
	    convert_layer($eps, $akai, %layer);
	}
	$akai->{common}->{name}
	= convert_program_name($akai->{common}->{name}, $n + 1); 
	$akai->save_program($destdir);
	undef $akai;
    }

    # check instrument file size
    my $too_big = (-s $file) > 4 * 1024 * 1024;
    
    # convert all samples
    for $n (0 .. 127) {
	my $akai = new Synth::AkaiSample;

	# convert common part
	my(%common) = $eps->read_common;
	$akai->write_common(convert_common(%common));

	my(%wave) = $eps->read_wave_header($n);
	next unless (exists $wave{name});
	next unless ($wave{copy} == 0);
	$eps->dump_wave(%wave) if $::opt_v;
	%wave = convert_wave($eps, $akai, %wave);
	if ($too_big and $wave{rate} >= 44100) {
	    %wave = convert_rate(%wave);
	}
	$akai->save_sample($destdir, %wave);
	undef %wave;
	undef $akai;
    }
    undef $eps;
}

__END__

=head1 NAME

eps2akai - convert Ensoniq EPS instruments to Akai S3000 programs/samples

=head1 SYNOPSIS

  eps2akai [-v] [-d dir] file...

=head1 DESCRIPTION

This manual page documents B<eps2akai>.  This program converts Ensoniq
EPS instrument files to Akai S3000 program and samples.  In the
process of conversion, B<eps2akai> preserves various information in
the original instrument file such as MIDI channel number, key split,
velocity split, looping information, pans, transposition, tuning, etc.

B<eps2akai> creates many S3000 sample files (in .s3s format) for each
wave contained in the EPS instrument and four program files (in .s3p
format) for each patch select.  Usually, a file name of a sample file
is identical to the corresponding wave name.  For example, B<eps2akai>
makes the follwing files from an EPS instrument file
F<GRAND_PIANO.INS>.

  grand_pian-1.a3p
  grand_pian-2.a3p
  grand_pian-3.a3p
  grand_pian-4.a3p
  a4.a3s
  a3.a3s
  c6.a3s
  unnamed_ws.a3s
  unnamed_ws-2.a3s
  unnamed_ws-3.a3s

=head1 OPTIONS

=over 4

=item C<-v>

Verbose mode.  Print much information for debugging.

=item C<-d> directory

Specify the directory in which all program and sample files will be
written.  By default, the current directory is used.

=back

=head1 BUGS

Currently, the following parameters of the EPS instrument file are
(tried to be) converted:

=over 4    

=item - Patch name

=item - Wave name

=item - MIDI channel number

=item - Velocity range

=item - Key range

=item - Sample rate

=item - Play start/end

=item - Loop point/type

=item - Transpose/tuning

=item - Volume

=item - Pan

=back

I had no information about the disk and file formats of both Ensoniq
and Akai samplers, so that I have analyzed them by using the binary
file editor.  But I have no EPS samplers, so I have not compared
sounds generated by B<eps2akai> with original ones.  If you have both
Ensoniq and Akai samplers, please help me to tune parameters and to
write a code to convert other parameters like
filter/envelope/modulation.  

=head1 SEE ALSO

akaitools(1), akaiformat(1), akailist(1), akaimkdir(1), akairead(1),
akaiwrite(1), epslist(1), epsread(1), eps2akai(1), wav2akai(1),
any2akai(1), akaiconv(1) 

=head1 AUTHOR

Hiroyuki Ohsaki <oosaki@ics.es.osaka-u.ac.jp>

=cut

