Hey all!
With AI being used more and more in coding, I’ve often wished there were a tool to help write VirtualDJ scripts more quickly and easily. Unless I’ve somehow missed it, I haven’t seen this idea brought up yet.
This is still in the early idea phase, but I’m excited about it and seriously considering developing it into a working prototype. Before going too far, though, I wanted to run it by the VDJ team and get feedback from the community as well.
The core idea is an AI-powered “Scripting Helper.” You’d describe what you want a button or control to do, and the tool would generate the appropriate script.
My hope is that this tool could significantly reduce the friction for those who appreciate VirtualDJ's customizability but don’t want to spend hours learning the scripting language, troubleshooting issues, or relying heavily on forum help. It could also support DJs who want to focus more on creativity—making it easier to test, experiment, and iterate on their ideas. Down the line, it might even serve as a teaching tool for learning scripting.
To the VDJ staff and Locodog:
This project depends on permission to use the official VirtualDJ scripting documentation, the default controller mappings, and possibly the 'VirtualDJ Script School' thread by Locodog. It would start as a personal project, but if there’s interest and it proves genuinely useful, I’d like to share it with the community—ideally as a free tool or in a form that’s sustainable to maintain.
To the Community:
Would a tool like this be useful to you? I’d love to hear about any challenges you’ve faced with scripting, features you’d want to see, or any other ideas you have. All feedback is welcome.
With AI being used more and more in coding, I’ve often wished there were a tool to help write VirtualDJ scripts more quickly and easily. Unless I’ve somehow missed it, I haven’t seen this idea brought up yet.
This is still in the early idea phase, but I’m excited about it and seriously considering developing it into a working prototype. Before going too far, though, I wanted to run it by the VDJ team and get feedback from the community as well.
The core idea is an AI-powered “Scripting Helper.” You’d describe what you want a button or control to do, and the tool would generate the appropriate script.
My hope is that this tool could significantly reduce the friction for those who appreciate VirtualDJ's customizability but don’t want to spend hours learning the scripting language, troubleshooting issues, or relying heavily on forum help. It could also support DJs who want to focus more on creativity—making it easier to test, experiment, and iterate on their ideas. Down the line, it might even serve as a teaching tool for learning scripting.
To the VDJ staff and Locodog:
This project depends on permission to use the official VirtualDJ scripting documentation, the default controller mappings, and possibly the 'VirtualDJ Script School' thread by Locodog. It would start as a personal project, but if there’s interest and it proves genuinely useful, I’d like to share it with the community—ideally as a free tool or in a form that’s sustainable to maintain.
To the Community:
Would a tool like this be useful to you? I’d love to hear about any challenges you’ve faced with scripting, features you’d want to see, or any other ideas you have. All feedback is welcome.
Mensajes Fri 30 May 25 @ 12:45 am
I like this idea...AI integration in everything we do or utilize is inevitable.

Mensajes Fri 30 May 25 @ 1:21 am
I'm charging more to fix broken vibe coding.
Mensajes Fri 30 May 25 @ 8:02 am
I just tried using ChatGPT. I had given it the links for the manual and the wiki related to scripting. It really didn't work that well to start. I think it just need's more training. I may revisit it later. At this time I am just trying to see if my complex tasks are doable without going to python.
Mensajes Thu 19 Jun 25 @ 5:59 pm
VDJ code is it's own thing so AI won't help unless it has source files to learn from.
Mensajes Thu 19 Jun 25 @ 7:58 pm
I spent about 10 hours today vibe coding an extension to the hidden (and limited) save_deck_state and load_deck_state functions. Currently, they only save tracks, positions, volume, gain, and pitch. Which is helpful, but doesn't cover what stems are on/off, equalizer settings, crossfader, etc. It took quite some time and lots of massaging, but chatgpt did eventually pull it off. (You can see the version we ended on was 1.41, so yes, it had to try writing this 41 times before it was completely working). I don't know how to publish to the extensions browser and don't want to shadily share the compiled .dll, but here's the source, which I have confirmed is working for what I described. Pretty sweet if you ask me. I always wanted an easy way to revist old mashups (oh I wish I had lowered the bass on that song, or I wish I had upped the vocals on that, etc), and now I can save/load projects like most DAWs.
// MixStateFX.cpp — Save/Load deck state with correct stem pad on/off using mute_stem.
// - Save: writes EQ/volume/etc + *_muted flags and compact <stem deck=".."> snapshot.
// - Load: loads tracks, applies stem mutes ONLY (no stem levels), then restores posture; EQ last.
// - Debounced buttons to avoid double dialogs.
// - Load dialog defaults to %LOCALAPPDATA%\VirtualDJ\Deck Sets\ .
//
// Build target: VirtualDJ 8+ (IVdjPlugin8)
#include <windows.h>
#include <commdlg.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include "vdjPlugin8.h"
using std::string;
// ---------- small utils
static void scopy(char* dst, int cap, const char* src){
if (!dst || cap<=0) return;
int i=0; if (src){ for (; src && i<cap-1; ++i) dst=src; }
dst=0;
}
static string env(const char* k){ const char* v=std::getenv(k); return (v && *v)? v : ""; }
static inline ULONGLONG ft64(const FILETIME& f){ return ((ULONGLONG)f.dwHighDateTime<<32) | f.dwLowDateTime; }
static inline double clamp01(double v){ if (v<0) v=0; if (v>1) v=1; return v; }
// ---------- text IO (ANSI/UTF8/UTF16)
struct Text{
enum Enc { ANSI, UTF8, UTF16LE, UTF16BE } enc{ANSI};
std::string bytes,u8; std::wstring w;
};
static bool read_all_shared(const string& path, std::string& out){
HANDLE h=CreateFileA(path.c_str(),GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,nullptr,OPEN_EXISTING,0,nullptr);
if (h==INVALID_HANDLE_VALUE) return false;
LARGE_INTEGER sz{}; if (!GetFileSizeEx(h,&sz) || sz.QuadPart<0){ CloseHandle(h); return false; }
out.resize((size_t)sz.QuadPart);
DWORD off=0, rd=0; while (off<out.size()){
if (!ReadFile(h,&out[off],(DWORD)out.size()-off,&rd,nullptr)){ CloseHandle(h); return false; }
if (!rd) break; off+=rd;
}
CloseHandle(h); return off==out.size();
}
static Text::Enc detect(const std::string& b){
if (b.size()>=3 && (unsigned char)b[0]==0xEF && (unsigned char)b[1]==0xBB && (unsigned char)b[2]==0xBF) return Text::UTF8;
if (b.size()>=2 && (unsigned char)b[0]==0xFF && (unsigned char)b[1]==0xFE) return Text::UTF16LE;
if (b.size()>=2 && (unsigned char)b[0]==0xFE && (unsigned char)b[1]==0xFF) return Text::UTF16BE;
size_t nul=0, pairs=0; for (size_t i=0;i+1<b.size();i+=2){ ++pairs; if (b==0) ++nul; }
if (pairs>0 && nul*5>=pairs) return Text::UTF16LE;
return Text::ANSI;
}
static void to_utf(const std::string& b, Text::Enc e, std::wstring& w, std::string& u8){
if (e==Text::UTF16LE || e==Text::UTF16BE){
size_t off=(b.size()>=2 && ((e==Text::UTF16LE && (unsigned char)b[0]==0xFF && (unsigned char)b[1]==0xFE) || (e==Text::UTF16BE && (unsigned char)b[0]==0xFE && (unsigned char)b[1]==0xFF)))? 2:0;
size_t n=(b.size()-off)/2; w.resize(n);
if (e==Text::UTF16LE) memcpy(&w[0],b.data()+off,n*2);
else for (size_t i=0;i<n;i++){ unsigned char hi=b[off+i*2+0], lo=b[off+i*2+1]; w=(wchar_t)((lo<<8)|hi); }
int need=WideCharToMultiByte(CP_UTF8,0,w.data(),(int)w.size(),nullptr,0,nullptr,nullptr);
u8.resize(need); if (need>0) WideCharToMultiByte(CP_UTF8,0,w.data(),(int)w.size(),&u8[0],need,nullptr,nullptr);
}else{
size_t off=(e==Text::UTF8 && b.size()>=3 && (unsigned char)b[0]==0xEF && (unsigned char)b[1]==0xBB && (unsigned char)b[2]==0xBF)? 3:0;
int need=MultiByteToWideChar(CP_UTF8,0,b.data()+off,(int)(b.size()-off),nullptr,0);
w.resize(need); if (need>0) MultiByteToWideChar(CP_UTF8,0,b.data()+off,(int)(b.size()-off),&w[0],need);
u8.assign(b.data()+off,b.data()+b.size());
}
}
static bool read_text(const string& path, Text& t){ if (!read_all_shared(path,t.bytes)) return false; t.enc=detect(t.bytes); to_utf(t.bytes,t.enc,t.w,t.u8); return true; }
static bool write_text_utf8(const string& path, const std::string& u8){
HANDLE h=CreateFileA(path.c_str(),GENERIC_WRITE,0,nullptr,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,nullptr);
if (h==INVALID_HANDLE_VALUE) return false; DWORD wr=0; BOOL ok=WriteFile(h,u8.data(),(DWORD)u8.size(),&wr,nullptr); CloseHandle(h); return ok && wr==u8.size();
}
static std::string narrow(const std::wstring& w){
int need=WideCharToMultiByte(CP_UTF8,0,w.data(),(int)w.size(),nullptr,0,nullptr,nullptr);
std::string s; s.resize(need); if (need>0) WideCharToMultiByte(CP_UTF8,0,w.data(),(int)w.size(),&s[0],need,nullptr,nullptr); return s;
}
// ---------- tiny XML helpers
static void append_tag(std::string& s, const char* k, double v){ char b[128]; std::snprintf(b,sizeof(b),"<%s>%.6f</%s>",k,v,k); s+=b; }
static bool tag_number(const std::string& src, const char* tag, double& out){
std::string o="<"; o+=tag; o+=">"; std::string c="</"; c+=tag; c+=">";
size_t a=src.find(o); if (a==std::string::npos) return false;
size_t b=src.find(c,a+o.size()); if (b==std::string::npos) return false;
out = atof(src.substr(a+o.size(), b-(a+o.size())).c_str()); return true;
}
static void strip_mixstate(std::string& xml){
size_t a=xml.find("<mixstate>"); if (a==std::string::npos) return;
size_t b=xml.find("</mixstate>",a); if (b==std::string::npos) return;
b+=strlen("</mixstate>"); xml.erase(a,b-a);
}
static size_t find_deckset_end(const std::string& xml){
const char* ends[]={"</deckset>","</Deckset>","</DECKSET>"}; for (auto* e: ends){ size_t p=xml.rfind(e); if (p!=std::string::npos) return p; } return std::string::npos;
}
static std::string slice_mixstate(const std::string& xml){
size_t a=xml.find("<mixstate>"); if (a==std::string::npos) return {};
size_t b=xml.find("</mixstate>",a); if (b==std::string::npos) return {};
return xml.substr(a,b-a);
}
static string basename_only(const string& p){ size_t s=p.find_last_of("\\/"); return (s==string::npos)? p : p.substr(s+1); }
static string directory_of(const char* p){ string s=p? p:""; size_t k=s.find_last_of("\\/"); return (k==string::npos)? string() : s.substr(0,k+1); }
static string unesc(const string& s){
string r=s; auto rep=[&](const char* a,const char* b){ size_t p=0; while((p=r.find(a,p))!=string::npos){ r.replace(p,strlen(a),b); p+=strlen(b);} };
rep("&","&"); rep("\"","\""); rep("'","'"); rep("<","<"); rep(">",">"); return r;
}
static int attr_find(const std::string& tag, const char* key, size_t& from, size_t& to){
std::string k=string(key)+"=\""; size_t a=tag.find(k); if (a==std::string::npos) return 0; a+=k.size();
size_t b=tag.find('"',a); if (b==std::string::npos) return 0; from=a; to=b; return 1;
}
static int attr_int(const std::string& tag, const char* key, int defv){ size_t a=0,b=0; if(!attr_find(tag,key,a,b)) return defv; return atoi(tag.substr(a,b-a).c_str()); }
static string attr_str(const std::string& tag, const char* key){ size_t a=0,b=0; if(!attr_find(tag,key,a,b)) return {}; return tag.substr(a,b-a); }
// ---------- plugin
class MixStateFX : public IVdjPlugin8
{
public:
HRESULT VDJ_API OnGetPluginInfo(TVdjPluginInfo8* info) override{
info->PluginName="MixStateFX";
info->Author="Admin/ChatGPT";
info->Description="Save/Load deck state + stems (mute_stem on/off). EQ restored last; stems do not touch EQ values.";
info->Version="1.41";
info->Bitmap=nullptr; info->Flags=0; return S_OK;
}
HRESULT VDJ_API OnLoad() override{
DeclareParameterButton(&pSave_,0,"Save Mixstate","Save");
DeclareParameterButton(&pLoad_,1,"Load Mixstate","Load");
vdjLocal_=env("LOCALAPPDATA")+"\\VirtualDJ\\Deck Sets\\";
vdjDocs_ =env("USERPROFILE") +"\\Documents\\VirtualDJ\\Deck Sets\\";
vdjRoam_ =env("APPDATA") +"\\VirtualDJ\\Deck Sets\\";
return S_OK;
}
HRESULT VDJ_API OnGetParameterString(int id, char* outParam, int outParamSize) override{
if (id==0){ scopy(outParam,outParamSize,"Save Mixstate"); return S_OK; }
if (id==1){ scopy(outParam,outParamSize,"Load Mixstate"); return S_OK; }
return E_NOTIMPL;
}
HRESULT VDJ_API OnParameter(int id) override{
ULONGLONG now=GetTickCount64();
if (id==0){
if (now<saveCooldownUntil_) return S_OK;
if (InterlockedCompareExchange(&busySave_,1,0)!=0) return S_OK;
saveCooldownUntil_=now+8000; // absorb press/release bounce
do_save();
InterlockedExchange(&busySave_,0);
saveCooldownUntil_=GetTickCount64()+8000;
return S_OK;
}
if (id==1){
if (now<loadCooldownUntil_) return S_OK;
if (InterlockedCompareExchange(&busyLoad_,1,0)!=0) return S_OK;
loadCooldownUntil_=now+8000;
do_load();
InterlockedExchange(&busyLoad_,0);
loadCooldownUntil_=GetTickCount64()+8000;
return S_OK;
}
return S_OK;
}
ULONG VDJ_API Release() override{ delete this; return 0; }
private:
int pSave_{0}, pLoad_{0};
string vdjLocal_, vdjDocs_, vdjRoam_, lastDir_;
LONG busySave_{0}, busyLoad_{0};
ULONGLONG saveCooldownUntil_{0}, loadCooldownUntil_{0};
bool get_num(const char* q, double& out){ return GetInfo(q,&out)==S_OK; }
bool get_bool(const char* q){ double v=0; return GetInfo(q,&v)==S_OK && v!=0.0; }
void cmd(const char* s){ SendCommand(s); }
// ---- shared types for stem mutes
struct SM { int muted{0}; int present{0}; }; // muted: 1 muted/off, 0 unmuted/on
struct DeckSM { SM voc, ins, bas, kik, hat, mel; };
// --- robust mute_stem read: return 1 if muted, 0 if unmuted, -1 unknown
int stem_muted(int deck, const char* name){
double v=0;
// Prefer explicit boolean numeric
{ char q[160]; std::snprintf(q,sizeof(q),"deck %d mute_stem '%s' ? 1 : 0",deck,name);
if (GetInfo(q,&v)==S_OK) return (v>0.5)? 1:0; }
// Fallback raw (any nonzero => muted)
{ char q[128]; std::snprintf(q,sizeof(q),"deck %d mute_stem '%s'",deck,name);
if (GetInfo(q,&v)==S_OK) return (v!=0.0)? 1:0; }
// Last resort: use stem level (0 -> muted, >0 -> unmuted)
{ char q[128]; std::snprintf(q,sizeof(q),"deck %d stem '%s'",deck,name);
if (GetInfo(q,&v)==S_OK) return (v<=0.01)? 1:0; }
return -1;
}
// ---------- SAVE ----------
void do_save(){
// Use VDJ's own dialog (no OS dialog here)
cmd("save_deck_set");
Sleep(700);
string path=newest_xml(); if (path.empty()){ cmd("param_echo \"MixStateFX: no deckset found after save\""); return; }
// posture
double xf=0;
double d1_pos=0,d1_pitch=0,d1_key=0,d1_gain=0,d1_vol=0,d1_eqL=0,d1_eqM=0,d1_eqH=0,d1_filter=0;
double d2_pos=0,d2_pitch=0,d2_key=0,d2_gain=0,d2_vol=0,d2_eqL=0,d2_eqM=0,d2_eqH=0,d2_filter=0;
get_num("crossfader", xf);
get_num("deck 1 song_pos", d1_pos); get_num("deck 1 pitch", d1_pitch); get_num("deck 1 key", d1_key);
get_num("deck 1 gain", d1_gain); get_num("deck 1 volume", d1_vol);
get_num("deck 1 eq_low", d1_eqL); get_num("deck 1 eq_mid", d1_eqM); get_num("deck 1 eq_high", d1_eqH);
get_num("deck 1 filter", d1_filter);
get_num("deck 2 song_pos", d2_pos); get_num("deck 2 pitch", d2_pitch); get_num("deck 2 key", d2_key);
get_num("deck 2 gain", d2_gain); get_num("deck 2 volume", d2_vol);
get_num("deck 2 eq_low", d2_eqL); get_num("deck 2 eq_mid", d2_eqM); get_num("deck 2 eq_high", d2_eqH);
get_num("deck 2 filter", d2_filter);
// stems pad mutes via mute_stem
int d1_voc_m = stem_muted(1,"Vocal");
int d1_ins_m = stem_muted(1,"Instru");
int d1_bas_m = stem_muted(1,"Bass");
int d1_kik_m = stem_muted(1,"Kick");
int d1_hat_m = stem_muted(1,"HiHat");
int d1_mel_m = stem_muted(1,"Melody");
int d2_voc_m = stem_muted(2,"Vocal");
int d2_ins_m = stem_muted(2,"Instru");
int d2_bas_m = stem_muted(2,"Bass");
int d2_kik_m = stem_muted(2,"Kick");
int d2_hat_m = stem_muted(2,"HiHat");
int d2_mel_m = stem_muted(2,"Melody");
Text t; if (!read_text(path,t)){ cmd("param_echo \"MixStateFX: read failed\""); return; }
std::string xml=(t.enc==Text::UTF16LE || t.enc==Text::UTF16BE)? narrow(t.w) : t.u8;
strip_mixstate(xml);
std::string ms; ms.reserve(1024);
ms += "<mixstate>";
append_tag(ms,"xf",xf);
append_tag(ms,"d1_pos",d1_pos); append_tag(ms,"d1_pitch",d1_pitch); append_tag(ms,"d1_key",d1_key);
append_tag(ms,"d1_gain",d1_gain); append_tag(ms,"d1_vol",d1_vol);
append_tag(ms,"d1_eqL",d1_eqL); append_tag(ms,"d1_eqM",d1_eqM); append_tag(ms,"d1_eqH",d1_eqH);
append_tag(ms,"d1_filter",d1_filter);
append_tag(ms,"d2_pos",d2_pos); append_tag(ms,"d2_pitch",d2_pitch); append_tag(ms,"d2_key",d2_key);
append_tag(ms,"d2_gain",d2_gain); append_tag(ms,"d2_vol",d2_vol);
append_tag(ms,"d2_eqL",d2_eqL); append_tag(ms,"d2_eqM",d2_eqM); append_tag(ms,"d2_eqH",d2_eqH);
append_tag(ms,"d2_filter",d2_filter);
// *_muted flags (muted = 1 means pad OFF). Omit if unknown (-1).
auto tagb=[&](const char* k,int v){ char b[64]; std::snprintf(b,sizeof(b),"<%s>%d</%s>",k,(v?1:0),k); ms+=b; };
if (d1_voc_m!=-1) tagb("d1_voc_muted", d1_voc_m);
if (d1_ins_m!=-1) tagb("d1_ins_muted", d1_ins_m);
if (d1_bas_m!=-1) tagb("d1_bass_muted", d1_bas_m);
if (d1_kik_m!=-1) tagb("d1_kick_muted", d1_kik_m);
if (d1_hat_m!=-1) tagb("d1_hihat_muted",d1_hat_m);
if (d1_mel_m!=-1) tagb("d1_melody_muted",d1_mel_m);
if (d2_voc_m!=-1) tagb("d2_voc_muted", d2_voc_m);
if (d2_ins_m!=-1) tagb("d2_ins_muted", d2_ins_m);
if (d2_bas_m!=-1) tagb("d2_bass_muted", d2_bas_m);
if (d2_kik_m!=-1) tagb("d2_kick_muted", d2_kik_m);
if (d2_hat_m!=-1) tagb("d2_hihat_muted",d2_hat_m);
if (d2_mel_m!=-1) tagb("d2_melody_muted",d2_mel_m);
// compact <stem/> snapshot (on/off text). On==unmuted.
auto add_stem=[&](int deck,int kik_m,int hat_m,int bas_m,int mel_m,int voc_m,int ins_m){
auto txt=[&](int muted)->const char*{ if (muted==-1) return nullptr; return muted? "off":"on"; };
std::string s; s += "<stem deck=\""; s += (deck==1? "1":"2"); s += "\"";
if (txt(kik_m)) s += string(" kick=\"") + txt(kik_m) + "\"";
if (txt(hat_m)) s += string(" hihat=\"") + txt(hat_m) + "\"";
if (txt(bas_m)) s += string(" bass=\"") + txt(bas_m) + "\"";
if (txt(mel_m)) s += string(" melody=\"") + txt(mel_m) + "\"";
if (txt(voc_m)) s += string(" vocal=\"") + txt(voc_m) + "\"";
if (txt(ins_m)) s += string(" instrumental=\"") + txt(ins_m) + "\"";
s += " />"; ms += s;
};
add_stem(1,d1_kik_m,d1_hat_m,d1_bas_m,d1_mel_m,d1_voc_m,d1_ins_m);
add_stem(2,d2_kik_m,d2_hat_m,d2_bas_m,d2_mel_m,d2_voc_m,d2_ins_m);
ms += "</mixstate>";
size_t p=find_deckset_end(xml);
if (p!=std::string::npos) xml.insert(p,"\n"+ms+"\n"); else xml += "\n"+ms+"\n";
if (!write_text_utf8(path,xml)){ cmd("param_echo \"MixStateFX: write failed\""); return; }
char echo[256]; std::snprintf(echo,sizeof(echo),"param_echo \"MixStateFX: embedded into %s\"", basename_only(path).c_str()); cmd(echo);
}
// ---------- LOAD ----------
void do_load(){
// OS dialog: default to Local\VirtualDJ\Deck Sets (requested). lastDir_ respected if set.
char file[MAX_PATH]={0};
OPENFILENAMEA ofn{}; ofn.lStructSize=sizeof(ofn);
ofn.lpstrFilter="Deck Set XML (*.xml)\0*.xml\0All Files\0*.*\0";
ofn.lpstrFile=file; ofn.nMaxFile=MAX_PATH;
string init=!lastDir_.empty()? lastDir_ : vdjLocal_;
ofn.lpstrInitialDir = init.empty()? nullptr : init.c_str();
ofn.Flags=OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
if (!GetOpenFileNameA(&ofn)){ cmd("param_echo \"MixStateFX: cancelled\""); return; }
lastDir_=directory_of(file);
Text t; if (!read_text(file,t)){ cmd("param_echo \"MixStateFX: read failed\""); return; }
std::string xml=(t.enc==Text::UTF16LE || t.enc==Text::UTF16BE)? narrow(t.w) : t.u8;
// load track paths from <load deck="N" filepath="...">
LoadRec r1{},r2{}; extract_load_entries(xml,r1,r2);
if (r1.deck==1 && !r1.path.empty()) load_track(1,r1.path);
if (r2.deck==2 && !r2.path.empty()) load_track(2,r2.path);
std::string ms=slice_mixstate(xml);
// parse *_muted flags
auto gMS = [&](const char* k, int& present)->int{
double v=0; if (!tag_number(ms,k,v)) { present=0; return 0; } present=1; return (v!=0.0)? 1:0;
};
DeckSM m1{}, m2{};
int pr=0;
m1.voc.muted=gMS("d1_voc_muted",pr); m1.voc.present=pr;
m1.ins.muted=gMS("d1_ins_muted",pr); m1.ins.present=pr;
m1.bas.muted=gMS("d1_bass_muted",pr); m1.bas.present=pr;
m1.kik.muted=gMS("d1_kick_muted",pr); m1.kik.present=pr;
m1.hat.muted=gMS("d1_hihat_muted",pr); m1.hat.present=pr;
m1.mel.muted=gMS("d1_melody_muted",pr);m1.mel.present=pr;
m2.voc.muted=gMS("d2_voc_muted",pr); m2.voc.present=pr;
m2.ins.muted=gMS("d2_ins_muted",pr); m2.ins.present=pr;
m2.bas.muted=gMS("d2_bass_muted",pr); m2.bas.present=pr;
m2.kik.muted=gMS("d2_kick_muted",pr); m2.kik.present=pr;
m2.hat.muted=gMS("d2_hihat_muted",pr); m2.hat.present=pr;
m2.mel.muted=gMS("d2_melody_muted",pr);m2.mel.present=pr;
// Posture (only apply tags that exist; do not seed from live)
Pst P{};
P.has_xf=tag_number(ms,"xf",P.xf);
P.has_d1_pos = tag_number(ms,"d1_pos",P.d1_pos);
P.has_d1_pitch = tag_number(ms,"d1_pitch",P.d1_pitch);
P.has_d1_key = tag_number(ms,"d1_key",P.d1_key);
P.has_d1_gain = tag_number(ms,"d1_gain",P.d1_gain);
P.has_d1_vol = tag_number(ms,"d1_vol",P.d1_vol);
P.has_d1_eqL = tag_number(ms,"d1_eqL",P.d1_eqL);
P.has_d1_eqM = tag_number(ms,"d1_eqM",P.d1_eqM);
P.has_d1_eqH = tag_number(ms,"d1_eqH",P.d1_eqH);
P.has_d1_filter= tag_number(ms,"d1_filter",P.d1_filter);
P.has_d2_pos = tag_number(ms,"d2_pos",P.d2_pos);
P.has_d2_pitch = tag_number(ms,"d2_pitch",P.d2_pitch);
P.has_d2_key = tag_number(ms,"d2_key",P.d2_key);
P.has_d2_gain = tag_number(ms,"d2_gain",P.d2_gain);
P.has_d2_vol = tag_number(ms,"d2_vol",P.d2_vol);
P.has_d2_eqL = tag_number(ms,"d2_eqL",P.d2_eqL);
P.has_d2_eqM = tag_number(ms,"d2_eqM",P.d2_eqM);
P.has_d2_eqH = tag_number(ms,"d2_eqH",P.d2_eqH);
P.has_d2_filter= tag_number(ms,"d2_filter",P.d2_filter);
wait_loaded(5000);
// Apply stem pad mutes ONLY (no stem levels, no only/fx)
apply_mutes(1,m1);
apply_mutes(2,m2);
// Apply posture: non-EQ then EQ last
apply_posture(P);
// Reassert EQ once in case of late scripting
if (P.has_d1_eqL || P.has_d1_eqM || P.has_d1_eqH || P.has_d2_eqL || P.has_d2_eqM || P.has_d2_eqH){
Sleep(700);
apply_eq_only(P);
}
cmd("param_echo \"MixStateFX: loaded\"");
}
// ---------- helpers
struct LoadRec{ int deck{-1}; string path; };
static void extract_load_entries(const std::string& xml, LoadRec& d1, LoadRec& d2){
size_t p=0; while(true){
p=xml.find("<load",p); if (p==std::string::npos) break;
size_t e=xml.find('>',p); if (e==std::string::npos) break;
std::string tag=xml.substr(p,e-p+1);
int deck=attr_int(tag,"deck",-1); string fp=attr_str(tag,"filepath");
if (deck==1){ d1.deck=1; d1.path=unesc(fp); }
if (deck==2){ d2.deck=2; d2.path=unesc(fp); }
p=e+1;
}
}
void load_track(int deck, const string& path){
string esc; esc.reserve(path.size()+2); esc.push_back('"'); for(char c: path){ if (c=='"') esc+="\\\""; else esc.push_back(c);} esc.push_back('"');
string c="deck "; c+=(deck==1? "1":"2"); c+=" load "; c+=esc; SendCommand(c.c_str());
}
void wait_loaded(int ms){
const ULONGLONG t0=GetTickCount64();
while (GetTickCount64()-t0<(ULONGLONG)ms){
double v1=0,v2=0;
if (GetInfo("deck 1 loaded",&v1)==S_OK && GetInfo("deck 2 loaded",&v2)==S_OK){
if (v1!=0.0 && v2!=0.0) break;
}
Sleep(50);
}
Sleep(150);
}
struct Pst{
int has_xf{0}; double xf{0};
int has_d1_pos{0},has_d1_pitch{0},has_d1_key{0},has_d1_gain{0},has_d1_vol{0},has_d1_eqL{0},has_d1_eqM{0},has_d1_eqH{0},has_d1_filter{0};
double d1_pos{0},d1_pitch{0},d1_key{0},d1_gain{0},d1_vol{0},d1_eqL{0},d1_eqM{0},d1_eqH{0},d1_filter{0};
int has_d2_pos{0},has_d2_pitch{0},has_d2_key{0},has_d2_gain{0},has_d2_vol{0},has_d2_eqL{0},has_d2_eqM{0},has_d2_eqH{0},has_d2_filter{0};
double d2_pos{0},d2_pitch{0},d2_key{0},d2_gain{0},d2_vol{0},d2_eqL{0},d2_eqM{0},d2_eqH{0},d2_filter{0};
};
static void append_cmd(std::string& s, const char* fmt, double v){ char b[128]; std::snprintf(b,sizeof(b),fmt,v); if (!s.empty()) s+=" & "; s+=b; }
void apply_posture(const Pst& P){
char b[128];
if (P.has_xf){ std::snprintf(b,sizeof(b),"crossfader %.6f", clamp01(P.xf)); SendCommand(b); }
// deck 1 non-EQ
{ std::string s;
if (P.has_d1_pos) append_cmd(s,"deck 1 song_pos %.6f",P.d1_pos);
if (P.has_d1_pitch) append_cmd(s,"deck 1 pitch %.6f", P.d1_pitch);
if (P.has_d1_key) append_cmd(s,"deck 1 key %.6f", P.d1_key);
if (P.has_d1_gain) append_cmd(s,"deck 1 gain %.6f", P.d1_gain);
if (P.has_d1_vol) append_cmd(s,"deck 1 volume %.6f", P.d1_vol);
if (P.has_d1_filter)append_cmd(s,"deck 1 filter %.6f", P.d1_filter);
if (!s.empty()) SendCommand(s.c_str()); }
// deck 2 non-EQ
{ std::string s;
if (P.has_d2_pos) append_cmd(s,"deck 2 song_pos %.6f",P.d2_pos);
if (P.has_d2_pitch) append_cmd(s,"deck 2 pitch %.6f", P.d2_pitch);
if (P.has_d2_key) append_cmd(s,"deck 2 key %.6f", P.d2_key);
if (P.has_d2_gain) append_cmd(s,"deck 2 gain %.6f", P.d2_gain);
if (P.has_d2_vol) append_cmd(s,"deck 2 volume %.6f", P.d2_vol);
if (P.has_d2_filter)append_cmd(s,"deck 2 filter %.6f", P.d2_filter);
if (!s.empty()) SendCommand(s.c_str()); }
// EQ last
{ std::string s1,s2;
if (P.has_d1_eqL) append_cmd(s1,"deck 1 eq_low %.6f", clamp01(P.d1_eqL));
if (P.has_d1_eqM) append_cmd(s1,"deck 1 eq_mid %.6f", clamp01(P.d1_eqM));
if (P.has_d1_eqH) append_cmd(s1,"deck 1 eq_high %.6f", clamp01(P.d1_eqH));
if (!s1.empty()) SendCommand(s1.c_str());
if (P.has_d2_eqL) append_cmd(s2,"deck 2 eq_low %.6f", clamp01(P.d2_eqL));
if (P.has_d2_eqM) append_cmd(s2,"deck 2 eq_mid %.6f", clamp01(P.d2_eqM));
if (P.has_d2_eqH) append_cmd(s2,"deck 2 eq_high %.6f", clamp01(P.d2_eqH));
if (!s2.empty()) SendCommand(s2.c_str()); }
}
void apply_eq_only(const Pst& P){
std::string s1,s2;
if (P.has_d1_eqL) append_cmd(s1,"deck 1 eq_low %.6f", clamp01(P.d1_eqL));
if (P.has_d1_eqM) append_cmd(s1,"deck 1 eq_mid %.6f", clamp01(P.d1_eqM));
if (P.has_d1_eqH) append_cmd(s1,"deck 1 eq_high %.6f", clamp01(P.d1_eqH));
if (!s1.empty()) SendCommand(s1.c_str());
if (P.has_d2_eqL) append_cmd(s2,"deck 2 eq_low %.6f", clamp01(P.d2_eqL));
if (P.has_d2_eqM) append_cmd(s2,"deck 2 eq_mid %.6f", clamp01(P.d2_eqM));
if (P.has_d2_eqH) append_cmd(s2,"deck 2 eq_high %.6f", clamp01(P.d2_eqH));
if (!s2.empty()) SendCommand(s2.c_str());
}
void apply_mutes(int deck, const DeckSM& s){
auto setmute=[&](const char* n, const SM& sm){
if (!sm.present) return;
char b[128]; std::snprintf(b,sizeof(b),"deck %d mute_stem '%s' %s", deck, n, sm.muted? "on":"off");
SendCommand(b);
};
setmute("Vocal", s.voc);
setmute("Instru", s.ins);
setmute("Bass", s.bas);
setmute("Kick", s.kik);
setmute("HiHat", s.hat);
setmute("Melody", s.mel);
}
// newest xml chooser — prefer Local, then Docs, then Roam, then lastDir_
string newest_xml(){
string dirs[4] = { vdjLocal_, vdjDocs_, vdjRoam_, lastDir_ };
ULONGLONG best_ts=0; string bestp; bool have=false;
auto scan=[&](const string& d){
if (d.empty()) return;
WIN32_FIND_DATAA fd{}; HANDLE h=FindFirstFileA((d+"*.xml").c_str(),&fd);
if (h==INVALID_HANDLE_VALUE) return;
do{
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
ULONGLONG cur=ft64(fd.ftLastWriteTime);
if (!have || cur>best_ts){ best_ts=cur; bestp=d+fd.cFileName; have=true; }
}while(FindNextFileA(h,&fd));
FindClose(h);
};
scan(dirs[0]); scan(dirs[1]); scan(dirs[2]); scan(dirs[3]);
if (!have) return {};
size_t k=bestp.find_last_of("\\/"); if (k!=string::npos) lastDir_=bestp.substr(0,k+1);
return bestp;
}
};
// ---------- exports
HRESULT VDJ_API DllGetClassObject(const GUID &rclsid, const GUID &riid, void** ppObject)
{
if (memcmp(&rclsid,&CLSID_VdjPlugin8,sizeof(GUID))==0 &&
memcmp(&riid, &IID_IVdjPluginBasic8,sizeof(GUID))==0){
*ppObject = new MixStateFX(); return NOERROR;
}
return CLASS_E_CLASSNOTAVAILABLE;
}
/*extern "C" __declspec(dllexport) HRESULT __stdcall VdjPlugin8(const char* Module, IVdjPlugin8** Plugin)
{
if (Module && (strcmp(Module,"Effect")==0 || strcmp(Module,"Other")==0)){
*Plugin = new MixStateFX(); return S_OK;
}
return S_FALSE;
}*/
Mensajes 5 hours ago
Well that looks like a lot of effort for things you could have saved and called from persistent variables.
Mensajes 5 hours ago
Also @locodog what does that C++ code post have to do with the original post OP made?
Mensajes 5 hours ago
locodog wrote :
Well that looks like a lot of effort for things you could have saved and called from persistent variables.
I mean, code wise, it's only 500 lines. That's pretty slim for a dll. If using persistent variables was easier, I'm surprised I haven't seen anyone share an easy "save/load *EVERYTHING*" keyboard mapping by now, as it's honestly the only feature VirtualDJ is really lacking on. I get it. "It's meant for live performances" but if the ability to save and load projects is there, why not have it?
DJ VinylTouch wrote :
Also @locodog what does that C++ code post have to do with the original post OP made?
That C++ code was entirely AI generated (via chatgpt), specifically for VirtualDJ, as an extension to it's existing functions. Isn't that exactly what the OP was about? It's definitely proof of concept of a "scripting helper" that could be useful/actually work, and also an example of how brutal it can be to add even simple things (10 hours, 41 tries) with AI code assist tools.
Mensajes 5 hours ago
Can you tell chatgpt to remove this part. It is useless
Quote :
extern "C" __declspec(dllexport) HRESULT __stdcall VdjPlugin8(const char* Module, IVdjPlugin8** Plugin)
{
if (Module && (strcmp(Module,"Effect")==0 || strcmp(Module,"Other")==0)){
*Plugin = new MixStateFX(); return S_OK;
}
return S_FALSE;
}
{
if (Module && (strcmp(Module,"Effect")==0 || strcmp(Module,"Other")==0)){
*Plugin = new MixStateFX(); return S_OK;
}
return S_FALSE;
}
Mensajes 4 hours ago