Skip to content

Commit

Permalink
Solo/mute
Browse files Browse the repository at this point in the history
  • Loading branch information
tonygoldcrest committed Feb 13, 2024
1 parent 8ac16dd commit cd4faaf
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 24 deletions.
11 changes: 8 additions & 3 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ ipcMain.on('like-song', async (event, id, liked) => {
store.set(`songs.${id}.liked`, liked);
});

ipcMain.on('prevent-sleep', async () => {
powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');
});

ipcMain.on('resume-sleep', async () => {
powerSaveBlocker.stop(powerSaveBlockerId);
});

if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
Expand Down Expand Up @@ -128,8 +136,6 @@ const createWindow = async () => {
return display.bounds.x !== 0 || display.bounds.y !== 0;
});

powerSaveBlockerId = powerSaveBlocker.start('prevent-display-sleep');

mainWindow = new BrowserWindow({
show: false,
x: externalDisplay ? externalDisplay.bounds.x + 50 : 0,
Expand Down Expand Up @@ -180,7 +186,6 @@ app.on('window-all-closed', () => {
// after all windows have been closed
if (process.platform !== 'darwin') {
app.quit();
powerSaveBlocker.stop(powerSaveBlockerId);
}
});

Expand Down
4 changes: 3 additions & 1 deletion src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export type Channels =
| 'load-song-list'
| 'load-song'
| 'rescan-songs'
| 'like-song';
| 'like-song'
| 'prevent-sleep'
| 'resume-sleep';

const electronHandler = {
ipcRenderer: {
Expand Down
38 changes: 34 additions & 4 deletions src/renderer/components/AudioVolume/AudioVolume.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import { Slider } from 'antd';
import { FileName, Wrapper } from './styles';
import { Button } from 'antd';
import { faS, faVolumeMute } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FileName, VolumeControl, VolumeSlider, Wrapper } from './styles';

export interface AudioVolumeProps {
name: string;
volume: number;
isMuted: boolean;
isSoloed: boolean;
onChange: (value: number) => void;
onSoloClick: () => void;
onMuteClick: () => void;
}

export function AudioVolume({ name, volume, onChange }: AudioVolumeProps) {
export function AudioVolume({
name,
volume,
onChange,
isMuted,
isSoloed,
onSoloClick,
onMuteClick,
}: AudioVolumeProps) {
return (
<Wrapper>
<FileName>{name}</FileName>
<Slider defaultValue={volume * 100} onChange={onChange} />
<VolumeControl>
<VolumeSlider value={volume} onChange={onChange} />
<Button
shape="circle"
type={isMuted ? 'primary' : 'default'}
size="small"
icon={<FontAwesomeIcon icon={faVolumeMute} />}
onClick={onMuteClick}
/>
<Button
shape="circle"
type={isSoloed ? 'primary' : 'default'}
size="small"
icon={<FontAwesomeIcon icon={faS} />}
onClick={onSoloClick}
/>
</VolumeControl>
</Wrapper>
);
}
17 changes: 15 additions & 2 deletions src/renderer/components/AudioVolume/styles.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import styled from 'styled-components';
import { Slider } from 'antd';
import { theme } from '../../theme';

export const Wrapper = styled.div`
flex-grow: 1;
margin: 0 3px;
padding: 0 10px;
padding-top: 5px;
padding: 5px 10px;
border-radius: ${theme.borderRadius}px;
box-shadow: ${theme.boxShadow.soft};
`;
Expand All @@ -15,3 +15,16 @@ export const FileName = styled.div`
font-size: 12px;
color: ${theme.color.text.secondary};
`;

export const VolumeControl = styled.div`
display: flex;
align-items: center;
& > * + * {
margin-left: 7px;
}
`;

export const VolumeSlider = styled(Slider)`
flex-grow: 1;
`;
9 changes: 8 additions & 1 deletion src/renderer/services/audio-player/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ export class AudioPlayer {

isInitialised: boolean = false;

onEnded: (() => void) | null;

private startedAt: number = -1;

private offset: number = 0;

duration: number = 0;

constructor(trackConfigs: TrackConfig[]) {
constructor(trackConfigs: TrackConfig[], onEnded: () => void) {
this.context = new AudioContext();

this.ready = this.createTracks(trackConfigs);

this.onEnded = onEnded;

this.ready
.then((tracks) => {
this.duration = Math.max(...tracks.map((track) => track.duration));
Expand Down Expand Up @@ -89,6 +93,8 @@ export class AudioPlayer {
this.audioTracks.forEach((track) => track.destroy());
this.audioTracks = [];

this.onEnded = null;

this.context.close();
}

Expand All @@ -98,5 +104,6 @@ export class AudioPlayer {
}

this.stop();
this.onEnded?.();
};
}
123 changes: 110 additions & 13 deletions src/renderer/views/SongView/SongView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { SettingsMenu } from '../../components/SettingsMenu/SettingsMenu';
import { AudioVolume } from '../../components/AudioVolume/AudioVolume';
import { TrackConfig } from '../../services/audio-player/types';
import { Difficulty } from '../../../midi-parser/parser';
import { VolumeControl } from './types';

export function SongView() {
const [midiData, setMidiData] = useState<Buffer>();
Expand All @@ -32,10 +33,19 @@ export function SongView() {
const [isPlaying, setIsPlaying] = useState(false);
const [audioPlayer, setAudioPlayer] = useState<AudioPlayer | null>(null);
const [trackData, setTrackData] = useState<TrackConfig[]>([]);
const [volumeControls, setVolumeControls] = useState<VolumeControl[]>([]);

const { id } = useParams();
const navigate = useNavigate();

useEffect(() => {
window.electron.ipcRenderer.sendMessage('prevent-sleep');

return () => {
window.electron.ipcRenderer.sendMessage('resume-sleep');
};
}, []);

const loadSong = useCallback(() => {
window.electron.ipcRenderer.once<IpcLoadSongResponse>(
'load-song',
Expand All @@ -61,7 +71,16 @@ export function SongView() {
if (trackData.length === 0) {
return;
}
const player = new AudioPlayer(trackData);
const player = new AudioPlayer(trackData, () => setIsPlaying(false));

setVolumeControls(
trackData.map(({ name }) => ({
trackName: name,
volume: 100,
isMuted: false,
isSoloed: false,
})),
);

player.ready
.then(() => {
Expand All @@ -83,9 +102,6 @@ export function SongView() {
setCurrentPlayback(audioPlayer.currentTime);
};

// const endedListener = () => {
// setIsPlaying(false);
// };
const audioPolling = setInterval(playbackEventListener, 20);

return () => {
Expand All @@ -96,6 +112,24 @@ export function SongView() {
};
}, [audioPlayer]);

useEffect(() => {
if (volumeControls.length === 0 || !audioPlayer) {
return;
}

volumeControls.forEach((control) => {
const audioTrack = audioPlayer.audioTracks.find(
(track) => track.name === control.trackName,
);

if (!audioTrack) {
return;
}

audioTrack.setVolume(control.volume / 100);
});
}, [volumeControls, audioPlayer]);

useEffect(() => {
if (audioPlayer === null) {
return;
Expand All @@ -111,23 +145,86 @@ export function SongView() {
}, [audioPlayer, isPlaying]);

const volumeSliders = useMemo(() => {
if (!audioPlayer) {
if (volumeControls.length === 0 || !audioPlayer) {
return [];
}

return audioPlayer.audioTracks
.sort((a, b) => a.name.localeCompare(b.name))
.map((track) => {
return volumeControls
.sort((a, b) => a.trackName.localeCompare(b.trackName))
.map((control) => {
return (
<AudioVolume
key={track.name}
name={track.name}
volume={track.volume}
onChange={(value) => track.setVolume(value / 100)}
key={control.trackName}
name={control.trackName}
volume={control.volume}
isMuted={control.isMuted}
isSoloed={control.isSoloed}
onMuteClick={() => {
if (control.isMuted) {
setVolumeControls([
...volumeControls.filter((c) => c !== control),
{
...control,
volume: control.previousVolume ?? 100,
previousVolume: undefined,
isMuted: false,
},
]);
} else {
setVolumeControls([
...volumeControls.filter((c) => c !== control),
{
...control,
volume: 0,
previousVolume: control.volume,
isMuted: true,
},
]);
}
}}
onSoloClick={() => {
if (control.isSoloed) {
setVolumeControls([
...volumeControls
.filter((c) => c !== control)
.map((c) => ({
...c,
isMuted: false,
previousVolume: undefined,
volume: c.previousVolume ?? 100,
})),
{
...control,
isSoloed: false,
},
]);
} else {
setVolumeControls([
...volumeControls
.filter((c) => c !== control)
.map((c) => ({
...c,
isMuted: true,
previousVolume: c.previousVolume,
volume: 0,
})),
{
...control,
isSoloed: true,
},
]);
}
}}
onChange={(value) =>
setVolumeControls([
...volumeControls.filter((c) => c !== control),
{ ...control, volume: value },
])
}
/>
);
});
}, [audioPlayer]);
}, [volumeControls, audioPlayer]);

return (
<FullHeightLayout>
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/views/SongView/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface VolumeControl {
trackName: string;
volume: number;
previousVolume?: number;
isMuted: boolean;
isSoloed: boolean;
}

0 comments on commit cd4faaf

Please sign in to comment.