add a tone test mode that bypasses the soundcard
Add -tone-test to transmit a generated 440 Hz Opus tone and save incoming audio to the file named by -tone-out. Diagnosing an audio problem previously required a working capture and playback device, which is exactly what is in question. This mode opens no OpenAL device at all, so the network and codec path can be tested on a machine with no sound hardware. Open the capture file before starting the generator. Starting transmission first left a tone goroutine running with nowhere to write when the path was unusable. Refuse file playback while in tone test mode, and ignore the microphone volume keys. Both operate on a stream that does not exist here. Stop the generator and detach the file saver on disconnect. A reconnect otherwise attached a second saver to the same file. Add -auto-transmit to key the microphone as soon as the connection is up. Useful for a bot or a monitoring client that should never need a keypress. It runs from both the connect event and the point where the audio stream is created, because the server's welcome arrives before the stream exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
17a4662c6c
commit
ecca0a63ed
+19
@@ -29,6 +29,7 @@ type Barnard struct {
|
|||||||
|
|
||||||
Stream *gumbleopenal.Stream
|
Stream *gumbleopenal.Stream
|
||||||
Tx bool
|
Tx bool
|
||||||
|
AutoTransmit bool // auto-start transmission on connect
|
||||||
Connected bool
|
Connected bool
|
||||||
|
|
||||||
Ui *uiterm.Ui
|
Ui *uiterm.Ui
|
||||||
@@ -62,6 +63,13 @@ type Barnard struct {
|
|||||||
FileStream *fileplayback.Player
|
FileStream *fileplayback.Player
|
||||||
FileStreamMutex sync.Mutex
|
FileStreamMutex sync.Mutex
|
||||||
|
|
||||||
|
// Added for tone test mode (bypasses all soundcard/OpenAL)
|
||||||
|
ToneTest bool
|
||||||
|
ToneTestOutput string
|
||||||
|
toneTestStop chan struct{}
|
||||||
|
toneTestSaver *AudioFileSaver
|
||||||
|
toneTestSaverDetach gumble.Detacher
|
||||||
|
|
||||||
// Added for recording
|
// Added for recording
|
||||||
RecordingMutex sync.Mutex
|
RecordingMutex sync.Mutex
|
||||||
Recorder *recording.Recorder
|
Recorder *recording.Recorder
|
||||||
@@ -74,6 +82,17 @@ type Barnard struct {
|
|||||||
adminACL *gumble.ACL
|
adminACL *gumble.ACL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) cleanupToneTestAudio() {
|
||||||
|
if b.toneTestSaverDetach != nil {
|
||||||
|
b.toneTestSaverDetach.Detach()
|
||||||
|
b.toneTestSaverDetach = nil
|
||||||
|
}
|
||||||
|
if b.toneTestSaver != nil {
|
||||||
|
b.toneTestSaver.Stop()
|
||||||
|
b.toneTestSaver = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Barnard) StopTransmission() {
|
func (b *Barnard) StopTransmission() {
|
||||||
if b.Tx {
|
if b.Tx {
|
||||||
b.Notify("micdown", "me", "")
|
b.Notify("micdown", "me", "")
|
||||||
|
|||||||
@@ -17,16 +17,27 @@ func (b *Barnard) start() {
|
|||||||
b.Config.Attach(gumbleutil.AutoBitrate)
|
b.Config.Attach(gumbleutil.AutoBitrate)
|
||||||
b.Config.Attach(b)
|
b.Config.Attach(b)
|
||||||
b.Config.Address = b.Address
|
b.Config.Address = b.Address
|
||||||
|
|
||||||
|
if b.ToneTest {
|
||||||
|
// Tone test mode: skip all OpenAL/soundcard initialization.
|
||||||
|
// We just need a network connection — the tone generator and
|
||||||
|
// file saver are set up in connect().
|
||||||
|
} else {
|
||||||
// test Audio
|
// test Audio
|
||||||
_, err := gumbleopenal.New(b.Client, b.UserConfig.GetInputDevice(), b.UserConfig.GetOutputDevice(), true)
|
_, err := gumbleopenal.New(b.Client, b.UserConfig.GetInputDevice(), b.UserConfig.GetOutputDevice(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.exitWithError(err)
|
b.exitWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//connect, not reconnect
|
//connect, not reconnect
|
||||||
b.connect(false)
|
b.connect(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) toneTestAutoTransmit() bool {
|
||||||
|
return b.ToneTest && b.AutoTransmit
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Barnard) exitWithError(err error) {
|
func (b *Barnard) exitWithError(err error) {
|
||||||
b.Ui.Close()
|
b.Ui.Close()
|
||||||
b.exitStatus = 1
|
b.exitStatus = 1
|
||||||
@@ -45,6 +56,31 @@ func (b *Barnard) connect(reconnect bool) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.ToneTest {
|
||||||
|
// --- Tone test mode: skip all OpenAL; generate 440 Hz tone
|
||||||
|
// --- and save incoming audio to a file.
|
||||||
|
|
||||||
|
// Open the output first. Starting transmission before this succeeds
|
||||||
|
// leaves an orphaned tone goroutine when the path is unusable.
|
||||||
|
saver, err := NewAudioFileSaver(b.ToneTestOutput)
|
||||||
|
if err != nil {
|
||||||
|
b.exitWithError(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b.toneTestSaver = saver
|
||||||
|
b.toneTestSaverDetach = b.Client.Config.AttachAudio(saver)
|
||||||
|
|
||||||
|
b.Connected = true
|
||||||
|
if b.toneTestAutoTransmit() {
|
||||||
|
b.toneTestStop = make(chan struct{})
|
||||||
|
go StartToneGenerator(b.Client, b.toneTestStop)
|
||||||
|
b.Tx = true
|
||||||
|
b.UpdateGeneralStatus(" Tx ", true)
|
||||||
|
b.AddOutputLine("Tone test transmission started")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
stream, err := gumbleopenal.New(b.Client, b.UserConfig.GetInputDevice(), b.UserConfig.GetOutputDevice(), false)
|
stream, err := gumbleopenal.New(b.Client, b.UserConfig.GetInputDevice(), b.UserConfig.GetOutputDevice(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.exitWithError(err)
|
b.exitWithError(err)
|
||||||
@@ -71,6 +107,9 @@ func (b *Barnard) connect(reconnect bool) bool {
|
|||||||
b.FileStreamMutex.Unlock()
|
b.FileStreamMutex.Unlock()
|
||||||
|
|
||||||
b.Connected = true
|
b.Connected = true
|
||||||
|
// Dial delivers OnConnect before connect creates the OpenAL stream, so
|
||||||
|
// start auto-transmit here as well for initial connections and reconnects.
|
||||||
|
b.startAutoTransmit()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +144,21 @@ func (b *Barnard) OnConnect(e *gumble.ConnectEvent) {
|
|||||||
b.AddOutputLine(fmt.Sprintf("Welcome message: %s", wmsg))
|
b.AddOutputLine(fmt.Sprintf("Welcome message: %s", wmsg))
|
||||||
}
|
}
|
||||||
b.Ui.Refresh()
|
b.Ui.Refresh()
|
||||||
|
|
||||||
|
b.startAutoTransmit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Barnard) startAutoTransmit() {
|
||||||
|
if !b.AutoTransmit || b.Tx || b.Stream == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := b.Stream.StartSource(b.UserConfig.GetInputDevice()); err != nil {
|
||||||
|
b.AddOutputLine(fmt.Sprintf("auto-transmit failed: %s", err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Tx = true
|
||||||
|
b.UpdateGeneralStatus(" AutoTx ", true)
|
||||||
|
b.AddOutputLine("Auto-transmit started")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
|
func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
|
||||||
@@ -121,6 +175,16 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
|
|||||||
reason = e.String
|
reason = e.String
|
||||||
}
|
}
|
||||||
b.stopRecordingForDisconnect()
|
b.stopRecordingForDisconnect()
|
||||||
|
|
||||||
|
// Tone test cleanup
|
||||||
|
if b.ToneTest {
|
||||||
|
if b.toneTestStop != nil {
|
||||||
|
close(b.toneTestStop)
|
||||||
|
b.toneTestStop = nil
|
||||||
|
}
|
||||||
|
b.cleanupToneTestAudio()
|
||||||
|
}
|
||||||
|
|
||||||
b.Notify("disconnect", "me", reason)
|
b.Notify("disconnect", "me", reason)
|
||||||
if reason == "" {
|
if reason == "" {
|
||||||
b.AddOutputLine("Disconnected")
|
b.AddOutputLine("Disconnected")
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ func main() {
|
|||||||
jitterBuffer := flag.Int("jitter-buffer", 40, "incoming per-user audio buffer in ms (0, 20, 40, or 60)")
|
jitterBuffer := flag.Int("jitter-buffer", 40, "incoming per-user audio buffer in ms (0, 20, 40, or 60)")
|
||||||
profile := flag.Bool("profile", false, "add http server to serve profiles")
|
profile := flag.Bool("profile", false, "add http server to serve profiles")
|
||||||
noiseSuppressionEnabled := flag.Bool("noise-suppression", false, "enable noise suppression for microphone input")
|
noiseSuppressionEnabled := flag.Bool("noise-suppression", false, "enable noise suppression for microphone input")
|
||||||
|
autoTransmit := flag.Bool("auto-transmit", false, "start transmitting immediately on connect")
|
||||||
|
toneTest := flag.Bool("tone-test", false, "send a 440 Hz test tone instead of microphone (bypasses soundcard)")
|
||||||
|
toneTestOutput := flag.String("tone-out", "incoming.pcm", "file to save incoming audio to in tone-test mode")
|
||||||
tcpOnly := flag.Bool("tcp", false, "disable UDP, force audio through TCP tunnel")
|
tcpOnly := flag.Bool("tcp", false, "disable UDP, force audio through TCP tunnel")
|
||||||
logLevel := flag.String("log", "warn", "log level: debug, info, warn, error")
|
logLevel := flag.String("log", "warn", "log level: debug, info, warn, error")
|
||||||
logFile := flag.String("logfile", "", "write logs to this file (logging is disabled when omitted)")
|
logFile := flag.String("logfile", "", "write logs to this file (logging is disabled when omitted)")
|
||||||
@@ -194,6 +197,9 @@ func main() {
|
|||||||
Config: gumble.NewConfig(),
|
Config: gumble.NewConfig(),
|
||||||
UserConfig: userConfig,
|
UserConfig: userConfig,
|
||||||
Address: *server,
|
Address: *server,
|
||||||
|
AutoTransmit: *autoTransmit,
|
||||||
|
ToneTest: *toneTest,
|
||||||
|
ToneTestOutput: *toneTestOutput,
|
||||||
MutedChannels: make(map[uint32]bool),
|
MutedChannels: make(map[uint32]bool),
|
||||||
NoiseSuppressor: noise.NewSuppressor(),
|
NoiseSuppressor: noise.NewSuppressor(),
|
||||||
}
|
}
|
||||||
|
|||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 440 Hz tone generator
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// StartToneGenerator begins generating a 440 Hz sine wave and writing it to
|
||||||
|
// the client's outgoing audio channel at the configured interval. It blocks
|
||||||
|
// until the stop channel is closed.
|
||||||
|
func StartToneGenerator(client *gumble.Client, stop <-chan struct{}) {
|
||||||
|
interval := client.Config.AudioInterval
|
||||||
|
frameSize := client.Config.AudioFrameSize() // mono samples per frame
|
||||||
|
sampleRate := float64(gumble.AudioSampleRate)
|
||||||
|
frequency := 440.0
|
||||||
|
|
||||||
|
// Pre-compute one full sine wave cycle so we can just index into it.
|
||||||
|
// This avoids calling math.Sin in the hot loop.
|
||||||
|
phase := 0.0
|
||||||
|
phaseIncrement := 2.0 * math.Pi * frequency / sampleRate
|
||||||
|
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
outgoing := client.AudioOutgoing()
|
||||||
|
defer close(outgoing)
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "tonetest: starting 440 Hz tone generator (frameSize=%d, interval=%v)\n",
|
||||||
|
frameSize, interval)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
fmt.Fprintf(os.Stderr, "tonetest: tone generator stopped\n")
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
buf := make([]int16, frameSize)
|
||||||
|
for i := 0; i < frameSize; i++ {
|
||||||
|
// Generate sine wave with amplitude 0.5 to avoid clipping
|
||||||
|
buf[i] = int16(math.Sin(phase) * 16000) // ~ -6dBFS
|
||||||
|
phase += phaseIncrement
|
||||||
|
if phase > 2.0*math.Pi {
|
||||||
|
phase -= 2.0 * math.Pi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outgoing <- gumble.AudioBuffer(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Incoming audio file saver
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// AudioFileSaver implements gumble.AudioListener and writes all incoming PCM
|
||||||
|
// audio to a single raw 16-bit little-endian stereo 48kHz file.
|
||||||
|
type AudioFileSaver struct {
|
||||||
|
file *os.File
|
||||||
|
stop chan struct{}
|
||||||
|
mu sync.Mutex
|
||||||
|
stopped bool
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAudioFileSaver creates the output file and returns a configured saver.
|
||||||
|
// The file is raw PCM: s16le, stereo, 48000 Hz.
|
||||||
|
// Play it back with:
|
||||||
|
//
|
||||||
|
// ffplay -f s16le -ar 48000 -ac 2 <file>
|
||||||
|
//
|
||||||
|
// or convert with:
|
||||||
|
//
|
||||||
|
// ffmpeg -f s16le -ar 48000 -ac 2 -i <file> output.wav
|
||||||
|
func NewAudioFileSaver(path string) (*AudioFileSaver, error) {
|
||||||
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "tonetest: saving incoming audio to %s\n", path)
|
||||||
|
return &AudioFileSaver{
|
||||||
|
file: f,
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop closes the stop channel, waits for all stream goroutines to finish,
|
||||||
|
// and closes the output file.
|
||||||
|
func (s *AudioFileSaver) Stop() {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.stopped {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.stopped = true
|
||||||
|
close(s.stop)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
s.wg.Wait()
|
||||||
|
s.mu.Lock()
|
||||||
|
_ = s.file.Close()
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnAudioStream implements gumble.AudioListener.
|
||||||
|
func (s *AudioFileSaver) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.stopped {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.wg.Add(1)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "tonetest: incoming audio stream from %s\n", e.User.Name)
|
||||||
|
go func() {
|
||||||
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.stop:
|
||||||
|
return
|
||||||
|
case packet, ok := <-e.C:
|
||||||
|
if !ok {
|
||||||
|
fmt.Fprintf(os.Stderr, "tonetest: audio stream from %s ended\n", e.User.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
samples := packet.AudioBuffer
|
||||||
|
if len(samples) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write as raw PCM s16le. The decoder outputs stereo
|
||||||
|
// interleaved, so the sample count already accounts for
|
||||||
|
// both channels.
|
||||||
|
buf := make([]byte, len(samples)*2)
|
||||||
|
for i, s := range samples {
|
||||||
|
binary.LittleEndian.PutUint16(buf[i*2:], uint16(s))
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
if _, err := s.file.Write(buf); err != nil {
|
||||||
|
s.mu.Unlock()
|
||||||
|
fmt.Fprintf(os.Stderr, "tonetest: write error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regression: tone transmission was started before opening its output file,
|
||||||
|
// so a creation error left audio transmission running without cleanup.
|
||||||
|
// Regression: tone test transmitted immediately, preventing the configured
|
||||||
|
// talk key from controlling it.
|
||||||
|
func TestToneTestRequiresAutoTransmit(t *testing.T) {
|
||||||
|
if (&Barnard{ToneTest: true}).toneTestAutoTransmit() {
|
||||||
|
t.Fatal("tone test started without auto-transmit")
|
||||||
|
}
|
||||||
|
if !(&Barnard{ToneTest: true, AutoTransmit: true}).toneTestAutoTransmit() {
|
||||||
|
t.Fatal("tone test did not auto-transmit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAudioFileSaverReportsUnavailableOutputPath(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), "missing", "tone.pcm")
|
||||||
|
if saver, err := NewAudioFileSaver(path); err == nil || saver != nil {
|
||||||
|
t.Fatalf("got saver=%v err=%v", saver, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAudioFileSaverDoesNotOverwriteExistingOutput(t *testing.T) {
|
||||||
|
path := filepath.Join(t.TempDir(), "tone.pcm")
|
||||||
|
if err := os.WriteFile(path, []byte("existing"), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if saver, err := NewAudioFileSaver(path); err == nil || saver != nil {
|
||||||
|
t.Fatalf("got saver=%v err=%v", saver, err)
|
||||||
|
}
|
||||||
|
contents, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(contents) != "existing" {
|
||||||
|
t.Fatalf("output was overwritten: %q", contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDetacher struct{ detached bool }
|
||||||
|
|
||||||
|
func (d *testDetacher) Detach() { d.detached = true }
|
||||||
|
|
||||||
|
// Regression: reconnecting tone-test mode kept prior savers attached to the
|
||||||
|
// shared audio listener list, causing callbacks to write to closed files.
|
||||||
|
func TestCleanupToneTestAudioDetachesSaver(t *testing.T) {
|
||||||
|
saver, err := NewAudioFileSaver(filepath.Join(t.TempDir(), "tone.pcm"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
detacher := &testDetacher{}
|
||||||
|
b := &Barnard{toneTestSaver: saver, toneTestSaverDetach: detacher}
|
||||||
|
|
||||||
|
b.cleanupToneTestAudio()
|
||||||
|
if !detacher.detached {
|
||||||
|
t.Fatal("tone saver listener was not detached")
|
||||||
|
}
|
||||||
|
if b.toneTestSaver != nil || b.toneTestSaverDetach != nil {
|
||||||
|
t.Fatal("tone saver cleanup retained connection state")
|
||||||
|
}
|
||||||
|
b.cleanupToneTestAudio()
|
||||||
|
}
|
||||||
@@ -245,6 +245,10 @@ func (b *Barnard) CommandPlayFile(ui *uiterm.Ui, cmd string) {
|
|||||||
b.AddOutputLine("Not connected to server")
|
b.AddOutputLine("Not connected to server")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if b.ToneTest {
|
||||||
|
b.AddOutputLine("File playback is unavailable in tone test mode")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
b.FileStreamMutex.Lock()
|
b.FileStreamMutex.Lock()
|
||||||
defer b.FileStreamMutex.Unlock()
|
defer b.FileStreamMutex.Unlock()
|
||||||
@@ -319,7 +323,14 @@ func (b *Barnard) setTransmit(ui *uiterm.Ui, val int) {
|
|||||||
b.Notify("micdown", "me", "")
|
b.Notify("micdown", "me", "")
|
||||||
b.Tx = false
|
b.Tx = false
|
||||||
b.UpdateGeneralStatus(" Idle ", false)
|
b.UpdateGeneralStatus(" Idle ", false)
|
||||||
|
if b.ToneTest {
|
||||||
|
if b.toneTestStop != nil {
|
||||||
|
close(b.toneTestStop)
|
||||||
|
b.toneTestStop = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
b.Stream.StopSource()
|
b.Stream.StopSource()
|
||||||
|
}
|
||||||
} else if b.Connected == false {
|
} else if b.Connected == false {
|
||||||
b.Notify("error", "me", "no tx while disconnected")
|
b.Notify("error", "me", "no tx while disconnected")
|
||||||
b.Tx = false
|
b.Tx = false
|
||||||
@@ -331,6 +342,12 @@ func (b *Barnard) setTransmit(ui *uiterm.Ui, val int) {
|
|||||||
b.UpdateGeneralStatus("cannot transmit in muted channel", true)
|
b.UpdateGeneralStatus("cannot transmit in muted channel", true)
|
||||||
} else {
|
} else {
|
||||||
b.Tx = true
|
b.Tx = true
|
||||||
|
if b.ToneTest {
|
||||||
|
b.toneTestStop = make(chan struct{})
|
||||||
|
go StartToneGenerator(b.Client, b.toneTestStop)
|
||||||
|
b.Notify("micup", "me", "")
|
||||||
|
b.UpdateGeneralStatus(" Tx ", true)
|
||||||
|
} else {
|
||||||
err := b.Stream.StartSource(b.UserConfig.GetInputDevice())
|
err := b.Stream.StartSource(b.UserConfig.GetInputDevice())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Notify("error", "me", err.Error())
|
b.Notify("error", "me", err.Error())
|
||||||
@@ -340,9 +357,13 @@ func (b *Barnard) setTransmit(ui *uiterm.Ui, val int) {
|
|||||||
b.UpdateGeneralStatus(" Tx ", true)
|
b.UpdateGeneralStatus(" Tx ", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Barnard) OnMicVolumeDown(ui *uiterm.Ui, key uiterm.Key) {
|
func (b *Barnard) OnMicVolumeDown(ui *uiterm.Ui, key uiterm.Key) {
|
||||||
|
if b.ToneTest {
|
||||||
|
return
|
||||||
|
}
|
||||||
b.Stream.SetMicVolume(-0.1, true)
|
b.Stream.SetMicVolume(-0.1, true)
|
||||||
b.UserConfig.SetMicVolume(b.Stream.GetMicVolume())
|
b.UserConfig.SetMicVolume(b.Stream.GetMicVolume())
|
||||||
if err := b.UserConfig.SaveConfig(); err != nil {
|
if err := b.UserConfig.SaveConfig(); err != nil {
|
||||||
@@ -351,6 +372,9 @@ func (b *Barnard) OnMicVolumeDown(ui *uiterm.Ui, key uiterm.Key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Barnard) OnMicVolumeUp(ui *uiterm.Ui, key uiterm.Key) {
|
func (b *Barnard) OnMicVolumeUp(ui *uiterm.Ui, key uiterm.Key) {
|
||||||
|
if b.ToneTest {
|
||||||
|
return
|
||||||
|
}
|
||||||
b.Stream.SetMicVolume(0.1, true)
|
b.Stream.SetMicVolume(0.1, true)
|
||||||
b.UserConfig.SetMicVolume(b.Stream.GetMicVolume())
|
b.UserConfig.SetMicVolume(b.Stream.GetMicVolume())
|
||||||
if err := b.UserConfig.SaveConfig(); err != nil {
|
if err := b.UserConfig.SaveConfig(); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user