### ----------------------------------------------------
### -- Audio::PortMIDI
### -- Licenses: Artistic-2.0
### -- Authors: Jonathan Stowe <jns+gh@gellyfish.co.uk>, Pepe Schwarz <peschwa@gmail.com>
### -- File: lib/Audio/PortMIDI.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
]]));
            my $rc = $.rc-chan;
            if $rc < 0 {
                X::PortMIDI.new(code => $rc, what => "synchronizing stream").throw;
            }
        }


        sub Pm_Poll(StreamPointer $stream ) is native(LIB) returns int32 { * }

        method poll() returns Bool {
            $.ev-chan.send(%(call => &Pm_Poll, args => [$.ptr[0]]));
            my $rc = await $.rc-chan;
            if $rc < 0 {
                X::PortMIDI.new(code => $rc, what => "polling stream").throw;
            }
            Bool($rc);
        }

        sub Pm_Read(StreamPointer $stream, CArray $buffer, int32 $length) is native(LIB) returns int32 { * }


        proto method read(|c) { * }

        multi method read(Int $length) {
            my CArray[int64] $buff = CArray[int64].new;
            $buff[$length - 1] = 0;
            $.ev-chan.send(%(call => &Pm_Read, args => [$.ptr[0], $buff, $length]));
            my $rc = await $.rc-chan;
            if $rc < 0 {
                X::PortMIDI.new(code => $rc, what => "reading stream").throw;
            }

            my @buff;

            for ^$rc -> $i {
                @buff.append: Event.new(event => $buff[$i]);
            }

            @buff;
        }

        sub Pm_Write(StreamPointer $stream, CArray[int64] $buffer, int32  $length ) is native(LIB) returns int32 { * }

        proto method write(|c) { * }

        multi method write(Event @events) {
            my $buffer = CArray[int64].new;
            my $length = @events.elems;
            for @events -> $event {
                $buffer[$++] = $event.Int;
            }
            $.ev-chan.send(%(call => &Pm_Write, args => [$.ptr[0], $buffer, $length]));
            my $rc = await $.rc-chan;
            if $rc < 0 {
                X::PortMIDI.new(code => $rc, what => "writing stream").throw;
            }
        }

        sub Pm_WriteShort(StreamPointer $stream, int32 $when, int32 $msg) is native(LIB) returns int32 { * }

        multi method write(Event $event) {
            $.ev-chan.send(%(call => &Pm_WriteShort, args => [$.ptr[0], $event.timestamp // 0, $event.message]));
            my $rc = await $.rc-chan;
            if $rc < 0 {
                X::PortMIDI.new(code => $rc, what => "writing stream").throw;
            }
        }

        sub Pm_WriteSysEx(StreamPointer $stream, int32 $when, Pointer[uint8] $msg) is native(LIB) returns int32 { * }
    }

    class Time {

        sub Pt_Started() returns int32 is native(LIB) { * }

        method started() returns Bool {
            my $rc = Pt_Started();
            Bool($rc);
        }
        sub Pt_Start(int32 $resolution, &ccb (int32 $timestamp, Pointer $userdata), Pointer $u) returns int32 is native(LIB) { * }

        method start() {
            Pt_Start(1, Pointer, Pointer);
        }

        sub Pt_Time() returns int32 is native(LIB) { * }

        method time() returns Int {
            Pt_Time();
        }
    }


    multi submethod BUILD() {
        self.initialize();
        Time.start();
    }

    sub Pm_Initialize() is native(LIB) returns int32 { * }

    method initialize() {
        my $rc = Pm_Initialize();
        if $rc < 0 {
            X::PortMIDI.new(code => $rc, what => 'initialising portmidi').throw;
        }
    }

    sub Pm_Terminate() is native(LIB) returns int32  { * }

    method terminate() {
        my $rc = Pm_Terminate();
        if $rc < 0 {
            X::PortMIDI.new(code => $rc, what => 'terminating portmidi').throw;
        }
    }

    sub Pm_CountDevices() is native(LIB) returns int32 { * }

    method count-devices() returns Int {
        my $rc = Pm_CountDevices();
        if $rc < 0 {
            X::PortMIDI.new(code => $rc, what => 'count-devices').throw;
        }
        $rc;
    }

    sub Pm_GetDeviceInfo(int32 $id) is native(LIB) returns DeviceInfoX { * }

    method device-info(Int $device-id) returns DeviceInfo {
        DeviceInfo.new(device-info => Pm_GetDeviceInfo($device-id), :$device-id);
    }

    method devices() {
        gather {
            for ^(self.count-devices()) -> $id {
                take self.device-info($id);
            }
        }
    }

    sub Pm_GetDefaultInputDeviceID() is native(LIB) returns int32 { * }

    method default-input-device() returns DeviceInfo {
        my $rc = Pm_GetDefaultInputDeviceID();
        if $rc < 0 {
            X::PortMIDI.new(code => $rc, what => "default-input-device").throw;
        }
        self.device-info($rc);
    }

    sub Pm_GetDefaultOutputDeviceID() is native(LIB) returns int32 { * }

    method default-output-device() returns DeviceInfo {
        my $rc = Pm_GetDefaultOutputDeviceID();
        if $rc < 0 {
            X::PortMIDI.new(code => $rc, what => "default-output-device").throw;
        }
        self.device-info($rc);
    }

    sub Pm_OpenInput(CArray[StreamPointer] $stream, int32 $inputDevice, Pointer $inputDriverInfo, int32 $bufferSize ,&time_proc (Pointer --> int32), Pointer $time_info) is native(LIB) returns int32 { * }

    proto method open-input(|c) { * }

    multi method open-input(DeviceInfo:D $dev, Int $buffer-size) returns Stream {
        if $dev.input {
            self.open-input($dev.device-id, $buffer-size);
        }
        else {
            X::PortMIDI.new(code => -9999, message => "not an input device", what => "opening input stream").throw;
        }
    }

    multi method open-input(Int $device-id, Int $buffer-size) returns Stream {
        my $stream = Stream.new;
        my $rc = Pm_OpenInput($stream.ptr, $device-id, Pointer, $buffer-size, Pointer, Pointer);
        if $rc < 0 {
            X::PortMIDI.new(code => $rc, what => "opening input stream").throw;
        }
        $stream;
    }

    sub Pm_OpenOutput(CArray[StreamPointer] $stream, int32 $outputDevice, Pointer $outputDriverInfo, int32 $bufferSize ,&time_proc (Pointer --> int32), Pointer $time_info, int32 $latency) is native(LIB) returns int32 { * }


    proto method open-output(|c) { * }


    multi method open-output(DeviceInfo:D $dev, Int $buffer-size, Int $latency = 0 ) returns Stream {
        if $dev.output {
            self.open-output($dev.device-id, $buffer-size, $latency);
        }
        else {
            X::PortMIDI.new(code => -9999, message => "not an output device", what => "opening output stream").throw;
        }
    }

    multi method open-output(Int $device-id, Int $buffer-size, Int $latency = 0) returns Stream {
        my $stream = Stream.new;
        my $rc = Pm_OpenOutput($stream.ptr, $device-id, Pointer, $buffer-size, Pointer, Pointer, $latency);
        if $rc < 0 {
            X::PortMIDI.new(code => $rc, what => "opening output stream").throw;
        }
        $stream;
    }
}

# vim: expandtab shiftwidth=4 ft=raku


