简体   繁体   中英

controlling Python script with usb midi controller

I have a usb midi controller (uc-33e) and I have a working python script see below. I would like to be able to control the variables of the python script using my usb controller does anyone have an example of this? I would like to use the "midi learn" function to automatically map a midi control to an object (like in Ardour which is a music program for linux) by ctrl-middle clicking the object and just turning the control on the usb midi controller to map it. But I'm also willing to hard code the midi CC into the python script if needed.

在此处输入图像描述

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# ***************************************************************************
# *   Copyright (C) 2011, Paul Lutus                                        *
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of the GNU General Public License as published by  *
# *   the Free Software Foundation; either version 2 of the License, or     *
# *   (at your option) any later version.                                   *
# *                                                                         *
# *   This program is distributed in the hope that it will be useful,       *
# *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
# *   GNU General Public License for more details.                          *
# *                                                                         *
# *   You should have received a copy of the GNU General Public License     *
# *   along with this program; if not, write to the                         *
# *   Free Software Foundation, Inc.,                                       *
# *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
# ***************************************************************************

# version date 01-12-2011

VERSION = '1.1'

import re, sys, os

import gobject
gobject.threads_init()
import gst
import gtk
gtk.gdk.threads_init()
import time
import struct
import math
import random
import signal
import webbrowser

class Icon:
  icon = [
    "32 32 17 1",
    "   c None",
    ".  c #2A2E30",
    "+  c #333739",
    "@  c #464A4C",
    "#  c #855023",
    "$  c #575A59",
    "%  c #676A69",
    "&  c #CC5B00",
    "*  c #777A78",
    "=  c #DB731A",
    "-  c #8A8C8A",
    ";  c #969895",
    ">  c #F68C22",
    ",  c #A5A7A4",
    "'  c #F49D4A",
    ")  c #B3B5B2",
    "!  c #DEE0DD",
    "                        &&&&&&& ",
    "                  &&&===='''''& ",
    "                  &'''''====&'& ",
    "             +++++&'&&&&&   &'& ",
    "          +@$%****&'&+      &'& ",
    "        +@**%$@++@&'&*@+    &'& ",
    "      +@**@+++++++&'&@**@+  &'& ",
    "     +$*$+++++++++&'&++$*$+ &'& ",
    "     @*@++++++++++&'&+++@#&&&'& ",
    "    +*@++++++++#&&&'&+++#=''''& ",
    "   +*$++++++++#=''''&+++&'>>>'& ",
    "   @*+++++++++&'>>>'&+++#='''=  ",
    "  +%$++++++++@#='''=#@@++#&&&#  ",
    "  +*@+++++++@@@#&&&#@@@@@++@*+  ",
    "  +*+++++++@@@@++@$%$$@@@@++*+  ",
    "  +*++++++@@+@;,,*@@*$$$@@@+*+  ",
    "  +*@++++@@@%!!!!,;@$*$$$@@@*+  ",
    "  +%$++++@@+)!!!),-*+-%$$$@$%+  ",
    "  +@*+++@@@+-!!!,;-%@;%%$$+*@+  ",
    "   +*@++@@@@+$*-*%@+*-%%$@@*+   ",
    "   ++*@+@@@$$%@++@%;;*%%$@-$+   ",
    "    +@%+@@@$$%*;;;;-*%%%@**+    ",
    "    .+$%@@@$$$*******%$$*-+.    ",
    "     .+@%%@@$$*@*@%%%$%-%+.     ",
    "      .++@%$$$$$$%%%%--@+.      ",
    "        +++@@$%*****%+++        ",
    "         +++++++++++++@.        ",
    "          @--%@++@$*-%+         ",
    "           +%,))),;%+.          ",
    "             ++++++.            ",
    "                                ",
    "                                "
  ]

# this should be a temporary hack

class WidgetFinder:
  def localize_widgets(self,parent,xmlfile):
    # an unbelievable hack made necessary by
    # someone unwilling to fix a year-old bug
    with open(xmlfile) as f:
      for name in re.findall('(?s) id="(.*?)"',f.read()):
        if re.search('^k_',name):
          obj = parent.builder.get_object(name)
          setattr(parent,name,obj)

class ConfigManager:
  def __init__(self,path,dic):
    self.path = path
    self.dic = dic

  def read_config(self):
    if os.path.exists(self.path):
      with open(self.path) as f:
        for record in f.readlines():
          se = re.search('(.*?)\s*=\s*(.*)',record.strip())
          if(se):
            key,value = se.groups()
            if (key in self.dic):
              widget = self.dic[key]
              typ = type(widget)
              if(typ == list):
                widget[0] = value
              elif(typ == gtk.Entry):
                widget.set_text(value)
              elif(typ == gtk.HScale):
                widget.set_value(float(value))
              elif(typ == gtk.Window):
                w,h = value.split(',')
                widget.resize(int(w),int(h))
              elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton):
                widget.set_active(value == 'True')
              elif(typ == gtk.ComboBox):
                if(value in widget.datalist):
                  i = widget.datalist.index(value)
                  widget.set_active(i)
              else:
                print "ERROR: reading, cannot identify key %s with type %s" % (key,type(widget))

  def write_config(self):
    with open(self.path,'w') as f:
      for key,widget in sorted(self.dic.iteritems()):
        typ = type(widget)
        if(typ == list):
          value = widget[0]
        elif(typ == gtk.Entry):
          value = widget.get_text()
        elif(typ == gtk.HScale):
          value = str(widget.get_value())
        elif(typ == gtk.Window):
          _,_,w,h = widget.get_allocation()
          value = "%d,%d" % (w,h)
        elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton):
          value = ('False','True')[widget.get_active()]
        elif(typ == gtk.ComboBox):
          value = widget.get_active_text()
        else:
          print "ERROR: writing, cannot identify key %s with type %s" % (key,type(widget))
          value = "Error"
        f.write("%s = %s\n" % (key,value))

  def preset_combobox(self,box,v):
    if(v in box.datalist):
          i = box.datalist.index(v)
      box.set_active(i)
    else:
      box.set_active(0)

  def load_combobox(self,obj,data):
    if(len(obj.get_cells()) == 0):
      # Create a text cell renderer
      cell = gtk.CellRendererText ()
      obj.pack_start(cell)
      obj.add_attribute (cell, "text", 0)
    obj.get_model().clear()
    for s in data:
      obj.append_text(s.strip())
    setattr(obj,'datalist',data)

class TextEntryController:
  def __init__(self,parent,widget):
    self.par = parent
    self.widget = widget
    widget.connect('scroll-event',self.scroll_event)
    widget.set_tooltip_text('Enter number or:\n\
    Mouse wheel: increase,decrease\n\
    Shift/Ctrl/Alt: faster change')

  def scroll_event(self,w,evt):
    q = (-1,1)[evt.direction == gtk.gdk.SCROLL_UP]
    # magnify change if shift,ctrl,alt pressed
    for m in (1,2,4):
      if(self.par.mod_key_val & m): q *= 10
    s = self.widget.get_text()
    v = float(s)
    v += q
    v = max(0,v)
    s = self.par.format_num(v)
    self.widget.set_text(s)

class SignalGen:
  M_AM,M_FM = range(2)
  W_SINE,W_TRIANGLE,W_SQUARE,W_SAWTOOTH = range(4)
  waveform_strings = ('Sine','Triangle','Square','Sawtooth')
  R_48000,R_44100,R_22050,R_16000,R_11025,R_8000,R_4000 = range(7)
  sample_rates = ('48000','44100','22050','16000', '11025', '8000', '4000')
  def __init__(self):
    self.restart = False
    # exit correctly on system signals
    signal.signal(signal.SIGTERM, self.close)
    signal.signal(signal.SIGINT, self.close)
    # precompile struct operator
    self.struct_int = struct.Struct('i')
    self.max_level = (2.0**31)-1
    self.gen_functions = (
      self.sine_function,
      self.triangle_function,
      self.square_function,
      self.sawtooth_function
    )
    self.main_color = gtk.gdk.color_parse('#c04040')
    self.sig_color = gtk.gdk.color_parse('#40c040')
    self.mod_color = gtk.gdk.color_parse('#4040c0')
    self.noise_color = gtk.gdk.color_parse('#c040c0')
    self.pipeline = False
    self.count = 0
    self.imod = 0
    self.rate = 1
    self.mod_key_val = 0
    self.sig_freq = 440
    self.mod_freq = 3
    self.sig_level = 100
    self.mod_level = 100
    self.noise_level = 100
    self.enable = True
    self.sig_waveform = SignalGen.W_SINE
    self.sig_enable = True
    self.sig_function = False
    self.mod_waveform = SignalGen.W_SINE
    self.mod_function = False
    self.mod_mode = SignalGen.M_AM
    self.mod_enable = False
    self.noise_enable = False
    self.sample_rate = SignalGen.R_22050
    self.left_audio  = True
    self.right_audio = True
    self.program_name = self.__class__.__name__
    self.config_file = os.path.expanduser("~/." + self.program_name)
    self.builder = gtk.Builder()
    self.xmlfile = 'signalgen_gui.glade'
    self.builder.add_from_file(self.xmlfile)
    WidgetFinder().localize_widgets(self,self.xmlfile)
    self.k_quit_button.connect('clicked',self.close)
    self.k_help_button.connect('clicked',self.launch_help)
    self.k_mainwindow.connect('destroy',self.close)
    self.k_mainwindow.set_icon(gtk.gdk.pixbuf_new_from_xpm_data(Icon.icon))
    self.title = self.program_name + ' ' + VERSION
    self.k_mainwindow.set_title(self.title)
    self.tooltips = {
      self.k_sample_rate_combobox : 'Change data sampling rate',
      self.k_left_checkbutton : 'Enable left channel audio',
      self.k_right_checkbutton : 'Enable right channel audio',
      self.k_sig_waveform_combobox : 'Select signal waveform',
      self.k_mod_waveform_combobox : 'Select modulation waveform',
      self.k_mod_enable_checkbutton  : 'Enable modulation',
      self.k_sig_enable_checkbutton  : 'Enable signal',
      self.k_noise_enable_checkbutton  : 'Enable white noise',
      self.k_mod_am_radiobutton : 'Enable amplitude modulation',
      self.k_mod_fm_radiobutton : 'Enable frequency modulation',
      self.k_quit_button : 'Quit %s' % self.title,
      self.k_enable_checkbutton : 'Enable output',
      self.k_help_button : 'Visit the %s Web page' % self.title,
    }
    for k,v in self.tooltips.iteritems():
      k.set_tooltip_text(v)
    self.config_data = {
      'SampleRate' : self.k_sample_rate_combobox,
      'LeftChannelEnabled' : self.k_left_checkbutton,
      'RightChannelEnabled' : self.k_right_checkbutton,
      'SignalWaveform' : self.k_sig_waveform_combobox,
      'SignalFrequency' : self.k_sig_freq_entry,
      'SignalLevel' : self.k_sig_level_entry,
      'SignalEnabled' : self.k_sig_enable_checkbutton,
      'ModulationWaveform' : self.k_mod_waveform_combobox,
      'ModulationFrequency' : self.k_mod_freq_entry,
      'ModulationLevel' : self.k_mod_level_entry,
      'ModulationEnabled' : self.k_mod_enable_checkbutton,
      'AmplitudeModulation' : self.k_mod_am_radiobutton,
      'FrequencyModulation' : self.k_mod_fm_radiobutton,
      'NoiseEnabled' : self.k_noise_enable_checkbutton,
      'NoiseLevel' : self.k_noise_level_entry,
      'OutputEnabled' : self.k_enable_checkbutton,
    }
    self.cm = ConfigManager(self.config_file,self.config_data)
    self.cm.load_combobox(self.k_sig_waveform_combobox,self.waveform_strings)
    self.k_sig_waveform_combobox.set_active(self.sig_waveform)
    self.cm.load_combobox(self.k_mod_waveform_combobox,self.waveform_strings)
    self.k_mod_waveform_combobox.set_active(self.mod_waveform)
    self.cm.load_combobox(self.k_sample_rate_combobox,self.sample_rates)
    self.k_sample_rate_combobox.set_active(self.sample_rate)
    self.k_sig_freq_entry.set_text(self.format_num(self.sig_freq))
    self.k_sig_level_entry.set_text(self.format_num(self.sig_level))
    self.k_mod_freq_entry.set_text(self.format_num(self.mod_freq))
    self.k_mod_level_entry.set_text(self.format_num(self.mod_level))
    self.k_noise_level_entry.set_text(self.format_num(self.noise_level))
    self.k_main_viewport_border.modify_bg(gtk.STATE_NORMAL,self.main_color)
        self.k_sig_viewport_border.modify_bg(gtk.STATE_NORMAL,self.sig_color)
    self.k_mod_viewport_border.modify_bg(gtk.STATE_NORMAL,self.mod_color)
    self.k_noise_viewport_border.modify_bg(gtk.STATE_NORMAL,self.noise_color)
    self.sig_freq_cont = TextEntryController(self,self.k_sig_freq_entry)
    self.sig_level_cont = TextEntryController(self,self.k_sig_level_entry)
    self.mod_freq_cont = TextEntryController(self,self.k_mod_freq_entry)
    self.mod_level_cont = TextEntryController(self,self.k_mod_level_entry)
    self.noise_level_cont = TextEntryController(self,self.k_noise_level_entry)
    self.k_mainwindow.connect('key-press-event',self.key_event)
    self.k_mainwindow.connect('key-release-event',self.key_event)
    self.k_enable_checkbutton.connect('toggled',self.update_values)
    self.k_sig_freq_entry.connect('changed',self.update_entry_values)
    self.k_sig_level_entry.connect('changed',self.update_entry_values)
    self.k_sig_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
        self.k_mod_freq_entry.connect('changed',self.update_entry_values)
    self.k_mod_level_entry.connect('changed',self.update_entry_values)
    self.k_noise_level_entry.connect('changed',self.update_entry_values)
    self.k_sample_rate_combobox.connect('changed',self.update_values)
    self.k_sig_waveform_combobox.connect('changed',self.update_values)
    self.k_mod_waveform_combobox.connect('changed',self.update_values)
    self.k_left_checkbutton.connect('toggled',self.update_checkbutton_values)
    self.k_right_checkbutton.connect('toggled',self.update_checkbutton_values)
    self.k_mod_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
    self.k_noise_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
    self.k_mod_am_radiobutton.connect('toggled',self.update_checkbutton_values)
    self.cm.read_config()
    self.update_entry_values()
    self.update_checkbutton_values()
    self.update_values()

  def format_num(self,v):
    return "%.2f" % v

  def get_widget_text(self,w):
    typ = type(w)
    if(typ == gtk.ComboBox):
      return w.get_active_text()
    elif(typ == gtk.Entry):
      return w.get_text()

  def get_widget_num(self,w):
    try:
      return float(self.get_widget_text(w))
    except:
      return 0.0

  def restart_test(self,w,pv):
    nv = w.get_active()
    self.restart |= (nv != pv)
    return nv

  def update_entry_values(self,*args):
    self.sig_freq = self.get_widget_num(self.k_sig_freq_entry)
    self.sig_level = self.get_widget_num(self.k_sig_level_entry) / 100.0
    self.mod_freq = self.get_widget_num(self.k_mod_freq_entry)
    self.mod_level = self.get_widget_num(self.k_mod_level_entry) / 100.0
    self.noise_level = self.get_widget_num(self.k_noise_level_entry) / 100.0

  def update_checkbutton_values(self,*args):
    self.left_audio = self.k_left_checkbutton.get_active()
    self.right_audio = self.k_right_checkbutton.get_active()
    self.mod_enable = self.k_mod_enable_checkbutton.get_active()
    self.sig_enable = self.k_sig_enable_checkbutton.get_active()
    self.mod_mode = (SignalGen.M_FM,SignalGen.M_AM)[self.k_mod_am_radiobutton.get_active()]
    self.noise_enable = self.k_noise_enable_checkbutton.get_active()

  def update_values(self,*args):
    self.restart = (not self.sig_function)
    self.sample_rate = self.restart_test(self.k_sample_rate_combobox, self.sample_rate)
    self.enable = self.restart_test(self.k_enable_checkbutton,self.enable)
    self.mod_waveform = self.k_mod_waveform_combobox.get_active()
    self.mod_function = self.gen_functions[self.mod_waveform]
    self.sig_waveform = self.k_sig_waveform_combobox.get_active()
    self.sig_function = self.gen_functions[self.sig_waveform]
    self.k_sample_rate_combobox.set_sensitive(not self.enable)
    if(self.restart):
      self.init_audio()

  def make_and_chain(self,name):
    target = gst.element_factory_make(name)
    self.chain.append(target)
    return target

  def unlink_gst(self):
    if(self.pipeline):
      self.pipeline.set_state(gst.STATE_NULL)
      self.pipeline.remove_many(*self.chain)
      gst.element_unlink_many(*self.chain)
      for item in self.chain:
        item = False
      self.pipeline = False
      time.sleep(0.01)

  def init_audio(self):
    self.unlink_gst()
    if(self.enable):
          self.chain = []
      self.pipeline = gst.Pipeline("mypipeline")
      self.source = self.make_and_chain("appsrc")
      rs = SignalGen.sample_rates[self.sample_rate]
      self.rate = float(rs)
      self.interval = 1.0 / self.rate
      caps = gst.Caps(
      'audio/x-raw-int,'
      'endianness=(int)1234,'
      'channels=(int)2,'
      'width=(int)32,'
      'depth=(int)32,'
      'signed=(boolean)true,'
      'rate=(int)%s' % rs)
      self.source.set_property('caps', caps)
      self.sink = self.make_and_chain("autoaudiosink")
      self.pipeline.add(*self.chain)
      gst.element_link_many(*self.chain)
      self.source.connect('need-data', self.need_data)
      self.pipeline.set_state(gst.STATE_PLAYING)

  def key_event(self,w,evt):
    cn = gtk.gdk.keyval_name(evt.keyval)
    if(re.search('Shift',cn) != None):
      mod = 1
    elif(re.search('Control',cn) != None):
      mod = 2
    elif(re.search('Alt|Meta',cn) != None):
      mod = 4
    else:
      return
    if(evt.type == gtk.gdk.KEY_PRESS):
      self.mod_key_val |= mod
    else:
      self.mod_key_val &= ~mod

  def sine_function(self,t,f):
    return math.sin(2.0*math.pi*f*t)

  def triangle_function(self,t,f):
    q = 4*math.fmod(t*f,1)
    q = (q,2-q)[q > 1]
    return (q,-2-q)[q < -1]

  def square_function(self,t,f):
    if(f == 0): return 0
    q = 0.5 - math.fmod(t*f,1)
    return (-1,1)[q > 0]

  def sawtooth_function(self,t,f):
    return 2.0*math.fmod((t*f)+0.5,1.0)-1.0

  def need_data(self,src,length):
    bytes = ""
    # sending two channels, so divide requested length by 2
    ld2 = length / 2
    for tt in range(ld2):
      t = (self.count + tt) * self.interval
      if(not self.mod_enable):
        datum = self.sig_function(t,self.sig_freq)
      else:
        mod = self.mod_function(t,self.mod_freq)
        # AM mode
        if(self.mod_mode == SignalGen.M_AM):
          datum = 0.5 * self.sig_function(t,self.sig_freq) * (1.0 + (mod * self.mod_level))
        # FM mode
        else:
          self.imod += (mod * self.mod_level * self.interval)
          datum = self.sig_function(t+self.imod,self.sig_freq)
      v = 0
      if(self.sig_enable):
        v += (datum * self.sig_level)
      if(self.noise_enable):
        noise = ((2.0 * random.random()) - 1.0)
        v += noise * self.noise_level
      v *= self.max_level
      v = max(-self.max_level,v)
      v = min(self.max_level,v)
      left  = (0,v)[self.left_audio]
      right = (0,v)[self.right_audio]
      bytes += self.struct_int.pack(left)
      bytes += self.struct_int.pack(right)
    self.count += ld2
    src.emit('push-buffer', gst.Buffer(bytes))

  def launch_help(self,*args):
    webbrowser.open("http://arachnoid.com/python/signalgen_program.html")

  def close(self,*args):
    self.unlink_gst()
    self.cm.write_config()
    gtk.main_quit()

app=SignalGen()
gtk.main()

You should take a look at pygame , which has an excellent and easy MIDI interface. After you get your controller configured, MIDI learn is simply a matter of listening to the first incoming CC and then saving the channel.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM