Sign In:     


Forum: General Discussion

Topic: AI Scripting Assistant Tool Idea
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.
 

Posted Fri 30 May 25 @ 12:45 am
I like this idea...AI integration in everything we do or utilize is inevitable.
 

I'm charging more to fix broken vibe coding.
 

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.
 

VDJ code is it's own thing so AI won't help unless it has source files to learn from.
 

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;
}*/
 

Well that looks like a lot of effort for things you could have saved and called from persistent variables.
 

Also @locodog what does that C++ code post have to do with the original post OP made?
 

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.
 

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;
}
 

@Ian Moore

What OP stated:

Quote :
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.


What you said you generated (which looks like a VDJ extension to save/recall more deck state, I didn't read through it entirely):

Quote :
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.


Do you think you've solved the problem OP described?
On top of that, what you posted only works on Windows (you're using the Windows API header + functions, and referring to Windows env vars and paths).

I get it - you had a problem, and AI does enable persons to more easily attempt to solve problems, but the problem you're actually trying to solve is important too. What you provided, imo, should have been under a separate post talking about deck state.
 

Do I think I solved the problem OP described? Uh, no? Again, like I said: it's a proof of concept that a scripting helper COULD work in the context that was described in the OP. In fact, if the scripting helper had a good system prompt (how to access the VirtualDJ documentation, access to some example add-ons/scripts, etc), it could even be quite powerful and less prone to having to revise a script 41 times like I had to go through.

Really not understanding people's weird, thinly veiled disdain for what I posted. It's working example code of what could be generated via the idea the OP had. That's it. It proves the idea is possible. That's all I was trying to get across. Yes, it's specifically for windows. Yes, it's specifically for one little idea I had for an extension. That doesn't mean it's somehow completely irrelevant or needs it's own topic. For crying out loud. I was just trying to move the discussion in a "here's what the output of OP's idea might look like and the effort required", nothing more.
 

In fact there are two different subjects here.
1) the use of vdjscript and generation by LLM (fine-tuning) , for example via tools like Chatbase,...
2) the use of the VirtualDJ SDK in C++ and generation by tools like GitHub Copilot (that I use for example in Visual Studio 2022 but you can also use it in Visual Studio Code)
 

To me it looks like going from New York to Los Angeles by doing a layover in Paris, but hey.. if it works, it works (I guess) :)




My point: Insteaad of spending 10 hours to debug Chat GPT's C++ code to create a plugin that does something that also envolves VDJ script which you also have to somehow "debug", you could have spent those hours to really learn your way around VDJ script and achieve the same thing far more efficiently.
Also by the end of that process you would have gained some knowledge that could become handy later, on other scripting needs you may have in the future.
Right now maybe you have gained some knowledge on how to instruct ChatGPT better, but ChatGPT still s*cks at writing VDJ script. Therefore the next time you'll need it, you'll most likely have to repeat the same process all over again.
 

djcel wrote :
In fact there are two different subjects here.
1) the use of vdjscript and generation by LLM (fine-tuning) , for example via tools like Chatbase,...
2) the use of the VirtualDJ SDK in C++ and generation by tools like GitHub Copilot (that I use for example in Visual Studio 2022 but you can also use it in Visual Studio Code)


@djcel this is all my point was - the original post was 1) (which sounded like training his own model and providing a chat based API on top to hopefully solve any vdjscript related problem) what the newer poster applied was a different thing (trying to use/train ChatGPT or an existing tool to solve a problem and providing a canned, unrelated problem (VDJ C++ extension dev) solved with ChatGPT after a lot of back and forth with it as an example), and is a different topic (2)).

I wasn't saying the new topic was a bad topic, I'm just saying doesn't fit the topic being discussed and that next time, new poster should create a new topic for that because now there are two different things being talked about under one post - it's just trying to suggest a bit of forum ettiquette.

 

Sure seems like alot of reddit-level "the cheese doesn't go there" splitting hairs to me. We're all talking about leveraging AI to solve scripting/extension issues/limitations in VirtualDJ. I really don't understand the need to compartmentalize the two things. My example was AI-gen C++ code that leverages the VirtualDJ SDK: the OP's idea was AI-assisted VDJScript code-gen that doesn't require compiling and could be simply slotted into any custom controller button with minimal effort. Both are valid uses of the tools VDJ provides to users who want to extend the program, with AI helping get to the finish line in both cases. They are not that dissimilar.
 

But they aren't the same thing 🤷🏾‍♂️.

Anyways, it probably doesn't make sensr to argue the semantics of the post - what you posted is appreciated (just being clear that I'm not fighting down), but as you see, it's not efficient (you spent 10 hrs and 41 prompts to get there and it still isn't complete), so it probably would be better to train a custom model to do it (and be focused just on it).