analogWrite uses a fixed frequency, but varies the mark:space ratio, i.e. the ratio of the time the waveform is high to the time the waveform is low.
If you look at the example sketch, you’ll see that it doesn’t use analogWrite, but instead uses explicit “delayMicroseconds” calls.
#define speakerPin 9
#define switchPin 3 //whatever
int length = 15; // the number of notes
char notes[] = "ccggaagffeeddc "; // a space represents a rest
int beats[] = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 4 };
int tempo = 300;
void playTone(int tone, int duration) {
for (long i = 0; i < duration * 1000L; i += tone * 2) {
digitalWrite(speakerPin, HIGH);
delayMicroseconds(tone);
digitalWrite(speakerPin, LOW);
delayMicroseconds(tone);
}
}
void playNote(char note, int duration) {
char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };
// play the tone corresponding to the note name
for (int i = 0; i < 8; i++) {
if (names[i] == note) {
playTone(tones[i], duration);
}
}
}
void setup() {
pinMode(speakerPin, OUTPUT);
pinMode(switchPin, INPUT);
digitalWrite (switchPin, HIGH); //enable the pull-ups
// switch is normally-open, connected between switchPin and Gnd
}
void loop() {
if (LOW == digitalRead (switchPin) ) {
for (int i = 0; i < length; i++) {
if (notes[i] == ' ') {
delay(beats[i] * tempo); // rest
} else {
playNote(notes[i], beats[i] * tempo);
}
// pause between notes
delay(tempo / 2);
}
}
}
Seems a likely starting point. (uncompiled, unchecked)