├── .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