Making Music Boxes

Your task is to take a sequence of characters (the music) as input (in a function or program), and print (or return) the music as it would look like in a music box.

You will only receive the characters ABCDEFG.() as input, and the input will never be empty. You may also receive the letters in lowercase, if you wish for it.

This is an empty music box, of length 3:

.......
.......
.......


As you can see, the lines are 7 characters long, and since the length of the music box is 3, we have 3 lines. There are only .s here, since the music box is empty. Let's put some music in it!

First, we create the music box. In this example, the input will be CDAG.DAG.

The length of CDAG.DAG is 8, so we need a music box of length 8:

.......
.......
.......
.......
.......
.......
.......
.......


Then, we read the input, one character at a time, and place an O at its respective position.

The first character is C, and the location of each note is equivalent to this (I added spaces for clarity):

 A B C D E F G
. . . . . . .
. . . . . . .
(and so on)


If the input character is a ., then we just print an empty line .......

So, the C would be the 3rd character along. Let's put it in our music box at the top:

..O....
.......
.......
.......
.......
.......
.......
.......


We will repeat this process for all the other characters (the text in brackets is just to show you the note, you shouldn't output that):

..O.... (C)
...O... (D)
O...... (A)
......O (G)
....... (.)
...O... (D)
O...... (A)
......O (G)


Because of how music boxes work, if we use a character other than O, . and <insert newline here>, such as a space, in our output, then it won't play the correct music!

This is a chord:

(ACE)


This chord is instructing us to play the notes A, C and E at the same time. There will never be a pause (ie a .) in a chord.

This is how it would be written:

O.O.O...


And this is how it might appear in music: B(ACE)D

You will never receive a chord in a chord, ie this won't be valid: (AB(CD)EF) or this: A(B()), and chord will not be empty, ie this won't be valid: A()B

You will never receive an invalid input.

Examples:

B(ACE)D

.O.....
O.O.O..
...O...

B

.O.....

GGABC

......O
......O
O......
.O.....
..O....

...

.......
.......
.......

A..F.C(DA).

O......
.......
.......
.....O.
.......
..O....
O..O...
.......

.(ABCDEF)

.......
OOOOOO.


Trailing/leading whitespace on the output is permitted.

As this is , the shortest code wins!

• can a music string contain () twice (e.g: AB(CD)E(FG)) ?? – Mr. Xcoder Mar 10 '17 at 9:01
• @Mr.Xcoder Yes, it can. – Okx Mar 10 '17 at 9:05
• Can the output be a list/array of characters? – Rod Mar 10 '17 at 12:00
• @Rod sure, as per PPCG standards – Okx Mar 10 '17 at 12:05
• Are we guaranteed to not get two of the same note in the same chord? – Business Cat Mar 10 '17 at 16:59

Pip, 29 bytes

28 bytes of code, +1 for -l flag.

'.X7RA_'OMz@?a@$$\w+.|.@XL  Takes input in lowercase as a command-line argument. Try it online! Explanation  a is 1st cmdline arg; XL is [a-z]; z is lowercase alphabet a@\(\w+.|. List of all matches in a of this regex: Either a ( followed by letters followed by another character (i.e. the closing paren), or any one character @XL For each of those matches, a list of all matches of this regex (effectively, split the match into a list of characters and keep only the lowercase letters) z@? Find index of each letter in the lowercase alphabet M To that list of lists of indices, map this function: '.X7 Take a string of 7 periods RA_ and replace the characters at all indices in the argument 'O with O Finally, autoprint the resulting list, with each item on its own line (-l flag)  Here's a sample of how an input is transformed: "b.(ceg)" ["b" "." "(ceg)"] [["b"] [] ["c" "e" "g"]] [[1] [] [2 4 6]] [".O....." "......." "..O.O.O"]  Python 2, 95 94 bytes -1 byte thanks to Value Ink x=1 for i in input(): if x:o=['.']*7 if'@'<i:o[ord(i)-65]='O' if'*'>i:x=i>'(' if x:print o  Explanation '@'<i is to check if i is a letter, replacing the . by O on the right position. '*'>i is to check if i is a parenthesis, if it is x=i>'(' will put 0 on x to prevent the printing/clearing of o, when i==')', it will put 1 on x re-enabling the printing/clearing of o. When i=='.' nothing will be changed, and '.......' will be printed. The charater order is given by their ASCII code, where '('<')'<'*'<'.'<'@'<'A' • oh i missed that comment. nvm. – quintopia Mar 10 '17 at 17:55 • Ditch the comma: ['.']*7. Perhaps it's a holdover from when you were using a tuple, which would require the comma. Also, I might be wrong, but this seems to output an array ['O', '.', '.', '.', '.', '.', '.'] per line, and I'm not sure if that's allowed? – Value Ink Mar 11 '17 at 2:14 • @ValueInk yep, OP allowed – Rod Mar 11 '17 at 3:16 • You mentioned the byte change and changed your TIO link but the code listed on your post is still the same :V – Value Ink Mar 11 '17 at 3:22 • @ValueInk ¯\_(ツ)_/¯ – Rod Mar 11 '17 at 6:30 Batch, 209 bytes @set s=%1 @set p=) @for %%n in (a b c d e f g)do @set %%n=. :g @if %s:~,1% lss @ (set "p=%s:~,1%")else set %s:~,1%=O @set s=%s:~1% @if %p%==( goto g @echo %a%%b%%c%%d%%e%%f%%g% @if not "%s%"=="" %0 %s%  Works by accumulating letters and outputting the line if the last symbol seen was not a (. Röda, 9778 76 bytes {search\(\w+$$|.|{|c|seq 65,71|{|l|["O"]if[chr(l)in c]else["."]}_;["
"]}_}


Try it online!

It's an anonymous function that reads the input from the stream. Use it like this: main { f={...}; push("ABCD") | f() }. It uses the regex from ETHproductions' answer.

Ungolfed:

{
search($$\w+$$|.) | for chord do
seq(ord("A"), ord("G")) | for note do
if [ chr(note) in chord ] do
push("O")
else
push(".")
done
done
push("\n")
done
}


f s{(s/"(?=([^()]*(\$$[^()]*\$$)?)*$)")|{|c|seq 65,71|{|l|["O"]if[chr(l)in c]else["."]}_;[" "]}_}  Try it online! It works by splitting the given string at places where the string following contains only matched parentheses. Then, for each chord, it iterates through possible notes and print O if the note is a member of the chord and . otherwise. JavaScript (ES6), 8685 76 bytes Saved 9 bytes thanks to @Neil let f = s=>s.replace(r=/$$\w+$$|./g,x=>ABCDEFG .replace(r,c=>x.match(c)?"O":".")) <input oninput="if(/^([A-G.]|$$[A-G]+$$)+$/.test(value))O.textContent=f(value)"><br>
<pre id=O></pre>

Explanation

First, we match what will form each line of the output: chords, and chars that aren't part of a chord. Then, for each line, we take the string ABCDEFG\n and replace each non-newline character in it with an O if the line contains it, and a . otherwise.

• If a trailing newline is acceptable, you can save 8 bytes using s=>s.replace(r=/$$\w+$$|./g,x=>ABCDEFG\n.replace(r,c=>x.match(c)?"O":".")). – Neil Mar 10 '17 at 19:32
• @Neil Wow, that's amazing :-) – ETHproductions Mar 10 '17 at 19:37
• Huh, now that I measure it again, it should be a 10 byte saving... – Neil Mar 10 '17 at 19:40
• Can \) be . ? – l4m2 Jul 23 '18 at 10:54

JavaScript (ES6), 118116 114 bytes

f=([c,...t],s)=>c?((s?0:x=[...'.......'],c='ABCDEFG)('.indexOf(c))>6?c-7:(x[c]='O',s))?f(t,1):x.join+
+f(t):''


Test cases

f=([c,...t],s)=>c?((s?0:x=[...'.......'],c='ABCDEFG)('.indexOf(c))>6?c-7:(x[c]='O',s))?f(t,1):x.join+
+f(t):''

console.log(f("B(ACE)D"))
console.log(f("GGABC"))
console.log(f("..."))
console.log(f("A..F.C(DA)."))
console.log(f(".(ABCDEF)"))

Ruby, 7875 71 bytes

->x{x.scan(/$$\w+$$|./).map{|x|l=?.*7
x.bytes{|x|x>47?l[x-65]=?O:1};l}}


Returns an array of strings.

Ungolfed + explanation

def boxes string
string.scan(/$$\w+$$|./)    # Split the string into an array of chords.
.map do |chord|             # Replace each chord with...
line = '.' * 7            # a line, where by default each character is a '.',
chord.bytes do |note|     # but for each note in the chord...
if note > '.'.ord       # (if it is in fact a note and not a dot or paren)
line[note-65] = 'O'   # replace the corresponding dot with an 'O'.
end
end
line
end
end

• Try x.gsub(...){l=?.*7;$&.bytes{...};l+$/} (swap scan with gsub, remove map, and skip the first |x| since you can use $& to access the last regex match) to save 3 bytes and return a multi-line string instead. (Also $/ maps to a newline by default.) – Value Ink Mar 11 '17 at 2:25

PHP, 171 bytes

preg_match_all('#[A-G\.]|$$[A-G]+$$#',$argv[1],$m);foreach($m[0]as$l){if($l=='.')echo".......";else foreach([A,B,C,D,E,F,G]as$a)echo strpos($l,$a)!==false?O:'.';echo"\n";}


Breakdown :

preg_match_all('#[A-G\.]|$$[A-G]+$$#',$argv[1],$m); // Matches either one character in the range [A-G.] OR multiple [A-G] characters between parentheses
foreach($m[0]as$l)                                  // For each match :
if($l=='.') // If no note is played echo"......."; // Echo empty music line else // Else foreach([A,B,C,D,E,F,G]as$a)                //     For each note in the [A-G] range
echo strpos($l,$a)!==false?O:'.';       //       Echo O i the note is played, . if not
echo"\n";                                       //  Echo new line
}


Try it here!

Retina, 120 bytes

O(?<=$$[^)]*)[^)] TL.d (?<=\([^)]*)\d *x$$
m¶
+\b(x+) \1(x+) m
$1 m$2
m?x

Tx m(.\O_
\d
$*.O¶ ¶ 6$*.¶
%7>.



I'm sure there's room for golfing, but it works now, so I'll try to golf it more later.

Try it online!

How it works

Basically, the program works by changing each character to a number, then assigning a O to that position in a line. It maps ABCDEFG. to 01234569.

To generate the single note lines, all that has to be done is to put an O after the corresponding number of .s, then pad out the line to 7 characters long.

However, the chords are a bit trickier to do. A similar process is used, but the numbers have to be translated to increments, i.e. the first note in the chord is (whatever), the second is X positions after the first, the third is Y positions after that, etc.

Code

O(?<=$$[^)]*)[^)]  Start out by sorting all characters within chords. TL.d  Perform the transliteration (mapping) from letters to numbers. (?<=\([^)]*)\d *x  Replace all digits within brackets with a unary representation (using xs), followed by a space. $$
m¶


Replace all closing brackets with m followed by a newline. The m will be used as a marker of sorts for the coming loop:

+\b(x+) \1(x+) m
$1 m$2


This is a replacement stage that loops until it can't replace anymore. It takes the last two sequences of xs before an m, and subtracts the first from the second, moving the m back. The marker m is needed because it has to perform this operation from right to left.

 m?x



Remove the first x in each sequence except the first one.

Tx m(.\O_


Transliterate by replacing x with ., space with O, and deleting m and (.

At this point, all the lines for the chords have been created. Now the single-note lines have to be created.

\d
$*.O¶  Replace each digit with that many .s, followed by an O and a newline. ¶ 6$*.¶
%7>.



Pad each line to length 7 by adding .s to the right. This works by adding 6 .s to the end of each line (each line will have at least 1 other character), then replacing every character after the first 7 on each line with nothing. (Since . maps to 9, the O will be cut out on those lines)

Perl, 8771 45 + 2 (-nl flag) = 47 bytes

#!/usr/bin/env perl -nl
use v5.10;
say map$&=~/$_/i?O:".",a..g while/$$\w+$$|./g


Using:

perl -nlE 'say map$&=~/$_/i?O:".",a..g while/$$\w+$$|./g' <<< "A..F.C(DA)."


Try it on Ideone.

Perl 5 - 78 + 1(flag) + 2(Input Quotes) = 81 Bytes

for(;/($$[a-g]+$$|[a-g\.])/g;){$i=$1;print$i=~/$_/?'o':'.'for(a..g);print"\n"}


Can be run like so:

perl -n <name of file holding script> <<< <input in quotations>

• Don't you think that the input in quotes would count as 2 extra bytes? I may be wrong, as there may be a meta consensus on this stating otherwise. – Okx Mar 10 '17 at 18:02
• @Okx updated my bytecount. Wasn't sure if it added to it, still kinda new here :) – CraigR8806 Mar 10 '17 at 18:03

Ruby, 68 bytes

->s{w=?.*m=7
s.bytes{|i|i>64?w[i-65]=?O:m=i!=40;m&&(puts w;w=?.*7)}}


The idea is to modify the string ....... every time we find a letter, then output and reset it, but only when we are outside brackets. ( switches the outputting off. ) and . both switch/leave the outputting on, but the latter is inconsequential as it will never be found inside a bracket.

Ungolfed in test program

f=->s{w=?.*m=7              #set m to a truthy value (7) and w to seven .'s
s.bytes{|i|               #for each byte in the string
i>64?w[i-65]=?O:m=i!=40 #if a letter, modify the appropriate character of w ELSE set m to false if inside brackets, true otherwise.
m&&(puts w;w=?.*7)      #if m is true, output the contents of w and reset to seven .'s
}
}

p 1
f["B(ACE)D"]
p 2
f["B"]
p 3
f["GGABC"]
p 4
f["A..F.C(DA)."]
p 5
f[".(ABCDEF)"]


Python 3, 94 bytes

An anonymous function

import re
lambda s:[''.join('.O'[c in x]for c in'ABCDEFG')for x in re.findall(r'$$\w+$$|.',s)]


c#s|elem c s=c|1<3='.'
s?r=map(#s)"ABCDEFG":p r
p('(':r)|(x,_:t)<-span(')'<)r=x?t
p(x:r)=[x]?r
p e=[]


Try it online! Usage: p "AB.(CA)D". Returns a list of strings.

Explanation:

The function p recurses over the string. If it finds an opening bracket '(' then (x,_:t)<-span(')'<)r partitions the rest string r into the strings x before the occurrence of the closing bracket ')' and t after it. Otherwise the current character x is turned into a string [x]. In both cases the function ? is called with the current string of notes and the rest string. ? maps the function # over the string "ABCDEFG", where # replaces all chars that are not in the current string of notes with '.'. The resulting music box line is prepended to the recursive call of p on the rest list r.

Retina 0.8.2, 52 bytes

$$\w+$$|.
abcdefg$&¶ +([a-g])(.*)\1 O$2
T().l___.


Try it online! Takes input in lower case. Explanation:

$$\w+$$|.
abcdefg$&¶  Split the music into chords or notes, and start building the output by adding the list of note equivalents. +([a-g])(.*)\1 O$2


For each note in each chord, change the output to an O and delete the note from the chord.

T().l___.


Delete all the now extraneous music, and change all the unmatched notes to empty.

PHP, 93 bytes

for($s=$t="
.......";$c=ord($argn[$i++]);$d||$s=$t.!print$s)$c<65?$c-46&&$d=~$c&1:$s[$c&7]=O;  Run as pipe with -nR or try it online. breakdown for($s=$t="\n......."; // init$c=ord($argn[$i++]);    // loop through characters
$d|| // 2. if chord flag is unset$s=$t.!print$s)             // then print and reset chord
$c<65 // 1. if not note ?$c-46                      // and not dot
&&$d=~$c&1              // then set or clear chord flag
:$s[$c&7]=O             // else set note in chord
;