package audio // Playing a message through the RADIO instead of a sound card. // // A SunSDR takes its transmit audio over TCI, on the same socket as the // commands, so the voice keyer can hand it the message directly: no virtual // cable, no second sound card, no Windows mixer between the recording and the // air. To everything above, that radio is simply another output device. // // The device it presents itself as is a name rather than a WASAPI endpoint id, // which is why Play checks for it before opening anything: there is no endpoint // to open, and asking Windows for one produces a confusing error about a device // that does not exist rather than the truth, which is that nothing is connected // to the radio. import ( "errors" "sync" ) // NetworkDeviceID is the id the radio-over-network output carries in the // settings and in the device lists. A fixed string, not a Windows endpoint id: // it is chosen by us and must survive a radio being switched off and on. const NetworkDeviceID = "net:radio" // NetworkPlayer sends already-decoded PCM to the radio, returning when the // message has been played or when stop is closed. // // It carries the same arguments as the sound-card path so that Play can hand // over whatever it read, and the radio can decide what converting it needs — // the sample rate a WAV was recorded at is not the radio's business until the // moment it has to be resampled. type NetworkPlayer func(pcm []byte, rate, ch, bits int, stop <-chan struct{}) error var ( netMu sync.RWMutex netPlayer NetworkPlayer ) // SetNetworkPlayer installs (or clears, with nil) the radio's transmit path. // // Package-level rather than per-Manager: there is one radio, the CAT backend // owns it, and a Manager that happened to be built before the radio connected // would otherwise be permanently unable to reach it. func SetNetworkPlayer(fn NetworkPlayer) { netMu.Lock() netPlayer = fn netMu.Unlock() } // networkPlayer returns the installed player, or nil. func networkPlayer() NetworkPlayer { netMu.RLock() defer netMu.RUnlock() return netPlayer } // NetworkPlayerReady says whether a radio is currently able to take transmit // audio, so the settings panel can offer the option honestly rather than // listing a device that would fail when used. func NetworkPlayerReady() bool { return networkPlayer() != nil } // errNoNetworkRadio is what a message played to a radio that is not there // comes back with. Named, because "the device could not be opened" would send // an operator hunting through Windows sound settings for a device that never // existed. var errNoNetworkRadio = errors.New("no radio is connected to take the audio — check the CAT link (the radio output only works with a TCI radio)")