Making Music Boxes

23

1

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!

Okx

Posted 2017-03-10T08:35:51.810

Reputation: 15 025

can a music string contain () twice (e.g: AB(CD)E(FG)) ?? – Mr. Xcoder – 2017-03-10T09:01:52.740

@Mr.Xcoder Yes, it can. – Okx – 2017-03-10T09:05:51.013

Can the output be a list/array of characters? – Rod – 2017-03-10T12:00:50.960

@Rod sure, as per PPCG standards – Okx – 2017-03-10T12:05:32.293

Are we guaranteed to not get two of the same note in the same chord? – Business Cat – 2017-03-10T16:59:39.487

@BasicSunset Yes. – Okx – 2017-03-10T17:19:50.143

In a language where a list of characters counts as a string, can we take input as a nested list, such as {B, {A, C, E}, D} for your first example? – Greg Martin – 2017-03-10T18:42:30.800

@GregMartin No, I don't that would be vaid. – Okx – 2017-03-10T22:38:18.463

Can input be (B) instead of B? – l4m2 – 2018-07-23T10:51:46.470

@l4m2 No, it cannot. – Okx – 2018-07-23T13:39:49.373

Answers

0

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"]

DLosc

Posted 2017-03-10T08:35:51.810

Reputation: 21 213

6

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

Try it online! or Try all test cases

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'

Rod

Posted 2017-03-10T08:35:51.810

Reputation: 17 588

oh i missed that comment. nvm. – quintopia – 2017-03-10T17:55:28.143

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 – 2017-03-11T02:14:20.637

@ValueInk yep, OP allowed

– Rod – 2017-03-11T03:16:57.150

You mentioned the byte change and changed your TIO link but the code listed on your post is still the same :V – Value Ink – 2017-03-11T03:22:36.370

1@ValueInk ¯\(ツ) – Rod – 2017-03-11T06:30:36.423

4

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 (.

Neil

Posted 2017-03-10T08:35:51.810

Reputation: 95 035

4

Röda, 97 78 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
}

Previous answer:

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.

fergusq

Posted 2017-03-10T08:35:51.810

Reputation: 4 867

4

JavaScript (ES6), 86 85 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.

ETHproductions

Posted 2017-03-10T08:35:51.810

Reputation: 47 880

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 – 2017-03-10T19:32:50.177

@Neil Wow, that's amazing :-) – ETHproductions – 2017-03-10T19:37:23.113

Huh, now that I measure it again, it should be a 10 byte saving... – Neil – 2017-03-10T19:40:26.117

Can \) be . ? – l4m2 – 2018-07-23T10:54:10.480

2

JavaScript (ES6), 118 116 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)"))

Arnauld

Posted 2017-03-10T08:35:51.810

Reputation: 111 334

2

Ruby, 78 75 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

m-chrzan

Posted 2017-03-10T08:35:51.810

Reputation: 1 390

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 – 2017-03-11T02:25:55.720

1

Perl, 87 71 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.

Denis Ibaev

Posted 2017-03-10T08:35:51.810

Reputation: 876

1

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!

roberto06

Posted 2017-03-10T08:35:51.810

Reputation: 351

1

Retina, 120 bytes

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

T`x 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.

T`L.`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.

T`x 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)

Business Cat

Posted 2017-03-10T08:35:51.810

Reputation: 8 927

0

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>

CraigR8806

Posted 2017-03-10T08:35:51.810

Reputation: 480

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 – 2017-03-10T18:02:24.360

@Okx updated my bytecount. Wasn't sure if it added to it, still kinda new here :) – CraigR8806 – 2017-03-10T18:03:44.007

0

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)"]

Level River St

Posted 2017-03-10T08:35:51.810

Reputation: 22 049

0

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)]

RootTwo

Posted 2017-03-10T08:35:51.810

Reputation: 1 749

0

Haskell, 101 bytes

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.

Laikoni

Posted 2017-03-10T08:35:51.810

Reputation: 23 676

0

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.

Neil

Posted 2017-03-10T08:35:51.810

Reputation: 95 035

0

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
    ;

Titus

Posted 2017-03-10T08:35:51.810

Reputation: 13 814