Normally to simulate an analog voltage with a digital-only pin of a microcontroller you'd use Pulse Width Modulation. This works great for LEDs because your eyes can't the 490 / 976Hz flicker of the standard analogWrite() function. But for audio things are a bit more difficult. Because your ears can easily detect frequencies between 20 - 20,000Hz, any PWM with a frequency in this range is out.
Luckily, the ATmega328p allows you to change the clock prescalers for ultrasonic PWM! We need to use Timer0, because it can drive PWM at a max frequency of 62,500Hz, which even if you cut that in half would still be above your hearing range. (NOTE - this is probably far above the frequency response range of your project speaker anyways.) Now that we have ultrasonic PWM on the Supported Pins, we configure Timer1 to fire an Interrupt Service Routine at a rate of "desired frequency" * 2. (Twice speed for toggling on and off every other round) There's an awesome website where you can generate the code for Timer Interrupts, but my library does this for you in the background.
Finally, inside the Timer1 ISR routine, we incorporate our volume trick. Instead of digitalWrite()'ing the pin HIGH and LOW like the normal Tone() function does, we analogWrite() "HIGH" with our volume value (0 - 255) and analogWrite(0) for "LOW". Because of how fast the PWM is running, the user doesn't hear the 62.5KHz PWM frequency, and instead perceives a 50% percent duty cycle as a speaker driven with only 2.5 volts! While a few volume levels do produce subtle artifacts to the sound, it mostly delivers quality 8-bit volume control to replace the standard Tone() function.
The library makes this trick as easy to write as a normal Tone() function, and the GitHub repo has documentation / ready to use example sketches.
no decent sound after 5k.
both for piezzo and speaker driven by mosfet. many spurious armonics