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
  • \$\begingroup\$ Thanks! I also found a=cX(7#<Β*:*Wr"b ")$wR"Cb Fb" at the same byte count. \$\endgroup\$
    – corvus_192
    Commented yesterday
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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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