Fixed volume and panning for ninjam override.
This commit is contained in:
1 parent
b45f800489
commit
d3bcbbeacf
11 files changed
+412
-170
No files matched your search
@@ -22,6 +22,7 @@ import (
|
||||
type Adapter struct {
|
||||
previous screen.Snapshot
|
||||
focusID, focusText, instruction, status string
|
||||
volumePan string
|
||||
chat []string
|
||||
latest string
|
||||
regions []applicationoverrides.Region
|
||||
@@ -38,6 +39,7 @@ func (a *Adapter) Reset() {
|
||||
a.previous = screen.Snapshot{}
|
||||
a.focusID, a.focusText, a.instruction, a.status = "", "", "", ""
|
||||
a.chat, a.regions, a.historyNavigation = nil, nil, false
|
||||
a.volumePan = ""
|
||||
}
|
||||
func (a *Adapter) Close() { a.Reset(); a.latest = "" }
|
||||
|
||||
@@ -98,7 +100,7 @@ func (a *Adapter) Observe(next screen.Snapshot) applicationoverrides.Frame {
|
||||
stable := a.previous.Active && a.previous.Terminal == next.Terminal && a.previous.Generation == next.Generation && a.previous.Rows == next.Rows && a.previous.Cols == next.Cols
|
||||
chatHeight := max(next.Rows/4, 4)
|
||||
chatTop := next.Rows - 2 - chatHeight
|
||||
focusID, focusText := selected_control(next, chatTop)
|
||||
focusID, focusText, volumePan := selected_control(next, chatTop)
|
||||
if next.Y == next.Rows-3 {
|
||||
focusID, focusText = "chat", i18n.T("Chat message")
|
||||
frame.Input = &screen.Region{Left: 0, Top: next.Y, Right: next.Cols - 1, Bottom: next.Y}
|
||||
@@ -114,7 +116,19 @@ func (a *Adapter) Observe(next screen.Snapshot) applicationoverrides.Frame {
|
||||
}
|
||||
frame.FocusChanged = stable && focusID != "" && a.focusID != "" && focusID != a.focusID
|
||||
if focusText != "" && (!stable || focusText != a.focusText || focusID != a.focusID) {
|
||||
frame.Focus = []presentation.Message{{Text: focusText}}
|
||||
spoken := focusText
|
||||
if stable && focusID == a.focusID && volumePan != "" && a.volumePan != "" {
|
||||
before, after := strings.Fields(a.volumePan), strings.Fields(volumePan)
|
||||
var changed []string
|
||||
if before[0] != after[0] {
|
||||
changed = append(changed, spoken_volume(after[0]))
|
||||
}
|
||||
if before[1] != after[1] {
|
||||
changed = append(changed, spoken_pan(after[1]))
|
||||
}
|
||||
spoken = strings.Join(changed, " ")
|
||||
}
|
||||
frame.Focus = []presentation.Message{{Text: spoken}}
|
||||
}
|
||||
if instruction != "" && (!stable || instruction != a.instruction) {
|
||||
frame.Updates = append(frame.Updates, presentation.Message{Text: instruction})
|
||||
@@ -146,6 +160,7 @@ func (a *Adapter) Observe(next screen.Snapshot) applicationoverrides.Frame {
|
||||
frame.Regions = slices.Clone(a.regions)
|
||||
chatChanged := !slices.Equal(a.chat, chat)
|
||||
a.previous, a.chat = next, chat
|
||||
a.volumePan = volumePan
|
||||
a.focusID, a.focusText, a.instruction, a.status = focusID, focusText, instruction, status
|
||||
a.historyNavigation = historyNavigation && !chatChanged
|
||||
return frame
|
||||
@@ -179,9 +194,9 @@ func fields(line []rune) []field {
|
||||
return result
|
||||
}
|
||||
|
||||
func selected_control(snapshot screen.Snapshot, chatTop int) (string, string) {
|
||||
func selected_control(snapshot screen.Snapshot, chatTop int) (string, string, string) {
|
||||
if len(snapshot.Attributes) != len(snapshot.Cells) {
|
||||
return "", ""
|
||||
return "", "", ""
|
||||
}
|
||||
remote, user := false, ""
|
||||
for row := 1; row < snapshot.Rows; row++ {
|
||||
@@ -232,6 +247,7 @@ func selected_control(snapshot screen.Snapshot, chatTop int) (string, string) {
|
||||
context = i18n.Format("Local channel {0}", controls[0].value)
|
||||
}
|
||||
value := control.value
|
||||
volumePan := ""
|
||||
switch control.label {
|
||||
case "mute":
|
||||
value = i18n.T("unmuted")
|
||||
@@ -249,8 +265,9 @@ func selected_control(snapshot screen.Snapshot, chatTop int) (string, string) {
|
||||
value = i18n.T("receiving")
|
||||
}
|
||||
default:
|
||||
if strings.Contains(value, "dB") {
|
||||
value = i18n.Format("Volume and pan {0}", value)
|
||||
if parts := strings.Fields(value); len(parts) == 2 && strings.HasSuffix(parts[0], "dB") {
|
||||
volumePan = value
|
||||
value = i18n.Format("Volume and pan {0}", spoken_volume(parts[0])+" "+spoken_pan(parts[1]))
|
||||
}
|
||||
if len(controls) >= 5 && index == 0 {
|
||||
value = i18n.Format("Channel name {0}", value)
|
||||
@@ -271,10 +288,34 @@ func selected_control(snapshot screen.Snapshot, chatTop int) (string, string) {
|
||||
value = i18n.T("Quit Ninjam")
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%d:%d:%s", row, index, context), strings.TrimSpace(context + " " + value)
|
||||
return fmt.Sprintf("%d:%d:%s", row, index, context), strings.TrimSpace(context + " " + value), volumePan
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
// Keep signs and pan directions audible regardless of the punctuation profile.
|
||||
func spoken_volume(value string) string {
|
||||
if strings.HasPrefix(value, "-") {
|
||||
return i18n.Format("minus {0}", value[1:])
|
||||
}
|
||||
if strings.HasPrefix(value, "+") {
|
||||
return i18n.Format("plus {0}", value[1:])
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func spoken_pan(value string) string {
|
||||
if value == "center" {
|
||||
return i18n.T("center")
|
||||
}
|
||||
if amount, ok := strings.CutSuffix(value, "%L"); ok {
|
||||
return i18n.Format("{0} percent left", amount)
|
||||
}
|
||||
if amount, ok := strings.CutSuffix(value, "%R"); ok {
|
||||
return i18n.Format("{0} percent right", amount)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func chat_lines(snapshot screen.Snapshot, first, last int) []string {
|
||||
|
||||
@@ -5,6 +5,7 @@ package ninjam_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -93,7 +94,7 @@ func TestFocusChatMetersAndValueReflow(t *testing.T) {
|
||||
t.Fatal(got)
|
||||
}
|
||||
got = update(snapshot(1, " ", "-0.5dB center", "<Username> first", "<Username> second"))
|
||||
if !slices.Equal(got, []string{"Master Volume and pan -0.5dB center"}) {
|
||||
if !slices.Equal(got, []string{"minus 0.5dB"}) {
|
||||
t.Fatal("reflow", got)
|
||||
}
|
||||
s := snapshot(1, " ", "-0.5dB center", "<Username> first", "<Username> second")
|
||||
@@ -234,3 +235,70 @@ func TestWrappedChatRecallAndChangedGeometry(t *testing.T) {
|
||||
t.Fatal("resized interval region", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolumeAndPanAnnounceOnlyChangedComponent(t *testing.T) {
|
||||
for _, selected := range []int{1, 3, 8, 12} {
|
||||
t.Run(strconv.Itoa(selected), func(t *testing.T) {
|
||||
a, r := &ninjam.Adapter{}, reader.New()
|
||||
makeSnapshot := func(value string, chat ...string) screen.Snapshot {
|
||||
s := snapshot(selected, " ", "0.0dB center", chat...)
|
||||
// Repaint the selected bracket with the same style, including
|
||||
// changes in width at center and across signed dB values.
|
||||
for row := range s.Rows {
|
||||
for col, attribute := range s.Attributes[row*s.Cols : (row+1)*s.Cols] {
|
||||
if attribute.Background != screen.ColorIndexed|7 {
|
||||
continue
|
||||
}
|
||||
line := string(s.Line(row))
|
||||
start := col
|
||||
end := start + strings.Index(line[start:], "]")
|
||||
line = line[:start] + value + line[end:]
|
||||
for i := range s.Line(row) {
|
||||
s.Line(row)[i] = ' '
|
||||
s.Attributes[row*s.Cols+i] = screen.Attribute{Foreground: screen.ColorIndexed | 7, Background: screen.ColorIndexed}
|
||||
}
|
||||
copy(s.Line(row), []rune(line))
|
||||
for i := start; i < start+len(value); i++ {
|
||||
s.Attributes[row*s.Cols+i] = attribute
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
t.Fatal("missing selected field")
|
||||
return s
|
||||
}
|
||||
update := func(value string, chat ...string) []string {
|
||||
s := makeSnapshot(value, chat...)
|
||||
return texts(r.Update_application(s, a.Observe(s)))
|
||||
}
|
||||
update("0.0dB center", "<Username> old")
|
||||
for _, trial := range []struct{ value, want string }{
|
||||
{"0.0dB 1%L", "1 percent left"},
|
||||
{"0.0dB 2%L", "2 percent left"},
|
||||
{"0.0dB center", "center"},
|
||||
{"0.0dB 1%R", "1 percent right"},
|
||||
{"-0.5dB 1%R", "minus 0.5dB"},
|
||||
{"0.0dB 1%R", "0.0dB"},
|
||||
{"+0.5dB 1%R", "plus 0.5dB"},
|
||||
{"+1.0dB 2%R", "plus 1.0dB 2 percent right"},
|
||||
} {
|
||||
if got := update(trial.value, "<Username> old"); !slices.Equal(got, []string{trial.want}) {
|
||||
t.Fatalf("%s: %v", trial.value, got)
|
||||
}
|
||||
}
|
||||
if got := update("+1.0dB 2%R", "<Username> old"); len(got) != 0 {
|
||||
t.Fatal("unchanged value repeated", got)
|
||||
}
|
||||
if got := update("+1.0dB 3%R", "<Username> old", "<Username> new"); !slices.Equal(got, []string{"3 percent right", "<Username> new"}) {
|
||||
t.Fatal("chat alongside pan", got)
|
||||
}
|
||||
if result, _ := a.Command(command.ReadLiveRegion2); !strings.Contains(result.Text, "Volume and pan plus 1.0dB 3 percent right") {
|
||||
t.Fatal("full control recall lost", result)
|
||||
}
|
||||
r.Command(command.CurrentLine)
|
||||
if got := update("+1.0dB 4%R", "<Username> new", "<Username> review"); !slices.Equal(got, []string{"<Username> review"}) {
|
||||
t.Fatal("pan disturbed detached review", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user