17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-30 00:27:29 +01:00
Files
maszyna/audio/fmradio.h
IwanXXI b4b1e6c32f Broadcast radio receiver in the cab
Plays internet radio streams (http, mp3) through a positional emitter in the cab, with analog tuning, inter-station noise and two speaker profiles. Stations are listed in fmradio.txt, cab controls are defined with fmradio entries in the .mmd file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:14:32 +02:00

142 lines
5.9 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "audio/audio.h"
#include "audio/radiostream.h"
namespace audio
{
float const EU07_FMRADIO_BANDSTART{87.5f}; // MHz
float const EU07_FMRADIO_BANDEND{108.f}; // MHz
// kind of the receiver, determines character of its sound
enum class fmradio_type
{
cheap, // pocket-sized plastic receiver with a tiny speaker
wooden // old table receiver with a large speaker in a wooden cabinet
};
// broadcast radio receiver installed in a vehicle cab.
// the stations are internet radio streams assigned to frequencies of the fm band in a text file.
// the sound is generated piece by piece from the stream and synthesized noise, then played through a positional emitter
// owned by the receiver, as this doesn't fit the regular buffer-based sound pipeline
class fm_radio
{
public:
// constructors
fm_radio() = default;
// methods
// owner-side interface, to be called by the vehicle the receiver is installed in.
// NOTE: the owner is expected to call power() on regular basis, a receiver left without supervision turns itself off
void power(bool const State);
// sets frequency the receiver is tuned to, in MHz
void tune(float const Frequency);
// sets position of the volume knob, in range 0-1
void volume(float const Volume);
void type(fmradio_type const Type);
// sets location of the receiver in the simulation region space, and audibility parameters at the current position of the listener
void placement(glm::dvec3 const &Location, float const Soundproofing, float const Range);
// returns frequency of the first station on the list, or middle of the band if there's none
float default_frequency();
// renderer-side interface
// updates state of the receiver. expects active audio context
void update(double const Deltatime, glm::dvec3 const &Listener);
// stops the reception and frees implementation-side resources. expects active audio context
void release();
private:
// types
struct control_block
{
bool power{false};
float frequency{EU07_FMRADIO_BANDSTART};
float volume{0.f};
fmradio_type type{fmradio_type::cheap};
glm::dvec3 location{0.0};
float soundproofing{1.f};
float range{15.f};
double unattended{0.0}; // time since the last call from the owner
};
struct station
{
std::string name;
std::string url;
float frequency{0.f}; // MHz
};
// second order filter section
struct biquad
{
float b0{1.f}, b1{0.f}, b2{0.f}, a1{0.f}, a2{0.f};
float z1{0.f}, z2{0.f};
float process(float const Input)
{
auto const output{b0 * Input + z1};
z1 = b1 * Input - a1 * output + z2;
z2 = b2 * Input - a2 * output;
return output;
}
static biquad highpass(float const Rate, float const Frequency, float const Q);
static biquad lowpass(float const Rate, float const Frequency, float const Q);
static biquad peak(float const Rate, float const Frequency, float const Q, float const Gain);
};
// methods
void load_stations();
// returns index of the station which can be received at specified frequency, or -1
int find_station(float const Frequency) const;
// selects the station to receive, taking into account movement of the tuning knob
void update_tuning(control_block const &Control, double const Deltatime);
// prepares speaker simulation for specified receiver
void setup_speaker(fmradio_type const Type);
// fills the staging area with next piece of the receiver output
void generate_chunk(control_block const &Control, std::size_t const Samplecount);
bool create_emitter();
// stops the playback and takes back all sound buffers
void reset_emitter();
// collects buffers which were played back. returns: number of buffers still waiting in the queue
int reclaim_buffers();
// members
std::mutex m_controlmutex;
control_block m_control; // settings requested by the owner
std::mutex m_stationsmutex;
std::vector<station> m_stations;
bool m_stationsloaded{false};
radio_stream m_stream;
bool m_active{false}; // the receiver is switched on
int m_station{-1}; // station the stream is connected with
int m_candidate{-1}; // station at the current position of the tuning knob
double m_candidatetime{0.0}; // time the knob has spent at the candidate station
double m_lingertime{0.0}; // time since the knob has left the station the stream is connected with
bool m_buffering{true}; // station audio is held until there's enough data to keep it going
float m_reception{0.f}; // current quality of the station audio, 0-1
float m_carrier{0.f}; // current strength of the station signal, 0-1
bool m_locked{false}; // the receiver is locked on a station signal
int m_thumpposition{-1}; // progress of the sound made when the receiver locks on a station or loses it, in samples. negative if the sound isn't playing
double m_inputphase{0.0}; // sample rate conversion, position between two most recent samples of station audio
std::array<std::int16_t, 2> m_inputsamples{0, 0}; // sample rate conversion, two most recent samples of station audio
fmradio_type m_speakertype{fmradio_type::cheap};
std::vector<biquad> m_speaker; // filters simulating the speaker and the case
float m_speakerdrive{1.f}; // signal level at the input of the simulated amplifier
float m_speakergain{1.f}; // make-up gain
std::uint32_t m_noisestate{0x2545f491}; // state of the noise generator
bool m_emitterfailed{false}; // helper, prevents flooding the log with the same error
ALuint m_source{audio::null_resource};
std::vector<ALuint> m_buffers; // all sound buffers owned by the receiver
std::vector<ALuint> m_sparebuffers; // sound buffers which aren't queued for playback
std::vector<std::int16_t> m_chunk; // helper, staging area for audio data
std::vector<std::int16_t> m_input; // helper, staging area for audio data
std::vector<float> m_mix; // helper, staging area for audio data
};
} // namespace audio