Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto player duck on mic live. Closes #70 #177

Merged
merged 9 commits into from
Jan 24, 2021
66 changes: 60 additions & 6 deletions src/mixer/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ interface PlayerState {
loadError: boolean;
state: PlayerStateEnum;
volume: number;
volumeEnum: VolumePresetEnum;
gain: number;
trim: number;
micAutoDuck: boolean;
pfl: boolean;
timeCurrent: number;
timeRemaining: number;
Expand Down Expand Up @@ -67,7 +69,9 @@ const BasePlayerState: PlayerState = {
loading: -1,
state: "stopped",
volume: 1,
volumeEnum: "full",
gain: 0,
micAutoDuck: false,
trim: defaultTrimDB,
pfl: false,
timeCurrent: 0,
Expand All @@ -87,6 +91,7 @@ const mixerState = createSlice({
mic: {
open: false,
volume: 1,
volumeEnum: "full",
gain: 1,
baseGain: 0,
openError: null,
Expand Down Expand Up @@ -147,9 +152,12 @@ const mixerState = createSlice({
action: PayloadAction<{
player: number;
volume: number;
volumeEnum: VolumePresetEnum;
}>
) {
state.players[action.payload.player].volume = action.payload.volume;
state.players[action.payload.player].volumeEnum =
action.payload.volumeEnum;
},
setPlayerGain(
state,
Expand All @@ -169,6 +177,15 @@ const mixerState = createSlice({
) {
state.players[action.payload.player].trim = action.payload.trim;
},
setPlayerMicAutoDuck(
state,
action: PayloadAction<{
player: number;
enabled: boolean;
}>
) {
state.players[action.payload.player].micAutoDuck = action.payload.enabled;
},
setPlayerPFL(
state,
action: PayloadAction<{
Expand Down Expand Up @@ -665,6 +682,8 @@ export const {
toggleAutoAdvance,
togglePlayOnLoad,
toggleRepeat,
setTracklistItemID,
setPlayerMicAutoDuck,
} = mixerState.actions;

export const redrawWavesurfers = (): AppThunk => () => {
Expand All @@ -673,8 +692,6 @@ export const redrawWavesurfers = (): AppThunk => () => {
});
};

export const { setTracklistItemID } = mixerState.actions;

const FADE_TIME_SECONDS = 1;
export const setVolume = (
player: number,
Expand Down Expand Up @@ -711,7 +728,13 @@ export const setVolume = (
playerGainTweens[player].tweens.forEach((tween) => tween.pause());
if (playerGainTweens[player].target === level) {
delete playerGainTweens[player];
dispatch(mixerState.actions.setPlayerVolume({ player, volume: uiLevel }));
dispatch(
mixerState.actions.setPlayerVolume({
player,
volume: uiLevel,
volumeEnum: level,
})
);
dispatch(mixerState.actions.setPlayerGain({ player, gain: volume }));
return;
}
Expand All @@ -726,7 +749,13 @@ export const setVolume = (

// If not fading, just do it.
if (!fade) {
dispatch(mixerState.actions.setPlayerVolume({ player, volume: uiLevel }));
dispatch(
mixerState.actions.setPlayerVolume({
player,
volume: uiLevel,
volumeEnum: level,
})
);
dispatch(mixerState.actions.setPlayerGain({ player, gain: volume }));
return;
}
Expand All @@ -745,7 +774,13 @@ export const setVolume = (
const volumeTween = new Between(currentLevel, uiLevel)
.time(FADE_TIME_SECONDS * 1000)
.on("update", (val: number) => {
dispatch(mixerState.actions.setPlayerVolume({ player, volume: val }));
dispatch(
mixerState.actions.setPlayerVolume({
player,
volume: val,
volumeEnum: level,
})
);
});
const gainTween = new Between(currentGain, volume)
.time(FADE_TIME_SECONDS * 1000)
Expand Down Expand Up @@ -844,15 +879,34 @@ export const setMicProcessingEnabled = (enabled: boolean): AppThunk => async (
};

export const setMicVolume = (level: MicVolumePresetEnum): AppThunk => (
dispatch
dispatch,
getState
) => {
const players = getState().mixer.players;

// no tween fuckery here, just cut the level
const levelVal = level === "full" ? 1 : 0;
// actually, that's a lie - if we're turning it off we delay it a little to compensate for
// processing latency

if (levelVal !== 0) {
dispatch(mixerState.actions.setMicLevels({ volume: levelVal }));
for (let player = 0; player < players.length; player++) {
// If we have auto duck enabled on this channel player, tell it to fade down.
if (
players[player].micAutoDuck &&
players[player].volumeEnum === "full"
) {
dispatch(setVolume(player, "bed"));
}
}
} else {
for (let player = 0; player < players.length; player++) {
// If we have auto duck enabled on this channel player, tell it to fade back up.
if (players[player].micAutoDuck && players[player].volumeEnum === "bed") {
dispatch(setVolume(player, "full"));
}
}
window.setTimeout(() => {
dispatch(mixerState.actions.setMicLevels({ volume: levelVal }));
// latency, plus a little buffer
Expand Down
6 changes: 5 additions & 1 deletion src/optionsMenu/helpers/VUMeter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export function VUMeter(props: VUMeterProps) {

const isMic = props.source.substr(0, 3) === "mic";

const FPS = 30; // Limit the FPS so that lower spec machines have a better time juggling CPU.

useEffect(() => {
const animate = () => {
if (!isMic || isMicOpen) {
Expand All @@ -38,7 +40,9 @@ export function VUMeter(props: VUMeterProps) {
if (props.stereo) {
setPeakR(result[1]);
}
rafRef.current = requestAnimationFrame(animate);
setTimeout((current = rafRef.current, a = animate) => {
current = requestAnimationFrame(a);
}, 1000 / FPS);
}
};
if (!isMic || isMicOpen) {
Expand Down
2 changes: 1 addition & 1 deletion src/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const SessionHandler: React.FC = function() {
);
}

return <p></p>;
return <></>;
};

export default SessionHandler;
65 changes: 41 additions & 24 deletions src/showplanner/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,46 @@ function TimingButtons({ id }: { id: number }) {
);
}

function LoadedTrackInfo({ id }: { id: number }) {
const dispatch = useDispatch();
const loadedItem = useSelector(
(state: RootState) => state.mixer.players[id].loadedItem
);
const loading = useSelector(
(state: RootState) => state.mixer.players[id].loading
);
const loadError = useSelector(
(state: RootState) => state.mixer.players[id].loadError
);

return (
<span className="card-title">
<strong>
{loadedItem !== null && loading === -1
? loadedItem.title
: loading !== -1
? `LOADING`
: loadError
? "LOAD FAILED"
: "No Media Selected"}
</strong>
<small
className={
"border rounded border-danger text-danger p-1 m-1" +
(loadedItem !== null &&
loading === -1 &&
"clean" in loadedItem &&
!loadedItem.clean
? ""
: " d-none")
}
>
Explicit
</small>
</span>
);
}

export function Player({ id }: { id: number }) {
// Define time remaining (secs) when the play icon should flash.
const SECS_REMAINING_WARNING = 20;
Expand Down Expand Up @@ -302,30 +342,7 @@ export function Player({ id }: { id: number }) {
</div>
{settings.proMode && !customOutput && <ProModeButtons channel={id} />}
<div className="card-body p-0">
<span className="card-title">
<strong>
{playerState.loadedItem !== null && playerState.loading === -1
? playerState.loadedItem.title
: playerState.loading !== -1
? `LOADING`
: playerState.loadError
? "LOAD FAILED"
: "No Media Selected"}
</strong>
<small
className={
"border rounded border-danger text-danger p-1 m-1" +
(playerState.loadedItem !== null &&
playerState.loading === -1 &&
"clean" in playerState.loadedItem &&
!playerState.loadedItem.clean
? ""
: " d-none")
}
>
Explicit
</small>
</span>
<LoadedTrackInfo id={id} />
<br />
<span className="text-muted">
{playerState.loadedItem !== null && playerState.loading === -1
Expand Down
38 changes: 35 additions & 3 deletions src/showplanner/ProModeButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import React, { useState } from "react";
import { FaHeadphonesAlt, FaTachometerAlt } from "react-icons/fa";
import {
FaHeadphonesAlt,
FaMicrophoneAlt,
FaTachometerAlt,
} from "react-icons/fa";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../rootReducer";
import { setChannelTrim, setChannelPFL } from "../mixer/state";
import {
setChannelPFL,
setChannelTrim,
setPlayerMicAutoDuck,
} from "../mixer/state";

type ButtonIds = "trim" | "pfl";
type ButtonIds = "trim" | "pfl" | "autoDuck";

export default function ProModeButtons({ channel }: { channel: number }) {
const [activeButton, setActiveButton] = useState<ButtonIds | null>(null);
const trimVal = useSelector(
(state: RootState) => state.mixer.players[channel]?.trim
);

const micAutoDuck = useSelector(
(state: RootState) => state.mixer.players[channel]?.micAutoDuck
);

const pflState = useSelector(
(state: RootState) => state.mixer.players[channel]?.pfl
);
Expand Down Expand Up @@ -41,6 +54,20 @@ export default function ProModeButtons({ channel }: { channel: number }) {
>
<FaHeadphonesAlt />
</button>
<button
className={
"mr-1 btn " + (micAutoDuck ? "btn-info" : "btn-outline-dark")
}
title="Auto Duck on Mic Live"
onClick={() => {
dispatch(
setPlayerMicAutoDuck({ player: channel, enabled: !micAutoDuck })
);
setActiveButton("autoDuck");
}}
>
<FaMicrophoneAlt />
</button>
{activeButton === "trim" && (
<>
<input
Expand All @@ -63,6 +90,11 @@ export default function ProModeButtons({ channel }: { channel: number }) {
Pre Fader Listen:&nbsp;<strong>{pflState ? "Yes" : "No"}</strong>
</span>
)}
{activeButton === "autoDuck" && (
<span className="mt-2 ml-2">
Duck on Mic:&nbsp;<strong>{micAutoDuck ? "Yes" : "No"}</strong>
</span>
)}
</div>
</>
);
Expand Down
Loading