Skip to content

Commit

Permalink
Allow multiple people to use the mixing console, and monitor multiple…
Browse files Browse the repository at this point in the history
… people. Fixes #211.
  • Loading branch information
jeffkaufman committed Feb 7, 2021
1 parent b218a77 commit 8e0ce82
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 44 deletions.
65 changes: 42 additions & 23 deletions html/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -998,27 +998,22 @@ window.backingTrack.addEventListener("change", (e) => {
const consoleChannels = new Map();
window.consoleChannels = consoleChannels;

let monitoredUserId = null;

let nMonitoredUsers = 0;
let iterationsWithNoMonitoredUsers = 0;
function mixerMonitorButtonClick(userid) {
if (singer_client) {
if (monitoredUserId) {
consoleChannels.get(monitoredUserId).children[5].classList.remove('activeButton');
}
if (monitoredUserId === userid) {
singer_client.x_send_metadata("monitoredUserId", "end");
monitoredUserId = null;
if (micPaused) {
toggle_mic();
}
}
else {
singer_client.x_send_metadata("monitoredUserId", userid);
monitoredUserId = userid;
consoleChannels.get(userid).children[5].classList.add('activeButton');
if (!micPaused) {
toggle_mic();
}
const monitorButton = consoleChannels.get(userid).children[5];
monitorButton.classList.add('edited');

if (monitorButton.classList.contains('activeButton')) {
singer_client.x_send_metadata("unmonitor", userid);
nMonitoredUsers--;
} else {
window.hearMonitor.checked = true;
window.hearMonitorDiv.style.display = "block";
singer_client.x_send_metadata("monitor", userid);
nMonitoredUsers++;
iterationsWithNoMonitoredUsers = 0;
}
}
}
Expand Down Expand Up @@ -1109,6 +1104,7 @@ function update_active_users(
const userid = user_summary[i][3];
const rms_volume = user_summary[i][4];
const muted = user_summary[i][5];
const is_monitored = user_summary[i][6];

let est_bucket = estimateBucket(offset_s);
if (!showBuckets) {
Expand All @@ -1122,7 +1118,7 @@ function update_active_users(
}
}

mic_volume_inputs.push([name, userid, mic_volume, rms_volume, offset_s]);
mic_volume_inputs.push([name, userid, mic_volume, rms_volume, offset_s, is_monitored]);
userids.add(userid);

// Don't update user buckets when we are not looking at that screen.
Expand Down Expand Up @@ -1233,13 +1229,15 @@ function update_active_users(
}
}

nMonitoredUsers = 0;
for (var i = 0; i < mic_volume_inputs.length; i++) {

const name = mic_volume_inputs[i][0];
const userid = mic_volume_inputs[i][1];
const vol = mic_volume_inputs[i][2];
const rms_volume = mic_volume_inputs[i][3];
const offset_s = mic_volume_inputs[i][4];
const is_monitored = mic_volume_inputs[i][5];

const channel = consoleChannels.get(userid);
const post_volume = vol < 0.0000001?
Expand Down Expand Up @@ -1267,12 +1265,30 @@ function update_active_users(
else {
channelVolumeInput.value = vol;
}


const monitorButton = channel.children[5];
monitorButton.classList.remove("edited");
monitorButton.classList.toggle("activeButton", is_monitored);
if (is_monitored) {
nMonitoredUsers++;
}
}
if (nMonitoredUsers > 0) {
iterationsWithNoMonitoredUsers = 0;
} else {
iterationsWithNoMonitoredUsers++;
}
const definitelyNotMonitoring = iterationsWithNoMonitoredUsers > 3;
window.hearMonitorDiv.style.display = definitelyNotMonitoring ? "none" : "block";
if (definitelyNotMonitoring) {
window.hearMonitor.checked = false;
}
}


window.hearMonitor.addEventListener("change", () => {
if (window.hearMonitor.checked) {
singer_client.x_send_metadata("begin_monitor", 1);
}
});

async function stop() {
if (app_state != APP_RUNNING &&
Expand Down Expand Up @@ -1878,6 +1894,9 @@ async function start_singing() {
if (in_spectator_mode) {
singer_client.x_send_metadata("spectator", 1);
}
if (window.hearMonitor.checked) {
singer_client.x_send_metadata("hear_monitor", 1);
}

if (delay_seconds) {
if (delay_seconds > 0) {
Expand Down
9 changes: 9 additions & 0 deletions html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
.activeButton {
background-color: red;
}
button.edited {
background-color: yellow;
}
input.editing {
background-color: wheat;
}
Expand Down Expand Up @@ -531,6 +534,9 @@
.healthy {
background-color: #0a0;
}
#hearMonitorDiv {
display: none;
}
</style>
<link rel="stylesheet" href="local-style.css"></link>

Expand Down Expand Up @@ -602,6 +608,9 @@ <h4>Best to leave these alone, since they do affect everyone</h4>
<h3>Mixing Console</h3>

<div id=mixingConsole>
<div id=hearMonitorDiv>
Hear Monitor Mix: <input type="checkbox" id="hearMonitor">
</div>
<button id=sortConsole>Sort</button>
</div>

Expand Down
7 changes: 4 additions & 3 deletions html/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,14 @@ export class ServerConnection extends ServerConnectionBase {
0, /*littleEndian=*/false);
pos += 2;

const muted =
const bits =
new DataView(data.slice(pos, pos + 1)).getUint8(0);
const muted = bits & 0b00000001;
const is_monitored = bits & 0b00000010;
pos += 1;

metadata.user_summary.push([
delay, name, mic_volume, userid, rms_volume, muted]);
delay, name, mic_volume, userid, rms_volume, muted, is_monitored]);
}
data = data.slice(pos);
}
Expand Down Expand Up @@ -321,7 +323,6 @@ export async function samples_to_server(
backingVolume: 'backing_volume',
micVolumes: 'mic_volume',
backingTrack: 'track',
monitoredUserId: 'monitor',
loopback_mode: 'loopback',
}

Expand Down
72 changes: 54 additions & 18 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
# 4 mic_volume: float32,
# 4 rms_volume: float32
# 2 delay: uint16
# 1 muted: boolean
BINARY_USER_CONFIG_FORMAT = struct.Struct(">Q32sffH?")
# 1 muted: uint8
BINARY_USER_CONFIG_FORMAT = struct.Struct(">Q32sffHB")

FRAME_SIZE = 128

Expand Down Expand Up @@ -578,20 +578,32 @@ def clean_users(server_clock) -> None:
if not active_users() and not state.server_controlled:
state.reset()

def setup_monitoring(monitoring_userid, monitored_userid) -> None:
for user in users.values():
user.is_monitoring = False
user.is_monitored = False
def samples_to_position(samples):
return round(samples / SAMPLE_RATE)

# We turn off monitoring by asking to monitor an invalid user ID.
if monitored_userid not in users:
def jump_user_after(user, position):
target = position + DELAY_INTERVAL
if target == samples_to_position(user.delay_samples):
return
user.send("delay_seconds", target)

def max_monitor_position():
max_delay_samples = 0
for user in active_users():
if user.is_monitored and user.delay_samples > max_delay_samples:
max_delay_samples = user.delay_samples
return samples_to_position(max_delay_samples)

users[monitoring_userid].is_monitoring = True
users[monitored_userid].is_monitored = True
def jump_to_latest_monitored_user(user):
max_pos = max_monitor_position()
current_pos = samples_to_position(user.delay_samples)
if max_pos > 0:
jump_user_after(user, max_pos)

users[monitoring_userid].send("delay_seconds", round(
users[monitored_userid].delay_samples / SAMPLE_RATE) + DELAY_INTERVAL)
def jump_monitors_to_latest_monitored_user():
for user in active_users():
if user.is_monitoring:
jump_to_latest_monitored_user(user)

def active_users():
server_clock = calculate_server_clock()
Expand All @@ -611,7 +623,8 @@ def user_summary(requested_user_summary) -> List[Any]:
user.mic_volume,
user.userid,
user.rms_volume,
user.muted))
user.muted,
user.is_monitored))
summary.sort()
return summary

Expand All @@ -630,20 +643,27 @@ def binary_user_summary(summary):
compact by only sending names if they have changed.
"""
binary_summaries = [struct.pack(">H", len(summary))]
for delay, name, mic_volume, userid, rms_volume, muted in summary:
for delay, name, mic_volume, userid, rms_volume, muted, is_monitored in summary:
# delay is encoded as a uint16
if delay < 0:
delay = 0
elif delay > 0xffff:
delay = 0xffff

bits = 0
if muted:
bits += 0b00000001
if is_monitored:
bits += 0b00000010

binary_summaries.append(
BINARY_USER_CONFIG_FORMAT.pack(
int(userid),
name.encode('utf8'),
mic_volume,
rms_volume,
delay,
muted))
bits))
resp = np.frombuffer(b"".join(binary_summaries), dtype=np.uint8)

if len(resp) != summary_length(len(summary)):
Expand Down Expand Up @@ -683,7 +703,9 @@ def update_audio(pos, n_samples, in_data, is_monitored):
wrap_assign(audio_queue, pos, new_audio)

if is_monitored:
wrap_assign(monitor_queue, pos, in_data)
wrap_assign(monitor_queue,
pos,
wrap_get(monitor_queue, pos, n_samples) + in_data)

old_n_people = wrap_get(n_people_queue, pos, n_samples)
new_n_people = old_n_people + np.ones(n_samples, np.int16)
Expand Down Expand Up @@ -1097,9 +1119,23 @@ def handle_post(in_json, in_data) -> Tuple[Any, str]:
# Handle all operations that do not require a userid
handle_special(query_params, server_clock, user, client_read_clock)

user.is_monitoring = query_params.get("hear_monitor", False)
monitor_userid = query_params.get("monitor", None)
if monitor_userid:
setup_monitoring(userid, monitor_userid)
changedMonitoring = False
if monitor_userid and monitor_userid in users:
users[monitor_userid].is_monitored = True
changedMonitoring = True
user.is_monitoring = True

unmonitor_userid = query_params.get("unmonitor", None)
if unmonitor_userid and unmonitor_userid in users:
users[unmonitor_userid].is_monitored = False
changedMonitoring = True

if changedMonitoring:
jump_monitors_to_latest_monitored_user()
if query_params.get("begin_monitor", False):
jump_to_latest_monitored_user(user)

### XXX: Debugging note: We used to do clearing of the buffer here, but now
### we do it above, closer to the top of the function.
Expand Down

0 comments on commit 8e0ce82

Please sign in to comment.