How to generate random waves for a bullet hell game that feel balanced and natural

My game consists of ‘waves’ of objects called ‘spawners’, which once every certain amount of time (their firetime), move to a new place on the screen and spawn an enemy. Each wave has 4 important properties:
1: the amount of spawners the wave has
2: the interval of time between creating new spawners
3: the total length of the wave
4: the types of spawners that can appear in a wave (represented as a std::vector<std::pair<std::string, int>> where the string is the spawner name, and the integer is its spawn weight.

The game works by picking a random spawner from the possible spawner types (with a weighted rng) every new spawner interval. Currently waves are set and are loaded from a file at runtime.

My problem is that I cannot find a good way to randomly generate waves that feel balanced and natural. Currently, I am trying to generate waves based on a difficulty value, mostly using weighted random number generation. However, this does not produce balanced waves that correspond well to the target difficulty. Even after trying several different techniques, I am unable to get a system that generates waves that fell balanced and natural (like the ones hand made).

Is there any way to generate waves that feel natural, based off of the difficulty value? How should I approach this problem?

Also if its of any help, each spawner also defines its own difficulty value.