So...T seconds have elapsed since your last frame, and you have a new data point you'd like to incorporate into your EMA (and, for this problem, that data point is T itself). You keep e^-(bT) of the old data, 1 minus that of the new data, and you're done. Alpha is indeed 1-e^-(b * cur_spf) for some constant b, just like the AI said.
Which b do you choose though? I usually prefer to think of it in terms of half lives. Let H be your desired half life, and set 1-e^-(bH) equal to 1/2. You get b=log(2)/H. That's similar to the AI answer, but it rescales window_secs into a parameter you can actually start to reason about. The AI answer gives you a 1/e life instead, which is a less comfortable constant to mentally process.
That answer also naturally generalizes to other "time" axes or denominators. Suppose you want to EMA using discrete events rather than wall-clock time. Replace T in your dynamic alpha with the discrete count of events you haven't updated the EMA with yet (e.g., if something happens every time tick you would usually take T=1, but you can increase that based on a few skipped frames or whatever if you'd like).
As a fun fact, you can tailor this sort of stateless solution to have almost any decay property you'd like. Start with your favorite ODE satisfying a few properties, and this equation falls out as the step a discrete solver would take to approximate the ODE.