Dup Ver Goto 📝

lp-mpd Launchpad-to-MPD app version 1

PT2/music/midi/controllers/lp-mpd does not exist
To
387 lines, 1076 words, 10381 chars Page 'V1' does not exist.
lp_mpd
#!/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()