Skip to content

Commit

Permalink
brclient: first version of audio notes
Browse files Browse the repository at this point in the history
  • Loading branch information
miki committed Oct 16, 2024
1 parent 98f2c45 commit 2c91042
Show file tree
Hide file tree
Showing 6 changed files with 508 additions and 0 deletions.
9 changes: 9 additions & 0 deletions brclient/appstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/companyzero/bisonrelay/client/resources/simplestore"
"github.com/companyzero/bisonrelay/client/rpcserver"
"github.com/companyzero/bisonrelay/clientrpc/types"
"github.com/companyzero/bisonrelay/internal/audio"
"github.com/companyzero/bisonrelay/internal/mdembeds"
"github.com/companyzero/bisonrelay/internal/strescape"
"github.com/companyzero/bisonrelay/internal/tlsconn"
Expand Down Expand Up @@ -220,6 +221,8 @@ type appState struct {
ssPayType simpleStorePayType
ssAcct string
ssShipCharge float64

noterec *audio.NoteRecorder
}

type appStateErr struct {
Expand Down Expand Up @@ -3946,6 +3949,11 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer,
})
go r.Run(ctx)

noterec, err := audio.NewRecorder(logBknd.logger("AREC"))
if err != nil {
return nil, fmt.Errorf("unable to init audio subsystem: %v", err)
}

ctx, cancel := context.WithCancel(context.Background())
as = &appState{
ctx: ctx,
Expand All @@ -3963,6 +3971,7 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer,
lnRouter: lnRouter,
httpClient: &httpClient,
rates: r,
noterec: noterec,

network: args.Network,
isRestore: isRestore,
Expand Down
196 changes: 196 additions & 0 deletions brclient/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/companyzero/bisonrelay/client"
"github.com/companyzero/bisonrelay/client/clientdb"
"github.com/companyzero/bisonrelay/client/clientintf"
"github.com/companyzero/bisonrelay/internal/audio"
"github.com/companyzero/bisonrelay/internal/strescape"
"github.com/companyzero/bisonrelay/zkidentity"
"github.com/decred/dcrd/dcrutil/v4"
Expand Down Expand Up @@ -3549,6 +3550,183 @@ var myAvatarCmds = []tuicmd{
},
}

var audioCmds = []tuicmd{
{
cmd: "devices",
aliases: []string{"listdevices"},
descr: "List capture and playback devices",
usableOffline: true,
handler: func(args []string, as *appState) error {
devices, err := audio.ListAudioDevices(as.log)
if err != nil {
return err
}

as.manyDiagMsgsCb(func(pf printf) {
printDevice := func(i int, dev *audio.Device) {
defaultStr := ""
if dev.IsDefault {
defaultStr = "(default) "
}
pf("Device %d %s%s", i, defaultStr, dev.Name)
pf("ID: %s", strescape.Nick(dev.ID))
pf("")
}

pf("")
if len(devices.Capture) == 0 {
pf("No audio capture devices found")
} else {
pf("Audio capture devices")
pf("")
for i := range devices.Capture {
printDevice(i, &devices.Capture[i])
}
}

if len(devices.Playback) == 0 {
pf("No audio playback devices found")
} else {
pf("Audio playback devices")
pf("")
for i := range devices.Playback {
printDevice(i, &devices.Playback[i])
}
}
})

return nil
},
}, {
cmd: "capturedevice",
usableOffline: true,
aliases: []string{"capdevice", "cdevice", "capdev"},
usage: "Select the capture device",
descr: "[<device index>]",
handler: func(args []string, as *appState) error {
if len(args) == 0 {
as.noterec.SetCaptureDevice(nil)
as.diagMsg("Using default device for audio capture")
return nil
}

devIndex, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
return usageError{msg: "Argument not a number"}
}
if devIndex < 0 {
return usageError{msg: "Device index cannot be negative"}
}

devices, err := audio.ListAudioDevices(as.log)
if err != nil {
return err
}
if devIndex >= int64(len(devices.Capture)) {
return fmt.Errorf("device %d does not exist", devIndex)
}

err = as.noterec.SetCaptureDevice(&devices.Capture[devIndex])
return err
},
}, {
cmd: "playbackdevice",
usableOffline: true,
aliases: []string{"playdevice", "pdevice", "playdev"},
usage: "Select the playback device",
descr: "[<device index>]",
handler: func(args []string, as *appState) error {
if len(args) == 0 {
as.noterec.SetPlaybackDevice(nil)
as.diagMsg("Using default device for audio capture")
return nil
}

devIndex, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
return usageError{msg: "Argument not a number"}
}
if devIndex < 0 {
return usageError{msg: "Device index cannot be negative"}
}

devices, err := audio.ListAudioDevices(as.log)
if err != nil {
return err
}
if devIndex >= int64(len(devices.Playback)) {
return fmt.Errorf("device %d does not exist", devIndex)
}

err = as.noterec.SetPlaybackDevice(&devices.Playback[devIndex])
return err
},
}, {
cmd: "send",
descr: "Send an audio note",
usage: "[<target>]",
handler: func(args []string, as *appState) error {
var targetId clientintf.UserID
var targetIsGC bool

if len(args) > 0 {
uid, err := as.c.UIDByNick(args[0])
if err == nil {
targetId = uid
} else if gcid, err := as.c.GCIDByName(args[0]); err == nil {
targetId = gcid
targetIsGC = true
} else {
return usageError{"Target user or GC not found"}
}
} else {
cw := as.activeChatWindow()
if cw == nil || cw.isPage {
return usageError{"No target specified"}
}
if cw.isGC {
targetId = cw.gc
targetIsGC = true
} else {
targetId = cw.uid
}
}

as.sendMsg(msgSendAudioNote{targetID: targetId, targetIsGC: targetIsGC})
return nil
},
completer: func(args []string, arg string, as *appState) []string {
if len(args) == 0 {
return nickCompleter(arg, as)
}
return nil
},
}, {
cmd: "test",
usableOffline: true,
descr: "Record and playback a 3-second test note",
handler: func(args []string, as *appState) error {
go func() {
as.diagMsg("Starting 3 second capture")
ctx, cancel := context.WithTimeout(as.ctx, 3*time.Second)
defer cancel()
err := as.noterec.Capture(ctx)
if err != nil {
as.diagMsg("Error capturing audio: %v", err)
return
}

as.diagMsg("Starting playback")
err = as.noterec.Playback(as.ctx)
if err != nil {
as.diagMsg("Error playing back audio: %v", err)
}
}()
return nil
},
},
}

var commands = []tuicmd{
{
cmd: "backup",
Expand Down Expand Up @@ -4279,6 +4457,12 @@ var commands = []tuicmd{
as.cwHelpMsg("Cleared payment stats%s", forUser)
return nil
},
completer: func(args []string, arg string, as *appState) []string {
if len(args) == 0 {
return nickCompleter(arg, as)
}
return nil
},
}, {
cmd: "info",
usableOffline: true,
Expand Down Expand Up @@ -4452,6 +4636,18 @@ var commands = []tuicmd{
}()
return nil
},
}, {
cmd: "audio",
usableOffline: true,
descr: "Audio-related commands",
sub: audioCmds,
completer: func(args []string, arg string, as *appState) []string {
if len(args) == 0 {
return cmdCompleter(audioCmds, arg, false)
}
return nil
},
handler: subcmdNeededHandler,
}, {
cmd: "quit",
usableOffline: true,
Expand Down
3 changes: 3 additions & 0 deletions brclient/mainwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,9 @@ func (mws mainWindowState) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
mws.completeIdx = 0
}

case msgSendAudioNote:
return newSendAudioNoteWindow(mws.as, msg)

default:
// Handle other messages.
mws.textArea, cmd = mws.textArea.Update(msg)
Expand Down
19 changes: 19 additions & 0 deletions brclient/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ type msgUnwelcomeError struct {

type msgReplaceCmd string

type msgRecordNote struct{}
type msgPlaybackNote struct{}
type msgRefreshAudioNoteUI struct{}
type msgRecordComplete struct{}
type msgPlaybackComplete struct{}
type msgAudioError error

func paste() tea.Msg {
str, err := clipboard.ReadAll()
if err != nil {
Expand Down Expand Up @@ -194,6 +201,13 @@ func emitMsg(msg tea.Msg) tea.Cmd {
}
}

func emitAfter(msg tea.Msg, delay time.Duration) tea.Cmd {
return func() tea.Msg {
time.Sleep(delay)
return msg
}
}

type msgRunCmd func() tea.Msg

type msgExternalCommentResult struct {
Expand All @@ -202,6 +216,11 @@ type msgExternalCommentResult struct {
parent *zkidentity.ShortID
}

type msgSendAudioNote struct {
targetID clientintf.UserID
targetIsGC bool
}

// isQuitMsg returns true if the app should quit as a response to the given
// msg. It returns an error with the reason for quitting.
func isQuitMsg(msg tea.Msg) error {
Expand Down
Loading

0 comments on commit 2c91042

Please sign in to comment.