Skip to content

Commit

Permalink
ASE: implement mute/solo/volume for tracks
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Westerfeld <[email protected]>
  • Loading branch information
swesterfeld committed Sep 23, 2023
1 parent 734b635 commit 0dc2e50
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 3 deletions.
6 changes: 6 additions & 0 deletions ase/api.hh
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@ class Track : public virtual Device {
public:
virtual int32 midi_channel () const = 0; ///< Midi channel assigned to this track, 0 uses internal per-track channel.
virtual void midi_channel (int32 midichannel) = 0;
virtual bool mute () const = 0; ///< Whether the track is muted
virtual void mute (bool newmute) = 0;
virtual bool solo () const = 0; ///< Whether the track is solo
virtual void solo (bool newsolo) = 0;
virtual double volume () const = 0; ///< Volume of the track [0..1]
virtual void volume (double newvolume) = 0;
virtual bool is_master () const = 0; ///< Flag set on the main output track.
virtual ClipS launcher_clips () = 0; ///< Retrieve the list of clips that can be directly played.
virtual DeviceP access_device () = 0; ///< Retrieve Device handle for this track.
Expand Down
40 changes: 37 additions & 3 deletions ase/combo.cc
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@ AudioChain::initialize (SpeakerArrangement busses)

void
AudioChain::reset (uint64 target_stamp)
{}
{
volume_smooth_.reset (sample_rate(), 0.020);
volume_smooth_.set (volume_, true);
}

uint
AudioChain::schedule_children()
Expand Down Expand Up @@ -205,12 +208,28 @@ AudioChain::render (uint n_frames)
else
{
const float *cblock = last_output_->ofloats (OUT1, std::min (c, nlastchannels - 1));
redirect_oblock (OUT1, c, cblock);
float *output_block = oblock (OUT1, c);
if (volume_smooth_.is_constant())
{
float v = volume_smooth_.get_next();
v = v * v * v;
for (uint i = 0; i < n_frames; i++)
output_block[i] = cblock[i] * v;
}
else
{
for (uint i = 0; i < n_frames; i++)
{
float v = volume_smooth_.get_next();
v = v * v * v;
output_block[i] = cblock[i] * v;
}
}
if (probes)
{
// SPL = 20 * log10 (root_mean_square (p) / p0) dB ; https://en.wikipedia.org/wiki/Sound_pressure#Sound_pressure_level
// const float sqrsig = square_sum (n_frames, cblock) / n_frames; // * 1.0 / p0^2
const float sqrsig = square_max (n_frames, cblock);
const float sqrsig = square_max (n_frames, output_block);
const float log2div = 3.01029995663981; // 20 / log2 (10) / 2.0
const float db_spl = ISLIKELY (sqrsig > 0.0) ? log2div * fast_log2 (sqrsig) : -192;
(*probes)[c].dbspl = db_spl;
Expand All @@ -220,6 +239,21 @@ AudioChain::render (uint n_frames)
// FIXME: assign obus if no children are present
}

void
AudioChain::volume (float new_volume)
{
/* compute volume factor so that volume_ * volume_ * volume_ is in range [0..2] */
const float cbrt_2 = 1.25992104989487; /* 2^(1/3) */
volume_ = new_volume * cbrt_2;
volume_smooth_.set (volume_);
}

float
AudioChain::volume_db (float volume)
{
return voltage2db (2 * volume * volume * volume);
}

/// Reconnect AudioChain child processors at start and after.
void
AudioChain::reconnect (size_t index, bool insertion)
Expand Down
5 changes: 5 additions & 0 deletions ase/combo.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#define __ASE_COMBO_HH__

#include <ase/processor.hh>
#include <devices/blepsynth/linearsmooth.hh>

namespace Ase {

Expand All @@ -29,6 +30,8 @@ class AudioChain : public AudioCombo {
const SpeakerArrangement ospeakers_ = SpeakerArrangement (0);
InletP inlet_;
AudioProcessor *last_output_ = nullptr;
float volume_ = 0;
LinearSmooth volume_smooth_;
protected:
void initialize (SpeakerArrangement busses) override;
void reset (uint64 target_stamp) override;
Expand All @@ -42,6 +45,8 @@ public:
struct Probe { float dbspl = -192; };
using ProbeArray = std::array<Probe,2>;
ProbeArray* run_probes (bool enable);
void volume (float new_volume);
static float volume_db (float volume);
static void static_info (AudioProcessorInfo &info);
private:
ProbeArray *probes_ = nullptr;
Expand Down
65 changes: 65 additions & 0 deletions ase/track.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ TrackImpl::_activate ()
DeviceImpl::_activate();
midi_prod_->_activate();
chain_->_activate();
set_chain_volumes();
}

void
Expand Down Expand Up @@ -168,6 +169,70 @@ TrackImpl::midi_channel (int32 midichannel) // TODO: implement
emit_notify ("midi_channel");
}

void
TrackImpl::mute (bool new_mute)
{
mute_ = new_mute;
set_chain_volumes();
emit_notify ("mute");
}

void
TrackImpl::solo (bool new_solo)
{
solo_ = new_solo;
set_chain_volumes();
emit_notify ("solo");
}

void
TrackImpl::volume (double new_volume)
{
volume_ = new_volume;
// TODO: display this value if track volume is changed in the UI
// printf ("Track '%s' -> set volume to %f dB\n", name().c_str(), AudioChain::volume_db (new_volume));
set_chain_volumes();
emit_notify ("volume");
}

void
TrackImpl::set_chain_volumes()
{
Ase::Project *project = dynamic_cast<Ase::Project*> (_project());
if (!project)
return;

/* due to mute / solo, the volume of each track depends on its own volume and
* the mute/solo settings of all other tracks so we update all volumes
* together in this function (note: if we had automation we might want to do
* it differently if only one track volume changes)
*/
auto all_tracks = project->all_tracks();

bool have_solo_tracks = false;
for (const auto& track : all_tracks)
have_solo_tracks = have_solo_tracks || track->solo();

for (const auto& track : all_tracks)
{
auto track_impl = dynamic_cast<Ase::TrackImpl*> (track.get());

bool mute;
if (track_impl->solo_) // solo tracks are never muted
mute = false;
else if (have_solo_tracks) // if there are solo tracks all other tracks are muted
mute = true;
else // there isn't any solo track in the project, use mute from track
mute = track_impl->mute_;

Ase::AudioChain *audio_chain = dynamic_cast<Ase::AudioChain*> (&*track_impl->chain_->_audio_processor());
if (mute)
audio_chain->volume (0);
else
audio_chain->volume (track_impl->volume_);
}
}

static constexpr const uint MAX_LAUNCHER_CLIPS = 8;

ClipS
Expand Down
10 changes: 10 additions & 0 deletions ase/track.hh
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ class TrackImpl : public DeviceImpl, public virtual Track {
DeviceP chain_, midi_prod_;
ClipImplS clips_;
uint midi_channel_ = 0;
bool mute_ = false;
bool solo_ = false;
double volume_ = 0.5407418735601; // -10dB
ASE_DEFINE_MAKE_SHARED (TrackImpl);
friend class ProjectImpl;
virtual ~TrackImpl ();
void set_chain_volumes ();
protected:
String fallback_name () const override;
void serialize (WritNode &xs) override;
Expand All @@ -30,6 +34,12 @@ public:
bool is_master () const override { return MASTER_TRACK & gadget_flags(); }
int32 midi_channel () const override { return midi_channel_; }
void midi_channel (int32 midichannel) override;
bool mute () const override { return mute_; }
void mute (bool new_mute) override;
bool solo () const override { return solo_; }
void solo (bool new_solo) override;
double volume () const override { return volume_; }
void volume (double new_volume) override;
ClipS launcher_clips () override;
DeviceP access_device () override;
MonitorP create_monitor (int32 ochannel) override;
Expand Down

0 comments on commit 0dc2e50

Please sign in to comment.