def get_on_off_pairs(_on_msgs: List[Message], _off_msgs: List[Message]) -> List[Tuple[Message, Message]]: _pairs = [] for _on_msg in _on_msgs: _matching_off_msg = next((_off_msg for _off_msg in _off_msgs if (_off_msg.note == _on_msg.note and _off_msg.time > _on_msg.time)), None) if _matching_off_msg is not None: _off_msgs.remove(_matching_off_msg) _pairs.append((_on_msg, _matching_off_msg)) return _pairs on_msgs: List[Message] = Message.normalize_chords_in_file(on_path) off_msgs: List[Message] = Message.construct_many_from_file(off_path) on_off_pairs = get_on_off_pairs(on_msgs, off_msgs[:]) for i, (on, off) in enumerate(on_off_pairs): for j, (next_on, _) in enumerate(on_off_pairs[i + 1:], i + 1): if next_on.note == on.note and next_on.time < off.time: warning = "\n".join([f"ON time and matching OFF time don't make sense:", f"OFF happened only after ON happened TWICE.", f"{on_path = }, {off_path = }", f"1st ON: {on}, 2nd ON: {next_on}, 1st OFF: {off}"]) logger.log_thin(warning, should_pf=False, title="WARNING") on_off_chained = it.chain.from_iterable(on_off_pairs) on_off_sorted = sorted(on_off_chained, key=lambda m: m.time) with open(base_path, mode="w") as base: base.writelines(map(lambda m: m.to_line(), on_off_sorted))
def main(): def _dump_data(): try: obj = dict( params=dict( allowed_rhythm_deviation=allowed_rhythm_deviation, allowed_tempo_deviation=allowed_tempo_deviation, current_level=current_level, experiment_type=experiment_type, trial_on_path=trial_on_path, truth_on_path=truth_on_path, ), processing=dict( hits=[h.to_dict() for h in hits], messages=[m.to_dict() for m in msgs], messages_tempo_transformed=[ tmsg.to_dict() for tmsg in tempoed_msgs ], tempo_estimation=tempo_estimation, tempo_str=tempo_str, ), results=dict( passed=passed, mistakes=mistakes, played_enough_notes=played_enough_notes, ), ) if check_rhythm: obj['processing'].update( tempo_ceil=tempo_ceil, tempo_floor=tempo_floor, ) if not passed: # only time when passed is True (all_hits_correct), advance_trial isn't referenced obj['results'].update(advance_trial=advance_trial) with open(data_dump_path, mode='w') as f: json.dump(obj, f, indent=4, sort_keys=True) except: pass if len(sys.argv) > 1: allowed_rhythm_deviation = int(sys.argv[1][:-1]) allowed_tempo_deviation = int(sys.argv[2][:-1]) trial_on_path = sys.argv[3] truth_on_path = sys.argv[4] current_level = json.loads(sys.argv[5]) experiment_type = sys.argv[6] else: allowed_rhythm_deviation = 20 allowed_tempo_deviation = 30 trial_on_path = r'c:\Sync\Code\Python\Pyano-release\src\experiments\subjects\tests\ORIN\level_1_trial_0_on.txt' truth_on_path = r'c:\Sync\Code\Python\Pyano-release\src\experiments\truths\fur_elise_B_on.txt' current_level = dict(notes=10, trials=1, rhythm=True, tempo=50) experiment_type = 'exam' data_dump_path = trial_on_path.rpartition('_on.txt')[0] + '_data.json' truths: List[Message] = Message.normalize_chords_in_file(truth_on_path) msgs: List[Message] = Message.normalize_chords_in_file(trial_on_path) check_rhythm = current_level['rhythm'] current_level_notes = current_level['notes'] tempo_estimation = estimate_tempo_percentage(msgs, truths, current_level_notes) tempoed_msgs: List[Message] = Message.transform_to_tempo( msgs, tempo_estimation) mistakes = [] hits = [] # for data dumping truth_chords = Message.get_chords(truths[:current_level_notes]) try: Message.normalize_chords( tempoed_msgs, truth_chords) # of tempoed_msgs, according to truth_chords except Exception as e: entry = logger.log( dict(tempoed_msgs=tempoed_msgs, truth_chords=truth_chords, msg=msgs, current_level_notes=current_level_notes, check_rhythm=check_rhythm, tempo_estimation=tempo_estimation, truths=truths, e=e), title= f"Exception at Message.normalize_chords(tempoed_msgs, truth_chords)" ) raise Exception( f"check done trial normalize chords exception. see log check_done_trial, entry: {entry}" ) for i in range(min(current_level_notes, len(msgs))): hit = Hit(tempoed_msgs[i], truths[i], allowed_rhythm_deviation) hits.append(hit) mistakes.append(hit.get_mistake_kind()) played_enough_notes = len(msgs) >= current_level_notes if not played_enough_notes: # needed to play 4 notes but playeed 3: [ null, null, null, "accuracy" ] # if also made a mistake: [ null, "rhythm", null, "accuracy" ] # Failed feedback msg could be "[ null, 'rhythm', null, 'accuracy' ], not enough notes and too fast" mistakes += ["accuracy"] * (current_level_notes - len(msgs)) tempo_str = "ok" if check_rhythm: # Failed feedback msg could be "[ null, 'rhythm', null, 'accuracy' ] and too fast" if not (0 <= allowed_tempo_deviation <= 100): entry = logger.log( dict(trial_on_path=trial_on_path, truth_on_path=truth_on_path, current_level=current_level, allowed_tempo_deviation=allowed_tempo_deviation), title="check_done_trial ValueError bad allowed_tempo_deviation" ) raise ValueError( f"check_done_trial inside rhythm checking got bad allowed_tempo_deviation, got: {allowed_tempo_deviation}. see classes.log, entry: {entry}" ) level_tempo = current_level['tempo'] # 75 extra = level_tempo * allowed_tempo_deviation / 100 # 75*0.1 = 7.5 tempo_floor = level_tempo - extra # 67.5 tempo_ceil = max(100, level_tempo + extra) # max(100, 82.5) = 100 if tempo_estimation < tempo_floor: tempo_str = "slow" elif tempo_estimation > tempo_ceil: tempo_str = "fast" else: tempo_str = "ok" if tempo_str != 'ok': # acc mistake when checking rhythm. if experiemnt is exam, advance anyway if experiment_type == 'exam': advance_trial = True else: advance_trial = 'accuracy' not in mistakes passed = False _dump_data() prfl( dict( advance_trial=advance_trial, mistakes=mistakes, passed=passed, played_enough_notes=played_enough_notes, tempo_str=tempo_str, )) return else: # delete rhythm mistakes if not checking rhythm. ["rhythm", null, "accuracy"] => [null, null, "accuracy"] mistakes = [None if m == "rhythm" else m for m in mistakes] if not played_enough_notes: # not enough notes == accuracy mistakes. dont adv if check rhythm. if experiemnt is exam, advance anyway if experiment_type == 'exam': advance_trial = True else: advance_trial = not check_rhythm passed = False played_enough_notes = False _dump_data() prfl( dict(passed=passed, mistakes=mistakes, advance_trial=advance_trial, played_enough_notes=played_enough_notes, tempo_str=tempo_str)) return # Played all notes or too many notes all_hits_correct = all([mistake is None for mistake in mistakes]) if all_hits_correct: passed = True played_too_many_notes = len(msgs) > current_level_notes _dump_data() prfl(dict(passed=passed, played_too_many_notes=played_too_many_notes)) return else: # Had mistakes # Tempo ok, played all required notes # if not check rhythm: has accuracy mistakes # if check rhythm: has accuracy and/or rhythm mistakes # ['accuracy', 'rhythm', None, ...] if experiment_type == 'exam': advance_trial = True else: advance_trial = not (check_rhythm and 'accuracy' in mistakes) passed = False _dump_data() prfl( dict(passed=passed, advance_trial=advance_trial, mistakes=mistakes))