The basics are fairly straightforward, but as Mark points out, optimising for frequency response is a little more dificult.

I start with "a" as the guideline parameter for an EI core, where this is the width of the I plate, so that the centre section is 2a wide.

The inductance L=ur.u0.n^2.A/lm where ur is the relative permeability of the core, u0 that of free space, n the number of turns, A the centre section area (4a^2 if you use a square stack, minus a little for core insulation) and lm the path length (12a).

You then have to make sure that the saturation is not exceeded, B=ur.u0.i.n/lm where i is the operating current and n the number of turns. Typically that has to be quite low even thoughsome better grades of iron may have Bsat=1.7T for grain oriented silicon steel, but cores I build tend to have set B=0.6T which allows a little leeway on DC current. ur may be 15,000 for high grade iron, or 7,000 for a lower grade.

Then you need to work out the resistance of the winding which depends on the available area of the bobbin. For waste-free EI, the iron window is 3a^2 but the utilisation of the window (as area is taken up by the bobbin and insulated copper wire is about 10% larger than the rated copper diameter because of the insulation) can be as low as 0.4 for a single winding, less for two.

You can juggle with the maths to obtain a value for a given the current, magnetic field and inductance. With an air gap ur is reduced from the iron value to whatever is required to get the desired B. The air gap then has to be calculated. A simple approach says

L=u0.n^2.A/(lm/ur+g). Then you can see if your resistance is low enough and if not start with a bigger a, or write a program to iterate the parameters to seek the goals or, play with the maths to work out an expression for a.