Skip to content

Commit

Permalink
Merge pull request #44 from orchetect/dev
Browse files Browse the repository at this point in the history
MIDI events refactoring
  • Loading branch information
orchetect authored Oct 7, 2021
2 parents 0d90991 + 276dee8 commit 8371091
Show file tree
Hide file tree
Showing 128 changed files with 8,146 additions and 1,772 deletions.
249 changes: 249 additions & 0 deletions Docs/Events/Event Filters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# MIDIKit Event Filters

### Table of Contents

- [Channel Voice](#Channel-Voice)
- [System Common](#System-Common)
- [System Exclusive](#System-Exclusive)
- [System Real Time](#System-Real-Time)
- [UMP Group](#UMP-Group)

### Summary

Filters are available as category methods on `[MIDI.Event]`.

For example:

```swift
let events: [MIDI.Event] = [ ... ]

let onlyChannel0Events = events.filter(chanVoice: .onlyChannel(0))
```

## Channel Voice

There are three main categories of Channel Voice event filters:

- **only**: retains only events matching the criteria
- **keep**: keeps Channel Voice events matching the criteria, while all non-Channel Voice events are retained
- **drop**: drops Channel Voice events matching the criteria, while all non-Channel Voice events are retained

Channel Voice filter sub-type(s) available:

```swift
.noteOn
.noteOff
.noteCC
.notePitchBend
.notePressure
.noteManagement
.cc
.programChange
.pressure
.pitchBend
```

### "Only"

`.filter(chanVoice: .only*)` methods:

- retains only Channel Voice events matching the criteria

```swift
// return only Channel Voice events
.filter(chanVoice: .only)
```

```swift
// return only certain event type(s)
.filter(chanVoice: .onlyType(.noteOn))
.filter(chanVoice: .onlyTypes([.noteOn, .noteOff]))
```

```swift
// return only events on certain channel(s)
.filter(chanVoice: .onlyChannel(0))
.filter(chanVoice: .onlyChannels([0, 1, 2]))
```

```swift
// return only CC events matching certain controller(s)
.filter(chanVoice: .onlyCC(1))
.filter(chanVoice: .onlyCCs([1, 11, 64]))
```

```swift
// return only note on/off events within certain note number range(s)
.filter(chanVoice: .onlyNotesInRange(40...80))
.filter(chanVoice: .onlyNotesInRanges([20...40, 60...80]))
```

### "Keep"

`.filter(chanVoice: .keep*)` methods:

- retains Channel Voice events matching the given criteria
- retains all non-Channel Voice events

```swift
// retains Channel Voice events only with certain type(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .keepType(.noteOn))
.filter(chanVoice: .keepTypes([.noteOn, .noteOff]))
```

```swift
// retains Channel Voice events only with certain channel(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .keepChannel(0))
.filter(chanVoice: .keepChannels([0, 1, 2]))
```

```swift
// retains only CC events with certain controller(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .keepCC(1))
.filter(chanVoice: .keepCCs([1, 11, 64]))
```

```swift
// retains only note on/off events within certain note ranges(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .keepNotesInRange(40...80))
.filter(chanVoice: .keepNotesInRanges([20...40, 60...80]))
```

### "Drop"

`.filter(chanVoice: .drop*)` methods:

- filter any Channel Voice events by the given criteria
- do not affect non-Channel Voice events

```swift
// drop all Channel Voice events,
// while retaining all non-Channel Voice events
.filter(chanVoice: .drop)
```

```swift
// drop Channel Voice events only with certain type(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .dropType(.noteOn))
.filter(chanVoice: .dropTypes([.noteOn, .noteOff]))
```

```swift
// drop Channel Voice events only with certain channel(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .dropChannel(0))
.filter(chanVoice: .dropChannels([0, 1, 2]))
```

```swift
// drop CC events with certain controller(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .dropCC(1))
.filter(chanVoice: .dropCCs([1, 11, 64]))
```

```swift
// drop note on/off events within certain note ranges(s),
// while retaining all non-Channel Voice events
.filter(chanVoice: .dropNotesInRange(40...80))
.filter(chanVoice: .dropNotesInRanges([20...40, 60...80]))
```

## System Common

### "Only"

```swift
.filter(sysCommon: .only)
.filter(sysCommon: .onlyType(.songSelect))
.filter(sysCommon: .onlyTypes([.songSelect, .tuneRequest]))
```

### "Keep"

```swift
.filter(sysCommon: .keepType(.songSelect))
.filter(sysCommon: .keepTypes([.songSelect, .tuneRequest]))
```

### "Drop"

```swift
.filter(sysCommon: .drop)
.filter(sysCommon: .dropType(.songSelect))
.filter(sysCommon: .dropTypes([.songSelect, .tuneRequest]))
```

## System Exclusive

### "Only"

```swift
.filter(sysEx: .only)
.filter(sysEx: .onlyType(.sysEx))
.filter(sysEx: .onlyTypes([.sysEx, .universalSysEx]))
```

### "Keep"

```swift
.filter(sysEx: .keepType(.sysEx))
.filter(sysEx: .keepTypes([.sysEx, .universalSysEx]))
```

### "Drop"

```swift
.filter(sysEx: .drop)
.filter(sysEx: .dropType(.sysEx))
.filter(sysEx: .dropTypes([.sysEx, .universalSysEx]))
```

## System Real Time

### "Only"

```swift
.filter(sysRealTime: .only)
.filter(sysRealTime: .onlyType(.timingClock))
.filter(sysRealTime: .onlyTypes([.start, .stop, .continue]))
```

### "Keep"

```swift
.filter(sysRealTime: .keepType(.timingClock))
.filter(sysRealTime: .keepTypes([.start, .stop, .continue]))
```

### "Drop"

```swift
.filter(sysRealTime: .drop)
.filter(sysRealTime: .dropType(.activeSensing))
.filter(sysRealTime: .dropTypes([.activeSensing, .timingClock]))
```

## UMP Group

### Filter

```swift
// retains only events in the given UMP group(s)
.filter(group: 0)
.filter(groups: [0, 1])
```

### Drop

```swift
// drops events in the given UMP group(s)
.drop(group: 0)
.drop(groups: [0, 1])
```

2 changes: 1 addition & 1 deletion Examples/MIDIEventLogger/MIDIEventLogger/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
Log.default(error)
}

#warning("> remove this")
#warning("> TODO: remove this")
//newManager.preferredAPI = .newCoreMIDI

return newManager
Expand Down
23 changes: 12 additions & 11 deletions Examples/MIDIEventLogger/MIDIEventLogger/ContentView SubViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ extension ContentView {
} label: { Text("Note Off") }

Button {
sendEvent(.polyAftertouch(note: 60,
pressure: 64,
channel: midiChannel,
group: midiGroup))
} label: { Text("Poly Aftertouch") }
sendEvent(.notePressure(note: 60,
amount: .midi1(64),
channel: midiChannel,
group: midiGroup))
} label: { Text("Note Pressure") }

HStack(alignment: .center, spacing: 4) {
Button {
Expand Down Expand Up @@ -136,13 +136,13 @@ extension ContentView {
} label: { Text("Program Change") }

Button {
sendEvent(.chanAftertouch(pressure: 64,
channel: midiChannel,
group: midiGroup))
} label: { Text("Channel Aftertouch") }
sendEvent(.pressure(amount: .midi1(64),
channel: midiChannel,
group: midiGroup))
} label: { Text("Channel Pressure") }

Button {
sendEvent(.pitchBend(value: .midpoint,
sendEvent(.pitchBend(value: .midi1(.midpoint),
channel: midiChannel,
group: midiGroup))
} label: { Text("PitchBend") }
Expand Down Expand Up @@ -249,7 +249,8 @@ extension ContentView {
} label: { Text("Song Select") }

Button {
sendEvent(.unofficialBusSelect(group: midiGroup))
sendEvent(.unofficialBusSelect(bus: 2,
group: midiGroup))
} label: { Text("Bus Select (Unofficial)") }

Button {
Expand Down
4 changes: 2 additions & 2 deletions Examples/MIDIEventLogger/MIDIEventLogger/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ struct ContentView: View {
// check for existing connection and compare new selection against it
if let ic = midiManager.managedInputConnections[kInputConnectionTag] {
// if endpoint is the same, don't reconnect
if ic.endpoint == midiInputConnectionEndpoint {
if ic.endpoints.first == midiInputConnectionEndpoint {
Log.debug("Already connected.")
return
}
Expand All @@ -137,7 +137,7 @@ struct ContentView: View {
Log.debug("Setting up new input connection to \(endpointName).")
do {
try midiManager.addInputConnection(
toOutput: .uniqueID(endpoint.uniqueID),
toOutputs: [.uniqueID(endpoint.uniqueID)],
tag: kInputConnectionTag,
receiveHandler: .eventsLogging()
)
Expand Down
7 changes: 3 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ let package = Package(
.target(
name: "MIDIKitC",
publicHeadersPath: "."
// cxxSettings: [
// .headerSearchPath(".")
// ]
),

.target(name: "XCTestExtensions"),
Expand All @@ -48,6 +45,8 @@ let package = Package(
.target(name: "XCTestExtensions")
]
)
]
],

swiftLanguageVersions: [.v5]

)
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ An elegant and modern Swift CoreMIDI wrapper with:
- (ie: a single 'note on' event & its value types are homogenous between MIDI 1.0 and 2.0)
- event filters
- easily filter or drop events by message type, channel, CC number, UMP group, and more
- seamless unified API across **macOS 10.12 Sierra** and later (including **macOS 12.0 Monterey**)
- seamless unified API across **iOS 10** and later (including **iOS 15**)
- beta support for **tvOS** and **watchOS** in development

Back-porting a limited subset of MIDI 2.0 features to older platforms (macOS Catalina or older, and iOS 13 or older) is planned, but not yet supported.
- seamless unified API, transparently adopting newer Core MIDI API and MIDI 2.0 on platforms that support them

## MIDIKit Extensions

Expand All @@ -27,7 +23,7 @@ Abstractions are built as optional extensions in their own repos.
- In an app project or framework, in Xcode:
- Select the menu: **File → Swift Packages → Add Package Dependency...**
- Enter this URL: `https://github.com/orchetect/MIDIKit`

- In a Swift Package, add it to the Package.swift dependencies:
```swift
.package(url: "https://github.com/orchetect/MIDIKit", from: "0.2.0")
Expand All @@ -40,10 +36,19 @@ Abstractions are built as optional extensions in their own repos.

3. See [Examples](https://github.com/orchetect/MIDIKit/blob/master/Examples/) folder and [Docs](https://github.com/orchetect/MIDIKit/blob/master/Docs/) folder for usage.

## Known Issues

- MIDI 2.0 implementation on supported OS versions is still in active development and will be available in a future MIDIKit update. For the time being, MIDIKit will internally default to MIDI 1.0 which is fully operational.
- Back-porting a limited subset of MIDI 2.0 features to older platforms (macOS Catalina or older, and iOS 13 or older) is planned, but not yet supported.

- Beta support for **tvOS 14+** and **watchOS 7+** in development and is planned to be added in future.

## Documentation

See [Docs](https://github.com/orchetect/MIDIKit/blob/master/Docs/) folder.

Also see project [Examples](https://github.com/orchetect/MIDIKit/blob/master/Examples/) folder.

## Author

Coded by a bunch of 🐹 hamsters in a trenchcoat that calls itself [@orchetect](https://github.com/orchetect).
Expand Down
Loading

0 comments on commit 8371091

Please sign in to comment.