1 module osc.message; 2 3 import std.range; 4 import std.conv; 5 import std.algorithm; 6 import std.variant; 7 import std.datetime; 8 9 import osc.packet; 10 import osc.address; 11 import osc.typetag; 12 import osc.oscstring; 13 14 15 16 /// Supported types for an OSC argument. 17 /// 18 alias OSCArgument = Algebraic!( 19 int, 20 float, 21 string, 22 ubyte[], 23 bool, 24 typeof(null), 25 char, 26 SysTime 27 ); 28 29 30 31 /// An OSC message contains an address and a set of arguments. 32 /// 33 class OSCMessage : OSCPacket 34 { 35 36 private { 37 Address _address; 38 TypeTagString _types; 39 OSCArgument[] _arguments; 40 } 41 42 43 44 /// Constructs a new empty OSC message. 45 /// 46 this( ) 47 { 48 _address = Address( [], false ); 49 } 50 51 /// Constructs a new OSC message using an address. 52 /// 53 this( string addr ) 54 in 55 { 56 assert( addr.length > 0 ); 57 } 58 do 59 { 60 this( ); 61 _address = Address( addr ); 62 } 63 64 /// Constructs a new OSC message using an address. 65 /// 66 this( Address addr ) 67 { 68 this( ); 69 _address = Address( addr ); 70 } 71 72 /// Constructs a new OSC message by cloning another one. 73 /// 74 this( const(OSCMessage) copy ) 75 { 76 this( ); 77 _address = Address( copy._address ); 78 _arguments = copy._arguments.dup; 79 } 80 81 82 /// Gets the message's address. 83 /// 84 @property Address address( ) const 85 { 86 return Address( _address ); 87 } 88 89 /// Sets the message's address. 90 /// 91 @property void address( Address addr ) 92 { 93 _address = Address( addr ); 94 } 95 96 /// Gets the argment's types. 97 /// 98 @property const(TypeTagString) types( ) const 99 { 100 return _types; 101 } 102 103 /// Gets the message's argument count. 104 /// 105 @property size_t length( ) const 106 { 107 return _arguments.length; 108 } 109 110 111 112 /// Duplicates the current message. 113 /// 114 OSCMessage dup( ) const 115 { 116 return new OSCMessage( this ); 117 } 118 119 120 121 /// Gets the type of the argument at the given index. 122 /// 123 TypeTag typeAt( size_t idx ) const 124 in 125 { 126 assert( idx < _arguments.length ); 127 } 128 do 129 { 130 return cast(TypeTag) _types[ idx ]; 131 } 132 133 /// Gets the argument at the given index. 134 /// 135 OSCArgument argumentAt( size_t idx ) const 136 in 137 { 138 assert( idx < _arguments.length ); 139 } 140 do 141 { 142 return _arguments[ idx ]; 143 } 144 145 146 147 /// Checks if the given argument is an integer. 148 /// 149 bool isInt( size_t idx ) const 150 in 151 { 152 assert( idx < _arguments.length ); 153 } 154 do 155 { 156 return _types[ idx ] == TypeTag.Integer; 157 } 158 159 /// Checks if the given argument is a float. 160 /// 161 bool isFloat( size_t idx ) const 162 in 163 { 164 assert( idx < _arguments.length ); 165 } 166 do 167 { 168 return _types[ idx ] == TypeTag.Float; 169 } 170 171 /// Checks if the given argument is an string. 172 /// 173 bool isString( size_t idx ) const 174 in 175 { 176 assert( idx < _arguments.length ); 177 } 178 do 179 { 180 return _types[ idx ] == TypeTag.String; 181 } 182 183 /// Checks if the given argument is an blob. 184 /// 185 bool isInt( size_t idx ) const 186 in 187 { 188 assert( idx < _arguments.length ); 189 } 190 do 191 { 192 return _types[ idx ] == TypeTag.Blob; 193 } 194 195 /// Checks if the given argument is true. 196 /// 197 bool isTrue( size_t idx ) const 198 in 199 { 200 assert( idx < _arguments.length ); 201 } 202 do 203 { 204 return _types[ idx ] == TypeTag.True; 205 } 206 207 /// Checks if the given argument is false. 208 /// 209 bool isFalse( size_t idx ) const 210 in 211 { 212 assert( idx < _arguments.length ); 213 } 214 do 215 { 216 return _types[ idx ] == TypeTag.False; 217 } 218 219 /// Checks if the given argument is a boolean. 220 /// 221 bool isBool( size_t idx ) const 222 { 223 return isTrue( idx ) || isFalse( idx ); 224 } 225 226 /// Checks if the given argument is null. 227 /// 228 bool isNull( size_t idx ) const 229 in 230 { 231 assert( idx < _arguments.length ); 232 } 233 do 234 { 235 return _types[ idx ] == TypeTag.Null; 236 } 237 238 /// Checks if the given argument is an impulse. 239 /// 240 bool isImpulse( size_t idx ) const 241 in 242 { 243 assert( idx < _arguments.length ); 244 } 245 do 246 { 247 return _types[ idx ] == TypeTag.Impulse; 248 } 249 250 /// Checks if the given argument is a timetag. 251 /// 252 bool isTimeTag( size_t idx ) const 253 in 254 { 255 assert( idx < _arguments.length ); 256 } 257 do 258 { 259 return _types[ idx ] == TypeTag.TimeTag; 260 } 261 262 263 264 /// Gets the argument at the given index. 265 /// 266 T at( T:Variant )( size_t idx ) const 267 in 268 { 269 assert( idx < _arguments.length ); 270 } 271 do 272 { 273 return _arguments[ idx ]; 274 } 275 276 /// Gets the integer at the given index. 277 /// 278 T at( T:int )( size_t idx ) const 279 in 280 { 281 assert( idx < _arguments.length ); 282 assert( _types[ idx ] == TypeTag.Integer ); 283 } 284 do 285 { 286 return _arguments[ idx ].get!T; 287 } 288 289 /// Gets the float at the given index. 290 /// 291 T at( T:float )( size_t idx ) const 292 in 293 { 294 assert( idx < _arguments.length ); 295 assert( _types[ idx ] == TypeTag.Float ); 296 } 297 do 298 { 299 return _arguments[ idx ].get!T; 300 } 301 302 /// Gets the string at the given index. 303 /// 304 T at( T:string )( size_t idx ) const 305 in 306 { 307 assert( idx < _arguments.length ); 308 assert( _types[ idx ] == TypeTag.String ); 309 } 310 do 311 { 312 return _arguments[ idx ].get!T; 313 } 314 315 /// Gets the blob at the given index. 316 /// 317 T at( T:ubyte[] )( size_t idx ) const 318 in 319 { 320 assert( idx < _arguments.length ); 321 assert( _types[ idx ] == TypeTag.Blob ); 322 } 323 do 324 { 325 return _arguments[ idx ].get!T.dup; 326 } 327 328 /// Gets the bool at the given index. 329 /// 330 T at( T:bool )( size_t idx ) const 331 in 332 { 333 assert( idx < _arguments.length ); 334 assert( _types[ idx ] == TypeTag.True || _types[ idx ] == TypeTag.False ); 335 } 336 do 337 { 338 return _arguments[ idx ].get!T; 339 } 340 341 /// Gets the timetag at the given index. 342 /// 343 T at( T:SysTime )( size_t idx ) const 344 in 345 { 346 assert( idx < _arguments.length ); 347 assert( _types[ idx ] == TypeTag.TimeTag ); 348 } 349 do 350 { 351 return _arguments[ idx ].get!T; 352 } 353 354 355 356 /// Adds an argument to the message. 357 /// 358 OSCMessage add( T )( T v ) 359 { 360 _types = _types.add!T; 361 OSCArgument va = v; 362 _arguments ~= va; 363 364 return this; 365 } 366 367 /// Adds a bool to the message. 368 /// 369 OSCMessage add( T:bool )( T v ) 370 { 371 _types = _types.add!bool( v ); 372 OSCArgument va = v; 373 _arguments ~= va; 374 375 return this; 376 } 377 378 /// Adds a null to the message. 379 /// 380 OSCMessage addNull( ) 381 { 382 _types = _types.addNull; 383 OSCArgument va = null; 384 _arguments ~= va; 385 386 return this; 387 } 388 389 /// Adds an impulse to the message. 390 /// 391 OSCMessage addImpulse( ) 392 { 393 _types = _types.addImpulse; 394 OSCArgument va = 'I'; 395 _arguments ~= va; 396 397 return this; 398 } 399 400 401 402 @property override ubyte[] data( ) const 403 { 404 ubyte[] res; 405 string types = "," ~ _types; 406 407 res ~= _address.toString.toOSC ~ types.toOSC; 408 409 int i; 410 foreach( t; _types ) 411 { 412 switch( t ) 413 { 414 case TypeTag.Integer: 415 res ~= _set!int( _arguments[ i ].get!int ); 416 break; 417 418 case TypeTag.Float: 419 res ~= _set!float( _arguments[ i ].get!float ); 420 break; 421 422 case TypeTag.String: 423 res ~= _set!string( _arguments[ i ].get!string ); 424 break; 425 426 case TypeTag.Blob: 427 res ~= _set!(ubyte[])( _arguments[ i ].get!(ubyte[]) ); 428 break; 429 430 case TypeTag.TimeTag: 431 res ~= _set!SysTime( _arguments[ i ].get!SysTime ); 432 break; 433 434 case TypeTag.True: 435 case TypeTag.False: 436 case TypeTag.Null: 437 case TypeTag.Impulse: 438 break; 439 440 default: 441 assert( 0 ); 442 } 443 444 i += 1; 445 } 446 447 return res; 448 } 449 450 } 451 452 453 unittest 454 { 455 ubyte[] raw = [ 456 0x2f, 0x6f, 0x73, 0x63, 457 0x69, 0x6c, 0x6c, 0x61, 458 0x74, 0x6f, 0x72, 0x2f, 459 0x34, 0x2f, 0x66, 0x72, 460 0x65, 0x71, 0x75, 0x65, 461 0x6e, 0x63, 0x79, 0x00, 462 0x2c, 0x66, 0x00, 0x00, 463 0x43, 0xdc, 0x00, 0x00 464 ]; 465 466 OSCMessage msg = new OSCMessage( "/oscillator/4/frequency" ); 467 msg.add!float( 440.0 ); 468 469 assert( msg.data( ) == raw ); 470 471 OSCMessage parsed = cast(OSCMessage) OSCPacket.parse( raw ); 472 473 assert( parsed.address.toString == "/oscillator/4/frequency" ); 474 assert( parsed.length == 1 ); 475 assert( parsed.isFloat( 0 ) ); 476 assert( parsed.at!float( 0 ) == 440 ); 477 } 478 479 unittest 480 { 481 import std.math : abs; 482 483 OSCMessage initial = new OSCMessage( "/unittest/test" ); 484 initial.add!int( 42 ); 485 initial.add!float( 3.14 ); 486 initial.add!string( "test" ); 487 initial.add!(ubyte[])( cast(ubyte[]) "ubyte" ); 488 initial.add!bool( true ); 489 initial.add!bool( false ); 490 initial.addNull; 491 initial.addImpulse; 492 SysTime currTime = Clock.currTime; 493 initial.add!SysTime( currTime ); 494 495 ubyte[] raw = initial.data( ); 496 497 OSCMessage parsed = cast(OSCMessage) OSCPacket.parse( raw ); 498 499 assert( parsed.address == initial.address ); 500 assert( parsed.types == initial.types ); 501 assert( parsed.at!int( 0 ) == 42 ); 502 assert( abs( parsed.at!float( 1 ) - 3.14 ) < 0.001 ); 503 assert( parsed.at!string( 2 ) == "test" ); 504 assert( parsed.at!(ubyte[])( 3 ) == cast(ubyte[]) "ubyte" ); 505 assert( parsed.at!bool( 4 ) == true ); 506 assert( parsed.at!bool( 5 ) == false ); 507 assert( parsed.isNull( 6 ) ); 508 assert( parsed.isImpulse( 7 ) ); 509 assert( parsed.at!SysTime( 8 ) == currTime ); 510 } 511 512 unittest 513 { 514 import std.math : abs; 515 516 ubyte[] raw = [ 517 0x2f, 0x66, 0x6f, 0x6f, 518 0x00, 0x00, 0x00, 0x00, 519 0x2c, 0x69, 0x69, 0x73, 520 0x66, 0x66, 0x00, 0x00, 521 0x00, 0x00, 0x03, 0xe8, 522 0xff, 0xff, 0xff, 0xff, 523 0x68, 0x65, 0x6c, 0x6c, 524 0x6f, 0x00, 0x00, 0x00, 525 0x3f, 0x9d, 0xf3, 0xb6, 526 0x40, 0xb5, 0xb2, 0x2d, 527 ]; 528 529 OSCMessage a = cast(OSCMessage) OSCPacket.parse( raw ); 530 531 assert( a.address.toString == "/foo" ); 532 assert( a.length == 5 ); 533 assert( a.types == "iisff" ); 534 assert( a.at!int( 0 ) == 1000 ); 535 assert( a.at!int( 1 ) == -1 ); 536 assert( a.at!string( 2 ) == "hello" ); 537 assert( abs( a.at!float( 3 ) - 1.234 ) < 0.001 ); 538 assert( abs( a.at!float( 4 ) - 5.678 ) < 0.001 ); 539 540 const OSCMessage b = new OSCMessage( "/foo" ) 541 .add!int( 1000 ) 542 .add!int( -1 ) 543 .add!string( "hello" ) 544 .add!float( 1.234 ) 545 .add!float( 5.678 ); 546 547 assert( b.data == raw ); 548 }