File: midi_handler.py

package info (click to toggle)
quisk 4.2.50-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 17,908 kB
  • sloc: ansic: 74,628; python: 23,309; makefile: 1,270; sh: 2
file content (161 lines) | stat: -rwxr-xr-x 6,719 bytes parent folder | download | duplicates (3)
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# This module implements Midi processing in Quisk. The OnReadMIDI() method is called for any Midi bytes
# received. Do not change this file. If you want to replace it with your own Midi handler, create a
# configuration file, copy this file into it and make any changes there. Quisk will use your configuration
# file MidiHandler instead of this one.

# Midi messages are generally three bytes long. The first byte is the status and has the most significant bit set.
# Subsequent bytes have the most significant bit zero. The status byte of a channel message is a 1 bit, three bits of message
# type and 4 bits of channel number. This is followed by two bytes of data.

# For Note On (status 0x9?) and Note Off (status 0x8?) the data bytes are the note number and velocity. Velocity
# indicates how hard the key was pressed. If the velocity of a Note On message is zero it is treated the same as Note Off.

# For a control change (status = 0xB?) the data bytes are the controller number and the controller value. For some controllers
# it only matters if the value is less than 64 (down) or 64 or greater (up). For other controllers the value 0 to 127
# is the actual control setting.

import traceback

class MidiHandler:	# Quisk calls this to make the Midi handler instance.
  tune_speed = {0:10, 1:20, 2:50, 3:100, 4:200, 5:500, 6:1000, 7:2000, 8:5000, 9:10000}
  slider_speed = {0:1, 1:2, 2:3, 3:5, 4:7, 5:9, 6:12, 7:15, 8:18, 9:22}
  def __init__(self, app, conf):
    self.app = app		# The application object
    self.conf = conf		# The configuration settings
    self.midi_message = []	# Save Midi bytes until a whole message is received.
  def OnReadMIDI(self, byts):	# Quisk calls this for any Midi bytes received.
    for byt in byts:
      if byt & 0x80:		# this is a status byte and the start of a new message
        self.midi_message = [byt]
      else:
        self.midi_message.append(byt)
      if len(self.midi_message) == 3:
        #print ("0x%2X%02X %d" % tuple(self.midi_message))
        status   = self.midi_message[0]
        status = status & 0xF0	# Ignore channel
        if status == 0x90:	# Note On
          if self.midi_message[2] == 0:		# Note On with zero velocity is the same as Note Off
            self.NoteOff()
          else:
            self.NoteOn()
        elif status == 0x80:	# Note Off
          self.NoteOff()
        elif status == 0xB0:	# Control Change
          try:
            name = self.app.local_conf.MidiNoteDict["0x%02X%02X" % (self.midi_message[0], self.midi_message[1])]
          except:
            pass
            #traceback.print_exc()
          else:
            if len(name) > 3 and name[-3] == " " and name[-2] in "+-" and name[-1] in "0123456789":
              self.JogWheel(name)
            else:
              self.ControlKnob(name)
  def NoteOn(self):
    try:
      name = self.app.local_conf.MidiNoteDict["0x%02X%02X" % (self.midi_message[0], self.midi_message[1])]
      btn = self.app.idName2Button[name]
    except:
      return
    if btn.idName == 'PTT' and not self.conf.midi_ptt_toggle:
      btn.SetValue(True, True)
    else:
      btn.Shortcut(None, name)
  def NoteOff(self):
    try:	# Look up the Note On name
      name = self.app.local_conf.MidiNoteDict["0x9%X%02X" % (self.midi_message[0] & 0xF, self.midi_message[1])]
      btn = self.app.idName2Button[name]
    except:
      return
    if hasattr(btn, "repeat_state"):	# This is a QuiskRepeatbutton
      btn.Shortcut(None, "_end_")
    elif btn.idName == 'PTT' and not self.conf.midi_ptt_toggle:
      btn.SetValue(False, True)
  def ControlKnob(self, name):
    if self.midi_message[2] == 64:	# Mid control
      dec_value = 0.5
    else:
      dec_value = self.midi_message[2] / 127.0
    if name == "Tune":
      tune = self.app.sample_rate * (dec_value - 0.5) * 0.98
      tune = int(tune)
      self.app.ChangeHwFrequency(tune, self.app.VFO, 'FreqEntry')
    elif name == "Rit":		# Offset values by the CW tone frequency
      ctrl, func = self.app.midiControls[name]
      value = self.midi_message[2] - 64		# Center value
      if self.app.mode == 'CWU':
        offset = - self.conf.cwTone
        value = value * 1000 // 63 + offset
      elif self.app.mode == 'CWL':
        offset = self.conf.cwTone
        value = value * 1000 // 63 + offset
      else:
        offset = 0
        value = value * 2000 // 63 + offset
      if value < ctrl.themin:
        value = ctrl.themin
      elif value > ctrl.themax:
        value = ctrl.themax
      ctrl.SetValue(value)
      if self.app.remote_control_head:
        self.app.Hardware.RemoteCtlSend(f'{ctrl.idName};{ctrl.GetValue()}\n')
      func()
    elif name in self.app.midiControls:
      ctrl, func = self.app.midiControls[name]
      if ctrl:
        ctrl.SetDecValue(dec_value, False)
        if self.app.remote_control_head:
          self.app.Hardware.RemoteCtlSend(f'{ctrl.idName};{ctrl.GetValue()}\n')
        func()
    else:	# Try to treat as Note On/Off
      try:
        btn = self.app.idName2Button[name]
      except:
        return
      if self.midi_message[2] == 0:		# Note On with zero velocity is the same as Note Off
        if hasattr(btn, "repeat_state"):	# This is a QuiskRepeatbutton
          btn.Shortcut(None, "_end_")
      else:		# Note On
        btn.Shortcut(None, name)
  def JogWheel(self, name):
    speed = int(name[-1])
    if name[-2] == '+':
      direction = +1
    else:
      direction = -1
    name = name[0:-3]
    if name == "Tune":
      freq = self.app.txFreq + self.app.VFO
      delta = self.tune_speed[speed]
      if self.midi_message[2] < 64:
        freq += direction * delta
      else:
        freq -= direction * delta
      freq = ((freq + delta // 2) // delta) * delta
      tune = freq - self.app.VFO
      d = self.app.sample_rate * 45 // 100
      if -d <= tune <= d:  # Frequency is on-screen
        vfo = self.app.VFO
      else:  # Change the VFO
        vfo = (freq // 5000) * 5000 - 5000
        tune = freq - vfo
      self.app.ChangeHwFrequency(tune, vfo, 'FreqEntry')
    elif name in self.app.midiControls:
      ctrl, func = self.app.midiControls[name]
      self.AdjSlider(ctrl, direction, speed)
      if self.app.remote_control_head:
        self.app.Hardware.RemoteCtlSend(f'{ctrl.idName};{ctrl.GetValue()}\n')
      func()
    else:
      pass #print ("Unknown jog name", name)
  def AdjSlider(self, ctrl, direction, speed):
    value = ctrl.GetValue()
    if self.midi_message[2] < 64:
      value += direction * self.slider_speed[speed]
    else:
      value -= direction * self.slider_speed[speed]
    if value < ctrl.themin:
      value = ctrl.themin
    elif value > ctrl.themax:
      value = ctrl.themax
    ctrl.SetValue(value)