Calculating frequency of a note from a string I've been working on a solution to Pset3 of CS50 Harvard course for last couple of hours and managed to finish it. Although it works as intended I'm unhappy with how clean this code is, mostly because of use of multiple nested conditionals inside the main switch case. The function takes a string, for example "B#3", and converts it to correct frequency.

If interested here is the Pset and here is the frequency table.

// Calculates frequency (in Hz) of a note
int frequency(string note)
{
    const double a4Frequency = 440;
    char key;
    float numOfSemitones = 0;
    double octave = 0;
    bool flat, sharp;

    // Determine octave and whether key is sharp or flat
    if (note[1] == 'b')
    {
        octave = note[2] - '0';
        flat = true;
    }
    else if(note[1] == '#')
    {
        octave = note[2] - '0';
        sharp = true;
    }
    else
        octave = note[1] - '0';

    key = note[0];

    // Base frequency knowing that A4 is 440 hz
    if(((key == 'A') &amp;&amp; (octave == 4)) &amp;&amp; ((!flat) &amp;&amp; (!sharp)))
        return 440;

    // TODO find alternative way to count distance between keys - there is a pattern
    // Count frequency using distance in semitones from A4
    double frequency = 0.0;
    switch(key)
    {
        case 'B':
            if (flat)
                numOfSemitones = 1;
            else
                numOfSemitones = 2;

            frequency = pow(2, (numOfSemitones / 12))  * a4Frequency;
            break;
        case 'A':
            if (flat){
                numOfSemitones = 1;
                frequency = a4Frequency / pow(2, (numOfSemitones / 12));
            }
            else if (sharp){
                numOfSemitones = 1;
                frequency = pow(2, (numOfSemitones / 12))  * a4Frequency;
            }
            else
                frequency = a4Frequency;

            break;
        case 'G':
            if (flat)
                numOfSemitones = 3;
            else if (sharp)
                numOfSemitones = 1;
            else
                numOfSemitones = 2;

            frequency = a4Frequency / pow(2, (numOfSemitones / 12));
            break;
        case 'F':
            if (sharp)
                numOfSemitones = 3;
            else
                numOfSemitones = 4;

            frequency = a4Frequency / pow(2, (numOfSemitones / 12));
            break;
        case 'E':
            if (flat)
                numOfSemitones = 6;
            else
                numOfSemitones = 5;

            frequency = a4Frequency / pow(2, (numOfSemitones / 12));
            break;
        case 'D':
            if (flat)
                numOfSemitones = 8;
            else if (sharp)
                numOfSemitones = 6;
            else
                numOfSemitones = 7;

            frequency = a4Frequency / pow(2, (numOfSemitones / 12));
            break;
        case 'C':
            if (sharp)
                numOfSemitones = 8;
            else
                numOfSemitones = 9;

            frequency = a4Frequency / pow(2, (numOfSemitones / 12));
            break;
    }

    // Multiply or divide frequency to get note in correct octave
    int final;
    if (octave &gt; 4)
    {
        octave = pow(2, (octave - 4));
        final = round(frequency * octave);
    }
    else if (octave == 4)
    {
        octave = 0.0;
        final = round(frequency);
    }
    else
    {
        octave = pow(2, (4 - octave));
        final = round(frequency / octave);
    }
    return final;
}


I'm trying to focus on writing clean code from start, yet this solutions seems a bit hacked to me. How can I make this code more readable? What's the alternative to nested conditionals? What's "illegal" about this code?
Overall this is pretty readable code. It's straightforward to understand, so it would be fairly easy to maintain or debug if there were found to be problems.
Table-driven Development
If you look at the various case statements within your switch you'll notice that they all take the same form, just with different values. This indicates that it might be a good idea to use a table of values to reduce the amount of code. I would build a table of the number of semitone change for each note like this:
int kSemitoneOffsets[] = { -2, 0, 2, 4, 5, 7, 9 };

In the natural case where there are no accidentals (sharps or flats) applied, every case from your switch statements breaks down to this:
frequency = a4Frequency / pow(2, (kSemitoneOffsets [ index ]  / 12));

Here index is calculated from the key character. If we don't mind rearranging the above table so it progresses from A up to G, it ends up looking like this:
int kSemitoneOffsets[] = { 0, -2, 9, 7, 5, 4, 2 };

Now you can calculate the index like this:
int index = key - 'A';

Next, we can handle accidentals by adding or subtracting 1 from the lookup. Putting it all together, it looks like this:
int kSemitoneOffsets[] = { 0, -2, 9, 7, 5, 4, 2 };
int index = key - 'A';
int numOfSemitones = kSemitoneOffsets [ index ];
if (flat)
{
    numOfSemitones--;
}
else if (sharp)
{
    numOfSemitones++;
}
frequency = a4Frequency / pow(2, (numOfSemitones / 12));

Notice that I changed the calculation for B from a multiplication to a division so it was the same form as the other calculations. This is possible because raising a number to a negative power is the same as dividing by that number raised to the positive power.
We can use this same trick to calculate final without the if statement, like this:
final = round(frequency * pow(2, octave - 4));

When octave is less than 4, we get a negative number and end up dividing. When it's 0, we get 1 so the multiplication does nothing.
Remove Unnecessary Code
In addition to switching to using a table, you can also remove unnecessary code. For example the if statement you use to check if the user specifically entered A4. That code is unnecessary because you will correctly calculate the value using the rest of your code. Unless this function is being called inside some tight inner loop, its speed isn't a concern, so that optimization is not very helpful.
Avoid Magic Numbers
Your code also has a bunch of magic numbers. These are hard-coded values in the code that have no indication what their purpose is. You should create named constants for these. You have one for 440Hz, but the values of the semitone offsets are a bit mysterious. In my code I put them in a table with a name so it's more obvious what they are. But in general, values other than 0 or 1 should have a name. I would also comment the relationship of 2^(numSemitones / 12) as it would be non-obvious to people who haven't studied the physics of Western music.
Nitpicks
One other thing I noticed is that in your code you didn't handle the cases of Fb, Cb, E#, or B#. These are valid notes that you'll want to handle. They don't come up often, and they are synonyms (at least in equal temperament) for E, B, F, and C, but they are legit, so I'd recommend handling them. (And if you want to get really technical, you could handle double sharps (Ax4, for example), and double flats (Bbb3, for example).)