de.kaleidox.vban.packet.VBANPacketHead Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vban-api Show documentation
Show all versions of vban-api Show documentation
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 super T> sampleRate,
int samples,
int channel,
FormatValue super T> 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 super T> sampleRate;
private int samples;
private int channel;
private FormatValue super T> 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 super T> getSampleRate() {
return sampleRate;
}
public Builder setSRValue(DataRateValue super T> 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 super T> getFormat() {
return format;
}
public Builder setFormatValue(FormatValue super T> 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);
}
}
}
}