Created
October 20, 2015 23:43
-
-
Save mooware/c0674dbc3bd91f152046 to your computer and use it in GitHub Desktop.
foobar2k channel mixer plugin to only play on a single of multiple channels
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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