162 lines
6.6 KiB
Python
162 lines
6.6 KiB
Python
# 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)
|