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