Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
foobar2k channel mixer plugin to only play on a single of multiple channels
// foobar2k plugin that mixes a multi-channel stream down to mono,
// and then up to multiple channels again, but with all channels but one empty.
// this can be used to play several different tracks simultaneously over a
// multi-channel audio output.
//
// pre-built version can be found here:
// https://dl.dropboxusercontent.com/u/267889/foo_down_up_mix_dsp.dll
//
// to build locally, just add this source file into the SDK sample plugin.
#include "stdafx.h"
#include "resource.h"
#include "../SDK/foobar2000.h"
#include <vector>
#include <string>
#include <cstdlib>
// forward decl
static void RunDSPConfigPopup(const dsp_preset & p_data, HWND p_parent, dsp_preset_edit_callback & p_callback);
class down_up_mix_dsp : public dsp_impl_base
{
public:
down_up_mix_dsp(dsp_preset const & p_in)
: m_output_channel_count(0)
, m_output_channel_num(0)
, m_output_channel_config(0) {
unsigned int chcount = 0;
unsigned int chnum = 0;
dsp_preset_parser parser(p_in);
parser >> chcount >> chnum;
m_output_channel_config = audio_chunk::g_guess_channel_config(chcount);
console::printf("foo_down_up_mix_dsp: ch count: %u, ch num: %u, ch cfg: %u",
chcount, chnum, m_output_channel_config);
if (m_output_channel_config == 0)
return; // invalid channel count
if (chnum < 1 || chnum > chcount)
return; // invalid channel number
m_output_channel_count = chcount;
m_output_channel_num = chnum - 1; // number to index
}
static GUID g_get_guid() {
static const GUID guid = { 0x26c4c4e, 0x1397, 0x4bd7,{ 0x87, 0x25, 0xcb, 0xb6, 0x4e, 0x16, 0xa2, 0x2b } };
return guid;
}
static void g_get_name(pfc::string_base & p_out) { p_out = "Down-Up-Mix DSP";}
bool on_chunk(audio_chunk * chunk,abort_callback &) {
// sanity checks
if (m_output_channel_count == 0 || m_output_channel_config == 0 || chunk->is_empty())
return true;
unsigned int channels = chunk->get_channel_count();
unsigned int samples = chunk->get_sample_count();
audio_sample * ptr = chunk->get_data();
// downmix into the local buffer
m_buffer.resize(samples, 0.0);
for (unsigned int sa = 0; sa < samples; ++sa)
{
audio_sample mixed = 0.0;
for (unsigned int ch = 0; ch < channels; ++ch)
mixed += ptr[(sa * channels) + ch];
m_buffer[sa] = (mixed / channels);
}
// "upmix" back into the chunk, filling only the configured channel
chunk->set_data_size(m_output_channel_count * samples);
chunk->set_channels(m_output_channel_count, m_output_channel_config);
ptr = chunk->get_data();
for (unsigned int sa = 0; sa < samples; ++sa)
{
for (unsigned int ch = 0; ch < m_output_channel_count; ++ch)
{
audio_sample val = (ch == m_output_channel_num ? m_buffer[sa] : 0.0);
ptr[(sa * m_output_channel_count) + ch] = val;
}
}
return true; //Return true to keep the chunk or false to drop it from the chain.
}
void on_endofplayback(abort_callback &) {
// The end of playlist has been reached, we've already received the last decoded audio chunk.
// We need to finish any pending processing and output any buffered data through insert_chunk().
}
void on_endoftrack(abort_callback &) {
// Should do nothing except for special cases where your DSP performs special operations when changing tracks.
// If this function does anything, you must change need_track_change_mark() to return true.
// If you have pending audio data that you wish to output, you can use insert_chunk() to do so.
}
void flush() {
// If you have any audio data buffered, you should drop it immediately and reset the DSP to a freshly initialized state.
// Called after a seek etc.
}
double get_latency() {
// If the DSP buffers some amount of audio data, it should return the duration of buffered data (in seconds) here.
return 0;
}
bool need_track_change_mark() {
// Return true if you need on_endoftrack() or need to accurately know which track we're currently processing
// WARNING: If you return true, the DSP manager will fire on_endofplayback() at DSPs that are before us in the chain on track change to ensure that we get an accurate mark, so use it only when needed.
return false;
}
static void g_show_config_popup(const dsp_preset & p_data, HWND p_parent, dsp_preset_edit_callback & p_callback) {
::RunDSPConfigPopup(p_data, p_parent, p_callback);
}
static bool g_have_config_popup() { return true; }
static void g_parse_preset(dsp_preset const & p_in, unsigned int &p_count, unsigned int &p_num) {
dsp_preset_parser parser(p_in);
parser >> p_count >> p_num;
}
static void g_make_preset(dsp_preset & p_out, unsigned int p_count, unsigned int p_num) {
dsp_preset_builder builder;
builder << p_count << p_num; builder.finish(g_get_guid(), p_out);
}
static bool g_get_default_preset(dsp_preset & p_out) {
g_make_preset(p_out, 2, 1);
return true;
}
private:
unsigned int m_output_channel_count;
unsigned int m_output_channel_config;
unsigned int m_output_channel_num;
std::vector<audio_sample> m_buffer;
};
// Use dsp_factory_nopreset_t<> instead of dsp_factory_t<> if your DSP does not provide preset/configuration functionality.
static dsp_factory_t<down_up_mix_dsp> g_down_up_mix_dsp_factory;
class CDownUpMixDspPopup : public CDialogImpl<CDownUpMixDspPopup> {
public:
CDownUpMixDspPopup(const dsp_preset & initData, dsp_preset_edit_callback & callback) : m_initData(initData), m_callback(callback) {}
enum { IDD = IDD_DSP };
enum {
RangeMin = 1,
RangeMax = 8,
RangeTotal = RangeMax - RangeMin
};
BEGIN_MSG_MAP(CDownUpMixDspPopup)
MSG_WM_INITDIALOG(OnInitDialog)
COMMAND_HANDLER_EX(IDOK, BN_CLICKED, OnButton)
COMMAND_HANDLER_EX(IDCANCEL, BN_CLICKED, OnButton)
MSG_WM_HSCROLL(OnHScroll)
END_MSG_MAP()
private:
BOOL OnInitDialog(CWindow, LPARAM) {
m_countSlider = GetDlgItem(IDC_COUNT_SLIDER);
m_countSlider.SetRange(0, RangeTotal);
m_numberSlider = GetDlgItem(IDC_NUMBER_SLIDER);
m_numberSlider.SetRange(0, RangeTotal);
{
unsigned int count, num;
down_up_mix_dsp::g_parse_preset(m_initData, count, num);
m_countSlider.SetPos(pfc::clip_t<t_int32>(count, RangeMin, RangeMax) - RangeMin);
m_numberSlider.SetPos(pfc::clip_t<t_int32>(num, RangeMin, RangeMax) - RangeMin);
RefreshLabel(count, num);
}
return TRUE;
}
void OnButton(UINT, int id, CWindow) {
EndDialog(id);
}
void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar pScrollBar) {
unsigned int count = (unsigned int)(m_countSlider.GetPos() + RangeMin);
unsigned int num = (unsigned int)(m_numberSlider.GetPos() + RangeMin);
{
dsp_preset_impl preset;
down_up_mix_dsp::g_make_preset(preset, count, num);
m_callback.on_preset_changed(preset);
}
RefreshLabel(count, num);
}
void RefreshLabel(unsigned int count, unsigned int num) {
pfc::string_formatter msg; msg << "Play on channel " << pfc::format_uint(num) << " of " << pfc::format_uint(count);
::uSetDlgItemText(*this, IDC_SLIDER_LABEL, msg);
}
const dsp_preset & m_initData; // modal dialog so we can reference this caller-owned object.
dsp_preset_edit_callback & m_callback;
CTrackBarCtrl m_countSlider;
CTrackBarCtrl m_numberSlider;
};
static void RunDSPConfigPopup(const dsp_preset & p_data, HWND p_parent, dsp_preset_edit_callback & p_callback) {
CDownUpMixDspPopup popup(p_data, p_callback);
if (popup.DoModal(p_parent) != IDOK) {
// If the dialog exited with something else than IDOK,k
// tell host that the editing has been cancelled by sending it old preset data that we got initialized with
p_callback.on_preset_changed(p_data);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.