Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support mute/solo/volume for tracks #19

Open
wants to merge 15 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 87 additions & 3 deletions ase/combo.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,31 @@ AudioChain::initialize (SpeakerArrangement busses)
auto obus = add_output_bus ("Output", ospeakers_);
(void) ibus;
assert_return (OUT1 == obus);

const double default_volume = 0.5407418735601; // -10dB

ParameterMap pmap;
pmap.group = "Settings";
pmap[VOLUME] = Param { "volume", _("Volume"), _("Volume"), default_volume, "", { 0, 1 }, GUIONLY };
pmap[MUTE] = Param { "mute", _("Mute"), _("Mute"), false, "", {}, GUIONLY + ":toggle" };

ChoiceS solo_state_cs;
solo_state_cs += { "Off", "Solo is turned off" };
solo_state_cs += { "On", "This track is solo" };
solo_state_cs += { "Other", "Another track is solo" };
pmap[SOLO_STATE] = Param { "solo_state", _("Solo State"), _("Solo State"), 0, "", std::move (solo_state_cs), GUIONLY };

install_params (pmap);
prepare_event_input();
}

void
AudioChain::reset (uint64 target_stamp)
{}
{
volume_smooth_.reset (sample_rate(), 0.020);
reset_volume_ = true;
adjust_all_params();
}

uint
AudioChain::schedule_children()
Expand All @@ -189,6 +209,34 @@ AudioChain::schedule_children()
void
AudioChain::render (uint n_frames)
{
bool volume_changed = false;
MidiEventInput evinput = midi_event_input();
for (const auto &ev : evinput)
{
switch (ev.message())
{
case MidiMessage::PARAM_VALUE:
apply_event (ev);
adjust_param (ev.param);
if (ev.param == VOLUME || ev.param == MUTE || ev.param == SOLO_STATE)
volume_changed = true;
break;
default: ;
}
}
if (volume_changed)
{
const int solo_state = irintf (get_param (SOLO_STATE));
float new_volume = get_param (VOLUME);
if (solo_state == SOLO_STATE_OTHER)
new_volume = 0;
if (get_param (MUTE) && solo_state != SOLO_STATE_ON)
new_volume = 0;
// compute volume factor so that volume * volume * volume is in range [0..2]
const float cbrt_2 = 1.25992104989487; /* 2^(1/3) */
volume_smooth_.set (new_volume * cbrt_2, reset_volume_);
reset_volume_ = false;
}
// make the last processor output the chain output
const size_t nlastchannels = last_output_ ? last_output_->n_ochannels (OUT1) : 0;
const size_t n_och = n_ochannels (OUT1);
Expand All @@ -205,12 +253,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 +284,26 @@ AudioChain::render (uint n_frames)
// FIXME: assign obus if no children are present
}

std::string
AudioChain::param_value_to_text (uint32_t paramid, double value) const
{
if (paramid == VOLUME)
{
if (value > 0)
return string_format ("Volume %.1f dB", volume_db (value));
else
return "Volume -\u221E dB";
}
else
return AudioProcessor::param_value_to_text (paramid, value);
}

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
7 changes: 7 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,9 @@ class AudioChain : public AudioCombo {
const SpeakerArrangement ospeakers_ = SpeakerArrangement (0);
InletP inlet_;
AudioProcessor *last_output_ = nullptr;
static float volume_db (float volume);
LinearSmooth volume_smooth_;
bool reset_volume_ = false;
protected:
void initialize (SpeakerArrangement busses) override;
void reset (uint64 target_stamp) override;
Expand All @@ -43,6 +47,9 @@ public:
using ProbeArray = std::array<Probe,2>;
ProbeArray* run_probes (bool enable);
static void static_info (AudioProcessorInfo &info);
enum Params { VOLUME = 1, MUTE, SOLO_STATE };
enum { SOLO_STATE_OFF, SOLO_STATE_ON, SOLO_STATE_OTHER };
std::string param_value_to_text (uint32_t paramid, double value) const override;
private:
ProbeArray *probes_ = nullptr;
bool probes_enabled_ = false;
Expand Down
2 changes: 1 addition & 1 deletion ase/properties.hh
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class PropertyImpl : public ParameterProperty {
public:
ASE_DEFINE_MAKE_SHARED (PropertyImpl);
Value get_value () override { Value v; getter_ (v); return v; }
bool set_value (const Value &v) override { return setter_ (v); }
bool set_value (const Value &v) override { bool changed = setter_ (v); if (changed) emit_notify (ident()); return changed; }
ChoiceS choices () override { return lister_ ? lister_ (*this) : parameter_->choices(); }
};

Expand Down
87 changes: 87 additions & 0 deletions ase/track.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ TrackImpl::serialize (WritNode &xs)
}
// device chain
xs["chain"] & *dynamic_cast<Serializable*> (&*chain_); // always exists
/* TODO: while other properties on the track are not suitable for automation,
* the following properies are; so we will need a different serialization
* strategy for these to once we support automation
*/
for (auto prop : { "volume", "mute" })
{
if (xs.in_save())
{
Value v = get_value (prop);
xs[prop] & v;
}
if (xs.in_load())
{
Value v;
xs[prop] & v;
set_value (prop, v);
}
}
}

void
Expand Down Expand Up @@ -120,6 +138,7 @@ TrackImpl::_activate ()
DeviceImpl::_activate();
midi_prod_->_activate();
chain_->_activate();
set_solo_states();
}

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

bool
TrackImpl::solo (bool new_solo)
{
return_unless (new_solo != solo_, false);
solo_ = new_solo;
set_solo_states();
emit_notify ("solo");
return true;
}

void
TrackImpl::set_solo_states()
{
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)
{
auto track_impl = dynamic_cast<Ase::TrackImpl*> (track.get());
have_solo_tracks = have_solo_tracks || track_impl->solo();
}

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

Ase::AudioChain *audio_chain = dynamic_cast<Ase::AudioChain*> (&*track_impl->chain_->_audio_processor());
if (track_impl->solo_)
audio_chain->send_param (Ase::AudioChain::SOLO_STATE, Ase::AudioChain::SOLO_STATE_ON);
else if (have_solo_tracks)
audio_chain->send_param (Ase::AudioChain::SOLO_STATE, Ase::AudioChain::SOLO_STATE_OTHER);
else
audio_chain->send_param (Ase::AudioChain::SOLO_STATE, Ase::AudioChain::SOLO_STATE_OFF);
}
}

void
TrackImpl::create_properties ()
{
// chain to base class
DeviceImpl::create_properties();
// create own properties
auto getsolo = [this] (Value &val) { val = solo(); };
auto setsolo = [this] (const Value &val) { return solo (val.as_double()); };
PropertyBag bag = property_bag();
bag.group = _("Mix");
bag += Prop (getsolo, setsolo, { "solo", _("Solo"), _("Solo"), false, "", {}, STANDARD + String (":toggle") });
}

PropertyS
TrackImpl::access_properties ()
{
PropertyS props = DeviceImpl::access_properties();
PropertyS chain_props = chain_->access_properties();
props.insert (props.end(), chain_props.begin(), chain_props.end());
return props;
}


static constexpr const uint MAX_LAUNCHER_CLIPS = 8;

ClipS
Expand Down
6 changes: 6 additions & 0 deletions ase/track.hh
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ class TrackImpl : public DeviceImpl, public virtual Track {
DeviceP chain_, midi_prod_;
ClipImplS clips_;
uint midi_channel_ = 0;
bool solo_ = false;
ASE_DEFINE_MAKE_SHARED (TrackImpl);
friend class ProjectImpl;
virtual ~TrackImpl ();
void set_solo_states ();
protected:
String fallback_name () const override;
void serialize (WritNode &xs) override;
void create_properties () override;
bool solo () const { return solo_; }
bool solo (bool new_solo);
public:
class ClipScout;
explicit TrackImpl (ProjectImpl&, bool masterflag);
Expand All @@ -32,6 +37,7 @@ public:
void midi_channel (int32 midichannel) override;
ClipS launcher_clips () override;
DeviceP access_device () override;
PropertyS access_properties () override;
MonitorP create_monitor (int32 ochannel) override;
void update_clips ();
ssize_t clip_index (const ClipImpl &clip) const;
Expand Down
77 changes: 77 additions & 0 deletions ui/b/trackbutton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
// @ts-check

import { LitComponent, html, JsExtract, live, docs, ref } from '../little.js';
import * as Util from '../util.js';

/** @class BTrackButton
* @description
* The <b-trackvolume> element is an editor for the track volume, implemented as thin wrapper
* arount <b-toggle>.
* ### Properties:
* *label*
* : Either "M" for mute property or "S" for solo property
* *track*
* : The track
* *value*
* : Current value
*/

// <STYLE/>
JsExtract.scss`
`;

// <HTML/>
const HTML = t => [
html`
<b-toggle label="${t.label}" .value="${t.value}" @valuechange="${event => t.set_value (event.target.value)}"></b-toggle>
`
];

const OBJ_ATTRIBUTE = { type: Object, reflect: true }; // sync attribute with property
const STRING_ATTRIBUTE = { type: String, reflect: true }; // sync attribute with property
const BOOL_ATTRIBUTE = { type: Boolean, reflect: true }; // sync attribute with property

// <SCRIPT/>
class BTrackButton extends LitComponent {
createRenderRoot() { return this; }
render() { return HTML (this); }
static properties = {
track: OBJ_ATTRIBUTE,
label: STRING_ATTRIBUTE,
value: BOOL_ATTRIBUTE
};
constructor() {
super();
this.label = "";
this.track = null;
this.prop = null;
}
set_value (value)
{
this.prop.set_value (value);
}
updated (changed_props)
{
this.update_value();
}
async update_value()
{
if (!this.prop)
{
let property;
if (this.label == "M")
property = "mute";
if (this.label == "S")
property = "solo";
let prop = await this.track.access_property (property);
this.prop = prop;
this.prop.on ("notify", args => {
if (args.detail == property)
this.update_value();
});
}
this.value = await this.prop.get_value();
}
}
customElements.define ('b-trackbutton', BTrackButton);
9 changes: 9 additions & 0 deletions ui/b/trackview.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ b-trackview {
.-track-name {
display: inline-flex; position: relative; width: 7em; overflow: hidden;
}
.-mute-solo {
display: flex;
flex-direction: row;
}
}
b-trackview[current-track] .b-trackview-control {
background-color: zmod($b-button-border, Jz+=25%);
Expand All @@ -76,6 +80,11 @@ const HTML = (t, d) => html`
selectall @change=${event => t.track.name (event.detail.value.trim())}
>${t.wtrack_.name}</b-editable>
</span>
<span class="-mute-solo">
<b-trackbutton .track="${t.track}" label="M"></b-trackbutton>
<b-trackbutton .track="${t.track}" label="S"></b-trackbutton>
<b-trackvolume .track="${t.track}"></b-trackvolume>
</span>
<div class="-lvm-main">
<div class="-lvm-levelbg" ${ref (h => t.levelbg_ = h)}></div>
<div class="-lvm-covermid0" ${ref (h => t.covermid0_ = h)}></div>
Expand Down
Loading