/*
Copyright 2012 Kasper Sk å rh ø j , SKAARHOJ , kasperskaarhoj @ gmail . com
This file is part of the ATEM library for Arduino
The ATEM library is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by the
Free Software Foundation , either version 3 of the License , or ( at your
option ) any later version .
The ATEM library is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE .
See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License along
with the ATEM library . If not , see http : //www.gnu.org/licenses/.
*/
# if defined(ARDUINO) && ARDUINO >= 100
# include "Arduino.h"
# else
# include "WProgram.h"
# endif
# include "ATEM.h"
/**
* Constructor , setting up IP address for the switcher ( and local port to send packets from )
*/
ATEM : : ATEM ( IPAddress ip , uint16_t localPort ) {
// Set up Udp communication object:
EthernetUDP Udp ;
_Udp = Udp ;
_switcherIP = ip ; // Set switcher IP address
_localPort = localPort ; // Set local port (just a random number I picked)
_serialOutput = false ;
}
/**
* Initiating connection handshake to the ATEM switcher
*/
void ATEM : : connect ( ) {
_localPacketIdCounter = 1 ; // Init localPacketIDCounter to 1;
_hasInitialized = false ;
_Udp . begin ( _localPort ) ;
// Send connectString to ATEM:
// TODO: Describe packet contents according to rev.eng. API
byte connectHello [ ] = {
0x10 , 0x14 , 0x53 , 0xAB , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x3A , 0x00 , 0x00 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 } ;
_Udp . beginPacket ( _switcherIP , 9910 ) ;
_Udp . write ( connectHello , 20 ) ;
_Udp . endPacket ( ) ;
// Waiting for the ATEM to answer back with a packet 20 bytes long.
// According to packet analysis with WireShark, this feedback from ATEM
// comes within a few microseconds!
uint16_t packetSize = 0 ;
while ( packetSize ! = 20 ) {
packetSize = _Udp . parsePacket ( ) ;
}
// Read the response packet. We will only subtract the session ID
// According to packet analysis with WireShark, this feedback from ATEM
// comes a few microseconds after our connect invitation above. Two packets immediately follow each other.
// After approx. 200 milliseconds a third packet is sent from ATEM - a sort of re-sent because it gets impatient.
// And it seems that THIS third packet is the one we actually read and respond to. In other words, I believe that
// the ethernet interface on Arduino actually misses the first two for some reason!
while ( ! _Udp . available ( ) ) { } // Waiting.... TODO: Implement some way to exit if there is no answer!
_Udp . read ( _packetBuffer , 20 ) ;
_sessionID = _packetBuffer [ 15 ] ;
// Send connectAnswerString to ATEM:
_Udp . beginPacket ( _switcherIP , 9910 ) ;
// TODO: Describe packet contents according to rev.eng. API
byte connectHelloAnswerString [ ] = {
0x80 , 0x0c , 0x53 , 0xab , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x03 , 0x00 , 0x00 } ;
_Udp . write ( connectHelloAnswerString , 12 ) ;
_Udp . endPacket ( ) ;
}
/**
* Keeps connection to the switcher alive - basically , this means answering back to ping packages .
* Therefore : Call this in the Arduino loop ( ) function and make sure it gets call at least 2 times a second
* Other recommendations might come up in the future .
*/
void ATEM : : runLoop ( ) {
// WARNING:
// It can cause severe timing problems using "slow" functions such as Serial.print*()
// in the runloop, in particular during "boot" where the ATEM delivers some 10-20 kbytes of system status info which
// must exit the RX-buffer quite fast. Therefore, using Serial.print for debugging in this
// critical phase will in it self affect program execution!
// Limit of the RX buffer of the Ethernet interface is another general issue.
// When ATEM sends the initial system status packets (10-20 kbytes), they are sent with a few microseconds in between
// The RX buffer of the Ethernet interface on Arduino simply does not have the kapacity to take more than 2k at a time.
// This means, that we only receive the first packet, the others seems to be discarded. Luckily most information we like to
// know about is in the first packet (and some in the second, the rest is probably thumbnails for the media player).
// It may be possible to bump up this buffer to 4 or 8 k by simply re-configuring the amount of allowed sockets on the interface.
// For some more information from a guy seemingly having a similar issue, look here:
// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1282170842
// If there's data available, read a packet
uint16_t packetSize = _Udp . parsePacket ( ) ;
if ( _Udp . available ( ) & & packetSize ! = 0 ) {
// Read packet header of 12 bytes:
_Udp . read ( _packetBuffer , 12 ) ;
// Read out packet length (first word), remote packet ID number and "command":
uint16_t packetLength = word ( _packetBuffer [ 0 ] & B00000111 , _packetBuffer [ 1 ] ) ;
_lastRemotePacketID = word ( _packetBuffer [ 10 ] , _packetBuffer [ 11 ] ) ;
uint8_t command = _packetBuffer [ 0 ] & B11111000 ;
boolean command_ACK = command & B00001000 ? true : false ; // If true, ATEM expects an acknowledgement answer back!
// The five bits in "command" (from LSB to MSB):
// 1 = ACK, "Please respond to this packet" (using the _lastRemotePacketID). Exception: The initial 10-20 kbytes of Switcher status
// 2 = ?. Set during initialization? (first hand-shake packets contains that)
// 3 = "This is a retransmission". You will see this bit set if the ATEM switcher did not get a timely response to a packet.
// 4 = ? ("hello packet" according to "ratte", forum at atemuser.com)
// 5 = "This is a response on your request". So set this when answering...
if ( packetSize = = packetLength ) { // Just to make sure these are equal, they should be!
// If a packet is 12 bytes long it indicates that all the initial information
// has been delivered from the ATEM and we can begin to answer back on every request
// Currently we don't know any other way to decide if an answer should be sent back...
if ( ! _hasInitialized & & packetSize = = 12 ) {
_hasInitialized = true ;
if ( _serialOutput ) Serial . println ( " _hasInitialized=TRUE " ) ;
}
if ( packetLength > 12 ) {
_parsePacket ( packetLength ) ;
}
// If we are initialized, lets answer back no matter what:
// TODO: "_hasInitialized && " should be inserted back before "command_ACK" but
// with Arduino 1.0 UDP library it has proven MORE likely that the initial
// connection is made if we ALWAYS answer the switcher back.
// Apparently the initial "chaos" of keeping up with the incoming data confuses
// the UDP library so that we might never get initialized - and thus never get connected
// So... for now this is how we do it:
if ( command_ACK ) {
if ( _serialOutput ) {
Serial . print ( " ACK, rpID: " ) ;
Serial . println ( _lastRemotePacketID , DEC ) ;
}
_sendAnswerPacket ( _lastRemotePacketID ) ;
}
} else {
if ( _serialOutput ) {
Serial . print ( " ERROR: Packet size mismatch: " ) ;
Serial . print ( packetSize , DEC ) ;
Serial . print ( " != " ) ;
Serial . println ( packetLength , DEC ) ;
}
// Flushing the buffer:
// TODO: Other way? _Udp.flush() ??
while ( _Udp . available ( ) ) {
_Udp . read ( _packetBuffer , 96 ) ;
}
}
}
}
/**
* If a package longer than a normal acknowledgement is received from the ATEM Switcher we must read through the contents .
* Usually such a package contains updated state information about the mixer
* Selected information is extracted in this function and transferred to internal variables in this library .
*/
void ATEM : : _parsePacket ( uint16_t packetLength ) {
uint8_t idx ; // General reusable index usable for keyers, mediaplayer etc below.
// If packet is more than an ACK packet (= if its longer than 12 bytes header), lets parse it:
uint16_t indexPointer = 12 ;
while ( indexPointer < packetLength ) {
// Read the length of segment (first word):
_Udp . read ( _packetBuffer , 2 ) ;
uint16_t cmdLength = word ( 0 , _packetBuffer [ 1 ] ) ;
// If length of segment fits into buffer, lets read it, otherwise throw an error:
if ( cmdLength > 2 & & cmdLength < = 96 ) {
// Read the rest of the segment:
_Udp . read ( _packetBuffer , cmdLength - 2 ) ;
// Get the "command string", basically this is the 4 char variable name in the ATEM memory holding the various state values of the system:
char cmdStr [ ] = {
_packetBuffer [ - 2 + 4 ] , _packetBuffer [ - 2 + 5 ] , _packetBuffer [ - 2 + 6 ] , _packetBuffer [ - 2 + 7 ] , ' \0 ' } ;
// Extract the specific state information we like to know about:
if ( strcmp ( cmdStr , " PrgI " ) = = 0 ) { // Program Bus status
_ATEM_PrgI = _packetBuffer [ - 2 + 8 + 1 ] ;
if ( _serialOutput ) Serial . print ( " Program Bus: " ) ;
if ( _serialOutput ) Serial . println ( _ATEM_PrgI , DEC ) ;
} else
if ( strcmp ( cmdStr , " PrvI " ) = = 0 ) { // Preview Bus status
_ATEM_PrvI = _packetBuffer [ - 2 + 8 + 1 ] ;
if ( _serialOutput ) Serial . print ( " Preview Bus: " ) ;
if ( _serialOutput ) Serial . println ( _packetBuffer [ - 2 + 8 + 1 ] , DEC ) ;
} else
if ( strcmp ( cmdStr , " TlIn " ) = = 0 ) { // Tally status for inputs 1-8
// Inputs 1-8, bit 0 = Prg tally, bit 1 = Prv tally. Both can be set simultaneously.
_ATEM_TlIn [ 0 ] = _packetBuffer [ - 2 + 8 + 2 ] ;
_ATEM_TlIn [ 1 ] = _packetBuffer [ - 2 + 8 + 3 ] ;
_ATEM_TlIn [ 2 ] = _packetBuffer [ - 2 + 8 + 4 ] ;
_ATEM_TlIn [ 3 ] = _packetBuffer [ - 2 + 8 + 5 ] ;
_ATEM_TlIn [ 4 ] = _packetBuffer [ - 2 + 8 + 6 ] ;
_ATEM_TlIn [ 5 ] = _packetBuffer [ - 2 + 8 + 7 ] ;
_ATEM_TlIn [ 6 ] = _packetBuffer [ - 2 + 8 + 8 ] ;
_ATEM_TlIn [ 7 ] = _packetBuffer [ - 2 + 8 + 9 ] ;
if ( _serialOutput ) Serial . println ( " Tally updated: " ) ;
} else
if ( strcmp ( cmdStr , " Time " ) = = 0 ) { // Time. What is this anyway?
} else
if ( strcmp ( cmdStr , " TrPr " ) = = 0 ) { // Transition Preview
_ATEM_TrPr = _packetBuffer [ - 2 + 8 + 1 ] > 0 ? true : false ;
if ( _serialOutput ) Serial . print ( " Transition Preview: " ) ;
if ( _serialOutput ) Serial . println ( _ATEM_TrPr , BIN ) ;
} else
if ( strcmp ( cmdStr , " TrPs " ) = = 0 ) { // Transition Position
_ATEM_TrPs_frameCount = _packetBuffer [ - 2 + 8 + 2 ] ; // Frames count down
_ATEM_TrPs_position = _packetBuffer [ - 2 + 8 + 4 ] * 256 + _packetBuffer [ - 2 + 8 + 5 ] ; // Position 0-1000
} else
if ( strcmp ( cmdStr , " TrSS " ) = = 0 ) { // Transition Style and Keyer on next transition
_ATEM_TrSS_KeyersOnNextTransition = _packetBuffer [ - 2 + 8 + 2 ] & B11111 ; // Bit 0: Background; Bit 1-4: Key 1-4
if ( _serialOutput ) Serial . print ( " Keyers on Next Transition: " ) ;
if ( _serialOutput ) Serial . println ( _ATEM_TrSS_KeyersOnNextTransition , BIN ) ;
_ATEM_TrSS_TransitionStyle = _packetBuffer [ - 2 + 8 + 1 ] ;
if ( _serialOutput ) Serial . print ( " Transition Style: " ) ; // 0=MIX, 1=DIP, 2=WIPE, 3=DVE, 4=STING
if ( _serialOutput ) Serial . println ( _ATEM_TrSS_TransitionStyle , DEC ) ;
} else
if ( strcmp ( cmdStr , " FtbS " ) = = 0 ) { // Fade To Black State
_ATEM_FtbS_frameCount = _packetBuffer [ - 2 + 8 + 2 ] ; // Frames count down
} else
if ( strcmp ( cmdStr , " DskS " ) = = 0 ) { // Downstream Keyer state. Also contains information about the frame count in case of "Auto"
idx = _packetBuffer [ - 2 + 8 + 0 ] ;
if ( idx > = 0 & & idx < = 1 ) {
_ATEM_DskOn [ idx ] = _packetBuffer [ - 2 + 8 + 1 ] > 0 ? true : false ;
if ( _serialOutput ) Serial . print ( " Dsk Keyer " ) ;
if ( _serialOutput ) Serial . print ( idx + 1 ) ;
if ( _serialOutput ) Serial . print ( " : " ) ;
if ( _serialOutput ) Serial . println ( _ATEM_DskOn [ idx ] , BIN ) ;
}
} else
if ( strcmp ( cmdStr , " DskP " ) = = 0 ) { // Downstream Keyer Tie
idx = _packetBuffer [ - 2 + 8 + 0 ] ;
if ( idx > = 0 & & idx < = 1 ) {
_ATEM_DskTie [ idx ] = _packetBuffer [ - 2 + 8 + 1 ] > 0 ? true : false ;
if ( _serialOutput ) Serial . print ( " Dsk Keyer " ) ;
if ( _serialOutput ) Serial . print ( idx + 1 ) ;
if ( _serialOutput ) Serial . print ( " Tie: " ) ;
if ( _serialOutput ) Serial . println ( _ATEM_DskTie [ idx ] , BIN ) ;
}
} else
if ( strcmp ( cmdStr , " KeOn " ) = = 0 ) { // Upstead Keyer on
idx = _packetBuffer [ - 2 + 8 + 1 ] ;
if ( idx > = 0 & & idx < = 3 ) {
_ATEM_KeOn [ idx ] = _packetBuffer [ - 2 + 8 + 2 ] > 0 ? true : false ;
if ( _serialOutput ) Serial . print ( " Upstream Keyer " ) ;
if ( _serialOutput ) Serial . print ( idx + 1 ) ;
if ( _serialOutput ) Serial . print ( " : " ) ;
if ( _serialOutput ) Serial . println ( _ATEM_KeOn [ idx ] , BIN ) ;
}
} else
if ( strcmp ( cmdStr , " ColV " ) = = 0 ) { // Color Generator Change
// Todo: Relatively easy: 8 bytes, first is the color generator, the last 6 is hsl words
} else
if ( strcmp ( cmdStr , " MPCE " ) = = 0 ) { // Media Player Clip Enable
idx = _packetBuffer [ - 2 + 8 + 0 ] ;
if ( idx > = 0 & & idx < = 1 ) {
_ATEM_MPType [ idx ] = _packetBuffer [ - 2 + 8 + 1 ] ;
_ATEM_MPStill [ idx ] = _packetBuffer [ - 2 + 8 + 2 ] ;
_ATEM_MPClip [ idx ] = _packetBuffer [ - 2 + 8 + 3 ] ;
}
} else
if ( strcmp ( cmdStr , " AuxS " ) = = 0 ) { // Aux Output Source
uint8_t auxInput = _packetBuffer [ - 2 + 8 + 0 ] ;
if ( auxInput > = 0 & & auxInput < = 2 ) {
_ATEM_AuxS [ auxInput ] = _packetBuffer [ - 2 + 8 + 1 ] ;
if ( _serialOutput ) Serial . print ( " Aux " ) ;
if ( _serialOutput ) Serial . print ( auxInput + 1 ) ;
if ( _serialOutput ) Serial . print ( " Output: " ) ;
if ( _serialOutput ) Serial . println ( _ATEM_AuxS [ auxInput ] , DEC ) ;
}
} else
if ( _hasInitialized ) { // All the rest...
if ( _serialOutput ) {
Serial . print ( " ???? Unknown token: " ) ;
Serial . print ( cmdStr ) ;
Serial . print ( " : " ) ;
}
for ( uint8_t a = ( - 2 + 8 ) ; a < cmdLength - 2 ; a + + ) {
if ( _serialOutput ) Serial . print ( ( uint8_t ) _packetBuffer [ a ] , HEX ) ;
if ( _serialOutput ) Serial . print ( " " ) ;
}
if ( _serialOutput ) Serial . println ( " " ) ;
}
indexPointer + = cmdLength ;
} else {
// Error, just get out of the loop ASAP:
if ( _serialOutput ) Serial . print ( " ERROR: Command Size mismatch: " ) ;
if ( _serialOutput ) Serial . print ( cmdLength , DEC ) ;
indexPointer = 2000 ;
// Flushing the buffer:
// TODO: Other way? _Udp.flush() ??
while ( _Udp . available ( ) ) {
_Udp . read ( _packetBuffer , 96 ) ;
}
}
}
}
/**
* Sending a regular answer packet back ( tell the switcher that " we heard you, thanks. " )
*/
void ATEM : : _sendAnswerPacket ( uint16_t remotePacketID ) {
//Answer packet:
memset ( _answer , 0 , 12 ) ; // Using 12 bytes of answer buffer, setting to zeros.
_answer [ 2 ] = 0x80 ; // ??? API
_answer [ 3 ] = _sessionID ; // Session ID
_answer [ 4 ] = remotePacketID / 256 ; // Remote Packet ID, MSB
_answer [ 5 ] = remotePacketID % 256 ; // Remote Packet ID, LSB
_answer [ 9 ] = 0x41 ; // ??? API
// The rest is zeros.
// Create header:
uint16_t returnPacketLength = 10 + 2 ;
_answer [ 0 ] = returnPacketLength / 256 ;
_answer [ 1 ] = returnPacketLength % 256 ;
_answer [ 0 ] | = B10000000 ;
// Send connectAnswerString to ATEM:
_Udp . beginPacket ( _switcherIP , 9910 ) ;
_Udp . write ( _answer , returnPacketLength ) ;
_Udp . endPacket ( ) ;
}
/**
* Sending a command packet back ( ask the switcher to do something )
*/
void ATEM : : _sendCommandPacket ( char cmd [ 4 ] , uint8_t commandBytes [ 16 ] , uint8_t cmdBytes ) {
if ( cmdBytes < = 16 ) { // Currently, only a lenght up to 16 - can be extended, but then the _answer buffer must be prolonged as well (to more than 36)
//Answer packet preparations:
memset ( _answer , 0 , 36 ) ;
_answer [ 2 ] = 0x80 ; // ??? API
_answer [ 3 ] = _sessionID ; // Session ID
_answer [ 10 ] = _localPacketIdCounter / 256 ; // Remote Packet ID, MSB
_answer [ 11 ] = _localPacketIdCounter % 256 ; // Remote Packet ID, LSB
// The rest is zeros.
// Command identifier (4 bytes, after header (12 bytes) and local segment length (4 bytes)):
int i ;
for ( i = 0 ; i < 4 ; i + + ) {
_answer [ 12 + 4 + i ] = cmd [ i ] ;
}
// Command value (after command):
for ( i = 0 ; i < cmdBytes ; i + + ) {
_answer [ 12 + 4 + 4 + i ] = commandBytes [ i ] ;
}
// Command length:
_answer [ 12 ] = ( 4 + 4 + cmdBytes ) / 256 ;
_answer [ 12 + 1 ] = ( 4 + 4 + cmdBytes ) % 256 ;
// Create header:
uint16_t returnPacketLength = 10 + 2 + ( 4 + 4 + cmdBytes ) ;
_answer [ 0 ] = returnPacketLength / 256 ;
_answer [ 1 ] = returnPacketLength % 256 ;
_answer [ 0 ] | = B00001000 ;
// Send connectAnswerString to ATEM:
_Udp . beginPacket ( _switcherIP , 9910 ) ;
_Udp . write ( _answer , returnPacketLength ) ;
_Udp . endPacket ( ) ;
_localPacketIdCounter + + ;
}
}
/********************************
*
* General Getter / Setter methods
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* Setter method : If _serialOutput is set , the library may use Serial . print ( ) to give away information about its operation - mostly for debugging .
*/
void ATEM : : serialOutput ( boolean serialOutput ) {
_serialOutput = serialOutput ;
}
/**
* Getter method : If true , the initial handshake and " stressful " information exchange has occured and now the switcher connection should be ready for operation .
*/
bool ATEM : : hasInitialized ( ) {
return _hasInitialized ;
}
/**
* Returns last Remote Packet ID
*/
uint16_t ATEM : : getATEM_lastRemotePacketId ( ) {
return _lastRemotePacketID ;
}
/********************************
*
* ATEM Switcher state methods
* Returns the most recent information we ' ve
* got about the switchers state
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
uint8_t ATEM : : getProgramInput ( ) {
return _ATEM_PrgI ;
}
uint8_t ATEM : : getPreviewInput ( ) {
return _ATEM_PrvI ;
}
boolean ATEM : : getProgramTally ( uint8_t inputNumber ) {
// TODO: Validate that input number exists on current model! <8 at the moment.
return ( _ATEM_TlIn [ inputNumber - 1 ] & 1 ) > 0 ? true : false ;
}
boolean ATEM : : getPreviewTally ( uint8_t inputNumber ) {
// TODO: Validate that input number exists on current model! 1-8 at the moment.
return ( _ATEM_TlIn [ inputNumber - 1 ] & 2 ) > 0 ? true : false ;
}
/********************************
*
* ATEM Switcher Change methods
* Asks the switcher to changes something
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void ATEM : : changeProgramInput ( uint8_t inputNumber ) {
// TODO: Validate that input number exists on current model!
// On ATEM 1M/E: Black (0), 1 (1), 2 (2), 3 (3), 4 (4), 5 (5), 6 (6), 7 (7), 8 (8), Bars (9), Color1 (10), Color 2 (11), Media 1 (12), Media 2 (14)
uint8_t commandBytes [ 4 ] = { 0 , inputNumber , 0 , 0 } ;
_sendCommandPacket ( " CPgI " , commandBytes , 4 ) ;
}
void ATEM : : changePreviewInput ( uint8_t inputNumber ) {
// TODO: Validate that input number exists on current model!
uint8_t commandBytes [ 4 ] = { 0 , inputNumber , 0 , 0 } ;
_sendCommandPacket ( " CPvI " , commandBytes , 4 ) ;
}
void ATEM : : doCut ( ) {
uint8_t commandBytes [ 4 ] = { 0 , 0xef , 0xbf , 0x5f } ; // I don't know what that actually means...
_sendCommandPacket ( " DCut " , commandBytes , 4 ) ;
}
void ATEM : : doAuto ( ) {
uint8_t commandBytes [ 4 ] = { 0 , 0x32 , 0x16 , 0x02 } ; // I don't know what that actually means...
_sendCommandPacket ( " DAut " , commandBytes , 4 ) ;
}
void ATEM : : fadeToBlackActivate ( ) {
uint8_t commandBytes [ 4 ] = { 0x00 , 0x02 , 0x58 , 0x99 } ;
_sendCommandPacket ( " FtbA " , commandBytes , 4 ) ; // Reflected back from ATEM in "FtbS"
}
void ATEM : : changeTransitionPosition ( word value ) {
if ( value > 0 & & value < = 1000 ) {
uint8_t commandBytes [ 4 ] = { 0 , 0xe4 , value / 256 , value % 256 } ;
_sendCommandPacket ( " CTPs " , commandBytes , 4 ) ; // Change Transition Position (CTPs)
}
}
void ATEM : : changeTransitionPositionDone ( ) { // When the last value of the transition is sent (1000), send this one too (we are done, change tally lights and preview bus!)
uint8_t commandBytes [ 4 ] = { 0 , 0xf6 , 0 , 0 } ; // Done
_sendCommandPacket ( " CTPs " , commandBytes , 4 ) ; // Change Transition Position (CTPs)
}
void ATEM : : changeTransitionPreview ( bool state ) {
uint8_t commandBytes [ 4 ] = { 0x00 , state ? 0x01 : 0x00 , 0x00 , 0x00 } ;
_sendCommandPacket ( " CTPr " , commandBytes , 4 ) ; // Reflected back from ATEM in "TrPr"
}
void ATEM : : changeTransitionType ( uint8_t type ) {
if ( type > = 0 & & type < = 4 ) { // 0=MIX, 1=DIP, 2=WIPE, 3=DVE, 4=STING
uint8_t commandBytes [ 4 ] = { 0x01 , 0x00 , type , 0x02 } ;
_sendCommandPacket ( " CTTp " , commandBytes , 4 ) ; // Reflected back from ATEM in "TrSS"
}
}
void ATEM : : changeUpstreamKeyOn ( uint8_t keyer , bool state ) {
if ( keyer > = 1 & & keyer < = 4 ) { // Todo: Should match available keyers depending on model?
uint8_t commandBytes [ 4 ] = { 0x00 , keyer - 1 , state ? 0x01 : 0x00 , 0x90 } ;
_sendCommandPacket ( " CKOn " , commandBytes , 4 ) ; // Reflected back from ATEM in "KeOn"
}
}
void ATEM : : changeUpstreamKeyNextTransition ( uint8_t keyer , bool state ) { // Not supporting "Background"
if ( keyer > = 1 & & keyer < = 4 ) { // Todo: Should match available keyers depending on model?
uint8_t stateValue = _ATEM_TrSS_KeyersOnNextTransition ;
if ( state ) {
stateValue = stateValue | ( B10 < < ( keyer - 1 ) ) ;
} else {
stateValue = stateValue & ( ~ ( B10 < < ( keyer - 1 ) ) ) ;
}
// TODO: Requires internal storage of state here so we can preserve all other states when changing the one we want to change.
uint8_t commandBytes [ 4 ] = { 0x02 , 0x00 , 0x6a , stateValue & B11111 } ;
_sendCommandPacket ( " CTTp " , commandBytes , 4 ) ; // Reflected back from ATEM in "TrSS"
}
}
void ATEM : : changeDownstreamKeyOn ( uint8_t keyer , bool state ) {
if ( keyer > = 1 & & keyer < = 2 ) { // Todo: Should match available keyers depending on model?
uint8_t commandBytes [ 4 ] = { keyer - 1 , state ? 0x01 : 0x00 , 0xff , 0xff } ;
_sendCommandPacket ( " CDsL " , commandBytes , 4 ) ; // Reflected back from ATEM in "DskP" and "DskS"
}
}
void ATEM : : changeDownstreamKeyTie ( uint8_t keyer , bool state ) {
if ( keyer > = 1 & & keyer < = 2 ) { // Todo: Should match available keyers depending on model?
uint8_t commandBytes [ 4 ] = { keyer - 1 , state ? 0x01 : 0x00 , 0xff , 0xff } ;
_sendCommandPacket ( " CDsT " , commandBytes , 4 ) ;
}
}
void ATEM : : doAutoDownstreamKeyer ( uint8_t keyer ) {
if ( keyer > = 1 & & keyer < = 2 ) { // Todo: Should match available keyers depending on model?
uint8_t commandBytes [ 4 ] = { keyer - 1 , 0x32 , 0x16 , 0x02 } ; // I don't know what that actually means...
_sendCommandPacket ( " DDsA " , commandBytes , 4 ) ;
}
}
void ATEM : : changeAuxState ( uint8_t auxOutput , uint8_t inputNumber ) {
// TODO: Validate that input number exists on current model!
// On ATEM 1M/E: Black (0), 1 (1), 2 (2), 3 (3), 4 (4), 5 (5), 6 (6), 7 (7), 8 (8), Bars (9), Color1 (10), Color 2 (11), Media 1 (12), Media 1 Key (13), Media 2 (14), Media 2 Key (15), Program (16), Preview (17), Clean1 (18), Clean 2 (19)
if ( auxOutput > = 1 & & auxOutput < = 3 ) { // Todo: Should match available aux outputs
uint8_t commandBytes [ 4 ] = { auxOutput - 1 , inputNumber , 0 , 0 } ;
_sendCommandPacket ( " CAuS " , commandBytes , 4 ) ;
}
}
void ATEM : : settingsMemorySave ( ) {
uint8_t commandBytes [ 4 ] = { 0 , 0 , 0 , 0 } ;
_sendCommandPacket ( " SRsv " , commandBytes , 4 ) ;
}
void ATEM : : settingsMemoryClear ( ) {
uint8_t commandBytes [ 4 ] = { 0 , 0 , 0 , 0 } ;
_sendCommandPacket ( " SRcl " , commandBytes , 4 ) ;
}
void ATEM : : changeColorValue ( uint8_t colorGenerator , uint16_t hue , uint16_t saturation , uint16_t lightness ) {
if ( colorGenerator > = 1 & & colorGenerator < = 2
& & hue > = 0 & & hue < = 3600
& & saturation > = 0 & & saturation < = 1000
& & lightness > = 0 & & lightness < = 1000
) { // Todo: Should match available aux outputs
uint8_t commandBytes [ 8 ] = { 0x07 , colorGenerator - 1 ,
highByte ( hue ) , lowByte ( hue ) ,
highByte ( saturation ) , lowByte ( saturation ) ,
highByte ( lightness ) , lowByte ( lightness )
} ;
_sendCommandPacket ( " CClV " , commandBytes , 8 ) ;
}
}
void ATEM : : mediaPlayerSelectSource ( uint8_t mediaPlayer , boolean movieclip , uint8_t sourceIndex ) {
if ( mediaPlayer > = 1 & & mediaPlayer < = 2 ) { // TODO: Adjust to particular ATEM model... (here 1M/E)
uint8_t commandBytes [ 12 ] ;
memset ( commandBytes , 0 , 12 ) ;
commandBytes [ 1 ] = mediaPlayer - 1 ;
if ( movieclip ) {
commandBytes [ 0 ] = 4 ;
if ( sourceIndex > = 1 & & sourceIndex < = 2 ) {
commandBytes [ 4 ] = sourceIndex - 1 ;
}
} else {
commandBytes [ 0 ] = 2 ;
if ( sourceIndex > = 1 & & sourceIndex < = 32 ) {
commandBytes [ 3 ] = sourceIndex - 1 ;
}
}
commandBytes [ 9 ] = 0x10 ;
_sendCommandPacket ( " MPSS " , commandBytes , 12 ) ;
// For some reason you have to send this command immediate after (or in fact it could be in the same packet)
// If not done, the clip will not change if there is a shift from stills to clips or vice versa.
uint8_t commandBytes2 [ 8 ] = { 0x01 , mediaPlayer - 1 , movieclip ? 2 : 1 , 0xbf , movieclip ? 0x96 : 0xd5 , 0xb6 , 0x04 , 0 } ;
_sendCommandPacket ( " MPSS " , commandBytes2 , 8 ) ;
}
}