Complete implementation of the real-time Chinese-to-French translation system: Architecture: - 3-threaded pipeline: Audio capture → AI processing → UI rendering - Thread-safe queues for inter-thread communication - Configurable audio chunk sizes for latency tuning Core Features: - Audio capture with PortAudio (configurable sample rate/channels) - Whisper API integration for Chinese speech-to-text - Claude API integration for Chinese-to-French translation - ImGui real-time display with stop button - Full recording saved to WAV on stop Modules Implemented: - audio/: AudioCapture (PortAudio wrapper) + AudioBuffer (WAV export) - api/: WhisperClient + ClaudeClient (HTTP API wrappers) - ui/: TranslationUI (ImGui interface) - core/: Pipeline (orchestrates all threads) - utils/: Config (JSON/.env loader) + ThreadSafeQueue (template) Build System: - CMake with vcpkg for dependency management - vcpkg.json manifest for reproducible builds - build.sh helper script Configuration: - config.json: Audio settings, API parameters, UI config - .env: API keys (OpenAI + Anthropic) Documentation: - README.md: Setup instructions, usage, architecture - docs/implementation_plan.md: Technical design document - docs/SecondVoice.md: Project vision and motivation Next Steps: - Test build with vcpkg dependencies - Test audio capture on real hardware - Validate API integrations - Tune chunk size for optimal latency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
107 lines
3.6 KiB
C++
107 lines
3.6 KiB
C++
#include "Config.h"
|
|
#include <nlohmann/json.hpp>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
|
|
using json = nlohmann::json;
|
|
|
|
namespace secondvoice {
|
|
|
|
Config& Config::getInstance() {
|
|
static Config instance;
|
|
return instance;
|
|
}
|
|
|
|
bool Config::load(const std::string& config_path, const std::string& env_path) {
|
|
// Load .env file
|
|
std::ifstream env_file(env_path);
|
|
if (env_file.is_open()) {
|
|
std::string line;
|
|
while (std::getline(env_file, line)) {
|
|
if (line.empty() || line[0] == '#') continue;
|
|
|
|
auto pos = line.find('=');
|
|
if (pos != std::string::npos) {
|
|
std::string key = line.substr(0, pos);
|
|
std::string value = line.substr(pos + 1);
|
|
|
|
// Remove quotes if present
|
|
if (!value.empty() && value.front() == '"' && value.back() == '"') {
|
|
value = value.substr(1, value.length() - 2);
|
|
}
|
|
|
|
if (key == "OPENAI_API_KEY") {
|
|
openai_key_ = value;
|
|
} else if (key == "ANTHROPIC_API_KEY") {
|
|
anthropic_key_ = value;
|
|
}
|
|
}
|
|
}
|
|
env_file.close();
|
|
} else {
|
|
std::cerr << "Warning: Could not open .env file: " << env_path << std::endl;
|
|
}
|
|
|
|
// Load config.json
|
|
std::ifstream config_file(config_path);
|
|
if (!config_file.is_open()) {
|
|
std::cerr << "Error: Could not open config file: " << config_path << std::endl;
|
|
return false;
|
|
}
|
|
|
|
json config_json;
|
|
try {
|
|
config_file >> config_json;
|
|
} catch (const json::parse_error& e) {
|
|
std::cerr << "Error parsing config.json: " << e.what() << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Parse audio config
|
|
if (config_json.contains("audio")) {
|
|
auto& audio = config_json["audio"];
|
|
audio_config_.sample_rate = audio.value("sample_rate", 16000);
|
|
audio_config_.channels = audio.value("channels", 1);
|
|
audio_config_.chunk_duration_seconds = audio.value("chunk_duration_seconds", 10);
|
|
audio_config_.format = audio.value("format", "wav");
|
|
}
|
|
|
|
// Parse whisper config
|
|
if (config_json.contains("whisper")) {
|
|
auto& whisper = config_json["whisper"];
|
|
whisper_config_.model = whisper.value("model", "whisper-1");
|
|
whisper_config_.language = whisper.value("language", "zh");
|
|
whisper_config_.temperature = whisper.value("temperature", 0.0f);
|
|
}
|
|
|
|
// Parse claude config
|
|
if (config_json.contains("claude")) {
|
|
auto& claude = config_json["claude"];
|
|
claude_config_.model = claude.value("model", "claude-haiku-4-20250514");
|
|
claude_config_.max_tokens = claude.value("max_tokens", 1024);
|
|
claude_config_.temperature = claude.value("temperature", 0.3f);
|
|
claude_config_.system_prompt = claude.value("system_prompt", "");
|
|
}
|
|
|
|
// Parse UI config
|
|
if (config_json.contains("ui")) {
|
|
auto& ui = config_json["ui"];
|
|
ui_config_.window_width = ui.value("window_width", 800);
|
|
ui_config_.window_height = ui.value("window_height", 600);
|
|
ui_config_.font_size = ui.value("font_size", 16);
|
|
ui_config_.max_display_lines = ui.value("max_display_lines", 50);
|
|
}
|
|
|
|
// Parse recording config
|
|
if (config_json.contains("recording")) {
|
|
auto& recording = config_json["recording"];
|
|
recording_config_.save_audio = recording.value("save_audio", true);
|
|
recording_config_.output_directory = recording.value("output_directory", "./recordings");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace secondvoice
|