The design seems very simplistic to me. Without looking into it really deeply it is hard to guess what could really go wrong.
The ideas to try:
- Simplify it to use just a single channel
- Instead of reading from the SD card read from a table of constants
Once, you get it done, up and running than start complicating things.
What seems suspicious to me in the design is the assumption that f_read() completes before the timer updating PWMs expires. Maybe it would be worth verifying the correctness of the assumption.
In the interrupt handler, the indexing
TIM1->CCR1=sai_buf[....]; seems also suspicious to me.
In general, the current design is asynchronous which might lead to a couple of issues. One way of resolving them would be to go synchronous - ping pong implementation here tries to deal with it. Another solution, would be adding some circular buffer which you could fill with the read data and empty it once feeding PWMs. At least you could easily detect the buffer overflow or underflow condition.