Skip to content
This repository has been archived by the owner on Apr 29, 2024. It is now read-only.

Commit

Permalink
Changed connection model to peer-to-peer (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
spietras authored May 25, 2022
1 parent 8a929dd commit 51c6e5b
Show file tree
Hide file tree
Showing 15 changed files with 595 additions and 336 deletions.
5 changes: 4 additions & 1 deletion .env/example.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
APP_NAME=concerto
PACKAGE_NAME=com.spietras.concerto
SERVER_URL=http://localhost:80
THEATRE_ADDRESS=localhost:54321
GRUPPETTO_ADDRESS=localhost:3478
GRUPPETTO_USER=user
GRUPPETTO_PASSWORD=password
63 changes: 16 additions & 47 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import "expo-dev-client";
import { useDeviceContext } from "twrnc";
import tw from "./src/tailwind";
import Main from "./src/screens/Main";
import {
Nunito_700Bold,
Nunito_800ExtraBold,
useFonts,
} from "@expo-google-fonts/nunito";
import { useFonts } from "@expo-google-fonts/nunito";
import { NavigationContainer } from "@react-navigation/native";
import Stack from "./src/navigation/Stack";
import Play from "./src/screens/Play";
Expand All @@ -21,45 +17,14 @@ import { MidiProvider } from "./src/contexts/midi";
import { RoomProvider } from "./src/contexts/room";
import { PlayerProvider } from "./src/contexts/player";
import { LogBox } from "react-native";
import Constants from "expo-constants";
import useFetch from "./src/hooks/useFetch";
import { EntriesResponse } from "./src/server/models";
import { fonts, iceServers, urls } from "./src/constants";

const fonts = {
Nunito_700Bold,
Nunito_800ExtraBold,
};

const availableEmojiCodes = [
"1f349",
"1f34b",
"1f34c",
"1f34d",
"1f34e",
"1f351",
"1f352",
"1f353",
"1f95d",
"1f345",
"1f965",
"1f955",
"1f33d",
"1f336",
"1f966",
"1f344",
"1f9c0",
"1f354",
"1f35f",
"1f355",
"1f32d",
];

const serverUrl = Constants.manifest?.extra?.serverUrl;

function configureLogging() {
LogBox.ignoreLogs([
"`new NativeEventEmitter()`",
"EventEmitter.removeListener",
]);
}
LogBox.ignoreLogs([
"`new NativeEventEmitter()`",
"EventEmitter.removeListener",
]);

const mainScreen = gestureHandlerRootHOC(Main);
const playScreen = gestureHandlerRootHOC(Play);
Expand All @@ -69,6 +34,7 @@ export default function App() {
useDeviceContext(tw);

const [fontsLoaded] = useFonts(fonts);
const { data } = useFetch<EntriesResponse>(urls.entries);

const onLayoutReady = React.useCallback(
async () => await SplashScreen.hideAsync(),
Expand All @@ -77,17 +43,16 @@ export default function App() {

React.useEffect(() => {
SplashScreen.preventAutoHideAsync().then();
configureLogging();
}, []);

if (!fontsLoaded) return null;
if (!fontsLoaded || data === undefined) return null;

return (
<NavigationContainer onReady={onLayoutReady}>
<Provider store={store}>
<SettingsProvider>
<MidiProvider>
<RoomProvider url={serverUrl}>
<RoomProvider url={urls.connect} iceServers={iceServers}>
<PlayerProvider>
<Stack.Navigator
initialRouteName="main"
Expand All @@ -99,7 +64,11 @@ export default function App() {
<Stack.Screen
name="main"
component={mainScreen}
initialParams={{ availableEmojiCodes: availableEmojiCodes }}
initialParams={{
availableEmojiCodes: data.available.map(
(emoji) => emoji.id
),
}}
options={{ orientation: "default" }}
/>
<Stack.Screen
Expand Down
5 changes: 4 additions & 1 deletion app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export default {
visible: "immersive",
},
extra: {
serverUrl: process.env.SERVER_URL,
theatreAddress: process.env.THEATRE_ADDRESS,
gruppettoAddress: process.env.GRUPPETTO_ADDRESS,
gruppettoUser: process.env.GRUPPETTO_USER,
gruppettoPassword: process.env.GRUPPETTO_PASSWORD,
},
plugins: [
[
Expand Down
52 changes: 46 additions & 6 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,50 @@
export const defaultMidiInput = "none";
export const baseMidiInputs = [{ label: "None", value: defaultMidiInput }];
import Constants from "expo-constants";
import { Nunito_700Bold, Nunito_800ExtraBold } from "@expo-google-fonts/nunito";

export const defaultInstrument = "electric_piano_1";
export const baseInstruments = [
export const fonts = {
Nunito_700Bold,
Nunito_800ExtraBold,
};

export const config = {
theatreAddress: Constants.manifest?.extra?.theatreAddress,
gruppettoAddress: Constants.manifest?.extra?.gruppettoAddress,
gruppettoUser: Constants.manifest?.extra?.gruppettoUser,
gruppettoPassword: Constants.manifest?.extra?.gruppettoPassword,
};

export const urls = {
entries: `http://${config.theatreAddress}/entries`,
connect: `ws://${config.theatreAddress}/connect`,
};

export const iceServers = [
{
urls: "stun:stun.l.google.com:19302",
},
{
label: "Electric Piano 1",
value: defaultInstrument,
urls: `turn:${config.gruppettoAddress}`,
username: config.gruppettoUser,
credential: config.gruppettoPassword,
},
];

const defaultMidiInput = {
value: "none",
label: "None",
};

export const midiInputs = {
default: defaultMidiInput.value,
base: [defaultMidiInput],
};

const defaultInstrument = {
value: "electric_piano_1",
label: "Electric Piano 1",
};

export const instruments = {
default: defaultInstrument.value,
base: [defaultInstrument],
};
14 changes: 7 additions & 7 deletions src/contexts/midi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import React from "react";
import MidiController, { DeviceInfo } from "../sound/MidiController";
import { useSettings } from "./settings";
import Midi from "react-native-midi";
import { baseMidiInputs, defaultMidiInput } from "../constants";
import useCancellable from "../hooks/useCancellable";
import { midiInputs } from "../constants";

const MidiContext = React.createContext<
| { controller: MidiController; inputs: { label: string; value: string }[] }
| undefined
>(undefined);

export function MidiProvider(props: { children: React.ReactNode }) {
const [availableInputs, setAvailableInputs] = React.useState(baseMidiInputs);
const [availableInputs, setAvailableInputs] = React.useState(midiInputs.base);

const settings = useSettings();

Expand All @@ -26,7 +26,7 @@ export function MidiProvider(props: { children: React.ReactNode }) {
},
]);
settings.midiInput.setValue((previous) => {
if (previous === defaultMidiInput) return device.id.toString();
if (previous === midiInputs.default) return device.id.toString();
return previous;
});
});
Expand All @@ -36,7 +36,7 @@ export function MidiProvider(props: { children: React.ReactNode }) {
previous.filter((input) => input.value !== device.id.toString())
);
settings.midiInput.setValue((previous) => {
if (previous === device.id.toString()) return defaultMidiInput;
if (previous === device.id.toString()) return midiInputs.default;
return previous;
});
});
Expand All @@ -49,18 +49,18 @@ export function MidiProvider(props: { children: React.ReactNode }) {
MidiController.getDevices().then((devices) => {
if (cancelInfo.cancelled) return;
setAvailableInputs([
...baseMidiInputs,
...midiInputs.base,
...devices.map((device) => ({
value: device.id.toString(),
label: `${device.productName} (${device.id})`,
})),
]);
settings.midiInput.setValue((previous) => {
if (previous === defaultMidiInput) return previous;
if (previous === midiInputs.default) return previous;
const device = devices.find(
(device) => device.id.toString() === previous
);
if (!device) return defaultMidiInput;
if (!device) return midiInputs.default;
return previous;
});
});
Expand Down
7 changes: 4 additions & 3 deletions src/contexts/player.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import Player, { getAvailableInstruments } from "../sound/Player";
import { baseInstruments } from "../constants";
import useCancellable from "../hooks/useCancellable";
import { camelCase, startCase } from "lodash";
import { instruments } from "../constants";

const PlayerContext = React.createContext<
| {
Expand All @@ -13,8 +13,9 @@ const PlayerContext = React.createContext<
>(undefined);

export function PlayerProvider(props: { children: React.ReactNode }) {
const [availableInstruments, setAvailableInstruments] =
React.useState(baseInstruments);
const [availableInstruments, setAvailableInstruments] = React.useState(
instruments.base
);

useCancellable((cancelInfo) => {
getAvailableInstruments().then((instruments) => {
Expand Down
3 changes: 2 additions & 1 deletion src/contexts/room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ const RoomContext = React.createContext<Room | undefined>(undefined);

export function RoomProvider(props: {
url: string;
iceServers: RTCIceServer[];
children: React.ReactNode;
}) {
const room = React.useMemo(() => new Room(props.url), []);
const room = React.useMemo(() => new Room(props.url, props.iceServers), []);
return <RoomContext.Provider value={room} children={props.children} />;
}

Expand Down
6 changes: 3 additions & 3 deletions src/contexts/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import useSetting from "../hooks/useSetting";
import { defaultInstrument, defaultMidiInput } from "../constants";
import { instruments, midiInputs } from "../constants";

type Setting = ReturnType<typeof useSetting>;

Expand All @@ -17,8 +17,8 @@ export function SettingsProvider(props: { children: React.ReactNode }) {
const value = {
noteOnVelocity: useSetting("noteOnVelocity", "1.0"),
noteOffVelocity: useSetting("noteOffVelocity", "1.0"),
instrument: useSetting("instrument", defaultInstrument),
midiInput: useSetting("midiInput", defaultMidiInput),
instrument: useSetting("instrument", instruments.default),
midiInput: useSetting("midiInput", midiInputs.default),
};
return <SettingsContext.Provider value={value} children={props.children} />;
}
Expand Down
57 changes: 57 additions & 0 deletions src/hooks/useFetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import useCancellable from "./useCancellable";
import React from "react";
import axios from "axios";

export type FetchState = "idle" | "fetching" | "fetched" | "error";

export default function useFetch<T>(url: string) {
const cache = React.useRef<Record<string, T>>({}).current;
const [status, setStatus] = React.useState<FetchState>("idle");
const [data, setData] = React.useState<T>();
const [error, setError] = React.useState<any>();

useCancellable(
(cancelInfo) => {
if (!url) return;

const controller = new AbortController();

const fetchData = async () => {
if (cancelInfo.cancelled) return;

setStatus("fetching");
if (cache[url]) {
if (cancelInfo.cancelled) return;
setData(cache[url]);
setStatus("fetched");
} else {
try {
const response = await axios.get<T>(url, {
signal: controller.signal,
});
if (cancelInfo.cancelled) return;
const newData = response.data;
cache[url] = newData;
setData(newData);
setStatus("fetched");
} catch (e) {
if (cancelInfo.cancelled) return;
setError(e);
setStatus("error");
}
}
};

fetchData().then();

return () => controller.abort();
},
[url]
);

return {
status: status,
data: data,
error: error,
};
}
18 changes: 8 additions & 10 deletions src/screens/Play.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import useCancellable from "../hooks/useCancellable";
import useAppSelector from "../hooks/useAppSelector";
import useAppDispatch from "../hooks/useAppDispatch";
import { RoomEvent } from "../server/Room";
import { addUser, removeUser, setUsers } from "../state/slices/users";
import { addUser, removeUser } from "../state/slices/users";
import { getNotesForRange } from "../sound/utils";
import { useRoom } from "../contexts/room";
import { useMidi } from "../contexts/midi";
import { usePlayer } from "../contexts/player";
import { defaultMidiInput } from "../constants";
import { User } from "../server/models";
import { midiInputs } from "../constants";

type KeyboardState = Map<number, string>;

Expand Down Expand Up @@ -184,14 +184,12 @@ export default function Play({ navigation }: PlayProps) {
);

const handleRoomConnected = React.useCallback(
(user: User, connectedUsers: User[]) => {
(user: User) => {
dispatch(
setUsers(
[user, ...connectedUsers].map((user) => ({
id: user.id,
emoji: user.avatar.emoji.id,
}))
)
addUser({
id: user.id,
emoji: user.avatar.emoji.id,
})
);
setUser(user.id);
},
Expand Down Expand Up @@ -263,7 +261,7 @@ export default function Play({ navigation }: PlayProps) {
useCancellable(() => {
if (
settings.midiInput.value === undefined ||
settings.midiInput.value === defaultMidiInput
settings.midiInput.value === midiInputs.default
)
return;
midi.controller.disconnect();
Expand Down
Loading

0 comments on commit 51c6e5b

Please sign in to comment.