/* Optional documentation tool: real FLTK widgets and real backend, isolated data. */ #include "../src/gui/main_window.hpp" #include "../src/gui/sample_editor.hpp" #include "../src/gui/sequencer_widget.hpp" #include "../src/gui/mixer_widget.hpp" #include "../src/gui/piano_roll.hpp" #include "../src/gui/plugin_dialog.hpp" #include "../src/gui/plugin_library.hpp" #include "../src/gui/track_inserts.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { MainWindow *main_window; std::string destination; void check(xtc_result r) {if(r!=XTC_OK)throw std::runtime_error(xtc_result_string(r));} void pump() {auto end=std::chrono::steady_clock::now()+std::chrono::milliseconds(180);while(std::chrono::steady_clock::now() T *first(Fl_Group &g) {for(int i=0;i(c))return t;if(auto *n=dynamic_cast(c))if(auto *t=first(*n))return t;}return nullptr;} Fl_Widget *named(Fl_Group &g,const char *label) {for(int i=0;ilabel()&&!strcmp(c->label(),label))return c;if(auto *n=dynamic_cast(c))if(auto *w=named(*n,label))return w;}return nullptr;} void tab(const char *label) {auto *page=named(*main_window,label);if(!page)throw std::runtime_error(label);auto *tabs=dynamic_cast(page->parent());if(!tabs)throw std::runtime_error("Tab parent");tabs->value(page);tabs->redraw();pump();} void capture(const char *name) {if(std::getenv("XTC_CAPTURE_SAMPLE_ONLY")&&std::strncmp(name,"sample-",7))return;Fl::flush();pump();std::string command="import -window root '"+destination+"/"+name+".png'";if(system(command.c_str()))throw std::runtime_error("ImageMagick capture failed");std::printf("Captured %s\n",name);std::fflush(stdout);} struct Modal {const char *name;const char *page;}; void modal_capture(void *data) {auto *job=static_cast(data);auto *w=Fl::modal();if(!w||w==main_window)throw std::runtime_error("Expected a modal dialog");if(auto *editor=dynamic_cast(w)){editor->select(2400,std::min(size_t(12000),editor->frames()));if(!strcmp(job->name,"sample-transients")){editor->select(0,editor->frames());check(editor->detect_attacks(.05,2400));}} if(job->page){auto *tabs=first(*w);auto *page=named(*w,job->page);if(!tabs||!page)throw std::runtime_error(job->page);tabs->value(page);tabs->redraw();}capture(job->name);auto *cancel=named(*w,"Cancel");if(!cancel)cancel=named(*w,"Close");if(!cancel)cancel=named(*w,"OK");if(cancel)cancel->do_callback();else w->do_callback();} void dialog(const char *name,std::function action,const char *page=nullptr) {Modal job{name,page};Fl::add_timeout(.3,modal_capture,&job);action();Fl::remove_timeout(modal_capture,&job);pump();} void menu(const char *path) {auto *bar=first(*main_window);const auto *item=bar->find_item(path);if(!item)throw std::runtime_error(path);bar->picked(item);} void piano_view() { check(main_window->open_piano_roll()); auto *piano=main_window->piano_roll(); if(auto *scroll=dynamic_cast(piano->parent()))scroll->scroll_to(0,50*16); size_t count=0;check(xtc_project_note_count(piano->project(),&count)); for(size_t i=0;iproject(),i,¬e));if(note.clip_id==piano->clip()){piano->select_note(note.id);break;}} piano->redraw();pump(); } void button(const char *name) {auto *b=named(*main_window,name);if(!b)throw std::runtime_error(name);b->do_callback();pump();} } int main(int argc,char **argv) { if(argc!=2){std::fprintf(stderr,"Usage: capture_screenshots OUTPUT_DIRECTORY (absolute, no apostrophes)\n");return 1;} destination=argv[1];if(destination.empty()||destination[0]!='/'||destination.find('\'')!=std::string::npos)return 1; try { std::filesystem::create_directories(destination); char temp[]="/tmp/xtc-screenshots-XXXXXX";if(!mkdtemp(temp))return 1;std::string root=temp; struct Cleanup {std::string path;~Cleanup(){std::filesystem::remove_all(path);}} cleanup{root}; xtc_project *p=nullptr;check(xtc_project_create(&p));check(xtc_project_load(p,"examples/projects/modern-session.xtc")); for(auto pair:{std::pair{0,"Intro"},{3840,"Verse"},{7680,"Chorus"},{11520,"Outro"}})check(xtc_project_set_marker(p,pair.first,pair.second)); uint64_t bus;check(xtc_project_add_bus(p,"Music bus",&bus));check(xtc_project_route_track(p,0,bus)); xtc_automation_point a{0,0,XTC_AUTOMATION_GAIN,.65f,0};check(xtc_project_set_automation(p,&a));a.tick=3840;a.value=1;check(xtc_project_set_automation(p,&a));a.tick=7680;a.value=.8f;check(xtc_project_set_automation(p,&a)); std::string project=root+"/composition.xtc";check(xtc_project_save(p,project.c_str()));xtc_project_destroy(p); MainWindow window(false,"");main_window=&window;window.resize(0,0,1400,900);window.show();check(window.open_project(project.c_str()));pump(); auto *s=first(window);if(!s)throw std::runtime_error("Sequencer missing"); auto reset=[&](){window.reset_layout();check(window.open_project(project.c_str()));window.resize(0,20,1400,900);tab("Sequencer");s->select(0,0);s->position(0);check(window.set_loop(3840,7680,true));window.fit_song();pump();}; if(std::getenv("XTC_CAPTURE_MIDI_INPUT_ONLY")) { reset(); if(window.midi_devices(true).empty())throw std::runtime_error("No real MIDI input sources exposed by the host"); Fl::add_timeout(.3,[](void*){ auto *preferences=Fl::modal();if(!preferences)throw std::runtime_error("Preferences missing"); auto *tabs=first(*preferences);auto *page=named(*preferences,"midi"); if(!tabs || !page)throw std::runtime_error("MIDI preferences missing"); tabs->value(page);tabs->redraw();pump(); auto *choice=dynamic_cast(named(*preferences,"Default MIDI input source (next Play)")); if(!choice || choice->menubutton()->size()<3)throw std::runtime_error("MIDI source list is empty"); Fl::add_timeout(.3,[](void*){ capture("preferences-midi-input-devices"); if(system("xdotool key Escape"))std::abort(); }); choice->menubutton()->popup(); auto *close=named(*preferences,"Close");if(!close)throw std::runtime_error("Preferences close missing");close->do_callback(); }); window.show_preferences();window.do_callback(); std::puts("Real host MIDI input device dropdown captured.");return 0; } if(std::getenv("XTC_CAPTURE_MIDI_ONLY")) { reset();dialog("preferences-midi",[&]{window.show_preferences();},"midi"); dialog("preferences-midi-help",[&]{window.show_preferences();},"MIDI help"); auto *bar=first(window);auto *file=bar->find_item("File");if(!file)throw std::runtime_error("File menu missing"); Fl::add_timeout(.3,[](void*){capture("midi-file-menu");if(system("xdotool key Escape"))std::abort();}); (file+1)->pulldown(0,bar->y()+bar->h(),0,0,nullptr,bar); std::string midi=root+"/exported-keys.mid";check(window.export_midi_clip(midi.c_str())); check(window.new_project());check(window.import_midi_file(midi.c_str()));window.fit_song();capture("midi-import-arrangement"); piano_view();capture("midi-import-piano-roll"); window.do_callback();std::puts("MIDI workflow captures completed.");return 0; } if(std::getenv("XTC_CAPTURE_CREATIVE_ONLY")) { reset();check(window.open_track_inserts(0));auto *inserts=window.track_inserts(); check(inserts->apply_template(1));tab("Track inserts");capture("creative-dub-rack"); Fl::add_timeout(.3,[](void*){capture("creative-rack-actions");if(system("xdotool key Escape"))std::abort();}); auto *actions=dynamic_cast(named(window,"Rack actions"));if(!actions)throw std::runtime_error("Missing rack actions"); actions->menu()->pulldown(actions->x(),actions->y(),actions->w(),actions->h(),nullptr,actions); reset();dialog("creative-strum-dialog",[&]{s->strum_dialog();}); check(s->make_chord({4,7}));check(s->strum_chords(30,false));piano_view();capture("creative-strum-result"); window.do_callback();std::puts("Creative workflow captures completed.");return 0; } if(std::getenv("XTC_CAPTURE_HOST_VST3")) { reset();capture("compact-workspace"); auto *create=dynamic_cast(named(window,"Create"));if(!create)throw std::runtime_error("Missing Create dropdown"); Fl::add_timeout(.3,[](void*){capture("create-track-menu");if(system("xdotool key Escape"))std::abort();}); create->menu()->pulldown(create->x(),create->y(),create->w(),create->h(),nullptr,create); const char *effects[]={"ZamEQ2","ZamAutoSat","ZamDelay"}; for(const char *effect:effects){std::string binary=std::string("/usr/lib/vst3/")+effect+".vst3/Contents/x86_64-linux/"+effect+".so";check(window.add_track_effect(0,binary.c_str(),0));} auto *inserts=window.track_inserts();inserts->select_track(0);tab("Track inserts");capture("track-inserts-vst3"); dialog("track-insert-vst3-identity",[&]{check(inserts->inspect_slot());}); check(inserts->set_mix(.65,1));capture("track-inserts-parallel"); window.do_callback();std::puts("Installed host VST3 captures completed.");return 0; } if(std::getenv("XTC_CAPTURE_SAMPLE_ONLY")) { reset();char source[4096];if(!realpath("examples/projects/original-pluck.wav",source))throw std::runtime_error("Missing sample WAV"); check(window.create_track(false));check(window.import_audio(source)); dialog("sample-editor",[&]{check(window.open_sample_editor());});dialog("sample-transients",[&]{check(window.open_sample_editor());}); window.do_callback();std::puts("Sample-editor captures completed.");return 0; } reset();capture("overview");capture("sequencer-loop");capture("arrangement-markers"); s->position(4800);check(window.loop_section());capture("section-navigation"); dialog("marker-edit",[&]{s->marker_dialog();}); dialog("ripple-time",[&]{s->ripple_dialog(false);}); s->position(7680);check(s->ripple_time(1920,false));capture("ripple-result");reset(); check(s->duplicate_track("Keys layered"));capture("track-duplication");reset(); s->position(1920);check(s->split_selection());capture("midi-split"); s->select_all_clips(); // Consolidation requires one track; reset to just the two halves. s->select(0,0);size_t tracks=0,clips=0;xtc_project_get_counts(s->project(),&tracks,&clips); for(size_t i=0;iproject(),i,&c);if(c.track_id==0&&c.start_tick==1920)s->toggle_clip(c.id);} check(s->merge_midi());capture("midi-consolidation");reset(); dialog("midi-humanize",[&]{s->humanize_dialog();});dialog("midi-chord-builder",[&]{s->chord_dialog();});dialog("midi-arpeggio",[&]{s->arpeggio_dialog();}); check(s->make_chord({4,7}));piano_view();capture("midi-chords-result");check(s->arpeggiate(240,70,false));piano_view();capture("midi-arpeggio-result");reset(); piano_view();capture("piano-roll-velocity");window.detach_piano(false);reset(); dialog("commands",[&]{window.show_commands();}); tab("Mixer");capture("mixer-automation");button("Play");pump();capture("mixer-playing");button("Stop"); window.resize(0,0,1220,720);pump();capture("mixer-minimum-size");window.resize(0,0,1400,900); for(int i=0;i<12;++i){check(window.create_track(false));} tab("Mixer");auto *scroll=first(*window.mixer());if(scroll)scroll->scroll_to(std::max(0,static_cast(window.mixer()->strip_count())*156-scroll->w()),0);pump();capture("mixer-scrolled-session");reset(); tab("Modular Graph");capture("modular-graph");reset(); window.detach_transport(true);pump();capture("transport-detached");window.detach_mixer(true);pump();capture("workspace-detached");capture("mixer-detached");reset(); window.detach_editor(0,true);pump();capture("sequencer-detached");reset();tab("Modular Graph");window.detach_editor(1,true);pump();capture("graph-detached");reset(); char wave[4096];if(!realpath("examples/projects/original-pluck.wav",wave))throw std::runtime_error("Missing WAV");check(window.create_track(false));check(window.import_audio(wave));check(s->set_zoom(192));check(s->audio_properties(.8f,240,480));pump();capture("sequencer-audio-waveform"); dialog("sample-editor",[&]{check(window.open_sample_editor());});dialog("sample-transients",[&]{check(window.open_sample_editor());}); dialog("audio-clip-properties",[&]{s->audio_properties_dialog();});dialog("audio-normalization",[&]{s->normalize_dialog();});check(s->normalize_audio(.25f));capture("audio-normalization-result"); dialog("project-collection",[&]{menu("File/Collect project media...");});check(window.collect_project((root+"/portable").c_str())); dialog("loop-range",[&]{button("Loop range...");});dialog("midi-controller-mapping",[&]{menu("Audio/MIDI controller mapping");});dialog("about",[&]{menu("Help/About");}); for(const char *section:{"audio","plugins","midi","interface","editing","project_defaults","transport"}){std::string file="preferences-"+std::string(section);for(char &c:file)if(c==' ')c='_';dialog(file.c_str(),[&]{window.show_preferences();},section);} xtc_settings settings=window.settings();std::string fixtures=std::filesystem::canonical(argv[0]).parent_path().string();snprintf(settings.ladspa_paths,sizeof(settings.ladspa_paths),"%s",fixtures.c_str());settings.clap_paths[0]=settings.vst2_paths[0]=settings.vst3_paths[0]=0; dialog("plugin-library",[&]{std::string path;xtc_choose_plugin_file(settings,path);}); for(auto entry:{std::pair{"plugin-descriptors","plugin-fixture.so"},{"clap-instrument-descriptors","clap-synth-fixture.clap"},{"vst2-instrument-descriptors","vst2-synth-fixture.so"},{"vst3-instrument-descriptors","vst3-synth-fixture.so"}}){unsigned int index=0;dialog(entry.first,[&]{xtc_choose_plugin((fixtures+"/"+entry.second).c_str(),48000,index,strcmp(entry.first,"plugin-descriptors")!=0);});} for(auto entry:{std::pair{"internal-delay",2},{"internal-reverb",3},{"plugin-controls",2}}){xtc_plugin_spec spec{},edited{};xtc_plugin_details details{};if(!strcmp(entry.first,"plugin-controls"))check(xtc_plugin_probe((fixtures+"/plugin-fixture.so").c_str(),0,48000,&spec));else check(xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,XTC_BUILTIN_PATH,entry.second,48000,&spec));check(xtc_plugin_describe(&spec,48000,&details));dialog(entry.first,[&]{xtc_edit_plugin(spec,details,edited);});} reset();auto *choice=s->instrument_choice(0);if(!choice)throw std::runtime_error("Instrument menu missing");Fl::add_timeout(.3,[](void*){capture("internal-instruments");if(system("xdotool key Escape"))std::abort();});choice->menu()->pulldown(choice->x(),choice->y(),choice->w(),choice->h(),choice->mvalue(),choice); window.do_callback();std::puts("All documentation captures completed.");return 0; } catch(const std::exception &e){std::fprintf(stderr,"Screenshot capture failed: %s\n",e.what());return 1;} }