feat: basics of practice mode
This commit is contained in:
@@ -3,6 +3,7 @@ class Key:
|
|||||||
self.key = key
|
self.key = key
|
||||||
self.start = start
|
self.start = start
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
self.done = False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.key} ({self.start} - {self.duration})"
|
return f"{self.key} ({self.start} - {self.duration})"
|
||||||
|
|||||||
+27
-15
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
import logging
|
|
||||||
from chroma_case.Partition import Partition
|
from chroma_case.Partition import Partition
|
||||||
from chroma_case.Key import Key
|
from chroma_case.Key import Key
|
||||||
import sys
|
import sys
|
||||||
import select
|
import select
|
||||||
|
import itertools
|
||||||
|
import operator
|
||||||
import json
|
import json
|
||||||
from mido import MidiFile
|
from mido import MidiFile
|
||||||
|
|
||||||
@@ -11,10 +12,14 @@ RATIO = float(sys.argv[2] if len(sys.argv) > 2 else 1)
|
|||||||
OCTAVE = 5
|
OCTAVE = 5
|
||||||
OCTAVE_AMOUNT_KEYS = 12
|
OCTAVE_AMOUNT_KEYS = 12
|
||||||
|
|
||||||
|
# MODES
|
||||||
|
NORMAL = 0
|
||||||
|
PRACTICE = 1
|
||||||
|
|
||||||
|
|
||||||
def send(o):
|
def send(o):
|
||||||
print(json.dumps(o), flush=True)
|
print(json.dumps(o), flush=True)
|
||||||
|
|
||||||
|
|
||||||
def log(level, message):
|
def log(level, message):
|
||||||
send({"type": "log", "level": level, "message": message})
|
send({"type": "log", "level": level, "message": message})
|
||||||
|
|
||||||
@@ -36,6 +41,11 @@ class Scorometer():
|
|||||||
self.keys_down = []
|
self.keys_down = []
|
||||||
self.score = 0
|
self.score = 0
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
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]] = []
|
||||||
|
|
||||||
def getPartition(self, midiFile):
|
def getPartition(self, midiFile):
|
||||||
notes = []
|
notes = []
|
||||||
@@ -72,17 +82,18 @@ class Scorometer():
|
|||||||
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
||||||
self.keys_down.remove((_key, down_since))
|
self.keys_down.remove((_key, down_since))
|
||||||
key = Key(_key, down_since, (timestamp - down_since))
|
key = Key(_key, down_since, (timestamp - down_since))
|
||||||
debug({key: key})
|
#debug({key: key})
|
||||||
if key is None:
|
if key is None:
|
||||||
|
warn("Note off sent but did not receive earlier note on")
|
||||||
return
|
return
|
||||||
to_play = next((i for i in self.partition.notes if i.key == key.key and self.is_timing_close(key, i)), None)
|
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 == False), None)
|
||||||
if to_play == None:
|
if to_play == None:
|
||||||
self.score -= 50
|
self.score -= 50
|
||||||
debug(f"Invalid key.")
|
debug(f"Invalid key.")
|
||||||
else:
|
else:
|
||||||
timingScore, timingInformation = self.getTiming(key, to_play)
|
timingScore, timingInformation = self.getTiming(key, to_play)
|
||||||
timingInformation = self.getTimingInfo(key, to_play)
|
|
||||||
self.score += 100 if timingScore == "perfect" else 75 if timingScore == "great" else 50
|
self.score += 100 if timingScore == "perfect" else 75 if timingScore == "great" else 50
|
||||||
|
to_play.done = True
|
||||||
self.sendScore(obj["id"], timingScore, timingInformation)
|
self.sendScore(obj["id"], timingScore, timingInformation)
|
||||||
|
|
||||||
def handleNotePractice(self, obj):
|
def handleNotePractice(self, obj):
|
||||||
@@ -98,18 +109,23 @@ class Scorometer():
|
|||||||
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
down_since = next(since for (h_key, since) in self.keys_down if h_key == _key)
|
||||||
self.keys_down.remove((_key, down_since))
|
self.keys_down.remove((_key, down_since))
|
||||||
key = Key(_key, down_since, (timestamp - down_since))
|
key = Key(_key, down_since, (timestamp - down_since))
|
||||||
debug({key: key})
|
#debug({key: key})
|
||||||
if key is None:
|
if key is None:
|
||||||
|
warn("Note off sent but did not receive earlier note on")
|
||||||
return
|
return
|
||||||
to_play = next((i for i in self.partition.notes if i.key == key.key and self.is_timing_close(key, i)), None)
|
keys_to_play = next((i for i in self.practice_partition if any(x.done != True for x in i)), None)
|
||||||
|
if keys_to_play is None:
|
||||||
|
warn("Key sent but there is no keys to play")
|
||||||
|
return
|
||||||
|
to_play = next((i for i in keys_to_play if i.key == key.key and i.done != True), None)
|
||||||
if to_play == None:
|
if to_play == None:
|
||||||
self.score -= 50
|
self.score -= 50
|
||||||
debug(f"Invalid key.")
|
debug(f"Invalid key.")
|
||||||
else:
|
else:
|
||||||
timingScore, timingInformation = self.getTiming(key, to_play)
|
timingScore, _ = self.getTiming(key, to_play)
|
||||||
timingInformation = self.getTimingInfo(key, to_play)
|
|
||||||
self.score += 100 if timingScore == "perfect" else 75 if timingScore == "great" else 50
|
self.score += 100 if timingScore == "perfect" else 75 if timingScore == "great" else 50
|
||||||
self.sendScore(obj["id"], timingScore, timingInformation)
|
to_play.done = True
|
||||||
|
self.sendScore(obj["id"], timingScore, "practice")
|
||||||
|
|
||||||
def getTiming(self, key: Key, to_play: Key):
|
def getTiming(self, key: Key, to_play: Key):
|
||||||
return self.getTimingScore(key, to_play), self.getTimingInfo(key, to_play)
|
return self.getTimingScore(key, to_play), self.getTimingInfo(key, to_play)
|
||||||
@@ -162,9 +178,6 @@ class Scorometer():
|
|||||||
pass
|
pass
|
||||||
self.sendEnd(self.score, {})
|
self.sendEnd(self.score, {})
|
||||||
|
|
||||||
NORMAL = 0
|
|
||||||
PRACTICE = 1
|
|
||||||
|
|
||||||
def handleStartMessage(start_message):
|
def handleStartMessage(start_message):
|
||||||
if "type" not in start_message.keys():
|
if "type" not in start_message.keys():
|
||||||
raise Exception("type of start message not specified")
|
raise Exception("type of start message not specified")
|
||||||
@@ -174,7 +187,7 @@ def handleStartMessage(start_message):
|
|||||||
raise Exception("name of song not specified in start message")
|
raise Exception("name of song not specified in start message")
|
||||||
if "mode" not in start_message.keys():
|
if "mode" not in start_message.keys():
|
||||||
raise Exception("mode of song not specified in start message")
|
raise Exception("mode of song not specified in start message")
|
||||||
mode = NORMAL if start_message["mode"] == "NORMAL" else PRACTICE
|
mode = PRACTICE if start_message["mode"] == "practice" else NORMAL
|
||||||
# TODO get song path from the API
|
# TODO get song path from the API
|
||||||
song_path = f"partitions/{start_message['name']}.midi"
|
song_path = f"partitions/{start_message['name']}.midi"
|
||||||
return mode, song_path
|
return mode, song_path
|
||||||
@@ -183,7 +196,6 @@ def handleStartMessage(start_message):
|
|||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
start_message = json.loads(input())
|
start_message = json.loads(input())
|
||||||
# TODO handle mode
|
|
||||||
mode, song_path = handleStartMessage(start_message)
|
mode, song_path = handleStartMessage(start_message)
|
||||||
sc = Scorometer(mode, song_path)
|
sc = Scorometer(mode, song_path)
|
||||||
sc.gameLoop()
|
sc.gameLoop()
|
||||||
|
|||||||
Reference in New Issue
Block a user