7
\$\begingroup\$

Preface

The available keys are the keys of the A through G and A minor through G minor.

The Challenge

Create a program that creates and saves to a playable audio file a "song" of using (uniformly distributed) random chords. The song must be of length n and in key k. The chords in each "song" must conform to the key. If you don't know what belongs in a particular key, the image below may help.

Keys and Chords

Rules and Guidelines

  • Must conform to key

  • Chords must be chosen randomly with even distribution

  • Assume each chord is held for 1 beat in 4:4 time at 120 bpm, therefore each chord lasts .5 seconds

  • The same chord is allowed to be selected multiple times in a row, though it must be randomly

  • The program should theoretically be able to generate a "song" of any positive time value

  • Assume n > 1 in all cases

  • Input will be a length n (in seconds) and a key k as a major or minor key A, Amin, B, Bmin, etc

  • Output must be in the form of a playable audio file

Examples (as strings)

[key], [length] -> [output]

C, 1 -> Dmin, F

B, 6 -> D# min, C# min, F#, G# min, E, B, F#, C# min, C# min, D# min, E, G# min
\$\endgroup\$

closed as unclear what you're asking by Rɪᴋᴇʀ, FlipTack, Wheat Wizard, Conor O'Brien, Blue Jan 6 '17 at 17:47

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 2
    \$\begingroup\$ Alright, what's the audio output library with the shortest function names... starts googling \$\endgroup\$ – SIGSTACKFAULT Jan 4 '17 at 15:26
  • \$\begingroup\$ @Blacksilver I live to torture golfers (; \$\endgroup\$ – GracefulLemming Jan 4 '17 at 18:03
  • \$\begingroup\$ If I chose to implement the correct chords instead of defaulting to no accidentals, would that still be a valid submission? \$\endgroup\$ – Patrick Roberts Jan 4 '17 at 19:54
  • 2
    \$\begingroup\$ Using no accidentals for the root note of the chords does NOT allow you to ignore accidentals. Taking your second example, we have Dmin D F A, Cmin C Eb G, F F A C, Gmin G Bb D E E G# B, B B D# F#, F F A C etc, ie alternating between the keys of Bmajor/G#minor (5 sharps) and Bb major/Gminor (2 flats.) If you allow ALL notes to be non-accidentals, you effectively restrict the problem to the key of Cmaj. I think you need to think about that rule 2 again. It's totally at odds with rule 1 which says must conform with the key as much as possible (which by the way is too vague.) \$\endgroup\$ – Level River St Jan 4 '17 at 20:16
  • 1
    \$\begingroup\$ @LevelRiverSt fixed. The limitation made more sense in my mind \$\endgroup\$ – GracefulLemming Jan 4 '17 at 20:18
1
\$\begingroup\$

JavaScript (ES6) + WAV.js, 303 299 297 bytes (non-competing)

k=>l=>eval("for(m=/(.)(#?)(m?)/.exec(k),w=new WAV,g=m[3]?[2,1,2,2,1,3,1]:[2,2,1,2,2,2,1],s=WAV.semitone(m[1]+4+m[2]),n=0;n<l*2;n++)for(r=Math.random()*7|0,c=i=g.reduce((u,h,j)=>u+(j<r)*h,0);c-i<12;c+=g[r++%7]+g[r++%7])w.writeNote({note:WAV.note(s+c),time:.5,amplitude:.25},[],1,c-i<9);w.toBlob()")

De-obfuscated:

(key) => (length) => {
  let match = /(.)(#?)(m?)/.exec(key),
      wav = new WAV,
      // if minor, use minor gaps else major gaps
      gaps = match[3] ? [2, 1, 2, 2, 1, 3, 1] : [2, 2, 1, 2, 2, 2, 1],
      // get semitone index of scale root
      semitone = WAV.semitone(match[1] + 4 + match[2]);

  // 2 notes per second
  for (let note = 0; note < length * 2; note++) {

        // choose 1 of 7 random chords
    let random = Math.random() * 7 | 0,
        // get number of half steps from scale root to chord root
        initial = gaps.reduce(
          (sum, half, index) => sum + (index < random) * half,
        0);

    // of every tetrad, maximum of 11 half steps from root to 4th is possible
    // tetrads composed of thirds, so add two gaps for every increment
    for (let current = initial; current - initial < 12; current += gaps[random++ % 7] + gaps[random++ % 7]) {

      // write note data as binary
      wav.writeNote({note: WAV.note(semitone + current), time: .5, amplitude: .25}, [], 1, current - initial < 9);
    }
  }

  // return blob of wav binary data
  return wav.toBlob();
}

This is a completely valid answer to the challenge, but is unfortunately non-competing because it relies on a utility which I have yet to officially publish. An older version is publicly available here but it does not have the really nice static functions WAV.semitone() and WAV.note() that my answer relies on.

For those of you who might ask "Why isn't new Audio(URL.createObjectURL(...)) part of your byte-count?", according to MDN:

A Blob object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system.

Since this function returns a Blob with MIME-type audio/wav, it qualifies as a "playable audio file" as requested in the specification for the challenge.

Demo:

f=
k=>l=>eval("for(m=/(.)(#?)(m?)/.exec(k),w=new WAV,g=m[3]?[2,1,2,2,1,3,1]:[2,2,1,2,2,2,1],s=WAV.semitone(m[1]+4+m[2]),n=0;n<l*2;n++)for(r=Math.random()*7|0,c=i=g.reduce((u,h,j)=>u+(j<r)*h,0);c-i<12;c+=g[r++%7]+g[r++%7])w.writeNote({note:WAV.note(s+c),time:.5,amplitude:.25},[],1,c-i<9);w.toBlob()")

h=
()=>new Audio(URL.createObjectURL(f(k.value)(l.value))).play()
<script src=//puu.sh/tbPuk/7f8ab9a987.js></script>
<input id=k value=B>
<input id=l value=1>
<button type=button onclick=h()>Play</button>

If you want to verify that all chords which can be randomly chosen are correct, here is a table of all possible chords below, given valid input:

function markup(key) {
  let match = /(.)(#?)(m?)/.exec(key),
      numerals = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'],
      gaps = match[3] ? [2,1,2,2,1,3,1] : [2,2,1,2,2,2,1],
      semitone = WAV.semitone(match[1] + 4 + match[2]),
      string = '  ' + key + ':\n';

  for (let root = 0; root < 7; root++) {

    let initial = gaps.reduce(
          (sum, half, index) => sum + (index < root) * half,
        0),
        index = root;

    string += '    ' + numerals[root] + ': ';

    for (let current = initial; current - initial < 12; current += gaps[index++ % 7] + gaps[index++ % 7]) {
      
      string += WAV.note(semitone + current) + ' ';
    }

    string += '\n';
  }

  string += '\n';

  return string;
}

let keys = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
let html = '<pre>';

html += 'Major Scales:\n';

keys.forEach(key => {
  html += markup(key);
});

html += 'Minor Scales:\n';

keys.forEach(key => {
  html += markup(key + 'm');
});

html += '</pre>';

document.write(html);
<script src=//puu.sh/tbPuk/7f8ab9a987.js></script>

\$\endgroup\$
  • 1
    \$\begingroup\$ Why is this noncompeting? \$\endgroup\$ – Conor O'Brien Jan 5 '17 at 13:15
  • \$\begingroup\$ @ConorO'Brien "...but is unfortunately non-competing because it relies on a utility which I have yet to officially publish." The dependency for it was technically written after the challenge was posted, which I was under the impression deemed the answer as unable to compete. \$\endgroup\$ – Patrick Roberts Jan 5 '17 at 18:03
  • \$\begingroup\$ Just to clarify, the updated library has been on my computer for a couple months, I just haven't uploaded the class until I wrote my answer because I've been working on cleaning the code and writing up documentation. \$\endgroup\$ – Patrick Roberts Jan 5 '17 at 21:55
  • \$\begingroup\$ @PatrickRoberts Would it be ok if i added this jsfiddle: jsfiddle.net/v098s9u6 \$\endgroup\$ – GracefulLemming Jan 5 '17 at 23:33
  • \$\begingroup\$ @Caleb that's kind of the point of the stack snippet I already have for the demo.. why do you want to add the jsfiddle? \$\endgroup\$ – Patrick Roberts Jan 6 '17 at 0:33

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