add basic support for the griffin powermate (a control surface) (from ben loftis)
authorPaul Davis <paul@linuxaudiosystems.com>
Thu, 28 Jun 2007 19:35:48 +0000 (19:35 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Thu, 28 Jun 2007 19:35:48 +0000 (19:35 +0000)
git-svn-id: svn://localhost/ardour2/trunk@2078 d708f5d6-7413-0410-9779-e7cbd77b26cf

15 files changed:
SConstruct
gtk2_ardour/ladspa_pluginui.cc
gtk2_ardour/latency_gui.h
gtk2_ardour/plugin_ui.cc
gtk2_ardour/plugin_ui.h
gtk2_ardour/processor_box.cc
gtk2_ardour/route_params_ui.cc
libs/surfaces/powermate/README [new file with mode: 0644]
libs/surfaces/powermate/SConscript [new file with mode: 0644]
libs/surfaces/powermate/interface.cc [new file with mode: 0644]
libs/surfaces/powermate/interface.os [new file with mode: 0644]
libs/surfaces/powermate/libardour_powermate.so [new file with mode: 0755]
libs/surfaces/powermate/powermate.cc [new file with mode: 0644]
libs/surfaces/powermate/powermate.h [new file with mode: 0644]
libs/surfaces/powermate/powermate.os [new file with mode: 0644]

index 4dc0c8274beae7a24ee00391f05130d7c1745420..069136d427afb983c2cc0c699774291044e93c94 100644 (file)
@@ -989,7 +989,12 @@ else:
 #   its included in the tarball
 #
 
-surface_subdirs = [ 'libs/surfaces/control_protocol', 'libs/surfaces/generic_midi', 'libs/surfaces/tranzport', 'libs/surfaces/mackie' ]
+surface_subdirs = [ 'libs/surfaces/control_protocol',
+                    'libs/surfaces/generic_midi',
+                    'libs/surfaces/tranzport',
+                    'libs/surfaces/mackie',
+                    'libs/surfaces/powermate'
+                    ]
 
 if env['SURFACES']:
     if have_libusb:
index 3d516d30561ddda0633b92c2ab1a9840d3e38ef6..721e50753ad1547f6a713d1302adfd45f3c3c676 100644 (file)
@@ -56,8 +56,8 @@ using namespace Gtkmm2ext;
 using namespace Gtk;
 using namespace sigc;
 
-LadspaPluginUI::LadspaPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrollable)
-       : PlugUIBase (pi),
+LadspaPluginUI::LadspaPluginUI (boost::shared_ptr<PluginInsert> pi, nframes64_t sample_rate, nframes64_t period_size, bool scrollable)
+       : PlugUIBase (pi, sample_rate, period_size),
          button_table (initial_button_rows, initial_button_cols),
          output_table (initial_output_rows, initial_output_cols),
          hAdjustment(0.0, 0.0, 0.0),
@@ -77,7 +77,11 @@ LadspaPluginUI::LadspaPluginUI (boost::shared_ptr<PluginInsert> pi, bool scrolla
        Label* combo_label = manage (new Label (_("<span size=\"large\">Presets</span>")));
        combo_label->set_use_markup (true);
 
-       smaller_hbox->pack_start (*combo_label, false, false, 10);
+       Label* latency_label = manage (new Label (_("<span size=\"large\">Latency</span>")));
+       latency_label->set_use_markup (true);
+
+       smaller_hbox->pack_start (*latency_label, false, false, 10);
+       smaller_hbox->pack_start (latency_gui, false, false, 10);
        smaller_hbox->pack_start (combo, false, false);
        smaller_hbox->pack_start (save_button, false, false);
 
index 49f22fa2664c4128b9210103adb6fca360f99489..0be7fdbf4c422283c114944f0e2fba1fbd6de108 100644 (file)
@@ -1,3 +1,6 @@
+#ifndef __gtk2_ardour_latency_gui_h__
+#define __gtk2_ardour_latency_gui_h__
+
 #include <vector>
 #include <string>
 
@@ -59,3 +62,5 @@ class LatencyDialog : public ArdourDialog
   private:
        LatencyGUI lwidget;
 };
+
+#endif /* __gtk2_ardour_latency_gui_h__ */
index 80bd9ba6f336d4e71a840ec34977d8470e2fc877..935fc6000d6134de5f6fa43250a51a652e667142 100644 (file)
@@ -61,7 +61,7 @@ using namespace Gtkmm2ext;
 using namespace Gtk;
 using namespace sigc;
 
-PluginUIWindow::PluginUIWindow (boost::shared_ptr<PluginInsert> insert, bool scrollable)
+PluginUIWindow::PluginUIWindow (boost::shared_ptr<PluginInsert> insert, nframes64_t sr, nframes64_t period, bool scrollable)
        : ArdourDialog ("plugin ui")
 {
        if (insert->plugin()->has_editor()) {
@@ -73,7 +73,7 @@ PluginUIWindow::PluginUIWindow (boost::shared_ptr<PluginInsert> insert, bool scr
                if ((vp = boost::dynamic_pointer_cast<VSTPlugin> (insert->plugin())) != 0) {
                        
                        
-                       VSTPluginUI* vpu = new VSTPluginUI (insert, vp);
+                       VSTPluginUI* vpu = new VSTPluginUI (insert, vp, session.frame_rate(), session.engine().frames_per_cycle());
                        
                        _pluginui = vpu;
                        get_vbox()->add (*vpu);
@@ -90,7 +90,7 @@ PluginUIWindow::PluginUIWindow (boost::shared_ptr<PluginInsert> insert, bool scr
 
        } else {
 
-               LadspaPluginUI*  pu  = new LadspaPluginUI (insert, scrollable);
+               LadspaPluginUI*  pu  = new LadspaPluginUI (insert, sr, period, scrollable);
                
                _pluginui = pu;
                get_vbox()->add (*pu);
@@ -145,11 +145,12 @@ PluginUIWindow::plugin_going_away ()
        delete_when_idle (this);
 }
 
-PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi)
+PlugUIBase::PlugUIBase (boost::shared_ptr<PluginInsert> pi, nframes64_t sr, nframes64_t period)
        : insert (pi),
          plugin (insert->plugin()),
          save_button(_("Add")),
-         bypass_button (_("Bypass"))
+         bypass_button (_("Bypass")),
+         latency_gui (*pi, sr, period)
 {
         //combo.set_use_arrows_always(true);
        set_popdown_strings (combo, plugin->get_presets());
index 83fdf054440c17017e49266b4b24675e1a44e2df..ef899af6e7b3d30fcb74accafb3c38c7db64c864 100644 (file)
@@ -43,6 +43,8 @@
 #include <ardour_dialog.h>
 #include <ardour/types.h>
 
+#include "latency_gui.h"
+
 namespace ARDOUR {
        class PluginInsert;
        class Plugin;
@@ -66,7 +68,7 @@ namespace Gtkmm2ext {
 class PlugUIBase : public virtual sigc::trackable
 {
   public:
-       PlugUIBase (boost::shared_ptr<ARDOUR::PluginInsert>);
+       PlugUIBase (boost::shared_ptr<ARDOUR::PluginInsert>, nframes64_t sample_rate, nframes64_t period_size);
        virtual ~PlugUIBase() {}
 
        virtual gint get_preferred_height () = 0;
@@ -79,6 +81,7 @@ class PlugUIBase : public virtual sigc::trackable
        Gtk::ComboBoxText combo;
        Gtk::Button save_button;
        Gtk::ToggleButton bypass_button;
+       LatencyGUI latency_gui;
 
        void setting_selected();
        void save_plugin_setting (void);
@@ -88,7 +91,7 @@ class PlugUIBase : public virtual sigc::trackable
 class LadspaPluginUI : public PlugUIBase, public Gtk::VBox 
 {
   public:
-       LadspaPluginUI (boost::shared_ptr<ARDOUR::PluginInsert> plug, bool scrollable=false);
+       LadspaPluginUI (boost::shared_ptr<ARDOUR::PluginInsert> plug, nframes64_t sample_rate, nframes64_t period_size, bool scrollable = false);
        ~LadspaPluginUI ();
        
        gint get_preferred_height () { return prefheight; }
@@ -198,7 +201,7 @@ class LadspaPluginUI : public PlugUIBase, public Gtk::VBox
 class PluginUIWindow : public ArdourDialog
 {
   public:
-       PluginUIWindow (boost::shared_ptr<ARDOUR::PluginInsert> insert, bool scrollable=false);
+       PluginUIWindow (boost::shared_ptr<ARDOUR::PluginInsert> insert, nframes64_t sample_rate, nframes64_t period_size, bool scrollable = false);
        ~PluginUIWindow ();
 
        PlugUIBase& pluginui() { return *_pluginui; }
@@ -217,7 +220,7 @@ class PluginUIWindow : public ArdourDialog
 class VSTPluginUI : public PlugUIBase, public Gtk::VBox
 {
   public:
-       VSTPluginUI (boost::shared_ptr<ARDOUR::PluginInsert>, boost::shared_ptr<ARDOUR::VSTPlugin>);
+       VSTPluginUI (boost::shared_ptr<ARDOUR::PluginInsert>, boost::shared_ptr<ARDOUR::VSTPlugin>, nframes64_t sample_rate, nframes64_t period_size);
        ~VSTPluginUI ();
 
        gint get_preferred_height ();
index d9b0dbd845d8c0b52fe39ff49b96f3542c5c333d..51257e18c0e20c4d198cdd08ece45c4387c5666a 100644 (file)
@@ -1088,7 +1088,7 @@ ProcessorBox::edit_processor (boost::shared_ptr<Processor> processor)
                        
                                if (plugin_processor->get_gui() == 0) {
                                                                
-                                       plugin_ui = new PluginUIWindow (plugin_processor);
+                                       plugin_ui = new PluginUIWindow (plugin_processor, _session.frame_rate(), _session.engine().frames_per_cycle());
 
                                        if (_owner_is_mixer) {
                                                ARDOUR_UI::instance()->the_mixer()->ensure_float (*plugin_ui);
@@ -1190,7 +1190,7 @@ ProcessorBox::register_actions ()
        /* new stuff */
        ActionManager::register_action (popup_act_grp, X_("newplugin"), _("New Plugin ..."),  sigc::ptr_fun (ProcessorBox::rb_choose_plugin));
 
-       act = ActionManager::register_action (popup_act_grp, X_("newprocessor"), _("New Insert"),  sigc::ptr_fun (ProcessorBox::rb_choose_processor));
+       act = ActionManager::register_action (popup_act_grp, X_("newinsert"), _("New Insert"),  sigc::ptr_fun (ProcessorBox::rb_choose_processor));
        ActionManager::jack_sensitive_actions.push_back (act);
        act = ActionManager::register_action (popup_act_grp, X_("newsend"), _("New Send ..."),  sigc::ptr_fun (ProcessorBox::rb_choose_send));
        ActionManager::jack_sensitive_actions.push_back (act);
index 7807cd4a8466b2c35d15cc17dc8aa45784db607a..fb63ae8bd664f2e75d914da2dbc7323f9fec8cf1 100644 (file)
@@ -581,7 +581,7 @@ RouteParams_UI::redirect_selected (boost::shared_ptr<ARDOUR::Processor> insert,
                }
        } else if ((plugin_insert = boost::dynamic_pointer_cast<PluginInsert> (insert)) != 0) {                         
 
-               LadspaPluginUI *plugin_ui = new LadspaPluginUI (plugin_insert, true);
+               LadspaPluginUI *plugin_ui = new LadspaPluginUI (plugin_insert, session->frame_rate(), session->engine().frames_per_cycle(), true);
 
                if (place == PreFader) {
                        cleanup_pre_view();
diff --git a/libs/surfaces/powermate/README b/libs/surfaces/powermate/README
new file mode 100644 (file)
index 0000000..d786b1d
--- /dev/null
@@ -0,0 +1,21 @@
+
+This module works with the Griffin Powermate and allows some basic transport control.
+
+It autodetects the Powermate on any input device of the form "/dev/input/event*".  This means you must have the powermate module in your kernel.  It works out-of-the-box with 64Studio and presumably lots of other modern distributions.
+
+Turning the wheel left and right will act as a "Shuttle" wheel, adjusting playback speed up and down
+Pushing the knob will switch between play and stop
+Pushing the knob while turning will jump to the next or previous markers
+
+
+In order for the powermate to work, you have to have permission to open the input device for reading.
+In debian, I changed /etc/udev/rules.d/0_permissions.rules to have the line:
+KERNEL=="event[0-9]*",                 MODE="0666"
+but there are other ways to achieve this
+
+
+Feedback, tweaks, bug fixes and feature ideas are encouraged
+
+-Ben Loftis, ben@benloftis.com
+
\ No newline at end of file
diff --git a/libs/surfaces/powermate/SConscript b/libs/surfaces/powermate/SConscript
new file mode 100644 (file)
index 0000000..e6f7e25
--- /dev/null
@@ -0,0 +1,57 @@
+# -*- python -*-
+
+import os
+import os.path
+import glob
+
+Import('env final_prefix install_prefix final_config_prefix libraries i18n')
+
+powermate = env.Copy()
+
+#
+# this defines the version number of powermate
+# 
+
+domain = 'ardour_powermate'
+
+powermate.Append(DOMAIN = domain, MAJOR = 1, MINOR = 0, MICRO = 0)
+powermate.Append(CXXFLAGS = "-DPACKAGE=\\\"" + domain + "\\\"")
+powermate.Append(CXXFLAGS="-DLIBSIGC_DISABLE_DEPRECATED")
+powermate.Append(PACKAGE = domain)
+powermate.Append(POTFILE = domain + '.pot')
+
+powermate_files=Split("""
+interface.cc
+powermate.cc
+""")
+
+powermate.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE")
+powermate.Append(CXXFLAGS="-DDATA_DIR=\\\""+final_prefix+"/share\\\"")
+powermate.Append(CXXFLAGS="-DCONFIG_DIR=\\\""+final_config_prefix+"\\\"")
+powermate.Append(CXXFLAGS="-DLOCALEDIR=\\\""+final_prefix+"/share/locale\\\"")
+
+powermate.Merge ([
+    libraries['ardour'],
+    libraries['ardour_cp'],
+    libraries['sigc2'],
+    libraries['pbd'],
+    libraries['midi++2'],
+    libraries['xml'],
+    libraries['usb'],
+    libraries['glib2'],
+    libraries['glibmm2']
+    ])
+
+libardour_powermate = powermate.SharedLibrary('ardour_powermate', powermate_files)
+
+Default(libardour_powermate)
+
+if env['NLS']:
+       i18n (powermate, powermate_files, env)
+             
+env.Alias('install', env.Install(os.path.join(install_prefix, 'lib/ardour2/surfaces'), libardour_powermate))
+
+env.Alias('tarball', env.Distribute (env['DISTTREE'],
+                                    [ 'SConscript' ] +
+                                    powermate_files + 
+                                    glob.glob('po/*.po') + glob.glob('*.h')))
diff --git a/libs/surfaces/powermate/interface.cc b/libs/surfaces/powermate/interface.cc
new file mode 100644 (file)
index 0000000..6012c06
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+       Ardour9pin interface file
+       Ben Loftis
+       Created: 05/18/06 11:07:56
+       Copyright Harrison Audio, LLC, 2007
+*/
+
+#include "powermate.h"
+
+using namespace ARDOUR;
+
+ControlProtocol*
+new_powermate_protocol (ControlProtocolDescriptor* descriptor, Session* s)
+{
+       PowermateControlProtocol* pcp = new PowermateControlProtocol (*s);
+
+       if (pcp->set_active (true)) {
+               delete pcp;
+               return 0;
+       }
+
+       return pcp;
+       
+}
+
+void
+delete_powermate_protocol (ControlProtocolDescriptor* descriptor, ControlProtocol* cp)
+{
+       delete cp;
+}
+
+bool
+probe_powermate_protocol (ControlProtocolDescriptor* descriptor)
+{
+       return PowermateControlProtocol::probe ();
+}
+
+static ControlProtocolDescriptor powermate_descriptor = {
+       name : "powermate",
+       id : "uri://ardour.org/ardour/powermate:0",
+       ptr : 0,
+       module : 0,
+       mandatory : 0,
+       supports_feedback : false,
+       probe : probe_powermate_protocol,
+       initialize : new_powermate_protocol,
+       destroy : delete_powermate_protocol
+};
+       
+
+extern "C" {
+ControlProtocolDescriptor* 
+protocol_descriptor () {
+       return &powermate_descriptor;
+}
+}
+
diff --git a/libs/surfaces/powermate/interface.os b/libs/surfaces/powermate/interface.os
new file mode 100644 (file)
index 0000000..e0afeea
Binary files /dev/null and b/libs/surfaces/powermate/interface.os differ
diff --git a/libs/surfaces/powermate/libardour_powermate.so b/libs/surfaces/powermate/libardour_powermate.so
new file mode 100755 (executable)
index 0000000..8f17d04
Binary files /dev/null and b/libs/surfaces/powermate/libardour_powermate.so differ
diff --git a/libs/surfaces/powermate/powermate.cc b/libs/surfaces/powermate/powermate.cc
new file mode 100644 (file)
index 0000000..139313f
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+       powermate.cc
+       Ben Loftis
+       Created: 03/26/07 20:07:56
+*/
+
+
+#include <linux/input.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <i18n.h>
+#include <pbd/xml++.h>
+
+#include "powermate.h"
+
+using namespace ARDOUR;
+using namespace std;
+using namespace sigc;
+
+#define NUM_VALID_PREFIXES 2
+
+static const char *valid_prefix[NUM_VALID_PREFIXES] = {
+  "Griffin PowerMate",
+  "Griffin SoundKnob"
+};
+
+#define NUM_EVENT_DEVICES 16
+
+int open_powermate(const char *dev, int mode)
+{
+  int fd = open(dev, mode);
+  int i;
+  char name[255];
+
+  if(fd < 0){
+    fprintf(stderr, "Unable to open \"%s\": %s\n", dev, strerror(errno));
+    return -1;
+  }
+
+  if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0){
+    fprintf(stderr, "\"%s\": EVIOCGNAME failed: %s\n", dev, strerror(errno));
+    close(fd);
+    return -1;
+  }
+
+  // it's the correct device if the prefix matches what we expect it to be:
+  for(i=0; i<NUM_VALID_PREFIXES; i++)
+    if(!strncasecmp(name, valid_prefix[i], strlen(valid_prefix[i])))
+      return fd;
+
+  close(fd);
+  return -1;
+}
+
+int find_powermate(int mode)
+{
+  char devname[256];
+  int i, r;
+
+  for(i=0; i<NUM_EVENT_DEVICES; i++){
+    sprintf(devname, "/dev/input/event%d", i);
+    r = open_powermate(devname, mode);
+    if(r >= 0)
+      return r;
+  }
+
+  return -1;
+}
+
+PowermateControlProtocol::PowermateControlProtocol (Session& s)
+       : ControlProtocol  (s, "powermate")
+{
+}
+
+PowermateControlProtocol::~PowermateControlProtocol ()
+{
+       set_active (false);
+}
+
+bool
+PowermateControlProtocol::probe ()
+{
+       int port = find_powermate( O_RDONLY ); 
+
+       if (port < 0) {
+               printf ("powermate: Opening of powermate failed - %s\n", strerror(errno));
+               close (port);
+               return false;
+       }
+
+       close (port);
+       return true;
+}
+
+int
+PowermateControlProtocol::set_active (bool inActivate)
+{
+       if (inActivate != _active) {
+
+               if (inActivate) {
+
+                       mPort = find_powermate(O_RDONLY);
+                       
+                       if ( mPort < 0 ) {
+                               return -1;
+                       }
+                       
+                       if (pthread_create (&mThread, 0, SerialThreadEntry, this) == 0) {
+                               _active = true;
+                       } else {
+                               return -1;
+                       }
+
+                       printf("Powermate Control Protocol activated\n");
+
+               } else {
+                       pthread_cancel (mThread);
+                       close (mPort);
+                       _active = false;
+                       printf("Powermate Control Protocol deactivated\n");
+               } 
+       }
+
+       return 0;
+}
+
+XMLNode&
+PowermateControlProtocol::get_state () 
+{
+       XMLNode* node = new XMLNode (X_("Protocol"));
+       node->add_property (X_("name"), _name);
+       return *node;
+}
+
+int
+PowermateControlProtocol::set_state (const XMLNode& node)
+{
+       return 0;
+}
+
+
+void*
+PowermateControlProtocol::SerialThreadEntry (void* arg)
+{
+       return static_cast<PowermateControlProtocol*>(arg)->SerialThread ();
+}
+
+#define BUFFER_SIZE 32
+
+bool held = false;
+bool skippingMarkers = false;
+
+void
+PowermateControlProtocol::ProcessEvent(struct input_event *ev)
+{
+#ifdef VERBOSE
+  fprintf(stderr, "type=0x%04x, code=0x%04x, value=%d\n",
+         ev->type, ev->code, (int)ev->value);
+#endif
+
+  switch(ev->type){
+  case EV_MSC:
+    printf("The LED pulse settings were changed; code=0x%04x, value=0x%08x\n", ev->code, ev->value);
+    break;
+  case EV_REL:
+    if(ev->code != REL_DIAL)
+      fprintf(stderr, "Warning: unexpected rotation event; ev->code = 0x%04x\n", ev->code);
+    else{
+       if (held) {
+               //click and hold to skip forward and back by markers
+               skippingMarkers = true;;
+               if (ev->value > 0)
+                       next_marker();
+               else
+                       prev_marker();
+       } else {
+               //scale the range so that we can go from +/-8x within 180 degrees, with less precision at the higher speeds 
+               float speed = get_transport_speed();
+               speed += (float)ev->value * 0.05;
+               if (speed > 1.5 || speed < -1.5 )
+                       speed += ev->value;
+               set_transport_speed( speed );
+       }
+    }
+    break;
+  case EV_KEY:
+    if(ev->code != BTN_0)
+      fprintf(stderr, "Warning: unexpected key event; ev->code = 0x%04x\n", ev->code);
+    else
+      if (ev->value)
+               held = true;
+      else {
+               held = false;
+               if (skippingMarkers) {
+                       skippingMarkers = false;
+               } else {
+                       if (get_transport_speed() == 0.0) {
+                               set_transport_speed(1.0);
+                       } else {
+                               set_transport_speed(0.0);
+                       }
+               }
+       }
+    break;
+  }
+
+  fflush(stdout);
+}
+
+void*
+PowermateControlProtocol::SerialThread ()
+{
+  struct input_event ibuffer[BUFFER_SIZE];
+  int r, events, i;
+
+  while(1){
+    r = read(mPort, ibuffer, sizeof(struct input_event) * BUFFER_SIZE);
+    if( r > 0 ){
+               events = r / sizeof(struct input_event);
+      for(i=0; i<events; i++)
+               ProcessEvent(&ibuffer[i]);
+    }else{
+      fprintf(stderr, "read() failed: %s\n", strerror(errno));
+      return (void*) 0;
+    }
+  }
+
+       return (void*) 0;
+}
+
+
diff --git a/libs/surfaces/powermate/powermate.h b/libs/surfaces/powermate/powermate.h
new file mode 100644 (file)
index 0000000..6b0a2fb
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef ardour_powermate_h
+#define ardour_powermate_h
+
+#include <sys/time.h>
+#include <pthread.h>
+
+#include "control_protocol/control_protocol.h"
+
+class PowermateControlProtocol : public ARDOUR::ControlProtocol
+{
+  public:
+       PowermateControlProtocol (ARDOUR::Session&);
+       virtual ~PowermateControlProtocol();
+
+       int set_active (bool yn);
+       static bool probe ();
+
+       XMLNode& get_state ();
+       int set_state (const XMLNode&);
+
+  private:
+       
+       static void* SerialThreadEntry (void* arg);
+       void* SerialThread ();
+       
+       void    ProcessEvent(struct input_event *ev);
+       
+       int                     mPort;
+       pthread_t               mThread;
+
+};
+
+
+#endif
diff --git a/libs/surfaces/powermate/powermate.os b/libs/surfaces/powermate/powermate.os
new file mode 100644 (file)
index 0000000..45519ee
Binary files /dev/null and b/libs/surfaces/powermate/powermate.os differ