!7

srecord AudioSource(S description, S volume, S baseVolume, bool mute, int index) {}

cmodule ListAudioSources > DynObjectTable<AudioSource> {
  //transient SimpleLiveValue<Float> lvVolume = floatLiveValue(); // for selected source
  transient JTextField tfVolume;
  
  start {
    set fieldsInOrder;
    dontPersist();
    doEveryAndNow(10.0, r updateList);
  }
  
 
  void updateList enter {
    LS lines = lines(backtick("pactl list sources"));
    new Matches m;
    new L<AudioSource> list;
    for i over lines:
      if (swic(lines.get(i), "Source #")) {
        int idx = parseFirstInt(lines.get(i));
        AudioSource as = nu(AudioSource, index := idx, description := lines.get(i));
        for (int j = i+1; j < l(lines); j++) {
          S line = lines.get(j);
          if (!startsWithSpace(line)) break;
          line = trim(line);
          if (swic_trim(line, "Description:", m))
            as.description = m.rest();
          else if (swic_trim(line, "Base Volume:", m))
            as.baseVolume = m.rest();
          else if (swic_trim(line, "Volume:", m))
            as.volume = iround(parseFirstInt(m.rest())*100.0/65536) + "%";
          else if (swic_trim(line, "Mute:", m))
            as.mute = isYes(m.rest());
        }
        list.add(as);
      }
    setData(list);
  }
  
  visualize {
    JComponent c = super.visualize();
    onTableSelectionChanged(table, r {
      setText(tfVolume, selected() == null ? "" : firstIntAsString(selected().volume))
    });
    tfVolume = jTextField();
    onEnter(tfVolume, r {
      if (selected() == null) ret;
      backtickToConsole("pactl set-source-volume " + selected().index + " " + iround(parseFirstInt(gtt(tfVolume))/100.0*65536));
      updateList();
    });
    
    ret centerAndEastWithMargins(c, vstackWithSpacing(
      tableDependentButton_extraCondition(table, "Mute",
        rThread {
 muteSource(selected())
 }, func -> bool { !selected().mute }),
      tableDependentButton_extraCondition(table, "Unmute",
        rThread {
 unmuteSource(selected())
 }, func -> bool { selected().mute }),
      jlabel("Volume:"),
      tfVolume
,
      jbutton("Export as text", r { showText("Audio source settings", exportAsText()) })
    ));
  }
  
  // API
  
  S getSourceName(int index) {
    ret getString(objectWhere(data, +index), 'description);
  }
  
  AudioSource findSourceByName(S name) {
    ret objectWhereIC(data, description := name);
  }
  
  void muteSource(AudioSource src) {
    if (src == null) ret;
    backtickToConsole("pactl set-source-mute " + src.index + " 1");
    updateList();
  }

  void unmuteSource(AudioSource src) {
    if (src == null) ret;
    backtickToConsole("pactl set-source-mute " + src.index + " 0");
    updateList();
  }
  
  bool isSourceMuted(AudioSource src) {
    ret src != null && src.mute;
  }

  S exportAsText() {
    ret linesMap(data(), func(AudioSource src) -> S {
      (src.mute ? "Muted audio source" : "Audio source")
        + " " + quote(src.description) + " has volume " + parseFirstInt(src.volume) + "."
    });
  }
}