All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.kaleidox.vban.packet.VBANPacketHead Maven / Gradle / Ivy

Go to download

Java 7 compatible Library for communicating with the VB-Audio VBAN interface

The newest version!
package de.kaleidox.vban.packet;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

import de.kaleidox.util.model.ByteArray;
import de.kaleidox.vban.Util;
import de.kaleidox.vban.VBAN;
import de.kaleidox.vban.VBAN.AudioFormat;
import de.kaleidox.vban.VBAN.BitsPerSecond;
import de.kaleidox.vban.VBAN.Codec;
import de.kaleidox.vban.VBAN.CommandFormat;
import de.kaleidox.vban.VBAN.Format;
import de.kaleidox.vban.VBAN.Protocol;
import de.kaleidox.vban.VBAN.SampleRate;
import de.kaleidox.vban.exception.InvalidPacketAttributeException;
import de.kaleidox.vban.model.DataRateValue;
import de.kaleidox.vban.model.FormatValue;
import de.kaleidox.vban.model.UnfinishedByteArray;

import org.intellij.lang.annotations.MagicConstant;

import static de.kaleidox.vban.Util.checkRange;
import static de.kaleidox.vban.Util.intToByteArray;
import static de.kaleidox.vban.Util.stringToBytesASCII;
import static de.kaleidox.vban.Util.trimArray;
import static de.kaleidox.vban.packet.VBANPacketHead.Factory.builder;

public class VBANPacketHead implements ByteArray {
    public final static int SIZE = 28;

    private final UnfinishedByteArray unfinishedByteArray;

    private VBANPacketHead(byte[] bytes) {
        unfinishedByteArray = new UnfinishedByteArray(SIZE, true);
        unfinishedByteArray.append(bytes);
    }

    public VBANPacketHead(int protocol,
                          int sampleRateIndex,
                          int samples,
                          int channel,
                          int format,
                          int codec,
                          String streamName,
                          int frameCounter) {
        checkRange(samples, 0, 255);
        checkRange(channel, 0, 255);

        unfinishedByteArray = new UnfinishedByteArray(SIZE, true);

        unfinishedByteArray.append("VBAN".getBytes());
        unfinishedByteArray.append((byte) (protocol | sampleRateIndex));
        unfinishedByteArray.append((byte) samples, (byte) channel);
        unfinishedByteArray.append((byte) (format | codec));
        unfinishedByteArray.append(trimArray(stringToBytesASCII(streamName), 16));
        unfinishedByteArray.append(intToByteArray(frameCounter, 4));
    }

    @Override
    public byte[] getBytes() {
        return unfinishedByteArray.getBytes();
    }

    /**
     * Creates a Factory instance with the default properties for the specified Protocol.
     *
     * @param forProtocol The protocol to use standards for.
     * @param          Type-variable for the VBAN-Stream.
     *
     * @return A new Factory instance.
     * @throws UnsupportedOperationException If the Protocol is one of {@code [AUDIO, SERIAL, SERVICE]}.
     */
    public static  Factory defaultFactory(Protocol forProtocol) throws UnsupportedOperationException {
        return builder(forProtocol).build();
    }

    public static VBANPacketHead.Decoded decode(byte[] headBytes) throws InvalidPacketAttributeException {
        return new VBANPacketHead.Decoded(headBytes);
    }

    public static class Decoded extends VBANPacketHead {
        private final Protocol protocol;
        private final DataRateValue dataRateValue;
        private final int samples;
        private final int channel;
        private final FormatValue format;
        @MagicConstant(valuesFromClass = VBAN.Codec.class) private final int codec;
        private final String streamName;
        private final int frame;

        private Decoded(byte[] bytes) throws InvalidPacketAttributeException {
            super(bytes);

            if (bytes.length > SIZE)
                throw new IllegalArgumentException("Bytearray is too large, must be exactly " + SIZE + " bytes long!");

            if (bytes[0] != 'V' || bytes[1] != 'B' || bytes[2] != 'A' || bytes[3] != 'N')
                throw new InvalidPacketAttributeException("Invalid packet head: First bytes must be 'VBAN' [rcv='"
                        + new String(Util.subArray(bytes, 0, 4), StandardCharsets.US_ASCII) + "']");

            int protocolInt = bytes[4] & 0b11111000;
            protocol = VBAN.Protocol.byValue(protocolInt);

            // throw exception if protocol is SERVICE
            if (protocol.getValue() == 0x60)
                throw new IllegalStateException("Service Subprotocol is not supported!");

            int dataRateInt = bytes[4] & 0b00000111;
            switch (protocol.getValue()) {
                case 0x00: // AUDIO
                    dataRateValue = SampleRate.byValue(dataRateInt);
                    break;
                case 0x20: // SERIAL
                case 0x40: // TEXT
                    dataRateValue = BitsPerSecond.byValue(dataRateInt);
                    break;
                case 0x60: // SERVICE
                default:
                    // to avoid compiler warning, set to null.
                    // service protocol is not supported
                    dataRateValue = null;
                    break;
            }

            // +1 to avoid indexed counting
            samples = bytes[5] + 1;
            channel = bytes[6] + 1;

            int formatInt = bytes[7] & 0b00011111;
            switch (protocol.getValue()) {
                case 0x00: // AUDIO
                    format = AudioFormat.byValue(formatInt);
                    break;
                case 0x20: // SERIAL
                    format = Format.byValue(formatInt);
                    break;
                case 0x40: // TEXT
                    format = CommandFormat.byValue(formatInt);
                    break;
                case 0x60: // SERVICE
                default:
                    // to avoid compiler warning, set to null.
                    // service protocol is not supported
                    format = null;
                    break;
            }

            // reserved bit
            int reservedBit = bytes[7] & 0b11101111;

            int codecInt = bytes[7] & 0b11110000;
            switch (codecInt) {
                case Codec.PCM:
                case Codec.VBCA:
                case Codec.VBCV:
                case Codec.USER:
                    //noinspection MagicConstant
                    codec = codecInt;
                    break;
                default:
                    throw new InvalidPacketAttributeException("Invalid Codec selector: " + Integer.toHexString(codecInt));
            }

            byte[] nameBytes = new byte[16];
            System.arraycopy(bytes, 8, nameBytes, 0, 16);
            streamName = Util.bytesToString(nameBytes, StandardCharsets.US_ASCII);

            byte[] frameBytes = new byte[4];
            System.arraycopy(bytes, 24, frameBytes, 0, 4);
            frame = ByteBuffer.wrap(frameBytes).asIntBuffer().get();
        }

        public Protocol getProtocol() {
            return protocol;
        }

        public DataRateValue getDataRateValue() {
            return dataRateValue;
        }

        public int getSamples() {
            return samples;
        }

        public int getChannel() {
            return channel;
        }

        public FormatValue getFormat() {
            return format;
        }

        @MagicConstant(valuesFromClass = VBAN.Codec.class)
        public int getCodec() {
            return codec;
        }

        public String getStreamName() {
            return streamName;
        }
    }

    public static class Factory implements de.kaleidox.util.model.Factory> {
        private final int protocol;
        private final int sampleRate;
        private final int samples;
        private final int channel;
        private final int format;
        private final int codec;
        private final String streamName;
        private int counter;

        private Factory(Protocol protocol,
                        DataRateValue sampleRate,
                        int samples,
                        int channel,
                        FormatValue format,
                        int codec,
                        String streamName) {
            this.protocol = protocol.getValue();
            this.sampleRate = sampleRate.getValue();
            this.samples = samples;
            this.channel = channel;
            this.format = format.getValue();
            this.codec = codec;
            this.streamName = streamName;

            counter = 0;
        }

        @Override
        public synchronized VBANPacketHead create() {
            return new VBANPacketHead<>(protocol, sampleRate, samples, channel, format, codec, streamName, counter++);
        }

        @Override
        public synchronized int counter() {
            return counter;
        }

        /**
         * Creates a new Builder with the default properties pre-set for the specified protocol.
         *
         * @param protocol The protocol to create the Builder for.
         * @param       Type-Variable for the stream type.
         *
         * @return A new builder for the given protocol.
         * @throws UnsupportedOperationException If the protocol is {@link Protocol#SERVICE}.
         */
        public static  Builder builder(Protocol protocol) throws UnsupportedOperationException {
            return new Builder<>(protocol);
        }

        public static class Builder implements de.kaleidox.util.model.Builder> {
            private final Protocol protocol;
            private DataRateValue sampleRate;
            private int samples;
            private int channel;
            private FormatValue format;
            @MagicConstant(valuesFromClass = Codec.class)
            private int codec = Codec.PCM;
            private String streamName = null;

            /*
            Suppress ConstantConditions because, while the MIDI branch breaks away due to Serial communication not
            being implemented yet, the IF in the Text communication branch will always be 'false'
             */
            @SuppressWarnings({"unchecked", "ConstantConditions"})
            private Builder(Protocol protocol) throws UnsupportedOperationException {
                this.protocol = protocol;

                switch (protocol.getValue()) {
                    case 0x00:
                        sampleRate = (DataRateValue) SampleRate.Hz48000;
                        samples = 255;
                        channel = 2;
                        format = (FormatValue) AudioFormat.INT16;
                        streamName = "Stream1";
                        return;
                    case 0x20:
                        sampleRate = (DataRateValue) BitsPerSecond.Bps256000;
                        samples = 0;
                        channel = 0;
                        format = (FormatValue) Format.BYTE8;
                        streamName = "MIDI1";
                        return;
                    case 0x40:
                        sampleRate = (DataRateValue) BitsPerSecond.Bps256000;
                        samples = 0;
                        channel = 0;
                        format = (FormatValue) CommandFormat.ASCII;
                        // if because we are in a shared branch
                        if (streamName == null) streamName = "Command1";
                        return;
                    case 0x60:
                        break;
                    default:
                        throw new AssertionError("Unknown Protocol: " + protocol);
                }

                throw new UnsupportedOperationException("Unsupported Protocol: " + protocol);
            }

            public Protocol getProtocol() {
                return protocol;
            }

            public DataRateValue getSampleRate() {
                return sampleRate;
            }

            public Builder setSRValue(DataRateValue sampleRate) {
                this.sampleRate = sampleRate;
                return this;
            }

            public int getSamples() {
                return samples;
            }

            public Builder setSamples(byte samples) {
                this.samples = samples - 1;
                return this;
            }

            public int getChannel() {
                return channel;
            }

            public Builder setChannel(byte channel) {
                this.channel = channel - 1;
                return this;
            }

            public FormatValue getFormat() {
                return format;
            }

            public Builder setFormatValue(FormatValue format) {
                this.format = format;
                return this;
            }

            public int getCodec() {
                return codec;
            }

            public Builder setCodec(int codec) {
                this.codec = codec;
                return this;
            }

            public String getStreamName() {
                return streamName;
            }

            public Builder setStreamName(String streamName) {
                this.streamName = streamName;
                return this;
            }

            @Override
            public Factory build() {
                assert protocol != null : "No protocol defined!";

                return new Factory<>(protocol, sampleRate, samples, channel, format, codec, streamName);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy