The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
# -*- cperl -*-

# Makefile.PL - tastes great, less filling (than h2ph)
#
# Copyright (c) 2000 Cepstral LLC. All rights Reserved.
#
# This module is free software; you can redistribute it and/or modify
# it under the same terms as Perl itself.
#
# Written by David Huggins-Daines <dhd@cepstral.com>

use ExtUtils::MakeMaker;
use Getopt::Long;
use strict;

my %constants =
    (ioctls => [qw(
		   SNDCTL_DSP_RESET
		   SNDCTL_DSP_SYNC
		   SNDCTL_DSP_SPEED
		   SNDCTL_DSP_STEREO
		   SNDCTL_DSP_GETBLKSIZE
		   SNDCTL_DSP_SAMPLESIZE
		   SNDCTL_DSP_CHANNELS
		   SNDCTL_DSP_POST
		   SNDCTL_DSP_SUBDIVIDE
		   SNDCTL_DSP_SETFRAGMENT
		   SNDCTL_DSP_GETOSPACE
		   SNDCTL_DSP_GETISPACE
		   SNDCTL_DSP_NONBLOCK
		   SNDCTL_DSP_GETCAPS
		   SNDCTL_DSP_GETFMTS
		   SNDCTL_DSP_SETFMT
		   SNDCTL_DSP_GETTRIGGER
		   SNDCTL_DSP_SETTRIGGER
		   SNDCTL_DSP_GETIPTR
		   SNDCTL_DSP_GETOPTR
		   SNDCTL_DSP_MAPINBUF
		   SNDCTL_DSP_MAPOUTBUF
		   SNDCTL_DSP_SETSYNCRO
		   SNDCTL_DSP_SETDUPLEX
		   SNDCTL_DSP_GETODELAY

		   SNDCTL_DSP_GETCHANNELMASK
		   SNDCTL_DSP_BIND_CHANNEL
		   SNDCTL_DSP_PROFILE

		   SOUND_PCM_READ_RATE
		   SOUND_PCM_READ_CHANNELS
		   SOUND_PCM_READ_BITS
		   SOUND_PCM_READ_FILTER

		   SOUND_MIXER_READ_VOLUME
		   SOUND_MIXER_READ_RECSRC
		   SOUND_MIXER_READ_DEVMASK
		   SOUND_MIXER_READ_RECMASK
		   SOUND_MIXER_READ_STEREODEVS
		   SOUND_MIXER_READ_CAPS

		   SOUND_MIXER_WRITE_VOLUME
		   SOUND_MIXER_WRITE_RECSRC

                   SOUND_MIXER_INFO
		   SOUND_MIXER_AGC
		   SOUND_MIXER_3DSE
		   SOUND_MIXER_PRIVATE1
		   SOUND_MIXER_PRIVATE2
		   SOUND_MIXER_PRIVATE3
		   SOUND_MIXER_PRIVATE4
		   SOUND_MIXER_PRIVATE5
		   SOUND_MIXER_GETLEVELS
		   SOUND_MIXER_SETLEVELS

		   OSS_GETVERSION
		  )],

     mixer => [qw(
		  SOUND_MIXER_NRDEVICES
		  SOUND_MIXER_VOLUME
		  SOUND_MIXER_BASS
		  SOUND_MIXER_TREBLE
		  SOUND_MIXER_SYNTH
		  SOUND_MIXER_PCM
		  SOUND_MIXER_SPEAKER
		  SOUND_MIXER_LINE
		  SOUND_MIXER_MIC
		  SOUND_MIXER_CD
		  SOUND_MIXER_IMIX
		  SOUND_MIXER_ALTPCM
		  SOUND_MIXER_RECLEV
		  SOUND_MIXER_IGAIN
		  SOUND_MIXER_OGAIN
		  SOUND_MIXER_LINE1
		  SOUND_MIXER_LINE2
		  SOUND_MIXER_LINE3
		  SOUND_MIXER_DIGITAL1
		  SOUND_MIXER_DIGITAL2
		  SOUND_MIXER_DIGITAL3
		  SOUND_MIXER_PHONEIN
		  SOUND_MIXER_PHONEOUT
		  SOUND_MIXER_VIDEO
		  SOUND_MIXER_RADIO
		  SOUND_MIXER_MONITOR
		  SOUND_MIXER_NONE
		  SOUND_ONOFF_MIN
		  SOUND_ONOFF_MAX

		  SOUND_MIXER_RECSRC
		  SOUND_MIXER_DEVMASK
		  SOUND_MIXER_RECMASK
		  SOUND_MIXER_CAPS
		  SOUND_CAP_EXCL_INPUT
		  SOUND_MIXER_STEREODEVS
		  SOUND_MIXER_OUTSRC
		  SOUND_MIXER_OUTMASK
		 )],

     formats => [qw(
		   AFMT_QUERY
		   AFMT_MU_LAW
		   AFMT_A_LAW
		   AFMT_IMA_ADPCM
		   AFMT_U8
		   AFMT_S16_LE
		   AFMT_S16_BE
		   AFMT_S16_NE
		   AFMT_S8
		   AFMT_U16_LE
		   AFMT_U16_BE
		   AFMT_MPEG
		   AFMT_AC3
		)],

     caps => [qw(
		 DSP_CAP_REVISION
		 DSP_CAP_DUPLEX
		 DSP_CAP_REALTIME
		 DSP_CAP_BATCH
		 DSP_CAP_COPROC
		 DSP_CAP_TRIGGER
		 DSP_CAP_MMAP
		 DSP_CAP_MULTI
		 DSP_CAP_BIND
		)],

     bind => [qw(
		   DSP_BIND_QUERY
		   DSP_BIND_FRONT
		   DSP_BIND_SURR
		   DSP_BIND_CENTER_LFE
		   DSP_BIND_HANDSET
		   DSP_BIND_MIC
		   DSP_BIND_MODEM1
		   DSP_BIND_MODEM2
		   DSP_BIND_I2S
		   DSP_BIND_SPDIF
		)],

     misc => [qw(
		   PCM_ENABLE_INPUT
		   PCM_ENABLE_OUTPUT
		   APF_NORMAL
		   APF_NETWORK
		   APF_CPUINTENS)],
    );

my ($khdrs, $ohdrs);
GetOptions('kernel-headers=s' => \$khdrs, 'oss-headers=s' => \$ohdrs);

my @possible_hdrdirs;
if (defined $khdrs or defined $ohdrs) {
    @possible_hdrdirs = ((defined $khdrs ? "$khdrs/linux"  : ()), $ohdrs);
} else {
    @possible_hdrdirs = qw(/usr/local/include
			   /usr/local/include/sys
			   /usr/include
			   /usr/include/sys
			   /usr/include/linux
			   /usr/src/linux/include/linux);
}

use vars qw($SOUNDCARD_H);
foreach my $dir (@possible_hdrdirs) {
    if (-r "$dir/soundcard.h") {
	$SOUNDCARD_H = "$dir/soundcard.h";
	last;
    }
}

unless (defined($SOUNDCARD_H)) {
    local $" = "\n";
    print <<"EOF";
No OSS header files could be found in any of the following
directories:

@possible_hdrdirs

You can use the --kernel-headers option to specify the location of
your Linux kernel header files (e.g. /home/foo/linux/include), or
the --oss-headers option to specify a directory containing Open
Sound System header files.
EOF
    exit 1;
}

print "Finding constants and values, this may take a while...\n";

use vars qw($CC $NO_DASH_INCLUDE);
$CC = $ENV{CC} || 'cc';
$NO_DASH_INCLUDE = 0;

unlink 'conftest';
if (system($CC, '-include', $SOUNDCARD_H, 'conftest.c', '-o', 'conftest') != 0) {
    $NO_DASH_INCLUDE = 1;
    if (system($CC, 'conftest.c', '-o', 'conftest') != 0) {
	# Give up
	print <<"EOF";
Compiling test program:

$CC -i $SOUNDCARD_H conftest.c -o conftest
$CC conftest.c -o conftest

Failed with status $?.  You can change the default compiler by
setting the \$CC environment variable in your shell.
EOF
	exit 1;
    }
}
unlink 'conftest';

sub check_const {
    my @cmd;
    unlink 'get-constants.c';
    open TEMPL, "<get-constants.c.in" or die "Could not open get-constants.c.in: $!";
    open TEST, ">get-constants.c" or die "Could not open get-constants.c: $!";
    if ($NO_DASH_INCLUDE) {
	open H, "<$SOUNDCARD_H" or die "Could not open $SOUNDCARD_H: $!";
	while (<H>) {
	    print TEST $_;
	}
	close H;
	@cmd = ($CC, 'get-constants.c', '-o', 'get-constants');
    } else {
	@cmd = ($CC, '-include', $SOUNDCARD_H, 'get-constants.c',
		'-o', 'get-constants');
    }
    while (<TEMPL>) {
	if (m,/\* INSERT CONSTANTS HERE \*/,) {
	    foreach my $const (@_) {
		print TEST qq/{ "$const", $const },\n/;
	    }
	} else {
	    print TEST $_;
	}
    }
    close TEMPL;
    close TEST;

    if (my $pid = fork) {
	waitpid $pid, 0;
	if ($?) {
	    unlink 'get-constants.c', 'get-constants';
	    return undef;
	}
    } else {
	close STDERR;
	exec @cmd
	    or die "exec failed: $!";
    }

    my @ret;
    open PROG, './get-constants|' or die "Could not open pipe: $! $?";
    while (<PROG>) {
	chomp;
	my ($const, $val) = split /:/;
	push @ret, $val;
    }

    unlink 'get-constants.c', 'get-constants';
    return (@ret == 1) ? $ret[0] : @ret;
}

my (@constmap, %valid);
foreach my $t (keys %constants) {
    foreach my $c (@{$constants{$t}}) {
	{
	    print "Checking for $c... ";
	    local $|=1;
	}
	my $val = check_const($c);
	if (defined $val) {
	    print "$val.\n";
	    push @constmap, [$c => $val];
	    $valid{$t}{$c} = 1;
	} else {
	    print "not found.\n";
	}
    }
}

unlink 'OSS/Constants.pm';
mkdir 'OSS', 0755;
open CONFMOD, '>OSS/Constants.pm'
    or die "Could not open OSS/Constants.pm: $!";

# Random header stuff
print CONFMOD <<'EOP';
package Audio::OSS;
use strict;
use vars qw(@EXPORT_OK %EXPORT_TAGS @CONSTANTS);
push @EXPORT_OK, qw(
EOP

# Now the constant names
foreach (@constmap) {
    print CONFMOD "\t$_->[0]\n";
}

print CONFMOD <<'EOP';
	);
EOP

foreach my $t (keys %constants) {
    print CONFMOD "\$EXPORT_TAGS{'$t'} = [qw(\n";
    foreach my $c (@{$constants{$t}}) {
	print CONFMOD "\t$c\n" if $valid{$t}{$c};
    }
    print CONFMOD "\t)];\n";
}

print CONFMOD <<'EOP';
@CONSTANTS = (
EOP

# And their values
foreach (@constmap) {
    print CONFMOD "\t[ $_->[0] => $_->[1] ],\n";
}

# And some cruft to define them for us
print CONFMOD <<'EOP';
    );

sub make_const {
    my ($const, $val) = @_;

    no strict 'refs';
    *$const = sub () { $val };
}

foreach (@CONSTANTS) {
    make_const(@$_);
}
1;
EOP
close CONFMOD;

WriteMakefile(
	      NAME	   => 'Audio::OSS',
	      VERSION_FROM => 'OSS.pm',
	      C            => [], # NO MAKEMAKER, THAT'S WRONG
	      clean        => { FILES => 'conftest get-constants.c get-constants OSS/Constants.pm' },
	     );