Some slow scanning of the argument is needed:
\documentclass{article}
\usepackage{fixltx2e} % for \textsubscript
\makeatletter
\newcommand{\note}[1]{\emit@note#1\@nil}
\def\emit@note#1#2\@nil{%
\textup{#1}%
\if\relax\detokenize{#2}\relax
\expandafter\@gobble
\else
\expandafter\@firstofone
\fi
{\emit@@note#2\@nil}%
}
\def\emit@@note{\@ifnextchar##{\emit@sharp}{\emit@@@note}}
\def\emit@sharp#1{$\sharp$\emit@@@note}
\def\emit@@@note{\@ifnextchar b{$\flat$\emit@@@@note}{\emit@@@@note{}}}
\def\emit@@@@note#1#2\@nil{\textsubscript{#2}}
\makeatother
\begin{document}
\note{A}
\note{A#}
\note{Ab}
\note{A2}
\note{A#2}
\note{Ab2}
\end{document}

By kind request, here's a possible LaTeX3 implementation:
\documentclass{article}
\usepackage{fixltx2e} % for \textsubscript
\usepackage{xparse}
\ExplSyntaxOn
% user level command
\NewDocumentCommand{\note}{m}
{
\emit_note:n { #1 }
}
% a variable
\tl_new:N \l_emit_specs_tl
% internal main function
\cs_new_protected:Npn \emit_note:n #1
{% Get the first token
\emit_textup:x { \tl_head:n { #1 } }
% Get the rest and pass control to \emit_do_specs:n
\tl_set:Nx \l_emit_specs_tl { \tl_tail:n { #1 } }
\tl_if_empty:NF \l_emit_specs_tl
{
\emit_do_specs:o { \l_emit_specs_tl \q_stop }
}
}
% We can't use \textup{ \tl_head:n { #1 } } because of possible #
\cs_new_protected:Npn \emit_textup:n #1
{
\textup { #1 }
}
\cs_generate_variant:Nn \emit_textup:n { x }
% Check if the first token is # or b
% and take appropriate actions
% Then hand the remainder to \emit_range:w
\cs_new_protected:Npn \emit_do_specs:n #1
{
\peek_charcode_remove:NTF ##
{ $\sharp$ \emit_range:w }
{
\peek_charcode_remove:NTF b
{ $\flat$ \emit_range:w }
{ \emit_range:w }
}
#1
}
\cs_generate_variant:Nn \emit_do_specs:n { o }
\cs_new_protected:Npn \emit_range:w #1 \q_stop
{
\tl_if_empty:nF { #1 } { \textsubscript{#1} }
}
\ExplSyntaxOff
\begin{document}
\note{A}
\note{A#}
\note{Ab}
\note{A2}
\note{A#2}
\note{Ab2}
\end{document}
A new implementation with l3regex using its splitting capabilities. Some kind of error checking could be implemented for malformed input.
The accidentals are taken from a MusixTeX font using a variant of the method found in this answer by clemens
\documentclass{article}
\usepackage{xparse,l3regex}
\DeclareFontFamily{U}{musix}{}
\DeclareFontShape{U}{musix}{m}{n}{
<-12> s * [1.5] musix11
<12-15> s * [1.5] musix13
<15-18> s * [1.5] musix16
<18-23> s * [1.5] musix20
<23-> s * [1.5] musix29
}{}
\NewDocumentCommand{\musixsym}{m}{%
\raisebox{.6ex}{\normalfont\usefont{U}{musix}{m}{n}\symbol{#1}}%
}
\newcommand\mflat{\musixsym{'62}}
\newcommand\mdoubleflat{\musixsym{'63}}
\newcommand\msharp{\musixsym{'64}}
\newcommand\mdoublesharp{\musixsym{'65}}
\newcommand\mnatural{\musixsym{'66}}
\ExplSyntaxOn
\NewDocumentCommand{\note}{m}
{
\tl_set:Nn \l_emit_note_tl { #1 }
\regex_replace_all:nnN { \# } { S } \l_emit_note_tl
\regex_split:nVN { ([A-G]+) ([Sbn]*) (\d*) } \l_emit_note_tl \l_emit_note_seq
\seq_item:Nn \l_emit_note_seq { 2 }
\str_case_x:nn { \seq_item:Nn \l_emit_note_seq { 3 } }
{
{S}{\msharp}
{SS}{\,\mdoublesharp}
{b}{\mflat}
{bb}{\mdoubleflat}
{n}{\mnatural}
}
\textsubscript{\seq_item:Nn \l_emit_note_seq { 4 }}
}
\cs_generate_variant:Nn \regex_split:nnN { nVN }
\tl_new:N \l_emit_note_tl
\seq_new:N \l_emit_note_seq
\ExplSyntaxOff
\begin{document}
\note{A}
\note{B#}
\note{C##}
\note{Db}
\note{Ebb}
\note{F2}
\note{G#2}
\note{A##2}
\note{Bb2}
\note{Cbb2}
\note{Dn1}
\end{document}

Note that, as of LaTeX version 2015/01/01, \textsubscript has been incorporated in the kernel.