### ----------------------------------------------------
### -- Audio::Encode::LameMP3
### -- Licenses: Artistic-2.0
### -- Authors: Jonathan Stowe <jns+git@gellyfish.co.uk>
### -- File: lib/Audio/Encode/LameMP3.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
 is an error
        sub lame_encode_flush(GlobalFlags, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }
        # nogap allows you to continue using the same encoder - useful for streaming
        sub lame_encode_flush_nogap(GlobalFlags, CArray[uint8], int32 --> int32 ) is native('mp3lame',v0) { * }

        # allocate an overly long buffer to take the last bit
        multi method encode-flush(:$nogap! --> Buf ) {
            my ( $buffer, $bytes-out) = self.encode-flush(:nogap, :raw).list;
            copy-carray-to-buf($buffer, $bytes-out);
        }
        multi method encode-flush( --> Buf ) {
            my ( $buffer, $bytes-out) = self.encode-flush(:raw).list;
            copy-carray-to-buf($buffer, $bytes-out);
        }
        multi method encode-flush(:$nogap! , :$raw! --> RawEncode ) {
            my $buffer = get-out-buffer(8192);
            my $bytes-out = lame_encode_flush_nogap(self, $buffer, 8192);

            if $bytes-out < 0 {
                X::EncodeError.new(error => EncodeError($bytes-out)).throw;
            }
            [$buffer, $bytes-out];
        }
        multi method encode-flush(:$raw! --> RawEncode ) {
            my $buffer = get-out-buffer(8192);
            my $bytes-out = lame_encode_flush(self, $buffer, 8192);

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


        sub lame_set_in_samplerate(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_in_samplerate(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method in-samplerate( --> Int ) is rw
            is accessor-facade(&lame_get_in_samplerate, &lame_set_in_samplerate, Code, &check) { }

        sub lame_set_num_channels(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_num_channels(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method num-channels( --> Int )
            is accessor-facade(&lame_get_num_channels, &lame_set_num_channels, Code, &check) { }

        sub lame_set_brate(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_brate(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method bitrate( --> Int )
            is accessor-facade(&lame_get_brate, &lame_set_brate, Code, &check) { }

        sub lame_set_quality(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_quality(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method quality( --> Int )
            is accessor-facade(&lame_get_quality, &lame_set_quality, Code, &check) { }


        sub lame_set_mode(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_mode(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method mode( --> MPEG-Mode )
            is accessor-facade(&lame_get_mode, &lame_set_mode, Code, &check ) { }


        # below less commonly used

        sub lame_set_num_samples(GlobalFlags, uint64 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_num_samples(GlobalFlags --> uint64 ) is native('mp3lame',v0) { * }

        method num-samples( --> Int ) is rw
            is accessor-facade(&lame_get_num_samples, &lame_set_num_samples, Code, &check ) { }


        sub lame_set_scale(GlobalFlags, num32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_scale(GlobalFlags --> num32 ) is native('mp3lame',v0) { * }

        method scale( --> Num ) is rw
            is accessor-facade(&lame_get_scale, &lame_set_scale, Code, &check ) { }

        sub lame_set_scale_left(GlobalFlags, num32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_scale_left(GlobalFlags --> num32 ) is native('mp3lame',v0) { * }

        method scale-left( --> Num ) is rw
            is accessor-facade(&lame_get_scale_left, &lame_set_scale_left, Code, &check ) { }

        sub lame_set_scale_right(GlobalFlags, num32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_scale_right(GlobalFlags --> num32 ) is native('mp3lame',v0) { * }

        method scale-right( --> Num ) is rw
            is accessor-facade(&lame_get_scale_right, &lame_set_scale_right, Code, &check ) { }

        sub lame_set_out_samplerate(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_out_samplerate(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method out-samplerate( --> Int ) is rw
            is accessor-facade(&lame_get_out_samplerate, &lame_set_out_samplerate, Code, &check ) { }


        sub lame_set_analysis(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_analysis(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method set-analysis( --> Bool ) is rw
            is accessor-facade(&lame_get_analysis, &lame_set_analysis, Code, &check ) { }


        sub lame_set_bWriteVbrTag(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_bWriteVbrTag(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method write-vbr-tag( --> Bool ) is rw
            is accessor-facade(&lame_get_bWriteVbrTag, &lame_set_bWriteVbrTag, Code, &check ) { }

        sub lame_set_decode_only(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_decode_only(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method decode-only(--> Bool ) is rw
            is accessor-facade(&lame_get_decode_only, &lame_set_decode_only, Code, &check ) { }


        sub lame_set_nogap_total(GlobalFlags, int32 --> int32 ) is native('mp3lame',v0) { * }
        sub lame_get_nogap_total(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method nogap-total( --> Int ) is rw
            is accessor-facade(&lame_get_nogap_total, &lame_set_nogap_total, Code, &check ) { }

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

        method init() {
            my $rc = lame_init_params(self);

            if $rc != 0 {
                X::LameError.new(message => "Error initialising parameters").throw;
            }
        }

        # This is not necessary but using flush_nogap and this it is possible to reuse
        # the same encoder which may be useful for streaming
        sub lame_init_bitstream(GlobalFlags --> int32 ) is native('mp3lame',v0) { * }

        method init-bitstream() {
            my $rc = lame_init_bitstream(self);

            if $rc != 0 {
                X::LameError.new(message => "Error (re)initialising bitstream").throw;
            }
        }

        # The API docs and the include differ in the necessity of calling this.
        # As we'll only be "streaming" I'll hedge.

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

        method mp3-tags() {
            lame_mp3_tags_fid(self, Pointer);
        }


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

        method DESTROY() {
            lame_close(self);
        }
    }

    has GlobalFlags $!gfp handles <
                                    in-samplerate
                                    num-channels
                                    bitrate
                                    quality
                                    mode
                                    num-samples
                                    scale
                                    scale-left
                                    scale-right
                                    out-samplerate
                                    set-analysis
                                    write-vbr-tag
                                    decode-only
                                    nogap-total
                                    title
                                    artist
                                    album
                                    year
                                    comment
                                  >;

    has Bool $!initialised = False;

    method init() {
        if not $!initialised {
            $!gfp.init;
            $!initialised = True;
        }
    }


    # for some reason there aren't interleaved versions of all the
    # different encode variants
    multi sub uninterleave(@frames) {
        my ( $left, $right);
        ($++ %% 2 ?? $left !! $right).push: $_ for @frames;
        return $left, $right;
    }

    multi sub uninterleave(CArray $c, $frames) {
	    my $left = $c.WHAT.new;
	    my $right = $c.WHAT.new;

	    my $left-index = 0;
	    my $right-index = 0;

	    for ^(2 * $frames) -> $i {
		    if $i % 2 {
			    $right[$right-index++] = $c[$i];
		    }
		    else {
			    $left[$left-index++] = $c[$i];
		    }
	    }
	    return $left, $right;
    }

    multi method encode-short(@left, @right --> Buf ) {
        self.init();
        $!gfp.encode-short(@left, @right);
    }

    multi method encode-short(@frames --> Buf ) {
        self.init();
        $!gfp.encode-short(@frames);
    }
    multi method encode-short(@left, @right, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-short(@left, @right, :raw);
    }

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

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

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

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

    multi method encode-int(@left, @right --> Buf ) {
        self.init();
        $!gfp.encode-int(@left, @right);
    }

    multi method encode-int(@frames --> Buf ) {
        self.init();
        my ( $left, $right ) = uninterleave(@frames);
        $!gfp.encode-int($left, $right);
    }

    multi method encode-int(@left, @right, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-int(@left, @right, :raw);
    }

    multi method encode-int(@frames, :$raw! --> RawEncode ) {
        self.init();
        my ( $left, $right ) = uninterleave(@frames);
        $!gfp.encode-int($left, $right, :raw);
    }

    multi method encode-int(CArray[int32] $left, CArray[int32] $right, Int $frames --> Buf ) {
        self.init();
        $!gfp.encode-int($left, $right, $frames);
    }

    multi method encode-int(CArray[int32] $frames-in, Int $frames --> Buf ) {
        self.init();
        my ( $left, $right ) = uninterleave($frames-in, $frames);
        $!gfp.encode-int($left, $right, $frames);
    }

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

    multi method encode-int(CArray[int32] $frames-in, Int $frames, :$raw! --> RawEncode ) {
        self.init();
        my ( $left, $right ) = uninterleave($frames-in, $frames);
        $!gfp.encode-int($left, $right, $frames, :raw);
    }

    multi method encode-long(@left, @right --> Buf ) {
        self.init();
        $!gfp.encode-long(@left, @right);
    }

    multi method encode-long(@frames --> Buf ) {
        self.init();
        my ( $left, $right ) = uninterleave(@frames);
        $!gfp.encode-long($left, $right);
    }

    multi method encode-long(@left, @right, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-long(@left, @right, :raw);
    }

    multi method encode-long(@frames, :$raw! --> RawEncode ) {
        self.init();
        my ( $left, $right ) = uninterleave(@frames);
        $!gfp.encode-long($left, $right, :raw);
    }

    multi method encode-long(CArray[int64] $left, CArray[int64] $right, Int $frames --> Buf ) {
        self.init();
        $!gfp.encode-long($left, $right, $frames);
    }

    multi method encode-long(CArray[int64] $frames-in, Int $frames --> Buf ) {
        self.init();
        my ( $left, $right ) = uninterleave($frames-in, $frames);
        $!gfp.encode-long($left, $right, $frames);
    }

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

    multi method encode-long(CArray[int64] $frames-in, Int $frames, :$raw! --> RawEncode ) {
        self.init();
        my ( $left, $right ) = uninterleave($frames-in, $frames);
        $!gfp.encode-long($left, $right, $frames, :raw);
    }

    multi method encode-float(@left, @right --> Buf ) {
        self.init();
        $!gfp.encode-float(@left, @right);
    }

    multi method encode-float(@frames --> Buf ) {
        self.init();
        $!gfp.encode-float(@frames);
    }

    multi method encode-float(@left, @right, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-float(@left, @right, :raw);
    }

    multi method encode-float(@frames, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-float(@frames, :raw);
    }

    multi method encode-float(CArray[num32] $left, CArray[num32] $right, Int $frames --> Buf ) {
        self.init();
        $!gfp.encode-float($left, $right, $frames);
    }

    multi method encode-float(CArray[num32] $frames-in, Int $frames --> Buf ) {
        self.init();
        $!gfp.encode-float($frames-in, $frames);
    }

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

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

    multi method encode-double(@left, @right --> Buf ) {
        self.init();
        $!gfp.encode-double(@left, @right);
    }

    multi method encode-double(@frames --> Buf ) {
        self.init();
        $!gfp.encode-double(@frames);
    }

    multi method encode-double(@left, @right, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-double(@left, @right, :raw);
    }

    multi method encode-double(@frames, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-double(@frames, :raw);
    }

    multi method encode-double(CArray[num64] $left, CArray[num64] $right, Int $frames --> Buf ) {
        self.init();
        $!gfp.encode-double($left, $right, $frames);
    }

    multi method encode-double(CArray[num64] $frames-in, Int $frames --> Buf ) {
        self.init();
        $!gfp.encode-double($frames-in, $frames);
    }

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

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

    multi method encode-flush( --> Buf ) {
        self.init();
        $!gfp.encode-flush();
    }
    multi method encode-flush(:$nogap! --> Buf ) {
        self.init();
        $!gfp.encode-flush(:nogap);
    }
    multi method encode-flush(:$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-flush(:raw);
    }
    multi method encode-flush(:$nogap!, :$raw! --> RawEncode ) {
        self.init();
        $!gfp.encode-flush(:nogap, :raw);
    }

    method init-bitstream() {
        if $!initialised {
            $!gfp.init-bitstream();
        }
    }

    sub get_lame_version( --> Str ) is native('mp3lame',v0) { * }

    method lame-version( --> Version ) {
        my $v = get_lame_version();
        Version.new($v);
    }

    submethod BUILD(*%params) {
        $!gfp = GlobalFlags.new(|%params);
        # No file descriptor so this must be off;
        $!gfp.write-vbr-tag = False;
    }
}

# vim: expandtab shiftwidth=4 ft=raku


