12
\$\begingroup\$

Before you leave, you do not have to understand much musical notation to do this challenge.

EXPLANATION

In standard sheet music, double clefs go across the page serving as reference points to the notes, letting you know what note should be played. If you are not already familiar with the treble and bass clef, here is a description from Wikipedia:

A clef is a musical symbol used to indicate the pitch of written notes. Placed on one of the lines at the beginning of the stave, it indicates the name and pitch of the notes on that line. This line serves as a reference point by which the names of the notes on any other line or space of the stave may be determined.

Sheet Music

In the image above, the top half of the lines is the Treble clef, denoted with a Treble Clef

The bottom half is the Bass clef, denoted with a Bass Clef

As you can see on the treble clef a note on the bottom-most line is an E. (I am not counting notes outside of the clef lines for this challenge) On the bass clef, the lowest line is a G. To complete this challenge, you must do the following:

CHALLENGE

Given an input in one of the following forms (your choice), convert it to the opposite clef. Whether it is the Treble or Bass clef can be a Truthey/Falsey value in your language (not just any two values), e.g.

F# T or F# True or F# Treble

but not

F# -1 or F# 4

Spaces and capitalization are optional, Flats will not appear, and trailing whitespace is not allowed.

Input          Expected Output
E   Treble     G
F   Treble     A
F#  Treble     A#
G   Treble     B
G#  Treble     C
A   Treble     C
A#  Treble     C#
B   Treble     D
C   Treble     E
C#  Treble     F
D   Treble     F
D#  Treble     F#
E   Treble     G
F   Treble     A
F#  Treble     A#
G   Bass       E
G#  Bass       F
A   Bass       F
A#  Bass       F#
B   Bass       G
C   Bass       A
C#  Bass       A#
D   Bass       B
D#  Bass       C
E   Bass       C
F   Bass       D
F#  Bass       D#
G   Bass       E
G#  Bass       F
A   Bass       F
A#  Bass       F#

Be forewarned, this is not a trivial constant difference challenge. Look closely at the inputs and outputs. If you look at a piano,

Piano

the black keys are sharps, denoted by #. Note that there is not an E# or a B#. This means that if you are given G# on the Bass clef, instead of returning E#, you need to return F

This is , so the smallest byte-count wins.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Do we have to worry about flats? How about double flats/sharps? \$\endgroup\$
    – mypetlion
    Commented Nov 6, 2017 at 20:45
  • 1
    \$\begingroup\$ Please don't create tags for topics that don't warrant them. \$\endgroup\$ Commented Nov 6, 2017 at 20:47
  • 3
    \$\begingroup\$ Is trailing whitespace (returning C instead of C) okay? \$\endgroup\$
    – lynn
    Commented Nov 6, 2017 at 20:48
  • 2
    \$\begingroup\$ Is using 1 and -1 (or even say, 4 and -4) for the clef indicator input allowed or would this only be acceptable if they are truthy/falsey values in our language? \$\endgroup\$ Commented Nov 6, 2017 at 21:13
  • 1
    \$\begingroup\$ This is a nice and well presented challenge, but it would have been even better IMO with slightly relaxed input/output formats. \$\endgroup\$
    – Arnauld
    Commented Nov 8, 2017 at 3:14

7 Answers 7

9
\$\begingroup\$

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 '#'.
\$\endgroup\$
5
\$\begingroup\$

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"
\$\endgroup\$
1
  • \$\begingroup\$ Green check: Hmm, so nobody yet beat this score - I am fairly shocked. \$\endgroup\$ Commented Nov 19, 2017 at 21:40
3
\$\begingroup\$

Perl 5, 56 bytes

$_=<>;s/./chr 65+(-4*<>+ord$&)%7/e;s/B#/C/;s/E#/F/;print

Reads the note and clef as two lines from STDIN and prints the new note to STDOUT. The clef is 0 for treble and 1 for bass.

\$\endgroup\$
1
3
\$\begingroup\$

JavaScript (ES6) 74 bytes

Takes input in currying syntax (note)(clef) where clef is 0 for bass and 1 for treble.

n=>c=>'FC.DAFCGDAEBF'[k=(parseInt(n,36)*15+!n[1]*90+c)%98%13]+(k<5?'#':'')

Demo

let f =

n=>c=>'FC.DAFCGDAEBF'[k=(parseInt(n,36)*15+!n[1]*90+c)%98%13]+(k<5?'#':'')

;[1, 0].forEach(c => {
  [ 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'C', 'C#', 'D', 'D#' ].forEach(n => {
    console.log(
      (n + '  ').slice(0, 3) +
      ['Bass  ', 'Treble'][c] +
      ' --> ' +
      f(n)(c)
    )
  })
})

How?

This is actually slightly less fun than my previous version, but the underlying lookup table is now F#,C#,(unused),D#,A#,F,C,G,D,A,E,B,F which allows to shorten the # condition while avoiding the NUL character trick -- which was a bit border-line, I suppose.


Previous version 76 75 bytes

n=>c=>'ACCDFF.CDEFGABCDE'[k=parseInt(4*!!n[1]+c+n,21)%24%17]+'\0#'[45>>k&1]

Demo

let f =

n=>c=>'ACCDFF.CDEFGABCDE'[k=parseInt(4*!!n[1]+c+n,21)%24%17]+'\0#'[45>>k&1]

;[1, 0].forEach(c => {
  [ 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B', 'C', 'C#', 'D', 'D#' ].forEach(n => {
    console.log(
      (n + '  ').slice(0, 3) +
      ['Bass  ', 'Treble'][c] +
      ' --> ' +
      f(n)(c)
    )
  })
})

How?

The input (n, c) is processed through the following steps:

  1. We first evaluate 4 * !!n[1] + c + n where !!n[1] is true (coerced to 1) if the note contains a #, and false (coerced to 0) otherwise. The expression 4 * !!n[1] + c results in a numeric value which is added in front of the string n.

  2. Implicit step: leading zeros and trailing # are ignored by parseInt(). For instance, "5G#" is actually parsed as "5G".

  3. We convert the new string to a decimal value by parsing it as a base-21 quantity.

  4. We apply modulo 24.

  5. We apply modulo 17.

Below is the summary table for all possible input pairs, along with the expected output. Note that a # must be added to the output if the final result is 0, 2, 3 or 5. Hence the use of the binary mask 101101 (45 in decimal).

 n   | c | (1)   | (2)   | (3) | (4) | (5) | Output
-----+---+-------+-------+-----+-----+-----+-------
"E"  | 1 | "1E"  | "1E"  |  35 |  11 |  11 | "G"
"F"  | 1 | "1F"  | "1F"  |  36 |  12 |  12 | "A"
"F#" | 1 | "5F#" | "5F"  | 120 |   0 |   0 | "A#"
"G"  | 1 | "1G"  | "1G"  |  37 |  13 |  13 | "B"
"G#" | 1 | "5G#" | "5G"  | 121 |   1 |   1 | "C"
"A"  | 1 | "1A"  | "1A"  |  31 |   7 |   7 | "C"
"A#" | 1 | "5A#" | "5A"  | 115 |  19 |   2 | "C#"
"B"  | 1 | "1B"  | "1B"  |  32 |   8 |   8 | "D"
"C"  | 1 | "1C"  | "1C"  |  33 |   9 |   9 | "E"
"C#" | 1 | "5C#" | "5C"  | 117 |  21 |   4 | "F"
"D"  | 1 | "1D"  | "1D"  |  34 |  10 |  10 | "F"
"D#" | 1 | "5D#" | "5D"  | 118 |  22 |   5 | "F#"
-----+---+-------+-------+-----+-----+-----+-------
"E"  | 0 | "0E"  | "E"   |  14 |  14 |  14 | "C"
"F"  | 0 | "0F"  | "F"   |  15 |  15 |  15 | "D"
"F#" | 0 | "4F#" | "4F"  |  99 |   3 |   3 | "D#"
"G"  | 0 | "0G"  | "G"   |  16 |  16 |  16 | "E"
"G#" | 0 | "4G#" | "4G"  | 100 |   4 |   4 | "F"
"A"  | 0 | "0A"  | "A"   |  10 |  10 |  10 | "F"
"A#" | 0 | "4A#" | "4A"  |  94 |  22 |   5 | "F#"
"B"  | 0 | "0B"  | "B"   |  11 |  11 |  11 | "G"
"C"  | 0 | "0C"  | "C"   |  12 |  12 |  12 | "A"
"C#" | 0 | "4C#" | "4C"  |  96 |   0 |   0 | "A#"
"D"  | 0 | "0D"  | "D"   |  13 |  13 |  13 | "B"
"D#" | 0 | "4D#" | "4D"  |  97 |   1 |   1 | "C"
\$\endgroup\$
3
\$\begingroup\$

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.
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Sorry, only 0&1, Truthey&Falsey, or T&B \$\endgroup\$
    – qqq
    Commented Nov 7, 2017 at 2:35
  • \$\begingroup\$ @tfbninja thanks for the clarification \$\endgroup\$
    – FlipTack
    Commented Nov 8, 2017 at 15:42
2
\$\begingroup\$

Java 8, 119 bytes

n->b->(b?"C D E F G A B C# F F# A# C":"F G A B C D E F# A# C D# F").split(" ")["A B C D E F G A#C#D#F#G#".indexOf(n)/2]

Explanation:

Try it here.

n->b->         // Method with String and boolean parameters and String return-type
  (b?          //  If it's Treble:
    "C D E F G A B C# F F# A# C"
               //   Use this String
   :           //  Else (it's Bass):
    "F G A B C D E F# A# C D# F")
               //   Use this String
  .split(" ")  //  Split this String by spaces,
   [           //  and then get the item at index:
    "A B C D E F G A#C#D#F#G#".indexOf(n)
               //   Get the index of the String on the left,
    /2]        //   and divide this by 2
               // End of method (implicit / single-line return-statement)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ another solution with 99 bytes: n->b->((char)((n.charAt(0)-(b?0:4))%7+65)+n.substring(1)).replaceAll("B#","C").replaceAll("E#","F") \$\endgroup\$ Commented Nov 7, 2017 at 16:02
  • \$\begingroup\$ @NahuelFouilleul Ah nice! I was indeed thinking something with a char-cast and some modulo might be shorter. But since it's a bit too different from my current answer, feel free to post it as a separate answer. You've got my upvote if you do. :) \$\endgroup\$ Commented Nov 7, 2017 at 16:06
0
\$\begingroup\$

R, 111 bytes

function(k,C,N=paste0(LETTERS[2:15%/%2],c("","#")))sub("E#","F",sub("B#","C",N[which(k==N[(4:17+6*C)%%14+1])]))

Try it online!

Ungolfed:

function(k,C){
  N=paste0(LETTERS[2:15%/%2],c("","#")) # Generate a vector of the notes, including E# and B#
  M=N[(4:17+6*C)%%14+1])                # Create a copy that's cycled either up 4 or down 4
  P=N[which(k==M)]                      # Look up the input note in the complementary vector
  P=sub("B#","C",P)                     # Replace B# with C
  P=sub("E#","F",P)                     # Replace E# with F
}
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.