abraker95 / ultimate_osu_analyzer

Python rewrite of my old osu analyzer that aims to be a lot more useful
MIT License
28 stars 1 forks source link

Question regarding your implementation #33

Closed DR1ZZ closed 4 years ago

DR1ZZ commented 4 years ago

I've been playing around with parsing .osr files recently (of osumania, if that's relevant), and they seem to match up reasonably well with the hitpoints from the relevant .osu files from looking at a couple random samples. There's way too many of them (by a couple thousand, usually) though, and drawing them in a manner similar to how you do it (visually, not sure how you actually do it) yields little to no consistent overlap.

Testing the same replay and beatmap with your tool worked just fine though. I've never written Python code before and while some parts of its syntax are conveniently intuitive, others decidedly aren't -- I haven't been able to grasp the full "lifecycle" of the replay stream in your application.

Thus my question(s): Do you filter actions at all, and how do you determine which column they belong in? (I do some dirty bitwise manipulation on the x field currently, not sure if that is the standard way.) I'd be perfectly happy with you just pointing out the relevant source files, I'll take it from there.

Thanks in advance, and sorry if this isn't the place for questions like these!

abraker95 commented 4 years ago

There's way too many of them (by a couple thousand, usually) though, and drawing them in a manner similar to how you do it (visually, not sure how you actually do it) yields little to no consistent overlap.

Not entirely sure what you are trying to say here, but you can find the code responsible for drawing the notes in

and replays in

Thus my question(s): Do you filter actions at all, and how do you determine which column they belong in? (I do some dirty bitwise manipulation on the x field currently, not sure if that is the standard way.) I'd be perfectly happy with you just pointing out the relevant source files, I'll take it from there.

Look in the gui/objects/layers/mania folder. I already have various layers for drawing specific actions in there. In run.py see the apply_replay function for how those layers added. If that doesn't really show you how to do something you want, feel free to ask. It will help if you say what you are trying to do or make so I can give a better answer.

I also realize the way it's set up right now is very inconvenient, broken up and all over the place. I hope in some future update to have replays and hitobject data converted into action_data so that it can all be drawn from one place via analysis/osu/mania/mania_layers.py. Then remove a lot of things gui/objects/layers since they will no longer be needed.

DR1ZZ commented 4 years ago

Thanks a lot for your quick and detailed reply! I'll have to do some experimenting before I get back to you again.

DR1ZZ commented 4 years ago

Okay so I figured out which function does the "filtering" that was the core of my question through playing around with the in-build console (which is very convenient) and it's get_replay_data.

The following is get_replay_data, annotated to my understanding of it.

def get_replay_data(replay_events, columns):
        # An array of empty arrays, one for each column.
        event_data   = list([ [] for _ in range(columns) ])
        # An array of `None`, one for each column. If a column `i` is currently "being pressed",
        # press_states[i] will contain the time where the key press started, otherwise it will be `None`.
        press_states = list([ None for _ in range(columns) ])

        for replay_event in replay_events:
            for column in range(columns):
                # This is essentially the same bitwise magic I have, except that I directly read the column
                # from `x`.
                is_key_press = ((int(replay_event.x) & (1 << column)) > 0)

                # If the current column isn't currently being pressed and the current event is a key press,
                # set press_states[column] to the current time.
                if press_states[column] == None and is_key_press:
                    press_states[column] = replay_event.t

                # This is what I don't understand. The first check is obvious, first you want to do 
                # something if it's not currently being pressed, now you want to add the "finished"
                # event to the list. But why the `and not is_key_press`? Shouldn't it be the opposite?
                if press_states[column] != None and not is_key_press:
                    event_data[column].append(np.asarray([ press_states[column], replay_event.t ]))
                    press_states[column] = None

        return event_data
abraker95 commented 4 years ago

not is_key_press is correct. Think of it like this: it's checking change in state, and not just the state itself. So for the first one I don't want to just check whether the current column is currently being pressed down. I want to check whether the current column just started being pressed down. The same goes for the second if statement. I want to check whether the current column has just been released. press_states holds the previous state of the column, and is_key_press holds current state of the column.

is_release_to_press = !prev_press && curr_press and is_press_to_release = prev_press && !curr_press. Except I am using None here instead of True and False for some reason and I can't remember why.

edit: Oh I'm putting timings into press_states, that's why. I am abusing python so I can use it as something to put timings into and to use it as a prev_press flag lol.

DR1ZZ commented 4 years ago

This... explains a lot. So you weren't "filtering" actions, there's just more than one action per event! I absolutely didn't get the focus on checking the change in state, that was enlightening.

Have a good one, my implementation now works as expected, thanks a bunch!