What's new

Windows: Use touchpad (e.g. Apple Magic Trackpad) as flexible MIDI controller

porrasm

Active Member
This is currently a work in progress. Windows only for now, sorry :(

This app only support Windows Precision Touchpads. (For example the Logitech T650 is not a Windows Precision Touchpad, so it doesn't work. Apple Trackpad works)

I made an application which transforms touchpad input to MIDI input. Here is a video demo. Excuse my poor playing :laugh: I'm still getting used to the touchpad.



I will release it for Windows, for free, once I have time to finish it (may be a couple of weeks because I'm leaving for a trip soon).

In the demo the Y axis of the first finger is always dynamics and the Y axis of the second finger is always the vibrato. So you could use either your index finger or the ring finger as the dynamics, depending on in which order you start touching the touchpad.

Features:
- assign different fingers to different CC values (e.g. left most finger is CC 1 and right most CC2 or first finger pressed is CC100 and second finger pressed is CC101). Should work for as many fingers as the touchpad supports. Apple Magic Trackpad supports 5+
- assign different partitions for the touchpad (e.g. left side is CC1 and right side is CC2).
- XY slider support
- Assign a MIDI CC and a MIDI channel. You can assign the output range of the CC values as well (e.g. 0-127 or 50-100)

For example you could partition the touchpad into 4 different XY sliders such that the top left, top right, bottom left and bottom right areas are different XY sliders, totaling 8 different CC channels. Of course each area can have the multiple finger support as well, so with 4 partitions and 3 fingers, each using the X and Y axes, you could controls 24 different CC values with this app (not useful probably).

Alternatively you could setup e.g. 12 different Y sliders next to each other.

One use case I found was that I assigned my first finger Y axis to dynamics and X axis to vibrato. Then I assigned the second pressed finger Y axis to flutter, giving me control of dynamic, vibrato and flutter all with 1 hand using 2 fingers.

The app is buggy right now and lacks the feature to edit the configuration without touching the code. Once the bugs are fixed this should be ready.

If you know how to code in C# you can start to use it already: https://github.com/porrasm/touchpad-midi-controller
 
Last edited:
This would be very useful. I have a Logitech wireless touchpad and that would be so interesting to be able to use it as a MIDI controller.
Color me very interested.
It runs on Windows. There is a small problem that I still have to fix(shouldn’t cause problems in normal usage). Otherwise it’s finished. The GUI is quite bad and configuring it must be done using manual editing of JSON configuration files (which is easy if you know how to code).

If you want a working version now, I can instruct you how to set it up (and help with the configs). But it only runs on Windows (and supports any Windows precision touchpad including Apple trackpads)


PS: I’ve been quit busy which is why it has not progressed but it is basically ready to use.
 
It runs on Windows. There is a small problem that I still have to fix(shouldn’t cause problems in normal usage). Otherwise it’s finished. The GUI is quite bad and configuring it must be done using manual editing of JSON configuration files (which is easy if you know how to code).

If you want a working version now, I can instruct you how to set it up (and help with the configs). But it only runs on Windows (and supports any Windows precision touchpad including Apple trackpads)


PS: I’ve been quit busy which is why it has not progressed but it is basically ready to use.
I'd definitely be interested in trying your program, and editing a JSON is no problem.
 
Here is the release of this app: https://github.com/porrasm/touchpad-midi-controller/releases/tag/v0.1

I recommend downloading the .Net runtime (not required though) so you can use the smaller release version, otherwise you have to download the larger, self contained release.

On the readme in https://github.com/porrasm/touchpad-midi-controller there is a description of the config JSON type to which you can refer to. Also there are examples and other useful information.

If you have any questions (e.g. how to implement a config) I'll be happy to answer them.

Here is the config that's used in the video. You can easily copy & paste this and just change the "midiCC" values in this config.


JSON:
{
  "name": "XY Dynamics & Vibrato w/ aux",
  "partitions": [
    {
      "xMin": 0,
      "xMax": 7612,
      "yMin": 0,
      "yMax": 5065,
      "fingerOrdering": "pressOrder",
      "fingers": [
        {
          "xAxis": {
            "midiCC": 57,
            "midiChannel": 1,
            "minCC": 0,
            "maxCC": 127,
            "invertValue": false,
            "defaultValue": -1
          },
          "yAxis": {
            "midiCC": 61,
            "midiChannel": 1,
            "minCC": 0,
            "maxCC": 127,
            "invertValue": true,
            "defaultValue": -1
          },
          "xSwipe": null,
          "ySwipe": null,
          "pairings": [
            {
              "positionFilter": "topLeft",
              "finger": {
                "xAxis": null,
                "yAxis": null,
                "xSwipe": null,
                "ySwipe": {
                  "sensitivity": 0.03,
                  "midiCC": 49,
                  "midiChannel": 1,
                  "minCC": 0,
                  "maxCC": 127,
                  "invertValue": true,
                  "defaultValue": 0
                },
                "pairings": null
              }
            },
            {
              "positionFilter": "topRight",
              "finger": {
                "xAxis": null,
                "yAxis": null,
                "xSwipe": null,
                "ySwipe": {
                  "sensitivity": 0.03,
                  "midiCC": 3,
                  "midiChannel": 1,
                  "minCC": 0,
                  "maxCC": 127,
                  "invertValue": true,
                  "defaultValue": 0
                },
                "pairings": null
              }
            },
            {
              "positionFilter": "bottom",
              "finger": {
                "xAxis": null,
                "yAxis": null,
                "xSwipe": {
                  "sensitivity": 0.03,
                  "midiCC": 60,
                  "midiChannel": 1,
                  "minCC": 0,
                  "maxCC": 127,
                  "invertValue": false,
                  "defaultValue": 63
                },
                "ySwipe": null,
                "pairings": null
              }
            }
          ]
        }
      ]
    }
  ]
}
 
PS: I've only ever tested this using the Apple Magic Trackpad, so I have no clue how it works with other trackpads. Please tell me if there are any issues.

PS2: The GUI sucks.

PS3: You need a virtual MIDI device such loopbe1 or loopmidi

If you have any issues please let me know! I'll be going now but I'll answer questions later on this thread.
 
Here is the release of this app: https://github.com/porrasm/touchpad-midi-controller/releases/tag/v0.1

I recommend downloading the .Net runtime (not required though) so you can use the smaller release version, otherwise you have to download the larger, self contained release.

On the readme in https://github.com/porrasm/touchpad-midi-controller there is a description of the config JSON type to which you can refer to. Also there are examples and other useful information.

If you have any questions (e.g. how to implement a config) I'll be happy to answer them.

Here is the config that's used in the video. You can easily copy & paste this and just change the "midiCC" values in this config.


JSON:
{
  "name": "XY Dynamics & Vibrato w/ aux",
  "partitions": [
    {
      "xMin": 0,
      "xMax": 7612,
      "yMin": 0,
      "yMax": 5065,
      "fingerOrdering": "pressOrder",
      "fingers": [
        {
          "xAxis": {
            "midiCC": 57,
            "midiChannel": 1,
            "minCC": 0,
            "maxCC": 127,
            "invertValue": false,
            "defaultValue": -1
          },
          "yAxis": {
            "midiCC": 61,
            "midiChannel": 1,
            "minCC": 0,
            "maxCC": 127,
            "invertValue": true,
            "defaultValue": -1
          },
          "xSwipe": null,
          "ySwipe": null,
          "pairings": [
            {
              "positionFilter": "topLeft",
              "finger": {
                "xAxis": null,
                "yAxis": null,
                "xSwipe": null,
                "ySwipe": {
                  "sensitivity": 0.03,
                  "midiCC": 49,
                  "midiChannel": 1,
                  "minCC": 0,
                  "maxCC": 127,
                  "invertValue": true,
                  "defaultValue": 0
                },
                "pairings": null
              }
            },
            {
              "positionFilter": "topRight",
              "finger": {
                "xAxis": null,
                "yAxis": null,
                "xSwipe": null,
                "ySwipe": {
                  "sensitivity": 0.03,
                  "midiCC": 3,
                  "midiChannel": 1,
                  "minCC": 0,
                  "maxCC": 127,
                  "invertValue": true,
                  "defaultValue": 0
                },
                "pairings": null
              }
            },
            {
              "positionFilter": "bottom",
              "finger": {
                "xAxis": null,
                "yAxis": null,
                "xSwipe": {
                  "sensitivity": 0.03,
                  "midiCC": 60,
                  "midiChannel": 1,
                  "minCC": 0,
                  "maxCC": 127,
                  "invertValue": false,
                  "defaultValue": 63
                },
                "ySwipe": null,
                "pairings": null
              }
            }
          ]
        }
      ]
    }
  ]
}
THANK YOU!
So when I tried downloading the "Self Contained" version it seemed that "Edge" (I know), would not download it indicating a Virus BUT, I ran the link through Virus Total and it came back clean, so I have downloaded the other file.
Now I am just waiting for my touchpad to charge up and we shall see what happens.

Joyfully,
Simeon
 
PS: I've only ever tested this using the Apple Magic Trackpad, so I have no clue how it works with other trackpads. Please tell me if there are any issues.

PS2: The GUI sucks.

PS3: You need a virtual MIDI device such loopbe1 or loopmidi

If you have any issues please let me know! I'll be going now but I'll answer questions later on this thread.
Just an update:
So my touchpad is a Logitech T650 which comes to find out is locked into Logitech's proprietary system so the result is that Windows does not recognize it as a Precision Multitouch Surface.
I might investigate picking up a Magic Trackpad, so we shall see.
Regardless of my situation I think this is a very worthy experiment in seeing where this type of control might be able to take us.
Thanks again for your stepping out into this.
Joyfully,
Simeon
 
This looks super useful, @porrasm . Thanks for putting it out there!

I've tried it today (with a Apple Magic Trackpad + the driver you recommend), but unfortunately it crashes once I use the trackpad:


Code:
C:\Users\-\Downloads\touchpad-midi-controller>touchpad-midi-controller.exe

Selected MIDI device: lightguide
Midi device not found: LoopBe Internal MIDI
Selected configuration: Default (Dynamics & Expression)
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at RawInput.Touchpad.Midi.MIDIPlayer.Dispose()
   at RawInput.Touchpad.Touchpad.StopListening()
   at RawInput.Touchpad.Touchpad.Restart()
   at RawInput.Touchpad.ApplicationState.Save(ApplicationState state)
   at RawInput.Touchpad.MainWindow.<>c__DisplayClass25_0.<Act_SelectConfiguration>b__0(Int32 index)
   at RawInput.Touchpad.MultiOptionSelector.optionsList_SelectionChanged(Object sender, SelectionChangedEventArgs e)
   at System.Windows.Controls.SelectionChangedEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.Controls.ListBox.OnSelectionChanged(SelectionChangedEventArgs e)
   at System.Windows.Controls.Primitives.Selector.InvokeSelectionChanged(List`1 unselectedInfos, List`1 selectedInfos)
   at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
   at System.Windows.Controls.Primitives.Selector.SetSelectedHelper(Object item, FrameworkElement UI, Boolean selected)
   at System.Windows.Controls.Primitives.Selector.NotifyIsSelectedChanged(FrameworkElement container, Boolean selected, RoutedEventArgs e)
   at System.Windows.Controls.Primitives.Selector.OnSelected(Object sender, RoutedEventArgs e)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.Controls.ListBoxItem.OnSelected(RoutedEventArgs e)
   at System.Windows.Controls.ListBoxItem.OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
   at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
   at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
   at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
   at System.Windows.DependencyObject.SetCurrentValueInternal(DependencyProperty dp, Object value)
   at System.Windows.Controls.ListBox.NotifyListItemClicked(ListBoxItem item, MouseButton mouseButton)
   at System.Windows.Controls.ListBoxItem.HandleMouseButtonDown(MouseButton mouseButton)
   at System.Windows.Controls.ListBoxItem.OnMouseLeftButtonDown(MouseButtonEventArgs e)
   at System.Windows.UIElement.OnMouseLeftButtonDownThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
   at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Window.ShowHelper(Object booleanBox)
   at System.Windows.Window.Show()
   at System.Windows.Window.ShowDialog()
   at RawInput.Touchpad.MainWindow.Act_SelectConfiguration(Object sender, RoutedEventArgs e)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.Controls.Primitives.ButtonBase.OnClick()
   at System.Windows.Controls.Button.OnClick()
   at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
   at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
   at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
   at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run()
   at RawInput.Touchpad.App.Main()
 
Ok, I found out that it works when using "LoopBe Internal MIDI" instead of my loopMIDI devices.

Is there any way to disable the mouse movement / interaction when MIDI output is active?
 
Just a quick idea here... I'm using AudioSwift on the Mac, but this can work for anything trackpad. I created my own custom graphic and had it produced as a trackpad 'skin' by SlickWraps. Cost about $20. I'm hoping it just helps me with positioning of my fingers as I use AudioSwift in various modes. I haven't tried it yet, but can see if being useful every once in one of the it's 'other' modes besides CC sliders. And it's just kinda fun :) I went with simple primary colors but you could create something that matches your own space, your DAW colors, whatever - it's fully custom.
 

Attachments

  • IMG_0423.jpeg
    IMG_0423.jpeg
    38.1 KB · Views: 9
  • IMG_0422.jpeg
    IMG_0422.jpeg
    31.4 KB · Views: 9
Ok, I found out that it works when using "LoopBe Internal MIDI" instead of my loopMIDI devices.

Is there any way to disable the mouse movement / interaction when MIDI output is active?
So you got it working? Unfortunately disabling the mouse while using it is not possible right now. I have an idea how to fix that but it would require some work.
 
Just a quick idea here... I'm using AudioSwift on the Mac, but this can work for anything trackpad. I created my own custom graphic and had it produced as a trackpad 'skin' by SlickWraps. Cost about $20. I'm hoping it just helps me with positioning of my fingers as I use AudioSwift in various modes. I haven't tried it yet, but can see if being useful every once in one of the it's 'other' modes besides CC sliders. And it's just kinda fun :) I went with simple primary colors but you could create something that matches your own space, your DAW colors, whatever - it's fully custom.
That’s really cool! Is that something that you’d be willing to share?
 
Top Bottom