x86-16 machine code, 77 7471 7169 bytes
00000000: ad2c 41bbadbb 4001fd00 d780 fc23 7c06 7502 2c0a 040b .,A.@.....#|.u.,...
00000010: 040b 92be 3a013801 33c9 acd4 108a cce3 1a02 c2d4 ..8.3.:.3.........
00000020: c2d4 0cbd 47014501 bb48 204b 4d38 4600 7ff9 7402 .. ..GE..H KM8F...t.
00000030: 7402 b723 93f3 abeb dfc3 4025 2017 1520 0002 t. .#......@% .. ..
00000040: 0002 0305 0708 0a .. .....
AD LODSW ; AH = note accidental, AL = note name
2C 41 SUB AL, 'A' ; AL = note name relative to A
BB 014000FD MOV BX, OFFSET SLT -'A' ; BX = semitone lookup table (ASCII offset index)
D7 XLAT ; convert to semitones
80 FC 23 CMP AH, '#' ; is it sharp?
7C 06 JL START_SONG ; if has no accidental, let's jam
75 02 JNZ IS_FLAT ; if not, it's a flat
IS_SHARP:
2C 0A SUB AL, 10 ; "sharp" the note up one semitone (fall through)
IS_FLAT:
04 0B ADD AL, 11 ; "flat" the note up 11 semitones
START_SONG:
92 XCHG AX, DX ; DL = root note (key) relative to A (A=0, C=3, Gb=11)
BE 013A0138 MOV SI, OFFSET SNG ; SI = song pattern data
33 C9 XOR CX, CX ; clear CX for counter
SONG_LOOP:
AC LODSB ; AL = length and note packed nibbles
D4 10 AAM 16 ; AH = # of repeats, AL = note
8A CC MOV CL, AH ; CX = bar repeat counter
E3 1A JCXZ SONG_DONE ; end, if the song is over
02 C2 ADD AL, DL ; transpose note to correct key
D4 0C AAM 12 ; AL = output note (mod 12 to fix wrap around)
BD 01470145 MOV BP, OFFSET SLT+7 ; BP = end of semitone table
BB 2048 MOV BX, 02048H ; BH=' ', BL='H' (G+1 since loop pre-decrements)
NOTE_LOOP:
4B DEC BX ; walk down notes starting from G
4D DEC BP ; loop backwards through semitone table
38 46 00 CMP BYTE PTR[BP], AL ; compare note to semitone
7F FAF9 JG NOTE_LOOP ; if note is higher than target, keep looping
74 02 JZ WRITE_NOTE ; if note is exactly target, it's natural
B7 23 MOV BH, '#' ; otherwise, it's sharp (ouch!)
WRITE_NOTE:
93 XCHG AX, BX ; move output to AX
F3 AB REPZ STOSW ; write to output string (repeatedly)
EB DF JMP SONG_LOOP ; jump to next song pattern
SONG_DONE:
C3 RET ; return to caller
; packed song data:
; high nibble = repeats, low nibble = note interval
SNG DB 40H, 25H, 20H, 17H, 15H, 20H
; semitone lookup table
; A B C D E F G
SLT DB 0, 2, 3, 5, 7, 8, 10
The first character of input string pointer is ASCII converted to 0-based index (A=0, C=2, G=6), which is then converted to a semitone scale relative to A, based on the table [0,2,3,5,7,8,10]. The pointer of this table is actually offset by 0x41 (ASCII 'A') so that the index is the ASCII value, eliminating the need to ASCII convert to 0-based index in the program. The second character's ASCII value is checked and if it's less than a '#' (ASCII 0x23) (white space, CR/LF, null, etc), the note is natural. If it is a '#', 10 is subtracted from the value followed by adding 11, for a net +1 (this is a machine code optimization to eliminate the need for a branch/jump). If it's a flat, the subtraction of 10 is skipped and only 11 is added, for a (relative) net -1. The remaining number is the input note's number of semitones relative to A -- the key, effectively.