Skip to content

Commit 07a44fd

Browse files
committed
add readme and smaller fixes
1 parent 6a25e7f commit 07a44fd

File tree

8 files changed

+147
-94
lines changed

8 files changed

+147
-94
lines changed

README.md

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# SmartMicrophone
2+
3+
SmartMicrophone is a modern, open-source web-based microphone and remote control system for UltraStar Deluxe (USDX) and compatible karaoke games. It lets you use your smartphone as a wireless microphone, control the game from your phone, manage songs, and adjust settings---all through a fast, mobile-friendly web interface.
4+
5+
---
6+
7+
## Features
8+
- **Wireless Microphone:** Use your phone as a microphone for UltraStar Deluxe.
9+
- **Multi-mic Support:** Up to 6 virtual microphones, each mapped to a player slot.
10+
- **Remote Control:** Send keystrokes and text to the game, including navigation and search.
11+
- **Song Management:** Search, preview, and add songs to playlists.
12+
- **Settings Panel:** Configure audio options, delays, and more --- instantly, and per device
13+
- **Hotspot & Network Integration:** Supports Wi-Fi hotspot mode and advanced network forwarding.
14+
- **Automatic Device Mapping:** Maps domain names to hotspot IPs for easy connection.
15+
- **Secure HTTPS Option:** SSL support and port remapping for secure connections.
16+
17+
---
18+
19+
## Installation
20+
git clone https://github.com/dgruss/SmartMicrophone.git
21+
### Supported OS: Ubuntu/Debian (recommended)
22+
23+
#### 1. Install System Dependencies
24+
```sh
25+
sudo add-apt-repository ppa:longsleep/golang-backports
26+
sudo apt update
27+
sudo apt update
28+
sudo apt install python3 python3-pip git make gcc make pkg-config libopus-dev libopusfile-dev libpulse-dev golang-go libsdl2-image-dev python3-flask pipewire pipewire-pulse
29+
```
30+
31+
#### 2. Clone the Repository, Submodules, and build webrtc-cli
32+
```sh
33+
git clone https://github.com/dgruss/SmartMicrophone.git
34+
cd SmartMicrophone
35+
git submodule init
36+
git submodule update
37+
cd webrtc-cli
38+
make
39+
cd ..
40+
```
41+
42+
#### 3. Prepare UltraStar Deluxe
43+
- Install UltraStar Deluxe and place your songs in the appropriate folder (currently only the /songs folder is supported but symlinks are followed)
44+
- Some features require using [the dgruss beta3](https://github.com/dgruss/USDX/tree/beta3) version as they are not upstreamed yet
45+
- Make sure you know the path to your usdx directory (e.g., `/home/user/usdx`)
46+
47+
#### 4. (Optional) Configure SSL, Set up Wi-Fi Hotspot and Internet Forwarding
48+
- Place your SSL certificate and key files in the project directory if you want HTTPS --- this might be required to convince your phone to use WebRTC. Self-signed certificates work. Otherwise you can use a domain or subdomain you have on the Internet (give the DNS entry a short lifetime on your server!) and configure SmartMicrophone to use domain and certificates
49+
- See Advanced Networking below for details
50+
51+
## Usage
52+
53+
### Basic Server Startup
54+
```sh
55+
python3 server.py
56+
```
57+
58+
Do **not** run the server with `sudo`.
59+
60+
### Common Command-Line Arguments
61+
62+
Some options perform operations that require `sudo` permission. However, SmartMicrophone should not be run with `sudo`. Instead, SmartMicrophone will invoke `sudo` internally, which means you may be prompted for your `sudo` password.
63+
64+
65+
#### Networking & Security
66+
| Option | Description |
67+
|--------|-------------|
68+
| `--start-hotspot <name>` | Start the given hotspot using nmcli before domain setup |
69+
| `--internet-device <iface>` | Network interface providing internet connectivity (e.g., wlan0), invokes sudo |
70+
| `--hotspot-device <iface>` | Network interface for the hotspot (e.g., wlan1), invokes sudo |
71+
| `--ssl` | Enable SSL (requires --chain and --key) |
72+
| `--chain <cert>` | SSL chain/cert file (fullchain.pem or cert.pem) |
73+
| `--key <key>` | SSL private key file (privkey.pem) |
74+
| `--port <port>` | Port to run the server on (default: 5000) |
75+
| `--remap-ssl-port` | Remap ports so that users can access the server on the default HTTPS port, invokes sudo |
76+
| `--domain <domain>` | Setup a domain to hotspot IP mapping via NetworkManager/dnsmasq, invokes sudo |
77+
78+
#### UltraStar Deluxe Integration
79+
| Option | Description |
80+
|--------|-------------|
81+
| `--usdx-dir <path>` | Path to usdx directory (default: ../usdx) |
82+
| `--playlist-name <name>` | Playlist filename (default: SmartMicSession.upl) |
83+
| `--run-usdx` | Run UltraStar Deluxe after server startup |
84+
| `--audio-format <ext>` | Audio format of songs in UltraStar Deluxe (default: m4a) |
85+
| `--set-inputs` | Initialize [Record] section in config.ini for 6 virtual sinks |
86+
87+
#### Server Options
88+
| Option | Description |
89+
|--------|-------------|
90+
| `--debug` | Enable debug mode |
91+
92+
### Example: Full Setup with Hotspot and Forwarding
93+
```sh
94+
python3 server.py --ssl --chain ../cert.pem --key ../key.pem --remap-ssl-port --domain usdx.gruss.cc --set-inputs --start-hotspot usdx --internet-device wlan1 --hotspot-device wlan0 --run-usdx
95+
```
96+
97+
---
98+
99+
## Web Interface Overview
100+
101+
Access the server from your phone or computer:
102+
```
103+
http://<server-ip>:<port>/
104+
```
105+
Or, if using SSL and port remapping:
106+
```
107+
https://<domain>/
108+
```
109+
110+
### Tabs & Screenshots
111+
| &nbsp; | &nbsp; |
112+
|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------
113+
| <img src="static/microphone_tab.jpg" alt="Microphone Tab" width="90%"> | <img src="static/songs_tab.jpg" alt="Songs Tab" width="90%"> |
114+
| **Microphone** <br>- View and join available mic slots<br>- Assign multiple phones to a mic<br>- See current assignments | **Songs** <br>- Search by artist or title<br>- Preview audio<br>- Add songs to playlist |
115+
| <img src="static/control_tab.jpg" alt="Control Tab" width="90%"> | <img src="static/settings_tab.jpg" alt="Settings Tab" width="90%"> |
116+
| **Control** <br>- Acquire/release control<br>- Send keystrokes<br>- Type text to the game<br>- Only one user controls at a time | **Settings** <br>- Noise suppression, echo cancellation, normalization<br>- Set per-player delay<br>- Change display name<br>- Enable debug output |
117+
| <img src="static/lock_screen.jpg" alt="Lock Screen" width="90%"> | **Lock Screen** <br>- Prevent accidental taps/swipes<br>- Standby mode during singing |
118+
119+
## Advanced Networking
120+
121+
### Hotspot & Domain Mapping
122+
- Use `--start-hotspot` to activate a Wi-Fi hotspot
123+
- Use `--domain` to map a domain name to the hotspot IP for easy phone connection
124+
125+
### Internet Forwarding
126+
- Use `--internet-device` and `--hotspot-device` to forward internet from one interface to another (iptables rules, requires sudo)
127+
128+
### SSL & Port Remapping
129+
- Use `--ssl`, `--chain`, and `--key` for HTTPS
130+
- Use `--remap-ssl-port` to allow access via port 443 (requires sudo)
131+
132+
---
133+
134+
## Troubleshooting
135+
136+
### Common Issues
137+
138+
- **Network problems:**
139+
- Verify that the hotspot exists in network manager with the corresponding name (e.g. usdx).
140+
- flush all iptables rules `sudo iptables -F`
141+
- First connect the internet device, then create the hotspot, then run this tool.
142+
143+
---
144+
145+
## Contributing
146+
Pull requests and issues are welcome!

static/control_tab.jpg

106 KB
Loading

static/lock_screen.jpg

33.1 KB
Loading

static/microphone_tab.jpg

132 KB
Loading

static/script.js

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,99 +1360,6 @@ document.addEventListener('DOMContentLoaded', function() {
13601360

13611361
});
13621362

1363-
// Lock overlay behavior: drag-to-unlock
1364-
document.addEventListener('DOMContentLoaded', function() {
1365-
try {
1366-
const lockBtn = document.getElementById('lockBtn');
1367-
const lockOverlay = document.getElementById('lockOverlay');
1368-
const unlockTrack = document.getElementById('unlockTrack');
1369-
const unlockHandle = document.getElementById('unlockHandle');
1370-
// It's fine if lockBtn is missing because we replaced it with a video element.
1371-
if (!lockOverlay || !unlockTrack || !unlockHandle) return;
1372-
1373-
let dragging = false;
1374-
let startX = 0;
1375-
let handleStartLeft = 0;
1376-
1377-
function showLock() {
1378-
lockOverlay.style.display = 'flex';
1379-
lockOverlay.setAttribute('aria-hidden', 'false');
1380-
document.documentElement.style.overflow = 'hidden';
1381-
// reset handle
1382-
unlockHandle.style.left = '6px';
1383-
}
1384-
1385-
function hideLock() {
1386-
lockOverlay.style.display = 'none';
1387-
lockOverlay.setAttribute('aria-hidden', 'true');
1388-
document.documentElement.style.overflow = '';
1389-
// reset handle
1390-
unlockHandle.style.left = '6px';
1391-
}
1392-
1393-
lockBtn.addEventListener('click', (e) => { e.preventDefault(); showLock(); });
1394-
1395-
// pointer events for dragging
1396-
unlockHandle.addEventListener('pointerdown', (ev) => {
1397-
ev.preventDefault();
1398-
dragging = true;
1399-
startX = ev.clientX;
1400-
handleStartLeft = parseFloat(getComputedStyle(unlockHandle).left || '6').valueOf();
1401-
unlockHandle.setPointerCapture(ev.pointerId);
1402-
});
1403-
1404-
unlockHandle.addEventListener('pointermove', (ev) => {
1405-
if (!dragging) return;
1406-
ev.preventDefault();
1407-
const rect = unlockTrack.getBoundingClientRect();
1408-
const handleRect = unlockHandle.getBoundingClientRect();
1409-
const minLeft = 6;
1410-
const maxLeft = rect.width - handleRect.width - 6;
1411-
let dx = ev.clientX - startX;
1412-
let newLeft = handleStartLeft + dx;
1413-
newLeft = Math.max(minLeft, Math.min(maxLeft, newLeft));
1414-
unlockHandle.style.left = newLeft + 'px';
1415-
});
1416-
1417-
function endDrag(ev) {
1418-
if (!dragging) return;
1419-
dragging = false;
1420-
try { unlockHandle.releasePointerCapture(ev.pointerId); } catch(e){}
1421-
const rect = unlockTrack.getBoundingClientRect();
1422-
const handleRect = unlockHandle.getBoundingClientRect();
1423-
const minLeft = 6;
1424-
const maxLeft = rect.width - handleRect.width - 6;
1425-
const curLeft = parseFloat(getComputedStyle(unlockHandle).left || '6');
1426-
const progress = (curLeft - minLeft) / Math.max(1, (maxLeft - minLeft));
1427-
if (progress >= 0.70) {
1428-
// unlocked
1429-
hideLock();
1430-
} else {
1431-
// animate back to start
1432-
unlockHandle.style.transition = 'left 180ms ease-out';
1433-
unlockHandle.style.left = minLeft + 'px';
1434-
setTimeout(() => { unlockHandle.style.transition = ''; }, 200);
1435-
}
1436-
}
1437-
1438-
unlockHandle.addEventListener('pointerup', endDrag);
1439-
unlockHandle.addEventListener('pointercancel', endDrag);
1440-
// Also allow releasing anywhere on the track
1441-
unlockTrack.addEventListener('pointerup', endDrag);
1442-
unlockTrack.addEventListener('pointercancel', endDrag);
1443-
1444-
// Prevent accidental scrolling/selection while locked
1445-
lockOverlay.addEventListener('touchmove', (e) => { if (lockOverlay.style.display === 'flex') e.preventDefault(); }, {passive:false});
1446-
1447-
// Optional: allow pressing Escape to unlock
1448-
document.addEventListener('keydown', (e) => {
1449-
if (lockOverlay.style.display === 'flex' && e.key === 'Escape') hideLock();
1450-
});
1451-
} catch (e) {
1452-
printLog('Lock UI initialization failed: ' + e);
1453-
}
1454-
});
1455-
14561363
// Bottom video behavior: attach provided base64 mp4 and make click request fullscreen
14571364
document.addEventListener('DOMContentLoaded', function() {
14581365
try {

static/settings_tab.jpg

151 KB
Loading

static/songs_tab.jpg

128 KB
Loading

templates/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ <h3>Debug</h3>
338338
<!-- change name moved into Settings tab -->
339339

340340
<!-- Bottom video (replaces previous lock button). Clicking requests fullscreen for the video -->
341-
<video id="lockBtn"
341+
<video id="lockVideo"
342342
playsinline
343343
muted
344344
loop

0 commit comments

Comments
 (0)