14
\$\begingroup\$

Do randomly generated drum loops sound good?

A drum loop is a \$5\times 32\$ matrix \$A\$ of \$1\$s and \$0\$s such that

  1. \$A_{1,1}=A_{1,17}=A_{2,9}=A_{2,25}=1\$,
  2. for each \$i\$, the \$i\$th row has exactly \$f(i)\$ different \$1\$s, where \$ f(1)=3, f(2)=2, f(3)=6, f(4)=8, f(5)=5\$,
  3. for each \$j\equiv 1\bmod 4\$, the \$j\$th column has exactly one \$1\$, and
  4. for each \$j\not\equiv 1\bmod 4\$, the \$j\$th column has at most one \$1\$.

Here, the rows provide instructions for each of five drums, with each column representing a beat. In particular, the first row corresponds to a kick, while second row corresponds to a snare.

Write the shortest code that draws a matrix uniformly from the set of drum loops.

Feel free to use your code to draw a random drum loop, implement the drum loop on this website, and then post a link to your creation in your post. (Note that we only use the first 5 rows of this online beat maker.)

Edit 1: To be clear, we are using 1-based indexing: the row indices are \$\{1,\ldots,5\}\$ and the column indices are \$\{1,\ldots,32\}\$.

Edit 2: Judging by the current submissions, the answer to my original question appears to be "yes": randomly generated drum loops do sound good. Here are links to the current examples:

\$\endgroup\$
10
  • \$\begingroup\$ May we output the transposed matrix instead? \$\endgroup\$
    – Arnauld
    Jan 7, 2020 at 1:11
  • \$\begingroup\$ Just to clarify, I am guessing that you want the code to generate a different drum pattern each time it is run with a different seed value rather than to produce a single 'random' pattern that is the same each time. \$\endgroup\$
    – tom
    Jan 7, 2020 at 1:11
  • \$\begingroup\$ @Arnauld - Yes, you may output the transpose instead. \$\endgroup\$ Jan 7, 2020 at 1:29
  • \$\begingroup\$ @tom - Correct. Each run of the code should produce an independent realization of the random matrix. \$\endgroup\$ Jan 7, 2020 at 1:30
  • \$\begingroup\$ I assume \$i,j\$ are 1-based in your challenge description? It isn't mentioned anywhere whether the 5x32 matrix you describe uses 1- or 0-based indexing, so you might want to add that. \$\endgroup\$ Jan 7, 2020 at 8:56

7 Answers 7

3
\$\begingroup\$

JavaScript (ES6),  194 193  189 bytes

Returns \$M^T\$.

_=>(g=n=>n?g(/1/.test(c=m[R()*32|0])?n:n-=c[h()]=1):m)(16,a=[1,,6,8,5],R=Math.random,h=_=>a[y=R()*5|0]?a[y]--&&y:h(),m=[...Array(32)].map((_,x)=>(c=[0,0,0,0,0],x&3?0:c[x&7?h():x/8&1]=1,c)))

Try it online!

How?

Note: Columns and rows in the following explanation refer to the matrix described in the challenge. They are stored the other way around in this implementation.

Initialization

We define an array \$a\$ holding the number of hits that need to be added to each row, ignoring the 4 fixed positions.

a = [ 1, , 6, 8, 5 ]

We define the helper function \$h\$ that picks a random row among those that are not completely filled, and updates \$a\$ accordingly.

h = _ => a[y = R() * 5 | 0] ? a[y]-- && y : h()

We define a matrix \$m\$ of \$32\times5\$ filled with \$0\$'s, except the columns such that \$x\equiv 0\pmod 4\$ (using 0-indexing) which are initialized according to the rules described in the challenge.

m = [...Array(32)]          // set up 32 columns
  .map((_, x) => (          // for each column at position x:
    c = [0, 0, 0, 0, 0],    //   start with all values set to 0
    x & 3 ?                 //   if x mode 4 is not equal to 0:
      0                     //     do nothing
    :                       //   else:
      c[                    //     update c:
        x & 7 ?             //       if x mod 8 is not equal to 0:
          h()               //         use a random row
        :                   //       else:
        x / 8 & 1           //         fixed position: use either row 0 or 1,
                            //         depending on the parity of floor(x / 8)
      ] = 1,                //     set the hit
    c                       //   yield the final column
  ))                        // end of map()

Main loop

We randomly add the \$16\$ remaining hits on empty columns (which implies \$x\not\equiv 0\pmod 4\$) until all rows are filled.

( g = n =>                  // n = total number of hits to add
  n ?                       // if n is not equal to 0:
    g(                      //   do a recursive call:
      /1/.test(             //     test whether
        c = m[R() * 32 | 0] //     a randomly selected column c
      ) ?                   //     contains any '1'; if it does:
        n                   //       just pass n unchanged to try again
      :                     //     else:
        n -= c[h()] = 1     //       set the hit on this column, on a random row
                            //       and decrement n
    )                       //   end of recursive call
  :                         // else:
    m                       //   we're done: return m[]
)(16)                       // initial call to g with n = 16
\$\endgroup\$
3
  • \$\begingroup\$ thanks for the question about M transpose, that was really useful. \$\endgroup\$
    – tom
    Jan 7, 2020 at 3:05
  • 1
    \$\begingroup\$ Here's a link to your creation: splice.com/sounds/beatmaker/ad842eea5cf7 \$\endgroup\$ Jan 7, 2020 at 11:15
  • 1
    \$\begingroup\$ Thanks for the explanation - I always learn so much from your submissions :) \$\endgroup\$
    – G0BLiN
    Jan 8, 2020 at 12:25
3
\$\begingroup\$

C (gcc), 413 394 389 379 378 360 342 327 307 299 298 293 bytes

#define F;for(b[8]=b[v=24]=3,n=0;n
#define B,b[n]==
a[]=L"14444448888888822222",b[32],r[24],n,k,j,t=20,v;main(i){srand(&i)F<v;*b=b[16]=1)r[n++]=n/3-~n F<4;a[k]=a[t])b[i=4+n++*8]=a[k=rand()%t--]-48 F<16;r[j]=r[v])b[r[j=rand()%v--]]=a[n++]-48 F<32;n++)printf("%d%d%d%d%d\n"B 1 B 3 B 4 B 8 B 2);}

Try it online!

#define F;for(b[8]=b[v=24]=3,n=0;n
#define B,b[n]==
a[]=L"14444448888888822222",b[32],r[24],n,k,j,t=20,v;main(i){srand(time(0))F<v;*b=b[16]=1)r[n++]=n/3-~n F<4;a[k]=a[t])b[i=4+n++*8]=a[k=rand()%t--]-48 F<16;r[j]=r[v])b[r[j=rand()%v--]]=a[n++]-48 F<32;n++)printf("%d%d%d%d%d\n"B 1 B 3 B 4 B 8 B 2);}

try it online -earlier algorithm 307 bytes

Many thanks to @ceilingcat for more byte savings (360 to 342 - then 342 to 328 and 327 to 307 - then modified algorithm from 328 to 299 and 298 to 293).

Compiles ok without #include<stdio.h> using the -w compiler flag, thanks to @Arnaud for pointing this out.

As written it generates a fresh sequence each time. If we are allowed manual intervention of a new number in srand to generate a different drum sequence with say srand(6); then we can save 16 bytes.

Thanks to @Arnaud who made asked the question about generating the transpose matrix, which is what this program does. Thanks also for suggesting the change from int to char for a[20] that saves 15 bytes. Also thanks for @Post Rock Garf Hunter who figured out a way to remove white space near the Bs and then saved more bytes fixing up the #defines so that F is included in R. `#define' tokens often seem to need whitespace so they are cleanly identified.

This code works on tio.run, but elsewhere may need to include time.h.

How the program works

logic:

  • setup
  • 4 sounds are fixed (at 1,9,17,25)
  • remaining sounds to be distributed randomly are in a[20]
  • b[32] holds output - one cell for each beat (1 to 32, but in C of course 0 to 31)
  • 4 of the b cells must have something (at 5,13,21,29)
  • remaining sounds randomly scattered to empty cells
  • r[24] holds list of empty cells (2,3,4, 6,7,8 etc. in C 0,1,2, 4,5,6 etc.)
  • execute
  • put in 4 fixed sounds
  • choose random sound from a to put in each of 4 cells with sounds
  • choose random cell from r to put in random sound from remaining a sounds (simplification to algorithm is possible here by putting remaining sounds one by one from 1 to 16 (or 0 to 15) in a random cell, but this gave more bytes..., but now thanks to @ceilingcat this is now smaller see tio.run link for details)
  • print result

this is a long version of the code without the #define and other space savers to show the logic, but with small differences e.g. in final version a is char * not int *

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int a[20]={1,4,4,4,4,4,4,8,8,8,8,8,8,8,8,2,2,2,2,2},
    b[32]={},r[24],n,k,j,t=20,v=24;
  time_t q;srand(time(&q));  
//random initialization

  for(n=0;n<24;n++n=0;n<v;n++)r[n]=n+n/3+1;
// set up r with list of cells that don't definitely have sound 012,456 etc.

  b[0]=b[16]=1;b[8]=b[24]=16;
//put in fixed sounds

  for (n=0;n<4;n++) 
    {k=rand()%t--;b[4+n*8]=a[k];a[k]=a[t];}
//choose a random sound from a to put in each of 4 cells that must  have sound
// each sound selected is then replaced by the sound at the end of the list
// and the length of the list is reduced by one

  for (n=0;n<16;n++) 
    {k=rand()%t--;j=rand()%v--;b[r[j]]=a[k];a[k]=a[t];r[j]=r[v];}
// random cell from remaining `r` cells and random sound are selected 
// again after selection from list final item replaces selected and length
// reduced by one 

  for (n=0;n<32;n++) 
    {printf("%d%d%d%d%d\n",(b[n]==1),(b[n]==16),(b[n]==4),(b[n]==8),(b[n]==2));}
 // print out 0 and 1 from logical tests to print transpose matrix
  return 0;
}
\$\endgroup\$
11
  • \$\begingroup\$ Trivial golfing suggestion: remove all the unnecessary whitespace. \$\endgroup\$ Jan 7, 2020 at 2:18
  • \$\begingroup\$ Suggested optimization for the initialization of a[]. \$\endgroup\$
    – Arnauld
    Jan 7, 2020 at 3:15
  • \$\begingroup\$ @PostRockGarfHunter many thanks. I guess that after ) and " the compiler does not get confused by the lack of white space before the B. \$\endgroup\$
    – tom
    Jan 7, 2020 at 3:57
  • \$\begingroup\$ Fiddling around with your defines a bit I could I removed another 10 bytes. \$\endgroup\$
    – Wheat Wizard
    Jan 7, 2020 at 4:00
  • 1
    \$\begingroup\$ Here's a link to your creation: splice.com/sounds/beatmaker/b3054cbac7f1 \$\endgroup\$ Jan 7, 2020 at 11:07
1
\$\begingroup\$

05AB1E, 65 bytes

31Ý©8ˆ24ˆ40¯ǝ0ˆ16ˆ®¯KΩˆ48¯¦¦ǝ•Wk¤]•3ôεćи}˜.ržw8и«®¯K.r.¡4Ö}é˜ǝb€¦

Outputs as a transposed list of strings.

Try it online. (The footer pretty-prints this as the intended rows/columns. Feel free to remove the footer to see the actual result.)

Explanation:

31Ý                # Push a list in the range [0,31]
   ©               # Store it in variable `®` (without popping)
8ˆ                 # Add 8 to the global array
  24ˆ              # Add 24 to the global array as well
     40¯ǝ          # Replace the values at the indices of the global array with 40
0ˆ                 # Add 0 to the global array
  16ˆ              # Add 16 to the global array as well
     ®             # Push the [0,31] list from variable `®` again
      ¯K           # Remove all values of the global array (the [0,8,16,24])
        Ω          # Pop and push a random index from this list
         ˆ         # Add it to the global array as well
            ¯¦¦    # Push the global array with the first two (8,24) removed
          48   ǝ   # Replace the values at the remaining three indices with 48
•Wk¤]•             # Push compressed integer 533636834
      3ô           # Split it into parts of size 3: [533,636,834]
        ε          # Map over each integer
         ć         #  Extract head; pop and push remainder and head
          и        #  Repeat the remainder the head amount of times
                   #   i.e. 533 becomes [33,33,33,33,33]
        }˜         # After the map: flatten the list of list of integers
          .r       # And randomly shuffle it
            žw8и   # Then push a list of 8 32s
                «  # And merge it to the end of the list
®                  # Push the [0,31] list from variable `®` again
 ¯K                # Remove all indices stored in the global array
                   # (the [0,8,16,24] and one random index)
   .r              # Randomly shuffle these remaining indices
     .¡            # Group the remaining indices by:
       4Ö          #  Where this (0-based) index is divisible by 4
                   #  (which would be i % 4 == 1 for the corresponding 1-based index)
     }é            # After the group by, sort the two inner lists by length,
                   # so the indices divisible by 4 are before the other indices
       ˜           # And flatten it to a single list
ǝ                  # Then insert the shuffled integers at those shuffled indices
 b                 # Convert each integer to a binary string
                   # (32=100000;33=100001;34=100010;36=100100;40=101000;48=110000)
  €¦               # And then remove the leading "1" from each
                   # (after which the resulting list is output implicitly)

See this 05AB1E tip of mine (section How to compress large integers?) to understand why •Wk¤]• is 533636834.

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

Jelly, 43 bytes

32s8Zs4ZḢ;FẊḣ⁴ƲƊḢŒœ;FẊ$ƲFṙ-“¥©€Þı‘œṖ;¹Ṭz0ZṖ

Try it online!

A niladic link that returns a 32x5 matrix of 1s and 0s representing the drum loop. The footer simply prints it concatenated and separated by newlines.

Explanation

32                                          | 32
  s8                                        | Split into groups of 8
    Z                                       | Transpose
     s4                                     | Split into groups of 4
       Z                                    | Transpose
               Ɗ                            | Following as a monad:
        Ḣ                                   | - Head
         ;    Ʋ                             | - Concatenate to following as a monad:
          F                                 |   - Flatten
           Ẋ                                |   - Shuffle list
            ḣ⁴                              |   - First 16
                       Ʋ                    | Following as a monad:
                Ḣ                           | - Head
                 Œœ                         | - Odd/even indexed items
                   ;  $                     | - Concatenate to:
                    F                       |   - Remainder of list flattened
                     Ẋ                      |   - Shuffled
                        F                   | Flatten
                         ṙ-                 | Rotate right 1 item
                           “¥©€Þı‘œṖ        | Split list before positions 4, 6, 12, 20, 25
                                    ;¹      | Concatenate to 32 (because 32 was the beginning of this niladic link)
                                      Ṭ     | Convert from lists of numeric indices to logical lists with 1s at those positions
                                       z0   | Transpose with 0 as filler
                                         Z  | Transpose
                                          Ṗ | Remove last (the sixth list introduced with the 32 above)
\$\endgroup\$
1
\$\begingroup\$

Python 3, 180 bytes

from random import*
r=range
s=sample
a=s(s([x for x in r(32)if x%4],16)+[x*8+4 for x in r(4)],20)
print([[i in t for i in r(32)]for t in[[0,16,a[0]],[8,24],a[1:7],a[7:15],a[15:]]])

Try it online!

Prints a logical matrix representing the drum loop.

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

R, 138 bytes

s=sample
y=s(c(8*0:3+5,s((1:32)[-(4*0:7+1)],16)))
z=matrix(0,5,32)
z[1,c(1,17,y[1])]=z[2,c(9,25)]=z[3,y[2:7]]=z[4,y[8:15]]=z[5,y[16:20]]=1

Try it online!

A full program that implicitly prints the drum loop as a matrix of 1s and 0s.

Alternative using a third party package:

R + DescTools, 130 bytes

s=sample
y=s(c(8*0:3+5,s((1:32)[-(4*0:7+1)],16)))
sapply(list(c(1,17,y[1]),c(9,25),y[2:7],y[8:15],y[16:20]),DescTools::Unwhich,32)

RDRR it!

A full program that implicitly prints a logical matrix (in the transposed form).

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

Python 3, 400 \$\cdots\$ 361 338 bytes

from random import*
r=randrange
p=65537
def h(b=p): 
 while b==p:b=p|1<<r(32)
 l=[b,16777472]
 for i in[6,8,5]:
  while bin(b).count('1')!=i:b=sum(1<<r(32)for j in[1]*i)
  l+=[b]
 return l
g=1
while g:
 l,g=h(),0
 for i in range(32):
  b=sum(j&1<<i!=0for j in l)
  if i%4:g|=b>1
  else:g|=b!=1
for i in l:print(f"{bin(i)[2:]:>032}"[::-1])

Try it online!

Full program outputs a \$5\times32\$ matrix of \$1\$s and \$0\$s. Bit slow at times (\$\sim15\$ seconds), but you can't rush the creative process! :P

\$\endgroup\$

Your Answer

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

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