diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 815d547..4a02a12 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -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: diff --git a/front/API.ts b/front/API.ts index 5d770ac..ea13034 100644 --- a/front/API.ts +++ b/front/API.ts @@ -10,7 +10,7 @@ import Constants from "expo-constants"; import store from "./state/Store"; import { Platform } from "react-native"; import { en } from "./i18n/Translations"; -import { useQuery, QueryClient } from "react-query"; +import { QueryClient } from "react-query"; type AuthenticationInput = { username: string; password: string }; type RegistrationInput = AuthenticationInput & { email: string }; @@ -370,15 +370,10 @@ export default class API { /** * Retrieve the authenticated user's play history */ - public static async getUserPlayHistory(): Promise { - 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 { + return this.fetch({ + route: '/history' + }); } /** diff --git a/front/i18n/Translations.ts b/front/i18n/Translations.ts index ab529e9..10bbc04 100644 --- a/front/i18n/Translations.ts +++ b/front/i18n/Translations.ts @@ -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', @@ -97,6 +98,17 @@ export const en = { errAlrdExst: 'Already exist', errIncrrct: 'Incorrect Credentials', + // 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', @@ -158,6 +170,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', @@ -256,6 +269,16 @@ export const fr: typeof en = { unknownError: 'Erreur inconnue', errIncrrct: 'Identifiant incorrect', + // 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', @@ -319,6 +342,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', @@ -402,6 +426,16 @@ export const sp: typeof en = { errAlrdExst: "Ya existe", errIncrrct: "credenciales incorrectas", + // 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', diff --git a/front/models/SongHistory.ts b/front/models/SongHistory.ts index a8c04c4..436d856 100644 --- a/front/models/SongHistory.ts +++ b/front/models/SongHistory.ts @@ -1,7 +1,8 @@ -interface LessonHistory { - songId: number; - userId: number; +interface SongHistory { + songID: number; + userID: number; score: number; + difficulties: JSON; } -export default LessonHistory; \ No newline at end of file +export default SongHistory; \ No newline at end of file diff --git a/front/package.json b/front/package.json index c6a3395..100655e 100644 --- a/front/package.json +++ b/front/package.json @@ -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", diff --git a/front/views/HomeView.tsx b/front/views/HomeView.tsx index 54e363c..7d7e50c 100644 --- a/front/views/HomeView.tsx +++ b/front/views/HomeView.tsx @@ -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
@@ -67,7 +79,11 @@ const HomeView = () => { } - 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, diff --git a/front/views/PlayView.tsx b/front/views/PlayView.tsx index 3486f53..ce0ddce 100644 --- a/front/views/PlayView.tsx +++ b/front/views/PlayView.tsx @@ -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 LoadingComponent 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) => { +const PlayView = ({ songId, type, route }: RouteProps) => { + 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(); - const timer = useStopwatch({ autoStart: false }); const [paused, setPause] = useState(true); + const stopwatch = useStopwatch(); const [midiPlayer, setMidiPlayer] = useState(); const [isVirtualPianoVisible, setVirtualPianoVisible] = useState(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) => { 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) => { 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) => { ]).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) => { 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
@@ -219,12 +274,12 @@ const PlayView = ({ songId }: RouteProps) => { } onPress={() => { setVirtualPianoVisible(!isVirtualPianoVisible); }}/> - {timer.minutes}:{timer.seconds.toString().padStart(2, '0')} + {Math.floor(time / 60000)}:{Math.floor((time % 60000) / 1000).toFixed(0).toString().padStart(2, '0')} } onPress={() => { onEnd(); - navigation.navigate('Score') + navigation.navigate('Score', { songId: song.data.id }); }}/> diff --git a/front/views/ScoreView.tsx b/front/views/ScoreView.tsx index 66bcf07..8284c2b 100644 --- a/front/views/ScoreView.tsx +++ b/front/views/ScoreView.tsx @@ -10,35 +10,40 @@ import LoadingComponent from "../components/Loading"; type ScoreViewProps = { songId: number } -const ScoreView = ({ songId }: RouteProps) => { +const ScoreView = ({ songId, route }: RouteProps) => { 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
; } return - Rolling in the Deep - Adele - 3:45 + {songQuery.data.name} + {artistQuery.data?.name} - + {/* - 80 + ' ' + t}/> @@ -47,15 +52,14 @@ const ScoreView = ({ songId }: RouteProps) => { 80 ' ' + t}/> - + */} - t + ' : '}/> + t + ' : '}/> - {"80" + "%"} + {songScoreQuery.data.score + "pts"} - {/* Precision */} ) => { onPress={() => navigation.navigate('Home')} /> navigation.navigate('Song', { songId: 1 })} + onPress={() => navigation.navigate('Song', { songId })} translate={{ translationKey: 'playAgain' }} /> diff --git a/front/views/SongLobbyView.tsx b/front/views/SongLobbyView.tsx index 00dac8e..8700f72 100644 --- a/front/views/SongLobbyView.tsx +++ b/front/views/SongLobbyView.tsx @@ -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 from "../components/Loading"; import React, { useEffect, useState } from "react"; @@ -7,7 +7,7 @@ import formatDuration from "format-duration"; import { Ionicons } from '@expo/vector-icons'; import API from "../API"; import TextButton from "../components/TextButton"; -import { useNavigation } from "../Navigation"; +import { useNavigation, RouteProps } from "../Navigation"; interface SongLobbyProps { // The unique identifier to find a song @@ -37,7 +37,7 @@ const SongLobbyView = (props: RouteProps) => { - + {songQuery.data!.name} ) => { /> navigation.navigate('Play', { songId: songQuery.data!.id })} + onPress={() => navigation.navigate('Play', { songId: songQuery.data!.id, type: 'normal' })} rightIcon={} /> - + navigation.navigate('Play', { songId: songQuery.data!.id, type: 'practice' })} + rightIcon={} + colorScheme='secondary' + /> + diff --git a/front/yarn.lock b/front/yarn.lock index 8d52035..0016866 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -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" diff --git a/scorometer/chroma_case/Message.py b/scorometer/chroma_case/Message.py new file mode 100644 index 0000000..19b9810 --- /dev/null +++ b/scorometer/chroma_case/Message.py @@ -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)), "" \ No newline at end of file diff --git a/scorometer/main.py b/scorometer/main.py index de37b00..af7c3b5 100755 --- a/scorometer/main.py +++ b/scorometer/main.py @@ -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() \ No newline at end of file diff --git a/scorometer/tests/almost_perfect_play/output b/scorometer/tests/almost_perfect_play/output index 85463e6..cbf6166 100644 --- a/scorometer/tests/almost_perfect_play/output +++ b/scorometer/tests/almost_perfect_play/output @@ -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}} diff --git a/scorometer/tests/early/output b/scorometer/tests/early/output index 77522b8..2e96442 100644 --- a/scorometer/tests/early/output +++ b/scorometer/tests/early/output @@ -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}} diff --git a/scorometer/tests/end_miss/output b/scorometer/tests/end_miss/output index 086b2a9..7422799 100644 --- a/scorometer/tests/end_miss/output +++ b/scorometer/tests/end_miss/output @@ -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}} diff --git a/scorometer/tests/hold_not_enough/output b/scorometer/tests/hold_not_enough/output index 6307a91..2388828 100644 --- a/scorometer/tests/hold_not_enough/output +++ b/scorometer/tests/hold_not_enough/output @@ -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}} diff --git a/scorometer/tests/hold_too_long/output b/scorometer/tests/hold_too_long/output index 6307a91..fe63dcc 100644 --- a/scorometer/tests/hold_too_long/output +++ b/scorometer/tests/hold_too_long/output @@ -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}} diff --git a/scorometer/tests/late/output b/scorometer/tests/late/output index 28c6d1f..2e96442 100644 --- a/scorometer/tests/late/output +++ b/scorometer/tests/late/output @@ -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}} diff --git a/scorometer/tests/perfect_play/output b/scorometer/tests/perfect_play/output index f35d8ce..ae68596 100644 --- a/scorometer/tests/perfect_play/output +++ b/scorometer/tests/perfect_play/output @@ -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}} diff --git a/scorometer/tests/random_miss/output b/scorometer/tests/random_miss/output index 40b5a92..488f81b 100644 --- a/scorometer/tests/random_miss/output +++ b/scorometer/tests/random_miss/output @@ -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}} diff --git a/scorometer/tests/runner.sh b/scorometer/tests/runner.sh index 7844adb..f18957d 100755 --- a/scorometer/tests/runner.sh +++ b/scorometer/tests/runner.sh @@ -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))