Skip to content

Commit dcf1c2b

Browse files
pseudo rtmidi platform impl
1 parent 2f9b009 commit dcf1c2b

File tree

9 files changed

+257
-42
lines changed

9 files changed

+257
-42
lines changed

Midinette.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bits", "bits", "{4DB40A2C-F
2020
readme.md = readme.md
2121
EndProjectSection
2222
EndProject
23+
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Midinette.Platform.RtMidi", "src\Midinette.Platform.RtMidi\Midinette.Platform.RtMidi.fsproj", "{1BDC05D1-A580-4692-B34D-FEEC335BD163}"
24+
EndProject
2325
Global
2426
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2527
Debug|Any CPU = Debug|Any CPU
@@ -42,6 +44,10 @@ Global
4244
{B7D84F4A-6664-4D07-8689-317CABE09A2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
4345
{B7D84F4A-6664-4D07-8689-317CABE09A2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
4446
{B7D84F4A-6664-4D07-8689-317CABE09A2F}.Release|Any CPU.Build.0 = Release|Any CPU
47+
{1BDC05D1-A580-4692-B34D-FEEC335BD163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48+
{1BDC05D1-A580-4692-B34D-FEEC335BD163}.Debug|Any CPU.Build.0 = Debug|Any CPU
49+
{1BDC05D1-A580-4692-B34D-FEEC335BD163}.Release|Any CPU.ActiveCfg = Release|Any CPU
50+
{1BDC05D1-A580-4692-B34D-FEEC335BD163}.Release|Any CPU.Build.0 = Release|Any CPU
4551
EndGlobalSection
4652
GlobalSection(SolutionProperties) = preSolution
4753
HideSolutionNode = FALSE

paket.dependencies

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ nuget Fable.Import.Browser
1313
nuget Fable.Import.WebMIDI
1414
nuget FSharp.Core
1515
nuget PortMidiSharp
16+
nuget RtMidi.Core
1617

1718
group Build
1819
source https://nuget.org/api/v2

paket.lock

Lines changed: 36 additions & 25 deletions
Large diffs are not rendered by default.

src/Midinette.Platform.PortMidi/PortMidiMidinettePlatformImpl.fs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ open Midi
33
open Midinette.Platform
44

55

6-
module internal Impl =
6+
module Impl =
77
let toMidiEvent (e :PortMidi.MidiEvent) = MidiEvent<int>(Midi.MidiMessage.FromWord e.Message.Word, e.Timestamp)
88
//let toMidiMessage (m: PortMidi.MidiMessage) = Midi.MidiMessage.FromWord m.Word
99
let fromMidiMessage (m: Midi.MidiMessage) = PortMidi.MidiMessage.FromWord m.Word
1010

1111
open Impl
12+
13+
type PortMidiDeviceInfo = { deviceInfo: PortMidi.MidiDeviceInfo }
14+
with
15+
interface IDeviceInfo with
16+
member d.Name = d.deviceInfo.Name
17+
//member d.DeviceId = d.deviceInfo.DeviceId
18+
1219
type PortMidiIn =
1320
{ inPort: PortMidi.MidiInput }
1421
with
@@ -20,9 +27,7 @@ with
2027
[<CLIEvent>] member x.RealtimeMessageReceived = x.inPort.RealtimeMessageReceived |> Event.map toMidiEvent
2128
member x.Open bufferSize = x.inPort.Open bufferSize
2229
member x.Close () = x.inPort.Close ()
23-
member x.DeviceInfo = { new IDeviceInfo with
24-
member d.Name = x.inPort.DeviceInfo.Name
25-
member d.DeviceId = x.inPort.DeviceInfo.DeviceId }
30+
member x.DeviceInfo = { deviceInfo = x.inPort.DeviceInfo } :> _
2631

2732
type PortMidiOut =
2833
{ outPort: PortMidi.MidiOutput }
@@ -33,19 +38,17 @@ with
3338
member x.WriteMessage timestamp message = x.outPort.WriteMessage timestamp (fromMidiMessage message)
3439
member x.WriteMessages timestamp messages = x.outPort.WriteMessages timestamp (messages |> Array.map fromMidiMessage)
3540
member x.WriteSysex timestamp sysex = x.outPort.WriteSysex timestamp sysex
36-
member x.DeviceInfo = { new IDeviceInfo with
37-
member d.Name = x.outPort.DeviceInfo.Name
38-
member d.DeviceId = x.outPort.DeviceInfo.DeviceId }
39-
40-
type PortMidiDeviceInfo = { deviceInfo: PortMidi.MidiDeviceInfo }
41-
with
42-
interface IDeviceInfo with
43-
member d.Name = d.deviceInfo.Name
44-
member d.DeviceId = d.deviceInfo.DeviceId
41+
member x.DeviceInfo = { deviceInfo = x.outPort.DeviceInfo } :> _
42+
43+
4544
type PortMidiMidinettePlatformImpl (platform: PortMidi.MidiPlatformTrigger) =
4645
let platform = platform
4746

4847
new () = PortMidiMidinettePlatformImpl(PortMidi.Runtime.platform)
48+
49+
member x.Events = x :> IMidiPlatformEvents<_,_,_,_>
50+
member x.Platform = x :> IMidiPlatform<_,_,_,_>
51+
4952
interface IMidiPlatformEvents<IDeviceInfo,Midi.MidiEvent<int>,int,Midi.MidiMessage> with
5053
[<CLIEvent>] member x.Error = platform.Error |> Event.map (fun (d,e) -> {deviceInfo = d } :> IDeviceInfo, e)
5154
[<CLIEvent>] member x.ChannelMessageReceived = platform.ChannelMessageReceived |> Event.map (fun (d,e) -> {deviceInfo = d } :> IDeviceInfo, toMidiEvent e)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace Midinette.Platform.RtMidi.AssemblyInfo
2+
3+
open System.Reflection
4+
open System.Runtime.CompilerServices
5+
open System.Runtime.InteropServices
6+
7+
// General Information about an assembly is controlled through the following
8+
// set of attributes. Change these attribute values to modify the information
9+
// associated with an assembly.
10+
[<assembly: AssemblyTitle("Midinette.Platform.RtMidi")>]
11+
[<assembly: AssemblyDescription("")>]
12+
[<assembly: AssemblyConfiguration("")>]
13+
[<assembly: AssemblyCompany("")>]
14+
[<assembly: AssemblyProduct("Midinette.Platform.RtMidi")>]
15+
[<assembly: AssemblyCopyright("Copyright © $year$")>]
16+
[<assembly: AssemblyTrademark("")>]
17+
[<assembly: AssemblyCulture("")>]
18+
19+
// Setting ComVisible to false makes the types in this assembly not visible
20+
// to COM components. If you need to access a type in this assembly from
21+
// COM, set the ComVisible attribute to true on that type.
22+
[<assembly: ComVisible(false)>]
23+
24+
// The following GUID is for the ID of the typelib if this project is exposed to COM
25+
[<assembly: Guid("1BDC05D1-A580-4692-B34D-FEEC335BD163")>]
26+
27+
// Version information for an assembly consists of the following four values:
28+
//
29+
// Major Version
30+
// Minor Version
31+
// Build Number
32+
// Revision
33+
//
34+
// You can specify all the values or you can default the Build and Revision Numbers
35+
// by using the '*' as shown below:
36+
// [<assembly: AssemblyVersion("1.0.*")>]
37+
[<assembly: AssemblyVersion("1.0.0.0")>]
38+
[<assembly: AssemblyFileVersion("1.0.0.0")>]
39+
40+
do
41+
()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.0;net472</TargetFrameworks>
5+
<OutputPath>..\..\build\$(Configuration)\$(Platform)</OutputPath>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<Compile Include="RtMidinettePlatformImpl.fs" />
9+
<Content Include="paket.references" />
10+
</ItemGroup>
11+
<ItemGroup>
12+
<ProjectReference Include="..\Midinette\Midinette.fsproj" />
13+
</ItemGroup>
14+
<Import Project="..\..\.paket\Paket.Restore.targets" />
15+
</Project>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
namespace Midinette.Platform.RtMidi
2+
3+
open System.Reflection
4+
open Midinette.Platform
5+
6+
module Impl =
7+
let getRawDataReceivedEventFromInput port =
8+
let f = port.GetType().GetEvent "Message";
9+
let dataReceived = new Event<byte array>()
10+
let handler = System.EventHandler<byte array>(fun _ -> dataReceived.Trigger)
11+
f.AddEventHandler(port, handler)
12+
dataReceived
13+
14+
let toBytes (message: Midi.MidiMessage) = [|message.Status;message.Data1;message.Data2|]
15+
let rtMidiOut (bytes: byte array) port =
16+
let f = port.GetType().GetField("_outputDevice", BindingFlags.NonPublic ||| BindingFlags.Instance)
17+
let rtoutport = f.GetValue port
18+
let sendmethod = rtoutport.GetType().GetMethod "SendMessage"
19+
let paramters = box bytes
20+
let result = sendmethod.Invoke(rtoutport, Array.singleton paramters) :?> bool
21+
()
22+
23+
open Impl
24+
open Midi
25+
open System.Diagnostics
26+
27+
type InPortEvents<'timestamp> = {
28+
error : Event<string>
29+
channelMessage : Event<Midi.MidiEvent<'timestamp>>
30+
systemMessage: Event<Midi.MidiEvent<'timestamp>>
31+
sysex: Event<byte array>
32+
realtimeMessage: Event<Midi.MidiEvent<'timestamp>>
33+
}
34+
35+
type RtMidiInfo = { info: RtMidi.Core.Devices.Infos.IMidiDeviceInfo }
36+
with
37+
interface IDeviceInfo with
38+
member d.Name = d.info.Name
39+
40+
type RtMidiIn = { inPort: RtMidi.Core.Devices.IMidiInputDevice; info: RtMidiInfo; inEvents: InPortEvents<int64> }
41+
with
42+
interface IMidiInput<Midi.MidiEvent<int64>> with
43+
44+
member x.Close () = x.inPort.Close()
45+
member x.Open bufferSize = x.inPort.Open() |> ignore
46+
member x.DeviceInfo = x.info :> _
47+
[<CLIEvent>] member x.Error = x.inEvents.error.Publish
48+
[<CLIEvent>] member x.ChannelMessageReceived = x.inEvents.channelMessage.Publish
49+
[<CLIEvent>] member x.SystemMessageReceived = x.inEvents.systemMessage.Publish
50+
[<CLIEvent>] member x.SysexReceived = x.inEvents.sysex.Publish
51+
[<CLIEvent>] member x.RealtimeMessageReceived = x.inEvents.realtimeMessage.Publish
52+
53+
type RtMidiOut<'timestamp> = { outPort: RtMidi.Core.Devices.IMidiOutputDevice; info: RtMidiInfo }
54+
with
55+
interface IMidiOutput<'timestamp,Midi.MidiMessage> with
56+
member x.DeviceInfo = x.info :> _
57+
member x.Open bufferSize latency = x.outPort.Open() |> ignore
58+
member x.Close () = x.outPort.Close()
59+
member x.WriteSysex timestamp data = rtMidiOut data x.outPort
60+
member x.WriteMessage timestamp message = rtMidiOut (toBytes message) x.outPort
61+
member x.WriteMessages timestamp messages =
62+
let bytes =
63+
Array.init
64+
(messages.Length * 3)
65+
(fun i ->
66+
let message = messages.[i / 3]
67+
match i % 3 with
68+
| 0 -> message.Status
69+
| 1 -> message.Data1
70+
| 2 -> message.Data2
71+
| _ -> failwithf "index %i not expected???" i
72+
)
73+
rtMidiOut bytes x.outPort
74+
75+
type RtMidiMidinettePlatformImpl() =
76+
let rtmidi = RtMidi.Core.MidiDeviceManager.Default
77+
let watch = Stopwatch.StartNew()
78+
let platformEvents = MidiPlatformTrigger()
79+
80+
interface IMidiPlatform<IDeviceInfo, Midi.MidiEvent<int64>, int64, Midi.MidiMessage> with
81+
member x.InputDevices =
82+
RtMidi.Core.MidiDeviceManager.Default.InputDevices
83+
|> Seq.map (fun d -> { info = d} :> IDeviceInfo)
84+
|> Seq.toArray
85+
member x.OutputDevices =
86+
RtMidi.Core.MidiDeviceManager.Default.InputDevices
87+
|> Seq.map (fun d -> { info = d} :> IDeviceInfo)
88+
|> Seq.toArray
89+
90+
member x.GetMidiInput deviceInfo =
91+
match deviceInfo with
92+
| :? RtMidiInfo as rtDevice ->
93+
match rtDevice.info with
94+
| :? RtMidi.Core.Devices.Infos.IMidiInputDeviceInfo as device ->
95+
// todo: keep that in store instead of creating / subscribing again
96+
let port = device.CreateDevice()
97+
let dataReceived = getRawDataReceivedEventFromInput port
98+
let error = new Event<_>()
99+
let channelMessage = new Event<_>()
100+
let sysex = new Event<_>()
101+
let systemMessage = new Event<_>()
102+
let realtimeMessage = new Event<_>()
103+
dataReceived.Publish.Add(fun bytes ->
104+
let status : MidiMessageType = LanguagePrimitives.EnumOfValue (bytes.[0])
105+
let timestamp = watch.ElapsedTicks
106+
let inline triggerMessageEvent (event1: Event<_>) platformNoticer =
107+
let message = MidiMessage.Encode bytes.[0] bytes.[1] bytes.[2]
108+
let event = Midi.MidiEvent(message, timestamp)
109+
event1.Trigger event
110+
platformNoticer (rtDevice,event)
111+
if MidiMessageTypeIdentifaction.isChannelMessage status then
112+
triggerMessageEvent channelMessage platformEvents.NoticeChannelMessage
113+
elif MidiMessageTypeIdentifaction.isRealtimeMessage status then
114+
triggerMessageEvent realtimeMessage platformEvents.NoticeRealtimeMessage
115+
elif MidiMessageTypeIdentifaction.isSystemMessage status then
116+
triggerMessageEvent systemMessage platformEvents.NoticeSystemMessage
117+
elif MidiMessageTypeIdentifaction.isSysexBeginOrEnd status then
118+
sysex.Trigger bytes
119+
platformEvents.NoticeSysex(rtDevice, bytes)
120+
else
121+
#if DEBUG
122+
failwithf "unable to parse what I received received %A" bytes
123+
#endif
124+
)
125+
let inEvents = {error = error; channelMessage = channelMessage; sysex = sysex; systemMessage = systemMessage; realtimeMessage = realtimeMessage }
126+
Some ({ inPort = port; info = rtDevice; inEvents = inEvents } :> _)
127+
| _ -> None
128+
| _ -> None
129+
member x.GetMidiOutput deviceInfo =
130+
match deviceInfo with
131+
| :? RtMidiInfo as rtDevice ->
132+
match rtDevice.info with
133+
| :? RtMidi.Core.Devices.Infos.IMidiOutputDeviceInfo as device ->
134+
Some ({ outPort = device.CreateDevice() ; info = rtDevice} :> IMidiOutput<_,_>)
135+
| _ -> None
136+
| _ -> None
137+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RtMidi.Core

src/Midinette/Midi.Sysex.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ module Sysex =
4545
elif data.[i] = 0xf7uy then yield getSlice beginIndex (i - beginIndex + 1) data
4646
|]
4747

48-
type DetectedDevice<'timestamp,'midievent> =
48+
type DetectedDevice<'timestamp,'midievent,'midimessage> =
4949

50-
| DetectedDevice of responseData: byte array * deviceOutput: IMidiInput<'timestamp> * deviceInput: IMidiOutput<'timestamp,'midievent>
51-
| Error of exn * deviceOutput: IMidiInput<'timestamp> * deviceInput: IMidiOutput<'timestamp,'midievent>
50+
| DetectedDevice of responseData: byte array * deviceOutput: IMidiInput<'midievent> * deviceInput: IMidiOutput<'timestamp,'midimessage>
51+
| Error of exn * deviceOutput: IMidiInput<'midievent> * deviceInput: IMidiOutput<'timestamp,'midimessage>
5252

53-
let deviceInquiry (inputPorts: IMidiInput<_> array) (outputPorts: IMidiOutput<_,_> array) sysexMatcher doWithOutput withInputAndOutput =
53+
let deviceInquiry (inputPorts: IMidiInput<'midievent> array) (outputPorts: IMidiOutput<'timestamp,'midimessage> array) sysexMatcher doWithOutput withInputAndOutput =
5454
// maybe buggy
5555
let detectedPairs = [|
5656
let sysexInputTimeout = System.TimeSpan.FromSeconds 5.0

0 commit comments

Comments
 (0)