16
\$\begingroup\$

In this challenge, you should take input as a number between 0 and 11 inclusive and a (possibly empty) list of the following strings/notes:

C Db D Eb E F Gb G Ab A Bb B

You are required to output flat notes, not sharp notes. Also, notes cannot contain more than one flat sign (Bbb is not allowed), and notes that can be written without a flat sign must not be written with one (Fb and Cb are not allowed).

The strings each represent a note in the chromatic scale. Your output should be the list of notes ‘transposed’ up with the input number of semitones. For example, the input

1, C D E F Eb Db B

should output

Db Eb F Gb E D C

. In other words, for each note in the sequence, output the one n notes up, wrapping from B to C if necessary.

Examples:

0, A B C Db Eb -> A B C Db Eb
1, C D E F G A B -> Db Eb F Gb Ab Bb C
2, Db Eb Gb Ab Bb -> Eb F Ab Bb C
7, C D Eb F G -> G A Bb C D
11, G E E -> Gb Eb Eb
5, (empty) -> (empty)

This is , so fewest bytes wins!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ 11, G -> F is incorrect. \$\endgroup\$
    – Neil
    Commented Jun 23 at 10:24
  • 3
    \$\begingroup\$ Is it acceptable to output "X " (with a space) instead of just "X" when there's no flat sign? \$\endgroup\$
    – Arnauld
    Commented Jun 23 at 22:01

15 Answers 15

7
\$\begingroup\$

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

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()
\$\endgroup\$
5
\$\begingroup\$

Python 3.8 (pre-release),  83   82   81  79 (73?) bytes

-1 byte: .split())[l.index(i)+t-12].split()*2)[l.index(i)+t].
-3 (-9?) bytes from Jonathan Allan.

lambda t,n,l='B Bb A Ab G Gb F E Eb D Db C'.split():[l[l.index(i)-t]for i in n]

73-byte solution (possibly allowed due to trailing spaces):

lambda t,n,l='G GbF E EbD DbC B BbA Ab':[l[l.find(s)-t-t:][:2]for s in n]

Alternate 83-byte solution because lookup tables are boring:

lambda t,n:[chr(int(m:=(ord(a)//.6-~t-len(b))%12*.6+65))+'b'*(m%1<.3)for a,*b in n]

Takes input like f(7, ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']).

Try #1 online!
Try #2 online!
Try #3 online!

Explanation

lambda t,n:                                                                        # Define lambda
           [                                                         for i in n]   # For each note:
            (l:='C Db D Eb E F Gb G Ab A Bb B'.split()  )                          # Set l to the list of music notes
                                                      *2                           # Duplicate so it doesn't overflow
                                                         [l.index(i)+t]            # Add t to original index
                                                                                   # (implicitly returned)
\$\endgroup\$
4
  • \$\begingroup\$ Save one byte moving l out to the function's arguments TIO. \$\endgroup\$ Commented Jun 24 at 16:48
  • \$\begingroup\$ Save two more (*2) by reversing l and subtracting t TIO. \$\endgroup\$ Commented Jun 24 at 17:25
  • 1
    \$\begingroup\$ Also, if trailing spaces become acceptable (as asked in this comment) then 73 bytes. \$\endgroup\$ Commented Jun 24 at 17:32
  • 1
    \$\begingroup\$ @JonathanAllan: I did try to get the 2-characters-per-note-name idea to work, but I modified it in the wrong place. Nice job reducing my solution! \$\endgroup\$ Commented Jun 24 at 18:03
4
\$\begingroup\$

R, 82 bytes

  • thanks to SamR for noticing a typo (-2 bytes) and pajonk for a -6 byte improvement

Edit: fixed the output for the empty string (returns an empty string instead of character(0))

Edit2: changed the input type to a vector/list, conform to the rules.

\(n,s)paste0(rep(N<-scan(,s,t="Ab A Bb B C Db D Eb E F Gb G"),2)[match(s,N)+n],"")

Attempt This Online!

old versions: 88 82 bytes

\(n,s)rep(N<-scan(,s,t="Ab A Bb B C Db D Eb E F Gb G"),2)[match(scan(,s,t=s),N)+n]

Attempt This Online!

Could not find anything shorter than scan(,"",t="Ab A Bb B C Db D Eb E F Gb G") (42 bytes). A somewhat more creative analog rbind(paste0(L<-LETTERS[1:7],"b"),L)[-c(5,11)] is 46 bytes long.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ I think this is actually 88 bytes because you included f= in the code as well as the header. I agree it feels like there should be a shorter way to produce the letters but I can't think of one. Something like chartr("1-78","A-Gb",c(18,1,28,2:3,48,4,58,5:6,78,7)) is 53 bytes - but if you can think of a way to express c(18,1,28,2:3,48,4,58,5:6,78,7) more compactly maybe there's something there. \$\endgroup\$
    – SamR
    Commented Jun 24 at 8:35
  • 1
    \$\begingroup\$ You can use any string as a second argument to scan , so s is enough (for -2 bytes). \$\endgroup\$
    – pajonk
    Commented Jun 24 at 8:56
  • 2
    \$\begingroup\$ And another -4 bytes by getting rid of modular arithmetic. \$\endgroup\$
    – pajonk
    Commented Jun 24 at 9:00
  • 1
    \$\begingroup\$ @SamR thanks, that was a silly typo from copypasting from RStudio. Not sure how to improve your vector - rbind(1:7*10+8,1:7)[-c(5,11)] is 2 bytes shorter but is still huge... \$\endgroup\$ Commented Jun 24 at 9:41
  • 1
    \$\begingroup\$ 77 bytes... \$\endgroup\$ Commented Jun 26 at 10:58
4
\$\begingroup\$

R, 76 bytes

\(n,s,`/`=paste0,L=LETTERS)c(N<-rbind(L/"b",L)[1.2*1:12],N)[match(s,N)+n]/""

Attempt This Online!

A somewhat golfed way to construct the list of notes, with the rest of the approach largely copied from int 21h Glory to Ukraine's answer - upvote that one!

\$\endgroup\$
0
4
\$\begingroup\$

Charcoal, 40 36 bytes

F⁷F⪪⪫קαι⁻²⁼²﹪ι³b²⊞υκ⪫E⁺NE⪪S ⌕υι§υι 

Try it online! Link is to verbose version of code. Explanation:

F⁷F⪪⪫קαι⁻²⁼²﹪ι³b²⊞υκ

Create a list of the note names from Ab through to G. This is inspired by @JonathanAllan's Jelly answer.

⪫E⁺NE⪪S ⌕υι§υι 

For each note in turn, find its index in the list, add on the offset, then cyclically look up the result in the list.

I tried using a smaller lookup string but the extra cost of decoding outweighed the saving:

≔bAbBCbDbEFbGζ⪫E⁺NE⪪S ⌕ζ⮌ιΦ⁺§ζ⊕ι§ζι∨μ№αλ 

Try it online! Link is to verbose version of code. Explanation: The note index can be directly looked up by finding the reverse of the note name in the lookup string but if the indexed character is a b then recreating the note name requires you to prefix it with the following character.

I tried not using a lookup string at all but the resulting calculations were simply far too long:

⪫E⁺NE⪪S ⁻÷⁺¹⁶×¹²⌕α…ι¹¦⁷Lι⁺§…α⁷÷⁺²×⁷ι¹²…b÷﹪⁺⁹×⁵ι¹²¦⁷ 

Try it online! Link is to verbose version of code.

\$\endgroup\$
2
\$\begingroup\$

Jelly,  25  24 bytes

7;28,ƊF%?€3ẎịØẠF€©iⱮ⁸+ị®

A dyadic Link that accepts the notes as a list of lists of characters on the left and the transpose on the right and yields the new notes as a list of lists of characters.

Try it online! Or see the test-suite.

How?

7;28,ƊF%?€3ẎịØẠF€©iⱮ⁸+ị® - Link: Notes; Transpose
7        €               - for each i in [1..7]:
        ?                -   if...
       %  3              -   ...{i} mod three?
     Ɗ                   -   ...then: last three links as a monad:
  28                     -     twenty-eight
 ;                       -     concatenate -> [i, 28]
    ,                    -     pair -> [[i, 28], i]
      F                  -   ...else: flatten -> [i]
           Ẏ             - tighten
            ịØẠ          - index into "ABCDEFG...Zab...z"
               F€        - flatten each
                 ©       - and copy that to the register
                  iⱮ⁸    - index-of mapped across {Notes}
                     +   - add {Transpose}
                      ị® - index into the register (modular)

Original 25:

ØAḣ7p⁾b 5,11œPẎt€©⁶iⱮ⁸+ị®

Try it

\$\endgroup\$
2
\$\begingroup\$

Zsh, 69 bytes

Takes notes as argv, transposition on stdin.

S=(${${:-{A..G}{b,}}:#[CF]b})
S+=($S)
eval '<<<$S[S[(i)'$^@\]+`<&0`\]

Try it online! Uses a wrapper function to reformat the output on a single line.

S=(${${:-{A..G}{b,}}:#[CF]b})
#    ${:-{A..G}{b,}}            # unnamed parameter expansion -> brace expansion
#  ${               :#[CF]b}    # remove Cb and Fb
S+=($S)                         # append the whole list a second time
eval '<<<$S[S[(i)'$^@\]+`<&0`\]
## Before eval:
#                       `<&0`   # capture stdin input (semitone offset)
#                 $^@           # argv, enable rc_expand_param
## During eval:
#           S[(i)     ]         # get the (i)ndex of the first matching array element
#        $S[           +`<&0` ] # add semitones, get array element
#     <<<                       # write to stdout followed by a newline
\$\endgroup\$
2
\$\begingroup\$

Haskell + hgl, 73 56 bytes

-6 bytes thanks to WheatWizard

m<(a!)<<fm(he<F isx a)<pl
a=cX(7#<Β*:*Wr"b ")$wR"Cb Fb"

Attempt This Online

Previous version:

m<(a!)<<(<(fromJust<F elemIndex a))<pl
a=wR"C Db D Eb E F Gb G Ab A Bb B"

Attempt This Online!

Pointfree version of this:

f :: Int -> [String] -> [String]
f inc arr = map ((a!) < (+inc) < fromJust < flip elemIndex a) arr

Where (!) is wrap-around indexing and (<) is function composition.

Sadly, hgl has no shorthand for fromJust, fromMaybe, elemIndex or findIndex, as far as I'm aware.

\$\endgroup\$
3
  • \$\begingroup\$ You can replace elemIndex with isx and fromJust with he. Slightly more complicated but you can reorganize a bit using fm to save a byte: m<(a!)<<fm(he<F isx a)<pl. \$\endgroup\$
    – Wheat Wizard
    Commented Jul 7 at 16:07
  • \$\begingroup\$ a can be shortened to tl$er"Fb"$"CDEFGAB"*:*Wr"b " \$\endgroup\$
    – Wheat Wizard
    Commented Jul 7 at 17:35
  • 1
    \$\begingroup\$ Thanks! I also found a=cX(7#<Β*:*Wr"b ")$wR"Cb Fb" at the same byte count. \$\endgroup\$
    – corvus_192
    Commented Jul 16 at 20:08
1
\$\begingroup\$

Retina 0.8.2, 82 bytes

T`L`BD\EGIJ\L
T`bL`__L`.b
\d+
$*
+T`1\LL`_L`1,.*
, 

[ACFHK]
$&b
T`L`AABBCDD\E\EFG

Try it online! Link includes test cases. Explanation:

T`L`BD\EGIJ\L
T`bL`__L`.b

Transliterate the notes Ab to G to the uppercase letters A to L.

\d+
$*
+T`1\LL`_L`1,.*
, 

For each semitone, transpose the notes up a letter.

[ACFHK]
$&b
T`L`AABBCDD\E\EFG

Turn the letters A to L back into notes Ab to G.

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

Perl 5 -apl, 88 bytes

map$r{$_}=$i++,@a=(C,Db,D,Eb,E,F,Gb,G,Ab,A,Bb,B);$i=<>;map$\.=$a[($r{$_}+$i)%@a].$",@F}{

Try it online!

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

J, 43 bytes

>:&.(C`Db`D`Eb`E`F`Gb`G`Ab`A`Bb`B`C i.;:)^:

Attempt This Online!

I have absolutely no idea to golf the gigantic array of strings...

The code is an "adverb", which takes the number of semitones on its left to become a "verb", and then takes a string representing the notes on its right to return the answer.

Since "shift a note by x semitones" is equal to "shift a note by 1 semitone x times", the code does the latter.

>:&.(C`Db`D`Eb`E`F`Gb`G`Ab`A`Bb`B`C i.;:)^:    shift by n semitones
                                         ^:    n times
>:&.(C`Db`D`Eb`E`F`Gb`G`Ab`A`Bb`B`C i.;:)      shift by 1 semitone:
                                      ;:    split at spaces
     C`Db`D`Eb`E`F`Gb`G`Ab`A`Bb`B`C i.      convert each word to the index in the array
>:                                          increment those indices
  &.(                                   )   undo the first two operations:
     C`Db`D`Eb`E`F`Gb`G`Ab`A`Bb`B`C i.      index back into the array
                                            (thus the trailing C is needed)
                                      ;:    join by spaces
\$\endgroup\$
1
\$\begingroup\$

05AB1E, 25 bytes

Au7£SD„CFм'b«s.ι'bKDIkI+è

Two loose inputs in the order \$n,notes\$, where \$notes\$ and the output are both lists of characters.

Try it online or verify all test cases.

Explanation:

Au                   # Push the uppercase alphabet
  7£                 # Pop and leave just its first 7 letters: "ABCDEFG"
    S                # Convert it to a list of characters
     D               # Duplicate this list
      „CFм           # Remove "C" and "F" from each inner letter,
                     # converting the "C" and "F" to empty strings ""
          'b«       '# Append a "b" to each
             s.ι     # Swap the two lists, and interleave them
                'bK '# Remove the two "b" items
D                    # Duplicate this list
 I                   # Push the input-list of notes
  k                  # Pop the copy and this input, and get the index of each
   I+                # Add the second input-integer n
     è               # (0-based modular) index those into the list
                     # (after which the resulting list is output implicitly)
\$\endgroup\$
1
\$\begingroup\$

Google Sheets, 117 bytes

=let(s,split("Ab,A,Bb,B,C,Db,D,Eb,E,F,Gb,G",","),iferror(choosecols(s,sort(mod(match(torow(B1:1,1),s,)+A1-1,12)+1))))

Put the number of semitones in cell A1 and the notes in cells B1:1.

screenshot

Uses sort() as an array enabler only.

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

jq, 75 bytes

. as $s|input as $n|["AbABbBCDbDEbEFGbG"*2|scan(".b?")]|[.[index($n[])+$s]]

Try it online!

Probably is an exact port of another answer, although the only thing I explicitly copied is the .b? matching.

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

Go, 166 bytes

import(."slices";S"strings")
func f(s int,n[]string)(o[]string){N:=S.Fields("C Db D Eb E F Gb G Ab A Bb B")
for _,e:=range n{o=append(o,N[(Index(N,e)+s)%12])}
return}

Attempt This Online!

\$\endgroup\$

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