### ----------------------------------------------------
### -- Audio::Encode::LameMP3
### -- Licenses: Artistic-2.0
### -- Authors: Jonathan Stowe <jns+git@gellyfish.co.uk>
### -- File: lib/Audio/Encode/LameMP3.rakumod
### ----------------------------------------------------
### -- Chunk 1 of 2
use v6;

=begin pod

=head1 NAME

Audio::Encode::LameMP3 - encode PCM data to MP3 using libmp3lame

=head1 SYNOPSIS

=begin code

    use Audio::Encode::LameMP3;
    use Audio::Sndfile;

    my $test-file = 't/data/cw_glitch_noise15.wav';

    my $sndfile = Audio::Sndfile.new(filename => $test-file, :r);
    my $encoder = Audio::Encode::LameMP3.new(bitrate => 128, quality => 3, in-samplerate => $sndfile.samplerate);

    my $out-file = 'encoded.mp3'.IO.open(:w, :bin);

    loop {
        my @in-frames = $sndfile.read-short(4192);
        my $buf = $encoder.encode-short(@in-frames);
        $out-file.write($buf);
        last if ( @in-frames / $sndfile.channels ) != 4192;
    }

    $sndfile.close();
    my  $buf = $encoder.encode-flush();
    $out-file.write($buf);
    $out-file.close;

=end code

See also the C<examples/> directory in the distribution.

=head1 DESCRIPTION

This module provides a simple binding to 'mp3lame',v0 an MP3 encoding library.

With this you can encode PCM data to MP3 at any bitrate or quality
supported by the lame library.

The interface is somewhat simplified in comparison to that of lame
and some of the esoteric or rarely used features may not be supported.

Because marshalling large arrays and buffers between Raku space and the
native world may be too slow for some use cases the interface provides
for passing and returning native CArrays (and their sizes) for the use
of other native bindings (e.g. L<Audio::Sndfile>, L<Audio::Libshout>) where
speed may prove important, which , for me at least, is quite a common
use-case.  The C<raku-lame-encode> example demonstrates this way of using
the interface.

=head2 METHODS

All of the encode methods below have multi variants that accept appropriately
shaped CArray arguments (along with the number of frames.)  With the C<:raw>
adverb they will return a C<RawEncode> sub-set which is defined as an Array
of two elements the first being a C<CArray[uint8]> containing the encoded
data and an C<Int> indicating how many items there are.  This is for ease of
interoperating with modules such as L<Audio::Sndfile> and L<Audio::Libshout>
and avoids the cost of marshalling to/from Raku Arrays where it is not needed.

=head3 method new

    method new(*%attributes) returns Audio::Encode::LameMP3

The constructor of objects of this type, this can be passed any of the
encoder parameters or id3 tags described below.  It will set some internal
defaults.

=head3 method init

    method init()

This initialises the encoder ready to start the encoding.  It may be called after
all the rquired parameters have been set, however it will be called for you when
the first encode method is called.  It is an error to attempt to set any encoding
parameters after C<init> has been called.

=head3 method init-bitstream

    method init-bitstream()

This can be used to re-initialise the encoder's internal state in order that it
can be re-used for the encoding of a new source (e.g. a new file.)  It should only
be called after C<encode-flush> has been called and the result of doing otherwise
is undefined.  Typically this could be used when streaming multiple files in sequence
to the the same stream endpoint for example, calling C<encode-flush> and C<init-bitstream>
between each file.

This is not necessary on the first initialisation as it is called by C<init()>

=head3 method lame-version

    method lame-version() returns Version

This returns a L<Version> object indicating the version of C<libmp3lame> that is
being used.

=head3 method encode-short

Encode the block of PCM data expressed as signed 16 bit integers to
MP3 returning the data as unsigned 8 bit integers.  The input data
can be provided either as separate channels or in interleaved form.
The multi variants allow the data to provided as Raku arrays or as
C<CArray[int16]> and the number of frames ( the number of frames is the
number left channel, right channel pairs.)

If the C<:raw> adverb is provided then the data will be returned as a
two element array containing a C<CArray[uint8]> and an C<Int> with the
number of elements in the array. Otherwise it will return a Raku Array
with the data.

Tests seem to demonstrate that this is the fastest of the encoding
methods, which is convenient as 16 bit PCM is probably the most common
format for general use (being that which is used on CDs.)


    multi method encode-short(@left, @right) returns Buf
    multi method encode-short(@frames) returns Buf
    multi method encode-short(@left, @right, :$raw!) returns RawEncode
    multi method encode-short(@frames, :$raw!) returns RawEncode
    multi method encode-short(CArray[int16] $left, CArray[int16] $right, Int $frames) returns Buf
    multi method encode-short(CArray[int16] $frames-in, Int $frames) returns Buf
    multi method encode-short(CArray[int16] $left, CArray[int16] $right, Int $frames, :$raw!) returns RawEncode
    multi method encode-short(CArray[int16] $frames-in, Int $frames, :$raw!) returns RawEncode

=head3 method encode-int

Encode the block of PCM data expressed as 32 bit integers to MP3 returning
the data as unsigned 8 bit integers.  The input data can be provided
either as separate channels or in interleaved form.  The multi variants
allow the data to provided as Raku arrays or as C<CArray[int32]> and
the number of frames ( the number of frames is the number left channel,
right channel pairs.)

If the C<:raw> adverb is provided then the data will be returned as a
two element array containing a C<CArray[uint8]> and an C<Int> with the
number of elements in the array. Otherwise it will return a Raku Array
with the data.

C<libmp3lame> doesn't provide the interleaved data option for this data
type so it is emulated in Raku code so it may be slower if used like that.

The C<libmp3lame> documentation suggests that the scaling of the integer
encoding may not be as good as for other data types, if you need to
use this data type you should test this and provide your own scaling
if necessary.

    multi method encode-int(@left, @right) returns Buf
    multi method encode-int(@frames) returns Buf
    multi method encode-int(@left, @right, :$raw!) returns RawEncode
    multi method encode-int(@frames, :$raw!) returns RawEncode
    multi method encode-int(CArray[int32] $left, CArray[int32] $right, Int $frames) returns Buf
    multi method encode-int(CArray[int32] $frames-in, Int $frames) returns Buf
    multi method encode-int(CArray[int32] $left, CArray[int32] $right, Int $frames, :$raw!) returns RawEncode
    multi method encode-int(CArray[int32] $frames-in, Int $frames, :$raw!) returns RawEncode

=head3 method encode-long

Encode the block of PCM data expressed as 64 bit integers to MP3 returning
the data as unsigned 8 bit integers.  The input data can be provided
either as separate channels or in interleaved form.  The multi variants
allow the data to provided as Raku arrays or as C<CArray[int64]> and
the number of frames ( the number of frames is the number left channel,
right channel pairs.)

If the C<:raw> adverb is provided then the data will be returned as a
two element array containing a C<CArray[uint8]> and an C<Int> with the
number of elements in the array. Otherwise it will return a Raku Array
with the data.

C<libmp3lame> doesn't provide the interleaved data option for this data
type so it is emulated in Raku code so it may be slower if used like that.

    multi method encode-long(@left, @right) returns Buf
    multi method encode-long(@frames) returns Buf
    multi method encode-long(@left, @right, :$raw!) returns RawEncode
    multi method encode-long(@frames, :$raw!) returns RawEncode
    multi method encode-long(CArray[int64] $left, CArray[int64] $right, Int $frames) returns Buf
    multi method encode-long(CArray[int64] $frames-in, Int $frames) returns Buf
    multi method encode-long(CArray[int64] $left, CArray[int64] $right, Int $frames, :$raw!) returns RawEncode
    multi method encode-long(CArray[int64] $frames-in, Int $frames, :$raw!) returns RawEncode

=head3 method encode-float

Encode the block of PCM data expressed as 32 bit floating point numbers
to MP3 returning the data as unsigned 8 bit integers.  The input data
can be provided either as separate channels or in interleaved form.
The multi variants allow the data to provided as Raku arrays or as
C<CArray[num32]> and the number of frames ( the number of frames is the
number left channel, right channel pairs.)

If the C<:raw> adverb is provided then the data will be returned as a
two element array containing a C<CArray[uint8]> and an C<Int> with the
number of elements in the array. Otherwise it will return a Raku Array
with the data.

    multi method encode-float(@left, @right) returns Buf
    multi method encode-float(@frames) returns Buf
    multi method encode-float(@left, @right, :$raw!) returns RawEncode
    multi method encode-float(@frames, :$raw!) returns RawEncode
    multi method encode-float(CArray[num32] $left, CArray[num32] $right, Int $frames) returns Buf
    multi method encode-float(CArray[num32] $frames-in, Int $frames) returns Buf
    multi method encode-float(CArray[num32] $left, CArray[num32] $right, Int $frames, :$raw!) returns RawEncode
    multi method encode-float(CArray[num32] $frames-in, Int $frames, :$raw!) returns RawEncode

=head3 method encode-double

Encode the block of PCM data expressed as 64 bit floating point numbers
to MP3 returning the data as unsigned 8 bit integers.  The input data
can be provided either as separate channels or in interleaved form.
The multi variants allow the data to provided as Raku arrays or as
C<CArray[num64]> and the number of frames ( the number of frames is the
number left channel, right channel pairs.)

If the C<:raw> adverb is provided then the data will be returned as a
two element array containing a C<CArray[uint8]> and an C<Int> with the
number of elements in the array. Otherwise it will return a Raku Array
with the data.

    multi method encode-double(@left, @right) returns Buf
    multi method encode-double(@frames) returns Buf
    multi method encode-double(@left, @right, :$raw!) returns RawEncode
    multi method encode-double(@frames, :$raw!) returns RawEncode
    multi method encode-double(CArray[num64] $left, CArray[num64] $right, Int $frames) returns Buf
    multi method encode-double(CArray[num64] $frames-in, Int $frames) returns Buf
    multi method encode-double(CArray[num64] $left, CArray[num64] $right, Int $frames, :$raw!) returns RawEncode
    multi method encode-double(CArray[num64] $frames-in, Int $frames, :$raw!) returns RawEncode

=head3 method encode-flush

This returns (flushes) the last encoded data and should always be called
after the last PCM data for a particular stream has been encoded. It
may return up to 8000 bytes of data.

If the C<:nogap> adverb is supplied then the padding at the end will
be adjusted such that a subsequent track (or file) will appear to
play seamlessly, typically this will be used with C<init-bitstream>
which should be called after this and before sending further PCM data
to create an (apparently) gapless stream.

If the C<:raw> adverb is provided then the data will be returned as a
two element array containing a C<CArray[uint8]> and an C<Int> with the
number of elements in the array. Otherwise it will return a Raku Array
with the data.

    multi method encode-flush() returns Buf
    multi method encode-flush(:$nogap!) returns Buf
    multi method encode-flush(:$raw!) returns RawEncode
    multi method encode-flush(:$nogap!, :$raw!) returns RawEncode

=head2 CONFIGURATION ATTRIBUTES

All of those can be supplied to the constructor or can be set as attributes
on a constructed object before it is initialised.  Some are more useful
than others as the library provides sensible defaults.

The lame library provides a wider range of settable parameters that are not
exposed as I either don't understand them or they don't seem to be useful.

If of course you need a particular parameter, please feel free to request
it to be added - most of the native stubs are there, just not exposed as
methods.

The first four are the most likely to be used in most code.

=head3 in-samplerate

This should reflect the samplerate of the input PCM data. The default
is 44100.  If this is not set correctly the speed of the playback of
the encoded data will be incorrect.

=head3 bitrate

This is the playback bitrate of the encoded data. It should be a value
understood by both lame and the target players,  values that are fairly
universally understood are 64, 128, 192 and 320.

=head3 quality

This is an integer value between 0 and 9 that indicates the quality
(and hence the speed) of the encoding, where 0 is the best (and slowest)
and 9 is the least good and fastest.  The default is 5.  Most applications
will typically use a value between 3 and 7 but your ears and patience might
better than mine.

=head3 mode

This is a value of the C<enum> L<Audio::Encode::LameMP3::MPEG-Mode> with the
following items:

=item Stereo

For lame this setting is probably un-necessary.  The stereo channels are
encoded separately and this may result in greater loss of stereo field
information than C<JointStereo>.

=item JointStereo

This is the most common setting for most uses.  The stereo channel separation
is essentially encoded losslessly in lame.

=item DualChannel

This is not implemented as a separate mode by lame, setting this will have no
effect.

=item Mono

The input source is to be encoded as mono.  If interleaved data is presented
then it will be read as if it represents a single channel.  If separate
channels are presented only the left channel will be encoded,

=item NotSet

This is the default.  The library will infact encode as C<JointStereo> by default.

=head3 num-samples

If the number of samples that will be encoded is known in advance (for instance
where the PCM data is read from a file.) this can be set.  The default is 2^31
samples.  If it is set the encoder may be able to make certain small optimisations.

=head3 num-channels

This should be either 1 or 2. lame doesn't support a greater number of channels
(e.g. surround modes) Setting this is probably completely un-necessary.

=head3 scale

This is a scaling factor between 0 and 1 that will be applied to the input data
before encoding.  The default is 1.

=head3 scale-left

scaling between 0 and 1 for the left channel. The default is undefined as C<scale>
will be used.

=head3 scale-right

scaling between 0 and 1 for the right channel. The default is undefined as C<scale>
will be used.

=head3 out-samplerate

This is the target samplerate of the encoded output (i.e. the samplerate of the
resulting PCM if the output were decoded.) The default is the same as the input
samplerate and it probably isn't necessary to change it unless some target software
or hardware requires a particular samplerate. If you want finer control over the
samplerate you may consider using another library such as 'libsamplerate'

=head2 ID3 Attributes

These will cause ID3 tags to be inserted into the output stream.  For some reason
there are no getters for these in 'lame' so they all return an undefined L<Str>.

Both ID3 v1 and v2 tags will be created.

These are quite limited, if you are saving to a file and want finer control
over the tags you might want to consider L<Audio::Taglib::Simple> which will let
you add more tags more flexibly.

These can be either applied to the constructor as parameters or as
attributes on an L<Audio::Encode::LameMP3> object before the encoder
is initialised.  Additionally the may be set as attributes after
C<encode-flush> has been called and before it is reinitialised.

=head3 title

The title tag.

=head3 artist

The artist.

=head3 album

The album.

=head3 year

The year (this is a string that should look like a year e.g. "2015" )

=head3 comment

A comment. The id3v2 tag is created with a language of "XXX" for some reason.

=end pod

class Audio::Encode::LameMP3:ver<0.0.11>:auth<github:jonathanstowe>:api<1.0> {
    use NativeCall;
    use AccessorFacade;
    use NativeHelpers::Array;

    # Output of ':raw' methods for notational convenience
    subset RawEncode of Array where  ($_.elems == 2 ) && ($_[0] ~~ CArray) && ($_[1] ~~ Int);

    enum EncodeError ( Okay => 0, BuffTooSmall => -1, Malloc => -2, NotInit => -3, Psycho => -4 );

    class X::LameError is Exception {
        has Str $.message;
    }

    class X::EncodeError is X::LameError {
        has Str $.message;
        has EncodeError $.error;

        multi method message( --> Str ) {
            if not $!message.defined {
                $!message = do given $!error {
                    when BuffTooSmall {
                        "supplied buffer too small for encoded output";
                    }
                    when Malloc {
                        "unable to allocate enough memory to perform encoding";
                    }
                    when NotInit {
                        "global flags not initialised before encoding";
                    }
                    when Psycho {
                        "problem with psychoacoustic model";
                    }
                    default {
                        "unknown or not an error";
                    }
                }
            }
            $!message;
        }
    }

    enum VBR-Mode <Off MT RH ABR MTRH>;
    enum MPEG-Mode <Stereo JointStereo DualChannel Mono NotSet>;
    enum PaddingType <No All Adjust>;

    # Values returned by the encode functions

    class GlobalFlags is repr('CPointer') {

        sub lame_init( --> GlobalFlags ) is native('mp3lame',v0) { * }

        method new(GlobalFlags:U: *%params) {
            my $lgf = lame_init();

            # call this here so we can add tags from the params.
            $lgf.id3tag_init();

            for %params.kv -> $param, $value {
                if $lgf.can($param) {
                    $lgf."$param"() = $value;
                }
            }
            $lgf;
        }

        # If we want to add id3 tags in the stream we need to set this before we
        # start adding them so call it in the constructor
        sub id3tag_init(GlobalFlags) is native('mp3lame',v0) { * }

        method id3tag_init() {
            id3tag_init(self);
            # everyone wants v2 tags right?
            self.id3-tag-add-v2();
        }

        sub id3tag_add_v2(GlobalFlags) is native('mp3lame',v0) { * }

        method id3-tag-add-v2() {
            id3tag_add_v2(self);
        }

        # The functions to set id3 tags have no get equivalents
        # neither do they return anything to indicate they worked.

        # Just use accessor facade with no return
        sub empty-get(GlobalFlags $) { Str }
        sub manage(GlobalFlags $self, Str $value is copy ) {
            explicitly-manage($value);
            $value;
        }

        role ID3Tag { }

        multi sub trait_mod:<is>(Method $m, :$id3tag! ) {
            $m does ID3Tag;
        }

        sub id3tag_set_title(GlobalFlags, Str) is native('mp3lame',v0) { * }

        method title( --> Str ) is rw is accessor-facade(&empty-get, &id3tag_set_title, &manage) is id3tag { * }

        sub id3tag_set_artist(GlobalFlags, Str) is native('mp3lame',v0) { * }

        method artist( --> Str ) is rw is accessor-facade(&empty-get, &id3tag_set_artist, &manage) is id3tag { }

        sub id3tag_set_album(GlobalFlags, Str) is native('mp3lame',v0) { * }

        method album( --> Str ) is rw is accessor-facade(&empty-get, &id3tag_set_album, &manage) is id3tag { }

        sub id3tag_set_year(GlobalFlags, Str) is native('mp3lame',v0) { * }

        method year( --> Str ) is rw is accessor-facade(&empty-get, &id3tag_set_year, &manage) is id3tag { }

        sub id3tag_set_comment(GlobalFlags, Str) is native('mp3lame',v0) { * }

        method comment( --> Str ) is rw is accessor-facade(&empty-get, &id3tag_set_comment, &manage) is id3tag { }

        sub check(GlobalFlags $self, Int $rc, Str :$what = 'unknown method') {
            if $rc < 0 {
                X::LameError.new(message => "Error setting parameter").throw
            }
            $rc;
        }

        # utilities

        sub get-buffer-size(Int $no-frames --> Int ) {
            my $num = ((1.25 * $no-frames) + 7200).Int;
            $num;
        }

        sub get-out-buffer(Int $size --> CArray ) {
            my $buff =  CArray[uint8].new;
            $buff[$size] = 0;
            $buff;
        }

        multi method encode(@left, @right, &encode-func, Mu $type  --> Buf ) {
            my ($buffer, $bytes-out) = self.encode(@left, @right, &encode-func, $type, :raw ).list;
            copy-carray-to-buf($buffer, $bytes-out);
        }

        multi method encode(CArray $left-in, CArray $right-in, Int $frames, &encode-func --> Buf ) {
            my ($buffer, $bytes-out) = self.encode($left-in, $right-in, $frames, &encode-func, :raw ).list;
            copy-carray-to-buf($buffer, $bytes-out);
        }

        multi method encode(@left, @right, &encode-func, Mu $type, :$raw! --> RawEncode ) {
            if (@left.elems == @right.elems ) {

                my $left-in   = copy-to-carray(@left, $type);
                my $right-in  = copy-to-carray(@right, $type);
                my $frames    = @left.elems;
                self.encode($left-in, $right-in, $frames, &encode-func, :raw);
            }
            else {
                X::EncodeError.new(message => "not equal length frames in");
            }
        }

        multi method encode(CArray $left-in, CArray $right-in, Int $frames, &encode-func, :$raw! --> RawEncode )  {
            my $buff-size = get-buffer-size($frames);
            my $buffer    = get-out-buffer($buff-size);
            my $bytes-out = &encode-func(self, $left-in, $right-in,  $frames, $buffer, $buff-size);
            if $bytes-out < 0 {
                X::EncodeError.new(error => EncodeError($bytes-out)).throw;
            }
            [$buffer, $bytes-out];
        }

        multi method encode(@frames, &encode-func, Mu $type --> Buf ) {
            my ( $buffer, $bytes-out ) = self.encode(@frames, &encode-func, $type, :raw ).list;
            copy-carray-to-buf($buffer, $bytes-out);
        }

        multi method encode(CArray $frames-in, Int $frames, &encode-func --> Buf ) {
            my ( $buffer, $bytes-out ) = self.encode($frames-in, $frames, &encode-func, :raw ).list;
            copy-carray-to-buf($buffer, $bytes-out);
        }

        multi method encode(@frames, &encode-func, Mu $type, :$raw! --> RawEncode ) {
            if (@frames.elems % 2 ) == 0  {

                my $frames-in   = copy-to-carray(@frames, $type);
                my $frames    = (@frames.elems / 2).Int;
                self.encode($frames-in, $frames, &encode-func, :raw);
            }
            else {
                X::EncodeError.new(message => "not equal length frames in");
            }
        }

        multi method encode(CArray $frames-in, Int $frames, &encode-func, :$raw! --> RawEncode ) {
            my $buff-size = get-buffer-size($frames);
            my $buffer    = get-out-buffer($buff-size);

            my $bytes-out = &encode-func(self, $frames-in, $frames, $buffer, $buff-size);

            if $bytes-out < 0 {
                X::EncodeError.new(error => EncodeError($bytes-out)).throw;
            }
            [ $buffer, $bytes-out ];
        }

        # encode functions all return the number of bytes in the encoded output or a value less than 0
        # from the enum EncodeError above

        # Non-interleaved inputs are left, right. num_samples is actually number of frames.
        sub lame_encode_buffer(GlobalFlags, CArray[int16], CArray[int16], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-short(@left, @right --> Buf ) {
            self.encode(@left, @right, &lame_encode_buffer, int16);
        }

        multi method encode-short(@left, @right, :$raw! --> RawEncode ) {
            self.encode(@left, @right, &lame_encode_buffer, int16, :raw);
        }

        multi method encode-short(CArray[int16] $left, CArray[int16] $right, Int $frames --> Buf ) {
            self.encode($left, $right, $frames, &lame_encode_buffer);
        }

        multi method encode-short(CArray[int16] $left, CArray[int16] $right, Int $frames, :$raw! --> RawEncode ) {
            self.encode($left, $right, $frames, &lame_encode_buffer, :raw);
        }

        sub lame_encode_buffer_interleaved(GlobalFlags, CArray[int16], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-short(@frames --> Buf ) {
            self.encode(@frames, &lame_encode_buffer_interleaved, int16);
        }

        multi method encode-short(@frames, :$raw! --> RawEncode ) {
            self.encode(@frames, &lame_encode_buffer_interleaved, int16, :raw);
        }

        multi method encode-short(CArray[int16] $frames-in, Int $frames --> Buf ) {
            self.encode($frames-in, $frames, &lame_encode_buffer_interleaved);
        }

        multi method encode-short(CArray[int16] $frames-in, Int $frames, :$raw! --> RawEncode ) {
            self.encode($frames-in, $frames, &lame_encode_buffer_interleaved, :raw);
        }

        # not sure what this one is about. The include file comment suggests it is ints but the signature suggests otherwise
        sub lame_encode_buffer_float(GlobalFlags, CArray[num32], CArray[num32], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        # seemed to be scaled to floats as we know them
        sub lame_encode_buffer_ieee_float(GlobalFlags, CArray[num32], CArray[num32], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-float(@left, @right --> Buf ) {
            self.encode(@left, @right, &lame_encode_buffer_ieee_float, num32);
        }
        multi method encode-float(@left, @right, :$raw! --> RawEncode ) {
            self.encode(@left, @right, &lame_encode_buffer_ieee_float, num32, :raw);
        }
        multi method encode-float(CArray[num32] $left, CArray[num32] $right, Int $frames --> Buf ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_ieee_float);
        }
        multi method encode-float(CArray[num32] $left, CArray[num32] $right, Int $frames, :$raw! --> RawEncode ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_ieee_float, :raw);
        }

        sub lame_encode_buffer_interleaved_ieee_float(GlobalFlags, CArray[num32], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-float(@frames --> Buf ) {
            self.encode(@frames, &lame_encode_buffer_interleaved_ieee_float, num32);
        }
        multi method encode-float(@frames, :$raw! --> RawEncode ) {
            self.encode(@frames, &lame_encode_buffer_interleaved_ieee_float, num32, :raw);
        }
        multi method encode-float(CArray[num32] $frames-in, Int $frames --> Buf ) {
            self.encode($frames-in, $frames, &lame_encode_buffer_interleaved_ieee_float);
        }
        multi method encode-float(CArray[num32] $frames-in, Int $frames, :$raw! --> RawEncode ) {
            self.encode($frames-in, $frames, &lame_encode_buffer_interleaved_ieee_float, :raw);
        }

        sub lame_encode_buffer_ieee_double(GlobalFlags, CArray[num64], CArray[num64], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-double(@left, @right --> Buf ) {
            self.encode(@left, @right, &lame_encode_buffer_ieee_float, num64);
        }
        multi method encode-double(@left, @right, :$raw! --> RawEncode ) {
            self.encode(@left, @right, &lame_encode_buffer_ieee_float, num64, :raw);
        }
        multi method encode-double(CArray[num64] $left, CArray[num64] $right, Int $frames --> Buf ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_ieee_float);
        }
        multi method encode-double(CArray[num64] $left, CArray[num64] $right, Int $frames, :$raw! --> RawEncode ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_ieee_float, :raw);
        }

        sub lame_encode_buffer_interleaved_ieee_double(GlobalFlags, CArray[num64], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-double(@frames --> Buf ) {
            self.encode(@frames, &lame_encode_buffer_interleaved_ieee_double, num64);
        }
        multi method encode-double(@frames, :$raw! --> RawEncode ) {
            self.encode(@frames, &lame_encode_buffer_interleaved_ieee_double, num64, :raw);
        }
        multi method encode-double(CArray[num64] $frames-in, Int $frames --> Buf ) {
            self.encode($frames-in, $frames, &lame_encode_buffer_interleaved_ieee_double);
        }
        multi method encode-double(CArray[num64] $frames-in, Int $frames, :$raw! --> RawEncode ) {
            self.encode($frames-in, $frames, &lame_encode_buffer_interleaved_ieee_double, :raw);
        }

        # ignoring the long variant as it appears to be a mistake
        # neither have an interleaved variant
        sub lame_encode_buffer_long2(GlobalFlags, CArray[int64], CArray[int64], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-long(@left, @right --> Buf ) {
            self.encode(@left, @right, &lame_encode_buffer_long2, int64);
        }

        multi method encode-long(@left, @right, :$raw! --> RawEncode ) {
            self.encode(@left, @right, &lame_encode_buffer_long2, int64, :raw);
        }
        multi method encode-long(CArray[int64] $left, CArray[int64] $right, Int $frames --> Buf ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_long2);
        }

        multi method encode-long(CArray[int64] $left, CArray[int64] $right, Int $frames, :$raw! --> RawEncode ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_long2, :raw);
        }

        # the include suggests that the scaling may be wonky on this.
        sub lame_encode_buffer_int(GlobalFlags, CArray[int32], CArray[int32], int32, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        multi method encode-int(@left, @right --> Buf ) {
            self.encode(@left, @right, &lame_encode_buffer_int, int32);
        }
        multi method encode-int(@left, @right, :$raw! --> RawEncode ) {
            self.encode(@left, @right, &lame_encode_buffer_int, int32, :raw);
        }

        multi method encode-int(CArray[int32] $left, CArray[int32] $right, Int $frames --> Buf ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_int);
        }
        multi method encode-int(CArray[int32] $left, CArray[int32] $right, Int $frames, :$raw! --> RawEncode ) {
            self.encode($left, $right, $frames, &lame_encode_buffer_int, :raw);
        }

        # The nogap variant means the stream can be reused or something return number of bytes (and I guess <0