// ace-understand.cpp: audio understanding CLI (thin wrapper) // // Audio -> VAE encode -> FSQ tokenize -> LM understand -> metadata + lyrics // // Output: request JSON with metadata + lyrics, reusable as ace-lm or ace-synth input. #include "audio-io.h" #include "model-registry.h" #include "model-store.h" #include "pipeline-understand.h" #include "request.h" #include "version.h" #include #include #include #include static void usage(const char * prog) { AceUnderstandParams d; ace_understand_default_params(&d); fprintf(stderr, "acestep.cpp %s\n\n", ACE_VERSION); fprintf(stderr, "Usage: %s --models --src-audio [--request ] [options]\n" "\n" "Required:\n" " --models Directory of GGUF model files\n" " --src-audio Source audio (WAV or MP3, any sample rate)\n" "\n" "Optional:\n" " --request Request JSON carrying model selection and\n" " sampling params (lm_model, synth_model,\n" " lm_temperature, lm_top_p, lm_top_k)\n" "\n" "When no --request is given, understand defaults apply\n" "(temperature 0.3, top_p disabled).\n" "\n" "Output:\n" " -o Output JSON (default: stdout summary)\n" "\n" "Memory control:\n" " --vae-chunk Latent frames per tile (default: %d)\n" " --vae-overlap Overlap frames per side (default: %d)\n" "\n" "Debug:\n" " --max-seq KV cache size (default: %d)\n" " --no-fsm Disable FSM constrained decoding\n" " --no-fa Disable flash attention\n" " --dump Dump tok_latents + tok_codes (skip LM)\n", prog, d.vae_chunk, d.vae_overlap, d.max_seq); } int main(int argc, char ** argv) { const char * models_dir = NULL; const char * src_audio_path = NULL; const char * request_path = NULL; const char * output_path = NULL; const char * dump_dir = NULL; AceUnderstandParams params; ace_understand_default_params(¶ms); if (argc < 2) { usage(argv[0]); return 1; } for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--models") && i + 1 < argc) { models_dir = argv[++i]; } else if (!strcmp(argv[i], "--src-audio") && i + 1 < argc) { src_audio_path = argv[++i]; } else if (!strcmp(argv[i], "--request") && i + 1 < argc) { request_path = argv[++i]; } else if (!strcmp(argv[i], "-o") && i + 1 < argc) { output_path = argv[++i]; } else if (!strcmp(argv[i], "--dump") && i + 1 < argc) { dump_dir = argv[++i]; } else if (!strcmp(argv[i], "--max-seq") && i + 1 < argc) { params.max_seq = atoi(argv[++i]); } else if (!strcmp(argv[i], "--vae-chunk") && i + 1 < argc) { params.vae_chunk = atoi(argv[++i]); } else if (!strcmp(argv[i], "--vae-overlap") && i + 1 < argc) { params.vae_overlap = atoi(argv[++i]); } else if (!strcmp(argv[i], "--no-fsm")) { params.use_fsm = false; } else if (!strcmp(argv[i], "--no-fa")) { params.use_fa = false; } else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { usage(argv[0]); return 0; } else { fprintf(stderr, "Unknown option: %s\n", argv[i]); usage(argv[0]); return 1; } } if (!models_dir) { fprintf(stderr, "[CLI] ERROR: --models required\n"); usage(argv[0]); return 1; } if (!src_audio_path) { fprintf(stderr, "[CLI] ERROR: --src-audio required\n"); usage(argv[0]); return 1; } // Parse request JSON (if provided). Sampling params come from JSON. // When no JSON, understand defaults apply (temperature=0.3 for transcription). AceRequest req; request_init(&req); req.lm_temperature = 0.3f; // understand default: lower than generation req.lm_top_p = 1.0f; // understand default: no nucleus sampling if (request_path) { if (!request_parse(&req, request_path)) { return 1; } request_dump(&req, stderr); } // Scan the registry and resolve model paths. Empty lm_model / synth_model // fall to the first entry of their bucket, matching server default behavior. ModelRegistry registry; if (!registry_scan(®istry, models_dir)) { fprintf(stderr, "[Ace-Understand] FATAL: cannot scan --models %s\n", models_dir); return 1; } if (registry.dit.empty() || registry.vae.empty()) { fprintf(stderr, "[Ace-Understand] FATAL: understand pipeline needs DiT and VAE models under %s\n", models_dir); return 1; } if (!dump_dir && registry.lm.empty()) { fprintf(stderr, "[Ace-Understand] FATAL: understand pipeline needs an LM model under %s\n", models_dir); return 1; } const ModelEntry * lm_entry = dump_dir ? NULL : (req.lm_model.empty() ? ®istry.lm[0] : registry_find(registry.lm, req.lm_model.c_str())); if (!dump_dir && !lm_entry) { fprintf(stderr, "[Ace-Understand] FATAL: lm_model '%s' not found in registry\n", req.lm_model.c_str()); return 1; } const ModelEntry * dit_entry = req.synth_model.empty() ? ®istry.dit[0] : registry_find(registry.dit, req.synth_model.c_str()); if (!dit_entry) { fprintf(stderr, "[Ace-Understand] FATAL: synth_model '%s' not found in registry\n", req.synth_model.c_str()); return 1; } params.model_path = lm_entry ? lm_entry->path.c_str() : NULL; params.dit_path = dit_entry->path.c_str(); params.vae_path = registry.vae[0].path.c_str(); params.dump_dir = dump_dir; // load pipeline ModelStore * store = store_create(EVICT_STRICT); AceUnderstand * ctx = ace_understand_load(store, ¶ms); if (!ctx) { store_free(store); return 1; } // Read and resample audio to 48kHz stereo int T_audio = 0; float * planar = audio_read_48k(src_audio_path, &T_audio); if (!planar) { fprintf(stderr, "[Ace-Understand] FATAL: cannot read %s\n", src_audio_path); ace_understand_free(ctx); store_free(store); return 1; } fprintf(stderr, "[Ace-Understand] %.2fs @ 48kHz\n", (float) T_audio / 48000.0f); float * src_interleaved = audio_planar_to_interleaved(planar, T_audio); free(planar); int src_len = T_audio; // run understand pipeline AceRequest out; int rc = ace_understand_generate(ctx, src_interleaved, src_len, nullptr, 0, // src_latents (audio path) &req, &out, nullptr, nullptr, // latent_out, T_latent_out NULL, NULL); free(src_interleaved); ace_understand_free(ctx); store_free(store); if (rc != 0) { return 1; } // write output JSON if (output_path) { request_write(&out, output_path); } return 0; }