All pastes #2465795 Raw Edit

OSCUtils

public unlisted text v1 · immutable
#2465795 ·published 2013-10-11 21:45 UTC
rendered paste body
//OSCUtils

//////description
//Utility library to encode/decode osc-packets
//inspired by original OSC reference implementation (OSC-Kit)
//and OSC.Net library as shipped with the TUIO-CSharp sample
//from http://reactable.iua.upf.edu/?software

//////licence
//GNU Lesser General Public License (LGPL)
//english: http://www.gnu.org/licenses/lgpl.html
//german: http://www.gnu.de/lgpl-ger.html

//////language/ide
//delphi

//////initial author
//joreg -> joreg@vvvv.org

//additions for FreePascal
//simon -> simonmoscrop@googlemail.com

//////instructions
////for use with FreePascal
//define: FPC

////encoding a single message:
//first create a message: msg := TOSCMessage.Create(address)
//then call msg.AddAsString(typetag, value) to add any number of arguments
//with  msg.ToOSCString you get its osc-string representation
////encoding a bundle:
//first create a bundle: bundle := TOSCBundle.Create
//then add any number of packets (i.e. message, bundle) via bundle.Add(packet)
//with bundle.ToOSCString you get its osc-string representation

////decoding a string
//use TOSCPacket.Unpack(PByte(osc-string), Length(osc-string)) to create
//TOSCPackets of your osc-strings (those can be either bundles or single
//messages. if you want to decode several packets at once you can create
//a container bundle first and add the packets you create like this.
//then use msg := FPacket.MatchAddress(address) to find a message with the
//according address in your packet-structure.
//before you now can access the arguments and typetags of a message you have
//to call msg.Decode
//voila.


unit OSCUtils;

interface

uses Classes, Contnrs;

type
  TOSCPacket = class;
  TOSCMessage = class;

  TOSCPacket = class (TObject)
  private
  protected
    FBytes: PByte;
    function MatchBrackets(pMessage, pAddress: PChar): Boolean;
    function MatchList(pMessage, pAddress: PChar): Boolean;
    function MatchPattern(pMessage, pAddress: PChar): Boolean;
  public
    constructor Create(Bytes: PByte);
    function MatchAddress(Address: String): TOSCMessage; virtual; abstract;
    function ToOSCString: string; virtual; abstract;
    procedure Unmatch; virtual; abstract;
    class function Unpack(Bytes: PByte; Count: Integer): TOSCPacket; overload;
    class function Unpack(Bytes: PByte; Offset, Count: Integer; TimeTag: Extended =
        0): TOSCPacket; overload; virtual;
  end;

  TOSCMessage = class(TOSCPacket)
  private
    FAddress: string;
    FArguments: TStringList;
    FIsDecoded: Boolean;
    FMatched: Boolean;
    FTimeTag: Extended;
    FTypeTagOffset: Integer;
    FTypeTags: string;
    function GetArgument(Index: Integer): string;
    function GetArgumentCount: Integer;
    function GetTypeTag(Index: Integer): string;
  public
    constructor Create(Address: string); overload;
    constructor Create(Bytes: PByte); overload;
    destructor Destroy; override;
    function AddAsString(TypeTag: Char; Value: String): HResult;
    procedure AddFloat(Value: Single);
    procedure AddInteger(Value: Integer);
    procedure AddString(Value: String);
    procedure Decode;
    function MatchAddress(Address: String): TOSCMessage; override;
    function ToOSCString: string; override;
    procedure Unmatch; override;
    class function Unpack(Bytes: PByte; PacketOffset, Count: Integer; TimeTag:
        Extended = 0): TOSCPacket; overload; override;
    property Address: string read FAddress write FAddress;
    property Argument[Index: Integer]: String read GetArgument;
    property ArgumentCount: Integer read GetArgumentCount;
    property IsDecoded: Boolean read FIsDecoded write FIsDecoded;
    property Matched: Boolean read FMatched write FMatched;
    property TimeTag: Extended read FTimeTag write FTimeTag;
    property TypeTag[Index: Integer]: String read GetTypeTag;
    property TypeTagOffset: Integer read FTypeTagOffset write FTypeTagOffset;
  end;

  TOSCBundle = class(TOSCPacket)
  private
    FPackets: TObjectList;
  public
    constructor Create(Bytes: PByte);
    destructor Destroy; override;
    procedure Add(const Packet: TOSCPacket);
    function MatchAddress(Address: String): TOSCMessage; override;
    function ToOSCString: string; override;
    procedure Unmatch; override;
    class function Unpack(Bytes: PByte; PacketOffset, Count: Integer; TimeTag:
        Extended = 0): TOSCPacket; overload; override;
  end;

  function MakeOSCFloat(value: Single): String;

  function MakeOSCInt(value: Integer): String;

  function MakeOSCString(value: String): String;

  function UnpackFloat(Bytes: PByte; var Offset: Integer): Single;

  function UnpackInt(Bytes: PByte; var Offset: Integer): Integer;

  function UnpackString(Bytes: PByte; var Offset: Integer): string;

  const
    OSC_OK = 0;
    OSC_UNRECOGNIZED_TYPETAG = 1;
    OSC_CONVERT_ERROR = 2;


implementation

uses
  SysUtils, Math {$IFNDEF FPC}, WinSock {$ENDIF};

function MakeOSCFloat(value: Single): String;
var
  tmp: Byte;
  intg, i: Integer;
begin
  result := '';
  intg := Integer(Pointer(value));
  {$IFDEF FPC}
  intg := BEtoN(intg);
  {$ELSE}
  intg := htonl(intg);
  {$ENDIF}
  for i := 0 to 3 do
  begin
    tmp := intg and $ff;
    result := result + chr(tmp);
    intg := intg shr 8;
  end;
end;

function MakeOSCInt(value: Integer): String;
var
  tmp: Byte;
  i, val: Integer;
begin
  result := '';
  {$IFDEF FPC}
  val := BEtoN(value);
  {$ELSE}
  val := htonl(value);
  {$ENDIF}
  for i := 0 to 3 do
  begin
    tmp := val and $ff;
    result := result + chr(tmp);
    val := val shr 8;
  end;
end;

function MakeOSCString(value: String): String;
var i, ln: Integer;
begin
  result := value;

  ln := 4 - (length(value)) mod 4;
  for i := 0 to ln - 1 do
    result := result + #0;
end;

function UnpackFloat(Bytes: PByte; var Offset: Integer): Single;
var
  i, value: Integer;
  tmp: PByte;
begin
  value := 0;
  tmp := Bytes;
  Inc(tmp, Offset);

  for i := 0 to 3 do
  begin
    value := value + tmp^ shl (i * 8);
    Inc(tmp);
  end;

  Inc(Offset, 4);

  {$IFDEF FPC}
  value := NtoBE(value);
  {$ELSE}
  value := ntohl(value);
  {$ENDIF}
  Result := Single(Pointer(value));
end;

function UnpackInt(Bytes: PByte; var Offset: Integer): Integer;
var
  i, value: Integer;
  tmp: PByte;
begin
  value := 0;
  tmp := Bytes;
  Inc(tmp, Offset);

  for i := 0 to 3 do
  begin
    value := value + tmp^ shl (i * 8);
    Inc(tmp);
  end;

  Inc(Offset, 4);
  {$IFDEF FPC}
  Result := NtoBE(value);
  {$ELSE}
  Result := ntohl(value);
  {$ENDIF}
end;

function UnpackString(Bytes: PByte; var Offset: Integer): string;
var
  tmp: PByte;
  off: Integer;
begin
  tmp := Bytes;
  Inc(tmp, Offset);

  Result := PChar(tmp);
  off := Length(PChar(tmp));
  off := off + (4 - off mod 4);
  Inc(Offset, off)
end;


constructor TOSCMessage.Create(Address: string);
begin
  FAddress := Address;
  Create(nil);
end;

constructor TOSCMessage.Create(Bytes: PByte);
begin
  inherited;

  FTypeTags := ',';
  FArguments := TStringList.Create;
  FIsDecoded := false;
end;

destructor TOSCMessage.Destroy;
begin
  FArguments.Free;
  inherited;
end;

function TOSCMessage.AddAsString(TypeTag: Char; Value: String): HResult;
begin
  Result := OSC_OK;

  try
    if TypeTag = 'f' then
      FArguments.Add(MakeOSCFloat(StrToFloat(Value)))
    else if TypeTag = 'i' then
      FArguments.Add(MakeOSCInt(StrToInt(Value)))
    else if TypeTag = 's' then
      FArguments.Add(MakeOSCString(Value))
    else
      Result := OSC_UNRECOGNIZED_TYPETAG;
  except on EConvertError do
    Result := OSC_CONVERT_ERROR;
  end;

  if Result  = OSC_OK then
    FTypeTags := FTypeTags + TypeTag;
end;

procedure TOSCMessage.AddFloat(Value: Single);
begin
  FTypeTags := FTypeTags + 'f';
  FArguments.Add(MakeOSCFloat(Value));
end;

procedure TOSCMessage.AddInteger(Value: Integer);
begin
  FTypeTags := FTypeTags + 'i';
  FArguments.Add(MakeOSCInt(Value));
end;

procedure TOSCMessage.AddString(Value: String);
begin
  FTypeTags := FTypeTags + 's';
  FArguments.Add(MakeOSCString(Value));
end;

procedure TOSCMessage.Decode;
var
  i, offset: Integer;
begin
  if FIsDecoded then
    exit;

  offset := FTypeTagOffset;
  FTypeTags := UnpackString(FBytes, offset);

  for i := 1 to Length(FTypeTags) - 1 do
  begin
    if FTypeTags[i+1] = 's' then
      FArguments.Add(UnpackString(FBytes, offset))
    else if FTypeTags[i+1] = 'i' then
      FArguments.Add(IntToStr(UnpackInt(FBytes, offset)))
    else if FTypeTags[i+1] = 'f' then
      FArguments.Add(FloatToStr(UnpackFloat(FBytes, offset)));
  end;

  FIsDecoded := true;
end;

function TOSCMessage.GetArgument(Index: Integer): string;
begin
  Result := FArguments[Index];
end;

function TOSCMessage.GetArgumentCount: Integer;
begin
  Result := FArguments.Count;
end;

function TOSCMessage.GetTypeTag(Index: Integer): string;
begin
  Result := FTypeTags[Index + 2];
end;

function TOSCMessage.MatchAddress(Address: String): TOSCMessage;
begin
  if not FMatched
  and MatchPattern(PChar(FAddress), PChar(Address)) then
  begin
    FMatched := true;
    Result := Self
  end
  else
    Result := nil;
end;

function TOSCMessage.ToOSCString: string;
var
  i: Integer;
begin
  Result := MakeOSCString(FAddress) + MakeOSCString(FTypeTags);

  for i := 0 to FArguments.Count - 1 do
    Result := Result + FArguments[i];
end;

procedure TOSCMessage.Unmatch;
begin
  FMatched := false;
end;

class function TOSCMessage.Unpack(Bytes: PByte; PacketOffset, Count: Integer;
    TimeTag: Extended = 0): TOSCPacket;
begin
  Result := TOSCMessage.Create(Bytes);
  //for now decode address only
  (Result as TOSCMessage).Address := UnpackString(Bytes, PacketOffset);
  (Result as TOSCMessage).TimeTag := TimeTag;

  //save offset for later decoding on demand
 (Result as TOSCMessage).TypeTagOffset := PacketOffset;
 (Result as TOSCMessage).IsDecoded := false;
end;

constructor TOSCBundle.Create(Bytes: PByte);
begin
  inherited;
  FPackets := TObjectList.Create;
  FPackets.OwnsObjects := true;
end;

destructor TOSCBundle.Destroy;
begin
  FPackets.Free;
  inherited;
end;

procedure TOSCBundle.Add(const Packet: TOSCPacket);
begin
  FPackets.Add(Packet);
end;

function TOSCBundle.MatchAddress(Address: String): TOSCMessage;
var
  i: Integer;
begin
  Result := nil;

  for i := 0 to FPackets.Count - 1 do
  begin
    Result := (FPackets[i] as TOSCPacket).MatchAddress(Address);
    if Assigned(Result) then
      break;
  end;
end;

function TOSCBundle.ToOSCString: string;
var
  timeTag: String;
  i: Integer;
  packet: String;
begin
  timeTag := #0#0#0#0#0#0#0#1; //immediately
  Result := MakeOSCString('#bundle') + timeTag;

  for i := 0 to FPackets.Count - 1 do
  begin
    packet := (FPackets[i] as TOSCPacket).ToOSCString;
    Result := Result + MakeOSCInt(Length(packet)) + packet;
  end;
end;

procedure TOSCBundle.Unmatch;
var
  i: Integer;
begin
  for i := 0 to FPackets.Count - 1 do
    (FPackets[i] as TOSCPacket).UnMatch;
end;

class function TOSCBundle.Unpack(Bytes: PByte; PacketOffset, Count: Integer;
    TimeTag: Extended = 0): TOSCPacket;
var
  packetLength: Integer;
  tt1, tt2: Cardinal;
begin
  Result := TOSCBundle.Create(Bytes);

  //advance the '#bundle' string
  UnpackString(Bytes, PacketOffset);

  //advance the timestamp
  tt1 := Cardinal(UnpackInt(Bytes, PacketOffset));
  tt2 := Cardinal(UnpackInt(Bytes, PacketOffset));

  TimeTag := tt1 + tt2 / power(2, 32);

  while PacketOffset < Count do
  begin
    packetLength := UnpackInt(Bytes, PacketOffset);
    //note: PacketOffset is always from the very beginning of Bytes!
    //not the beginning of the current packet.
    (Result as TOSCBundle).Add(TOSCPacket.Unpack(Bytes, PacketOffset, PacketOffset + packetLength, TimeTag));
    Inc(PacketOffset, packetLength);
  end;
end;

constructor TOSCPacket.Create(Bytes: PByte);
begin
  FBytes := Bytes;
end;

// we know that pattern[0] == '[' and test[0] != 0 */
function TOSCPacket.MatchBrackets(pMessage, pAddress: PChar): Boolean;
var
  negated: Boolean;
  p, p1, p2: PChar;
begin
  p := pMessage;
  Result := false;
  negated := false;

  Inc(pMessage);
  if pMessage^ = #0 then
  begin
    //LogWarningFMT('Unterminated [ in message: %s', [FInput[0]]);
    Dec(pMessage);
    exit;
  end;

  if pMessage^ = '!' then
  begin
    negated := true;
    Inc(p);
  end;

  Dec(pMessage);

  Result := negated;

  while p^ <> ']' do
  begin
    if p^ = #0 then
    begin
      //LogWarningFMT('Unterminated [ in message: %s', [FInput[0]]);
      exit;
    end;

    p1 := p + 1; // sizeOf(PChar);
    p2 := p1 + 1; //sizeOf(PChar);

    if (p1^ = '-')
    and (p2^ <> #0) then
      if (Ord(pAddress^) >= Ord(p^))
      and (Ord(pAddress^) <= Ord(p2^)) then
      begin
        Result := not negated;
        break;
      end;

    if p^ = pAddress^ then
    begin
      Result := not negated;
      break;
    end;

    Inc(p);
  end;

  if Result = false then
    exit;

  while p^ <> ']' do
  begin
    if p^ = #0 then
    begin
      //LogWarningFMT('Unterminated [ in message: %s', [FInput[0]]);
      exit;
    end;

    Inc(p);
  end;

  Inc(p);
  pMessage := p;
  Inc(pAddress);
  Result := MatchPattern(p, pAddress);
end;

function TOSCPacket.MatchList(pMessage, pAddress: PChar): Boolean;
var
  p, tp: PChar;
begin
  Result := false;

  p := pMessage;
  tp := pAddress;

  while p^ <> '}' do
  begin
    if p^ = #0 then
    begin
      //LogWarningFMT('Unterminated { in message: %s', [FInput[0]]);
      exit;
    end;

    Inc(p);
  end;


// for(restOfPattern = pattern; *restOfPattern != '}'; restOfPattern++) {
//  if (*restOfPattern == 0) {
//    OSCWarning("Unterminated { in pattern \".../%s/...\"", theWholePattern);
//    return FALSE;
//  }
//}

  Inc(p); // skip close curly brace
  Inc(pMessage); // skip open curly brace

  while true do
  begin
    if pMessage^ = ',' then
    begin
      if MatchPattern(p, tp) then
      begin
        Result := true;
        pMessage := p;
        pAddress := tp;
        exit;
      end
      else
      begin
        tp := pAddress;
        Inc(pMessage);
      end;
    end
    else if pMessage^ = '}' then
    begin
      Result := MatchPattern(p, tp);
      pMessage := p;
      pAddress := tp;
      exit;
    end
    else if pMessage^ = tp^ then
    begin
      Inc(pMessage);
      Inc(tp);
    end
    else
    begin
      tp := pAddress;
      while (pMessage^ <> ',')
        and (pMessage^ <> '}') do
          Inc(pMessage);

      if pMessage^ = ',' then
        Inc(pMessage);
    end;
  end;
end;

function TOSCPacket.MatchPattern(pMessage, pAddress: PChar): Boolean;
begin
  if (pMessage = nil)
  or (pMessage^ = #0) then
  begin
    Result := pAddress^ = #0;
    exit;
  end;

  if pAddress^ = #0 then
  begin
    if pMessage^ = '*' then
    begin
      Result := MatchPattern(pMessage + 1, pAddress);
      exit;
    end
    else
    begin
      Result := false;
      exit;
    end;
  end;

  case pMessage^ of
  #0 : Result := pAddress^ = #0;
  '?': Result := MatchPattern(pMessage + 1, pAddress + 1);
  '*':
  begin
      if MatchPattern(pMessage + 1, pAddress) then
        Result := true
      else
        Result := MatchPattern(pMessage, pAddress + 1);
  end;
  ']','}':
  begin
    //LogWarningFMT('Spurious %s in message: %s', [pMessage^, FInput[0]]);
    Result := false;
  end;
  '[': Result := MatchBrackets(pMessage, pAddress);
  '{': Result := MatchList(pMessage, pAddress);
  {'\\':
  begin
    if pMessage^ + 1 = #0 then
      Result := pAddress^ = #0
    else if pMessage^ + 1 = pAddress^
      Result := MatchPattern(pMessage + 2, pAddress + 1)
    else
      Result := false;
  end;   }
  else
  if pMessage^ = pAddress^ then
    Result := MatchPattern(pMessage + 1,pAddress + 1)
  else
    Result := false;
  end;
end;

class function TOSCPacket.Unpack(Bytes: PByte; Count: Integer): TOSCPacket;
begin
  Result := UnPack(Bytes, 0, Count);
end;

class function TOSCPacket.Unpack(Bytes: PByte; Offset, Count: Integer; TimeTag:
    Extended = 0): TOSCPacket;
var
  tmp: PByte;
begin
  tmp := Bytes;
  Inc(tmp, Offset);

  if Char(tmp^) = '#' then
    Result := TOSCBundle.UnPack(Bytes, Offset, Count)
  else
    Result := TOSCMessage.UnPack(Bytes, Offset, Count, TimeTag);
end;

end.