├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── NuGet.config ├── OscCore.sln ├── OscCore.sln.DotSettings.user ├── OscCore ├── Address │ ├── OscAddress.cs │ ├── OscAddressPart.cs │ ├── OscAddressPartType.cs │ ├── OscAddressRegexCache.cs │ └── OscAddressType.cs ├── DataTypes │ ├── OscColor.cs │ ├── OscImpulse.cs │ ├── OscMidiMessage.cs │ ├── OscNull.cs │ ├── OscSymbol.cs │ └── OscTimeTag.cs ├── IOscMessage.cs ├── Invoke │ └── OscInvoker.cs ├── LowLevel │ ├── ArraySegmentByteExt.cs │ ├── OscReader.cs │ ├── OscSerializationToken.cs │ ├── OscSerializationUtils.cs │ ├── OscStringReader.cs │ ├── OscStringWriter.cs │ ├── OscToken.cs │ ├── OscTypeTag.cs │ ├── OscUtils.cs │ └── OscWriter.cs ├── OscBundle.cs ├── OscBundleRaw.cs ├── OscCore.csproj ├── OscError.cs ├── OscException.cs ├── OscMessage.cs ├── OscMessageRaw.cs └── OscPacket.cs ├── OscCoreTests ├── OscAddressTest.cs ├── OscBundleRawTest.cs ├── OscBundleTest.cs ├── OscColorTest.cs ├── OscCoreTests.csproj ├── OscMessageRawTest.cs ├── OscMessageTest.cs ├── OscMidiMessageTest.cs ├── OscStringReaderTest.cs ├── OscTimeTagTest.cs ├── StringSerialization.cs └── UnitTestHelper.cs └── README.md /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | node: circleci/node@1.1.6 4 | jobs: 5 | build: 6 | docker: 7 | - image: mcr.microsoft.com/dotnet/core/sdk:2.2-alpine 8 | steps: 9 | - checkout 10 | - run: 11 | name: Restore 12 | command: dotnet restore 13 | working_directory: ./ 14 | - run: 15 | name: Build 16 | command: dotnet build --configuration Release --no-restore 17 | working_directory: ./ 18 | - run: 19 | name: Running Tests 20 | command: dotnet test --configuration Release --no-restore --logger "trx" 21 | working_directory: OscCoreTests 22 | - run: 23 | name: Convert Test Format 24 | working_directory: ./ 25 | when: always 26 | command: export PATH="$PATH:/root/.dotnet/tools" && dotnet tool install -g trx2junit && trx2junit ./**/TestResults/*.trx 27 | - store_test_results: 28 | path: OscCoreTests/TestResults 29 | - store_artifacts: 30 | path: OscCoreTests/TestResults 31 | destination: TestResults 32 | - run: 33 | name: Pack 34 | command: | 35 | mkdir ../.nuget 36 | dotnet pack --no-build --no-restore --configuration Release -o ../.nuget 37 | working_directory: OscCore 38 | - store_artifacts: 39 | path: ./.nuget 40 | destination: Nuget Packages 41 | - persist_to_workspace: 42 | root: ./ 43 | paths: 44 | - .nuget 45 | 46 | publish-latest: 47 | docker: 48 | - image: mcr.microsoft.com/dotnet/core/sdk:2.2-alpine 49 | steps: 50 | - attach_workspace: 51 | # Must be absolute path or relative path from working_directory 52 | at: ./ 53 | - run: 54 | name: Publish to nuget 55 | command: find ./.nuget/ | grep .nupkg | while read -r package; do dotnet nuget push "$package" -s https://api.nuget.org/v3/index.json -k $NUGET_API_TOKEN; done 56 | working_directory: ./ 57 | 58 | publish-github-release: 59 | docker: 60 | - image: cibuilds/github:0.10 61 | steps: 62 | - attach_workspace: 63 | at: ./artifacts 64 | - run: 65 | name: "Publish Release on GitHub" 66 | command: | 67 | ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} ./artifacts/.nuget/ 68 | 69 | workflows: 70 | version: 2 71 | build-master: 72 | jobs: 73 | - build: 74 | filters: # required since `publish-latest` has tag filters AND requires `build` 75 | tags: 76 | only: /.*/ 77 | 78 | - publish-latest: 79 | context: tilde-global 80 | requires: 81 | - build 82 | filters: 83 | tags: 84 | only: /.*/ 85 | branches: 86 | ignore: /.*/ 87 | 88 | - publish-github-release: 89 | context: tilde-global 90 | requires: 91 | - build 92 | # - publish-latest 93 | filters: 94 | tags: 95 | only: /.*/ 96 | branches: 97 | ignore: /.*/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | \.idea/ 4 | \.vs/ 5 | 6 | *.vspx 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tilde Love Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OscCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OscCore", "OscCore\OscCore.csproj", "{7EDFF8BE-94C3-44E5-A7D0-14206CA3CEB6}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OscCoreTests", "OscCoreTests\OscCoreTests.csproj", "{7D5694FF-0726-4AEC-96D3-51EC159729A8}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFiles", "SolutionFiles", "{AEA9E3C3-29FA-4850-88FF-336277400460}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE = LICENSE 13 | README.md = README.md 14 | .gitignore = .gitignore 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {7EDFF8BE-94C3-44E5-A7D0-14206CA3CEB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {7EDFF8BE-94C3-44E5-A7D0-14206CA3CEB6}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {7EDFF8BE-94C3-44E5-A7D0-14206CA3CEB6}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {7EDFF8BE-94C3-44E5-A7D0-14206CA3CEB6}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {7D5694FF-0726-4AEC-96D3-51EC159729A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {7D5694FF-0726-4AEC-96D3-51EC159729A8}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {7D5694FF-0726-4AEC-96D3-51EC159729A8}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {7D5694FF-0726-4AEC-96D3-51EC159729A8}.Release|Any CPU.Build.0 = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | GlobalSection(ExtensibilityGlobals) = postSolution 36 | SolutionGuid = {EC3E4AF0-DCFB-4655-9062-C83FAC3E7BFC} 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /OscCore.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | ForceIncluded 3 | ForceIncluded 4 | ForceIncluded 5 | ForceIncluded 6 | ForceIncluded 7 | ForceIncluded 8 | ForceIncluded 9 | ForceIncluded 10 | ForceIncluded 11 | ForceIncluded 12 | 2 13 | True 14 | 15 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 16 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 17 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> 18 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 19 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 20 | 21 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 22 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy> 23 | <Policy Inspect="True" Prefix="Osc" Suffix="" Style="AaBb" /> 24 | -------------------------------------------------------------------------------- /OscCore/Address/OscAddress.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace OscCore.Address 11 | { 12 | /// 13 | /// Encompasses an entire osc address 14 | /// 15 | public sealed class OscAddress : IEnumerable 16 | { 17 | private static readonly char[] AddressSeperatorChar = {'/'}; 18 | private static readonly Regex LiteralAddressValidator = new Regex(@"^/[^\s#\*,/\?\[\]\{}]+((/[^\s#\*,/\?\[\]\{}]+)*)$", RegexOptions.Compiled); 19 | 20 | private static readonly Regex PatternAddressPartExtractor = new Regex( 21 | @" 22 | (?([^\s#\*,/\?\[\]\{}]+)) | 23 | (?([\*\?]+)) | 24 | (?(\[(!?)[^\s#\*,/\?\[\]\{}-]-[^\s#\*,/\?\[\]\{}-]\])) | 25 | (?(\[(!?)[^\s#\*,/\?\[\]\{}]+\])) | 26 | (?{([^\s#\*/\?\,[\]\{}]+)((,[^\s#\*/\?\,[\]\{}]+)*)}) 27 | ", 28 | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace 29 | ); 30 | 31 | private static readonly Regex PatternAddressPartValidator = new Regex( 32 | @"^(( 33 | (?([^\s#\*,/\?\[\]\{}]+)) | 34 | (?([\*\?]+)) | 35 | (?(\[(!?)[^\s#\*,/\?\[\]\{}-]-[^\s#\*,/\?\[\]\{}-]\])) | 36 | (?(\[(!?)[^\s#\*,/\?\[\]\{}]+\])) | 37 | (?{([^\s#\*/\?\,[\]\{}]+)((,[^\s#\*/\?\,[\]\{}]+)*)}) 38 | )+)$", 39 | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace 40 | ); 41 | 42 | private static readonly Regex PatternAddressValidator = new Regex(@"^(//|/)[^\s#/]+((/[^\s#/]+)*)$", RegexOptions.Compiled); 43 | 44 | private OscAddressPart[] parts; 45 | private Regex regex; 46 | private OscAddressType type; 47 | 48 | /// 49 | /// Address parts 50 | /// 51 | /// the index of the part 52 | /// the address part at the given index 53 | public OscAddressPart this[int index] => parts[index]; 54 | 55 | /// 56 | /// The number of parts in the address 57 | /// 58 | public int Count => parts.Length; 59 | 60 | /// 61 | /// Is this address a literal 62 | /// 63 | public bool IsLiteral => type == OscAddressType.Literal; 64 | 65 | /// 66 | /// The string used to create the address 67 | /// 68 | public string OrigialString { get; private set; } 69 | 70 | /// 71 | /// Create an osc address from a string, must follow the rules set out in http://opensoundcontrol.org/spec-1_0 and 72 | /// http://opensoundcontrol.org/spec-1_1 73 | /// 74 | /// the address string 75 | public OscAddress(string address) 76 | { 77 | ParseAddress(address); 78 | } 79 | 80 | /// 81 | IEnumerator IEnumerable.GetEnumerator() 82 | { 83 | return parts.GetEnumerator(); 84 | } 85 | 86 | // /// 87 | // /// Only used for testing 88 | // /// 89 | // /// a string that would produce the same address pattern but not a copy of the original string 90 | // internal string ToString_Rebuild() 91 | // { 92 | // StringBuilder sb = new StringBuilder(); 93 | // 94 | // foreach (OscAddressPart part in parts) 95 | // { 96 | // sb.Append(part.Interpreted); 97 | // } 98 | // 99 | // return sb.ToString(); 100 | // } 101 | 102 | /// 103 | public IEnumerator GetEnumerator() 104 | { 105 | return (parts as IEnumerable).GetEnumerator(); 106 | } 107 | 108 | public override bool Equals(object obj) 109 | { 110 | return OrigialString.Equals(obj.ToString()); 111 | } 112 | 113 | public override int GetHashCode() 114 | { 115 | return OrigialString.GetHashCode(); 116 | } 117 | 118 | /// 119 | /// Does a address match a address pattern 120 | /// 121 | /// address pattern (may include wildcards and lists) 122 | /// literal address 123 | /// true if the addess matches the pattern 124 | public static bool IsMatch(string addressPattern, string address) 125 | { 126 | if (IsValidAddressLiteral(address) == false) 127 | { 128 | return false; 129 | } 130 | 131 | // are they both literals 132 | if (IsValidAddressLiteral(addressPattern)) 133 | { 134 | // preform a string match 135 | return addressPattern.Equals(address); 136 | } 137 | 138 | if (IsValidAddressPattern(addressPattern) == false) 139 | { 140 | return false; 141 | } 142 | 143 | // create a new pattern for the match 144 | OscAddress pattern = new OscAddress(addressPattern); 145 | 146 | // return the result 147 | return pattern.Match(address); 148 | } 149 | 150 | /// 151 | /// Is the supplied address a valid literal address (no wildcards or lists) 152 | /// 153 | /// the address to check 154 | /// true if the address is valid 155 | public static bool IsValidAddressLiteral(string address) 156 | { 157 | if (string.IsNullOrWhiteSpace(address)) 158 | { 159 | return false; 160 | } 161 | 162 | return LiteralAddressValidator.IsMatch(address); 163 | } 164 | 165 | /// 166 | /// Is the supplied address a valid address pattern (may include wildcards and lists) 167 | /// 168 | /// the address pattern to check 169 | /// true if the address pattern is valid 170 | public static bool IsValidAddressPattern(string addressPattern) 171 | { 172 | if (string.IsNullOrWhiteSpace(addressPattern)) 173 | { 174 | return false; 175 | } 176 | 177 | if (PatternAddressValidator.IsMatch(addressPattern) == false) 178 | { 179 | return false; 180 | } 181 | 182 | // is this address a liternal address? 183 | if (IsValidAddressLiteral(addressPattern)) 184 | { 185 | return true; 186 | } 187 | 188 | string[] parts = addressPattern.Split(AddressSeperatorChar, StringSplitOptions.RemoveEmptyEntries); 189 | 190 | bool isMatch = true; 191 | 192 | // scan for wild chars and lists 193 | foreach (string part in parts) 194 | { 195 | isMatch &= PatternAddressPartValidator.IsMatch(part); 196 | } 197 | 198 | return isMatch; 199 | } 200 | 201 | /// 202 | /// Match this address against an address string 203 | /// 204 | /// the address string to match against 205 | /// true if the addresses match, otherwise false 206 | public bool Match(string address) 207 | { 208 | // if this address in a literal 209 | if (type == OscAddressType.Literal) 210 | { 211 | // if the original string is the same then we are good 212 | return OrigialString.Equals(address); 213 | } 214 | 215 | // use the pattern regex to determin a match 216 | return regex.IsMatch(address); 217 | } 218 | 219 | /// 220 | /// Match this address against another 221 | /// 222 | /// the address to match against 223 | /// true if the addresses match, otherwise false 224 | public bool Match(OscAddress address) 225 | { 226 | // if both addresses are literals then we can match on original string 227 | if (type == OscAddressType.Literal && 228 | address.type == OscAddressType.Literal) 229 | { 230 | return OrigialString.Equals(address.OrigialString); 231 | } 232 | // if this address is a literal then use the others regex 233 | 234 | if (type == OscAddressType.Literal) 235 | { 236 | return address.regex.IsMatch(OrigialString); 237 | } 238 | // if the other is a literal use this ones regex 239 | 240 | if (address.type == OscAddressType.Literal) 241 | { 242 | return regex.IsMatch(address.OrigialString); 243 | } 244 | // if both are patterns then we just match on pattern original strings 245 | 246 | return OrigialString.Equals(address.OrigialString); 247 | } 248 | 249 | public override string ToString() 250 | { 251 | return OrigialString; 252 | } 253 | 254 | private void ParseAddress(string address) 255 | { 256 | // Ensure address is valid 257 | if (IsValidAddressPattern(address) == false) 258 | { 259 | throw new ArgumentException($"The address '{address}' is not a valid osc address", nameof(address)); 260 | } 261 | 262 | // stash the original string 263 | OrigialString = address; 264 | 265 | // is this address non-literal (an address pattern) 266 | bool nonLiteral = false; 267 | bool skipNextSeparator = false; 268 | 269 | // create a list for the parsed parts 270 | List addressParts = new List(); 271 | 272 | if (address.StartsWith("//")) 273 | { 274 | // add the wildcard 275 | addressParts.Add(OscAddressPart.AddressWildcard()); 276 | 277 | // strip off the "//" from the address 278 | address = address.Substring(2); 279 | 280 | // this address in not a literal 281 | nonLiteral = true; 282 | 283 | // do not add a Separator before the next token 284 | skipNextSeparator = true; 285 | } 286 | 287 | // the the bits of the path, split by the '/' char 288 | string[] parts = address.Split(AddressSeperatorChar, StringSplitOptions.RemoveEmptyEntries); 289 | 290 | // loop through all the parts 291 | foreach (string part in parts) 292 | { 293 | if (skipNextSeparator == false) 294 | { 295 | // add a separator 296 | addressParts.Add(OscAddressPart.AddressSeparator()); 297 | } 298 | else 299 | { 300 | // we dont want to skip the next one 301 | skipNextSeparator = false; 302 | } 303 | 304 | // get the matches within the part 305 | MatchCollection matches = PatternAddressPartExtractor.Matches(part); 306 | 307 | // loop through all matches 308 | foreach (Match match in matches) 309 | { 310 | if (match.Groups["Literal"] 311 | .Success) 312 | { 313 | addressParts.Add( 314 | OscAddressPart.Literal( 315 | match.Groups["Literal"] 316 | .Value 317 | ) 318 | ); 319 | } 320 | else if (match.Groups["Wildcard"] 321 | .Success) 322 | { 323 | addressParts.Add( 324 | OscAddressPart.Wildcard( 325 | match.Groups["Wildcard"] 326 | .Value 327 | ) 328 | ); 329 | nonLiteral = true; 330 | } 331 | else if (match.Groups["CharSpan"] 332 | .Success) 333 | { 334 | addressParts.Add( 335 | OscAddressPart.CharSpan( 336 | match.Groups["CharSpan"] 337 | .Value 338 | ) 339 | ); 340 | nonLiteral = true; 341 | } 342 | else if (match.Groups["CharList"] 343 | .Success) 344 | { 345 | addressParts.Add( 346 | OscAddressPart.CharList( 347 | match.Groups["CharList"] 348 | .Value 349 | ) 350 | ); 351 | nonLiteral = true; 352 | } 353 | else if (match.Groups["List"] 354 | .Success) 355 | { 356 | addressParts.Add( 357 | OscAddressPart.List( 358 | match.Groups["List"] 359 | .Value 360 | ) 361 | ); 362 | nonLiteral = true; 363 | } 364 | else 365 | { 366 | throw new Exception($"Unknown address part '{match.Value}'"); 367 | } 368 | } 369 | } 370 | 371 | // set the type 372 | type = nonLiteral ? OscAddressType.Pattern : OscAddressType.Literal; 373 | 374 | // set the parts array 375 | this.parts = addressParts.ToArray(); 376 | 377 | // build the regex if one is needed 378 | if (type == OscAddressType.Literal) 379 | { 380 | return; 381 | } 382 | 383 | StringBuilder regex = new StringBuilder(); 384 | 385 | if (this.parts[0] 386 | .Type == OscAddressPartType.AddressWildcard) 387 | { 388 | // dont care where the start is 389 | regex.Append("("); 390 | } 391 | else 392 | { 393 | // match the start of the string 394 | regex.Append("^("); 395 | } 396 | 397 | foreach (OscAddressPart part in this.parts) 398 | { 399 | // match the part 400 | regex.Append(part.PartRegex); 401 | } 402 | 403 | // match the end of the string 404 | regex.Append(")$"); 405 | 406 | // aquire the regex 407 | this.regex = OscAddressRegexCache.Aquire(regex.ToString()); 408 | } 409 | } 410 | } -------------------------------------------------------------------------------- /OscCore/Address/OscAddressPart.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace OscCore.Address 9 | { 10 | /// 11 | /// Encompasses a single part of an osc address 12 | /// 13 | public struct OscAddressPart 14 | { 15 | /// 16 | /// The regex representation of this part 17 | /// 18 | public readonly string PartRegex; 19 | 20 | /// 21 | /// The address part type 22 | /// 23 | public readonly OscAddressPartType Type; 24 | 25 | /// 26 | /// The original string value of this part 27 | /// 28 | public readonly string Value; 29 | 30 | /// 31 | /// How the string was interpreted (only used for testing) 32 | /// 33 | public readonly string Interpreted; 34 | 35 | private static readonly Regex CharMatcher = new Regex(@"[\.\$\^\{\[\(\|\)\*\+\?\\]", RegexOptions.Compiled); 36 | 37 | /// 38 | /// Create a address part 39 | /// 40 | /// the type of part 41 | /// the original string value 42 | /// the representation of the original value as interpreted by the parser 43 | /// the part as a regex expression 44 | private OscAddressPart( 45 | OscAddressPartType type, 46 | string value, 47 | string interpreted, 48 | string partRegex) 49 | { 50 | Type = type; 51 | Value = value; 52 | Interpreted = interpreted; 53 | PartRegex = partRegex; 54 | } 55 | 56 | /// 57 | /// Create a address separator part '/' 58 | /// 59 | /// the part 60 | internal static OscAddressPart AddressSeparator() 61 | { 62 | return new OscAddressPart(OscAddressPartType.AddressSeparator, "/", "/", "/"); 63 | } 64 | 65 | /// 66 | /// Create a address wildcard part "//" 67 | /// 68 | /// the part 69 | internal static OscAddressPart AddressWildcard() 70 | { 71 | return new OscAddressPart(OscAddressPartType.AddressWildcard, "//", "//", "/"); 72 | } 73 | 74 | /// 75 | /// Character list e.g. [abcde] 76 | /// 77 | /// the original string 78 | /// the part 79 | internal static OscAddressPart CharList(string value) 80 | { 81 | bool isNot = false; 82 | int index = 1; 83 | 84 | if (value[index] == '!') 85 | { 86 | isNot = true; 87 | index++; 88 | } 89 | 90 | string list = value.Substring(index, value.Length - 1 - index); 91 | 92 | string regex = $"[{(isNot ? "^" : string.Empty)}{EscapeString(list)}]+"; 93 | string rebuild = $"[{(isNot ? "!" : string.Empty)}{list}]"; 94 | 95 | return new OscAddressPart(OscAddressPartType.CharList, value, rebuild, regex); 96 | } 97 | 98 | /// 99 | /// Character span e.g. [a-e] 100 | /// 101 | /// the original string 102 | /// the part 103 | internal static OscAddressPart CharSpan(string value) 104 | { 105 | bool isNot = false; 106 | int index = 1; 107 | 108 | if (value[index] == '!') 109 | { 110 | isNot = true; 111 | index++; 112 | } 113 | 114 | char low = value[index++]; 115 | index++; 116 | char high = value[index++]; 117 | 118 | string rebuild = $"[{(isNot ? "!" : string.Empty)}{low}-{high}]"; 119 | 120 | // if the range is the wrong way round then swap them 121 | if (low > high) 122 | { 123 | char temp = high; 124 | 125 | high = low; 126 | low = temp; 127 | } 128 | 129 | string regex = $"[{(isNot ? "^" : string.Empty)}{EscapeChar(low)}-{EscapeChar(high)}]+"; 130 | 131 | return new OscAddressPart(OscAddressPartType.CharSpan, value, rebuild, regex); 132 | } 133 | 134 | /// 135 | /// Literal list e.g. {thing1,THING1} 136 | /// 137 | /// the original string 138 | /// the part 139 | internal static OscAddressPart List(string value) 140 | { 141 | string[] list = value.Substring(1, value.Length - 2) 142 | .Split(','); 143 | 144 | StringBuilder regSb = new StringBuilder(); 145 | StringBuilder listSb = new StringBuilder(); 146 | 147 | bool first = true; 148 | 149 | regSb.Append("("); 150 | listSb.Append("{"); 151 | 152 | foreach (string str in list) 153 | { 154 | if (first == false) 155 | { 156 | regSb.Append("|"); 157 | listSb.Append(","); 158 | } 159 | else 160 | { 161 | first = false; 162 | } 163 | 164 | regSb.Append("(" + EscapeString(str) + ")"); 165 | listSb.Append(str); 166 | } 167 | 168 | listSb.Append("}"); 169 | regSb.Append(")"); 170 | 171 | return new OscAddressPart(OscAddressPartType.List, value, listSb.ToString(), regSb.ToString()); 172 | } 173 | 174 | /// 175 | /// Create a literal address part 176 | /// 177 | /// the literal 178 | /// the part 179 | internal static OscAddressPart Literal(string value) 180 | { 181 | return new OscAddressPart(OscAddressPartType.Literal, value, value, "(" + EscapeString(value) + ")"); 182 | } 183 | 184 | /// 185 | /// Create a part for a wildcard part 186 | /// 187 | /// the original string 188 | /// the part 189 | internal static OscAddressPart Wildcard(string value) 190 | { 191 | string regex = value; 192 | 193 | // reduce needless complexity 194 | while (regex.Contains("**")) 195 | { 196 | regex = regex.Replace("**", "*"); 197 | } 198 | 199 | StringBuilder sb = new StringBuilder(); 200 | 201 | // single char mode indicates that 1 or more '?' has been encountered while parsing 202 | bool singleCharMode = false; 203 | 204 | // the number of '?' that have been encountered sequentially 205 | int count = 0; 206 | 207 | // replace with wildcard regex 208 | foreach (char c in regex) 209 | { 210 | switch (c) 211 | { 212 | case '*': 213 | // if we are in single char mode the output the match for the current count of sequential chars 214 | if (singleCharMode) 215 | { 216 | sb.Append($@"([^\s#\*,/\?\[\]\{{}}]{{{count}}})"); 217 | } 218 | 219 | // no longer in single char mode 220 | singleCharMode = false; 221 | 222 | // reset the count 223 | count = 0; 224 | 225 | // output the zero or more chars matcher 226 | sb.Append(@"([^\s#\*,/\?\[\]\{}]*)"); 227 | break; 228 | case '?': 229 | // indicate that a '?' has been encountered 230 | singleCharMode = true; 231 | 232 | // increment the count 233 | count++; 234 | break; 235 | } 236 | } 237 | 238 | // if we are in single char mode then output the match for the current count of sequential chars 239 | if (singleCharMode) 240 | { 241 | sb.Append($@"([^\s#\*,/\?\[\]\{{}}]{{{count}}})"); 242 | } 243 | 244 | return new OscAddressPart(OscAddressPartType.Wildcard, value, value, sb.ToString()); 245 | } 246 | 247 | private static string EscapeChar(char c) 248 | { 249 | return EscapeString(c.ToString()); 250 | } 251 | 252 | private static string EscapeString(string str) 253 | { 254 | return CharMatcher.Replace( 255 | str, 256 | match => 257 | { 258 | switch (match.Value) 259 | { 260 | case ".": 261 | return @"\."; 262 | 263 | case "$": 264 | return @"\$"; 265 | 266 | case "^": 267 | return @"\^"; 268 | 269 | case "{": 270 | return @"\{"; 271 | 272 | case "[": 273 | return @"\["; 274 | 275 | case "(": 276 | return @"\("; 277 | 278 | case "|": 279 | return @"\|"; 280 | 281 | case ")": 282 | return @"\)"; 283 | 284 | case "*": 285 | return @"\*"; 286 | 287 | case "+": 288 | return @"\+"; 289 | 290 | case "?": 291 | return @"\?"; 292 | 293 | case "\\": 294 | return @"\\"; 295 | 296 | default: 297 | throw new Exception("Unexpected match"); 298 | } 299 | } 300 | ); 301 | 302 | //#pragma warning disable 303 | // // This should never be reached but should shut up the Unity compiler. 304 | // return null; 305 | //#pragma warning reset 306 | } 307 | } 308 | } -------------------------------------------------------------------------------- /OscCore/Address/OscAddressPartType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | namespace OscCore.Address 5 | { 6 | /// 7 | /// Type of address part 8 | /// 9 | public enum OscAddressPartType 10 | { 11 | /// 12 | /// Address separator char i.e. '/' 13 | /// 14 | AddressSeparator, 15 | 16 | /// 17 | /// Address wildcard i.e. '//' 18 | /// 19 | AddressWildcard, 20 | 21 | /// 22 | /// Any string literal i.e [^\s#\*,/\?\[\]\{}]+ 23 | /// 24 | Literal, 25 | 26 | /// 27 | /// Either single char or any length wildcard i.e '?' or '*' 28 | /// 29 | Wildcard, 30 | 31 | /// 32 | /// Char span e.g. [a-z]+ 33 | /// 34 | CharSpan, 35 | 36 | /// 37 | /// List of literal matches 38 | /// 39 | List, 40 | 41 | /// 42 | /// List of possible char matches e.g. [abcdefg]+ 43 | /// 44 | CharList 45 | } 46 | } -------------------------------------------------------------------------------- /OscCore/Address/OscAddressRegexCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System.Collections.Concurrent; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace OscCore.Address 8 | { 9 | /// 10 | /// Regex cache is an optimisation for regexs for address patterns. Caching is enabled by default. 11 | /// 12 | /// 13 | /// This mechanism assumes that the same addresses will be used multiple times 14 | /// and that there will be a finite number of unique addresses parsed over the course 15 | /// of the execution of the program. 16 | /// If there are to be many unique addresses used of the course of the execution of 17 | /// the program then it maybe desirable to disable caching. 18 | /// 19 | public static class OscAddressRegexCache 20 | { 21 | private static readonly ConcurrentDictionary Lookup = new ConcurrentDictionary(); 22 | 23 | /// 24 | /// The number of cached regex(s) 25 | /// 26 | public static int Count => Lookup.Count; 27 | 28 | /// 29 | /// Enable regex caching for the entire program (Enabled by default) 30 | /// 31 | public static bool Enabled { get; set; } 32 | 33 | static OscAddressRegexCache() 34 | { 35 | // enable caching by default 36 | Enabled = true; 37 | } 38 | 39 | /// 40 | /// Acquire a regex, either by creating it if no cached one can be found or retrieving the cached one. 41 | /// 42 | /// regex pattern 43 | /// a regex created from or retrieved for the pattern 44 | public static Regex Aquire(string regex) 45 | { 46 | return Enabled == false 47 | ? 48 | // if caching is disabled then just return a new regex 49 | new Regex(regex, RegexOptions.None) 50 | : 51 | // else see if we have one cached 52 | Lookup.GetOrAdd( 53 | regex, 54 | // create a new one, we can compile it as it will probably be reused 55 | func => new Regex(regex, RegexOptions.Compiled) 56 | ); 57 | } 58 | 59 | /// 60 | /// Clear the entire cache 61 | /// 62 | public static void Clear() 63 | { 64 | Lookup.Clear(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /OscCore/Address/OscAddressType.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | namespace OscCore.Address 5 | { 6 | internal enum OscAddressType 7 | { 8 | Literal, 9 | Pattern 10 | } 11 | } -------------------------------------------------------------------------------- /OscCore/DataTypes/OscColor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using OscCore.LowLevel; 7 | 8 | // ReSharper disable once CheckNamespace 9 | namespace OscCore 10 | { 11 | /// 12 | /// Represents a 32bit ARGB color 13 | /// 14 | /// 15 | /// This is a poor replacement for System.Drawing.Color but unfortunately many platforms do not support 16 | /// the System.Drawing namespace. 17 | /// 18 | public struct OscColor 19 | { 20 | private const int AlphaMask = 0x18; 21 | private const int RedMask = 0x10; 22 | private const int GreenMask = 0x08; 23 | private const int BlueMask = 0; 24 | 25 | /// 26 | /// Alpha, red, green and blue components packed into a single 32bit int 27 | /// 28 | public int ARGB { get; } 29 | 30 | /// 31 | /// Red component 32 | /// 33 | public byte R => (byte) ((ARGB >> RedMask) & 0xff); 34 | 35 | /// 36 | /// Green component 37 | /// 38 | public byte G => (byte) ((ARGB >> GreenMask) & 0xff); 39 | 40 | /// 41 | /// Blue component 42 | /// 43 | public byte B => (byte) (ARGB & 0xff); 44 | 45 | /// 46 | /// Alpha component 47 | /// 48 | public byte A => (byte) ((ARGB >> AlphaMask) & 0xff); 49 | 50 | /// 51 | /// Initate a new Osc-Color from an ARGB color value 52 | /// 53 | /// An 32bit ARGB integer 54 | public OscColor(int value) 55 | { 56 | ARGB = value; 57 | } 58 | 59 | public override bool Equals(object obj) 60 | { 61 | switch (obj) 62 | { 63 | case OscColor oscColor: 64 | return oscColor.ARGB == ARGB; 65 | case int intValue: 66 | return intValue == ARGB; 67 | case uint uintValue: 68 | return unchecked((int) uintValue) == ARGB; 69 | } 70 | 71 | return base.Equals(obj); 72 | } 73 | 74 | public override string ToString() 75 | { 76 | //return $"{A}, {R}, {G}, {B}"; 77 | return $"{R}, {G}, {B}, {A}"; 78 | } 79 | 80 | public override int GetHashCode() 81 | { 82 | return ARGB; 83 | } 84 | 85 | /// 86 | /// Create a Osc-Color from an 32bit ARGB integer 87 | /// 88 | /// An ARGB integer 89 | /// An Osc Color 90 | public static OscColor FromArgb(int argb) 91 | { 92 | return new OscColor(unchecked(argb & (int) 0xffffffff)); 93 | } 94 | 95 | /// 96 | /// Create a Osc-Color from 4 channels 97 | /// 98 | /// Alpha channel component 99 | /// Red channel component 100 | /// Green channel component 101 | /// Blue channel component 102 | /// An Osc Color 103 | public static OscColor FromArgb( 104 | int alpha, 105 | int red, 106 | int green, 107 | int blue) 108 | { 109 | CheckByte(alpha, "alpha"); 110 | CheckByte(red, "red"); 111 | CheckByte(green, "green"); 112 | CheckByte(blue, "blue"); 113 | 114 | return new OscColor(MakeArgb((byte) alpha, (byte) red, (byte) green, (byte) blue)); 115 | } 116 | 117 | private static int MakeArgb( 118 | byte alpha, 119 | byte red, 120 | byte green, 121 | byte blue) 122 | { 123 | return unchecked((int) ((uint) ((red << RedMask) | (green << GreenMask) | blue | (alpha << AlphaMask)) & 0xffffffff)); 124 | } 125 | 126 | private static void CheckByte(int value, string name) 127 | { 128 | if (value >= 0 && value <= 0xff) 129 | { 130 | return; 131 | } 132 | 133 | throw new ArgumentException($"The {name} channel has a value of {value}, color channel values must be in the range 0 to {0xff}", name); 134 | } 135 | 136 | public static OscColor Parse(ref OscStringReader reader, IFormatProvider provider) 137 | { 138 | string[] pieces = new string[4]; 139 | 140 | OscSerializationToken token = OscSerializationToken.None; 141 | 142 | for (int i = 0; i < 4; i++) 143 | { 144 | token = reader.ReadNextToken(out string value); 145 | pieces[i] = value; 146 | token = reader.ReadNextToken(out string _); 147 | } 148 | 149 | if (token != OscSerializationToken.ObjectEnd) 150 | { 151 | throw new Exception("Invalid color"); 152 | } 153 | 154 | byte a, r, g, b; 155 | 156 | r = byte.Parse( 157 | pieces[0] 158 | .Trim(), 159 | NumberStyles.None, 160 | provider 161 | ); 162 | g = byte.Parse( 163 | pieces[1] 164 | .Trim(), 165 | NumberStyles.None, 166 | provider 167 | ); 168 | b = byte.Parse( 169 | pieces[2] 170 | .Trim(), 171 | NumberStyles.None, 172 | provider 173 | ); 174 | a = byte.Parse( 175 | pieces[3] 176 | .Trim(), 177 | NumberStyles.None, 178 | provider 179 | ); 180 | 181 | return FromArgb(a, r, g, b); 182 | } 183 | 184 | public static OscColor Parse(string str, IFormatProvider provider) 185 | { 186 | string[] pieces = str.Split(','); 187 | 188 | if (pieces.Length != 4) 189 | { 190 | throw new Exception($"Invalid color \'{str}\'"); 191 | } 192 | 193 | byte a, r, g, b; 194 | 195 | r = byte.Parse( 196 | pieces[0] 197 | .Trim(), 198 | NumberStyles.None, 199 | provider 200 | ); 201 | g = byte.Parse( 202 | pieces[1] 203 | .Trim(), 204 | NumberStyles.None, 205 | provider 206 | ); 207 | b = byte.Parse( 208 | pieces[2] 209 | .Trim(), 210 | NumberStyles.None, 211 | provider 212 | ); 213 | a = byte.Parse( 214 | pieces[3] 215 | .Trim(), 216 | NumberStyles.None, 217 | provider 218 | ); 219 | 220 | return FromArgb(a, r, g, b); 221 | } 222 | } 223 | } -------------------------------------------------------------------------------- /OscCore/DataTypes/OscImpulse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | 6 | // ReSharper disable once CheckNamespace 7 | namespace OscCore 8 | { 9 | /// 10 | /// Osc Impulse Singleton 11 | /// 12 | public sealed class OscImpulse 13 | { 14 | public static readonly OscImpulse Value = new OscImpulse(); 15 | 16 | private OscImpulse() 17 | { 18 | } 19 | 20 | /// 21 | /// Matches the string against "Impulse", "Bang", "Infinitum", "Inf" the comparison is 22 | /// StringComparison.OrdinalIgnoreCase 23 | /// 24 | /// string to check 25 | /// true if the string matches any of the recognised impulse strings else false 26 | public static bool IsImpulse(string str) 27 | { 28 | bool isTrue = false; 29 | 30 | isTrue |= "Infinitum".Equals(str, StringComparison.OrdinalIgnoreCase); 31 | 32 | isTrue |= "Inf".Equals(str, StringComparison.OrdinalIgnoreCase); 33 | 34 | isTrue |= "Bang".Equals(str, StringComparison.OrdinalIgnoreCase); 35 | 36 | isTrue |= "Impulse".Equals(str, StringComparison.OrdinalIgnoreCase); 37 | 38 | return isTrue; 39 | } 40 | 41 | public override string ToString() 42 | { 43 | return "impulse"; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /OscCore/DataTypes/OscNull.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | 6 | // ReSharper disable once CheckNamespace 7 | namespace OscCore 8 | { 9 | /// 10 | /// Osc Null Singleton 11 | /// 12 | public sealed class OscNull 13 | { 14 | public static readonly OscNull Value = new OscNull(); 15 | 16 | private OscNull() 17 | { 18 | } 19 | 20 | public static bool IsNull(string str) 21 | { 22 | bool isTrue = false; 23 | 24 | isTrue |= "Null".Equals(str, StringComparison.OrdinalIgnoreCase); 25 | 26 | isTrue |= "Nil".Equals(str, StringComparison.OrdinalIgnoreCase); 27 | 28 | return isTrue; 29 | } 30 | 31 | public override string ToString() 32 | { 33 | return "null"; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /OscCore/DataTypes/OscSymbol.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | // ReSharper disable once CheckNamespace 5 | namespace OscCore 6 | { 7 | /// 8 | /// Osc symbol 9 | /// 10 | public struct OscSymbol 11 | { 12 | /// 13 | /// The string value of the symbol 14 | /// 15 | public readonly string Value; 16 | 17 | /// 18 | /// Create a new symbol 19 | /// 20 | /// literal string value 21 | public OscSymbol(string value) 22 | { 23 | Value = value; 24 | } 25 | 26 | public override string ToString() 27 | { 28 | return Value; 29 | } 30 | 31 | public override bool Equals(object obj) 32 | { 33 | return obj is OscSymbol symbol 34 | ? Value.Equals(symbol.Value) 35 | : Value.Equals(obj); 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | return Value.GetHashCode(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /OscCore/DataTypes/OscTimeTag.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Globalization; 6 | using OscCore.LowLevel; 7 | 8 | namespace OscCore 9 | { 10 | /// 11 | /// OSC time tag. 12 | /// 13 | public struct OscTimeTag 14 | { 15 | private static readonly string[] Formats = 16 | { 17 | "dd-MM-yy", 18 | "dd-MM-yyyy", 19 | "HH:mm", 20 | "HH:mm:ss", 21 | "HH:mm:ss.ffff", 22 | "dd-MM-yyyy HH:mm:ss", 23 | "dd-MM-yyyy HH:mm", 24 | "dd-MM-yyyy HH:mm:ss.ffff" 25 | }; 26 | 27 | /// 28 | /// The minimum date for any OSC time tag. 29 | /// 30 | public static readonly DateTime BaseDate = new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); 31 | 32 | /// 33 | /// Gets a OscTimeTag object that is set to the current date and time on this computer, expressed as the local time. 34 | /// 35 | public static OscTimeTag Now => FromDataTime(DateTime.Now); 36 | 37 | /// 38 | /// Gets a OscTimeTag object that is set to the current date and time on this computer, expressed as the Coordinated 39 | /// Universal Time (UTC). 40 | /// 41 | public static OscTimeTag UtcNow => FromDataTime(DateTime.UtcNow); 42 | 43 | /// 44 | /// Time tag value represented by a 64 bit fixed point number. The first 32 bits specify the number of seconds since 45 | /// midnight on January 1, 1900, and the last 32 bits specify fractional parts of a second to a precision of about 200 46 | /// picoseconds. This is the representation used by Internet NTP timestamps. 47 | /// 48 | public ulong Value; 49 | 50 | /// 51 | /// Gets the number of seconds since midnight on January 1, 1900. This is the first 32 bits of the 64 bit fixed point 52 | /// OSC time tag value. 53 | /// 54 | public uint Seconds => (uint) ((Value & 0xFFFFFFFF00000000) >> 32); 55 | 56 | /// 57 | /// Gets the fractional parts of a second. This is the 32 bits of the 64 bit fixed point OSC time tag value. 58 | /// 59 | public uint Fraction => (uint) (Value & 0xFFFFFFFF); 60 | 61 | /// 62 | /// Gets the number of seconds including fractional parts since midnight on January 1, 1900. 63 | /// 64 | public decimal SecondsDecimal => Seconds + (decimal) (Fraction / (double) uint.MaxValue); 65 | 66 | /// 67 | /// Build a OSC time tag from a NTP 64 bit integer. 68 | /// 69 | /// The 64 bit integer containing the time stamp. 70 | public OscTimeTag(ulong value) 71 | { 72 | Value = value; 73 | } 74 | 75 | /// 76 | /// Does this OSC time tag equal another object. 77 | /// 78 | /// An object. 79 | /// True if the objects are the same. 80 | public override bool Equals(object obj) 81 | { 82 | if (obj is OscTimeTag) 83 | { 84 | return Value.Equals(((OscTimeTag) obj).Value); 85 | } 86 | 87 | return Value.Equals(obj); 88 | } 89 | 90 | /// 91 | /// Gets a hashcode for this OSC time tag. 92 | /// 93 | /// A hashcode. 94 | public override int GetHashCode() 95 | { 96 | return Value.GetHashCode(); 97 | } 98 | 99 | /// 100 | /// Get a string of this OSC time tag in the format "dd-MM-yyyy HH:mm:ss.ffffZ". 101 | /// 102 | /// The string value of this OSC time tag. 103 | public override string ToString() 104 | { 105 | return ToDataTime() 106 | .ToString("dd-MM-yyyy HH:mm:ss.ffffZ"); 107 | } 108 | 109 | #region To Date Time 110 | 111 | /// 112 | /// Get the equivalent date-time value from the OSC time tag. 113 | /// 114 | /// the equivalent value as a date-time 115 | public DateTime ToDataTime() 116 | { 117 | // Kas: http://stackoverflow.com/questions/5206857/convert-ntp-timestamp-to-utc 118 | 119 | uint seconds = Seconds; 120 | 121 | uint fraction = Fraction; 122 | 123 | double milliseconds = fraction / (double) uint.MaxValue * 1000; 124 | 125 | DateTime datetime = BaseDate.AddSeconds(seconds) 126 | .AddMilliseconds(milliseconds); 127 | 128 | return datetime; 129 | } 130 | 131 | #endregion To Date Time 132 | 133 | #region From Data Time 134 | 135 | /// 136 | /// Get a Osc times tamp from a date-time value. 137 | /// 138 | /// Date-time value. 139 | /// The equivalent value as an osc time tag. 140 | public static OscTimeTag FromDataTime(DateTime datetime) 141 | { 142 | TimeSpan span = datetime.Subtract(BaseDate); 143 | 144 | double seconds = span.TotalSeconds; 145 | 146 | uint secondsUInt = (uint) seconds; 147 | 148 | double milliseconds = span.TotalMilliseconds - (double) secondsUInt * 1000; 149 | 150 | double fraction = milliseconds / 1000 * uint.MaxValue; 151 | 152 | return new OscTimeTag(((ulong) (secondsUInt & 0xFFFFFFFF) << 32) | ((ulong) fraction & 0xFFFFFFFF)); 153 | } 154 | 155 | #endregion From Data Time 156 | 157 | #region Parse 158 | 159 | public static OscTimeTag Parse(ref OscStringReader reader, IFormatProvider provider) 160 | { 161 | if (reader.ReadNextToken(out string str) != OscSerializationToken.Literal) 162 | { 163 | throw new Exception(@"Invalid osc-timetag string"); 164 | } 165 | 166 | if (reader.ReadNextToken(out string _) != OscSerializationToken.ObjectEnd) 167 | { 168 | throw new Exception(@"Invalid osc-timetag string"); 169 | } 170 | 171 | DateTimeStyles style = DateTimeStyles.AdjustToUniversal; 172 | 173 | if (str.Trim() 174 | .EndsWith("Z")) 175 | { 176 | style = DateTimeStyles.AssumeUniversal; 177 | 178 | str = str.Trim() 179 | .TrimEnd('Z'); 180 | } 181 | 182 | if (DateTime.TryParseExact(str, Formats, provider, style, out DateTime datetime)) 183 | { 184 | return FromDataTime(datetime); 185 | } 186 | 187 | if (str.StartsWith("0x") && 188 | ulong.TryParse(str.Substring(2), NumberStyles.HexNumber, provider, out ulong value)) 189 | { 190 | return new OscTimeTag(value); 191 | } 192 | 193 | if (ulong.TryParse(str, NumberStyles.Integer, provider, out value)) 194 | { 195 | return new OscTimeTag(value); 196 | } 197 | 198 | throw new Exception($@"Invalid osc-timetag string ""{str}"""); 199 | 200 | //return new OscTimeTag(0); 201 | 202 | // DateTimeStyles style = DateTimeStyles.AdjustToUniversal; 203 | // 204 | // if (str.Trim().EndsWith("Z") == true) 205 | // { 206 | // style = DateTimeStyles.AssumeUniversal; 207 | // 208 | // str = str.Trim().TrimEnd('Z'); 209 | // } 210 | // 211 | // if (DateTime.TryParseExact(str, formats, provider, style, out DateTime datetime) == true) 212 | // { 213 | // return FromDataTime(datetime); 214 | // } 215 | // 216 | // if (str.StartsWith("0x") == true && 217 | // ulong.TryParse(str.Substring(2), NumberStyles.HexNumber, provider, out ulong value) == true) 218 | // { 219 | // return new OscTimeTag(value); 220 | // } 221 | // 222 | // if (ulong.TryParse(str, NumberStyles.Integer, provider, out value) == true) 223 | // { 224 | // return new OscTimeTag(value); 225 | // } 226 | // 227 | // throw new Exception($@"Invalid osc-timetag string ""{str}"""); 228 | } 229 | 230 | /// 231 | /// Parse a OSC time tag from date-time string. 232 | /// 233 | /// String to parse. 234 | /// Format provider 235 | /// The parsed time tag. 236 | public static OscTimeTag Parse(string str, IFormatProvider provider) 237 | { 238 | DateTimeStyles style = DateTimeStyles.AdjustToUniversal; 239 | 240 | if (str.Trim() 241 | .EndsWith("Z")) 242 | { 243 | style = DateTimeStyles.AssumeUniversal; 244 | 245 | str = str.Trim() 246 | .TrimEnd('Z'); 247 | } 248 | 249 | if (DateTime.TryParseExact(str, Formats, provider, style, out DateTime datetime)) 250 | { 251 | return FromDataTime(datetime); 252 | } 253 | 254 | if (str.StartsWith("0x") && 255 | ulong.TryParse(str.Substring(2), NumberStyles.HexNumber, provider, out ulong value)) 256 | { 257 | return new OscTimeTag(value); 258 | } 259 | 260 | if (ulong.TryParse(str, NumberStyles.Integer, provider, out value)) 261 | { 262 | return new OscTimeTag(value); 263 | } 264 | 265 | throw new Exception($@"Invalid osc-timetag string ""{str}"""); 266 | } 267 | 268 | /// 269 | /// Parse a OSC time tag from date-time string. 270 | /// 271 | /// String to parse. 272 | /// The parsed time tag. 273 | public static OscTimeTag Parse(string str) 274 | { 275 | return Parse(str, CultureInfo.InvariantCulture); 276 | } 277 | 278 | /// 279 | /// Try to parse a OSC time tag from date-time string. 280 | /// 281 | /// String to parse. 282 | /// Format provider. 283 | /// The parsed time tag. 284 | /// True if parsed else false. 285 | public static bool TryParse(string str, IFormatProvider provider, out OscTimeTag value) 286 | { 287 | try 288 | { 289 | value = Parse(str, provider); 290 | 291 | return true; 292 | } 293 | catch 294 | { 295 | value = default(OscTimeTag); 296 | 297 | return false; 298 | } 299 | } 300 | 301 | /// 302 | /// Try to parse a OSC time tag from date-time string. 303 | /// 304 | /// String to parse. 305 | /// The parsed time tag. 306 | /// True if parsed else false. 307 | public static bool TryParse(string str, out OscTimeTag value) 308 | { 309 | try 310 | { 311 | value = Parse(str, CultureInfo.InvariantCulture); 312 | 313 | return true; 314 | } 315 | catch 316 | { 317 | value = default(OscTimeTag); 318 | 319 | return false; 320 | } 321 | } 322 | 323 | #endregion Parse 324 | } 325 | } -------------------------------------------------------------------------------- /OscCore/IOscMessage.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace OscCore 7 | { 8 | public interface IOscMessage 9 | { 10 | string Address { get; } 11 | 12 | int Count { get; } 13 | 14 | Uri Origin { get; } 15 | 16 | OscTimeTag? Timestamp { get; } 17 | } 18 | } -------------------------------------------------------------------------------- /OscCore/Invoke/OscInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using OscCore.Address; 6 | 7 | namespace OscCore.Invoke 8 | { 9 | // public class OscInvoker where TOscMessage : IOscMessage 10 | // { 11 | // public delegate void OscMessageEvent(TOscMessage message); 12 | // 13 | // private readonly ConcurrentDictionary literalAddresses = new ConcurrentDictionary(); 14 | // private readonly ConcurrentDictionary patternAddresses = new ConcurrentDictionary(); 15 | // 16 | // public void Attach(string address, OscMessageEvent @event) 17 | // { 18 | // if (@event == null) 19 | // { 20 | // throw new ArgumentNullException(nameof(@event)); 21 | // } 22 | // 23 | // // if the address is a literal then add it to the literal lookup 24 | // if (OscAddress.IsValidAddressLiteral(address)) 25 | // { 26 | // OscEventContainer container = literalAddresses.GetOrAdd(address, func => new OscEventContainer(new OscAddress(address))); 27 | // 28 | // // attach the event 29 | // container.Event += @event; 30 | // } 31 | // // if the address is a pattern add it to the pattern lookup 32 | // else if (OscAddress.IsValidAddressPattern(address)) 33 | // { 34 | // // add it to the lookup 35 | // OscEventContainer container = patternAddresses.GetOrAdd(address, func => new OscEventContainer(new OscAddress(address))); 36 | // 37 | // // attach the event 38 | // container.Event += @event; 39 | // } 40 | // else 41 | // { 42 | // throw new ArgumentException($"Invalid container address '{address}'", nameof(address)); 43 | // } 44 | // } 45 | // 46 | // public bool Contains(OscAddress oscAddress) 47 | // { 48 | // return Contains(oscAddress.ToString()); 49 | // } 50 | // 51 | // public bool Contains(string oscAddress) 52 | // { 53 | // return patternAddresses.ContainsKey(oscAddress) || literalAddresses.ContainsKey(oscAddress); 54 | // } 55 | // 56 | // public bool ContainsLiteral(string oscAddress) 57 | // { 58 | // return literalAddresses.ContainsKey(oscAddress); 59 | // } 60 | // 61 | // public bool ContainsPattern(OscAddress oscAddress) 62 | // { 63 | // return patternAddresses.ContainsKey(oscAddress.ToString()); 64 | // } 65 | // 66 | // /// 67 | // /// Detach an event listener 68 | // /// 69 | // /// the address of the container 70 | // /// the event to remove 71 | // public void Detach(string address, OscMessageEvent @event) 72 | // { 73 | // if (@event == null) 74 | // { 75 | // throw new ArgumentNullException(nameof(@event)); 76 | // } 77 | // 78 | // if (OscAddress.IsValidAddressLiteral(address)) 79 | // { 80 | // if (literalAddresses.TryGetValue(address, out OscEventContainer container) == false) 81 | // { 82 | // // no container was found so abort 83 | // return; 84 | // } 85 | // 86 | // // unregiser the event 87 | // container.Event -= @event; 88 | // 89 | // // if the container is now empty remove it from the lookup 90 | // if (container.IsNull) 91 | // { 92 | // literalAddresses.TryRemove(container.Address, out container); 93 | // } 94 | // } 95 | // else if (OscAddress.IsValidAddressPattern(address)) 96 | // { 97 | // if (patternAddresses.TryGetValue(address, out OscEventContainer container) == false) 98 | // { 99 | // // no container was found so abort 100 | // return; 101 | // } 102 | // 103 | // // unregiser the event 104 | // container.Event -= @event; 105 | // 106 | // // if the container is now empty remove it from the lookup 107 | // if (container.IsNull) 108 | // { 109 | // patternAddresses.TryRemove(container.Address, out container); 110 | // } 111 | // } 112 | // else 113 | // { 114 | // throw new ArgumentException($"Invalid container address '{address}'", nameof(address)); 115 | // } 116 | // } 117 | // 118 | // /// 119 | // /// Disposes of any resources and releases all events 120 | // /// 121 | // public void Dispose() 122 | // { 123 | // foreach (KeyValuePair value in literalAddresses) 124 | // { 125 | // value.Value.Clear(); 126 | // } 127 | // 128 | // literalAddresses.Clear(); 129 | // 130 | // foreach (KeyValuePair value in patternAddresses) 131 | // { 132 | // value.Value.Clear(); 133 | // } 134 | // 135 | // patternAddresses.Clear(); 136 | // } 137 | // 138 | // public IEnumerator GetEnumerator() 139 | // { 140 | // return GetAllAddresses() 141 | // .GetEnumerator(); 142 | // } 143 | // 144 | // public void Invoke(TOscMessage message) 145 | // { 146 | // bool invoked = false; 147 | // 148 | // if (OscAddress.IsValidAddressLiteral(message.Address)) 149 | // { 150 | // if (literalAddresses.TryGetValue(message.Address, out OscEventContainer container)) 151 | // { 152 | // container.Invoke(message); 153 | // 154 | // invoked = true; 155 | // } 156 | // } 157 | // else 158 | // { 159 | // foreach (KeyValuePair value in literalAddresses) 160 | // { 161 | // OscAddress oscAddress = value.Value.OscAddress; 162 | // 163 | // if (value.Value.OscAddress.Match(value.Key) != true) 164 | // { 165 | // continue; 166 | // } 167 | // 168 | // value.Value.Invoke(message); 169 | // 170 | // invoked = true; 171 | // } 172 | // } 173 | // 174 | // if (patternAddresses.Count > 0) 175 | // { 176 | // OscAddress oscAddress = new OscAddress(message.Address); 177 | // 178 | // foreach (KeyValuePair value in patternAddresses) 179 | // { 180 | // if (oscAddress.Match(value.Key) == false) 181 | // { 182 | // continue; 183 | // } 184 | // 185 | // value.Value.Invoke(message); 186 | // invoked = true; 187 | // } 188 | // } 189 | // 190 | // if (invoked == false) 191 | // { 192 | // UnknownAddress?.Invoke(message); 193 | // } 194 | // } 195 | // 196 | // public void Invoke(IEnumerable messages) 197 | // { 198 | // foreach (TOscMessage message in messages) 199 | // { 200 | // Invoke(message); 201 | // } 202 | // } 203 | // 204 | // public event OscMessageEvent UnknownAddress; 205 | // 206 | // private List GetAllAddresses() 207 | // { 208 | // List addresses = new List(); 209 | // 210 | // addresses.AddRange(patternAddresses.Values.Select(container => container.OscAddress)); 211 | // 212 | // addresses.AddRange(literalAddresses.Values.Select(container => container.OscAddress)); 213 | // 214 | // return addresses; 215 | // } 216 | // 217 | // private class OscEventContainer 218 | // { 219 | // public readonly string Address; 220 | // public readonly OscAddress OscAddress; 221 | // 222 | // public bool IsNull => Event == null; 223 | // 224 | // public OscEventContainer(OscAddress address) 225 | // { 226 | // OscAddress = address; 227 | // 228 | // Address = address.ToString(); 229 | // 230 | // Event = null; 231 | // } 232 | // 233 | // public void Clear() 234 | // { 235 | // Event = null; 236 | // } 237 | // 238 | // public event OscMessageEvent Event; 239 | // 240 | // public void Invoke(TOscMessage message) 241 | // { 242 | // Event?.Invoke(message); 243 | // } 244 | // } 245 | // } 246 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/ArraySegmentByteExt.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace OscCore.LowLevel 7 | { 8 | public static class ArraySegmentByteExt 9 | { 10 | public static byte[] ToArray(this ArraySegment arraySegment) 11 | { 12 | byte[] buffer = new byte[arraySegment.Count]; 13 | 14 | Buffer.BlockCopy(arraySegment.Array, arraySegment.Offset, buffer, 0, arraySegment.Count); 15 | 16 | return buffer; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/OscSerializationToken.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | namespace OscCore.LowLevel 5 | { 6 | public enum OscSerializationToken 7 | { 8 | None, 9 | 10 | Literal, 11 | String, 12 | Symbol, 13 | Char, 14 | 15 | Separator, 16 | 17 | ArrayStart, 18 | ArrayEnd, 19 | 20 | ObjectStart, 21 | ObjectEnd, 22 | 23 | End 24 | } 25 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/OscSerializationUtils.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace OscCore.LowLevel 9 | { 10 | public class OscSerializationUtils 11 | { 12 | /// 13 | /// Turn a byte array into a readable, escaped string 14 | /// 15 | /// bytes 16 | /// a string 17 | public static string Escape(string original) 18 | { 19 | // the result is maximum of bytes length * 4 20 | char[] chars = new char[original.Length * 4]; 21 | 22 | int j = 0; 23 | 24 | for (int i = 0; i < original.Length; i++) 25 | { 26 | char c = original[i]; 27 | 28 | if (c > '~') 29 | { 30 | //chars[j++] = '�'; 31 | chars[j++] = '\\'; 32 | chars[j++] = 'x'; 33 | chars[j++] = ((c & 240) >> 4).ToString("X")[0]; 34 | chars[j++] = (c & 15).ToString("X")[0]; 35 | } 36 | else 37 | { 38 | switch (c) 39 | { 40 | case '\0': 41 | chars[j++] = '\\'; 42 | chars[j++] = '0'; 43 | break; 44 | 45 | case '\a': 46 | chars[j++] = '\\'; 47 | chars[j++] = 'a'; 48 | break; 49 | 50 | case '\b': 51 | chars[j++] = '\\'; 52 | chars[j++] = 'b'; 53 | break; 54 | 55 | case '\f': 56 | chars[j++] = '\\'; 57 | chars[j++] = 'f'; 58 | break; 59 | 60 | case '\n': 61 | chars[j++] = '\\'; 62 | chars[j++] = 'n'; 63 | break; 64 | 65 | case '\r': 66 | chars[j++] = '\\'; 67 | chars[j++] = 'r'; 68 | break; 69 | 70 | case '\t': 71 | chars[j++] = '\\'; 72 | chars[j++] = 't'; 73 | break; 74 | 75 | case '\v': 76 | chars[j++] = '\\'; 77 | chars[j++] = 'v'; 78 | break; 79 | 80 | case '"': 81 | chars[j++] = '\\'; 82 | chars[j++] = '"'; 83 | break; 84 | 85 | case '\\': 86 | chars[j++] = '\\'; 87 | chars[j++] = '\\'; 88 | break; 89 | 90 | default: 91 | if (c >= ' ') 92 | { 93 | chars[j++] = c; 94 | } 95 | else 96 | { 97 | chars[j++] = '\\'; 98 | chars[j++] = 'x'; 99 | chars[j++] = ((c & 240) >> 4).ToString("X")[0]; 100 | chars[j++] = (c & 15).ToString("X")[0]; 101 | } 102 | 103 | break; 104 | } 105 | } 106 | } 107 | 108 | return new string(chars, 0, j); 109 | } 110 | 111 | public static bool IsValidEscape(string str) 112 | { 113 | bool isEscaped = false; 114 | bool parseHexNext = false; 115 | int parseHexCount = 0; 116 | 117 | // first we count the number of chars we will be returning 118 | for (int i = 0; i < str.Length; i++) 119 | { 120 | char c = str[i]; 121 | 122 | if (parseHexNext) 123 | { 124 | parseHexCount++; 125 | 126 | if (IsHexChar(c) == false) 127 | { 128 | return false; 129 | } 130 | // 131 | // if (Uri.IsHexDigit(c) == false) 132 | // { 133 | // return false; 134 | // } 135 | 136 | if (parseHexCount == 2) 137 | { 138 | parseHexNext = false; 139 | parseHexCount = 0; 140 | } 141 | } 142 | // if we are not in an escape sequence and the char is a escape char 143 | else if (isEscaped == false && c == '\\') 144 | { 145 | // escape 146 | isEscaped = true; 147 | } 148 | // else if we are escaped 149 | else if (isEscaped) 150 | { 151 | // reset escape state 152 | isEscaped = false; 153 | 154 | // check the char against the set of known escape chars 155 | switch (char.ToLower(c)) 156 | { 157 | case '0': 158 | case 'a': 159 | case 'b': 160 | case 'f': 161 | case 'n': 162 | case 'r': 163 | case 't': 164 | case 'v': 165 | case '"': 166 | case '\\': 167 | // do not increment count 168 | break; 169 | 170 | case 'x': 171 | // do not increment count 172 | parseHexNext = true; 173 | parseHexCount = 0; 174 | break; 175 | 176 | default: 177 | // this is not a valid escape sequence 178 | // return false 179 | return false; 180 | } 181 | } 182 | } 183 | 184 | if (parseHexNext) 185 | { 186 | return false; 187 | } 188 | 189 | return isEscaped == false; 190 | } 191 | 192 | public static string ToStringBlob(byte[] bytes) 193 | { 194 | // if the default is to be Base64 encoded 195 | return "64x" + System.Convert.ToBase64String(bytes); 196 | 197 | // StringBuilder sb = new StringBuilder(bytes.Length * 2 + 2); 198 | // 199 | // sb.Append("0x"); 200 | // 201 | // foreach (byte b in bytes) 202 | // { 203 | // sb.Append(b.ToString("X2")); 204 | // } 205 | // 206 | // return sb.ToString(); 207 | } 208 | 209 | /// 210 | /// Turn a readable string into a byte array 211 | /// 212 | /// a string, optionally with escape sequences in it 213 | /// a byte array 214 | public static string Unescape(string str) 215 | { 216 | int count = 0; 217 | bool isEscaped = false; 218 | bool parseHexNext = false; 219 | int parseHexCount = 0; 220 | 221 | // Uri.HexEscape( 222 | // first we count the number of chars we will be returning 223 | for (int i = 0; i < str.Length; i++) 224 | { 225 | char c = str[i]; 226 | 227 | if (parseHexNext) 228 | { 229 | parseHexCount++; 230 | 231 | if (IsHexChar(c) == false) 232 | { 233 | throw new Exception($@"Invalid escape sequence at char '{i}' ""{c}"" is not a valid hex digit."); 234 | } 235 | 236 | // if (Uri.IsHexDigit(c) == false) 237 | // { 238 | // throw new Exception($@"Invalid escape sequence at char '{i}' ""{c}"" is not a valid hex digit."); 239 | // } 240 | 241 | if (parseHexCount == 2) 242 | { 243 | parseHexNext = false; 244 | parseHexCount = 0; 245 | } 246 | } 247 | // if we are not in an escape sequence and the char is a escape char 248 | else if (isEscaped == false && c == '\\') 249 | { 250 | // escape 251 | isEscaped = true; 252 | 253 | // increment count 254 | count++; 255 | } 256 | // else if we are escaped 257 | else if (isEscaped) 258 | { 259 | // reset escape state 260 | isEscaped = false; 261 | 262 | // check the char against the set of known escape chars 263 | switch (char.ToLower(c)) 264 | { 265 | case '0': 266 | case 'a': 267 | case 'b': 268 | case 'f': 269 | case 'n': 270 | case 'r': 271 | case 't': 272 | case 'v': 273 | case '"': 274 | case '\\': 275 | // do not increment count 276 | break; 277 | 278 | case 'x': 279 | // do not increment count 280 | parseHexNext = true; 281 | parseHexCount = 0; 282 | break; 283 | 284 | default: 285 | // this is not a valid escape sequence 286 | throw new Exception($"Invalid escape sequence at char '{i - 1}'."); 287 | } 288 | } 289 | else 290 | { 291 | // normal char increment count 292 | count++; 293 | } 294 | } 295 | 296 | if (parseHexNext) 297 | { 298 | throw new Exception($"Invalid escape sequence at char '{str.Length - 1}' missing hex value."); 299 | } 300 | 301 | if (isEscaped) 302 | { 303 | throw new Exception($"Invalid escape sequence at char '{str.Length - 1}'."); 304 | } 305 | 306 | // reset the escape state 307 | // isEscaped = false; 308 | // parseHexNext = false; 309 | // parseHexCount = 0; 310 | 311 | // create a byte array for the result 312 | char[] chars = new char[count]; 313 | 314 | int j = 0; 315 | 316 | // actually populate the array 317 | for (int i = 0; i < str.Length; i++) 318 | { 319 | char c = str[i]; 320 | 321 | // if we are not in an escape sequence and the char is a escape char 322 | if (isEscaped == false && c == '\\') 323 | { 324 | // escape 325 | isEscaped = true; 326 | } 327 | // else if we are escaped 328 | else if (isEscaped) 329 | { 330 | // reset escape state 331 | isEscaped = false; 332 | 333 | // check the char against the set of known escape chars 334 | switch (char.ToLower(str[i])) 335 | { 336 | case '0': 337 | chars[j++] = '\0'; 338 | break; 339 | 340 | case 'a': 341 | chars[j++] = '\a'; 342 | break; 343 | 344 | case 'b': 345 | chars[j++] = '\b'; 346 | break; 347 | 348 | case 'f': 349 | chars[j++] = '\f'; 350 | break; 351 | 352 | case 'n': 353 | chars[j++] = '\n'; 354 | break; 355 | 356 | case 'r': 357 | chars[j++] = '\r'; 358 | break; 359 | 360 | case 't': 361 | chars[j++] = '\t'; 362 | break; 363 | 364 | case 'v': 365 | chars[j++] = '\v'; 366 | break; 367 | 368 | case '"': 369 | chars[j++] = '"'; 370 | break; 371 | 372 | case '\\': 373 | chars[j++] = '\\'; 374 | break; 375 | 376 | case 'x': 377 | chars[j++] = (char) ((FromHex(str[++i]) << 4) | FromHex(str[++i])); 378 | break; 379 | } 380 | } 381 | else 382 | { 383 | // normal char 384 | chars[j++] = c; 385 | } 386 | } 387 | 388 | 389 | return new string(chars); 390 | } 391 | 392 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 393 | private static bool IsHexChar( 394 | char digit) => (digit >= '0' && digit <= '9') || 395 | (digit >= 'a' && digit <= 'f') || 396 | (digit >= 'A' && digit <= 'F'); 397 | 398 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 399 | private static int FromHex(char digit) 400 | { 401 | if (digit >= '0' && digit <= '9') 402 | { 403 | return digit - '0'; 404 | } 405 | 406 | if (digit >= 'a' && digit <= 'f') 407 | { 408 | return digit - 'a' + 10; 409 | } 410 | 411 | if (digit >= 'A' && digit <= 'F') 412 | { 413 | return digit - 'A' + 10; 414 | } 415 | 416 | throw new ArgumentException("digit is not a valid hexadecimal digit (0-9, a-f, A-F).", nameof(digit)); 417 | } 418 | } 419 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/OscStringWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Text; 8 | using OscCore.Address; 9 | 10 | namespace OscCore.LowLevel 11 | { 12 | public class OscStringWriter 13 | { 14 | private readonly StringBuilder builder = new StringBuilder(); 15 | private readonly IFormatProvider provider; 16 | 17 | public OscStringWriter(IFormatProvider provider = null) 18 | { 19 | this.provider = provider ?? CultureInfo.InvariantCulture; 20 | } 21 | 22 | /// 23 | public override string ToString() 24 | { 25 | return builder.ToString(); 26 | } 27 | 28 | public void Write(OscAddress address) 29 | { 30 | builder.Append(address); 31 | } 32 | 33 | public void Write(string value) 34 | { 35 | if (value == null) 36 | { 37 | WriteNull(); 38 | 39 | return; 40 | } 41 | 42 | builder.Append($@"""{OscSerializationUtils.Escape(value)}"""); 43 | } 44 | 45 | public void Write(float value) 46 | { 47 | if (float.IsInfinity(value) || float.IsNaN(value)) 48 | { 49 | builder.Append(value.ToString(provider)); 50 | } 51 | else 52 | { 53 | builder.Append($"{value.ToString(provider)}f"); 54 | } 55 | } 56 | 57 | public void Write(double value) 58 | { 59 | builder.Append($"{value.ToString(provider)}d"); 60 | } 61 | 62 | public void Write(byte value) 63 | { 64 | builder.Append($@"'{OscSerializationUtils.Escape(new string((char) value, 1))}'"); 65 | } 66 | 67 | public void Write(int value) 68 | { 69 | builder.Append(value.ToString(provider)); 70 | } 71 | 72 | public void Write(long value) 73 | { 74 | builder.Append($"{value.ToString(provider)}L"); 75 | } 76 | 77 | public void Write(bool value) 78 | { 79 | builder.Append($"{value.ToString()}"); 80 | } 81 | 82 | public void Write(byte[] value) 83 | { 84 | builder.Append($"{{ Blob: {OscSerializationUtils.ToStringBlob(value)} }}"); 85 | } 86 | 87 | public void Write(ref OscSymbol value) 88 | { 89 | if (value.Value == null) 90 | { 91 | WriteNull(); 92 | 93 | return; 94 | } 95 | 96 | builder.Append($@"$""{OscSerializationUtils.Escape(value.Value)}"""); 97 | } 98 | 99 | public void Write(OscSymbol value) 100 | { 101 | Write(ref value); 102 | } 103 | 104 | public void Write(ref OscTimeTag value) 105 | { 106 | builder.Append($"{{ Time: {value} }}"); 107 | } 108 | 109 | public void Write(OscTimeTag value) 110 | { 111 | Write(ref value); 112 | } 113 | 114 | public void Write(ref OscMidiMessage value) 115 | { 116 | builder.Append($"{{ Midi: {value} }}"); 117 | } 118 | 119 | public void Write(OscMidiMessage value) 120 | { 121 | Write(ref value); 122 | } 123 | 124 | public void Write(ref OscColor value) 125 | { 126 | builder.Append($"{{ Color: {value} }}"); 127 | } 128 | 129 | public void Write(OscColor value) 130 | { 131 | Write(ref value); 132 | } 133 | 134 | public void Write(object @object) 135 | { 136 | switch (@object) 137 | { 138 | case object[] value: 139 | WriteToken(OscSerializationToken.ArrayStart); 140 | Write(value); 141 | WriteToken(OscSerializationToken.ArrayEnd); 142 | break; 143 | case int value: 144 | Write(value); 145 | break; 146 | case long value: 147 | Write(value); 148 | break; 149 | case float value: 150 | Write(value); 151 | break; 152 | case double value: 153 | Write(value); 154 | break; 155 | case byte value: 156 | Write(value); 157 | break; 158 | case OscColor value: 159 | Write(value); 160 | break; 161 | case OscTimeTag value: 162 | Write(value); 163 | break; 164 | case OscMidiMessage value: 165 | Write(value); 166 | break; 167 | case bool value: 168 | Write(value); 169 | break; 170 | case OscNull value: 171 | WriteNull(); 172 | break; 173 | case OscImpulse value: 174 | WriteImpulse(); 175 | break; 176 | case string value: 177 | Write(value); 178 | break; 179 | case OscSymbol value: 180 | Write(value); 181 | break; 182 | case byte[] value: 183 | Write(value); 184 | break; 185 | default: 186 | throw new Exception($"Unsupported arguemnt type '{@object.GetType()}'"); 187 | } 188 | } 189 | 190 | public void Write(IEnumerable args) 191 | { 192 | bool first = true; 193 | 194 | foreach (object @object in args) 195 | { 196 | if (first == false) 197 | { 198 | WriteToken(OscSerializationToken.Separator); 199 | } 200 | else 201 | { 202 | first = false; 203 | } 204 | 205 | Write(@object); 206 | } 207 | } 208 | 209 | public void Write(IEnumerable array) 210 | { 211 | bool first = true; 212 | 213 | switch (array) 214 | { 215 | case IEnumerable value: 216 | foreach (string item in value) 217 | { 218 | if (first == false) 219 | { 220 | WriteToken(OscSerializationToken.Separator); 221 | } 222 | 223 | Write(item); 224 | 225 | first = false; 226 | } 227 | 228 | break; 229 | case IEnumerable value: 230 | foreach (float item in value) 231 | { 232 | if (first == false) 233 | { 234 | WriteToken(OscSerializationToken.Separator); 235 | } 236 | 237 | Write(item); 238 | 239 | first = false; 240 | } 241 | 242 | break; 243 | case IEnumerable value: 244 | foreach (double item in value) 245 | { 246 | if (first == false) 247 | { 248 | WriteToken(OscSerializationToken.Separator); 249 | } 250 | 251 | Write(item); 252 | 253 | first = false; 254 | } 255 | 256 | break; 257 | case IEnumerable value: 258 | foreach (int item in value) 259 | { 260 | if (first == false) 261 | { 262 | WriteToken(OscSerializationToken.Separator); 263 | } 264 | 265 | Write(item); 266 | 267 | first = false; 268 | } 269 | 270 | break; 271 | case IEnumerable value: 272 | foreach (long item in value) 273 | { 274 | if (first == false) 275 | { 276 | WriteToken(OscSerializationToken.Separator); 277 | } 278 | 279 | Write(item); 280 | 281 | first = false; 282 | } 283 | 284 | break; 285 | case IEnumerable value: 286 | foreach (bool item in value) 287 | { 288 | if (first == false) 289 | { 290 | WriteToken(OscSerializationToken.Separator); 291 | } 292 | 293 | Write(item); 294 | 295 | first = false; 296 | } 297 | 298 | break; 299 | case IEnumerable value: 300 | Write(value); 301 | break; 302 | case IEnumerable value: 303 | foreach (OscSymbol item in value) 304 | { 305 | if (first == false) 306 | { 307 | WriteToken(OscSerializationToken.Separator); 308 | } 309 | 310 | Write(item); 311 | 312 | first = false; 313 | } 314 | 315 | break; 316 | case IEnumerable value: 317 | foreach (OscTimeTag item in value) 318 | { 319 | if (first == false) 320 | { 321 | WriteToken(OscSerializationToken.Separator); 322 | } 323 | 324 | Write(item); 325 | 326 | first = false; 327 | } 328 | 329 | break; 330 | case IEnumerable value: 331 | foreach (OscMidiMessage item in value) 332 | { 333 | if (first == false) 334 | { 335 | WriteToken(OscSerializationToken.Separator); 336 | } 337 | 338 | Write(item); 339 | 340 | first = false; 341 | } 342 | 343 | break; 344 | case IEnumerable value: 345 | foreach (OscColor item in value) 346 | { 347 | if (first == false) 348 | { 349 | WriteToken(OscSerializationToken.Separator); 350 | } 351 | 352 | Write(item); 353 | 354 | first = false; 355 | } 356 | 357 | break; 358 | case IEnumerable value: 359 | foreach (object item in value) 360 | { 361 | if (first == false) 362 | { 363 | WriteToken(OscSerializationToken.Separator); 364 | } 365 | 366 | Write(item); 367 | 368 | first = false; 369 | } 370 | 371 | break; 372 | default: 373 | throw new Exception(); 374 | } 375 | } 376 | 377 | public void WriteAddress(string address) 378 | { 379 | builder.Append(address); 380 | } 381 | 382 | public void WriteBundleIdent(OscTimeTag timeTag) 383 | { 384 | builder.Append($"#bundle, {timeTag}"); 385 | } 386 | 387 | public void WriteImpulse() 388 | { 389 | builder.Append(OscImpulse.Value); 390 | } 391 | 392 | public void WriteNull() 393 | { 394 | builder.Append(OscNull.Value); 395 | } 396 | 397 | public void WriteToken(OscSerializationToken token) 398 | { 399 | switch (token) 400 | { 401 | case OscSerializationToken.Separator: 402 | builder.Append(", "); 403 | break; 404 | case OscSerializationToken.ArrayStart: 405 | builder.Append("[ "); 406 | break; 407 | case OscSerializationToken.ArrayEnd: 408 | builder.Append(" ]"); 409 | break; 410 | case OscSerializationToken.ObjectStart: 411 | builder.Append("{ "); 412 | break; 413 | case OscSerializationToken.ObjectEnd: 414 | builder.Append(" }"); 415 | break; 416 | case OscSerializationToken.None: 417 | case OscSerializationToken.Literal: 418 | case OscSerializationToken.String: 419 | case OscSerializationToken.Symbol: 420 | case OscSerializationToken.Char: 421 | case OscSerializationToken.End: 422 | default: 423 | throw new ArgumentOutOfRangeException(nameof(token), token, null); 424 | } 425 | } 426 | } 427 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/OscToken.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | namespace OscCore.LowLevel 5 | { 6 | /// 7 | /// A single osc message token. 8 | /// 9 | public enum OscToken 10 | { 11 | /// 12 | /// No token. 13 | /// 14 | None, 15 | 16 | /// 17 | /// Osc address string token. 18 | /// 19 | OscAddress, 20 | 21 | /// 22 | /// Type-tag string token. 23 | /// 24 | TypeTag, 25 | 26 | /// 27 | /// Char/byte token. 28 | /// 29 | Char, 30 | 31 | /// 32 | /// Meta boolean token (actual bools are defined as either true or false in the type-tag). 33 | /// 34 | Bool, 35 | 36 | /// 37 | /// Bool true value token. 38 | /// 39 | True, 40 | 41 | /// 42 | /// Bool false value token. 43 | /// 44 | False, 45 | 46 | /// 47 | /// String token. 48 | /// 49 | String, 50 | 51 | /// 52 | /// Symbol string token. 53 | /// 54 | Symbol, 55 | 56 | /// 57 | /// Impulse / bang token. 58 | /// 59 | Impulse, 60 | 61 | /// 62 | /// Null token. 63 | /// 64 | Null, 65 | 66 | /// 67 | /// Int32 token. 68 | /// 69 | Int, 70 | 71 | /// 72 | /// Int64 token. 73 | /// 74 | Long, 75 | 76 | /// 77 | /// Float / Single token. 78 | /// 79 | Float, 80 | 81 | /// 82 | /// Double token. 83 | /// 84 | Double, 85 | 86 | /// 87 | /// Osc time-tag token. 88 | /// 89 | TimeTag, 90 | 91 | /// 92 | /// Osc time-tag token. 93 | /// 94 | Blob, 95 | 96 | /// 97 | /// Osc color token. 98 | /// 99 | Color, 100 | 101 | /// 102 | /// Osc midi token. 103 | /// 104 | Midi, 105 | 106 | /// 107 | /// Token represents the start of an array. 108 | /// 109 | ArrayStart, 110 | 111 | /// 112 | /// Token represents the end of an array. 113 | /// 114 | ArrayEnd, 115 | 116 | /// 117 | /// Meta token used to indicate multiple types are present in an argument array. 118 | /// 119 | MixedTypes, 120 | 121 | /// 122 | /// Token represents the end of the message. 123 | /// 124 | End, 125 | 126 | /// 127 | /// Bundle message length token. 128 | /// 129 | BundleMessageLength 130 | } 131 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/OscTypeTag.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace OscCore.LowLevel 8 | { 9 | public struct OscTypeTag 10 | { 11 | private readonly string typeTag; 12 | 13 | public OscTypeTag(string typeTag) 14 | { 15 | this.typeTag = typeTag; 16 | Index = 0; 17 | } 18 | 19 | public OscToken CurrentToken => GetTokenFromTypeTag(Index); 20 | 21 | public int Index { get; private set; } 22 | 23 | public OscToken NextToken() 24 | { 25 | return GetTokenFromTypeTag(++Index); 26 | } 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public int GetArgumentCount(out OscToken arrayType) 30 | { 31 | return GetArrayLength(0, out arrayType); 32 | } 33 | 34 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 35 | public int GetArrayElementCount(out OscToken arrayType) 36 | { 37 | return GetArrayLength(Index + 1, out arrayType); 38 | } 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | private OscToken GetTokenFromTypeTag(int index) 42 | { 43 | if (index == typeTag.Length) 44 | { 45 | return OscToken.End; 46 | } 47 | 48 | if (index < 0 || index > typeTag.Length) 49 | { 50 | throw new ArgumentOutOfRangeException(nameof(index), index, "Index is not a valid part of the type tag"); 51 | } 52 | 53 | char type = typeTag[index]; 54 | 55 | // ReSharper disable once SwitchStatementMissingSomeCases 56 | switch (type) 57 | { 58 | case 'b': 59 | return OscToken.Blob; 60 | case 's': 61 | return OscToken.String; 62 | case 'S': 63 | return OscToken.Symbol; 64 | case 'i': 65 | return OscToken.Int; 66 | case 'h': 67 | return OscToken.Long; 68 | case 'f': 69 | return OscToken.Float; 70 | case 'd': 71 | return OscToken.Double; 72 | case 't': 73 | return OscToken.TimeTag; 74 | case 'c': 75 | return OscToken.Char; 76 | case 'r': 77 | return OscToken.Color; 78 | case 'm': 79 | return OscToken.Midi; 80 | case 'T': 81 | return OscToken.True; 82 | case 'F': 83 | return OscToken.False; 84 | case 'N': 85 | return OscToken.Null; 86 | case 'I': 87 | return OscToken.Impulse; 88 | case '[': 89 | return OscToken.ArrayStart; 90 | case ']': 91 | return OscToken.ArrayEnd; 92 | default: 93 | // Unknown argument type 94 | throw new OscException(OscError.UnknownArguemntType, $@"Unknown OSC type '{type}' on argument '{index}'"); 95 | } 96 | } 97 | 98 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 99 | private int GetArrayLength(int index, out OscToken arrayType) 100 | { 101 | arrayType = OscToken.None; 102 | 103 | if (index == typeTag.Length) 104 | { 105 | return 0; 106 | } 107 | 108 | if (index < 0 || index > typeTag.Length) 109 | { 110 | throw new ArgumentOutOfRangeException(nameof(index), index, "Index is not a valid part of the type tag"); 111 | } 112 | 113 | int count = 0; 114 | int inset = 0; 115 | 116 | while (true) 117 | { 118 | OscToken token = GetTokenFromTypeTag(index++); 119 | 120 | // ReSharper disable once SwitchStatementMissingSomeCases 121 | switch (token) 122 | { 123 | case OscToken.None: 124 | case OscToken.OscAddress: 125 | case OscToken.TypeTag: 126 | throw new OscException(OscError.UnexpectedToken, $"Unexpected token {token}"); 127 | case OscToken.True: 128 | case OscToken.False: 129 | if (arrayType == OscToken.None) 130 | { 131 | arrayType = OscToken.Bool; 132 | } 133 | else if (arrayType != OscToken.Bool) 134 | { 135 | arrayType = OscToken.MixedTypes; 136 | } 137 | 138 | if (inset == 0) 139 | { 140 | count++; 141 | } 142 | 143 | break; 144 | case OscToken.Null: 145 | if (arrayType != OscToken.String && 146 | arrayType != OscToken.Blob) 147 | { 148 | arrayType = OscToken.MixedTypes; 149 | } 150 | 151 | if (inset == 0) 152 | { 153 | count++; 154 | } 155 | 156 | break; 157 | case OscToken.String: 158 | case OscToken.Blob: 159 | case OscToken.Char: 160 | case OscToken.Symbol: 161 | case OscToken.Impulse: 162 | case OscToken.Int: 163 | case OscToken.Long: 164 | case OscToken.Float: 165 | case OscToken.Double: 166 | case OscToken.TimeTag: 167 | case OscToken.Color: 168 | case OscToken.Midi: 169 | if (arrayType == OscToken.None) 170 | { 171 | arrayType = token; 172 | } 173 | else if (arrayType != token) 174 | { 175 | arrayType = OscToken.MixedTypes; 176 | } 177 | 178 | if (inset == 0) 179 | { 180 | count++; 181 | } 182 | 183 | break; 184 | case OscToken.ArrayStart: 185 | if (inset == 0) 186 | { 187 | count++; 188 | } 189 | 190 | inset++; 191 | break; 192 | case OscToken.ArrayEnd: 193 | inset--; 194 | 195 | if (inset == -1) 196 | { 197 | return count; 198 | } 199 | 200 | break; 201 | case OscToken.End: 202 | return count; 203 | case OscToken.MixedTypes: 204 | default: 205 | throw new OscException(OscError.UnknownArguemntType, $@"Unknown OSC type '{token}' on argument '{index}'"); 206 | } 207 | } 208 | } 209 | } 210 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/OscUtils.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | namespace OscCore.LowLevel 5 | { 6 | public static class OscUtils 7 | { 8 | /// 9 | /// Are the contents of 2 argument arrays the equivalent 10 | /// 11 | /// An array containing argument objects 12 | /// An array containing argument objects 13 | /// true if the object arrays are equivalent 14 | public static bool ArgumentsAreEqual(object[] array1, object[] array2) 15 | { 16 | // ensure the arrays the same length 17 | if (array1.Length != array2.Length) 18 | { 19 | return false; 20 | } 21 | 22 | // iterate through the arrays 23 | for (int i = 0; i < array1.Length; i++) 24 | { 25 | // ensure the objects at index i of the same type? 26 | if (array1[i] 27 | .GetType() != array2[i] 28 | .GetType()) 29 | { 30 | return false; 31 | } 32 | 33 | // is the argument an object array 34 | if (array1[i] is object[]) 35 | { 36 | object[] expectedArg = (object[]) array1[i]; 37 | object[] actualArg = (object[]) array2[i]; 38 | 39 | // ensure the argument object arrays are the same 40 | if (ArgumentsAreEqual(expectedArg, actualArg) == false) 41 | { 42 | return false; 43 | } 44 | } 45 | // is the argument an byte array 46 | else if (array1[i] is byte[]) 47 | { 48 | byte[] expectedArg = (byte[]) array1[i]; 49 | byte[] actualArg = (byte[]) array2[i]; 50 | 51 | // ensure the byte arrays are the same 52 | if (BytesAreEqual(expectedArg, actualArg) == false) 53 | { 54 | return false; 55 | } 56 | } 57 | // is the argument a color 58 | else if (array1[i] is OscColor) 59 | { 60 | OscColor expectedArg = (OscColor) array1[i]; 61 | OscColor actualArg = (OscColor) array2[i]; 62 | 63 | // check the RGBA values 64 | if (expectedArg.R != actualArg.R || 65 | expectedArg.G != actualArg.G || 66 | expectedArg.B != actualArg.B || 67 | expectedArg.A != actualArg.A) 68 | { 69 | return false; 70 | } 71 | } 72 | // anything else 73 | else 74 | { 75 | // just check the value 76 | if (array1[i] 77 | .Equals(array2[i]) == false) 78 | { 79 | return false; 80 | } 81 | } 82 | } 83 | 84 | // we are good 85 | return true; 86 | } 87 | 88 | /// 89 | /// Check the contents of 2 arrays of bytes are the same 90 | /// 91 | /// The expected contents 92 | /// The actual contents 93 | /// True if the contents are the same 94 | public static bool BytesAreEqual(byte[] expected, byte[] actual) 95 | { 96 | if (expected.Length != actual.Length) 97 | { 98 | return false; 99 | } 100 | 101 | for (int i = 0; i < expected.Length; i++) 102 | { 103 | if (expected[i] != actual[i]) 104 | { 105 | return false; 106 | } 107 | } 108 | 109 | return true; 110 | } 111 | 112 | /// 113 | /// Are 2 messages equivalent 114 | /// 115 | /// A message 116 | /// A message 117 | /// true if the objects are equivalent 118 | public static bool MessagesAreEqual(OscMessage message1, OscMessage message2) 119 | { 120 | // ensure the address is the same 121 | if (message1.Address != message2.Address) 122 | { 123 | return false; 124 | } 125 | 126 | // ensure the argument arrays are the same 127 | return ArgumentsAreEqual(message1.ToArray(), message2.ToArray()); 128 | } 129 | 130 | 131 | /// 132 | /// Calculate the size of the an object array in bytes 133 | /// 134 | /// the array 135 | /// the size of the array in bytes 136 | public static int SizeOfObjectArray(object[] args) 137 | { 138 | int size = 0; 139 | int nullCount = 0; 140 | 141 | foreach (object obj in args) 142 | { 143 | if (obj is object[]) 144 | { 145 | size += SizeOfObjectArray(obj as object[]); 146 | } 147 | else if ( 148 | obj is int || 149 | obj is float || 150 | obj is byte || 151 | obj is OscMidiMessage || 152 | obj is OscColor) 153 | { 154 | size += 4; 155 | } 156 | else if ( 157 | obj is long || 158 | obj is double || 159 | obj is OscTimeTag) 160 | { 161 | size += 8; 162 | } 163 | else if ( 164 | obj is string || 165 | obj is OscSymbol) 166 | { 167 | string value = obj.ToString(); 168 | 169 | // string and terminator 170 | size += value.Length + 1; 171 | 172 | // padding 173 | nullCount = 4 - size % 4; 174 | 175 | if (nullCount < 4) 176 | { 177 | size += nullCount; 178 | } 179 | } 180 | else if (obj is byte[]) 181 | { 182 | byte[] value = (byte[]) obj; 183 | 184 | // length integer 185 | size += 4; 186 | 187 | // content 188 | size += value.Length; 189 | 190 | // padding 191 | nullCount = 4 - size % 4; 192 | 193 | if (nullCount < 4) 194 | { 195 | size += nullCount; 196 | } 197 | } 198 | else if ( 199 | obj is bool || 200 | obj is OscNull || 201 | obj is OscImpulse) 202 | { 203 | size += 0; 204 | } 205 | } 206 | 207 | return size; 208 | } 209 | 210 | /// 211 | /// Calculate the size of the type tag of an object array 212 | /// 213 | /// the array 214 | /// the size of the type tag for the array 215 | public static int SizeOfObjectArray_TypeTag(object[] args) 216 | { 217 | int size = 0; 218 | 219 | // typetag 220 | foreach (object obj in args) 221 | { 222 | if (obj is object[]) 223 | { 224 | size += SizeOfObjectArray_TypeTag(obj as object[]); 225 | size += 2; // for the [ ] 226 | } 227 | else 228 | { 229 | size++; 230 | } 231 | } 232 | 233 | return size; 234 | } 235 | } 236 | } -------------------------------------------------------------------------------- /OscCore/LowLevel/OscWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | 10 | namespace OscCore.LowLevel 11 | { 12 | public class OscWriter 13 | { 14 | private static readonly bool IsLittleEndian; 15 | private readonly byte[] argumentBuffer; 16 | private int argumentBufferCount; 17 | 18 | private readonly MemoryStream buffer; 19 | private int count; 20 | private BitFlipper32 flipper32; 21 | 22 | private WriterState state; 23 | 24 | static OscWriter() 25 | { 26 | IsLittleEndian = BitConverter.IsLittleEndian; 27 | } 28 | 29 | public OscWriter(MemoryStream buffer) 30 | { 31 | flipper32 = new BitFlipper32(); 32 | 33 | this.buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); 34 | argumentBuffer = new byte[16]; 35 | argumentBufferCount = 0; 36 | count = 0; 37 | state = WriterState.NotStarted; 38 | } 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public void StartBundle(string ident, ref OscTimeTag timestamp) 42 | { 43 | StartMessage(); 44 | 45 | // write the address 46 | WriteDirect(Encoding.UTF8.GetBytes(ident)); 47 | 48 | // write null terminator 49 | Write((byte) 0); 50 | 51 | WritePadding(); 52 | 53 | Write(unchecked((long) timestamp.Value)); 54 | Flush(); 55 | } 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public void StartMessage() 59 | { 60 | state = WriterState.Address; 61 | count = 0; 62 | argumentBufferCount = 0; 63 | } 64 | 65 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 | public void WriteAddress(string address) 67 | { 68 | CheckWriterState(WriterState.Address); 69 | 70 | // write the address 71 | WriteDirect(Encoding.UTF8.GetBytes(address)); 72 | 73 | // write null terminator 74 | Write((byte) 0); 75 | 76 | WritePadding(); 77 | 78 | // write the comma for the type-tag 79 | Write((byte) ','); 80 | 81 | Flush(); 82 | 83 | state = WriterState.TypeTag; 84 | } 85 | 86 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 87 | public void WriteBlob(byte[] buffer) 88 | { 89 | CheckWriterState(WriterState.Arguments); 90 | 91 | // write length 92 | Write(buffer.Length); 93 | Flush(); 94 | 95 | // write bytes 96 | WriteDirect(buffer); 97 | 98 | WritePadding(); 99 | Flush(); 100 | } 101 | 102 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 103 | public void WriteBlob(ArraySegment buffer) 104 | { 105 | CheckWriterState(WriterState.Arguments); 106 | 107 | // write length 108 | Write(buffer.Count); 109 | Flush(); 110 | 111 | // write bytes 112 | WriteDirect(buffer.Array, buffer.Offset, buffer.Count); 113 | 114 | WritePadding(); 115 | Flush(); 116 | } 117 | 118 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 119 | public void WriteBundleMessageLength(int messageSizeInBytes) 120 | { 121 | Write(messageSizeInBytes); 122 | Flush(); 123 | } 124 | 125 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 126 | public void WriteChar(byte value) 127 | { 128 | CheckWriterState(WriterState.Arguments); 129 | 130 | Write(value); 131 | Write((byte) 0); 132 | Write((byte) 0); 133 | Write((byte) 0); 134 | 135 | Flush(); 136 | } 137 | 138 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 139 | public void WriteColor(ref OscColor value) 140 | { 141 | CheckWriterState(WriterState.Arguments); 142 | 143 | int intValue = (value.R << 24) | 144 | (value.G << 16) | 145 | (value.B << 8) | 146 | (value.A << 0); 147 | 148 | Write(intValue); 149 | 150 | Flush(); 151 | } 152 | 153 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 154 | public void WriteDouble(double value) 155 | { 156 | CheckWriterState(WriterState.Arguments); 157 | 158 | Write(BitConverter.DoubleToInt64Bits(value)); 159 | 160 | Flush(); 161 | } 162 | 163 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 164 | public void WriteFloat(float value) 165 | { 166 | CheckWriterState(WriterState.Arguments); 167 | 168 | flipper32.ValueFloat = value; 169 | 170 | Write(flipper32.ValueInt32); 171 | 172 | Flush(); 173 | } 174 | 175 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 176 | public void WriteInt(int value) 177 | { 178 | CheckWriterState(WriterState.Arguments); 179 | 180 | Write(value); 181 | 182 | Flush(); 183 | } 184 | 185 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 186 | public void WriteLong(long value) 187 | { 188 | CheckWriterState(WriterState.Arguments); 189 | 190 | Write(value); 191 | 192 | Flush(); 193 | } 194 | 195 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 196 | public void WriteMidi(ref OscMidiMessage value) 197 | { 198 | CheckWriterState(WriterState.Arguments); 199 | 200 | Write(unchecked((int) value.FullMessage)); 201 | 202 | Flush(); 203 | } 204 | 205 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 206 | public void WriteString(string value) 207 | { 208 | CheckWriterState(WriterState.Arguments); 209 | 210 | // write the address 211 | WriteDirect(Encoding.UTF8.GetBytes(value)); 212 | // write null terminator 213 | Write((byte) 0); 214 | 215 | WritePadding(); 216 | Flush(); 217 | } 218 | 219 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 220 | public void WriteSymbol(ref OscSymbol value) 221 | { 222 | CheckWriterState(WriterState.Arguments); 223 | 224 | // write the address 225 | WriteDirect(Encoding.UTF8.GetBytes(value.Value)); 226 | // write null terminator 227 | Write((byte) 0); 228 | 229 | WritePadding(); 230 | Flush(); 231 | } 232 | 233 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 234 | public void WriteTimeTag(ref OscTimeTag value) 235 | { 236 | CheckWriterState(WriterState.Arguments); 237 | 238 | Write(unchecked((long) value.Value)); 239 | 240 | Flush(); 241 | } 242 | 243 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 244 | public void WriteTypeTag(OscToken token) 245 | { 246 | CheckWriterState(WriterState.TypeTag); 247 | 248 | FlushConditionally(); 249 | 250 | switch (token) 251 | { 252 | case OscToken.Char: 253 | Write((byte) 'c'); 254 | break; 255 | case OscToken.True: 256 | Write((byte) 'T'); 257 | break; 258 | case OscToken.False: 259 | Write((byte) 'F'); 260 | break; 261 | case OscToken.String: 262 | Write((byte) 's'); 263 | break; 264 | case OscToken.Symbol: 265 | Write((byte) 'S'); 266 | break; 267 | case OscToken.Impulse: 268 | Write((byte) 'I'); 269 | break; 270 | case OscToken.Null: 271 | Write((byte) 'N'); 272 | break; 273 | case OscToken.Int: 274 | Write((byte) 'i'); 275 | break; 276 | case OscToken.Long: 277 | Write((byte) 'h'); 278 | break; 279 | case OscToken.Float: 280 | Write((byte) 'f'); 281 | break; 282 | case OscToken.Double: 283 | Write((byte) 'd'); 284 | break; 285 | case OscToken.TimeTag: 286 | Write((byte) 't'); 287 | break; 288 | case OscToken.Blob: 289 | Write((byte) 'b'); 290 | break; 291 | case OscToken.Color: 292 | Write((byte) 'r'); 293 | break; 294 | case OscToken.Midi: 295 | Write((byte) 'm'); 296 | break; 297 | case OscToken.ArrayStart: 298 | Write((byte) '['); 299 | break; 300 | case OscToken.ArrayEnd: 301 | Write((byte) ']'); 302 | break; 303 | default: 304 | throw new OscException(OscError.UnexpectedToken, $"Unexpected token {token}"); 305 | } 306 | } 307 | 308 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 309 | public void WriteTypeTagEnd() 310 | { 311 | CheckWriterState(WriterState.TypeTag); 312 | 313 | // write null terminator 314 | Write((byte) 0); 315 | 316 | WritePadding(); 317 | Flush(); 318 | 319 | state = WriterState.Arguments; 320 | } 321 | 322 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 323 | private int CalculatePadding() 324 | { 325 | int nullCount = 4 - count % 4; 326 | 327 | return nullCount < 4 ? nullCount : 0; 328 | } 329 | 330 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 331 | private void CheckWriterState(WriterState requiredState) 332 | { 333 | if (state != requiredState) 334 | { 335 | throw new OscException(OscError.UnexpectedWriterState, $"Unexpected writer state {state}"); 336 | } 337 | } 338 | 339 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 340 | private void Flush() 341 | { 342 | if (argumentBufferCount == 0) 343 | { 344 | return; 345 | } 346 | 347 | buffer.Write(argumentBuffer, 0, argumentBufferCount); 348 | argumentBufferCount = 0; 349 | } 350 | 351 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 352 | private void FlushConditionally() 353 | { 354 | if (argumentBufferCount + 4 > argumentBuffer.Length) 355 | { 356 | Flush(); 357 | } 358 | } 359 | 360 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 361 | private void Write(byte value) 362 | { 363 | argumentBuffer[argumentBufferCount++] = value; 364 | count++; 365 | } 366 | 367 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 368 | private void Write(int value) 369 | { 370 | if (IsLittleEndian) 371 | { 372 | uint uValue = unchecked((uint) value); 373 | 374 | for (int i = 0; i < 4; i++) 375 | { 376 | argumentBuffer[argumentBufferCount++] = unchecked((byte) (((uValue & 0xff000000) >> 24) & 0xff)); 377 | uValue = uValue << 8; 378 | count++; 379 | } 380 | } 381 | else 382 | { 383 | for (int i = 0; i < 4; i++) 384 | { 385 | argumentBuffer[argumentBufferCount++] = unchecked((byte) (value & 0xff)); 386 | value = value >> 8; 387 | count++; 388 | } 389 | } 390 | } 391 | 392 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 393 | private void Write(long value) 394 | { 395 | if (IsLittleEndian) 396 | { 397 | ulong uValue = unchecked((ulong) value); 398 | 399 | for (int i = 0; i < 8; i++) 400 | { 401 | argumentBuffer[argumentBufferCount++] = unchecked((byte) (((uValue & 0xff00000000000000) >> 56) & 0xff)); 402 | uValue = uValue << 8; 403 | count++; 404 | } 405 | } 406 | else 407 | { 408 | for (int i = 0; i < 8; i++) 409 | { 410 | argumentBuffer[argumentBufferCount++] = unchecked((byte) (value & 0xff)); 411 | value = value >> 8; 412 | count++; 413 | } 414 | } 415 | } 416 | 417 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 418 | private void WriteDirect(byte[] bytes) 419 | { 420 | buffer.Write(bytes, 0, bytes.Length); 421 | count += bytes.Length; 422 | } 423 | 424 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 425 | private void WriteDirect(byte[] bytes, int index, int count) 426 | { 427 | buffer.Write(bytes, index, count); 428 | this.count += count; 429 | } 430 | 431 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 432 | private void WritePadding() 433 | { 434 | int nullCount = CalculatePadding(); 435 | 436 | for (int i = 0; i < nullCount; i++) 437 | { 438 | argumentBuffer[argumentBufferCount++] = 0; 439 | count++; 440 | } 441 | } 442 | 443 | private enum WriterState 444 | { 445 | NotStarted, 446 | Address, 447 | TypeTag, 448 | Arguments 449 | } 450 | 451 | [StructLayout(LayoutKind.Explicit)] 452 | private struct BitFlipper32 453 | { 454 | [FieldOffset(0)] public readonly int ValueInt32; 455 | 456 | [FieldOffset(0)] public float ValueFloat; 457 | } 458 | } 459 | } -------------------------------------------------------------------------------- /OscCore/OscBundleRaw.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Runtime.CompilerServices; 8 | using OscCore.LowLevel; 9 | 10 | namespace OscCore 11 | { 12 | public sealed class OscBundleRaw : IEnumerable 13 | { 14 | private readonly OscMessageRaw[] messages; 15 | 16 | public OscMessageRaw this[int index] => messages[index]; 17 | 18 | public int Count => messages.Length; 19 | 20 | public Uri Origin { get; } 21 | 22 | public OscTimeTag Timestamp { get; } 23 | 24 | public OscBundleRaw(ArraySegment buffer, Uri origin = null) 25 | { 26 | Origin = origin; 27 | 28 | OscReader reader = new OscReader(buffer); 29 | 30 | List messages = new List(); 31 | 32 | ReadMessages(buffer, reader, buffer.Count, messages, out OscTimeTag timestamp); 33 | 34 | Timestamp = timestamp; 35 | 36 | this.messages = messages.ToArray(); 37 | } 38 | 39 | /// 40 | IEnumerator IEnumerable.GetEnumerator() 41 | { 42 | return messages.GetEnumerator(); 43 | } 44 | 45 | /// 46 | public IEnumerator GetEnumerator() 47 | { 48 | return (messages as IEnumerable).GetEnumerator(); 49 | } 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | private void ReadMessages( 53 | ArraySegment buffer, 54 | OscReader reader, 55 | int count, 56 | List messages, 57 | out OscTimeTag timestamp) 58 | { 59 | int start = reader.Position; 60 | int bundleEnd = start + count; 61 | 62 | reader.BeginBundle(count); 63 | 64 | string ident = reader.ReadAddress(); 65 | 66 | if (OscBundle.BundleIdent.Equals(ident, StringComparison.Ordinal) == false) 67 | { 68 | // this is an error 69 | throw new OscException(OscError.InvalidBundleIdent, $"Invalid bundle ident '{ident}'"); 70 | } 71 | 72 | timestamp = reader.ReadBundleTimeTag(); 73 | 74 | while (reader.Position < bundleEnd) 75 | { 76 | if (reader.Position + 4 > bundleEnd) 77 | { 78 | // this is an error 79 | throw new OscException(OscError.InvalidBundleMessageHeader, "Invalid bundle message header"); 80 | } 81 | 82 | int messageLength = reader.ReadBundleMessageLength(start, count); 83 | 84 | if (reader.Position + messageLength > bundleEnd || 85 | messageLength < 0 || 86 | messageLength % 4 != 0) 87 | { 88 | // this is an error 89 | throw new OscException(OscError.InvalidBundleMessageLength, "Invalid bundle message length"); 90 | } 91 | 92 | if (reader.PeekByte() == (byte) '#') 93 | { 94 | ReadMessages(buffer, reader, messageLength, messages, out OscTimeTag _); 95 | } 96 | else 97 | { 98 | messages.Add(new OscMessageRaw(new ArraySegment(buffer.Array, buffer.Offset + reader.Position, messageLength), Origin, timestamp)); 99 | 100 | reader.Position += messageLength; 101 | } 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /OscCore/OscCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.5 4 | true 5 | 6 | 7 | 8 | 9 | true 10 | -debug-$([System.DateTime]::Now.ToString("yyyyMMddHHmm")) 11 | 12 | 13 | 14 | false 15 | 16 | 17 | 18 | 19 | $(VersionBase)$(VersionSuffix) 20 | netstandard1.3 21 | $(AssemblyName) 22 | ../../.package-store 23 | Tilde Love Project 24 | http://tilde.love 25 | Phill Tew 26 | Copyright (c) Tilde Love Project. All rights reserved. 27 | https://s.gravatar.com/avatar/d56be08732b3f23cc1b11662034d8b1e?s=64 28 | 29 | 30 | 31 | Osc Core 32 | A preformant Open Sound Control library for .NET Standard from the creator of Rug.Osc 33 | https://github.com/tilde-love/osc-core 34 | tilde; OSC; Open Sound Control; 35 | 36 | 37 | -------------------------------------------------------------------------------- /OscCore/OscError.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | namespace OscCore 5 | { 6 | /// 7 | /// All errors that can occur while parsing or reading osc packets, messages and bundles 8 | /// 9 | public enum OscError 10 | { 11 | /// 12 | /// No error 13 | /// 14 | None, 15 | 16 | /// 17 | /// An invalid number or bytes has been read 18 | /// 19 | InvalidSegmentLength, 20 | 21 | /// 22 | /// The address string is empty 23 | /// 24 | MissingAddress, 25 | 26 | /// 27 | /// Missing comma after the address string 28 | /// 29 | MissingComma, 30 | 31 | /// 32 | /// Missing type-tag 33 | /// 34 | MissingTypeTag, 35 | 36 | /// 37 | /// Invalid type-tag 38 | /// 39 | MalformedTypeTag, 40 | 41 | /// 42 | /// Error parsing arguemnt 43 | /// 44 | ErrorParsingArgument, 45 | 46 | /// 47 | /// Error parsing blob argument 48 | /// 49 | ErrorParsingBlob, 50 | 51 | /// 52 | /// Error parsing string argument 53 | /// 54 | ErrorParsingString, 55 | 56 | /// 57 | /// Error parsing symbol argument 58 | /// 59 | ErrorParsingSymbol, 60 | 61 | /// 62 | /// Error parsing int argument 63 | /// 64 | ErrorParsingInt32, 65 | 66 | /// 67 | /// Error parsing long argument 68 | /// 69 | ErrorParsingInt64, 70 | 71 | /// 72 | /// Error parsing float argument 73 | /// 74 | ErrorParsingSingle, 75 | 76 | /// 77 | /// Error parsing double argument 78 | /// 79 | ErrorParsingDouble, 80 | 81 | /// 82 | /// Error parsing osc-color argument 83 | /// 84 | ErrorParsingColor, 85 | 86 | /// 87 | /// Error parsing char argument 88 | /// 89 | ErrorParsingChar, 90 | 91 | /// 92 | /// Error parsing midi message argument 93 | /// 94 | ErrorParsingMidiMessage, 95 | 96 | /// 97 | /// Error parsing midi message argument 98 | /// 99 | ErrorParsingOscTimeTag, 100 | 101 | /// 102 | /// The type of an argument is unsupported 103 | /// 104 | UnknownArguemntType, 105 | 106 | /// 107 | /// Bundle with missing ident 108 | /// 109 | MissingBundleIdent, 110 | 111 | /// 112 | /// Bundle with invalid ident 113 | /// 114 | InvalidBundleIdent, 115 | 116 | /// 117 | /// Invalid bundle message header 118 | /// 119 | InvalidBundleMessageHeader, 120 | 121 | /// 122 | /// An error occured while parsing a packet 123 | /// 124 | ErrorParsingPacket, 125 | 126 | /// 127 | /// Invalid bundle message length 128 | /// 129 | InvalidBundleMessageLength, 130 | 131 | /// 132 | /// 133 | UnexpectedToken, 134 | 135 | /// 136 | /// 137 | UnexpectedWriterState, 138 | ErrorParsingOscAdress, 139 | InvalidObjectName 140 | } 141 | } -------------------------------------------------------------------------------- /OscCore/OscException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace OscCore 7 | { 8 | public class OscException : Exception 9 | { 10 | public readonly OscError OscError; 11 | 12 | public OscException(OscError oscError, string message) : base(message) 13 | { 14 | OscError = oscError; 15 | } 16 | 17 | public OscException(OscError oscError, string message, Exception innerException) : base(message, innerException) 18 | { 19 | OscError = oscError; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /OscCore/OscMessageRaw.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Runtime.CompilerServices; 8 | using OscCore.LowLevel; 9 | 10 | namespace OscCore 11 | { 12 | public struct OscArgument 13 | { 14 | public int Position; 15 | 16 | public bool IsArray; 17 | 18 | public OscToken Type; 19 | 20 | public OscArgument[] Array; 21 | } 22 | 23 | public class OscMessageRaw : IOscMessage, IEnumerable 24 | { 25 | private readonly OscArgument[] arguments; 26 | 27 | private readonly OscReader reader; 28 | 29 | public OscArgument this[int index] => arguments[index]; 30 | 31 | public OscMessageRaw(ArraySegment buffer, Uri origin = null, OscTimeTag? timestamp = null) 32 | { 33 | Origin = origin; 34 | Timestamp = timestamp; 35 | 36 | reader = new OscReader(buffer); 37 | 38 | reader.BeginMessage(buffer.Count); 39 | 40 | Address = reader.ReadAddress(); 41 | 42 | if (reader.PeekToken() == OscToken.End) 43 | { 44 | arguments = new OscArgument[0]; 45 | 46 | return; 47 | } 48 | 49 | OscTypeTag typeTag = reader.ReadTypeTag(); 50 | 51 | arguments = new OscArgument[reader.GetArgumentCount(ref typeTag, out OscToken argumentsType)]; 52 | 53 | int argumentsStart = reader.Position; 54 | int position = argumentsStart; 55 | 56 | OscToken token = OscToken.None; 57 | 58 | for (int i = 0; i < arguments.Length; i++) 59 | { 60 | token = typeTag.CurrentToken; 61 | 62 | if (token == OscToken.ArrayStart) 63 | { 64 | arguments[i] = GetArrayArgument(ref typeTag, ref position); 65 | } 66 | else 67 | { 68 | arguments[i] = new OscArgument 69 | { 70 | Position = position, 71 | Type = token 72 | }; 73 | 74 | switch (token) 75 | { 76 | case OscToken.Bool: 77 | case OscToken.True: 78 | case OscToken.False: 79 | case OscToken.Impulse: 80 | case OscToken.Null: 81 | break; 82 | 83 | case OscToken.Char: 84 | case OscToken.Int: 85 | case OscToken.Float: 86 | case OscToken.Color: 87 | case OscToken.Midi: 88 | position += 4; 89 | break; 90 | 91 | case OscToken.Long: 92 | case OscToken.Double: 93 | case OscToken.TimeTag: 94 | position += 8; 95 | break; 96 | 97 | case OscToken.String: 98 | case OscToken.Symbol: 99 | position += reader.GetStringArgumentSize(position); 100 | break; 101 | 102 | case OscToken.Blob: 103 | position += reader.GetBlobArgumentSize(position); 104 | break; 105 | 106 | case OscToken.None: 107 | case OscToken.OscAddress: 108 | case OscToken.TypeTag: 109 | case OscToken.ArrayStart: 110 | case OscToken.ArrayEnd: 111 | case OscToken.MixedTypes: 112 | case OscToken.End: 113 | case OscToken.BundleMessageLength: 114 | default: 115 | throw new ArgumentOutOfRangeException(); 116 | } 117 | } 118 | 119 | token = typeTag.NextToken(); 120 | } 121 | } 122 | 123 | public string Address { get; } 124 | 125 | public int Count => arguments.Length; 126 | 127 | public Uri Origin { get; } 128 | 129 | public OscTimeTag? Timestamp { get; } 130 | 131 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 132 | public ArraySegment ReadBlob(ref OscArgument argument) 133 | { 134 | CheckArgument(ref argument, OscToken.Blob); 135 | 136 | reader.Position = argument.Position; 137 | 138 | return reader.ReadDirectBlob(); 139 | } 140 | 141 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 142 | public bool ReadBool(ref OscArgument argument) 143 | { 144 | CheckArgument(ref argument, OscToken.Bool); 145 | 146 | return argument.Type == OscToken.True; 147 | } 148 | 149 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 150 | public byte ReadChar(ref OscArgument argument) 151 | { 152 | CheckArgument(ref argument, OscToken.Char); 153 | 154 | reader.Position = argument.Position; 155 | 156 | return reader.ReadDirectChar(); 157 | } 158 | 159 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 160 | public OscColor ReadColor(ref OscArgument argument) 161 | { 162 | CheckArgument(ref argument, OscToken.Color); 163 | 164 | reader.Position = argument.Position; 165 | 166 | return reader.ReadDirectColor(); 167 | } 168 | 169 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 170 | public double ReadDouble(ref OscArgument argument) 171 | { 172 | CheckArgument(ref argument, OscToken.Double); 173 | 174 | reader.Position = argument.Position; 175 | 176 | return reader.ReadDirectDouble(); 177 | } 178 | 179 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 180 | public float ReadFloat(ref OscArgument argument) 181 | { 182 | CheckArgument(ref argument, OscToken.Float); 183 | 184 | reader.Position = argument.Position; 185 | 186 | return reader.ReadDirectFloat(); 187 | } 188 | 189 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 190 | public OscImpulse ReadImpulse(ref OscArgument argument) 191 | { 192 | CheckArgument(ref argument, OscToken.Impulse); 193 | 194 | return OscImpulse.Value; 195 | } 196 | 197 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 198 | public int ReadInt(ref OscArgument argument) 199 | { 200 | CheckArgument(ref argument, OscToken.Int); 201 | 202 | reader.Position = argument.Position; 203 | 204 | return reader.ReadDirectInt(); 205 | } 206 | 207 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 208 | public long ReadLong(ref OscArgument argument) 209 | { 210 | CheckArgument(ref argument, OscToken.Long); 211 | 212 | reader.Position = argument.Position; 213 | 214 | return reader.ReadDirectLong(); 215 | } 216 | 217 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 218 | public OscMidiMessage ReadMidi(ref OscArgument argument) 219 | { 220 | CheckArgument(ref argument, OscToken.Midi); 221 | 222 | reader.Position = argument.Position; 223 | 224 | return reader.ReadDirectMidi(); 225 | } 226 | 227 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 228 | public string ReadString(ref OscArgument argument) 229 | { 230 | if (argument.IsArray == false && 231 | argument.Type == OscToken.Null) 232 | { 233 | return null; 234 | } 235 | 236 | CheckArgument(ref argument, OscToken.String); 237 | 238 | reader.Position = argument.Position; 239 | 240 | return reader.ReadDirectString(); 241 | } 242 | 243 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 244 | public OscSymbol ReadSymbol(ref OscArgument argument) 245 | { 246 | if (argument.IsArray == false && 247 | argument.Type == OscToken.Null) 248 | { 249 | return new OscSymbol(null); 250 | } 251 | 252 | CheckArgument(ref argument, OscToken.Symbol); 253 | 254 | reader.Position = argument.Position; 255 | 256 | return reader.ReadDirectSymbol(); 257 | } 258 | 259 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 260 | public OscTimeTag ReadTimeTag(ref OscArgument argument) 261 | { 262 | CheckArgument(ref argument, OscToken.TimeTag); 263 | 264 | reader.Position = argument.Position; 265 | 266 | return reader.ReadDirectTimeTag(); 267 | } 268 | 269 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 270 | private void CheckArgument(ref OscArgument argument, OscToken expectedType) 271 | { 272 | if (argument.IsArray) 273 | { 274 | throw new OscException(OscError.UnexpectedToken, "Unexpected array"); 275 | } 276 | 277 | if (expectedType == OscToken.Bool) 278 | { 279 | if (argument.Type != OscToken.True && 280 | argument.Type != OscToken.False) 281 | { 282 | throw new OscException(OscError.UnexpectedToken, $"Unexpected token {argument.Type}"); 283 | } 284 | 285 | return; 286 | } 287 | 288 | if (argument.Type != expectedType) 289 | { 290 | throw new OscException(OscError.UnexpectedToken, $"Unexpected token {argument.Type}"); 291 | } 292 | } 293 | 294 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 295 | private OscArgument GetArrayArgument(ref OscTypeTag typeTag, ref int position) 296 | { 297 | int arrayLength = reader.StartArray(ref typeTag, out OscToken arrayType); 298 | 299 | OscArgument[] arguments = new OscArgument[arrayLength]; 300 | 301 | OscToken token = OscToken.None; 302 | 303 | for (int i = 0; i < arguments.Length; i++) 304 | { 305 | token = typeTag.CurrentToken; 306 | 307 | if (token == OscToken.ArrayStart) 308 | { 309 | arguments[i] = GetArrayArgument(ref typeTag, ref position); 310 | } 311 | else 312 | { 313 | arguments[i] = new OscArgument 314 | { 315 | Position = position, 316 | Type = token 317 | }; 318 | 319 | switch (token) 320 | { 321 | case OscToken.Bool: 322 | case OscToken.True: 323 | case OscToken.False: 324 | case OscToken.Impulse: 325 | case OscToken.Null: 326 | break; 327 | 328 | case OscToken.Char: 329 | case OscToken.Int: 330 | case OscToken.Float: 331 | case OscToken.Color: 332 | case OscToken.Midi: 333 | position += 4; 334 | break; 335 | 336 | case OscToken.Long: 337 | case OscToken.Double: 338 | case OscToken.TimeTag: 339 | position += 8; 340 | break; 341 | 342 | case OscToken.String: 343 | case OscToken.Symbol: 344 | position += reader.GetStringArgumentSize(position); 345 | break; 346 | 347 | case OscToken.Blob: 348 | position += reader.GetBlobArgumentSize(position); 349 | break; 350 | 351 | case OscToken.None: 352 | case OscToken.OscAddress: 353 | case OscToken.TypeTag: 354 | case OscToken.ArrayStart: 355 | case OscToken.ArrayEnd: 356 | case OscToken.MixedTypes: 357 | case OscToken.End: 358 | case OscToken.BundleMessageLength: 359 | default: 360 | throw new ArgumentOutOfRangeException(); 361 | } 362 | } 363 | 364 | token = typeTag.NextToken(); 365 | } 366 | 367 | return new OscArgument 368 | { 369 | Position = position, 370 | Type = arrayType, 371 | IsArray = true, 372 | Array = arguments 373 | }; 374 | } 375 | 376 | /// 377 | public IEnumerator GetEnumerator() 378 | { 379 | return (arguments as IEnumerable).GetEnumerator(); 380 | } 381 | 382 | /// 383 | IEnumerator IEnumerable.GetEnumerator() 384 | { 385 | return GetEnumerator(); 386 | } 387 | } 388 | } -------------------------------------------------------------------------------- /OscCore/OscPacket.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using OscCore.LowLevel; 6 | 7 | namespace OscCore 8 | { 9 | /// 10 | /// Base class for all osc packets 11 | /// 12 | public abstract class OscPacket 13 | { 14 | /// 15 | /// The packet origin 16 | /// 17 | public Uri Origin { get; protected set; } 18 | 19 | /// 20 | /// The size of the packet in bytes 21 | /// 22 | public abstract int SizeInBytes { get; } 23 | 24 | internal OscPacket() 25 | { 26 | } 27 | 28 | public static OscPacket Parse(string str, IFormatProvider provider = null) 29 | { 30 | if (string.IsNullOrWhiteSpace(str)) 31 | { 32 | throw new ArgumentNullException(nameof(str)); 33 | } 34 | 35 | OscStringReader reader = new OscStringReader(str); 36 | 37 | return Parse(ref reader, provider, OscSerializationToken.End); 38 | } 39 | 40 | public static OscPacket Parse(ref OscStringReader reader, IFormatProvider provider = null, OscSerializationToken endToken = OscSerializationToken.End) 41 | { 42 | if (reader.PeekChar() == '#') 43 | { 44 | return OscBundle.Parse(ref reader, provider, endToken); 45 | } 46 | 47 | return OscMessage.Parse(ref reader, provider, endToken); 48 | } 49 | 50 | /// 51 | /// Read the osc packet from a byte array 52 | /// 53 | /// array to read from 54 | /// the offset within the array where reading should begin 55 | /// the number of bytes in the packet 56 | /// the origin that is the origin of this packet 57 | /// the time tag asociated with the parent 58 | /// the packet 59 | public static OscPacket Read( 60 | byte[] bytes, 61 | int index, 62 | int count, 63 | Uri origin = null, 64 | OscTimeTag? timeTag = null) 65 | { 66 | //if (OscBundle.IsBundle(bytes, index, count) == true) 67 | if (bytes[index] == (byte) '#') 68 | { 69 | return OscBundle.Read(bytes, index, count, origin); 70 | } 71 | 72 | return OscMessage.Read(bytes, index, count, origin, timeTag); 73 | } 74 | 75 | public static OscPacket Read( 76 | OscReader reader, 77 | int count, 78 | Uri origin = null, 79 | OscTimeTag? timeTag = null) 80 | { 81 | if (reader.PeekByte() == (byte) '#') 82 | { 83 | return OscBundle.Read(reader, count, origin); 84 | } 85 | 86 | return OscMessage.Read(reader, count, origin, timeTag); 87 | } 88 | 89 | /// 90 | /// Get an array of bytes containing the entire packet 91 | /// 92 | /// 93 | public abstract byte[] ToByteArray(); 94 | 95 | public static bool TryParse(string str, out OscPacket packet) 96 | { 97 | try 98 | { 99 | packet = Parse(str); 100 | 101 | return true; 102 | } 103 | catch 104 | { 105 | packet = default(OscPacket); 106 | 107 | return false; 108 | } 109 | } 110 | 111 | public static bool TryParse(string str, IFormatProvider provider, out OscPacket packet) 112 | { 113 | try 114 | { 115 | packet = Parse(str, provider); 116 | 117 | return true; 118 | } 119 | catch 120 | { 121 | packet = default(OscPacket); 122 | 123 | return false; 124 | } 125 | } 126 | 127 | /// 128 | /// Send the packet into a byte array 129 | /// 130 | /// the destination for the packet 131 | /// the offset within the array where writing should begin 132 | /// the length of the packet in bytes 133 | public abstract int Write(byte[] data, int index); 134 | 135 | public abstract void Write(OscWriter writer); 136 | 137 | public abstract void WriteToString(OscStringWriter writer); 138 | } 139 | } -------------------------------------------------------------------------------- /OscCoreTests/OscAddressTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using OscCore.Address; 6 | using Xunit; 7 | 8 | namespace OscCoreTests 9 | { 10 | /// 11 | /// This is a test class for OscAddressTest and is intended 12 | /// to contain all OscAddressTest Unit Tests 13 | /// 14 | public class OscAddressTest 15 | { 16 | #region Match 17 | 18 | /// 19 | ///A test for IsMatch 20 | /// 21 | [Fact] 22 | public void IsMatchTest_LiteralMatch() 23 | { 24 | string addressPattern = "/container_A/method_A"; 25 | string address = "/container_A/method_A"; 26 | 27 | Assert.True(OscAddress.IsMatch(addressPattern, address)); 28 | } 29 | 30 | /// 31 | ///A test for IsMatch 32 | /// 33 | [Fact] 34 | public void IsMatchTest_LiteralMissmatch1() 35 | { 36 | string addressPattern = "/container_A/method_A"; 37 | string address = "/container_A/method_B"; 38 | 39 | Assert.False(OscAddress.IsMatch(addressPattern, address)); 40 | } 41 | 42 | /// 43 | ///A test for IsMatch 44 | /// 45 | [Fact] 46 | public void IsMatchTest_LiteralMissmatch2() 47 | { 48 | string addressPattern = "/container_A/method_A"; 49 | string address = "/container_B/method_A"; 50 | 51 | Assert.False(OscAddress.IsMatch(addressPattern, address)); 52 | } 53 | 54 | /// 55 | ///A test for IsMatch 56 | /// 57 | [Fact] 58 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard1() 59 | { 60 | string addressPattern = "/?*test"; 61 | string address = "/test"; 62 | 63 | Assert.False(OscAddress.IsMatch(addressPattern, address)); 64 | } 65 | 66 | /// 67 | ///A test for IsMatch 68 | /// 69 | [Fact] 70 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard2() 71 | { 72 | string addressPattern = "/?*?test"; 73 | string address = "/test"; 74 | 75 | Assert.False(OscAddress.IsMatch(addressPattern, address)); 76 | } 77 | 78 | /// 79 | ///A test for IsMatch 80 | /// 81 | [Fact] 82 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard3() 83 | { 84 | string addressPattern = "/*?test"; 85 | string address = "/test"; 86 | 87 | Assert.False(OscAddress.IsMatch(addressPattern, address)); 88 | } 89 | 90 | /// 91 | ///A test for IsMatch 92 | /// 93 | [Fact] 94 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard4() 95 | { 96 | string addressPattern = "/?*test"; 97 | string address = "/1test"; 98 | 99 | Assert.True(OscAddress.IsMatch(addressPattern, address)); 100 | } 101 | 102 | /// 103 | ///A test for IsMatch 104 | /// 105 | [Fact] 106 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard5() 107 | { 108 | string addressPattern = "/?*test"; 109 | string address = "/1_test"; 110 | 111 | Assert.True(OscAddress.IsMatch(addressPattern, address)); 112 | } 113 | 114 | /// 115 | ///A test for IsMatch 116 | /// 117 | [Fact] 118 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard6() 119 | { 120 | string addressPattern = "/???test"; 121 | string address = "/123test"; 122 | 123 | Assert.True(OscAddress.IsMatch(addressPattern, address)); 124 | } 125 | 126 | /// 127 | ///A test for IsMatch 128 | /// 129 | [Fact] 130 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard7() 131 | { 132 | string addressPattern = "/???test"; 133 | string address = "/test"; 134 | 135 | Assert.False(OscAddress.IsMatch(addressPattern, address)); 136 | } 137 | 138 | /// 139 | ///A test for IsMatch 140 | /// 141 | [Fact] 142 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard8() 143 | { 144 | string addressPattern = "/???test??"; 145 | string address = "/test"; 146 | 147 | Assert.False(OscAddress.IsMatch(addressPattern, address)); 148 | } 149 | 150 | /// 151 | ///A test for IsMatch 152 | /// 153 | [Fact] 154 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard9() 155 | { 156 | string addressPattern = "/???test??"; 157 | string address = "/123test45"; 158 | 159 | Assert.True(OscAddress.IsMatch(addressPattern, address)); 160 | } 161 | 162 | /// 163 | ///A test for IsMatch 164 | /// 165 | [Fact] 166 | public void IsMatchTest_PossibleIssueWithAddressPatternWildcard10() 167 | { 168 | string addressPattern = "/???test*?"; 169 | string address = "/123test9"; 170 | 171 | Assert.True(OscAddress.IsMatch(addressPattern, address)); 172 | } 173 | 174 | #endregion Match 175 | 176 | #region Validate Address 177 | 178 | /// 179 | ///A test for IsValidAddressPattern 180 | /// 181 | [Fact] 182 | public void IsValidAddressPatternTest_Good() 183 | { 184 | for (int i = 0; i < UnitTestHelper.Good_AddressPatterns.Length; i++) 185 | { 186 | string address = UnitTestHelper.Good_AddressPatterns[i]; 187 | 188 | bool result = OscAddress.IsValidAddressPattern(address); 189 | 190 | Assert.True(result, $"Failed to validate address pattern {i} '{address}'"); 191 | } 192 | } 193 | 194 | /// 195 | ///A test for IsValidAddressPattern 196 | /// 197 | [Fact] 198 | public void IsValidAddressPatternTest_Bad() 199 | { 200 | for (int i = 0; i < UnitTestHelper.Bad_AddressPatterns.Length; i++) 201 | { 202 | string address = UnitTestHelper.Bad_AddressPatterns[i]; 203 | 204 | bool result = OscAddress.IsValidAddressPattern(address); 205 | 206 | Assert.False(result, $"Incorrectly validated address pattern {i} '{address}'"); 207 | } 208 | } 209 | 210 | #endregion Validate Address 211 | 212 | #region Parse Address 213 | 214 | /// 215 | ///A test for Constructor 216 | /// 217 | [Fact] 218 | public void OscAddress_Constructor_Good() 219 | { 220 | for (int i = 0; i < UnitTestHelper.Good_AddressPatterns.Length; i++) 221 | { 222 | string address = UnitTestHelper.Good_AddressPatterns[i]; 223 | 224 | OscAddress result = new OscAddress(address); 225 | 226 | Assert.Equal(address, UnitTestHelper.RebuildOscAddress(result)); // , $"Failed to parse address pattern {i} '{address}'"); 227 | } 228 | } 229 | 230 | #endregion Parse Address 231 | 232 | #region Parse Address 233 | 234 | /// 235 | ///A test for Constructor 236 | /// 237 | [Fact] 238 | public void AddressPatternMatches() 239 | { 240 | for (int i = 0; i < UnitTestHelper.Good_AddressPatterns.Length; i++) 241 | { 242 | string pattern = UnitTestHelper.Good_AddressPatterns[i]; 243 | string address = UnitTestHelper.Good_AddressPatternMatches[i]; 244 | 245 | OscAddress target = new OscAddress(pattern); 246 | 247 | bool result = target.Match(address); 248 | 249 | Assert.True(result, String.Format("Failed to match address pattern {0} '{1}' to '{2}'", i, pattern, address)); 250 | } 251 | } 252 | 253 | #endregion Parse Address 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /OscCoreTests/OscBundleRawTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using OscCore; 6 | using Xunit; 7 | 8 | namespace OscCoreTests 9 | { 10 | public class OscBundleRawTest 11 | { 12 | /// 13 | ///A test for Write 14 | /// 15 | [Fact] 16 | public void Nested_ReadTest() 17 | { 18 | byte[] bytes = UnitTestHelper.DoubleNestedBundleBody; 19 | OscBundleRaw actual = new OscBundleRaw(new ArraySegment(bytes, 0, bytes.Length)); 20 | 21 | Assert.Equal(actual.Count, 2); 22 | 23 | for (int i = 0; i < actual.Count; i++) 24 | { 25 | OscMessageRaw raw = actual[i]; 26 | 27 | Assert.Equal(raw.Address, "/aa"); 28 | 29 | OscArgument argument = raw[0]; 30 | 31 | int value = raw.ReadInt(ref argument); 32 | 33 | Assert.Equal(value, -1); 34 | } 35 | } 36 | 37 | [Fact] 38 | public void OscBundleManyMessagesTest_1() 39 | { 40 | OscBundle expected = new OscBundle(new OscTimeTag(0), 41 | new OscMessage("/ping"), new OscMessage("/moop"), new OscMessage("/ping"), new OscMessage("/ping"), new OscMessage("/ping")); 42 | 43 | byte[] bytes = expected.ToByteArray(); 44 | 45 | OscBundleRaw actual = new OscBundleRaw(new ArraySegment(bytes, 0, bytes.Length)); 46 | 47 | Assert.Equal(actual.Count, expected.Count); 48 | 49 | for (int i = 0; i < actual.Count; i++) 50 | { 51 | OscMessageRaw raw = actual[i]; 52 | OscMessage expectedMessage = expected[i] as OscMessage; 53 | 54 | Assert.Equal(raw.Address, expectedMessage.Address); 55 | 56 | } 57 | } 58 | 59 | /// 60 | ///A test for Read 61 | /// 62 | [Fact] 63 | public void ReadTest_Bad_ToLong() 64 | { 65 | try 66 | { 67 | byte[] bytes = 68 | { 69 | // #bundle 70 | 35, 98, 117, 110, 100, 108, 101, 0, 71 | 72 | // Time-tag 73 | 197, 146, 134, 227, 3, 18, 110, 152, 74 | 75 | // length 76 | 0, 0, 0, 64, // 32, 77 | 78 | // message body 79 | 47, 116, 101, 115, 116, 0, 0, 0, 80 | 44, 105, 91, 105, 105, 105, 93, 0, 81 | 82 | 26, 42, 58, 74, 26, 42, 58, 74, 83 | 90, 106, 122, 138, 154, 170, 186, 202 84 | }; 85 | 86 | OscBundleRaw actual = new OscBundleRaw(new ArraySegment(bytes, 0, bytes.Length)); 87 | 88 | Assert.True(false, "Exception not thrown"); 89 | } 90 | catch (OscException ex) 91 | { 92 | Assert.Equal(ex.OscError, OscError.InvalidBundleMessageLength); 93 | } 94 | catch (Exception ex) 95 | { 96 | Assert.True(false, ex.Message); 97 | } 98 | } 99 | 100 | /// 101 | ///A test for Read 102 | /// 103 | [Fact] 104 | public void ReadTest_Bad_ToShort() 105 | { 106 | try 107 | { 108 | byte[] bytes = 109 | { 110 | // #bundle 111 | 35, 98, 117, 110, 100, 108, 101, 0, 112 | 113 | // Time-tag 114 | 197, 146, 134, 227, 3, 18, 110, 152, 115 | 116 | // length 117 | 0, 0, 0, 24, // 32, 118 | 119 | // message body 120 | 47, 116, 101, 115, 116, 0, 0, 0, 121 | 44, 105, 91, 105, 105, 105, 93, 0, 122 | 123 | 26, 42, 58, 74, 26, 42, 58, 74, 124 | 90, 106, 122, 138, 154, 170, 186, 202 125 | }; 126 | 127 | OscBundleRaw actual = new OscBundleRaw(new ArraySegment(bytes, 0, bytes.Length)); 128 | 129 | Assert.True(false, "Exception not thrown"); 130 | } 131 | catch (OscException ex) 132 | { 133 | Assert.Equal(ex.OscError, OscError.UnexpectedToken); 134 | } 135 | catch (Exception ex) 136 | { 137 | Assert.True(false, ex.Message); 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /OscCoreTests/OscBundleTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using OscCore; 8 | using Xunit; 9 | 10 | namespace OscCoreTests 11 | { 12 | /// 13 | /// This is a test class for OscBundleTest and is intended 14 | /// to contain all OscBundleTest Unit Tests 15 | /// 16 | public class OscBundleTest 17 | { 18 | [Fact] 19 | public void Nested_ParseTest() 20 | { 21 | string str = UnitTestHelper.DoubleNestedBundleString; 22 | OscBundle expected = UnitTestHelper.DoubleNestedBundle(); 23 | OscBundle actual; 24 | actual = OscBundle.Parse(str); 25 | UnitTestHelper.AreEqual(expected, actual); 26 | } 27 | 28 | /// 29 | ///A test for Write 30 | /// 31 | [Fact] 32 | public void Nested_ReadTest() 33 | { 34 | OscBundle expected = UnitTestHelper.DoubleNestedBundle(); 35 | byte[] bytes = UnitTestHelper.DoubleNestedBundleBody; 36 | 37 | int count = bytes.Length; 38 | OscBundle actual; 39 | 40 | actual = OscBundle.Read(bytes, 0, count); 41 | 42 | UnitTestHelper.AreEqual(expected, actual); 43 | } 44 | 45 | /// 46 | ///A test for ToByteArray 47 | /// 48 | [Fact] 49 | public void Nested_ToArrayTest() 50 | { 51 | OscBundle target = UnitTestHelper.DoubleNestedBundle(); 52 | byte[] expected = UnitTestHelper.DoubleNestedBundleBody; 53 | byte[] actual; 54 | actual = target.ToByteArray(); 55 | 56 | Assert.Equal(expected.Length, actual.Length); 57 | UnitTestHelper.AreEqual(expected, actual); 58 | } 59 | 60 | /// 61 | ///A test for ToString 62 | /// 63 | [Fact] 64 | public void Nested_ToStringTest() 65 | { 66 | OscBundle target = UnitTestHelper.DoubleNestedBundle(); 67 | string expected = UnitTestHelper.DoubleNestedBundleString; 68 | string actual; 69 | actual = target.ToString(); 70 | Assert.Equal(expected, actual); 71 | } 72 | 73 | /// 74 | ///A test for Write 75 | /// 76 | [Fact] 77 | public void Nested_WriteTest() 78 | { 79 | OscBundle target = UnitTestHelper.DoubleNestedBundle(); 80 | byte[] data = new byte[UnitTestHelper.DoubleNestedBundleBody.Length]; 81 | int index = 0; 82 | int expected = UnitTestHelper.DoubleNestedBundleBody.Length; 83 | int actual; 84 | actual = target.Write(data, index); 85 | 86 | Assert.Equal(expected, actual); 87 | UnitTestHelper.AreEqual(data, UnitTestHelper.DoubleNestedBundleBody); 88 | } 89 | 90 | /// 91 | ///A test for OscBundle Constructor 92 | /// 93 | [Fact] 94 | public void OscBundleConstructorTest() 95 | { 96 | OscTimeTag timestamp = new OscTimeTag(14236589681638796952); 97 | OscMessage[] messages = {UnitTestHelper.Message_Array_Ints(), UnitTestHelper.Message_Array_Ints()}; 98 | OscBundle target = new OscBundle(timestamp, messages); 99 | 100 | Assert.Equal(timestamp, target.Timestamp); 101 | UnitTestHelper.AreEqual(messages, target.ToArray()); 102 | } 103 | 104 | [Fact] 105 | public void OscBundleManyMessagesTest_1() 106 | { 107 | OscBundle target = OscBundle.Parse("#bundle, 0, { /ping }, { /moop }, { /ping }, { /ping }, { /ping }"); 108 | OscBundle expected = new OscBundle(new OscTimeTag(0), 109 | new OscMessage("/ping"), new OscMessage("/moop"), new OscMessage("/ping"), new OscMessage("/ping"), new OscMessage("/ping")); 110 | 111 | UnitTestHelper.AreEqual(target, expected); 112 | } 113 | 114 | [Fact] 115 | public void OscBundleManyMessagesTest_2() 116 | { 117 | OscBundle target = OscBundle.Parse("#bundle, 0, { /ping }, { /moop }, { /ping }, { /ping }, { /ping }"); 118 | OscBundle expected = new OscBundle(new OscTimeTag(0), 119 | new OscMessage("/ping"), new OscMessage("/moop"), new OscMessage("/ping"), new OscMessage("/ping"), new OscMessage("/ping")); 120 | 121 | byte[] targetBytes = target.ToByteArray(); 122 | 123 | OscBundle actual = OscBundle.Read(targetBytes, 0, targetBytes.Length); 124 | 125 | UnitTestHelper.AreEqual(actual, expected); 126 | } 127 | 128 | /// 129 | ///A test for Read 130 | /// 131 | [Fact] 132 | public void ReadTest() 133 | { 134 | OscTimeTag timestamp = new OscTimeTag(14236589681638796952); 135 | OscMessage[] messages = {UnitTestHelper.Message_Array_Ints(), UnitTestHelper.Message_Array_Ints()}; 136 | OscBundle expected = new OscBundle(timestamp, messages); 137 | 138 | byte[] bytes = expected.ToByteArray(); 139 | int index = 0; 140 | int count = bytes.Length; 141 | OscBundle actual; 142 | 143 | actual = OscBundle.Read(bytes, index, count); 144 | 145 | UnitTestHelper.AreEqual(expected, actual); 146 | 147 | //Assert.True(actual.Equals(expected)); 148 | } 149 | 150 | /// 151 | ///A test for Read 152 | /// 153 | [Fact] 154 | public void ReadOffsetTest() 155 | { 156 | Random random = new Random(); 157 | 158 | for (int i = 0; i < 1000; i++) 159 | { 160 | OscTimeTag timestamp = new OscTimeTag(14236589681638796952); 161 | 162 | List messages = new List(); 163 | 164 | for (int j = 0; j < 10; j++) 165 | { 166 | messages.Add(new OscMessage("/" + j, (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble() )); 167 | } 168 | 169 | OscBundle expected = new OscBundle(timestamp, messages.ToArray()); 170 | 171 | int index = random.Next(1, 32); 172 | int endPadding = random.Next(0, 32); 173 | int count = expected.SizeInBytes; 174 | 175 | byte[] bytes = new byte[count + index + endPadding]; 176 | 177 | random.NextBytes(bytes); 178 | 179 | expected.ToByteArray().CopyTo(bytes, index); 180 | 181 | OscBundle actual; 182 | 183 | actual = OscBundle.Read(bytes, index, count); 184 | 185 | UnitTestHelper.AreEqual(expected, actual); 186 | } 187 | } 188 | 189 | /// 190 | ///A test for Read 191 | /// 192 | [Fact] 193 | public void ReadTest_Bad_ToLong() 194 | { 195 | try 196 | { 197 | byte[] bytes = 198 | { 199 | // #bundle 200 | 35, 98, 117, 110, 100, 108, 101, 0, 201 | 202 | // Time-tag 203 | 197, 146, 134, 227, 3, 18, 110, 152, 204 | 205 | // length 206 | 0, 0, 0, 64, // 32, 207 | 208 | // message body 209 | 47, 116, 101, 115, 116, 0, 0, 0, 210 | 44, 105, 91, 105, 105, 105, 93, 0, 211 | 212 | 26, 42, 58, 74, 26, 42, 58, 74, 213 | 90, 106, 122, 138, 154, 170, 186, 202 214 | }; 215 | 216 | int index = 0; 217 | int count = bytes.Length; 218 | OscBundle actual; 219 | actual = OscBundle.Read(bytes, index, count); 220 | 221 | Assert.True(false, "Exception not thrown"); 222 | } 223 | catch (OscException ex) 224 | { 225 | Assert.Equal(ex.OscError, OscError.InvalidBundleMessageLength); 226 | } 227 | catch (Exception ex) 228 | { 229 | Assert.True(false, ex.Message); 230 | } 231 | } 232 | 233 | /// 234 | ///A test for Read 235 | /// 236 | [Fact] 237 | public void ReadTest_Bad_ToShort() 238 | { 239 | try 240 | { 241 | byte[] bytes = 242 | { 243 | // #bundle 244 | 35, 98, 117, 110, 100, 108, 101, 0, 245 | 246 | // Time-tag 247 | 197, 146, 134, 227, 3, 18, 110, 152, 248 | 249 | // length 250 | 0, 0, 0, 24, // 32, 251 | 252 | // message body 253 | 47, 116, 101, 115, 116, 0, 0, 0, 254 | 44, 105, 91, 105, 105, 105, 93, 0, 255 | 256 | 26, 42, 58, 74, 26, 42, 58, 74, 257 | 90, 106, 122, 138, 154, 170, 186, 202 258 | }; 259 | 260 | int index = 0; 261 | int count = bytes.Length; 262 | OscBundle actual; 263 | actual = OscBundle.Read(bytes, index, count); 264 | 265 | Assert.True(false, "Exception not thrown"); 266 | } 267 | catch (OscException ex) 268 | { 269 | Assert.Equal(ex.OscError, OscError.ErrorParsingInt32); 270 | } 271 | catch (Exception ex) 272 | { 273 | Assert.True(false, ex.Message); 274 | } 275 | } 276 | 277 | /// 278 | ///A test for TryParse 279 | /// 280 | [Fact] 281 | public void TryParseTest_Bad() 282 | { 283 | bool expected = false; 284 | foreach (string str in UnitTestHelper.Bundles_Bad) 285 | { 286 | OscBundle bundle = null; 287 | 288 | bool actual; 289 | actual = OscBundle.TryParse(str, out bundle); 290 | 291 | Assert.True(expected == actual, $"While parsing bad bundle '{str}'"); 292 | } 293 | } 294 | 295 | /// 296 | ///A test for TryParse 297 | /// 298 | [Fact] 299 | public void TryParseTest_Good() 300 | { 301 | bool expected = true; 302 | foreach (string str in UnitTestHelper.Bundles_Good) 303 | { 304 | OscBundle bundle = null; 305 | 306 | bool actual; 307 | actual = OscBundle.TryParse(str, out bundle); 308 | 309 | Assert.True(expected == actual, $"While parsing good bundle '{str}'"); 310 | } 311 | } 312 | } 313 | } -------------------------------------------------------------------------------- /OscCoreTests/OscColorTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using OscCore; 5 | using Xunit; 6 | 7 | namespace OscCoreTests 8 | { 9 | /// 10 | /// This is a test class for OscColorTest and is intended 11 | /// to contain all OscColorTest Unit Tests 12 | /// 13 | public class OscColorTest 14 | { 15 | /// 16 | ///A test for FromArgb 17 | /// 18 | [Fact] 19 | public void FromArgbTest_B() 20 | { 21 | byte alpha = 255; 22 | byte red = 0; 23 | byte green = 0; 24 | byte blue = 255; 25 | int argb = unchecked((int) 0xFF0000FF); 26 | 27 | OscColor expected = new OscColor(argb); 28 | OscColor actual; 29 | actual = OscColor.FromArgb(alpha, red, green, blue); 30 | 31 | Assert.Equal(expected, actual); 32 | 33 | Assert.Equal(argb, actual.ARGB); 34 | 35 | Assert.Equal(alpha, actual.A); 36 | Assert.Equal(red, actual.R); 37 | Assert.Equal(green, actual.G); 38 | Assert.Equal(blue, actual.B); 39 | } 40 | 41 | /// 42 | ///A test for FromArgb 43 | /// 44 | [Fact] 45 | public void FromArgbTest_G() 46 | { 47 | byte alpha = 255; 48 | byte red = 0; 49 | byte green = 255; 50 | byte blue = 0; 51 | int argb = unchecked((int) 0xFF00FF00); 52 | 53 | OscColor expected = new OscColor(argb); 54 | OscColor actual; 55 | actual = OscColor.FromArgb(alpha, red, green, blue); 56 | 57 | Assert.Equal(expected, actual); 58 | 59 | Assert.Equal(argb, actual.ARGB); 60 | 61 | Assert.Equal(alpha, actual.A); 62 | Assert.Equal(red, actual.R); 63 | Assert.Equal(green, actual.G); 64 | Assert.Equal(blue, actual.B); 65 | } 66 | 67 | /// 68 | ///A test for FromArgb 69 | /// 70 | [Fact] 71 | public void FromArgbTest_R() 72 | { 73 | byte alpha = 255; 74 | byte red = 255; 75 | byte green = 0; 76 | byte blue = 0; 77 | int argb = unchecked((int) 0xFFFF0000); 78 | 79 | OscColor expected = new OscColor(argb); 80 | OscColor actual; 81 | actual = OscColor.FromArgb(alpha, red, green, blue); 82 | 83 | Assert.Equal(expected, actual); 84 | 85 | Assert.Equal(argb, actual.ARGB); 86 | 87 | Assert.Equal(alpha, actual.A); 88 | Assert.Equal(red, actual.R); 89 | Assert.Equal(green, actual.G); 90 | Assert.Equal(blue, actual.B); 91 | } 92 | 93 | /// 94 | ///A test for OscColor Constructor 95 | /// 96 | [Fact] 97 | public void OscColorConstructorTest() 98 | { 99 | int value = unchecked((int) 0xFFFFFFFF); 100 | 101 | OscColor target = new OscColor(value); 102 | 103 | Assert.Equal(value, target.ARGB); 104 | 105 | Assert.Equal(255, target.A); 106 | Assert.Equal(255, target.R); 107 | Assert.Equal(255, target.G); 108 | Assert.Equal(255, target.B); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /OscCoreTests/OscCoreTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.2 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /OscCoreTests/OscMidiMessageTest.cs: -------------------------------------------------------------------------------- 1 | using OscCore; 2 | using Xunit; 3 | 4 | namespace OscCoreTests 5 | { 6 | /// 7 | ///This is a test class for OscMidiMessageTest and is intended 8 | ///to contain all OscMidiMessageTest Unit Tests 9 | /// 10 | public class OscMidiMessageTest 11 | { 12 | /// 13 | ///A test for Data14BitValue 14 | /// 15 | [Fact] 16 | public void Data14BitValueTest() 17 | { 18 | ushort expected = 0x1356; 19 | OscMidiMessage target = new OscMidiMessage(0x03F35626); 20 | ushort actual; 21 | actual = target.Data14BitValue; 22 | Assert.Equal(expected, actual); 23 | } 24 | 25 | /// 26 | ///A test for Equals 27 | /// 28 | [Fact] 29 | public void EqualsTest() 30 | { 31 | OscMidiMessage target = new OscMidiMessage(0x03F35626); 32 | uint obj = 0x03F35626; 33 | bool expected = true; 34 | bool actual; 35 | actual = target.Equals(obj); 36 | Assert.Equal(expected, actual); 37 | } 38 | 39 | 40 | /// 41 | ///A test for Equals 42 | /// 43 | [Fact] 44 | public void EqualsTest2() 45 | { 46 | OscMidiMessage target = new OscMidiMessage(0x03F35626); 47 | uint obj = 0x0832626; 48 | bool expected = false; 49 | bool actual; 50 | actual = target.Equals(obj); 51 | Assert.Equal(expected, actual); 52 | } 53 | 54 | /// 55 | ///A test for GetHashCode 56 | /// 57 | [Fact] 58 | public void GetHashCodeTest() 59 | { 60 | OscMidiMessage target = new OscMidiMessage(0x03F35626); 61 | int expected = 0x03F35626; 62 | int actual; 63 | actual = target.GetHashCode(); 64 | Assert.Equal(expected, actual); 65 | } 66 | 67 | 68 | /// 69 | ///A test for OscMidiMessage Constructor 70 | /// 71 | [Fact] 72 | public void OscMidiMessageConstructorTest() 73 | { 74 | OscMidiMessage expected = new OscMidiMessage(0x03962200); 75 | 76 | byte portID = 3; 77 | OscMidiMessageType type = OscMidiMessageType.NoteOn; 78 | byte channel = 6; 79 | byte data1 = 34; 80 | OscMidiMessage target = new OscMidiMessage(portID, type, channel, data1); 81 | 82 | Assert.Equal(expected, target); 83 | Assert.Equal(target.PortID, portID); 84 | Assert.Equal(target.Channel, channel); 85 | Assert.Equal(target.MessageType, type); 86 | Assert.Equal(target.Data1, data1); 87 | Assert.Equal(target.Data2, 0); 88 | } 89 | 90 | /// 91 | ///A test for OscMidiMessage Constructor 92 | /// 93 | [Fact] 94 | public void OscMidiMessageConstructorTest1() 95 | { 96 | OscMidiMessage expected = new OscMidiMessage(0x03F35626); 97 | 98 | byte portID = 3; 99 | OscMidiSystemMessageType type = OscMidiSystemMessageType.SongSelect; 100 | ushort value = 0x1356; 101 | 102 | OscMidiMessage target = new OscMidiMessage(portID, type, value); 103 | 104 | Assert.Equal(expected, target); 105 | Assert.Equal(target.PortID, portID); 106 | Assert.Equal(target.MessageType, OscMidiMessageType.SystemExclusive); 107 | Assert.Equal(target.SystemMessageType, type); 108 | Assert.Equal(target.Data14BitValue, value); 109 | } 110 | 111 | /// 112 | ///A test for OscMidiMessage Constructor 113 | /// 114 | [Fact] 115 | public void OscMidiMessageConstructorTest2() 116 | { 117 | uint value = 0x03F35626; 118 | OscMidiMessage target = new OscMidiMessage(value); 119 | 120 | byte portID = 3; 121 | OscMidiSystemMessageType type = OscMidiSystemMessageType.SongSelect; 122 | ushort data14BitValue = 0x1356; 123 | 124 | OscMidiMessage expected = new OscMidiMessage(portID, type, data14BitValue); 125 | 126 | Assert.Equal(expected, target); 127 | Assert.Equal(target.PortID, portID); 128 | Assert.Equal(target.MessageType, OscMidiMessageType.SystemExclusive); 129 | Assert.Equal(target.SystemMessageType, type); 130 | Assert.Equal(target.Data14BitValue, data14BitValue); 131 | Assert.Equal(target.Data1, 0x56); 132 | Assert.Equal(target.Data2, 0x26); 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /OscCoreTests/OscStringReaderTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using OscCore.LowLevel; 6 | using Xunit; 7 | 8 | namespace OscCoreTests 9 | { 10 | public class OscStringReaderTest 11 | { 12 | [Fact] 13 | public void ArrayEnd() 14 | { 15 | string original = @"]"; 16 | string expectedValue = null; 17 | OscSerializationToken expectedToken = OscSerializationToken.ArrayEnd; 18 | 19 | OscStringReader reader = new OscStringReader(original); 20 | 21 | OscSerializationToken token = reader.ReadNextToken(out string value); 22 | 23 | Assert.Equal(expectedToken, token); 24 | Assert.Equal(expectedValue, value); 25 | } 26 | 27 | [Fact] 28 | public void ArrayStart() 29 | { 30 | string original = @"["; 31 | string expectedValue = null; 32 | OscSerializationToken expectedToken = OscSerializationToken.ArrayStart; 33 | 34 | OscStringReader reader = new OscStringReader(original); 35 | 36 | OscSerializationToken token = reader.ReadNextToken(out string value); 37 | 38 | Assert.Equal(expectedToken, token); 39 | Assert.Equal(expectedValue, value); 40 | } 41 | 42 | [Fact] 43 | public void Char() 44 | { 45 | string original = @"'hello'"; 46 | string expectedValue = "hello"; 47 | OscSerializationToken expectedToken = OscSerializationToken.Char; 48 | 49 | OscStringReader reader = new OscStringReader(original); 50 | 51 | OscSerializationToken token = reader.ReadNextToken(out string value); 52 | 53 | Assert.Equal(expectedToken, token); 54 | Assert.Equal(expectedValue, value); 55 | } 56 | 57 | // [Fact] 58 | // public void Colon() 59 | // { 60 | // string original = @":"; 61 | // string expectedValue = null; 62 | // OscSerializationToken expectedToken = OscSerializationToken.Colon; 63 | // 64 | // OscStringReader reader = new OscStringReader(original); 65 | // 66 | // OscSerializationToken token = reader.ReadNextToken(out string value); 67 | // 68 | // Assert.Equal(expectedToken, token); 69 | // Assert.Equal(expectedValue, value); 70 | // } 71 | 72 | [Fact] 73 | public void End() 74 | { 75 | string original = @""; 76 | string expectedValue = null; 77 | OscSerializationToken expectedToken = OscSerializationToken.End; 78 | 79 | OscStringReader reader = new OscStringReader(original); 80 | 81 | OscSerializationToken token = reader.ReadNextToken(out string value); 82 | 83 | Assert.Equal(expectedToken, token); 84 | Assert.Equal(expectedValue, value); 85 | } 86 | 87 | [Fact] 88 | public void Literal() 89 | { 90 | string original = "hello"; 91 | string expectedValue = "hello"; 92 | OscSerializationToken expectedToken = OscSerializationToken.Literal; 93 | 94 | OscStringReader reader = new OscStringReader(original); 95 | 96 | OscSerializationToken token = reader.ReadNextToken(out string value); 97 | 98 | Assert.Equal(expectedToken, token); 99 | Assert.Equal(expectedValue, value); 100 | } 101 | 102 | [Fact] 103 | public void ObjectEnd() 104 | { 105 | string original = @"}"; 106 | string expectedValue = null; 107 | OscSerializationToken expectedToken = OscSerializationToken.ObjectEnd; 108 | 109 | OscStringReader reader = new OscStringReader(original); 110 | 111 | OscSerializationToken token = reader.ReadNextToken(out string value); 112 | 113 | Assert.Equal(expectedToken, token); 114 | Assert.Equal(expectedValue, value); 115 | } 116 | 117 | [Fact] 118 | public void ObjectStart() 119 | { 120 | string original = @"{"; 121 | string expectedValue = null; 122 | OscSerializationToken expectedToken = OscSerializationToken.ObjectStart; 123 | 124 | OscStringReader reader = new OscStringReader(original); 125 | 126 | OscSerializationToken token = reader.ReadNextToken(out string value); 127 | 128 | Assert.Equal(expectedToken, token); 129 | Assert.Equal(expectedValue, value); 130 | } 131 | 132 | [Fact] 133 | public void Separator() 134 | { 135 | string original = @","; 136 | string expectedValue = null; 137 | OscSerializationToken expectedToken = OscSerializationToken.Separator; 138 | 139 | OscStringReader reader = new OscStringReader(original); 140 | 141 | OscSerializationToken token = reader.ReadNextToken(out string value); 142 | 143 | Assert.Equal(expectedToken, token); 144 | Assert.Equal(expectedValue, value); 145 | } 146 | 147 | [Fact] 148 | public void SimpleString() 149 | { 150 | List originals = new List 151 | { 152 | @"/moop, ""thing"", 123, '!', [ 1, 2, 3, 4, 5]", 153 | @"/moop,""thing"",123,'!',[1,2,3,4,5]" 154 | }; 155 | 156 | List expectedTokens = new List 157 | { 158 | OscSerializationToken.Literal, 159 | OscSerializationToken.Separator, 160 | OscSerializationToken.String, 161 | OscSerializationToken.Separator, 162 | OscSerializationToken.Literal, 163 | OscSerializationToken.Separator, 164 | OscSerializationToken.Char, 165 | OscSerializationToken.Separator, 166 | 167 | OscSerializationToken.ArrayStart, 168 | 169 | OscSerializationToken.Literal, 170 | OscSerializationToken.Separator, 171 | OscSerializationToken.Literal, 172 | OscSerializationToken.Separator, 173 | OscSerializationToken.Literal, 174 | OscSerializationToken.Separator, 175 | OscSerializationToken.Literal, 176 | OscSerializationToken.Separator, 177 | OscSerializationToken.Literal, 178 | 179 | OscSerializationToken.ArrayEnd, 180 | OscSerializationToken.End 181 | }; 182 | 183 | List expectedValues = new List 184 | { 185 | "/moop", 186 | null, 187 | "thing", 188 | null, 189 | "123", 190 | null, 191 | "!", 192 | null, 193 | 194 | null, 195 | 196 | "1", 197 | null, 198 | "2", 199 | null, 200 | "3", 201 | null, 202 | "4", 203 | null, 204 | "5", 205 | null, 206 | 207 | null, 208 | null 209 | }; 210 | 211 | foreach (string orginal in originals) 212 | { 213 | OscStringReader reader = new OscStringReader(orginal); 214 | 215 | OscSerializationToken token; 216 | 217 | int index = 0; 218 | 219 | do 220 | { 221 | token = reader.ReadNextToken(out string value); 222 | 223 | Assert.Equal(expectedTokens[index], token); 224 | Assert.Equal(expectedValues[index], value); 225 | 226 | index++; 227 | } 228 | while (token != OscSerializationToken.End); 229 | } 230 | } 231 | 232 | [Fact] 233 | public void String() 234 | { 235 | string original = @"""hello"""; 236 | string expectedValue = "hello"; 237 | OscSerializationToken expectedToken = OscSerializationToken.String; 238 | 239 | OscStringReader reader = new OscStringReader(original); 240 | 241 | OscSerializationToken token = reader.ReadNextToken(out string value); 242 | 243 | Assert.Equal(expectedToken, token); 244 | Assert.Equal(expectedValue, value); 245 | } 246 | 247 | [Fact] 248 | public void StringWithEscape() 249 | { 250 | string original = @"""let's say \""hello\"""""; 251 | string expectedValue = @"let's say \""hello\"""; 252 | OscSerializationToken expectedToken = OscSerializationToken.String; 253 | 254 | OscStringReader reader = new OscStringReader(original); 255 | 256 | OscSerializationToken token = reader.ReadNextToken(out string value); 257 | 258 | Assert.Equal(expectedToken, token); 259 | Assert.Equal(expectedValue, value); 260 | } 261 | } 262 | } -------------------------------------------------------------------------------- /OscCoreTests/OscTimeTagTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using System; 5 | using OscCore; 6 | using Xunit; 7 | 8 | namespace OscCoreTests 9 | { 10 | /// 11 | ///This is a test class for OscTimeTagTest and is intended 12 | ///to contain all OscTimeTagTest Unit Tests 13 | /// 14 | public class OscTimeTagTest 15 | { 16 | /// 17 | ///A test for FromDataTime 18 | /// 19 | [Fact] 20 | public void FromDataTimeTest() 21 | { 22 | DateTime datetime = new DateTime(632413223390120000, DateTimeKind.Utc); 23 | OscTimeTag expected = new OscTimeTag(14236589681638796952); 24 | OscTimeTag actual; 25 | actual = OscTimeTag.FromDataTime(datetime); 26 | 27 | Assert.True(expected.Value <= actual.Value + 1 && expected.Value >= actual.Value - 1); 28 | } 29 | 30 | 31 | /// 32 | ///A test for OscTimeTag Constructor 33 | /// 34 | [Fact] 35 | public void OscTimeTagConstructorTest() 36 | { 37 | DateTime expected = new DateTime(632413223390120000, DateTimeKind.Utc); 38 | ulong value = 14236589681638796952; 39 | OscTimeTag target = new OscTimeTag(value); 40 | 41 | DateTime datetime = target.ToDataTime(); 42 | 43 | string valueString = datetime.ToString("dd/MM/yyyy HH:mm:ss") + " " + datetime.Millisecond; 44 | string expectedString = expected.ToString("dd/MM/yyyy HH:mm:ss") + " " + datetime.Millisecond; 45 | 46 | Assert.Equal(expectedString, valueString); // , "Date resolved to '{0}'", valueString); 47 | } 48 | 49 | /// 50 | ///A test for ToDataTime 51 | /// 52 | [Fact] 53 | public void ToDataTimeTest() 54 | { 55 | OscTimeTag target = new OscTimeTag(14236589681638796952); 56 | DateTime expected = new DateTime(632413223390120000, DateTimeKind.Utc); 57 | DateTime actual; 58 | actual = target.ToDataTime(); 59 | Assert.Equal(expected, actual); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /OscCoreTests/StringSerialization.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Tilde Love Project. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE in the project root for license information. 3 | 4 | using OscCore; 5 | using Xunit; 6 | 7 | namespace OscCoreTests 8 | { 9 | public class StringSerialization 10 | { 11 | /// 12 | ///A test for Parse 13 | /// 14 | [Fact] 15 | public void ToStringTest_NestedQuoteString() 16 | { 17 | string expected = UnitTestHelper.MessageString_NestedQuoteString; 18 | OscMessage message = UnitTestHelper.Message_NestedQuoteString(); 19 | string actual; 20 | actual = message.ToString(); 21 | Assert.Equal(expected, actual); 22 | } 23 | 24 | /// 25 | ///A test for Parse 26 | /// 27 | [Fact] 28 | public void ParseTest_NestedQuoteString() 29 | { 30 | string str = UnitTestHelper.MessageString_NestedQuoteString; 31 | OscMessage expected = UnitTestHelper.Message_NestedQuoteString(); 32 | OscMessage actual; 33 | actual = OscMessage.Parse(str); 34 | UnitTestHelper.AreEqual(expected, actual); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OscCore 2 | 3 | [![CircleCI](https://circleci.com/gh/tilde-love/osc-core.svg?style=svg)](https://circleci.com/gh/tilde-love/osc-core) 4 | 5 | **A preformant Open Sound Control library for .NET Standard from the creator of Rug.Osc** 6 | 7 | ## tl;dr 8 | 9 | **If you want ease of use and fast results use Rug.Osc. If you can do the work and want fast code use OscCore.** 10 | 11 | The motivation for this new library is: 12 | 13 | * Allow fast reading and writing of OSC messages and bundles without boxing of types or excessive memory allocation. 14 | * Decouple message format from transport layer. There in no UDP / TCP / Serial code in this library. 15 | * To use the lowest possible version of .NET Standard (currently 1.3) to allow maximum portability. 16 | * To move away from the monolith that was Rug.Osc. 17 | 18 | Differences over Rug.Osc: 19 | 20 | * Thread safety, Rug.Osc was aggressively thread safe. OscCore is **NOT thread safe!** you will have to deal with that stuff on your own. 21 | * Ease of use, Rug.Osc was very friendly. OscCore is **NOT your friend** it will allow you to create invalid messages and parse junk if you are not careful. 22 | * Transport layer, Rug.Osc had all the transport layer stuff built in, it is a one stop shop for sending and receiving OSC messages. **OscCore does non of this**, it is expected that you will use some other library to get data on and off the wire. 23 | * Osc Address routing, Rug.Osc has mechanisms for routing incoming messages to delegates. **OscCore does not provide routing**, you will have to devise the best method for how this happens in the context of your application. 24 | 25 | ## Why would I use this belligerent library? 26 | 27 | OscCore is: 28 | 29 | * Performant, using the 'Raw' api is typically more that 2x faster than Rug.Osc for read operations. 30 | * Uses minimal memory footprint / copying for normal operation. 31 | --------------------------------------------------------------------------------