Hi and welcome to the forums!
There are many ways to make adaptive music, but since you specified Celeste-like, it's one of the simpler methods i think;
One solution could be fully layered tracks cross-fading from one to another (the current one fading out, the next one simultaneously fading in)
Another way you can accomplish this, is to have multiple "layers" playing at the same time as different Source objects started together with love.audio.play() since that takes multiple arguments; you can set the volume of each both beforehand and during playback, so you can fade-in and fade-out specific tracks to your liking.
Even more complicated is when you combine the above two methods, but it'll get even more generic and have the potential to be even more expressive.
Now, up until this point, you could just use Source objects played simultaneously and/or one after another... but you could also use one QueueableSource object and Decoder objects if the number of Sources are getting out of hand. (There's a limit of
active as in playing Source objects, though it's usually large enough...)
If you'd want even more fine-tuned control, down to the note level, (sound)tracker modules (files) would be a solution, unfortunately löve doesn't expose any fine-grained control over them, so you'd need another hand-crafted solution (parser + player) which isn't the simplest to implement... i know, i tried and it's not finished
But yeah, on the level of Celeste, it should be easier, just wanted to cover all bases.