├── .swiftlint.yml
├── Source
└── OSCCore
│ ├── conversion
│ ├── Byte.swift
│ ├── extract.swift
│ ├── Bool+OSCConvertible.swift
│ ├── OSCMessageArgument.swift
│ ├── alignment.swift
│ ├── OSCSymbol+OSCMessageArgument.swift
│ ├── OSCConvertible.swift
│ ├── Character+OSCConvertible.swift
│ ├── String+OSCMessageArgument.swift
│ ├── Float+OSCMessageArgument.swift
│ ├── Double+OSCMessageArgument.swift
│ ├── RGBA+OSCMessageArgument.swift
│ ├── MIDI+OSCMessageArgument.swift
│ ├── Data+OSCMessageArgument.swift
│ ├── Array+OSCMessageArgument.swift
│ ├── Int+OSCMessageArgument.swift
│ ├── OSCTimeTag+OSCMessageArgument.swift
│ ├── OSCBundle+OSCConvertible.swift
│ └── OSCMessage+OSCConvertible.swift
│ ├── types
│ ├── OSCSymbol.swift
│ ├── RGBA.swift
│ ├── MIDI.swift
│ ├── OSCMessage.swift
│ ├── OSCBundle.swift
│ ├── TypeTagValues.swift
│ └── OSCTimeTag.swift
│ ├── dispatch
│ ├── events.swift
│ ├── BasicMessageDispatcher.swift
│ ├── OSCBundle+MessageEventHandler.swift
│ └── matcher.swift
│ └── nio
│ └── OSCPacketReader.swift
├── Playgrounds
└── AddressMatcher.playground
│ ├── playground.xcworkspace
│ └── contents.xcworkspacedata
│ ├── contents.xcplayground
│ └── Contents.swift
├── Examples
├── BasicListener
│ ├── README.md
│ └── main.swift
└── SoundColliderClient
│ └── main.swift
├── .github
└── workflows
│ └── test.yml
├── Tests
└── OSCCoreTests
│ ├── OSCBundleTests.swift
│ ├── AddressMatcherTests.swift
│ ├── OSCTimeTagTests.swift
│ ├── DispatcherTests.swift
│ ├── XCTestManifests.swift
│ ├── ValueConversionTests.swift
│ └── OSCMessageTests.swift
├── Package.swift
├── LICENSE
├── CHANGELOG.md
├── README.md
└── .gitignore
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | included:
2 | - Source
3 | - Tests
4 | opt_in_rules:
5 | - empty_count
6 | excluded:
7 | - Tests/*/XCTestManifests.swift
8 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/Byte.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Byte.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 19..
6 | //
7 |
8 | public typealias Byte = UInt8
9 |
--------------------------------------------------------------------------------
/Playgrounds/AddressMatcher.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Source/OSCCore/types/OSCSymbol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCSymbol.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 10..
6 | //
7 |
8 | import Foundation
9 |
10 | struct OSCSymbol {
11 | public let label: String
12 | }
13 |
--------------------------------------------------------------------------------
/Playgrounds/AddressMatcher.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Source/OSCCore/types/RGBA.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RGBA.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 29..
6 | //
7 |
8 | public struct RGBA {
9 | public let red: UInt8
10 | public let green: UInt8
11 | public let blue: UInt8
12 | public let alpha: UInt8
13 | }
14 |
--------------------------------------------------------------------------------
/Source/OSCCore/types/MIDI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MIDI.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 30..
6 | //
7 |
8 | public struct MIDI {
9 | public let portId: UInt8
10 | public let status: UInt8
11 | public let data1: UInt8
12 | public let data2: UInt8
13 | }
14 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/extract.swift:
--------------------------------------------------------------------------------
1 | //
2 | // extract.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 19..
6 | //
7 |
8 | public func decodeOSCPacket(from bytes: [Byte]) -> OSCConvertible? {
9 | if let bndl = OSCBundle(data: bytes ) {
10 | return bndl
11 | } else if let msg = OSCMessage(data: bytes) {
12 | return msg
13 | }
14 | return nil
15 | }
16 |
--------------------------------------------------------------------------------
/Source/OSCCore/dispatch/events.swift:
--------------------------------------------------------------------------------
1 | //
2 | // events.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 19..
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: Message Events
11 |
12 | public typealias MessageEvent = (when: Date, message: OSCMessage)
13 |
14 | public typealias MessageEventHandler = (MessageEvent) -> Void
15 |
16 | public typealias MessageHandler = (OSCMessage) -> Void
17 |
--------------------------------------------------------------------------------
/Source/OSCCore/types/OSCMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCMessage.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | public struct OSCMessage {
11 | public let address: String
12 | public let args: [OSCMessageArgument?]
13 |
14 | public init(address: String, args: [OSCMessageArgument?]) {
15 | self.address = address
16 | self.args = args
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Source/OSCCore/types/OSCBundle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCBundle.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | public struct OSCBundle {
11 | public let timetag: OSCTimeTag
12 |
13 | /// Bundle elements
14 | public let content: [OSCConvertible]
15 |
16 | public init(timetag: OSCTimeTag, content: [OSCConvertible]) {
17 | self.timetag = timetag
18 | self.content = content
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/BasicListener/README.md:
--------------------------------------------------------------------------------
1 | # What is this?
2 |
3 | A basic example that dumps out OSC packets captured on port 51770.
4 |
5 | ## Compile Example
6 |
7 | Just execute `swift build` command.
8 |
9 | ## Run Example
10 |
11 | Run `./.build/debug/BasicListener`. It will start listening on port 51770.
12 |
13 | But you can specify custom port number by setting it as an argument. `./.build/debug/BasicListener 3330`. Now listener will capture events coming from [TUIO Simulator](https://github.com/mkalten/TUIO11_Simulator).
14 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/Bool+OSCConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bool+OSCConvertible.swift
3 | // OSCCorePackageDescription
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 08..
6 | //
7 |
8 | import Foundation
9 |
10 | extension Bool: OSCMessageArgument {
11 | public var oscType: TypeTagValues {
12 | return self == true ? .TRUE_TYPE_TAG : .FALSE_TYPE_TAG
13 | }
14 |
15 | public init?(data: ArraySlice) {
16 | fatalError("Unsupported initializer")
17 | }
18 |
19 | public var oscValue: [Byte]? { nil }
20 |
21 | public var packetSize: Int { 0 }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | ///
11 | /// Types adopting this protocol
12 | /// can be part of OSC message arguments
13 | /// All arguments are denoted with a distinct type tag
14 | /// defined in TypeTagValues enum
15 | ///
16 | public protocol OSCMessageArgument: OSCConvertible {
17 | var oscType: TypeTagValues { get }
18 | }
19 |
20 | public protocol OSCMessageArgumentCollection {
21 | var oscTypes: [TypeTagValues] { get }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/alignment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // alignment.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 31..
6 | //
7 |
8 | import Foundation
9 |
10 | @inline(__always) func align(size: Int) -> Int {
11 | return (size + 3) & ~0x03
12 | }
13 |
14 | extension String {
15 | // return size of UTF-8 string including terminator rounded up to 4
16 | public var alignedSize: Int {
17 | return align(size: self.utf8.count+1)
18 | }
19 | }
20 |
21 | extension Data {
22 | // return size payload rounded up to 4
23 | public var alignedSize: Int {
24 | return align(size: self.count)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/OSCSymbol+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCSymbol+OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 10..
6 | //
7 |
8 | import Foundation
9 |
10 | extension OSCSymbol: OSCMessageArgument {
11 | public var oscType: TypeTagValues { .SYMBOL_TYPE_TAG }
12 |
13 | public var oscValue: [Byte]? { label.oscValue }
14 |
15 | public var alignedSize: Int { return label.alignedSize }
16 |
17 | var packetSize: Int { label.packetSize }
18 |
19 | public init?(data: ArraySlice) {
20 | guard let label = String(data: data) else {
21 | return nil
22 | }
23 |
24 | self.init(label: label)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test OSCCore
2 | on: [push]
3 |
4 | jobs:
5 | test-on-linux:
6 | strategy:
7 | matrix:
8 | image:
9 | - swift:5.5-focal
10 | - swift:5.6-focal
11 | runs-on: ubuntu-latest
12 | container: ${{ matrix.image }}
13 | steps:
14 | - name: Check out code
15 | uses: actions/checkout@v2
16 | - name: Build code
17 | run: swift build
18 | - name: Perform Tests
19 | run: swift test
20 | test-on-macos:
21 | runs-on: macos-11
22 | steps:
23 | - name: Check out code
24 | uses: actions/checkout@v2
25 | - name: Build code
26 | run: swift build
27 | - name: Perform Tests
28 | run: swift test
29 |
--------------------------------------------------------------------------------
/Tests/OSCCoreTests/OSCBundleTests.swift:
--------------------------------------------------------------------------------
1 | @testable import OSCCore
2 | import XCTest
3 |
4 | class OSCBundleTests: XCTestCase {
5 | func testNoArgMessage() {
6 | let ttag = OSCTimeTag.immediate
7 | let bundle = OSCBundle(timetag: ttag, content: [])
8 |
9 | let expectedPacket: [Byte] = [
10 | 0x23, 0x62, 0x75, 0x6e,
11 | 0x64, 0x6c, 0x65, 0x00,
12 | 0x00, 0x00, 0x00, 0x00,
13 | 0x00, 0x00, 0x00, 0x01
14 | ]
15 |
16 | XCTAssertNotNil(bundle.oscValue)
17 | XCTAssertEqual(expectedPacket, bundle.oscValue!)
18 | }
19 | }
20 |
21 | #if os(Linux)
22 | extension OSCBundleTests {
23 | static var allTests: [(String, (OSCBundleTests) -> () throws -> Void)] {
24 | return [
25 | ("testNoArgMessage", testNoArgMessage)
26 | ]
27 | }
28 | }
29 | #endif
30 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/OSCConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCConvertible.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | ///
11 | /// Objects implementing this protocol
12 | /// can be converted to OSC packets
13 | /// and back
14 | ///
15 | /// Containers like OSC messages and
16 | /// bundles implement this protocol
17 | ///
18 | public protocol OSCConvertible {
19 | /// return as byte sequence
20 | var oscValue: [Byte]? { get }
21 |
22 | /// returns size of OSC packet in bytes
23 | var packetSize: Int { get }
24 |
25 | /// construct OSC value from byte array
26 | init?(data: ArraySlice)
27 | }
28 |
29 | extension OSCConvertible {
30 | // construct type from byte array
31 | init?(data: [Byte]) {
32 | self.init(data: data[0..) {
16 | guard let rawValue = UInt32(data: data),
17 | let scalar = UnicodeScalar(rawValue)
18 | else {
19 | return nil
20 | }
21 |
22 | self.init(scalar)
23 | }
24 |
25 | public var oscValue: [Byte]? {
26 | let scalarArray = self.unicodeScalars
27 | return scalarArray[scalarArray.startIndex].value.oscValue
28 | }
29 |
30 | public var packetSize: Int {
31 | return MemoryLayout.size
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/String+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 19..
6 | //
7 |
8 | extension String: OSCMessageArgument {
9 | public var oscValue: [Byte]? {
10 | var oscBytes = self.utf8.map {Byte($0)}
11 | let packetSize = self.alignedSize
12 | let padding = packetSize - oscBytes.count
13 | if padding > 0 {
14 | oscBytes += [Byte](repeating: 0, count: padding)
15 | }
16 |
17 | return oscBytes
18 | }
19 |
20 | public var oscType: TypeTagValues { return .STRING_TYPE_TAG }
21 |
22 | public var packetSize: Int { self.alignedSize }
23 |
24 | public init?(data: ArraySlice) {
25 | guard let termIndex = data.firstIndex(of: 0) else {
26 | return nil
27 | }
28 |
29 | self.init(bytes: data[data.startIndex...size }
22 |
23 | // custom init
24 | public init?(data: ArraySlice) {
25 | let binary: [Byte] = [Byte](data)
26 | if binary.count != MemoryLayout.size {
27 | return nil
28 | }
29 |
30 | self = CFConvertFloatSwappedToHost(binary.withUnsafeBytes {
31 | $0.load(fromByteOffset: 0, as: CFSwappedFloat32.self)
32 | })
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Source/OSCCore/dispatch/BasicMessageDispatcher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BasicMessageDispatcher.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 19..
6 | //
7 |
8 | public final class BasicMessageDispatcher {
9 |
10 | /// map of observers
11 | private var listeners = [String: MessageHandler]()
12 |
13 | /// register a message listener
14 | public func register(pattern: String, _ listener: @escaping MessageHandler) {
15 | listeners[pattern] = listener
16 | }
17 |
18 | /// remove listener
19 | public func unregister(pattern: String) {
20 | listeners.removeValue(forKey: pattern)
21 | }
22 |
23 | /// notify observes about the event
24 | public func fire(event: MessageEvent) {
25 | // dispatchMessageEvent(event: event, listeners: listeners)
26 | listeners.forEach { ptn, handler in
27 | if match(address: event.message.address, pattern: ptn) {
28 | handler(event.message)
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/Double+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double+OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 29..
6 | //
7 |
8 | extension Double: OSCMessageArgument {
9 | public var oscValue: [Byte]? {
10 | guard self.isFinite else {
11 | return nil
12 | }
13 |
14 | return withUnsafeBytes(of: self.bitPattern.bigEndian) {[Byte]($0)}
15 | }
16 |
17 | public var oscType: TypeTagValues {
18 | return self.isInfinite ? .INFINITUM_TYPE_TAG : .DOUBLE_TYPE_TAG
19 | }
20 |
21 | public init?(data: ArraySlice) {
22 | let binary: [Byte] = [Byte](data)
23 | guard binary.count == MemoryLayout.size,
24 | let rawValue: UInt64 = UInt64(data: binary)
25 | else {
26 | return nil
27 | }
28 |
29 | self.init(bitPattern: rawValue)
30 | }
31 |
32 | public var packetSize: Int {
33 | return self.isInfinite ? 0 : MemoryLayout.size
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Source/OSCCore/nio/OSCPacketReader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCPacketReader.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2018. 06. 06..
6 | //
7 |
8 | import NIO
9 |
10 | public final class OSCPacketReader: ChannelInboundHandler {
11 | public typealias InboundIn = AddressedEnvelope
12 | public typealias InboundOut = OSCConvertible
13 |
14 | public init() {}
15 |
16 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
17 | let addressedEnvelope = self.unwrapInboundIn(data)
18 |
19 | if let rawBytes: [Byte] = addressedEnvelope.data.getBytes(at: 0, length: addressedEnvelope.data.readableBytes),
20 | let packet = decodeOSCPacket(from: rawBytes) {
21 | context.fireChannelRead(self.wrapInboundOut(packet))
22 | }
23 | }
24 |
25 | public func channelReadComplete(context: ChannelHandlerContext) {
26 | context.flush()
27 | }
28 |
29 | public func errorCaught(context: ChannelHandlerContext, error: Error) {
30 | context.close(promise: nil)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "OSCCore",
7 |
8 | products: [
9 | .library(name: "OSCCore", targets: ["OSCCore"]),
10 | .executable(name: "BasicListener", targets: ["BasicListener"])
11 | ],
12 |
13 | dependencies: [
14 | .package(url: "https://github.com/apple/swift-nio", from: "2.0.0")
15 | ],
16 |
17 | targets: [
18 | .target(name: "OSCCore", dependencies: [
19 | .product(name: "NIO", package: "swift-nio")
20 | ]),
21 | .executableTarget(name: "BasicListener",
22 | dependencies: ["OSCCore"],
23 | path: "Examples/BasicListener",
24 | exclude: ["README.md"]),
25 |
26 | .executableTarget(name: "SoundColliderClient",
27 | dependencies: ["OSCCore"],
28 | path: "Examples/SoundColliderClient"),
29 |
30 | .testTarget(name: "OSCCoreTests", dependencies: ["OSCCore"])
31 | ]
32 | )
33 |
--------------------------------------------------------------------------------
/Tests/OSCCoreTests/AddressMatcherTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddressMatcherTests.swift
3 | // OSCCore
4 | //
5 | // Created by Gábor Sebestyén on 13/08/16.
6 | //
7 | //
8 |
9 | @testable import OSCCore
10 | import XCTest
11 |
12 | class AddressMatcherTests: XCTestCase {
13 | func testMatcherFunction() {
14 | XCTAssertTrue(matchComponent(address: "ablak", pattern: "ablak"))
15 | XCTAssertTrue(matchComponent(address: "ablak", pattern: "ab?ak"))
16 | XCTAssertTrue(matchComponent(address: "ablak", pattern: "ab*"))
17 | XCTAssertTrue(matchComponent(address: "ablak", pattern: "ab*k"))
18 | XCTAssertTrue(matchComponent(address: "ablak", pattern: "a{blak}"))
19 | XCTAssertTrue(matchComponent(address: "ablak", pattern: "a{blak,jto}"))
20 | }
21 | }
22 |
23 | #if os(Linux)
24 | extension AddressMatcherTests {
25 | static var allTests: [(String, (AddressMatcherTests) -> () throws -> Void)] {
26 | return [
27 | ("testMatcherFunction", testMatcherFunction)
28 | ]
29 | }
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Gabor Sebestyen
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 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/RGBA+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RGBA+OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 29..
6 | //
7 |
8 | import Foundation
9 |
10 | extension RGBA: OSCMessageArgument {
11 |
12 | public init?(data: ArraySlice) {
13 | let binary: [Byte] = [Byte](data)
14 | guard let flatValue = UInt32(data: binary) else {
15 | return nil
16 | }
17 |
18 | let red = UInt8(flatValue >> 24)
19 | let green = UInt8( (flatValue >> 16) & 0xFF)
20 | let blue = UInt8( (flatValue >> 8) & 0xFF)
21 | let alpha = UInt8(flatValue & 0xFF)
22 |
23 | self.init(red: red, green: green, blue: blue, alpha: alpha)
24 | }
25 |
26 | public var oscType: TypeTagValues { .RGBA_COLOR_TYPE_TAG }
27 |
28 | public var oscValue: [Byte]? {
29 | let red: UInt32 = UInt32(self.red)
30 | let green: UInt32 = UInt32(self.green)
31 | let blue: UInt32 = UInt32(self.blue)
32 | let alpha: UInt32 = UInt32(self.alpha)
33 | let flatValue: UInt32 = red << 24 | green << 16 | blue << 8 | alpha
34 |
35 | return flatValue.oscValue
36 | }
37 |
38 | public var packetSize: Int { MemoryLayout.size }
39 | }
40 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/MIDI+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MIDI+OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 30..
6 | //
7 |
8 | extension MIDI: OSCMessageArgument {
9 |
10 | public init?(data: ArraySlice) {
11 | let binary: [Byte] = [Byte](data)
12 | guard let flatValue = UInt32(data: binary) else {
13 | return nil
14 | }
15 |
16 | let portId = UInt8(flatValue >> 24)
17 | let status = UInt8( (flatValue >> 16) & 0xFF)
18 | let data1 = UInt8( (flatValue >> 8) & 0xFF)
19 | let data2 = UInt8(flatValue & 0xFF)
20 |
21 | self.init(portId: portId, status: status, data1: data1, data2: data2)
22 | }
23 |
24 | public var oscType: TypeTagValues { .MIDI_MESSAGE_TYPE_TAG }
25 |
26 | public var oscValue: [Byte]? {
27 | let portId: UInt32 = UInt32(self.portId)
28 | let statusByte: UInt32 = UInt32(self.status)
29 | let data1: UInt32 = UInt32(self.data1)
30 | let data2: UInt32 = UInt32(self.data2)
31 | let flatValue: UInt32 = portId << 24 | statusByte << 16 | data1 << 8 | data2
32 |
33 | return flatValue.oscValue
34 | }
35 |
36 | public var packetSize: Int { MemoryLayout.size }
37 | }
38 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [0.12] - 2022-05-07
10 | ### Changed
11 | - Fixed a bug prevented receiving packets (issues #24 and #25)
12 | Thanks @mron
13 | - Switch to Swift 5.4
14 |
15 | ## [0.10] - 2018-08-30
16 | ### Changed
17 | - Switch networking to Swift NIO
18 |
19 | ## [0.9] - 2018-01-14
20 | ### Added
21 | - OSC Specification 1.0
22 |
23 | ## [0.4] - 2018-01-14
24 | ### Changed
25 | - Fix broken OSC timestamp serialization and date conversion
26 | - OSCBundles were not recognized
27 | - Switch to Swift 4.0.2
28 |
29 | ## [0.3.1] - 2017-05-15
30 | ### Added
31 | - iOS support
32 | Note: this is last Swift 3 supported version
33 |
34 | ## [0.3]
35 | ### Changed
36 | - Replace socket implementation to IBM's BlueSocket
37 |
38 | ## [0.2.3] - 2017-05-17
39 | ### Changed
40 | - Bugfix release (see issue #5).
41 |
42 | ## [0.2.2]
43 | ### Changed
44 | - Improved timetag support
45 |
46 | ## [0.2.1]
47 | ### Changed
48 | - UDP Client now accepts bundles too
49 |
50 | ## [0.2]
51 | ### Added
52 | - Dispatch bundle messages
53 |
--------------------------------------------------------------------------------
/Source/OSCCore/dispatch/OSCBundle+MessageEventHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCBundle+MessageEventHandler.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 19..
6 | //
7 |
8 | /// Decompose OSC Bundle content and pass each item to handler function
9 | extension OSCBundle {
10 | func unwrap(_ handler: @escaping MessageEventHandler) {
11 | recursive { visit, parentBundle in
12 | parentBundle.content.forEach { (item: OSCConvertible) in
13 | switch item {
14 | case let msg as OSCMessage:
15 | handler(MessageEvent(when: parentBundle.timetag.toDate(), message: msg))
16 | case let childBundle as OSCBundle:
17 | visit(childBundle)
18 | default:
19 | ()
20 | }
21 | }
22 | }(self)
23 | }
24 | }
25 |
26 | // MARK: recursive call support
27 |
28 | ///
29 | /// Workaround for lambda recursion
30 | /// See: http://stackoverflow.com/questions/30523285/how-do-i-create-a-recursive-closure-in-swift
31 | ///
32 |
33 | private func unimplemented() -> T {
34 | fatalError()
35 | }
36 |
37 | private func recursive(fun: (@escaping (((T) -> U), T) -> U)) -> ((T) -> U) {
38 | var closure: ((T) -> U) = { _ in unimplemented() }
39 |
40 | closure = { fun(closure, $0) }
41 |
42 | return closure
43 | }
44 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/Data+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+OSCMessageArgument.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 12. 30..
6 | //
7 |
8 | import Foundation
9 |
10 | extension Data: OSCMessageArgument {
11 |
12 | public init?(data: ArraySlice) {
13 | guard let size = UInt32(data: data[data.startIndex...size + align(size: self.count)
32 | }
33 |
34 | public var oscValue: [Byte]? {
35 | let dataSize = self.count
36 | var payload = [Byte]()
37 |
38 | payload += self.count.oscValue!
39 | payload += self.withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> [Byte] in
40 | return [Byte](pointer[0.. 0 {
47 | payload += [Byte](repeating: 0, count: padding)
48 | }
49 | return payload
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Source/OSCCore/types/TypeTagValues.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TypeTagValues.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | ///
9 | /// OSC Type Tag values
10 | ///
11 | /// Enum borrowed from https://github.com/mkalten/reacTIVision/blob/master/ext/oscpack/osc/OscTypes.h
12 | ///
13 | public enum TypeTagValues: Character {
14 | case TRUE_TYPE_TAG = "T" // swiftlint:disable:this identifier_name
15 | case FALSE_TYPE_TAG = "F" // swiftlint:disable:this identifier_name
16 | case NIL_TYPE_TAG = "N" // swiftlint:disable:this identifier_name
17 | case INFINITUM_TYPE_TAG = "I" // swiftlint:disable:this identifier_name
18 | case INT32_TYPE_TAG = "i" // swiftlint:disable:this identifier_name
19 | case FLOAT_TYPE_TAG = "f" // swiftlint:disable:this identifier_name
20 | case CHAR_TYPE_TAG = "c" // swiftlint:disable:this identifier_name
21 | case RGBA_COLOR_TYPE_TAG = "r" // swiftlint:disable:this identifier_name
22 | case MIDI_MESSAGE_TYPE_TAG = "m" // swiftlint:disable:this identifier_name
23 | case INT64_TYPE_TAG = "h" // swiftlint:disable:this identifier_name
24 | case TIME_TAG_TYPE_TAG = "t" // swiftlint:disable:this identifier_name
25 | case DOUBLE_TYPE_TAG = "d" // swiftlint:disable:this identifier_name
26 | case STRING_TYPE_TAG = "s" // swiftlint:disable:this identifier_name
27 | case SYMBOL_TYPE_TAG = "S" // swiftlint:disable:this identifier_name
28 | case BLOB_TYPE_TAG = "b" // swiftlint:disable:this identifier_name
29 | case ARRAY_BEGIN_TYPE_TAG = "[" // swiftlint:disable:this identifier_name
30 | case ARRAY_END_TYPE_TAG = "]" // swiftlint:disable:this identifier_name
31 | }
32 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/Array+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+OSCMessageArgument.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2018. 01. 04..
6 | //
7 |
8 | import Foundation
9 |
10 | // add default conformance
11 | extension Array: OSCMessageArgument, OSCMessageArgumentCollection {
12 | // DON'T CALL THIS METHOD!!
13 | public init?(data: ArraySlice) {
14 | return nil
15 | }
16 |
17 | public var oscType: TypeTagValues { .ARRAY_BEGIN_TYPE_TAG }
18 |
19 | public var oscTypes: [TypeTagValues] {
20 | var typeTags: [TypeTagValues] = [.ARRAY_BEGIN_TYPE_TAG]
21 | self.forEach { (elem) -> Void in
22 | switch elem {
23 | case let argCollection as OSCMessageArgumentCollection:
24 | typeTags.append(contentsOf: argCollection.oscTypes)
25 | case let arg as OSCMessageArgument:
26 | typeTags.append(arg.oscType)
27 | default:
28 | typeTags.append(.NIL_TYPE_TAG)
29 | }
30 | }
31 | typeTags.append(.ARRAY_END_TYPE_TAG)
32 | return typeTags
33 | }
34 |
35 | public var oscValue: [Byte]? {
36 | var bytes: [Byte] = [Byte]()
37 |
38 | self.forEach { (elem) -> Void in
39 | if let arg = elem as? OSCMessageArgument,
40 | let argBytes = arg.oscValue {
41 | bytes += argBytes
42 | }
43 | }
44 |
45 | return bytes
46 | }
47 |
48 | public var packetSize: Int {
49 | var total = 0
50 | self.forEach { (elem) -> Void in
51 | if let messageArg = elem as? OSCMessageArgument {
52 | total += messageArg.packetSize
53 | }
54 | }
55 | return total
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Tests/OSCCoreTests/OSCTimeTagTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCTimeTagTests.swift
3 | // OSCCorePackageDescription
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 27..
6 | //
7 |
8 | #if os(OSX) || os(iOS)
9 | import Darwin
10 | #elseif os(Linux)
11 | import Glibc
12 | #endif
13 |
14 | @testable import OSCCore
15 | import XCTest
16 |
17 | class OSCTimeTagTests: XCTestCase {
18 |
19 | internal static let EPSILON: Double = 0.0001
20 |
21 | public func testImmediateTimeTagToDateConversion() {
22 | let timeTag: OSCTimeTag = OSCTimeTag.immediate
23 |
24 | let now: Date = Date()
25 |
26 | let ttDate: Date = timeTag.toDate()
27 |
28 | let delta: Double = ttDate.timeIntervalSinceReferenceDate - now.timeIntervalSinceReferenceDate
29 |
30 | XCTAssertTrue( fabs(delta) < OSCTimeTagTests.EPSILON )
31 | }
32 |
33 | public func testSecondsSince1900TimeTagConversion() {
34 | let testOSCSeconds = 1234.0
35 |
36 | let timeTag: OSCTimeTag = OSCTimeTag.secondsSince1900(testOSCSeconds)
37 |
38 | let expectedDate: Date = Date(timeIntervalSince1970: OSCSecondsToInterval(testOSCSeconds))
39 |
40 | let ttDate = timeTag.toDate()
41 |
42 | let delta: Double = ttDate.timeIntervalSinceReferenceDate - expectedDate.timeIntervalSinceReferenceDate
43 |
44 | XCTAssertTrue( fabs(delta) < OSCTimeTagTests.EPSILON )
45 | }
46 | }
47 |
48 | #if os(Linux)
49 | extension OSCTimeTagTests {
50 | static var allTests: [(String, (OSCTimeTagTests) -> () throws -> Void)] {
51 | return [
52 | ("testImmediateTimeTagToDateConversion", testImmediateTimeTagToDateConversion),
53 | ("testSecondsSince1900TimeTagConversion", testSecondsSince1900TimeTagConversion)
54 | ]
55 | }
56 | }
57 | #endif
58 |
--------------------------------------------------------------------------------
/Source/OSCCore/types/OSCTimeTag.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCTimeTag.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | // Time tags are represented by a 64 bit fixed point number.
11 | // The first 32 bits specify the number of seconds since midnight on January 1, 1900,
12 | // and the last 32 bits specify fractional parts of a second to a precision of about 200 picoseconds.
13 | // This is the representation used by Internet NTP timestamps.
14 | //
15 | // The time tag value consisting of 63 zero bits followed by
16 | // a one in the least signifigant bit is a special case meaning "immediately."
17 |
18 | // input: TimeInterval, seconds since 1970 (as defined in NSDate)
19 | // output: seconds since 1900 (seconds counted in OSC)
20 | @inline(__always)
21 | internal func intervalToOSCSeconds(_ interval: TimeInterval) -> Double {
22 | return interval + OSCTimeTag.SecondsSince1900
23 | }
24 |
25 | @inline(__always)
26 | internal func OSCSecondsToInterval(_ seconds: Double) -> TimeInterval {
27 | return seconds - OSCTimeTag.SecondsSince1900
28 | }
29 |
30 | public enum OSCTimeTag: Equatable {
31 | /// seconds between 1900 and 1970
32 | static internal let SecondsSince1900: Double = 2_208_988_800
33 |
34 | case immediate
35 | case secondsSince1900(Double) // seconds since January 1, 1900
36 |
37 | public static func withDelay(_ delay: TimeInterval) -> OSCTimeTag {
38 | return OSCTimeTag.secondsSince1900( intervalToOSCSeconds(Date.timeIntervalSinceReferenceDate + delay) )
39 | }
40 |
41 | public func toDate() -> Date {
42 | switch self {
43 | case .immediate:
44 | return Date()
45 | case .secondsSince1900(let oscSeconds):
46 | return Date(timeIntervalSince1970: OSCSecondsToInterval(oscSeconds))
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Examples/BasicListener/main.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import OSCCore
3 | import NIO
4 |
5 |
6 | func debugOSCPacket(_ packet: OSCConvertible) {
7 | switch packet {
8 | case let msg as OSCMessage:
9 | let argsString: String = msg.args.map {
10 | if let arg = $0 {
11 | return String(describing: arg)
12 | } else {
13 | return "nil"
14 | }
15 | }.joined(separator: ", ")
16 | print("[message] Address: \(msg.address); Arguments: [\(argsString)]")
17 | case let bundle as OSCBundle:
18 | print("[bundle] Timestamp: \(bundle.timetag); elements:")
19 | bundle.content.forEach {
20 | debugOSCPacket($0)
21 | }
22 | default:
23 | ()
24 | }
25 | }
26 |
27 |
28 | private final class OSCDebugHandler: ChannelInboundHandler {
29 | typealias InboundIn = OSCConvertible
30 |
31 | func channelRead(context: ChannelHandlerContext, data: NIOAny) {
32 | let oscValue = unwrapInboundIn(data)
33 |
34 | print("Captured OSC packet ... ")
35 | debugOSCPacket(oscValue)
36 | }
37 | }
38 |
39 |
40 | // MAIN CODE STARTS HERE //
41 |
42 | let threadGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
43 | defer {
44 | do {
45 | try threadGroup.syncShutdownGracefully()
46 | } catch {
47 | }
48 | }
49 |
50 | let bootstrap = DatagramBootstrap(group: threadGroup)
51 | .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
52 | .channelInitializer { channel in
53 | channel.pipeline.addHandlers([OSCPacketReader(), OSCDebugHandler()])
54 | }
55 |
56 | let arguments = CommandLine.arguments
57 | let port = arguments
58 | .dropFirst()
59 | .compactMap {Int($0)}
60 | .first ?? 57110
61 |
62 | let channel = try bootstrap.bind(host: "127.0.0.1", port: port).wait()
63 |
64 | print("Channel accepting connections on \(channel.localAddress!)")
65 |
66 | try channel.closeFuture.wait()
67 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/Int+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int+OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 19..
6 | //
7 |
8 | /// Create integers from bytes
9 | extension FixedWidthInteger {
10 | public var packetSize: Int { MemoryLayout.size }
11 |
12 | public init?(data: ArraySlice) {
13 | let binary: [Byte] = [Byte](data)
14 | if binary.count != MemoryLayout.size {
15 | return nil
16 | }
17 |
18 | self = binary.withUnsafeBytes {
19 | $0.load(fromByteOffset: 0, as: Self.self)
20 | }.byteSwapped
21 | }
22 | }
23 |
24 | extension Int64: OSCMessageArgument {
25 | public var oscValue: [Byte]? {
26 | return withUnsafeBytes(of: self.bigEndian) {[Byte]($0)}
27 | }
28 |
29 | public var oscType: TypeTagValues { return .INT64_TYPE_TAG }
30 | }
31 |
32 | // for timestamps
33 | extension UInt64: OSCConvertible {
34 | public var oscValue: [Byte]? {
35 | return withUnsafeBytes(of: self.bigEndian) {[Byte]($0)}
36 | }
37 | }
38 |
39 | extension Int32: OSCMessageArgument {
40 | public var oscValue: [Byte]? {
41 | return withUnsafeBytes(of: self.bigEndian) {[Byte]($0)}
42 | }
43 |
44 | public var oscType: TypeTagValues { .INT32_TYPE_TAG }
45 | }
46 |
47 | extension UInt32: OSCConvertible {
48 | public var oscValue: [Byte]? {
49 | return withUnsafeBytes(of: self.bigEndian) {[Byte]($0)}
50 | }
51 | }
52 |
53 | // default Integers is converted to 32-bin integer for the sake of convenience
54 | extension Int: OSCMessageArgument {
55 | public var oscValue: [Byte]? {
56 | return Int32(self).oscValue
57 | }
58 |
59 | public var oscType: TypeTagValues { .INT32_TYPE_TAG }
60 |
61 | public var packetSize: Int { MemoryLayout.size }
62 |
63 | public init?(data: ArraySlice) {
64 | let binary: [Byte] = [Byte](data)
65 | if binary.count != MemoryLayout.size {
66 | return nil
67 | }
68 |
69 | self = Int(binary.withUnsafeBytes {
70 | $0.load(fromByteOffset: 0, as: Int32.self)
71 | }.byteSwapped)
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/OSCTimeTag+OSCMessageArgument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCTimeTag+OSCType.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: Conversion between double and 64 bit fixed point values
11 |
12 | internal extension Double {
13 | var fixPointValue: (integer: UInt32, fraction: UInt32) {
14 | let fraction: UInt32 = UInt32( (self-floor(self))*4_294_967_296 )
15 | let integer: UInt32 = UInt32(self)
16 |
17 | return (integer: integer, fraction: fraction)
18 | }
19 |
20 | init(integer: UInt32, fraction: UInt32) {
21 | self = Double(integer) + (Double(fraction)/4_294_967_296)
22 | }
23 |
24 | }
25 |
26 | // MARK: TimeTag type
27 |
28 | extension OSCTimeTag: OSCMessageArgument {
29 |
30 | static let oscImmediateBytes: [Byte] = [0, 0, 0, 0, 0, 0, 0, 1]
31 |
32 | public var oscValue: [Byte]? {
33 |
34 | switch self {
35 | case .immediate:
36 | return OSCTimeTag.oscImmediateBytes
37 | case .secondsSince1900(let seconds):
38 |
39 | // first convert double value to integer/fraction tuple
40 | let tuple = seconds.fixPointValue
41 |
42 | let timestamp: UInt64 = UInt64(tuple.integer) << 32 | UInt64(tuple.fraction)
43 |
44 | return timestamp.oscValue!
45 | }
46 | }
47 |
48 | public var oscType: TypeTagValues { .TIME_TAG_TYPE_TAG }
49 |
50 | public var packetSize: Int { MemoryLayout.size }
51 |
52 | public init?(data: S) where S.Iterator.Element == Byte {
53 |
54 | guard data.count == 8 else {
55 | return nil
56 | }
57 |
58 | if data.elementsEqual(OSCTimeTag.oscImmediateBytes) {
59 | self = .immediate
60 | } else {
61 | guard let timestamp = UInt64(data: [Byte](data)) else {
62 | return nil
63 | }
64 |
65 | let secs = UInt32(timestamp >> 32)
66 | let frac = UInt32(timestamp & 0xFFFFFFFF)
67 |
68 | // convert to double
69 | let seconds = Double(integer: secs, fraction: frac)
70 |
71 | self = .secondsSince1900(seconds)
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/OSCBundle+OSCConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCBundle+OSCConvertible.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | extension OSCBundle: OSCConvertible {
11 |
12 | public static let prefix = "#bundle"
13 |
14 | public var oscValue: [Byte]? {
15 | var result = [Byte]()
16 |
17 | // write out head first
18 | result += OSCBundle.prefix.oscValue!
19 | result += timetag.oscValue!
20 |
21 | /// asemble osc elements: size then content
22 | content.forEach { msg in
23 | if let msgValue = msg.oscValue {
24 | result += Int32(msgValue.count).oscValue!
25 | result += msgValue
26 | }
27 | }
28 | return result
29 | }
30 |
31 | public var packetSize: Int {
32 | return content
33 | .map { $0.packetSize }
34 | .reduce(OSCBundle.prefix.alignedSize, { $0 + $1.packetSize })
35 | }
36 |
37 | public init?(data: ArraySlice) {
38 | // Check the header
39 | // - packet length must be at least 16 bytes ('#bundle' + timetag)
40 | guard
41 | data.count >= 16,
42 | data[data.startIndex] == 0x23,
43 | let timestamp = OSCTimeTag(data: data[data.startIndex+8..
68 | var index: Int
69 |
70 | init(_ bytes: ArraySlice) { self.bytes = bytes; index = bytes.startIndex }
71 |
72 | mutating func next() -> ArraySlice? {
73 | guard
74 | index < bytes.endIndex,
75 | let len = Int(data: bytes[index..<(index+4)])
76 | else {
77 | return nil
78 | }
79 |
80 | let elemBytes = bytes[index+4.. () throws -> Void)] {
66 | return [
67 | ("testDispatchWithMatchingAddress", testDispatchWithMatchingAddress),
68 | ("testDispatchWithMatchingAddresses", testDispatchWithMatchingAddresses),
69 | ("testDispatchWithNonMatchingSubComponents", testDispatchWithNonMatchingSubComponents),
70 | ("testDispatchWithNonMatchingRootComponents", testDispatchWithNonMatchingRootComponents),
71 | ("testDispatchWithNonMatchingAddresses", testDispatchWithNonMatchingAddresses)
72 | ]
73 | }
74 | }
75 | #endif
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
12 |
13 | # OSCCore
14 |
15 | OSCCore is a [OpenSoundControl](http://opensoundcontrol.org/spec-1_0) implementation in pure Swift. It is aimed to run on various platforms, including embedded systems like Raspberry Pi.
16 |
17 | Using this module you can easily pass or receive OSC messages, communicate with SuperCollider or other great digital musical instruments.
18 | OSC is also great for implmenenting other protocols like TUIO.
19 |
20 | ## Composing OSC Messages
21 |
22 | To create a message simply instantiate `OSCMessage` class, assign address and parameters to it.
23 |
24 | ```swift
25 | let msg = OSCMessage("/instr/osc1", ["frequency", 440.0])
26 | ```
27 |
28 | OSC parameters can be any of the following
29 | - null value: `nil`
30 | - boolean values: `true`, `false`
31 | - characters: `Character("a")`
32 | - strings
33 | - numerical values: integers, floats and doubles
34 | - MIDI and RGBA values
35 | - OSC symbols: `OSCSymbol(label: symbolName)`
36 | - Array of parameters: `[Int32(0x12345678), "hello"]`
37 | - Blobs: `[UInt8](0xde, 0xad, 0xfa, 0xce)`
38 |
39 | ## OSC Bundles
40 |
41 | Bundles are mixed list of messages and other bundles. They also carry a time stamp or time tag.
42 |
43 | The following bundle when received by SuperCollider will create a new synth instance with the given frequency and amplitude in parameters.
44 |
45 | ```swift
46 | let synthID = Int32(4)
47 |
48 | let synthBundle = OSCBundle(timetag: OSCTimeTag.immediate, content: [
49 | // "/s_new", name, node ID, pos, group ID
50 | OSCMessage(address: "/s_new", args: ["sine", synthID, Int32(1), Int32(1)]),
51 | // "/n_set", "amp", sine amplitude
52 | OSCMessage(address: "/n_set", args: [synthID, "amp", Float32(0.5)]),
53 | // "/n_set", "freq", sine frequency
54 | OSCMessage(address: "/n_set", args: [synthID, "freq", Float32(440.0)])
55 | ])
56 | ```
57 |
58 | ## Sending and receiving OSC messages
59 |
60 | OSCCore is built on top of [SwiftNIO](https://github.com/apple/swift-nio), a asynchronous event-driven network application framework developed by Apple.
61 |
62 | For examples please see BasicListener and SuperColliderExample projects in Source folder.
63 |
64 | ## Usage
65 |
66 | OSCCore is currently provided as Swift PackageManager module. To include OSCCore to your project add the following dependency to your Package.swift file
67 |
68 | ```swift
69 | .Package(url: "https://github.com/segabor/OSCCore.git", majorVersion: 0)
70 | ```
71 |
72 | Other dependency managers like CocoaPods or Chartage is under consideration.
73 |
74 | ## Example Code
75 |
76 | OSC examples are located under `Examples` folder
77 |
78 | - BasicListener
79 | - SuperCollider Example Client
80 |
--------------------------------------------------------------------------------
/Examples/SoundColliderClient/main.swift:
--------------------------------------------------------------------------------
1 | import OSCCore
2 | import NIO
3 |
4 | // ---- //
5 |
6 | enum SuperColliderExampleError: Error {
7 | case decodeOSCPacketFailed
8 | }
9 |
10 |
11 | /**
12 | * Simple function that dumps OSC packets to the output
13 | */
14 | func debugOSCPacket(_ packet: OSCConvertible) {
15 | switch packet {
16 | case let msg as OSCMessage:
17 | let argsString: String = msg.args.map {
18 | if let arg = $0 {
19 | return String(describing: arg)
20 | } else {
21 | return "nil"
22 | }
23 | }.joined(separator: ", ")
24 | print("[message] Address: \(msg.address); Arguments: [\(argsString)]")
25 | case let bundle as OSCBundle:
26 | print("[bundle] Timestamp: \(bundle.timetag); elements:")
27 | bundle.content.forEach {
28 | debugOSCPacket($0)
29 | }
30 | default:
31 | ()
32 | }
33 | }
34 |
35 |
36 | private final class OSCDebugHandler: ChannelInboundHandler {
37 | typealias InboundIn = OSCConvertible
38 |
39 | func channelRead(context: ChannelHandlerContext, data: NIOAny) {
40 | let oscValue = unwrapInboundIn(data)
41 |
42 | debugOSCPacket(oscValue)
43 | }
44 | }
45 |
46 |
47 | extension Channel {
48 | public func writeAndFlush(_ packet: OSCConvertible, target remoteAddr: SocketAddress) throws {
49 | guard let bytes = packet.oscValue else {
50 | throw SuperColliderExampleError.decodeOSCPacketFailed
51 | }
52 |
53 | var buffer = self.allocator.buffer(capacity: bytes.count)
54 | buffer.writeBytes(bytes)
55 |
56 | // create envelope
57 | let envelope = AddressedEnvelope(remoteAddress: remoteAddr, data: buffer)
58 |
59 | return self.writeAndFlush(envelope, promise: nil)
60 | }
61 | }
62 |
63 |
64 | // MAIN CODE STARTS HERE //
65 |
66 | let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
67 | let bootstrap = DatagramBootstrap(group: group)
68 | .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
69 | .channelInitializer { channel in
70 | channel.pipeline.addHandlers([OSCPacketReader(), OSCDebugHandler()])
71 | }
72 | defer {
73 | try! group.syncShutdownGracefully()
74 | }
75 |
76 | let arguments = CommandLine.arguments
77 |
78 | let channel = try bootstrap.bind(host: "127.0.0.1", port: 57150).wait()
79 | let remoteAddr = try SocketAddress(ipAddress: "127.0.0.1", port: 57110)
80 |
81 | /// assemble a synth
82 |
83 | let synthID = Int32(4)
84 |
85 | let bndl = OSCBundle(timetag: OSCTimeTag.immediate, content: [
86 | // "/s_new", name, node ID, pos, group ID
87 | OSCMessage(address: "/s_new", args: ["sine", synthID, Int32(1), Int32(1)]),
88 | // "/n_set", "amp", sine amplitude
89 | OSCMessage(address: "/n_set", args: [synthID, "amp", Float32(0.5)]),
90 | // "/n_set", "freq", sine frequency
91 | OSCMessage(address: "/n_set", args: [synthID, "freq", Float32(440.0)])
92 | ])
93 |
94 | try channel.writeAndFlush(bndl, target: remoteAddr)
95 |
96 | // get and print out frequency number from SuperCollider
97 | let getFrqMessage = OSCMessage(address: "/s_get", args: [synthID, "freq"])
98 | try channel.writeAndFlush(getFrqMessage, target: remoteAddr)
99 |
100 | // let synth beep for two secs
101 | sleep(2)
102 |
103 | // free synth node
104 | let freeNodeMessage = OSCMessage(address: "/n_free", args: [synthID])
105 | try channel.writeAndFlush(freeNodeMessage, target: remoteAddr)
106 |
107 | try channel.closeFuture.wait()
108 |
--------------------------------------------------------------------------------
/Tests/OSCCoreTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | #if !canImport(ObjectiveC)
2 | import XCTest
3 |
4 | extension AddressMatcherTests {
5 | // DO NOT MODIFY: This is autogenerated, use:
6 | // `swift test --generate-linuxmain`
7 | // to regenerate.
8 | static let __allTests__AddressMatcherTests = [
9 | ("testMatcherFunction", testMatcherFunction),
10 | ]
11 | }
12 |
13 | extension DispatcherTests {
14 | // DO NOT MODIFY: This is autogenerated, use:
15 | // `swift test --generate-linuxmain`
16 | // to regenerate.
17 | static let __allTests__DispatcherTests = [
18 | ("testDispatchWithMatchingAddress", testDispatchWithMatchingAddress),
19 | ("testDispatchWithMatchingAddresses", testDispatchWithMatchingAddresses),
20 | ("testDispatchWithNonMatchingSubComponents", testDispatchWithNonMatchingSubComponents),
21 | ("testDispatchWithNonMatchingRootComponents", testDispatchWithNonMatchingRootComponents),
22 | ("testDispatchWithNonMatchingAddresses", testDispatchWithNonMatchingAddresses),
23 | ]
24 | }
25 |
26 | extension OSCBundleTests {
27 | // DO NOT MODIFY: This is autogenerated, use:
28 | // `swift test --generate-linuxmain`
29 | // to regenerate.
30 | static let __allTests__OSCBundleTests = [
31 | ("testNoArgMessage", testNoArgMessage),
32 | ]
33 | }
34 |
35 | extension OSCMessageTests {
36 | // DO NOT MODIFY: This is autogenerated, use:
37 | // `swift test --generate-linuxmain`
38 | // to regenerate.
39 | static let __allTests__OSCMessageTests = [
40 | ("testNoArgMessage", testNoArgMessage),
41 | ("testSingleArgMessage", testSingleArgMessage),
42 | ("testMessageHavingNilArgument", testMessageHavingNilArgument),
43 | ("testMultipleArgsMessage", testMultipleArgsMessage),
44 | ("testMessageHavingSymbolArgument", testMessageHavingSymbolArgument),
45 | ("testMessageHavingDoubleArgument", testMessageHavingDoubleArgument),
46 | ("testMessageHavingInfinityArgument", testMessageHavingInfinityArgument),
47 | ("testMessageHavingRGBAArgument", testMessageHavingRGBAArgument),
48 | ("testMessageHavingMIDIArgument", testMessageHavingMIDIArgument),
49 | ("testMessageHavingEmptyBlob", testMessageHavingEmptyBlob),
50 | ("testMessageHavingBlob", testMessageHavingBlob),
51 | ("testMessageHavingPaddedBlob", testMessageHavingPaddedBlob),
52 | ("testMessageHavingEmptyArray", testMessageHavingEmptyArray),
53 | ("testMessageHavingArrayOfNoValueArgs", testMessageHavingArrayOfNoValueArgs),
54 | ]
55 | }
56 |
57 | extension OSCTimeTagTests {
58 | // DO NOT MODIFY: This is autogenerated, use:
59 | // `swift test --generate-linuxmain`
60 | // to regenerate.
61 | static let __allTests__OSCTimeTagTests = [
62 | ("testImmediateTimeTagToDateConversion", testImmediateTimeTagToDateConversion),
63 | ("testSecondsSince1900TimeTagConversion", testSecondsSince1900TimeTagConversion),
64 | ]
65 | }
66 |
67 | extension ValueConversionTests {
68 | // DO NOT MODIFY: This is autogenerated, use:
69 | // `swift test --generate-linuxmain`
70 | // to regenerate.
71 | static let __allTests__ValueConversionTests = [
72 | ("testBooleanConversion", testBooleanConversion),
73 | ("testCharacterConversion", testCharacterConversion),
74 | ("testEmptyStringConversion", testEmptyStringConversion),
75 | ("testBasicStringConversion", testBasicStringConversion),
76 | ("testSymbolConversion", testSymbolConversion),
77 | ("testInt32Conversion", testInt32Conversion),
78 | ("testInt64Conversion", testInt64Conversion),
79 | ("testIntConversion", testIntConversion),
80 | ("testFloat32Conversion", testFloat32Conversion),
81 | ("testDoubleConversion", testDoubleConversion),
82 | ("testImmediateTimeTagConversion", testImmediateTimeTagConversion),
83 | ("testTimeTagConversion", testTimeTagConversion),
84 | ("testFixedPrecisionToDoubleConversion", testFixedPrecisionToDoubleConversion),
85 | ("testMIDIConversion", testMIDIConversion),
86 | ("testRGBAConversion", testRGBAConversion),
87 | ("testEmptyBlobConversion", testEmptyBlobConversion),
88 | ("testBlobConversion", testBlobConversion),
89 | ("testPaddedBlobConversion", testPaddedBlobConversion),
90 | ("testArrayConversion", testArrayConversion),
91 | ]
92 | }
93 |
94 | public func __allTests() -> [XCTestCaseEntry] {
95 | return [
96 | testCase(OSCMessageTests.__allTests__OSCMessageTests),
97 | testCase(ValueConversionTests.__allTests__ValueConversionTests),
98 | testCase(AddressMatcherTests.__allTests__AddressMatcherTests),
99 | testCase(DispatcherTests.__allTests__DispatcherTests),
100 | testCase(OSCBundleTests.__allTests__OSCBundleTests),
101 | testCase(OSCTimeTagTests.__allTests__OSCTimeTagTests),
102 | ]
103 | }
104 | #endif
105 |
--------------------------------------------------------------------------------
/Source/OSCCore/conversion/OSCMessage+OSCConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OSCMessage+OSCConvertible.swift
3 | // OSCCore
4 | //
5 | // Created by Sebestyén Gábor on 2017. 11. 18..
6 | //
7 |
8 | import Foundation
9 |
10 | extension OSCMessage: OSCConvertible {
11 | public init?(data: ArraySlice) {
12 | guard let address = String(data: data) else {
13 | return nil
14 | }
15 |
16 | var index: ArraySlice.Index = data.startIndex + address.alignedSize
17 |
18 | guard data[index] == 0x2C,
19 | let typeTagLabels = String(data: data.suffix(from: index))
20 | else {
21 | return nil
22 | }
23 |
24 | index += typeTagLabels.alignedSize
25 |
26 | // NOTE: unrecognized type tag labels will be mapped as nils
27 | let typeTags: [TypeTagValues?] = typeTagLabels.map { TypeTagValues(rawValue: $0) }
28 |
29 | var tagIterator = typeTags.makeIterator()
30 |
31 | // skip first, nil value
32 | // no tag type associated to comma character
33 | _ = tagIterator.next()
34 |
35 | // process args
36 | guard let args = OSCMessage.parseArguments(&tagIterator, &index, data) else {
37 | return nil
38 | }
39 |
40 | self.init(address: address, args: args)
41 | }
42 |
43 | public var typeTags: String {
44 | var typeTags: [Character] = [","]
45 | args.forEach {
46 | if let arg = $0 {
47 | switch arg {
48 | case let collection as [OSCMessageArgument?]:
49 | typeTags.append(contentsOf: collection.oscTypes.map {$0.rawValue})
50 | default:
51 | typeTags.append(arg.oscType.rawValue)
52 | }
53 | } else {
54 | typeTags.append(TypeTagValues.NIL_TYPE_TAG.rawValue)
55 | }
56 | }
57 |
58 | return String(typeTags)
59 | }
60 |
61 | // OSC Message Prefix := Address Pattern + Type Tag String
62 | public var prefixOscValue: [Byte] {
63 | return address.oscValue! + typeTags.oscValue!
64 | }
65 |
66 | public var oscValue: [Byte]? {
67 | // convert values to packets and collect them into a byte array
68 | var argsBytes: [Byte] = [Byte]()
69 | args.forEach {
70 | if let bytes = $0?.oscValue {
71 | argsBytes += bytes
72 | }
73 | }
74 |
75 | // OSC Message := Address Pattern + Type Tag String + Arguments
76 | return prefixOscValue + argsBytes
77 | }
78 |
79 | public var packetSize: Int {
80 | return args
81 | .map { $0?.packetSize ?? 0 }
82 | .reduce(address.oscValue!.count + typeTags.oscValue!.count, { (acc: Int, size: Int) in acc + size })
83 | }
84 |
85 | public static func parseArguments( _ tagIterator: inout IndexingIterator<[TypeTagValues?]>, _ index: inout ArraySlice.Index, _ data: ArraySlice) -> [OSCMessageArgument?]? { // swiftlint:disable:this cyclomatic_complexity line_length function_body_length
86 | var args: [OSCMessageArgument?] = [OSCMessageArgument?]()
87 |
88 | while let possibleTag = tagIterator.next() {
89 | guard let tag = possibleTag else {
90 | // Encountered an unknown tag, abort
91 | return nil
92 | }
93 |
94 | switch tag {
95 | case .ARRAY_BEGIN_TYPE_TAG:
96 | guard let argArray: [OSCMessageArgument?] = parseArguments(&tagIterator, &index, data) else {
97 | return nil
98 | }
99 | args.append(argArray as OSCMessageArgument)
100 | index += argArray.packetSize
101 | case .ARRAY_END_TYPE_TAG:
102 | // consume tag, return processed args
103 | return args
104 | case .STRING_TYPE_TAG:
105 | guard let val = String(data: data.suffix(from: index)) else {
106 | return nil
107 | }
108 | args.append(val)
109 | index += val.packetSize
110 | case .SYMBOL_TYPE_TAG:
111 | guard let val = OSCSymbol(data: data.suffix(from: index)) else {
112 | return nil
113 | }
114 | args.append(val)
115 | index += val.packetSize
116 | case .INT32_TYPE_TAG:
117 | guard let val = Int32(data: data[index.. Bool {
15 | let fullAddressRange = address.startIndex.. Bool { // swiftlint:disable:this function_body_length cyclomatic_complexity line_length
21 | var si = address.startIndex // swiftlint:disable:this identifier_name
22 | var pi = pattern.startIndex // swiftlint:disable:this identifier_name
23 |
24 | while pi < pattern.endIndex {
25 | guard si < address.endIndex || pattern[pi] == "*" else {
26 | return false
27 | }
28 |
29 | let char = pattern[pi]
30 | pi = pattern.index(after: pi)
31 |
32 | switch char {
33 | case "*":
34 |
35 | // consume star wild cards
36 | while pi < pattern.endIndex && pattern[pi] == "*" && pattern[pi] != "/" {
37 | pi = pattern.index(after: pi)
38 | }
39 |
40 | // pattern ending with stars matches address part
41 | if pi == pattern.endIndex {
42 | return true
43 | }
44 |
45 | // in case no other wild cards are found ...
46 | if pattern[pi] != "?" || pattern[pi] != "[" || pattern[pi] != "{" {
47 | // loop while
48 | while si < address.endIndex && pattern[pi] != address[si] {
49 | si = address.index(after: si)
50 | }
51 | }
52 |
53 | while si < address.endIndex {
54 | if matchComponent(address: address[si...], pattern: pattern[pi...]) {
55 | return true
56 | }
57 | }
58 |
59 | return false
60 | case "?":
61 | if si < address.endIndex {
62 | break
63 | }
64 |
65 | return false
66 | /*
67 | * set specification is inclusive, that is [a-z] is a, z and
68 | * everything in between. this means [z-a] may be interpreted
69 | * as a set that contains z, a and nothing in between.
70 | */
71 | case "[":
72 |
73 | let negate = (pattern[pi] == "!")
74 | if negate {
75 | pi = pattern.index(after: pi)
76 | }
77 |
78 | var match = false
79 |
80 | while !match && pi < pattern.endIndex {
81 |
82 | if pi == pattern.endIndex {
83 | return false
84 | }
85 |
86 | // pop next pattern character
87 | let patternChar = pattern[pi]
88 | pi = pattern.index(after: pi)
89 |
90 | // detect range
91 | // case c-c
92 | if patternChar == "-" {
93 | // skip char
94 | pi = pattern.index(after: pi)
95 | if pi == pattern.endIndex {
96 | return false
97 | }
98 |
99 | if pattern[pi] != "]" {
100 | if address[si] == patternChar || address[si] == pattern[pi]
101 | || (address[si] > patternChar && address[si] < pattern[pi]) {
102 | match = true
103 | }
104 | } else { // c-]
105 | if address[si] >= patternChar {
106 | match = true
107 | }
108 | break
109 | }
110 | } else {
111 | // cc or c]
112 | if patternChar == address[si] {
113 | match = true
114 | }
115 | if pattern[pi] != "]" {
116 | if pattern[pi] == address[si] {
117 | match = true
118 | }
119 | } else {
120 | break
121 | }
122 | }
123 | }
124 |
125 | if negate == match {
126 | match = false
127 | }
128 |
129 | // if there is a match, skip past the cset and continue on
130 | while pi < pattern.endIndex && pattern[pi] != "]" {
131 | pi = pattern.index(after: pi)
132 | }
133 |
134 | if pi == pattern.endIndex {
135 | return false
136 | } else {
137 | // consume closing character
138 | pi = pattern.index(after: pi)
139 | }
140 | case "{":
141 |
142 | let place = si // to backtrack
143 | var remainder = pi // to forward-track
144 |
145 | // iterate to the end of the choice list
146 | while remainder < pattern.endIndex && pattern[remainder] != "}" {
147 | remainder = pattern.index(after: remainder)
148 | }
149 | if remainder == pattern.endIndex {
150 | // print("ERROR: unbalanced Choice")
151 | return false
152 | }
153 |
154 | // ?? step back
155 | // reminder points to the last char AFTER closing curly brace
156 | remainder = pattern.index(after: remainder)
157 |
158 | // pick first char after opening curly brace
159 | var char = pattern[pi]
160 | pi = pattern.index(after: pi)
161 |
162 | while pi < pattern.endIndex {
163 | if char == "," {
164 | if matchComponent(address: address[si...], pattern: pattern[remainder...]) {
165 | return true
166 | } else {
167 | // backtrack on test string
168 | si = place
169 | // continue testing,
170 | // skip comma
171 | if pi == pattern.endIndex {
172 | return false
173 | } else {
174 | pi = pattern.index(after: pi)
175 | }
176 | }
177 | } else if char == "}" {
178 | // continue normal pattern matching
179 | if pi == pattern.endIndex && si == address.endIndex {
180 | return true
181 | }
182 |
183 | si = address.index(before: si) // str is incremented again below
184 | } else if char == address[si] {
185 | // print("#3 choice: \(char) == \(address[si])")
186 | si = address.index(after: si)
187 | if si == address.endIndex && remainder < pattern.endIndex {
188 | // print("ERROR: address == EOF, pattern != EOF")
189 | return false
190 | }
191 | } else { // skip to next comma
192 | // reset string position
193 | si = place
194 |
195 | while pi < pattern.endIndex && pattern[pi] != "," && pattern[pi] != "}" {
196 | pi = pattern.index(after: pi)
197 | }
198 |
199 | if pi == pattern.endIndex {
200 | // print("ERR: UNBALANCED CHOICE")
201 | return false
202 | }
203 |
204 | if pattern[pi] == "," {
205 | pi = pattern.index(after: pi)
206 | } else if pattern[pi] == "}" {
207 | return false
208 | }
209 | }
210 |
211 | // --- end of inner switch ---
212 | char = pattern[pi]
213 | if pi < pattern.endIndex {
214 | pi = pattern.index(after: pi)
215 | }
216 | }
217 | default:
218 | if char != address[si] {
219 | return false
220 | }
221 | }
222 |
223 | // --- end of outer switch ---
224 |
225 | if si < address.endIndex {
226 | si = address.index(after: si)
227 | }
228 |
229 | }
230 |
231 | return si == address.endIndex
232 | }
233 |
234 | public func match(address: String, pattern: String) -> Bool {
235 | let addrComponents: [String] = address.components(separatedBy: "/")
236 | let patternComponents: [String] = pattern.components(separatedBy: "/")
237 |
238 | // make sure OSC address and patterns have the same number of components
239 | guard addrComponents.count == patternComponents.count else {
240 | return false
241 | }
242 |
243 | return !zip(addrComponents, patternComponents)
244 | .map { matchComponents(address: $0.0, pattern: $0.1) }
245 | .contains(false)
246 | }
247 |
--------------------------------------------------------------------------------
/Playgrounds/AddressMatcher.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play
2 |
3 | import Foundation
4 |
5 |
6 | /*
7 | * Function borrowed from JavaOSC ( https://github.com/hoijui/JavaOSC/blob/master/modules/core/src/main/java/com/illposed/osc/utility/OSCPatternAddressSelector.java )
8 | * Which originates from LibLo ( https://github.com/radarsat1/liblo/blob/master/src/pattern_match.c )
9 | */
10 | func matches(address: String, pattern: String) -> Bool {
11 | // var negate = false
12 | // var match = false
13 |
14 | var si = address.startIndex
15 | var pi = pattern.startIndex
16 |
17 | while pi < pattern.endIndex {
18 | // si == address.endIndex && pattern[pi] != "*" return false
19 | guard si < address.endIndex || pattern[pi] == "*" else {
20 | return false
21 | }
22 |
23 | let char = pattern[pi]
24 | pi = pattern.index(after: pi)
25 |
26 | switch char {
27 | case "*":
28 |
29 | // consume star wild cards
30 | while pi < pattern.endIndex && pattern[pi] == "*" && pattern[pi] != "/" {
31 | pi = pattern.index(after: pi)
32 | }
33 |
34 | // pattern ending with stars matches address part
35 | if pi == pattern.endIndex {
36 | return true
37 | }
38 |
39 | // in case no other wild cards are found ...
40 | if pattern[pi] != "?" || pattern[pi] != "[" || pattern[pi] != "{" {
41 | // loop while
42 | while si < address.endIndex && pattern[pi] != address[si] {
43 | si = address.index(after: si)
44 | }
45 | }
46 |
47 | while si < address.endIndex {
48 | if matches(address: address.substring(from: si), pattern: pattern.substring(from:pi)) {
49 | return true
50 | }
51 | }
52 |
53 | return false
54 | case "?":
55 | if si < address.endIndex {
56 | break
57 | }
58 |
59 | return false
60 | /*
61 | * set specification is inclusive, that is [a-z] is a, z and
62 | * everything in between. this means [z-a] may be interpreted
63 | * as a set that contains z, a and nothing in between.
64 | */
65 | case "[":
66 |
67 | let negate = (pattern[pi] == "!")
68 | if negate {
69 | pi = pattern.index(after: pi)
70 | }
71 |
72 | var match = false
73 |
74 | while !match && pi < pattern.endIndex {
75 |
76 | if pi == pattern.endIndex {
77 | return false
78 | }
79 |
80 | // pop next pattern character
81 | let c = pattern[pi]
82 | pi = pattern.index(after: pi)
83 |
84 | // detect range
85 | // case c-c
86 | if c == "-" {
87 | // skip char
88 | pi = pattern.index(after: pi)
89 | if pi == pattern.endIndex {
90 | return false
91 | }
92 |
93 | if pattern[pi] != "]" {
94 | if address[si] == c || address[si] == pattern[pi]
95 | || (address[si] > c && address[si] < pattern[pi]) {
96 | match = true
97 | }
98 | } else { // c-]
99 | if address[si] >= c {
100 | match = true
101 | }
102 | break
103 | }
104 | } else {
105 | // cc or c]
106 | if c == address[si] {
107 | match = true
108 | }
109 | if pattern[pi] != "]" {
110 | if pattern[pi] == address[si] {
111 | match = true
112 | }
113 | } else {
114 | break
115 | }
116 | }
117 | }
118 |
119 | // FIXME : negate result
120 | if negate == match {
121 | match = false
122 | }
123 |
124 | // if there is a match, skip past the cset and continue on
125 | while pi < pattern.endIndex && pattern[pi] != "]" {
126 | pi = pattern.index(after: pi)
127 | }
128 |
129 | if pi == pattern.endIndex {
130 | return false
131 | } else {
132 | // consume closing character
133 | pi = pattern.index(after: pi)
134 | }
135 | case "{":
136 |
137 | let place = si // to backtrack
138 | var remainder = pi // to forward-track
139 |
140 | // iterate to the end of the choice list
141 | while remainder < pattern.endIndex && pattern[remainder] != "}" {
142 | remainder = pattern.index(after:remainder)
143 | }
144 | if remainder == pattern.endIndex {
145 | print("ERROR: unbalanced Choice")
146 | return false
147 | }
148 |
149 | // ?? step back
150 | // reminder points to the last char AFTER closing curly brace
151 | remainder = pattern.index(after:remainder)
152 |
153 |
154 | // pick first char after opening curly brace
155 | var char = pattern[pi]
156 | pi = pattern.index(after:pi)
157 |
158 | while pi < pattern.endIndex {
159 | if char == "," {
160 | // print("#1 choice: char = \(char)")
161 | // print("Test string \(address.substring(from: si)) against pattern \(pattern.substring(from: remainder))")
162 | if matches(address: address.substring(from: si), pattern: pattern.substring(from: remainder)) {
163 | return true
164 | } else {
165 | // backtrack on test string
166 | si = place
167 | // continue testing,
168 | // skip comma
169 | if pi == pattern.endIndex {
170 | return false
171 | } else {
172 | pi = pattern.index(after: pi)
173 | }
174 | }
175 | } else if char == "}" {
176 | // print("#2 choice: char = \(char)")
177 |
178 | // continue normal pattern matching
179 | if pi == pattern.endIndex && si == address.endIndex {
180 | return true
181 | }
182 |
183 | si = address.index(before: si) // str is incremented again below
184 | } else if char == address[si] {
185 | // print("#3 choice: \(char) == \(address[si])")
186 | si = address.index(after: si)
187 | if si == address.endIndex && remainder < pattern.endIndex {
188 | print("ERROR: address == EOF, pattern != EOF")
189 | return false
190 | }
191 | } else { // skip to next comma
192 | // print("#4 choice: char = \(char)")
193 |
194 | // reset string position
195 | si = place
196 |
197 | while pi < pattern.endIndex && pattern[pi] != "," && pattern[pi] != "}" {
198 | pi = pattern.index(after: pi)
199 | }
200 |
201 | if pi == pattern.endIndex {
202 | print("ERR: UNBALANCED CHOICE")
203 | return false
204 | }
205 |
206 | if pattern[pi] == "," {
207 | pi = pattern.index(after: pi)
208 | } else if pattern[pi] == "}" {
209 | return false
210 | }
211 | }
212 |
213 | // --- end of inner switch ---
214 | char = pattern[pi]
215 | if pi < pattern.endIndex {
216 | pi = pattern.index(after:pi)
217 | }
218 | }
219 | default:
220 | if char != address[si] {
221 | return false
222 | }
223 | }
224 |
225 | // --- end of outer switch ---
226 |
227 | if si < address.endIndex {
228 | si = address.index(after:si)
229 | }
230 |
231 | }
232 |
233 | return si == address.endIndex
234 | }
235 |
236 |
237 | print("true", matches(address:"ablak", pattern:"ablak"))
238 |
239 | print("false", matches(address:"ablak", pattern:"abrak"))
240 |
241 | print("true", matches(address:"ablak", pattern:"ab?ak"))
242 |
243 | print("true", matches(address:"ablak", pattern:"ab*"))
244 | print("true", matches(address:"ablak", pattern:"ab*k"))
245 |
246 | print("true", matches(address:"ablak", pattern:"a{blak}"))
247 | print("true", matches(address:"ablak", pattern:"a{blak,jto}"))
248 |
--------------------------------------------------------------------------------
/Tests/OSCCoreTests/ValueConversionTests.swift:
--------------------------------------------------------------------------------
1 | @testable import OSCCore
2 | import XCTest
3 |
4 | class ValueConversionTests: XCTestCase {
5 |
6 | func testBooleanConversion() {
7 | assertValueConversion(expected: nil, typeTag: TypeTagValues.TRUE_TYPE_TAG, testValue: true)
8 | assertValueConversion(expected: nil, typeTag: TypeTagValues.FALSE_TYPE_TAG, testValue: false)
9 | }
10 |
11 | func testCharacterConversion() {
12 | let testValue = Character(" ")
13 | let packet: [Byte] = [0x00, 0x00, 0x00, 0x20]
14 |
15 | assertValueConversion(expected: packet, typeTag: TypeTagValues.CHAR_TYPE_TAG, testValue: testValue)
16 | }
17 |
18 | func testEmptyStringConversion() {
19 | // test value
20 | let str = ""
21 | // expected packet
22 | let packet: [Byte] = [0x00, 0x00, 0x00, 0x00]
23 |
24 | assertValueConversion(expected: packet, typeTag: TypeTagValues.STRING_TYPE_TAG, testValue: str)
25 | }
26 |
27 | func testBasicStringConversion() {
28 | // test value
29 | let str = "hello"
30 | // expected packet
31 | let packet: [Byte] = [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00]
32 |
33 | assertValueConversion(expected: packet, typeTag: TypeTagValues.STRING_TYPE_TAG, testValue: str)
34 | }
35 |
36 | func testSymbolConversion() {
37 | // test value
38 | let value = OSCSymbol(label: "hello")
39 | // expected packet
40 | let packet: [Byte] = [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00]
41 |
42 | assertValueConversion(expected: packet, typeTag: TypeTagValues.SYMBOL_TYPE_TAG, testValue: value)
43 | }
44 |
45 | func testInt32Conversion() {
46 | let value: Int32 = 0x12345678
47 | let packet: [Byte] = [0x12, 0x34, 0x56, 0x78]
48 |
49 | assertValueConversion(expected: packet, typeTag: TypeTagValues.INT32_TYPE_TAG, testValue: value)
50 | }
51 |
52 | func testInt64Conversion() {
53 | let value: Int64 = 0x123456789abcdef0
54 | let packet: [Byte] = [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]
55 |
56 | assertValueConversion(expected: packet, typeTag: TypeTagValues.INT64_TYPE_TAG, testValue: value)
57 | }
58 |
59 | func testIntConversion() {
60 | let value: Int = 0x12345678
61 | let packet: [Byte] = [0x12, 0x34, 0x56, 0x78]
62 |
63 | assertValueConversion(expected: packet, typeTag: TypeTagValues.INT32_TYPE_TAG, testValue: value)
64 | }
65 |
66 | func testFloat32Conversion() {
67 | let value: Float32 = Float32(1.234)
68 | let packet: [Byte] = [0x3f, 0x9d, 0xf3, 0xb6]
69 |
70 | assertValueConversion(expected: packet, typeTag: TypeTagValues.FLOAT_TYPE_TAG, testValue: value)
71 | }
72 |
73 | func testDoubleConversion() {
74 | let value: Double = 1234.5678
75 | let packet: [Byte] = [0x40, 0x93, 0x4a, 0x45, 0x6d, 0x5c, 0xfa, 0xad]
76 |
77 | assertValueConversion(expected: packet, typeTag: .DOUBLE_TYPE_TAG, testValue: value)
78 |
79 | assertValueConversion(expected: nil, typeTag: .INFINITUM_TYPE_TAG, testValue: Double.infinity)
80 | }
81 |
82 | func testImmediateTimeTagConversion() {
83 | let value = OSCTimeTag.immediate
84 | let packet: [Byte] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
85 |
86 | assertValueConversion(expected: packet, typeTag: TypeTagValues.TIME_TAG_TYPE_TAG, testValue: value)
87 | }
88 |
89 | func testTimeTagConversion() {
90 | let interval = Double(0x12345678)
91 | let value: OSCTimeTag = OSCTimeTag.secondsSince1900(interval)
92 | let packet: [Byte] = [0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00]
93 |
94 | assertValueConversion(expected: packet, typeTag: TypeTagValues.TIME_TAG_TYPE_TAG, testValue: value)
95 | }
96 |
97 | func testFixedPrecisionToDoubleConversion() {
98 | let testZeroValue: Double = 0
99 |
100 | var fixedPointValue = testZeroValue.fixPointValue
101 |
102 | XCTAssertEqual(fixedPointValue.integer, UInt32(0))
103 | XCTAssertEqual(fixedPointValue.fraction, UInt32(0))
104 |
105 | var dblValue = Double(integer: fixedPointValue.integer, fraction: fixedPointValue.fraction)
106 |
107 | XCTAssertEqual(testZeroValue, dblValue)
108 |
109 | let testNonZeroValue: Double = 123.0+(456.0/4_294_967_296)
110 |
111 | fixedPointValue = testNonZeroValue.fixPointValue
112 |
113 | XCTAssertEqual(fixedPointValue.integer, UInt32(123))
114 | XCTAssertEqual(fixedPointValue.fraction, UInt32(456))
115 |
116 | dblValue = Double(integer: fixedPointValue.integer, fraction: fixedPointValue.fraction)
117 |
118 | XCTAssertEqual(testNonZeroValue, dblValue)
119 | }
120 |
121 | func testRGBAConversion() {
122 | let value = RGBA(red: 0x12, green: 0x34, blue: 0x56, alpha: 0x78)
123 | let packet: [Byte] = [0x12, 0x34, 0x56, 0x78]
124 |
125 | assertValueConversion(expected: packet, typeTag: TypeTagValues.RGBA_COLOR_TYPE_TAG, testValue: value)
126 | }
127 |
128 | func testMIDIConversion() {
129 | let value = MIDI(portId: 0x12, status: 0x34, data1: 0x56, data2: 0x78)
130 | let packet: [Byte] = [0x12, 0x34, 0x56, 0x78]
131 |
132 | assertValueConversion(expected: packet, typeTag: TypeTagValues.MIDI_MESSAGE_TYPE_TAG, testValue: value)
133 | }
134 |
135 | func testEmptyBlobConversion() {
136 | let bytes: [Byte] = []
137 |
138 | let value: Data = bytes.withUnsafeBytes {
139 | return Data(bytes: $0.baseAddress!, count: bytes.count)
140 | }
141 | let packet: [Byte] = [0x00, 0x00, 0x00, 0x00]
142 |
143 | assertValueConversion(expected: packet, typeTag: TypeTagValues.BLOB_TYPE_TAG, testValue: value)
144 | }
145 |
146 | func testBlobConversion() {
147 | let bytes: [Byte] = [0xde, 0xad, 0xba, 0xbe]
148 |
149 | let value: Data = bytes.withUnsafeBytes {
150 | return Data(bytes: $0.baseAddress!, count: bytes.count)
151 | }
152 | let packet: [Byte] = [0x00, 0x00, 0x00, 0x04, 0xde, 0xad, 0xba, 0xbe]
153 |
154 | assertValueConversion(expected: packet, typeTag: TypeTagValues.BLOB_TYPE_TAG, testValue: value)
155 | }
156 |
157 | func testPaddedBlobConversion() {
158 | let bytes: [Byte] = [0xde, 0xad, 0xba, 0xbe, 0xca, 0xfe, 0xba]
159 |
160 | let value: Data = bytes.withUnsafeBytes {
161 | return Data(bytes: $0.baseAddress!, count: bytes.count)
162 | }
163 | let packet: [Byte] = [0x00, 0x00, 0x00, 0x07, 0xde, 0xad, 0xba, 0xbe, 0xca, 0xfe, 0xba, 0x00]
164 |
165 | assertValueConversion(expected: packet, typeTag: TypeTagValues.BLOB_TYPE_TAG, testValue: value)
166 | }
167 |
168 | func testArrayConversion() {
169 | let value0: [OSCMessageArgument?] = [OSCMessageArgument?]()
170 | let packet0: [Byte] = [Byte]()
171 |
172 | assertValueConversion(expected: packet0, typeTag: TypeTagValues.ARRAY_BEGIN_TYPE_TAG, testValue: value0)
173 |
174 | let valueNil: [OSCMessageArgument?] = [nil]
175 | let packetNil: [Byte] = [Byte]()
176 |
177 | assertValueConversion(expected: packetNil, typeTag: .ARRAY_BEGIN_TYPE_TAG, testValue: valueNil)
178 |
179 | let value: [OSCMessageArgument?] = [Int32(0x12345678)]
180 | let packet: [Byte] = [0x12, 0x34, 0x56, 0x78]
181 |
182 | assertValueConversion(expected: packet, typeTag: .ARRAY_BEGIN_TYPE_TAG, testValue: value)
183 |
184 | let value2: [OSCMessageArgument?] = [Int32(0x12345678), Int32(0x12345678)]
185 | let packet2: [Byte] = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]
186 |
187 | assertValueConversion(expected: packet2, typeTag: TypeTagValues.ARRAY_BEGIN_TYPE_TAG, testValue: value2)
188 |
189 | let value3: [OSCMessageArgument?] = [nil, Int32(0x12345678)]
190 | let packet3: [Byte] = [0x12, 0x34, 0x56, 0x78]
191 |
192 | assertValueConversion(expected: packet3, typeTag: TypeTagValues.ARRAY_BEGIN_TYPE_TAG, testValue: value3)
193 | }
194 |
195 | private func assertValueConversion(expected bytes: [Byte]?, typeTag: TypeTagValues, testValue: OSCMessageArgument) {
196 | XCTAssertEqual(typeTag, testValue.oscType, "Type tag mismatch")
197 |
198 | if let bytes = testValue.oscValue {
199 | XCTAssertEqual(bytes.count, testValue.packetSize, "OSC Packet size mismatch")
200 | XCTAssertEqual(bytes, bytes, "Incorrect OSC Packet")
201 |
202 | if TypeTagValues.STRING_TYPE_TAG == typeTag {
203 | XCTAssertEqual(bytes.count%4, 0, "Packet length misaligment, it must be multiple of 4")
204 | }
205 | } else {
206 | XCTAssertNil(bytes, "Test OSC value should not return serialized bytes")
207 | }
208 | }
209 | }
210 |
211 | #if os(Linux)
212 | extension ValueConversionTests {
213 | static var allTests: [(String, (ValueConversionTests) -> () throws -> Void)] {
214 | return [
215 | ("testBooleanConversion", testBooleanConversion),
216 | ("testCharacterConversion", testCharacterConversion),
217 | ("testEmptyStringConversion", testEmptyStringConversion),
218 | ("testBasicStringConversion", testBasicStringConversion),
219 | ("testSymbolConversion", testSymbolConversion),
220 | ("testInt32Conversion", testInt32Conversion),
221 | ("testInt64Conversion", testInt64Conversion),
222 | ("testIntConversion", testIntConversion),
223 | ("testFloat32Conversion", testFloat32Conversion),
224 | ("testDoubleConversion", testDoubleConversion),
225 | ("testImmediateTimeTagConversion", testImmediateTimeTagConversion),
226 | ("testTimeTagConversion", testTimeTagConversion),
227 | ("testFixedPrecisionToDoubleConversion", testFixedPrecisionToDoubleConversion),
228 | ("testMIDIConversion", testMIDIConversion),
229 | ("testRGBAConversion", testRGBAConversion),
230 | ("testEmptyBlobConversion", testEmptyBlobConversion),
231 | ("testBlobConversion", testBlobConversion),
232 | ("testPaddedBlobConversion", testPaddedBlobConversion),
233 | ("testArrayConversion", testArrayConversion)
234 | ]
235 | }
236 | }
237 | #endif
238 |
--------------------------------------------------------------------------------
/Tests/OSCCoreTests/OSCMessageTests.swift:
--------------------------------------------------------------------------------
1 | @testable import OSCCore
2 | import XCTest
3 |
4 | class OSCMessageTests: XCTestCase {
5 | func testNoArgMessage() {
6 | let msg = OSCMessage(address: "hello", args: [])
7 |
8 | let expectedPacket: [Byte] = [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00]
9 |
10 | doTestOSCMessage(msg, expectedPacket, ",")
11 | }
12 |
13 | func testMessageHavingNilArgument() {
14 | let msg = OSCMessage(address: "/nil", args: [nil])
15 |
16 | let expectedPacket: [Byte] = [0x2f, 0x6e, 0x69, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x4e, 0x00, 0x00]
17 |
18 | doTestOSCMessage(msg, expectedPacket, ",N")
19 | }
20 |
21 | func testSingleArgMessage() {
22 | let msg = OSCMessage(address: "/oscillator/4/frequency", args: [Float32(440.0)])
23 |
24 | let expectedPacket: [Byte] = [
25 | 0x2f, 0x6f, 0x73, 0x63,
26 | 0x69, 0x6c, 0x6c, 0x61,
27 | 0x74, 0x6f, 0x72, 0x2f,
28 | 0x34, 0x2f, 0x66, 0x72,
29 | 0x65, 0x71, 0x75, 0x65,
30 | 0x6e, 0x63, 0x79, 0x00,
31 | 0x2c, 0x66, 0x00, 0x00,
32 | 0x43, 0xdc, 0x00, 0x00
33 | ]
34 |
35 | doTestOSCMessage(msg, expectedPacket, ",f")
36 | }
37 |
38 | func testMultipleArgsMessage() {
39 | let msg = OSCMessage(address: "/foo", args: [Int32(1000), Int32(-1), "hello", Float32(1.234), Float32(5.678)])
40 |
41 | let expectedPacket: [Byte] = [
42 | // "/foo"
43 | 0x2f, 0x66, 0x6f, 0x6f,
44 | 0x00, 0x00, 0x00, 0x00,
45 | // ",iisff"
46 | 0x2c, 0x69, 0x69, 0x73,
47 | 0x66, 0x66, 0x00, 0x00,
48 | // 1000
49 | 0x00, 0x00, 0x03, 0xe8,
50 | // -1
51 | 0xff, 0xff, 0xff, 0xff,
52 | // "hello"
53 | 0x68, 0x65, 0x6c, 0x6c,
54 | 0x6f, 0x00, 0x00, 0x00,
55 | // 1.234
56 | 0x3f, 0x9d, 0xf3, 0xb6,
57 | // 5.678
58 | 0x40, 0xb5, 0xb2, 0x2d
59 | ]
60 |
61 | doTestOSCMessage(msg, expectedPacket, ",iisff")
62 | }
63 |
64 | func testMessageHavingSymbolArgument() {
65 | let msg = OSCMessage(address: "/test", args: [OSCSymbol(label: "symbol1")])
66 |
67 | let expectedPacket: [Byte] = [
68 | // "/test"
69 | 0x2f, 0x74, 0x65, 0x73,
70 | 0x74, 0x00, 0x00, 0x00,
71 | // ",S"
72 | 0x2c, 0x53, 0x00, 0x00,
73 | // "symbol1"
74 | 0x73, 0x79, 0x6d, 0x62,
75 | 0x6f, 0x6c, 0x31, 0x00
76 | ]
77 |
78 | doTestOSCMessage(msg, expectedPacket, ",S")
79 | }
80 |
81 | func testMessageHavingDoubleArgument() {
82 | let msg = OSCMessage(address: "/test", args: [1234.5678])
83 |
84 | let expectedPacket: [Byte] = [
85 | // "/test"
86 | 0x2f, 0x74, 0x65, 0x73,
87 | 0x74, 0x00, 0x00, 0x00,
88 | // ",d"
89 | 0x2c, 0x64, 0x00, 0x00,
90 | // double value
91 | 0x40, 0x93, 0x4a, 0x45,
92 | 0x6d, 0x5c, 0xfa, 0xad
93 | ]
94 |
95 | doTestOSCMessage(msg, expectedPacket, ",d")
96 | }
97 |
98 | func testMessageHavingInfinityArgument() {
99 | let msg = OSCMessage(address: "/test", args: [Double.infinity])
100 |
101 | let expectedPacket: [Byte] = [
102 | // "/test"
103 | 0x2f, 0x74, 0x65, 0x73,
104 | 0x74, 0x00, 0x00, 0x00,
105 | // ",I"
106 | 0x2c, 0x49, 0x00, 0x00
107 | ]
108 |
109 | doTestOSCMessage(msg, expectedPacket, ",I")
110 | }
111 |
112 | func testMessageHavingRGBAArgument() {
113 | let msg = OSCMessage(address: "/test", args: [RGBA(red: 0x12, green: 0x34, blue: 0x56, alpha: 0x78)])
114 |
115 | let expectedPacket: [Byte] = [
116 | // "/test"
117 | 0x2f, 0x74, 0x65, 0x73,
118 | 0x74, 0x00, 0x00, 0x00,
119 | // ",r"
120 | 0x2c, 0x72, 0x00, 0x00,
121 | // value in bytes
122 | 0x12, 0x34, 0x56, 0x78
123 | ]
124 |
125 | doTestOSCMessage(msg, expectedPacket, ",r")
126 | }
127 |
128 | func testMessageHavingMIDIArgument() {
129 | let msg = OSCMessage(address: "/test", args: [MIDI(portId: 0x12, status: 0x34, data1: 0x56, data2: 0x78)])
130 |
131 | let expectedPacket: [Byte] = [
132 | // "/test"
133 | 0x2f, 0x74, 0x65, 0x73,
134 | 0x74, 0x00, 0x00, 0x00,
135 | // ",m"
136 | 0x2c, 0x6d, 0x00, 0x00,
137 | // value in bytes
138 | 0x12, 0x34, 0x56, 0x78
139 | ]
140 |
141 | doTestOSCMessage(msg, expectedPacket, ",m")
142 | }
143 |
144 | func testMessageHavingEmptyBlob() {
145 | let bytes: [Byte] = []
146 |
147 | let blob: Data = bytes.withUnsafeBytes {
148 | return Data(bytes: $0.baseAddress!, count: bytes.count)
149 | }
150 | let msg = OSCMessage(address: "/test", args: [blob])
151 |
152 | let expectedPacket: [Byte] = [
153 | // "/test"
154 | 0x2f, 0x74, 0x65, 0x73,
155 | 0x74, 0x00, 0x00, 0x00,
156 | // ",b"
157 | 0x2c, 0x62, 0x00, 0x00,
158 | // value in bytes
159 | 0x00, 0x00, 0x00, 0x00
160 | ]
161 |
162 | doTestOSCMessage(msg, expectedPacket, ",b")
163 | }
164 |
165 | func testMessageHavingBlob() {
166 | let bytes: [Byte] = [0xde, 0xad, 0xba, 0xbe]
167 |
168 | let blob: Data = bytes.withUnsafeBytes {
169 | return Data(bytes: $0.baseAddress!, count: bytes.count)
170 | }
171 |
172 | let msg = OSCMessage(address: "/test", args: [blob])
173 |
174 | let expectedPacket: [Byte] = [
175 | // "/test"
176 | 0x2f, 0x74, 0x65, 0x73,
177 | 0x74, 0x00, 0x00, 0x00,
178 | // ",b"
179 | 0x2c, 0x62, 0x00, 0x00,
180 | // value in bytes
181 | 0x00, 0x00, 0x00, 0x04,
182 | 0xde, 0xad, 0xba, 0xbe
183 | ]
184 |
185 | doTestOSCMessage(msg, expectedPacket, ",b")
186 | }
187 |
188 | func testMessageHavingPaddedBlob() {
189 | let bytes: [Byte] = [0xde, 0xad, 0xba, 0xbe, 0xca, 0xfe, 0xba]
190 |
191 | let blob: Data = bytes.withUnsafeBytes {
192 | return Data(bytes: $0.baseAddress!, count: bytes.count)
193 | }
194 |
195 | let msg = OSCMessage(address: "/test", args: [blob])
196 |
197 | let expectedPacket: [Byte] = [
198 | // "/test"
199 | 0x2f, 0x74, 0x65, 0x73,
200 | 0x74, 0x00, 0x00, 0x00,
201 | // ",b"
202 | 0x2c, 0x62, 0x00, 0x00,
203 | // value in bytes
204 | 0x00, 0x00, 0x00, 0x07,
205 | 0xde, 0xad, 0xba, 0xbe,
206 | 0xca, 0xfe, 0xba, 0x00
207 | ]
208 |
209 | doTestOSCMessage(msg, expectedPacket, ",b")
210 | }
211 |
212 | func testMessageHavingEmptyArray() {
213 | let emptyArray: [OSCMessageArgument?] = [[]]
214 | let msg = OSCMessage(address: "/test", args: emptyArray)
215 |
216 | let expectedPacket: [Byte] = [
217 | // "/test"
218 | 0x2f, 0x74, 0x65, 0x73,
219 | 0x74, 0x00, 0x00, 0x00,
220 | // ",[]"
221 | 0x2c, 0x5b, 0x5d, 0x00
222 | ]
223 |
224 | doTestOSCMessage(msg, expectedPacket, ",[]")
225 | }
226 |
227 | func testMessageHavingArrayOfNoValueArgs() {
228 | let msg = OSCMessage(address: "/test", args: [[true, false, nil]])
229 |
230 | let expectedPacket: [Byte] = [
231 | // "/test"
232 | 0x2f, 0x74, 0x65, 0x73,
233 | 0x74, 0x00, 0x00, 0x00,
234 | // ",[TFN]"
235 | 0x2c, 0x5b, 0x54, 0x46,
236 | 0x4e, 0x5d, 0x00, 0x00
237 | ]
238 |
239 | doTestOSCMessage(msg, expectedPacket, ",[TFN]")
240 |
241 | let msg2 = OSCMessage(address: "/test", args: [true, [false], nil])
242 | let expectedPacket2: [Byte] = [
243 | // "/test"
244 | 0x2f, 0x74, 0x65, 0x73,
245 | 0x74, 0x00, 0x00, 0x00,
246 | // ",T[F]N"
247 | 0x2c, 0x54, 0x5b, 0x46,
248 | 0x5d, 0x4e, 0x00, 0x00
249 | ]
250 |
251 | doTestOSCMessage(msg2, expectedPacket2, ",T[F]N")
252 |
253 | let msg3 = OSCMessage(address: "/test", args: [true, [], false, nil])
254 | let expectedPacket3: [Byte] = [
255 | // "/test"
256 | 0x2f, 0x74, 0x65, 0x73,
257 | 0x74, 0x00, 0x00, 0x00,
258 | // ",T[]FN"
259 | 0x2c, 0x54, 0x5b, 0x5d,
260 | 0x46, 0x4e, 0x00, 0x00
261 | ]
262 |
263 | doTestOSCMessage(msg3, expectedPacket3, ",T[]FN")
264 | }
265 |
266 | private func doTestOSCMessage(_ msg: OSCMessage, _ expectedPacket: [Byte], _ expectedTags: String) {
267 | XCTAssertNotNil(msg.oscValue)
268 |
269 | XCTAssertEqual(expectedTags, msg.typeTags, "Type tags mismatch")
270 |
271 | let convertedPacket = msg.oscValue!
272 |
273 | // check conversion is correct
274 | XCTAssertEqual(expectedPacket, convertedPacket)
275 |
276 | if let otherMsg = OSCMessage(data: convertedPacket) {
277 | XCTAssertEqual(msg.packetSize, convertedPacket.count, "OSC Packet size mismatch")
278 | XCTAssertEqual(msg.address, otherMsg.address, "Address field mismatch")
279 | XCTAssertEqual(msg.args.count, otherMsg.args.count, "Arguments size mismatch")
280 |
281 | for argPair in zip(msg.args, otherMsg.args) {
282 | if let msgVal = argPair.0, let msg2Val = argPair.1 {
283 | XCTAssertEqual(msgVal.oscType, msg2Val.oscType)
284 | // XCTAssertTrue(msgVal.isEqualTo(msg2Val))
285 | } else {
286 | XCTAssertNil(argPair.0)
287 | XCTAssertNil(argPair.1)
288 | }
289 | }
290 | } else {
291 | XCTFail("Failed to build message from bytes")
292 | }
293 | }
294 | }
295 |
296 | #if os(Linux)
297 | extension OSCMessageTests {
298 | static var allTests: [(String, (OSCMessageTests) -> () throws -> Void)] {
299 | return [
300 | ("testNoArgMessage", testNoArgMessage),
301 | ("testSingleArgMessage", testSingleArgMessage),
302 | ("testMessageHavingNilArgument", testMessageHavingNilArgument),
303 | ("testMultipleArgsMessage", testMultipleArgsMessage),
304 | ("testMessageHavingSymbolArgument", testMessageHavingSymbolArgument),
305 | ("testMessageHavingDoubleArgument", testMessageHavingDoubleArgument),
306 | ("testMessageHavingInfinityArgument", testMessageHavingInfinityArgument),
307 | ("testMessageHavingRGBAArgument", testMessageHavingRGBAArgument),
308 | ("testMessageHavingMIDIArgument", testMessageHavingMIDIArgument),
309 | ("testMessageHavingEmptyBlob", testMessageHavingEmptyBlob),
310 | ("testMessageHavingBlob", testMessageHavingBlob),
311 | ("testMessageHavingPaddedBlob", testMessageHavingPaddedBlob),
312 | ("testMessageHavingEmptyArray", testMessageHavingEmptyArray),
313 | ("testMessageHavingArrayOfNoValueArgs", testMessageHavingArrayOfNoValueArgs)
314 | ]
315 | }
316 | }
317 | #endif
318 |
--------------------------------------------------------------------------------