Front: Merge
This commit is contained in:
@@ -44,7 +44,6 @@ services:
|
||||
context: ./front
|
||||
dockerfile: Dockerfile.dev
|
||||
environment:
|
||||
- API_URL=http://back:3000/
|
||||
- SCOROMETER_URL=http://scorometer:6543/
|
||||
- NGINX_PORT=80
|
||||
ports:
|
||||
|
||||
13
front/API.ts
13
front/API.ts
@@ -388,15 +388,10 @@ export default class API {
|
||||
/**
|
||||
* Retrieve the authenticated user's play history
|
||||
*/
|
||||
public static async getUserPlayHistory(): Promise<Song[]> {
|
||||
const queryClient = new QueryClient();
|
||||
let songs = await queryClient.fetchQuery(
|
||||
["API", "allsongs"],
|
||||
API.getAllSongs
|
||||
);
|
||||
const shuffled = [...songs].sort(() => 0.5 - Math.random());
|
||||
|
||||
return shuffled.slice(0, 3);
|
||||
public static async getUserPlayHistory(): Promise<SongHistory[]> {
|
||||
return this.fetch({
|
||||
route: '/history'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ export const en = {
|
||||
searchBtn: 'Search',
|
||||
play: 'Play',
|
||||
playBtn: 'Play',
|
||||
practiceBtn: 'Practice',
|
||||
playAgain: 'Play Again',
|
||||
songPageBtn: 'Go to song page',
|
||||
level: 'Level',
|
||||
@@ -99,6 +100,18 @@ export const en = {
|
||||
userProfileFetchError: 'An error occured while fetching your profile',
|
||||
tryAgain: 'Try Again',
|
||||
|
||||
// Playback messages
|
||||
missed: 'Missed note',
|
||||
perfect: 'Perfect',
|
||||
great: 'Great',
|
||||
good: 'Good',
|
||||
wrong: 'Wrong',
|
||||
short: 'A little too short',
|
||||
long: 'A little too long',
|
||||
tooLong: 'Too Long',
|
||||
tooShort: 'Too Short',
|
||||
|
||||
|
||||
changePassword: 'Change password',
|
||||
oldPassword: 'Old password',
|
||||
newPassword: 'New password',
|
||||
@@ -159,6 +172,7 @@ export const fr: typeof en = {
|
||||
changeLanguageBtn: 'Changer la langue',
|
||||
searchBtn: 'Rechercher',
|
||||
playBtn: 'Jouer',
|
||||
practiceBtn: 'S\'entrainer',
|
||||
playAgain: 'Rejouer',
|
||||
songPageBtn: 'Aller sur la page de la chanson',
|
||||
level: 'Niveau',
|
||||
@@ -259,6 +273,16 @@ export const fr: typeof en = {
|
||||
userProfileFetchError: 'Une erreur est survenue lors de la récupération du profil',
|
||||
tryAgain: 'Réessayer',
|
||||
|
||||
// Playback messages
|
||||
missed: 'Raté',
|
||||
perfect: 'Parfait',
|
||||
great: 'Super',
|
||||
good: 'Bien',
|
||||
wrong: 'Oups',
|
||||
short: 'Un peu court',
|
||||
long: 'Un peu long',
|
||||
tooLong: 'Trop long',
|
||||
tooShort: 'Trop court',
|
||||
passwordUpdated: 'Mot de passe mis à jour',
|
||||
emailUpdated: 'Email mis à jour',
|
||||
|
||||
@@ -322,6 +346,7 @@ export const sp: typeof en = {
|
||||
changeLanguageBtn: 'Cambiar el idioma',
|
||||
searchBtn: 'Buscar',
|
||||
playBtn: 'reproducir',
|
||||
practiceBtn: 'Práctica',
|
||||
playAgain: 'Repetición',
|
||||
precisionScore: 'Précision',
|
||||
songPageBtn: 'canción',
|
||||
@@ -407,6 +432,17 @@ export const sp: typeof en = {
|
||||
userProfileFetchError: 'Ocurrió un error al obtener su perfil',
|
||||
tryAgain: 'intentar otra vez',
|
||||
|
||||
|
||||
// Playback messages
|
||||
missed: 'Te perdiste una nota',
|
||||
perfect: 'Perfecto',
|
||||
great: 'Excelente',
|
||||
good: 'Bueno',
|
||||
wrong: 'Equivocado',
|
||||
short: 'Un poco demasiado corto',
|
||||
long: 'Un poco demasiado largo',
|
||||
tooLong: 'Demasiado largo',
|
||||
tooShort: 'Demasiado corto',
|
||||
changePassword: 'Cambio de contraseña',
|
||||
oldPassword: 'Contraseña anterior',
|
||||
newPassword: 'Nueva contraseña',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
interface LessonHistory {
|
||||
songId: number;
|
||||
userId: number;
|
||||
interface SongHistory {
|
||||
songID: number;
|
||||
userID: number;
|
||||
score: number;
|
||||
difficulties: JSON;
|
||||
}
|
||||
|
||||
export default LessonHistory;
|
||||
export default SongHistory;
|
||||
@@ -56,6 +56,7 @@
|
||||
"react-native-web": "~0.18.7",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-timer-hook": "^3.0.5",
|
||||
"react-use-precision-timer": "^3.3.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"soundfont-player": "^0.12.0",
|
||||
"type-fest": "^3.6.0",
|
||||
|
||||
@@ -10,6 +10,7 @@ import CompetenciesTable from '../components/CompetenciesTable'
|
||||
import ProgressBar from "../components/ProgressBar";
|
||||
import Translate from "../components/Translate";
|
||||
import TextButton from "../components/TextButton";
|
||||
import Song from "../models/Song";
|
||||
|
||||
const HomeView = () => {
|
||||
const navigation = useNavigation();
|
||||
@@ -19,9 +20,20 @@ const HomeView = () => {
|
||||
const searchHistoryQuery = useQuery(['history', 'search'], () => API.getSearchHistory());
|
||||
const skillsQuery = useQuery(['skills'], () => API.getUserSkills());
|
||||
const nextStepQuery = useQuery(['user', 'recommendations'], () => API.getUserRecommendations());
|
||||
const artistsQueries = useQueries((playHistoryQuery.data?.concat(searchHistoryQuery.data ?? []).concat(nextStepQuery.data ?? []) ?? []).map((song) => (
|
||||
{ queryKey: ['artist', song.id], queryFn: () => API.getArtist(song.id) }
|
||||
)));
|
||||
const songHistory = useQueries(
|
||||
playHistoryQuery.data?.map(({ songID }) => ({
|
||||
queryKey: ['song', songID],
|
||||
queryFn: () => API.getSong(songID)
|
||||
})) ?? []
|
||||
);
|
||||
const artistsQueries = useQueries((songHistory
|
||||
.map((entry) => entry.data)
|
||||
.concat(nextStepQuery.data ?? [])
|
||||
.filter((s): s is Song => s !== undefined))
|
||||
.map((song) => (
|
||||
{ queryKey: ['artist', song.id], queryFn: () => API.getArtist(song.id) }
|
||||
))
|
||||
);
|
||||
|
||||
if (!userQuery.data || !skillsQuery.data || !searchHistoryQuery.data || !playHistoryQuery.data) {
|
||||
return <LoadingView/>
|
||||
@@ -65,7 +77,11 @@ const HomeView = () => {
|
||||
<Box flex={{ md: 1 }}>
|
||||
<SongCardGrid
|
||||
heading={<Translate translationKey='recentlyPlayed'/>}
|
||||
songs={playHistoryQuery.data?.filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId))
|
||||
songs={songHistory
|
||||
.filter((songQuery) => songQuery.data)
|
||||
.map(({ data }) => data)
|
||||
.filter((song, i, array) => array.map((s) => s.id).findIndex((id) => id == song.id) == i)
|
||||
.filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId))
|
||||
.map((song) => ({
|
||||
albumCover: song.cover,
|
||||
songTitle: song.name,
|
||||
|
||||
@@ -4,21 +4,25 @@ import * as ScreenOrientation from 'expo-screen-orientation';
|
||||
import { Box, Center, Column, Progress, Text, Row, View, useToast, Icon } from 'native-base';
|
||||
import IconButton from '../components/IconButton';
|
||||
import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
|
||||
import { useNavigation } from "../Navigation";
|
||||
import { useNavigation, RouteProps } from "../Navigation";
|
||||
import { useQuery, useQueryClient } from 'react-query';
|
||||
import API from '../API';
|
||||
import { LoadingView } from '../components/Loading';
|
||||
import Constants from 'expo-constants';
|
||||
import { useStopwatch } from 'react-timer-hook';
|
||||
import SlideView from '../components/PartitionVisualizer/SlideView';
|
||||
import MidiPlayer from 'midi-player-js';
|
||||
import SoundFont from 'soundfont-player';
|
||||
import VirtualPiano from '../components/VirtualPiano/VirtualPiano';
|
||||
import { strToKey, keyToStr, Note } from '../models/Piano';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '../state/Store';
|
||||
import { translate } from '../i18n/i18n';
|
||||
import { ColorSchemeType } from 'native-base/lib/typescript/components/types';
|
||||
import { useStopwatch } from "react-use-precision-timer";
|
||||
|
||||
type PlayViewProps = {
|
||||
songId: number
|
||||
songId: number,
|
||||
type: 'practice' | 'normal'
|
||||
}
|
||||
|
||||
|
||||
@@ -33,42 +37,52 @@ if (process.env.NODE_ENV != 'development' && Platform.OS === 'web') {
|
||||
}
|
||||
}
|
||||
|
||||
const PlayView = ({ songId }: RouteProps<PlayViewProps>) => {
|
||||
const PlayView = ({ songId, type, route }: RouteProps<PlayViewProps>) => {
|
||||
const accessToken = useSelector((state: RootState) => state.user.accessToken);
|
||||
const navigation = useNavigation();
|
||||
const queryClient = useQueryClient();
|
||||
const song = useQuery(['song', songId], () => API.getSong(songId));
|
||||
const toast = useToast();
|
||||
const webSocket = useRef<WebSocket>();
|
||||
const timer = useStopwatch({ autoStart: false });
|
||||
const [paused, setPause] = useState<boolean>(true);
|
||||
const stopwatch = useStopwatch();
|
||||
const [midiPlayer, setMidiPlayer] = useState<MidiPlayer.Player>();
|
||||
const [isVirtualPianoVisible, setVirtualPianoVisible] = useState<boolean>(false);
|
||||
|
||||
const [time, setTime] = useState(0);
|
||||
const [score, setScore] = useState(0); // Between 0 and 100
|
||||
const partitionRessources = useQuery(["partition", songId], () =>
|
||||
API.getPartitionRessources(songId)
|
||||
);
|
||||
|
||||
const onPause = () => {
|
||||
timer.pause();
|
||||
midiPlayer?.pause();
|
||||
stopwatch.pause();
|
||||
setPause(true);
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: "pause",
|
||||
paused: true,
|
||||
time: Date.now()
|
||||
time: time
|
||||
}));
|
||||
}
|
||||
const onResume = () => {
|
||||
if (stopwatch.isStarted()) {
|
||||
stopwatch.resume();
|
||||
} else {
|
||||
stopwatch.start();
|
||||
}
|
||||
setPause(false);
|
||||
midiPlayer?.play();
|
||||
timer.start();
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: "pause",
|
||||
paused: false,
|
||||
time: Date.now()
|
||||
time: time
|
||||
}));
|
||||
}
|
||||
const onEnd = () => {
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: "end"
|
||||
}));
|
||||
stopwatch.stop();
|
||||
webSocket.current?.close();
|
||||
midiPlayer?.pause();
|
||||
}
|
||||
@@ -86,19 +100,55 @@ const PlayView = ({ songId }: RouteProps<PlayViewProps>) => {
|
||||
webSocket.current.onopen = () => {
|
||||
webSocket.current!.send(JSON.stringify({
|
||||
type: "start",
|
||||
name: "clair-de-lune" /*song.data.id*/,
|
||||
id: song.data!.id,
|
||||
mode: type,
|
||||
bearer: accessToken
|
||||
}));
|
||||
};
|
||||
webSocket.current.onmessage = (message) => {
|
||||
try {
|
||||
const data = JSON.parse(message.data);
|
||||
if (data.type == 'end') {
|
||||
navigation.navigate('Score');
|
||||
} else if (data.song_launched == undefined) {
|
||||
toast.show({ description: data, placement: 'top', colorScheme: 'secondary' });
|
||||
navigation.navigate('Score', { songId: song.data!.id });
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
const points = data.info.score;
|
||||
const maxPoints = data.info.maxScore;
|
||||
|
||||
setScore(Math.floor(Math.max(points, 0) / maxPoints) * 100);
|
||||
|
||||
let formattedMessage = '';
|
||||
let messageColor: ColorSchemeType | undefined;
|
||||
|
||||
if (data.type == 'miss') {
|
||||
formattedMessage = translate('missed');
|
||||
messageColor = 'black';
|
||||
} else if (data.type == 'timing' || data.type == 'duration') {
|
||||
formattedMessage = translate(data[data.type]);
|
||||
switch (data[data.type]) {
|
||||
case 'perfect':
|
||||
messageColor = 'fuchsia';
|
||||
break;
|
||||
case 'great':
|
||||
messageColor = 'green';
|
||||
break;
|
||||
case 'short':
|
||||
case 'long':
|
||||
case 'good':
|
||||
messageColor = 'lightBlue';
|
||||
break;
|
||||
case 'too short':
|
||||
case 'too long':
|
||||
case 'wrong':
|
||||
messageColor = 'grey';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
toast.show({ description: formattedMessage, placement: 'top', colorScheme: messageColor ?? 'secondary' });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
inputs.forEach((input) => {
|
||||
@@ -110,9 +160,9 @@ const PlayView = ({ songId }: RouteProps<PlayViewProps>) => {
|
||||
const keyCode = message.data[1];
|
||||
webSocket.current?.send(JSON.stringify({
|
||||
type: keyIsPressed ? "note_on" : "note_off",
|
||||
node: keyCode,
|
||||
intensity: null,
|
||||
time: Date.now()
|
||||
note: keyCode,
|
||||
id: song.data!.id,
|
||||
time: time
|
||||
}))
|
||||
}
|
||||
inputIndex++;
|
||||
@@ -123,7 +173,6 @@ const PlayView = ({ songId }: RouteProps<PlayViewProps>) => {
|
||||
]).then(([midiFile, audioController]) => {
|
||||
const player = new MidiPlayer.Player((event) => {
|
||||
if (event['noteName']) {
|
||||
console.log(event);
|
||||
audioController.play(event['noteName']);
|
||||
}
|
||||
});
|
||||
@@ -134,17 +183,23 @@ const PlayView = ({ songId }: RouteProps<PlayViewProps>) => {
|
||||
const onMIDIFailure = () => {
|
||||
toast.show({ description: `Failed to get MIDI access` });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {});
|
||||
navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
|
||||
let interval = setInterval(() => setTime(() => stopwatch.getElapsedRunningTime()), 1);
|
||||
|
||||
return () => {
|
||||
ScreenOrientation.unlockAsync().catch(() => {});
|
||||
clearInterval(timer);
|
||||
onEnd();
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, [])
|
||||
const score = 20;
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (song.data) {
|
||||
navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
|
||||
}
|
||||
|
||||
}, [song.data]);
|
||||
|
||||
if (!song.data || !partitionRessources.data) {
|
||||
return <LoadingView/>;
|
||||
@@ -217,12 +272,12 @@ const PlayView = ({ songId }: RouteProps<PlayViewProps>) => {
|
||||
} onPress={() => {
|
||||
setVirtualPianoVisible(!isVirtualPianoVisible);
|
||||
}}/>
|
||||
<Text>{timer.minutes}:{timer.seconds.toString().padStart(2, '0')}</Text>
|
||||
<Text>{Math.floor(time / 60000)}:{Math.floor((time % 60000) / 1000).toFixed(0).toString().padStart(2, '0')}</Text>
|
||||
<IconButton size='sm' colorScheme='coolGray' variant='solid' icon={
|
||||
<Icon as={Ionicons} name="stop"/>
|
||||
} onPress={() => {
|
||||
onEnd();
|
||||
navigation.navigate('Score')
|
||||
navigation.navigate('Score', { songId: song.data.id });
|
||||
}}/>
|
||||
</Row>
|
||||
</Row>
|
||||
|
||||
@@ -10,33 +10,38 @@ import { LoadingView } from "../components/Loading";
|
||||
|
||||
type ScoreViewProps = { songId: number }
|
||||
|
||||
const ScoreView = ({ songId }: RouteProps<ScoreViewProps>) => {
|
||||
const ScoreView = ({ songId, route }: RouteProps<ScoreViewProps>) => {
|
||||
const theme = useTheme();
|
||||
const navigation = useNavigation();
|
||||
// const songQuery = useQuery(['song', props.songId], () => API.getSong(props.songId));
|
||||
// const songScoreQuery = useQuery(['song', props.songId, 'score', 'latest'], () => API.getLastSongPerformanceScore(props.songId));
|
||||
const songQuery = useQuery(['song', songId], () => API.getSong(songId));
|
||||
const artistQuery = useQuery(['song', songId],
|
||||
() => API.getArtist(songQuery.data!.artistId!),
|
||||
{ enabled: songQuery.data != undefined }
|
||||
);
|
||||
const songScoreQuery = useQuery(["score", songId], () => API.getUserPlayHistory()
|
||||
.then((history) => history.find((h) => h.songID == songId )!));
|
||||
// const perfoamnceRecommandationsQuery = useQuery(['song', props.songId, 'score', 'latest', 'recommendations'], () => API.getLastSongPerformanceScore(props.songId));
|
||||
const recommendations = useQuery(['song', 'recommendations'], () => API.getUserRecommendations());
|
||||
|
||||
if (!recommendations.data) {
|
||||
if (!recommendations.data || !songScoreQuery.data || !songQuery.data || (songQuery.data.artistId && !artistQuery.data)) {
|
||||
return <LoadingView/>;
|
||||
}
|
||||
|
||||
return <ScrollView p={8} contentContainerStyle={{ alignItems: 'center' }}>
|
||||
<VStack width={{ base: '100%', lg: '50%' }} textAlign='center'>
|
||||
<Text bold fontSize='lg'>Rolling in the Deep</Text>
|
||||
<Text bold>Adele - 3:45</Text>
|
||||
<Text bold fontSize='lg'>{songQuery.data.name}</Text>
|
||||
<Text bold>{artistQuery.data?.name}</Text>
|
||||
<Row style={{ justifyContent: 'center', display: 'flex' }}>
|
||||
<Card shadow={3} style={{ flex: 1 }}>
|
||||
<Image
|
||||
style={{ zIndex: 0, aspectRatio: 1, margin: 5, borderRadius: CardBorderRadius}}
|
||||
source={{ uri: 'https://imgs.search.brave.com/AinqAz0knOSOt0V3rcv7ps7aMVCo0QQfZ-1NTdwVjK0/rs:fit:1200:1200:1/g:ce/aHR0cDovLzEuYnAu/YmxvZ3Nwb3QuY29t/Ly0xTmZtZTdKbDVk/US9UaHd0Y3pieEVa/SS9BQUFBQUFBQUFP/TS9QdGx6ZWtWd2Zt/ay9zMTYwMC9BZGVs/ZSstKzIxKyUyNTI4/T2ZmaWNpYWwrQWxi/dW0rQ292ZXIlMjUy/OS5qcGc' }}
|
||||
source={{ uri: songQuery.data.cover }}
|
||||
/>
|
||||
</Card>
|
||||
<Card shadow={3} style={{ flex: 1 }}>
|
||||
<Column style={{ justifyContent: 'space-evenly', flexGrow: 1 }}>
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
{/*<Row style={{ alignItems: 'center' }}>
|
||||
<Text bold fontSize='xl'>
|
||||
80
|
||||
|
||||
</Text>
|
||||
<Translate translationKey='goodNotes' format={(t) => ' ' + t}/>
|
||||
</Row>
|
||||
@@ -45,15 +50,14 @@ const ScoreView = ({ songId }: RouteProps<ScoreViewProps>) => {
|
||||
80
|
||||
</Text>
|
||||
<Translate translationKey='goodNotesInARow' format={(t) => ' ' + t}/>
|
||||
</Row>
|
||||
</Row>*/}
|
||||
<Row style={{ alignItems: 'center' }}>
|
||||
<Translate translationKey='precisionScore' format={(t) => t + ' : '}/>
|
||||
<Translate translationKey='score' format={(t) => t + ' : '}/>
|
||||
<Text bold fontSize='xl'>
|
||||
{"80" + "%"}
|
||||
{songScoreQuery.data.score + "pts"}
|
||||
</Text>
|
||||
</Row>
|
||||
</Column>
|
||||
{/* Precision */}
|
||||
</Card>
|
||||
</Row>
|
||||
<SongCardGrid
|
||||
@@ -74,7 +78,7 @@ const ScoreView = ({ songId }: RouteProps<ScoreViewProps>) => {
|
||||
onPress={() => navigation.navigate('Home')}
|
||||
/>
|
||||
<TextButton
|
||||
onPress={() => navigation.navigate('Song', { songId: 1 })}
|
||||
onPress={() => navigation.navigate('Song', { songId })}
|
||||
translate={{ translationKey: 'playAgain' }}
|
||||
/>
|
||||
</Row>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Divider, Box, Center, Image, Text, VStack, PresenceTransition, Icon } from "native-base";
|
||||
import { Divider, Box, Center, Image, Text, VStack, PresenceTransition, Icon, Stack } from "native-base";
|
||||
import { useQuery } from 'react-query';
|
||||
import LoadingComponent, { LoadingView } from "../components/Loading";
|
||||
import React, { useEffect, useState } from "react";
|
||||
@@ -35,7 +35,7 @@ const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
||||
</Box>
|
||||
<Box style={{ flex: 0.5 }}/>
|
||||
<Box style={{ flex: 3, padding: 10, flexDirection: 'column', justifyContent: 'space-between' }}>
|
||||
<Box flex={1}>
|
||||
<Stack flex={1} space={3}>
|
||||
<Text bold isTruncated numberOfLines={2} fontSize='lg'>{songQuery.data!.name}</Text>
|
||||
<Text>
|
||||
<Translate translationKey='level'
|
||||
@@ -43,10 +43,15 @@ const SongLobbyView = (props: RouteProps<SongLobbyProps>) => {
|
||||
/>
|
||||
</Text>
|
||||
<TextButton translate={{ translationKey: 'playBtn' }} width='auto'
|
||||
onPress={() => navigation.navigate('Play', { songId: songQuery.data!.id })}
|
||||
onPress={() => navigation.navigate('Play', { songId: songQuery.data!.id, type: 'normal' })}
|
||||
rightIcon={<Icon as={Ionicons} name="play-outline"/>}
|
||||
/>
|
||||
</Box>
|
||||
<TextButton translate={{ translationKey: 'practiceBtn' }} width='auto'
|
||||
onPress={() => navigation.navigate('Play', { songId: songQuery.data!.id, type: 'practice' })}
|
||||
rightIcon={<Icon as={Ionicons} name="play-outline"/>}
|
||||
colorScheme='secondary'
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box style={{ flexDirection: 'row', justifyContent: 'space-between', padding: 30}}>
|
||||
|
||||
@@ -14939,6 +14939,11 @@ react-shallow-renderer@^16.13.1, react-shallow-renderer@^16.15.0:
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.12.0 || ^17.0.0 || ^18.0.0"
|
||||
|
||||
react-sub-unsub@^2.1.6:
|
||||
version "2.1.11"
|
||||
resolved "https://registry.yarnpkg.com/react-sub-unsub/-/react-sub-unsub-2.1.11.tgz#173d0803e1d7b29611cb29d95f47ed7798e93642"
|
||||
integrity sha512-FNKy0uD5wSieRE+l5RXaS0bUu6cR8XAXLDwOJnvSDGBMHcWVb1dod8ZkXYjPKtKR74tjYCEpMWcEAWCOoWNXxQ==
|
||||
|
||||
react-test-renderer@17.0.2, react-test-renderer@~17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
|
||||
@@ -14954,6 +14959,13 @@ react-timer-hook@^3.0.5:
|
||||
resolved "https://registry.yarnpkg.com/react-timer-hook/-/react-timer-hook-3.0.5.tgz#a8d930f99b180cd88da245965a26a17df3e7457b"
|
||||
integrity sha512-n+98SdmYvui2ne3KyWb3Ldu4k0NYQa3g/VzW6VEIfZJ8GAk/jJsIY700M8Nd2vNSTj05c7wKyQfJBqZ0x7zfiA==
|
||||
|
||||
react-use-precision-timer@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-use-precision-timer/-/react-use-precision-timer-3.3.1.tgz#8e49b6f58d507647925bf633a673a7cb0b2b924d"
|
||||
integrity sha512-PUCpFp48ftKoV2C+hz57mbqzqojE/Ol169Lyk2fFEIapsOH6tKIis8vZwmloedRe916qmJCOkXp+h9IB6QJY+A==
|
||||
dependencies:
|
||||
react-sub-unsub "^2.1.6"
|
||||
|
||||
react@18.1.0:
|
||||
version "18.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
|
||||
|
||||
78
scorometer/chroma_case/Message.py
Normal file
78
scorometer/chroma_case/Message.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Tuple
|
||||
|
||||
from validated_dc import ValidatedDC, get_errors, is_valid
|
||||
|
||||
|
||||
@dataclass
|
||||
class InvalidMessage:
|
||||
message: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class StartMessage(ValidatedDC):
|
||||
id: int
|
||||
bearer: str
|
||||
mode: Literal["normal", "practice"]
|
||||
type: Literal["start"] = "start"
|
||||
|
||||
|
||||
@dataclass
|
||||
class EndMessage(ValidatedDC):
|
||||
type: Literal["end"] = "end"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NoteOnMessage(ValidatedDC):
|
||||
time: int
|
||||
note: int
|
||||
id: int
|
||||
type: Literal["note_on"] = "note_on"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NoteOffMessage(ValidatedDC):
|
||||
time: int
|
||||
note: int
|
||||
id: int
|
||||
type: Literal["note_off"] = "note_off"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PauseMessage(ValidatedDC):
|
||||
paused: bool
|
||||
time: int
|
||||
type: Literal["pause"] = "pause"
|
||||
|
||||
|
||||
message_map = {
|
||||
"start": StartMessage,
|
||||
"end": EndMessage,
|
||||
"note_on": NoteOnMessage,
|
||||
"note_off": NoteOffMessage,
|
||||
"pause": PauseMessage,
|
||||
}
|
||||
|
||||
|
||||
def getMessage() -> (
|
||||
Tuple[
|
||||
StartMessage
|
||||
| EndMessage
|
||||
| NoteOnMessage
|
||||
| NoteOffMessage
|
||||
| PauseMessage
|
||||
| InvalidMessage,
|
||||
str,
|
||||
]
|
||||
):
|
||||
try:
|
||||
msg = input()
|
||||
obj = json.loads(msg)
|
||||
res = message_map[obj["type"]](**obj)
|
||||
if is_valid(res):
|
||||
return res, msg
|
||||
else:
|
||||
return InvalidMessage(str(get_errors(res))), msg
|
||||
except Exception as e:
|
||||
return InvalidMessage(str(e)), ""
|
||||
@@ -4,16 +4,22 @@ import json
|
||||
import logging
|
||||
import operator
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Tuple
|
||||
from typing import TypedDict
|
||||
|
||||
import requests
|
||||
from chroma_case.Key import Key
|
||||
from chroma_case.Message import (
|
||||
EndMessage,
|
||||
InvalidMessage,
|
||||
NoteOffMessage,
|
||||
NoteOnMessage,
|
||||
PauseMessage,
|
||||
StartMessage,
|
||||
getMessage,
|
||||
)
|
||||
from chroma_case.Partition import Partition
|
||||
from mido import MidiFile
|
||||
from validated_dc import ValidatedDC, get_errors, is_valid
|
||||
|
||||
BACK_URL = os.environ.get("BACK_URL") or "http://back:3000"
|
||||
MUSICS_FOLDER = os.environ.get("MUSICS_FOLDER") or "/musics/"
|
||||
@@ -27,77 +33,13 @@ NORMAL = 0
|
||||
PRACTICE = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class InvalidMessage:
|
||||
message: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class StartMessage(ValidatedDC):
|
||||
id: int
|
||||
bearer: str
|
||||
mode: Literal["normal", "practice"]
|
||||
type: Literal["start"] = "start"
|
||||
|
||||
|
||||
@dataclass
|
||||
class EndMessage(ValidatedDC):
|
||||
type: Literal["end"] = "end"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NoteOnMessage(ValidatedDC):
|
||||
time: int
|
||||
note: int
|
||||
id: int
|
||||
type: Literal["note_on"] = "note_on"
|
||||
|
||||
|
||||
@dataclass
|
||||
class NoteOffMessage(ValidatedDC):
|
||||
time: int
|
||||
note: int
|
||||
id: int
|
||||
type: Literal["note_off"] = "note_off"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PauseMessage(ValidatedDC):
|
||||
paused: bool
|
||||
time: int
|
||||
type: Literal["pause"] = "pause"
|
||||
|
||||
|
||||
message_map = {
|
||||
"start": StartMessage,
|
||||
"end": EndMessage,
|
||||
"note_on": NoteOnMessage,
|
||||
"note_off": NoteOffMessage,
|
||||
"pause": PauseMessage,
|
||||
}
|
||||
|
||||
|
||||
def getMessage() -> (
|
||||
Tuple[
|
||||
StartMessage
|
||||
| EndMessage
|
||||
| NoteOnMessage
|
||||
| NoteOffMessage
|
||||
| PauseMessage
|
||||
| InvalidMessage,
|
||||
str,
|
||||
]
|
||||
):
|
||||
try:
|
||||
msg = input()
|
||||
obj = json.loads(msg)
|
||||
res = message_map[obj["type"]](**obj)
|
||||
if is_valid(res):
|
||||
return res, msg
|
||||
else:
|
||||
return InvalidMessage(str(get_errors(res))), msg
|
||||
except Exception as e:
|
||||
return InvalidMessage(str(e)), ""
|
||||
class ScoroInfo(TypedDict):
|
||||
max_score: int
|
||||
score: int
|
||||
missed: int
|
||||
perfect: int
|
||||
great: int
|
||||
good: int
|
||||
|
||||
|
||||
def send(o):
|
||||
@@ -105,30 +47,29 @@ def send(o):
|
||||
|
||||
|
||||
class Scorometer:
|
||||
def __init__(self, mode, midiFile, song_id, user_id) -> None:
|
||||
self.partition = self.getPartition(midiFile)
|
||||
def __init__(self, mode: int, midiFile: str, song_id: int, user_id: int) -> None:
|
||||
self.partition: Partition = self.getPartition(midiFile)
|
||||
self.practice_partition: list[list[Key]] = self.getPracticePartition(mode)
|
||||
self.keys_down = []
|
||||
self.mode = mode
|
||||
self.song_id = song_id
|
||||
self.user_id = user_id
|
||||
self.score = 0
|
||||
self.missed = 0
|
||||
self.perfect = 0
|
||||
self.great = 0
|
||||
self.good = 0
|
||||
self.mode: int = mode
|
||||
self.song_id: int = song_id
|
||||
self.user_id: int = user_id
|
||||
self.wrong_ids = []
|
||||
self.difficulties = {}
|
||||
if mode == PRACTICE:
|
||||
get_start = operator.attrgetter("start")
|
||||
self.practice_partition = [
|
||||
list(g)
|
||||
for _, g in itertools.groupby(
|
||||
sorted(self.partition.notes, key=get_start), get_start
|
||||
)
|
||||
]
|
||||
else:
|
||||
self.practice_partition: list[list[Key]] = []
|
||||
self.info: ScoroInfo = {
|
||||
"max_score": len(self.partition.notes) * 100,
|
||||
"score": 0,
|
||||
"missed": 0,
|
||||
"perfect": 0,
|
||||
"great": 0,
|
||||
"good": 0,
|
||||
}
|
||||
|
||||
def getPartition(self, midiFile):
|
||||
def send(self, obj):
|
||||
obj["info"] = self.info
|
||||
send(obj)
|
||||
|
||||
def getPartition(self, midiFile: str):
|
||||
notes = []
|
||||
s = 3500
|
||||
notes_on = {}
|
||||
@@ -150,21 +91,26 @@ class Scorometer:
|
||||
notes_on[d["note"]] = s # 500
|
||||
return Partition(midiFile, notes)
|
||||
|
||||
def handleNoteOn(self, message: NoteOnMessage):
|
||||
_key = message.note
|
||||
timestamp = message.time
|
||||
is_down = any(x[0] == _key for x in self.keys_down)
|
||||
if not is_down:
|
||||
self.keys_down.append((_key, timestamp))
|
||||
logging.debug({"note": _key})
|
||||
def getPracticePartition(self, mode: int) -> list[list[Key]]:
|
||||
get_start = operator.attrgetter("start")
|
||||
return (
|
||||
[
|
||||
list(g)
|
||||
for _, g in itertools.groupby(
|
||||
sorted(self.partition.notes, key=get_start), get_start
|
||||
)
|
||||
]
|
||||
if mode == PRACTICE
|
||||
else []
|
||||
)
|
||||
|
||||
def handleNoteOff(self, message: NoteOffMessage):
|
||||
_key = message.note
|
||||
timestamp = message.time
|
||||
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
||||
self.keys_down.remove((_key, down_since))
|
||||
key = Key(_key, down_since, (timestamp - down_since))
|
||||
# debug({key: key})
|
||||
def handleNoteOn(self, message: NoteOnMessage):
|
||||
is_down = any(x[0] == message.note for x in self.keys_down)
|
||||
logging.debug({"note_on": message.note})
|
||||
if is_down:
|
||||
return
|
||||
self.keys_down.append((message.note, message.time))
|
||||
key = Key(key=message.note, start=message.time, duration=0)
|
||||
to_play = next(
|
||||
(
|
||||
i
|
||||
@@ -173,82 +119,129 @@ class Scorometer:
|
||||
),
|
||||
None,
|
||||
)
|
||||
if to_play is None:
|
||||
self.score -= 50
|
||||
logging.info("Invalid key.")
|
||||
if to_play:
|
||||
perf = self.getTimingScore(key, to_play)
|
||||
logging.debug({"note_on": f"{perf} on {message.note}"})
|
||||
self.send({"type": "timing", "id": message.id, "timing": perf})
|
||||
else:
|
||||
timingScore, timingInformation = self.getTiming(key, to_play)
|
||||
self.score += (
|
||||
self.info["score"] -= 50
|
||||
self.wrong_ids += [message.id]
|
||||
logging.debug({"note_on": f"wrong key {message.note}"})
|
||||
self.send({"type": "timing", "id": message.id, "timing": "wrong"})
|
||||
|
||||
def handleNoteOff(self, message: NoteOffMessage):
|
||||
logging.debug({"note_off": message.note})
|
||||
down_since = next(
|
||||
since for (h_key, since) in self.keys_down if h_key == message.note
|
||||
)
|
||||
self.keys_down.remove((message.note, down_since))
|
||||
if message.id in self.wrong_ids:
|
||||
logging.debug({"note_off": f"wrong key {message.note}"})
|
||||
self.send({"type": "duration", "id": message.id, "duration": "wrong"})
|
||||
return
|
||||
key = Key(
|
||||
key=message.note, start=down_since, duration=(message.time - down_since)
|
||||
)
|
||||
to_play = next(
|
||||
(
|
||||
i
|
||||
for i in self.partition.notes
|
||||
if i.key == key.key and self.is_timing_close(key, i) and i.done is False
|
||||
),
|
||||
None,
|
||||
)
|
||||
if to_play:
|
||||
perf = self.getDurationScore(key, to_play)
|
||||
self.info["score"] += (
|
||||
100
|
||||
if timingScore == "perfect"
|
||||
if perf == "perfect"
|
||||
else 75
|
||||
if timingScore == "great"
|
||||
if perf == "short" or perf == "long"
|
||||
else 50
|
||||
)
|
||||
to_play.done = True
|
||||
self.sendScore(message.id, timingScore, timingInformation)
|
||||
logging.debug({"note_off": f"{perf} on {message.note}"})
|
||||
self.send({"type": "duration", "id": message.id, "duration": perf})
|
||||
else:
|
||||
logging.warning("note_off: no key to play but it was not a wrong note_on")
|
||||
|
||||
def handleNoteOnPractice(self, message: NoteOnMessage):
|
||||
_key = message.note
|
||||
timestamp = message.time
|
||||
is_down = any(x[0] == _key for x in self.keys_down)
|
||||
if not is_down:
|
||||
self.keys_down.append((_key, timestamp))
|
||||
logging.debug({"note": _key})
|
||||
|
||||
def handleNoteOffPractice(self, message: NoteOffMessage):
|
||||
_key = message.note
|
||||
timestamp = message.time
|
||||
# is_down = any(x[0] == _key for x in self.keys_down)
|
||||
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
||||
self.keys_down.remove((_key, down_since))
|
||||
key = Key(_key, down_since, (timestamp - down_since))
|
||||
is_down = any(x[0] == message.note for x in self.keys_down)
|
||||
logging.debug({"note_on": message.note})
|
||||
if is_down:
|
||||
return
|
||||
self.keys_down.append((message.note, message.time))
|
||||
key = Key(key=message.note, start=message.time, duration=0)
|
||||
keys_to_play = next(
|
||||
(i for i in self.practice_partition if any(x.done is not True for x in i)),
|
||||
None,
|
||||
)
|
||||
if keys_to_play is None:
|
||||
logging.info("Key sent but there is no keys to play")
|
||||
self.score -= 50
|
||||
self.send({"type": "error", "error": "no keys should be played"})
|
||||
return
|
||||
to_play = next(
|
||||
(i for i in keys_to_play if i.key == key.key and i.done is not True), None
|
||||
)
|
||||
if to_play is None:
|
||||
self.score -= 50
|
||||
logging.info("Invalid key.")
|
||||
if to_play:
|
||||
perf = "practice"
|
||||
logging.debug({"note_on": f"{perf} on {message.note}"})
|
||||
self.send({"type": "timing", "id": message.id, "timing": perf})
|
||||
else:
|
||||
timingScore, _ = self.getTiming(key, to_play)
|
||||
self.score += (
|
||||
100
|
||||
if timingScore == "perfect"
|
||||
else 75
|
||||
if timingScore == "great"
|
||||
else 50
|
||||
)
|
||||
self.wrong_ids += [message.id]
|
||||
logging.debug({"note_on": f"wrong key {message.note}"})
|
||||
self.send({"type": "timing", "id": message.id, "timing": "wrong"})
|
||||
|
||||
def handleNoteOffPractice(self, message: NoteOffMessage):
|
||||
logging.debug({"note_off": message.note})
|
||||
down_since = next(
|
||||
since for (h_key, since) in self.keys_down if h_key == message.note
|
||||
)
|
||||
self.keys_down.remove((message.note, down_since))
|
||||
if message.id in self.wrong_ids:
|
||||
logging.debug({"note_off": f"wrong key {message.note}"})
|
||||
self.send({"type": "duration", "id": message.id, "duration": "wrong"})
|
||||
return
|
||||
key = Key(
|
||||
key=message.note, start=down_since, duration=(message.time - down_since)
|
||||
)
|
||||
keys_to_play = next(
|
||||
(i for i in self.practice_partition if any(x.done is not True for x in i)),
|
||||
None,
|
||||
)
|
||||
if keys_to_play is None:
|
||||
logging.info("Invalid key.")
|
||||
self.info["score"] -= 50
|
||||
# TODO: I dont think this if is right
|
||||
# self.sendScore(message.id, "wrong key", "wrong key")
|
||||
return
|
||||
to_play = next(
|
||||
(i for i in keys_to_play if i.key == key.key and i.done is not True), None
|
||||
)
|
||||
if to_play:
|
||||
perf = "practice"
|
||||
to_play.done = True
|
||||
self.sendScore(message.id, timingScore, "practice")
|
||||
logging.debug({"note_off": f"{perf} on {message.note}"})
|
||||
self.send({"type": "duration", "id": message.id, "duration": perf})
|
||||
else:
|
||||
self.send({"type": "duration", "id": message.id, "duration": "wrong"})
|
||||
|
||||
def getTiming(self, key: Key, to_play: Key):
|
||||
return self.getTimingScore(key, to_play), self.getTimingInfo(key, to_play)
|
||||
|
||||
def getTimingScore(self, key: Key, to_play: Key):
|
||||
def getDurationScore(self, key: Key, to_play: Key):
|
||||
tempo_percent = abs((key.duration / to_play.duration) - 1)
|
||||
if tempo_percent < 0.3:
|
||||
timingScore = "perfect"
|
||||
elif tempo_percent < 0.5:
|
||||
timingScore = "great"
|
||||
timingScore = "short" if key.duration < to_play.duration else "long"
|
||||
else:
|
||||
timingScore = "good"
|
||||
timingScore = "too short" if key.duration < to_play.duration else "too long"
|
||||
return timingScore
|
||||
|
||||
def getTimingInfo(self, key: Key, to_play: Key):
|
||||
def getTimingScore(self, key: Key, to_play: Key):
|
||||
return (
|
||||
"perfect"
|
||||
if abs(key.start - to_play.start) < 200
|
||||
else "fast"
|
||||
if key.start < to_play.start
|
||||
else "late"
|
||||
if abs(key.start - to_play.start) < 100
|
||||
else "great"
|
||||
if (key.start < to_play.start) < 300
|
||||
else "good"
|
||||
)
|
||||
|
||||
# is it in the 500 ms range
|
||||
@@ -268,7 +261,7 @@ class Scorometer:
|
||||
match message:
|
||||
case InvalidMessage(error):
|
||||
logging.warning(f"Invalid message {line} with error: {error}")
|
||||
send({"error": f"Invalid message {line} with error: {error}"})
|
||||
self.send({"error": f"Invalid message {line} with error: {error}"})
|
||||
case NoteOnMessage():
|
||||
if self.mode == NORMAL:
|
||||
self.handleNoteOn(message)
|
||||
@@ -288,43 +281,25 @@ class Scorometer:
|
||||
f"Expected note_on note_off or pause message but got {message.type} instead"
|
||||
)
|
||||
|
||||
def sendScore(self, id, timingScore, timingInformation):
|
||||
send(
|
||||
{
|
||||
"id": id,
|
||||
"timingScore": timingScore,
|
||||
"timingInformation": timingInformation,
|
||||
}
|
||||
)
|
||||
|
||||
def gameLoop(self):
|
||||
while True:
|
||||
if select.select(
|
||||
[
|
||||
sys.stdin,
|
||||
],
|
||||
[],
|
||||
[],
|
||||
0.0,
|
||||
)[0]:
|
||||
message, line = getMessage()
|
||||
logging.info(f"handling message {line}")
|
||||
self.handleMessage(message, line)
|
||||
else:
|
||||
pass
|
||||
message, line = getMessage()
|
||||
logging.debug(f"handling message {line}")
|
||||
self.handleMessage(message, line)
|
||||
|
||||
def endGame(self):
|
||||
for i in self.partition.notes:
|
||||
if i.done is False:
|
||||
self.score -= 50
|
||||
self.info["score"] -= 50
|
||||
self.info["missed"] += 1
|
||||
send(
|
||||
{
|
||||
"overallScore": self.score,
|
||||
"overallScore": self.info["score"],
|
||||
"score": {
|
||||
"missed": self.missed,
|
||||
"good": self.good,
|
||||
"great": self.great,
|
||||
"perfect": self.perfect,
|
||||
"missed": self.info["missed"],
|
||||
"good": self.info["good"],
|
||||
"great": self.info["great"],
|
||||
"perfect": self.info["perfect"],
|
||||
"maxScore": len(self.partition.notes) * 100,
|
||||
},
|
||||
}
|
||||
@@ -335,7 +310,7 @@ class Scorometer:
|
||||
json={
|
||||
"songID": self.song_id,
|
||||
"userID": self.user_id,
|
||||
"score": self.score,
|
||||
"score": self.info["score"],
|
||||
"difficulties": self.difficulties,
|
||||
},
|
||||
)
|
||||
@@ -399,4 +374,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
@@ -1,10 +1,19 @@
|
||||
{"id": 2, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 3, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 4, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 5, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 6, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 7, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 8, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 9, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 10, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"overallScore": 850, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
{"type": "timing", "id": 2, "timing": "perfect", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 2, "duration": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 7, "timing": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 7, "duration": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 9, "timing": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 9, "duration": "perfect", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 10, "timing": "perfect", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 10, "duration": "perfect", "info": {"max_score": 1000, "score": 900, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 850, "score": {"missed": 1, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{"id": 1, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 2, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 3, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 4, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 5, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 6, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 7, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 8, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 9, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"id": 10, "timingScore": "perfect", "timingInformation": "fast"}
|
||||
{"type": "timing", "id": 1, "timing": "great", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 1, "duration": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 2, "timing": "great", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 2, "duration": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "great", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "great", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "great", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "great", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 7, "timing": "great", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 7, "duration": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "great", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "perfect", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 9, "timing": "great", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 9, "duration": "perfect", "info": {"max_score": 1000, "score": 900, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 10, "timing": "great", "info": {"max_score": 1000, "score": 900, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 10, "duration": "perfect", "info": {"max_score": 1000, "score": 1000, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 1000, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{"id": 1, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 2, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 3, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 4, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 5, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 6, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 7, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 8, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"overallScore": 700, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
{"type": "timing", "id": 1, "timing": "perfect", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 1, "duration": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 2, "timing": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 2, "duration": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 7, "timing": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 7, "duration": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "perfect", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 700, "score": {"missed": 2, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{"id": 1, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 2, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 3, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 4, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 5, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 6, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 7, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 8, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 9, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 10, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"type": "timing", "id": 1, "timing": "perfect", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 1, "duration": "too short", "info": {"max_score": 1000, "score": 50, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 2, "timing": "perfect", "info": {"max_score": 1000, "score": 50, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 2, "duration": "too short", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "too short", "info": {"max_score": 1000, "score": 150, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "perfect", "info": {"max_score": 1000, "score": 150, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "too short", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "too short", "info": {"max_score": 1000, "score": 250, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "perfect", "info": {"max_score": 1000, "score": 250, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "too short", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 7, "timing": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 7, "duration": "too short", "info": {"max_score": 1000, "score": 350, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "perfect", "info": {"max_score": 1000, "score": 350, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "too short", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 9, "timing": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 9, "duration": "too short", "info": {"max_score": 1000, "score": 450, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 10, "timing": "perfect", "info": {"max_score": 1000, "score": 450, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 10, "duration": "too short", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 500, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{"id": 1, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 2, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 3, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 4, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 5, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 6, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 7, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 8, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 9, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"id": 10, "timingScore": "good", "timingInformation": "perfect"}
|
||||
{"type": "timing", "id": 1, "timing": "perfect", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 1, "duration": "too long", "info": {"max_score": 1000, "score": 50, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 2, "timing": "perfect", "info": {"max_score": 1000, "score": 50, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 2, "duration": "too long", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "too long", "info": {"max_score": 1000, "score": 150, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "perfect", "info": {"max_score": 1000, "score": 150, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "too long", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "too long", "info": {"max_score": 1000, "score": 250, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "perfect", "info": {"max_score": 1000, "score": 250, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "too long", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 7, "timing": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 7, "duration": "too long", "info": {"max_score": 1000, "score": 350, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "perfect", "info": {"max_score": 1000, "score": 350, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "too long", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 9, "timing": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 9, "duration": "too long", "info": {"max_score": 1000, "score": 450, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 10, "timing": "perfect", "info": {"max_score": 1000, "score": 450, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 10, "duration": "too long", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 500, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{"id": 1, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 2, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 3, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 4, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 5, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 6, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 7, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 8, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 9, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"id": 10, "timingScore": "perfect", "timingInformation": "late"}
|
||||
{"type": "timing", "id": 1, "timing": "great", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 1, "duration": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 2, "timing": "great", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 2, "duration": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "great", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "great", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "great", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "great", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 7, "timing": "great", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 7, "duration": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "great", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "perfect", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 9, "timing": "great", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 9, "duration": "perfect", "info": {"max_score": 1000, "score": 900, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 10, "timing": "great", "info": {"max_score": 1000, "score": 900, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 10, "duration": "perfect", "info": {"max_score": 1000, "score": 1000, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 1000, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
{"id": 1, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 2, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 3, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 4, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 5, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 6, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 7, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 8, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 9, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 10, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"type": "timing", "id": 1, "timing": "perfect", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 1, "duration": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 2, "timing": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 2, "duration": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 7, "timing": "perfect", "info": {"max_score": 1000, "score": 600, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 7, "duration": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "perfect", "info": {"max_score": 1000, "score": 700, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "perfect", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 9, "timing": "perfect", "info": {"max_score": 1000, "score": 800, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 9, "duration": "perfect", "info": {"max_score": 1000, "score": 900, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 10, "timing": "perfect", "info": {"max_score": 1000, "score": 900, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 10, "duration": "perfect", "info": {"max_score": 1000, "score": 1000, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 1000, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{"id": 1, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 3, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 4, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 5, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 6, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 8, "timingScore": "good", "timingInformation": "late"}
|
||||
{"id": 9, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"id": 10, "timingScore": "perfect", "timingInformation": "perfect"}
|
||||
{"overallScore": 650, "score": {"missed": 0, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
{"type": "timing", "id": 1, "timing": "perfect", "info": {"max_score": 1000, "score": 0, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 1, "duration": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 3, "timing": "perfect", "info": {"max_score": 1000, "score": 100, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 3, "duration": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 4, "timing": "perfect", "info": {"max_score": 1000, "score": 200, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 4, "duration": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 5, "timing": "perfect", "info": {"max_score": 1000, "score": 300, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 5, "duration": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 6, "timing": "perfect", "info": {"max_score": 1000, "score": 400, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 6, "duration": "perfect", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 8, "timing": "great", "info": {"max_score": 1000, "score": 500, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 8, "duration": "too long", "info": {"max_score": 1000, "score": 550, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 9, "timing": "perfect", "info": {"max_score": 1000, "score": 550, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 9, "duration": "perfect", "info": {"max_score": 1000, "score": 650, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "timing", "id": 10, "timing": "perfect", "info": {"max_score": 1000, "score": 650, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"type": "duration", "id": 10, "duration": "perfect", "info": {"max_score": 1000, "score": 750, "missed": 0, "perfect": 0, "great": 0, "good": 0}}
|
||||
{"overallScore": 650, "score": {"missed": 2, "good": 0, "great": 0, "perfect": 0, "maxScore": 1000}}
|
||||
|
||||
@@ -13,7 +13,13 @@ function test {
|
||||
cat $1/input | BACK_URL="http://localhost:3000" MUSICS_FOLDER="../../musics/" python3 ../main.py 1> /tmp/scorometer_res 2> /tmp/scorometer_log
|
||||
TESTS_DONE=$((TESTS_DONE + 1))
|
||||
if ! diff $1/output /tmp/scorometer_res &>/dev/null; then
|
||||
echo "$t failed, do runner.sh $t for more info"
|
||||
|
||||
echo "=========== CURRENT OUTPUT ==========="
|
||||
cat /tmp/scorometer_res
|
||||
echo "======================================"
|
||||
echo "=========== EXPECTED OUTPUT =========="
|
||||
cat $1/output
|
||||
echo "======================================"
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
else
|
||||
TESTS_SUCCESS=$((TESTS_SUCCESS + 1))
|
||||
|
||||
Reference in New Issue
Block a user