Convert between Music clefs

Befunge, 70 64 bytes

~0~:70p##~+2%00p+"A"-~7%2++7%:3%2%00g*:10p+"A"+,00g!10g+#@_"#",@

Try it online!

The input should be in the form C# Treble or F Bass, although the clef can simply be the first letter (i.e. T or B), since the rest of the input is ignored anyway.

Explanation

~0        Read the note and push a zero (the purpose of this will become apparent later).
~:70p     Read the following sharp or space and write that out as the next instruction.

As a result of this code modification, the next sequence of instructions will take one of two forms:

##~       The first # jumps over the second, and thus we perform the read instruction.
 #~       But if there's only one #, we'll ending up skipping the read instruction.

At this point the stack either contains note,0,sharp,space or note,0,space.

+2%       Add the top two stack items mod 2, returning 1 if we read a sharp, else 0 if not.
00p       Save this 'sharp' boolean for later use.

At this point the stack either contains note,0 or just note (with an implicit zero below).

+         By adding the top two items, we combine the 0 (if present) onto the note below.
"A"-      We can then subtract 'A' to convert the note into a number in the range 0 to 6.
~7%2+     Read the T/B clef, then mod 7 and add 2, returning 2 or 5 (the conversion offset).
+7%       Add that offset to our note number, then mod 7, to get the converted note number.
:3%2%     Make a dup, and calculate mod 3 mod 2 to determine the special cases (B# or E#).
00g*      Multiply that by the 'sharp' boolean, since we only care if the input was sharp.
:10p      Duplicate and save this special case boolean for later.
+         Now add it to the note number, since the special cases need to be offset by 1.
"A"+,     Then we can finally convert the number back into a character and output it.
00g!10g+  Now we check if the original note was not sharp, or if this was a special case.
#@_       If so, we exit immediately.
"#",@     Otherwise, we output a '#'.

Jelly,  35  34 bytes

I have a feeling some arithmetic may win over this method.

ØAḣ7µW€ż;€”#$Ẏ
Ç”C4¦”F⁵¦
Ñi+_⁸?4ị¢

Try it online!

A full program taking 1) the clef indicator 0 or 1 for Bass or Treble respectively and 2) the note; and printing the resulting note.

Would be 31 bytes if -4 and 4 were acceptable as the clef indicator input values (then Ñi+_⁸?4ị¢ can become Ñi+⁸ị¢) but this has been clarified as not allowed unless -4 is falsey and 4 is truthy, which is not the case for Jelly.

How?

Builds a keyboard with phantom B# and E# keys, finds the index of the input, offsets that by 4 in the required direction, indexes back into a keyboard with those phantom keys replaced by their required results (the key above them):

ØAḣ7µW€ż;€”#$Ẏ - Link 1, keyboard with phantoms: no inputs
ØA             - alphabet yield        -> ['A', 'B', ..., 'Z']
   7           - literal seven
  ḣ            - head                  -> ['A','B','C','D','E','F','G']
    µ          - new monadic chain, call that K
     W€        - wrap €ach             -> ["A","B","C","D","E","F","G"] ("" being lists of characters)
            $  - last two links as a monad:
          ”#   -   character '#'
        ;€     -   concatenate to €ach -> ["A#","B#","C#","D#","E#","F#","G#"]
       ż       - zip together          -> [["A","A#"],["B","B#"],["C","C#"],["D","D#"],["E","E#"],["F","F#"],["G","G#"]]
             Ẏ - tighten               -> ["A","A#","B","B#","C","C#","D","D#","E","E#","F","F#","G","G#"]

Ç”C4¦”F⁵¦ - Link 2, keyboard with phantoms replaced: no inputs
Ç         - call the last link (1) as a monad  ["A","A#","B","B#","C","C#","D","D#","E","E#","F","F#","G","G#"]
    ¦     - sparse application:
   4      - ...to index: literal four
 ”C       - ...action: character 'C'    -> ["A","A#","B","C","C","C#","D","D#","E","E#","F","F#","G","G#"]
        ¦ - sparse application:
       ⁵  - ...to index: literal ten
     ”F   - ...action: character 'F'    -> ["A","A#","B","C","C","C#","D","D#","E","F","F","F#","G","G#"]

Ñi+_⁸?4ị¢ - Main link: integer, clef (1 Treble / 0 Bass); list of characters, key
                                      e.g. 0; "D#"
Ñ         - next link (1) as a monad (no atom for next link as a nilad, but this works here anyway)
          -                               ["A","A#","B","B#","C","C#","D","D#","E","E#","F","F#","G","G#"]
 i        - first index of key in that    8
      4   - literal four
     ?    - if:
    ⁸     - ...condition: chain's left argument, clef
  +       - ...then: addition
   _      - ...else: subtraction          4
        ¢ - next link as a nilad          ["A","A#","B","C","C","C#","D","D#","E","F","F","F#","G","G#"]
       ị  - index into                    "C"

Python 2, 77 bytes

Function which prints to STDOUT. True converts bass to treble, and False converts treble to bass.

def f(n,c):N=ord(n[0])-63-4*c;M=-~N%3<1<len(n);print chr((N+M)%7+65)+n[1:2-M]

Try it online!

Explanation:

  • The first statement, N=ord(n[0])-63-4*c;, calculates the index (0 to 7) of the new note's letter, disregarding sharps.
    • ord(N[0])-63-4*c gets the current letter's index, and adds or subtracts 2 depending on the value of c (variable to toggle conversion direction)
  • The next statement, M=-~N%3<1<len(n); calculates whether or not this variable will need to be adjusted. For example, if the new note is E, and the original note had a sharp, this will need to be adjusted to an F. The chained inequality works as follows:
    • -~N%3<1 checks whether the new note's index is in the sequence 3n-1. This will only yield true for E and B, the two notes which do not have a sharp.
    • 1<len(n) checks if the original note had a sharp (this would make the string's length larger than 1). This is necessary as, if there was no sharp, there is no need to adjust the new note letters.
    • This sets the value of M to either True or False, which can be used in calculation as 1 and 0 respectively, so to perform the adjustment we need only add M to N and modulo by 7.
  • The final statement creates and outputs the final result.
    • chr((N+M)%7+65) adds the adjustment if necessary, then converts the value from an index back to a character.
    • +n[1:2-M] will append a sharp symbol if both M=0 (no adjustment was made) and the original value also had a sharp.