JavaScript (ES6), 74 bytes
Expects (n)(list)
and returns another list.
n=>a=>a.map(s=>b[(b.indexOf(s)+n)%12],b="AbABbBCDbDEbEFGbG".match(/.b?/g))
Try it online!
Commented
n => // n = number of semitones
a => // a[] = list of notes
a.map(s => // for each note string s in a[]:
b[ // get from b[]:
( //
b.indexOf(s) // the note at the position of s in b[]
+ n // + the required number of semitones
) % 12 // modulo 12
], //
b = // where b[] is initialized to the list
"AbABbBCDbDEbEFGbG" // of all 12 notes from Ab to G, obtained
.match(/.b?/g) // by matching in this string each capital
// letter followed by an optional 'b'
) // end of map()
Node.js, no looking table, 81 bytes
Or 78 bytes if it's acceptable to output a space when there's no flat sign.
Same input format.
n=>a=>a.map(s=>(B=Buffer)([~~(i=(B(s)[0]/.6+!s[1]+n)%12)*.6+65])+["b"[~i%5+1&1]])
Try it online!
Method
Given a transposition amount \$n\$, the ASCII code \$c\$ of the original note and a flag \$k\$ set to \$0\$ if there's a flat sign or \$1\$ if there's no flat sign, we compute the index of the target note with:
$$i=\left\lfloor c\times\frac{5}{3}+k+n\right\rfloor \bmod 12$$
which results in the following mapping:
![keyboard](https://cdn.statically.io/img/i.sstatic.net/TMLdBTlJ.png)
Try it online!
By reducing these indices modulo \$5\$, we get \$0\$ or \$2\$ for black keys and \$1\$, \$3\$ or \$4\$ for white keys.
If we compute ~i % 5
instead, we get an odd negative value for black keys and either \$0\$ or an even negative value for white keys.
Commented
n => // n = number of semitones
a => // a[] = list of notes
a.map(s => // for each string note s in a[]:
(B = Buffer)([ // B = alias for Buffer
~~( // coerce to an integer:
i = ( // save in i:
B(s)[0] // ASCII code of note (e.g. "A" -> 65)
/ .6 + // divided by 0.6 (e.g. 108.333)
+ !s[1] // +1 if there's no flat sign
+ n // + the required number of semitones
) % 12 // modulo 12
) * .6 // turn this back into a letter ASCII code
+ 65 // by multiplying by 0.6 and adding 65
]) + // (the decimal part is ignored)
[ // append:
"b"[ // the flat sign "b" if
~i % 5 // ~i % 5
+ 1 & 1 // is odd
] // (or an empty string otherwise)
] //
) // end of map()
11, G -> F
is incorrect. \$\endgroup\$"X "
(with a space) instead of just"X"
when there's no flat sign? \$\endgroup\$