summaryrefslogtreecommitdiff
path: root/VRCSDK3Worlds/Assets/VRCSDK/SDK3/Runtime/Midi/VRCPortMidi.cs
blob: e4a6200bb78bfcb54b55a1b8b56c46d143140d0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#if (UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN) && !UNITY_ANDROID
using System;
using System.Collections.Generic;
using System.Linq;
#if VRC_CLIENT
using VRC.Core;
#endif
using PortMidi;
using UnityEngine;
using VRC.SDK3.Midi;


namespace VRC.SDKBase.Midi
{
    public class VRCPortMidiInput: IVRCMidiInput
    {

        private MidiInput _input;
        private byte[] _data;
        private MidiDeviceInfo _info;
        
        public bool OpenDevice(string name = null)
        {
            try
            {
                if (!string.IsNullOrWhiteSpace(name))
                {
                    _info = MidiDeviceManager.AllDevices.FirstOrDefault(device =>
                        device.IsInput && device.Name.ToLower().Contains(name.ToLower()));
                }
                else
                {
                    _info = MidiDeviceManager.AllDevices.FirstOrDefault(device =>
                        device.ID == MidiDeviceManager.DefaultInputDeviceId);
                }
                
                _input = MidiDeviceManager.OpenInput(_info.ID);
                _input.SetFilter(MidiFilter.Active | MidiFilter.SysEx | MidiFilter.Clock | MidiFilter.Play | MidiFilter.Tick | MidiFilter.Undefined | MidiFilter.Reset | MidiFilter.RealTime | MidiFilter.AF | MidiFilter.Program | MidiFilter.PitchBend | MidiFilter.SystemCommon);
                _data = new byte[1024];
                return true;
            }
            catch (Exception e)
            {
                #if VRC_CLIENT
                VRC.Core.Logger.LogError($"Error opening Default Device: {e.Message}");
                #else
                Debug.Log($"Error opening Default Device: {e.Message}");
                #endif
                return false;
            }
        }

        public void Close()
        {
            if (_input != null)
            {
                _input.Close();
            }
        }

        public void Update()
        {
            if (_input == null) return;
            
            if (_input.HasData)
            {
                // Portmidi reports 4 bytes per event but the buffer has 8 bytes so we multiply count by 2
                int count = (_input.Read(_data, 0, _data.Length)) * 2;
                for (int i = 0; i < count; i+=8)
                {
                    ConvertAndSend(_data[i], _data[i + 1], _data[i + 2]);
                }
            }
        }

        public IEnumerable<string> GetDeviceNames()
        {
            return MidiDeviceManager.AllDevices.Select(d => d.Name);
        }

        private void ConvertAndSend(byte status, byte data1, byte data2)
        {
            var command = status & 0xF0;  // mask off all but top 4 bits

            if (command >= 0x80 && command <= 0xE0) {
                // it's a voice message
                // find the channel by masking off all but the low 4 bits
                var channel = status & 0x0F;
                
                if (command == VRCMidiHandler.STATUS_NOTE_ON || command == VRCMidiHandler.STATUS_NOTE_OFF || command == VRCMidiHandler.STATUS_CONTROL_CHANGE)
                {
                    OnMidiVoiceMessage?.Invoke(this, new MidiVoiceEventArgs(command, channel, data1, data2));
                }
                else
                {
#if VRC_CLIENT
                VRC.Core.Logger.Log($"command:{command} channel:{channel} data1:{data1} data2:{data2}");
#else
                Debug.Log($"command:{command} channel:{channel} data1:{data1} data2:{data2}");
#endif
                }

            }
            else
            {
                // it's a system message, ignore for now
                OnMidiRawMessage?.Invoke(this, new MidiRawEventArgs(status, data1, data2));
            }
        }

        public string Name => _info.Name;
        public event MidiVoiceMessageDelegate OnMidiVoiceMessage;
        public event MidiRawMessageDelegate OnMidiRawMessage;
    }
}
#endif