Make a random drum loop

13

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:

Dustin G. Mixon

Posted 2020-01-07T00:31:14.857

Reputation: 1 833

May we output the transposed matrix instead? – Arnauld – 2020-01-07T01:11:10.223

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. – tom – 2020-01-07T01:11:15.853

@Arnauld - Yes, you may output the transpose instead. – Dustin G. Mixon – 2020-01-07T01:29:33.090

@tom - Correct. Each run of the code should produce an independent realization of the random matrix. – Dustin G. Mixon – 2020-01-07T01:30:51.580

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. – Kevin Cruijssen – 2020-01-07T08:56:42.177

@KevinCruijssen - Good point. I clarified this in my post. – Dustin G. Mixon – 2020-01-07T10:53:26.557

Can we output a matrix of True and False rather than 1 and 0? – Nick Kennedy – 2020-01-07T20:48:11.737

@NickKennedy - yes. – Dustin G. Mixon – 2020-01-07T23:27:11.220

Why can't I read "for each i, the ith row has exactly f(i) different 1s" -- what are "different 1s" meant? 1 is always an 1, there is only 1 1 and there can't be any different 1s. Where am I wrong?... – Alexey Burdin – 2020-01-13T01:03:14.970

@AlexeyBurdin - There are exactly f(i) entries in the ith row that equal 1. – Dustin G. Mixon – 2020-01-13T01:16:55.570

Answers

3

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;
}

tom

Posted 2020-01-07T00:31:14.857

Reputation: 141

Trivial golfing suggestion: remove all the unnecessary whitespace. – pppery – 2020-01-07T02:18:12.523

Suggested optimization for the initialization of a[]. – Arnauld – 2020-01-07T03:15:01.780

@PostRockGarfHunter many thanks. I guess that after ) and " the compiler does not get confused by the lack of white space before the B. – tom – 2020-01-07T03:57:06.000

Fiddling around with your defines a bit I could I removed another 10 bytes.

– Post Rock Garf Hunter – 2020-01-07T04:00:00.480

The general rule seems to be that gcc needs whitespace between macros and letters or numbers, but not other sorts of characters. This is a pretty common rule for a lot of languages. – Post Rock Garf Hunter – 2020-01-07T04:03:24.047

Probably the last thing I will see but since F is always followed by a space removing < from the end of its definition, and adding it instead of the space after each use, will actually save 1 byte. – Post Rock Garf Hunter – 2020-01-07T04:14:36.880

You can omit #include<stdio.h>. You may compile with -w (using the Compiler flags section) to get rid of the warnings. – Arnauld – 2020-01-07T09:40:23.850

1

Here's a link to your creation: https://splice.com/sounds/beatmaker/b3054cbac7f1

– Dustin G. Mixon – 2020-01-07T11:07:23.113

Suggest &i instead of time(0) – ceilingcat – 2020-01-27T06:23:36.070

@ceilingcat - how does that work?... well it does so great... - so I guess that the pointer for i is in effect a random number each time the code runs... !! Many thanks again. – tom – 2020-01-27T18:25:29.427

Some platforms, like TIO, have Address Space Layout Randomization

– ceilingcat – 2020-01-27T18:54:59.003

3

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

Arnauld

Posted 2020-01-07T00:31:14.857

Reputation: 111 334

thanks for the question about M transpose, that was really useful. – tom – 2020-01-07T03:05:30.213

1

Here's a link to your creation: https://splice.com/sounds/beatmaker/ad842eea5cf7

– Dustin G. Mixon – 2020-01-07T11:15:28.860

1Thanks for the explanation - I always learn so much from your submissions :) – G0BLiN – 2020-01-08T12:25:44.440

1

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.

Kevin Cruijssen

Posted 2020-01-07T00:31:14.857

Reputation: 67 575

Here's a link to your creation: https://splice.com/sounds/beatmaker/96d2538ae65b

– Dustin G. Mixon – 2020-01-07T14:28:26.740

1

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)

Nick Kennedy

Posted 2020-01-07T00:31:14.857

Reputation: 11 829

1

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.

Nick Kennedy

Posted 2020-01-07T00:31:14.857

Reputation: 11 829

0

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

Noodle9

Posted 2020-01-07T00:31:14.857

Reputation: 2 776

0

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

Nick Kennedy

Posted 2020-01-07T00:31:14.857

Reputation: 11 829