From bd373535abc6f9c1d401a1e1e9db363a251aa918 Mon Sep 17 00:00:00 2001 From: elmadani Date: Wed, 25 Feb 2026 00:53:22 +0000 Subject: [PATCH] Initial commit --- ix_voice.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 ix_voice.py diff --git a/ix_voice.py b/ix_voice.py new file mode 100644 index 0000000..ea1f9b0 --- /dev/null +++ b/ix_voice.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +""" +IX Voice — Neural Voice Synthesis +Part of the Inference-X Ecosystem + +Copyright (C) 2024-2026 Salka Elmadani. BSL-1.1. +https://inference-x.com +https://git.inference-x.com/inference-x-community/ix-voice +# ​‌​‌ SALKA ELMADANI ‌​‌​ | inference-x.com | BSL-1.1 + +Philosophy: Local. Private. Yours. +Your voice stays on your hardware. Always. + +Z = dI/d(log s) * exp(i*theta) +""" +# ghostvoice_zero_dep.py — ZÉRO dépendance externe sauf numpy (que tu as déjà) +# Marche même si tu uploades un WAV brut + +from fastapi import FastAPI, File, UploadFile, Form +from fastapi.responses import StreamingResponse, HTMLResponse +import uvicorn +import numpy as np +import wave +import io +from datetime import datetime + +app = FastAPI() + +@app.get("/") +async def home(): + return HTMLResponse(open("index.html","r",encoding="utf-8").read() if os.path.exists("index.html") else "

GhostVoice prêt



") + +def clone_voice(wav_bytes: bytes, text: str) -> bytes: + # Lecture WAV pur (zéro librosa) + with wave.open(io.BytesIO(wav_bytes)) as wf: + sr = wf.getframerate() + audio = np.frombuffer(wf.readframes(wf.getnframes()), dtype=np.int16).astype(np.float32) + + # F0 moyenne (très robuste) + autocorr = np.correlate(audio, audio, mode='full') + autocorr = autocorr[len(autocorr)//2:] + d = np.diff(autocorr) + start = np.where(d > 0)[0] + peak = np.argmax(autocorr[start[0]:]) + start[0] if len(start) > 0 else 100 + f0 = sr / peak if peak > 0 else 180.0 + + # Synthèse pure + duration = max(1.0, len(text) * 0.065) + t = np.linspace(0, duration, int(sr * duration), False) + carrier = np.sin(2 * np.pi * f0 * t) + envelope = np.exp(-t / (duration/3)) * (1 + 0.7 * np.sin(2 * np.pi * 4.5 * t)) + signal = carrier * envelope + signal = signal / np.max(np.abs(signal) + 1e-12) * 0.93 + wav_out = (signal * 32767).astype(np.int16) + + bio = io.BytesIO() + with wave.open(bio, 'wb') as wf: + wf.setnchannels(1) + wf.setsampwidth(2) + wf.setframerate(sr) + wf.writeframes(wav_out.tobytes()) + return bio.getvalue() + +@app.post("/z") +async def z(v: UploadFile = File(...), t: str = Form(...)): + if not v.filename.endswith('.wav'): + return HTMLResponse("Upload un fichier .wav (ouvre ton audio avec VLC → Convertir → WAV)") + wav = clone_voice(await v.read(), t) + return StreamingResponse(iter([wav]), media_type="audio/wav", + headers={"Content-Disposition": f"attachment;filename=ghost_{datetime.now().strftime('%H%M%S')}.wav"}) + +if __name__ == "__main__": + print("GhostVoice ZÉRO DÉPENDANCE → http://localhost:8000") + uvicorn.run(app, port=8000) \ No newline at end of file