title: lp-mpd Launchpad-to-MPD app version 1 ![lp_mpd]{black boxed centre small shadow}(lp-mpd-v1-001.png) ```py #!/usr/bin/env python3 from rtmidi import MidiOut, MidiIn from time import sleep from subprocess import run,PIPE realrun = run def run(*xs,**kw): print(f"run({xs=},{kw=})") return realrun(*xs,**kw) import re from collections import defaultdict import json # Create classes # Device class + delegate # Profile -- delegeate selects profile, activate, and sends all events as but(r,c,x) rbut(r,x) tbut(c,x) # profile sends back button color info but(r,c,x) etc. # static method on Launchpad to select color: color(x,brightness) x is 0,1,2,3 for off red green yellow # brightness os 0..3 for off..1,2,3 # store entire status string when we get it, before parsing it. mptr = """ t420b: 0 -- ytsets/alexorion/AlexORionAleksandarMarkovic_ProgressiveTalesPodcast.m4a -- [paused] #14/15 33:15/171:11 (19%) vol: 64% t420b: 15 -- FearAndLoathing/FnlTech_050_DetroitVsBerlin.m4a -- [paused] #1/1 4:50/84:46 (5%) vol: 64% t420b: 14 -- --stopped-- """ mpst = """ ytsets/alexorion/AlexORionAleksandarMarkovic_ProgressiveTalesPodcast.m4a [paused] #14/15 33:15/171:11 (19%) volume: 64% repeat: on random: on single: off consume: off > mpc status """ # write mp json command to return all statuses, parsed, as json -- write the parser code here # to parse into a dict struct int => status -- then migrate that code to mp # See https://pt-a.allsup.co/hh/launchpad/MpdControlIdea1 for idea class MidiDevice: def __init__(self,input_port=None,output_port=None,pattern=None,index=0): self.midi_in = MidiIn() self.midi_out = MidiOut() self.input_port = input_port self.output_port = output_port self.input_open = False self.output_open = False self.set_callback(None) input_port_candidates = [] output_port_candidates = [] if pattern is not None: for i,x in enumerate(self.midi_in.get_ports()): print(i,x) if pattern in x: input_port_candidates.append(i) break for i,x in enumerate(self.midi_out.get_ports()): if pattern in x: output_port_candidates.append(i) break if not input_port: self.input_port = input_port_candidates[index] if not output_port: self.output_port = output_port_candidates[index] def open(self): if not self.input_open and self.input_port is not None: print(f"opening input port {self.input_port}") self.midi_in.open_port(self.input_port) self.input_open = True if not self.output_open and self.output_port is not None: print(f"opening output port {self.output_port}") self.midi_out.open_port(self.output_port) self.output_open = True def __del__(self): self.close_ports() def close_ports(self): if self.input_open: print(f"closing input port") self.midi_in.close_port() if self.output_open: print(f"closing output port") self.midi_out.close_port() def set_callback(self,callback): self.midi_in.set_callback(callback) def send_message(self,msg): self.midi_out.send_message(msg) def cc(self,n,v): msg = bytes([0xB0,n,v]) self.send_message(msg) def on(self,p,v): msg = bytes([0x90,p,v]) self.send_message(msg) def off(self,p,v=0): msg = bytes([0x90,p,0]) self.send_message(msg) class LpdDevice: def __init__(self,device): self.device = device device.set_callback(self.midi_callback) delegate = None device.open() self.setup() def set_delegate(self,delegate): self.delegate = delegate def make_color(self,idx,br): idx = idx & 3 br = br & 3 r = (idx&1) * br g = ((idx&2)>>1) * br g <<= 4 #print(f"color {idx=} {br=} {r:b=} {g:b=}") return r | g def midi_callback(self,x,y): msg,t = x print("midi",msg) if msg[0] & 0xF0 == 0xB0: print("top") n,v = msg[1:3] self.tbut(0,v) elif msg[0] & 0xF0 == 0x90: print("rest of pads") n,v = msg[1:3] row = (n>>4) col = (n&0xf) if col < 8: self.but(row,col,v) else: self.rbut(row,v) def setup(self): pass def tbut(self,c,v): print("1tbut",c,v) if self.delegate is not None: return self.delegate.tbut(c,v) def rbut(self,r,v): print("1rbut",r,v) if self.delegate is not None: return self.delegate.rbut(r,v) def but(self,r,c,v): print("1but",r,c,v) if self.delegate is not None: return self.delegate.but(r,c,v) def set_all(self,idx,br): for i in range(8): self.set_tbut(i,idx,br) self.set_rbut(i,idx,br) for j in range(8): self.set_but(i,j,idx,br) def set_but(self,r,c,idx,br): color = self.make_color(idx,br) n = (r << 4) | c self.device.on(n,color) def set_tbut(self,c,idx,br): color = self.make_color(idx,br) # TODO get n = 0x68 + c self.device.cc(n,color) def set_rbut(self,r,idx,br): color = self.make_color(idx,br) return self.set_but(r,8,idx,br) class LpMpd(LpdDevice): def setup(self): print("LpMpd setup") self.mode = 0 # select, touch-to-play-pause, touch-to-stop self.ch = 0 self.data = None self.json = None self.setup_lower() def poll_srv(self): m = run(["mp","json"],stdout=PIPE) newjson = m.stdout.decode() if newjson != self.json: self.json = newjson self.data = json.loads(newjson) self.update() def set_ch(self,ch): print("set_ch",ch) if ch != self.ch: self.ch = ch self.update_chs() def cleanup(self): self.set_all(0) def color_for_state(self,state): if state == "stopped": return 1 if state == "playing": return 2 if state == "paused": return 3 return 0 def setup_lower(self): # row 5 -- seek self.set_but(4,0,2,3) self.set_but(4,1,2,2) self.set_but(4,2,2,1) self.set_but(4,3,1,1) self.set_but(4,4,1,1) self.set_but(4,5,2,1) self.set_but(4,6,2,2) self.set_but(4,7,2,3) # row 6 -- prev/next XX vol -10, -2, 2, 10 self.set_but(5,0,3,1) self.set_but(5,1,3,1) self.set_but(5,2,0,0) self.set_but(5,3,0,0) self.set_but(5,4,1,3) self.set_but(5,5,1,1) self.set_but(5,6,1,1) self.set_but(5,7,1,3) self.set_but(7,4,1,3) self.set_but(7,5,3,3) self.set_but(7,6,2,3) self.set_but(7,7,3,3) def update_chs(self): #print("update_chs") for i in range(16): row = i // 8 col = i % 8 chdata = self.data[i] state = chdata['playstate'] color = self.color_for_state(state) br = 3 if i == self.ch else 1 #print("chs:",i,state,row,col,color,br) self.set_but(row,col,color,br) def update_ch(self): # volume bar chdata = self.data[self.ch] chvol = chdata['volume'] state = chdata['playstate'] t_pc = chdata['t_pc'] t_pc /= 100.0 t_pc *= 8 t_pc = min(round(t_pc),7) v = chvol // 10 # 90-100 = 8 # 0-19 = 0 v = min(8,max(0,v-1)) color = self.color_for_state(state) for j in range(8): self.set_but(3,j,color,3 if j < v else 0) if state == "stopped": self.set_but(2,j,0,0) else: self.set_but(2,j,color,3 if j == t_pc else 0) self.set_but(7,0,1 if chdata["repeat"] else 2,3) self.set_but(7,1,1 if chdata["single"] else 2,3) self.set_but(7,2,1 if chdata["random"] else 2,3) self.set_but(7,3,1 if chdata["consume"] else 2,1) def update(self): self.update_chs() self.update_ch() def set_ch_but(self,i,idx,br): row = i // 8 col = i % 8 self.set_but(row,col,idx,br) def tbut(self,c,v): pass # ignore def rbut(self,r,v): print("2rbut",r,v) if v == 0: return if r == 0: self.mode = (self.mode + 1) % 3 if self.mode == 0: self.set_rbut(0,0,0) if self.mode == 1: self.set_rbut(0,2,3) if self.mode == 2: self.set_rbut(0,1,3) print("Mode now",self.mode) # else ignore def but(self,r,c,v): print("2but",r,c,v) if v == 0: return if r < 2: ch = (r << 3) | c self.set_ch(ch) if self.mode == 1: chdata = self.data[ch] state = chdata['playstate'] if state == "stopped": self.play(ch) else: self.toggle(ch) elif self.mode == 2: self.stop(ch) if r == 3: # volume newv = 20+(c * 10) self.set_vol(self.ch,newv) ch = str(self.ch) if r == 2: c = int((c * 100.0/8)) c = str(c)+"%" run(["mp",ch,",",f"seek {c}"]) if r == 4: # seek if c == 0: s = "-10:00" elif c == 1: s = "-5:00" elif c == 2: s = "-1:00" elif c == 3: s = "-0:05" elif c == 4: s = "+0:05" elif c == 5: s = "+1:00" elif c == 6: s = "+5:00" elif c == 7: s = "+10:00" print(f"Seek {s}") run(["mp",ch,",",f"seek {s}"]) if r == 5: if c == 0: run(["mp",ch,",",f"prev"]) elif c == 1: run(["mp",ch,",",f"next"]) elif c == 2: pass elif c == 3: pass elif c == 4: run(["mp",ch,",",f"vol -10"]) elif c == 5: run(["mp",ch,",",f"vol -2"]) elif c == 6: run(["mp",ch,",",f"vol +2"]) elif c == 7: run(["mp",ch,",",f"vol +10"]) if r == 7: if c == 0: run(["mp",ch,",",f"repeat"]) elif c == 1: run(["mp",ch,",",f"single"]) elif c == 2: run(["mp",ch,",",f"random"]) elif c == 3: run(["mp",ch,",",f"consume"]) elif c == 4: self.stop(ch) elif c == 5: self.pause(ch) elif c == 6: self.play(ch) elif c == 7: self.toggle(ch) def set_vol(self,ch,newvol): run(["mp",str(ch),",","volume",str(newvol)]) def toggle(self,ch): run(["mp",str(ch),",","p"]) def stop(self,ch): run(["mp",str(ch),",","stop"]) def pause(self,ch): run(["mp",str(ch),",","pause"]) def play(self,ch): run(["mp",str(ch),",","play"]) midi_device = MidiDevice(pattern="Launchpad") lp_mpd = LpMpd(midi_device) try: while True: lp_mpd.poll_srv() sleep(1) except KeyboardInterrupt: print("Ctrl-C") lp_mpd.set_all(0,0) midi_device.close_ports() ```