File size: 10,917 Bytes
6fbae64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294ea8d
 
 
6fbae64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
016b5f9
 
 
 
6fbae64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Orbit E-Studio — Private Voice Studio in Your Browser</title>
    <link rel="stylesheet" href="style.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;600;700&family=Nunito:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
    <!-- Runtime Web (loaded by worker, kept here for potential main thread usage) -->
    <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.0/dist/ort.min.js"></script>
</head>

<body>
    <!-- Ambient Background Effects -->
    <div class="ambient-layer">
        <div class="orb orb--primary"></div>
        <div class="orb orb--secondary"></div>
        <div class="orb orb--tertiary"></div>
        <div class="grid-overlay"></div>
    </div>

    <div class="app-shell">
        <!-- Hero Header -->
        <header class="hero">
            <div class="hero__brand">
                <div class="logo">
                    <svg class="logo__icon" viewBox="0 0 32 32" fill="none">
                        <path d="M16 4C16 4 8 8 8 16C8 24 16 28 16 28" stroke="url(#logoGrad)" stroke-width="2.5" stroke-linecap="round" />
                        <path d="M16 4C16 4 24 8 24 16C24 24 16 28 16 28" stroke="url(#logoGrad)" stroke-width="2.5" stroke-linecap="round" />
                        <path d="M12 10V22" stroke="url(#logoGrad)" stroke-width="2.5" stroke-linecap="round" />
                        <path d="M16 8V24" stroke="url(#logoGrad)" stroke-width="2.5" stroke-linecap="round" />
                        <path d="M20 10V22" stroke="url(#logoGrad)" stroke-width="2.5" stroke-linecap="round" />
                        <defs>
                            <linearGradient id="logoGrad" x1="8" y1="4" x2="24" y2="28" gradientUnits="userSpaceOnUse">
                                <stop stop-color="#3eb489" />
                                <stop offset="0.5" stop-color="#00d4aa" />
                                <stop offset="1" stop-color="#7fffd4" />
                            </linearGradient>
                        </defs>
                    </svg>
                    <span class="logo__text">Orbit E-Studio</span>
                </div>
                <div class="hero__badge">
                    <span class="badge">Orbit Engine</span>
                </div>
            </div>
            <p class="hero__tagline">Real-time text-to-speech with personal voice presets — running entirely in your browser</p>
        </header>

        <main class="main">
            <!-- Input Section -->
            <section class="input-section">
                <!-- Voice Selection -->
                <div class="voice-section">
                    <div class="voice-selector">
                        <label for="voice-select" class="voice-selector__label">Voice</label>
                        <select id="voice-select" class="voice-selector__dropdown">
                            <option value="">Loading voices...</option>
                        </select>
                    </div>
                    <div class="voice-upload">
                        <button id="voice-upload-btn" class="btn btn--outline btn--small">
                            <svg class="btn__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                                <polyline points="17 8 12 3 7 8" />
                                <line x1="12" y1="3" x2="12" y2="15" />
                            </svg>
                            <span>Upload Voice</span>
                        </button>
                        <input type="file" id="voice-upload" accept="audio/*" hidden>
                        <span id="voice-upload-status" class="voice-upload-status"></span>
                    </div>
                </div>

                <div class="textarea-wrap">
                    <textarea id="text-input" placeholder="Type or paste text to generate audio..." aria-label="Text to synthesize"></textarea>
                    <div class="textarea-meta">
                        <span class="char-count"><span id="char-count">0</span> chars</span>
                    </div>
                </div>

                <!-- Sample Texts -->
                <div class="sample-texts">
                    <span class="sample-texts__label">Try:</span>
                    <button class="sample-btn" data-text="Hello, welcome to Orbit E-Studio. This is a demonstration of real-time speech generation running entirely in your browser.">Demo greeting</button>
                    <button class="sample-btn" data-text="I completely understand how frustrating this must be for you. Let me take care of this right away and make sure we get it resolved.">Empathetic support</button>
                    <button class="sample-btn" data-text="Wow, congratulations! That's absolutely fantastic news! I'm so thrilled for you!">Excited</button>
                    <button class="sample-btn" data-text="I'm really sorry to hear about your loss. Please know that we're here for you, and take all the time you need.">Compassionate</button>
                    <button class="sample-btn" data-text="Good question. Let’s go step by step—start with the basics, then build from there.">Helpful guide</button>
                </div>

                <div class="controls">
                    <button id="generate-btn" class="btn btn--primary">
                        <svg class="btn__icon" viewBox="0 0 24 24" fill="currentColor">
                            <polygon points="5,3 19,12 5,21" />
                        </svg>
                        <span class="btn__text">Generate Audio</span>
                        <div class="btn__loader" id="btn-loader"></div>
                    </button>
                    <button id="stop-btn" class="btn btn--secondary" disabled>
                        <svg class="btn__icon" viewBox="0 0 24 24" fill="currentColor">
                            <rect x="6" y="6" width="12" height="12" rx="1" />
                        </svg>
                        <span class="btn__text">Stop</span>
                    </button>
                </div>
            </section>

            <!-- Output Section: Visualizer + Metrics -->
            <section class="output-section">
                <div class="visualizer-panel">
                    <div class="visualizer-panel__header">
                        <span class="visualizer-panel__title">Audio Output</span>
                        <div class="status-indicator" id="status-indicator">
                            <span class="status-dot"></span>
                            <span class="status-text" id="stat-status">Idle</span>
                        </div>
                    </div>
                    <div class="visualizer-container">
                        <canvas id="visualizer-waveform"></canvas>
                        <canvas id="visualizer-bars" class="visualizer-bars"></canvas>
                    </div>
                </div>

                <div class="metrics-panel">
                    <h3 class="metrics-panel__title">Performance</h3>

                    <div class="metric">
                        <div class="metric__header">
                            <span class="metric__label">Time to First Byte</span>
                            <button class="metric__info" aria-label="TTFB explanation" data-tooltip="Time from request until first audio chunk is received">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                    <circle cx="12" cy="12" r="10" />
                                    <path d="M12 16v-4M12 8h.01" />
                                </svg>
                            </button>
                        </div>
                        <div class="metric__value">
                            <span class="metric__number" id="stat-ttfb">--</span>
                            <span class="metric__unit">ms</span>
                        </div>
                        <div class="metric__bar">
                            <div class="metric__bar-fill" id="ttfb-bar"></div>
                        </div>
                    </div>

                    <div class="metric metric--highlight">
                        <div class="metric__header">
                            <span class="metric__label">Real-Time Factor</span>
                            <button class="metric__info" aria-label="RTFx explanation" data-tooltip="Audio duration divided by processing time. Values above 1x mean faster than real-time playback.">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                    <circle cx="12" cy="12" r="10" />
                                    <path d="M12 16v-4M12 8h.01" />
                                </svg>
                            </button>
                        </div>
                        <div class="metric__value">
                            <span class="metric__number metric__number--large" id="stat-rtfx">--</span>
                            <span class="metric__unit">x</span>
                        </div>
                        <div class="metric__context" id="rtfx-context">&gt;1x = faster than real-time</div>
                        <div class="metric__note" id="edge-opt-note" style="display: none;">(edge optimization applied)</div>
                        <div class="metric__note" id="full-gen-note" style="display: none;">(waiting for full generation first)</div>
                    </div>

                    <div class="metric metric--status">
                        <span class="metric__label">Engine</span>
                        <div class="model-status" id="model-status">
                            <span class="model-status__dot"></span>
                            <span class="model-status__text">Not ready</span>
                        </div>
                    </div>
                </div>
            </section>
        </main>

        <footer class="footer">
            <p>
                <a href="#" rel="nofollow noopener" onclick="event.preventDefault(); return false;">Orbit E-Studio</a>
                <span> — Private Voice Studio</span>
            </p>
            <p class="footer__disclaimer">Do not use without consent, or for fraud, misinformation, or any harmful/illegal purpose.</p>
        </footer>
    </div>

    <script type="module" src="onnx-streaming.js?v=1"></script>
</body>

</html>