| Bytes | Lang | Time | Link |
|---|---|---|---|
| 050 | UiuaSBCS | 240727T181057Z | Europe20 |
| 056 | Haskell + hgl | 240625T232416Z | corvus_1 |
| 036 | Charcoal | 240623T174432Z | Neil |
| 076 | R | 240626T130113Z | Dominic |
| 166 | Go | 240625T143053Z | bigyihsu |
| 075 | jq | 240624T213238Z | GammaFun |
| 069 | Zsh | 240624T210259Z | GammaFun |
| 7973 | Python 3.8 prerelease | 240623T121208Z | squarero |
| 074 | JavaScript ES6 | 240623T132502Z | Arnauld |
| 082 | R | 240624T074231Z | int 21h |
| 117 | Google Sheets | 240624T145753Z | doubleun |
| 025 | 05AB1E | 240624T073637Z | Kevin Cr |
| 043 | J | 240624T054254Z | Bubbler |
| 088 | Perl 5 apl | 240624T013958Z | Xcali |
| 024 | Jelly | 240623T183816Z | Jonathan |
| 082 | Retina 0.8.2 | 240623T103032Z | Neil |
UiuaSBCS, 50 bytes
b←⊂:@b.
F←b¤-1⊢.b-1.
n←{F"C"F"F"b"G"}
⊏:n◿12+:⊗:n:
Converts the notes to numbers and does the shifting on them before turning them back into notes.
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"
Previous version:
m<(a!)<<(<(fromJust<F elemIndex a))<pl
a=wR"C Db D Eb E F Gb G Ab A Bb B"
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.
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.
R, 76 bytes
\(n,s,`/`=paste0,L=LETTERS)c(N<-rbind(L/"b",L)[1.2*1:12],N)[match(s,N)+n]/""
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!
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}
jq, 75 bytes
. as $s|input as $n|["AbABbBCDbDEbEFGbG"*2|scan(".b?")]|[.[index($n[])+$s]]
Probably is an exact port of another answer, although the only thing I explicitly copied is the .b? matching.
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
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)
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))
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]])
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:
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()
R, 82 bytes
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],"")
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]
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.
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.

Uses sort() as an array enabler only.
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)
J, 43 bytes
>:&.(C`Db`D`Eb`E`F`Gb`G`Ab`A`Bb`B`C i.;:)^:
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
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}{
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Ɱ⁸+ị®
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.
