mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 12:48:35 +01:00
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>
This commit is contained in:
141
audio/fmradio.h
Normal file
141
audio/fmradio.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
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
|
||||
Reference in New Issue
Block a user