# Compose a Song! [closed]

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.

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

• Alright, what's the audio output library with the shortest function names... starts googling – SIGSTACKFAULT Jan 4 '17 at 15:26
• @Blacksilver I live to torture golfers (; – GracefulLemming Jan 4 '17 at 18:03
• If I chose to implement the correct chords instead of defaulting to no accidentals, would that still be a valid submission? – Patrick Roberts Jan 4 '17 at 19:54
• 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.) – Level River St Jan 4 '17 at 20:16
• @LevelRiverSt fixed. The limitation made more sense in my mind – GracefulLemming Jan 4 '17 at 20:18

# JavaScript (ES6) + synth-js, 303 bytes

k=>l=>eval("for(m=/(.)(#?)(m?)/.exec(k),W=synth.WAV,w=new W,g=m[3]?[2,1,2,2,1,3,1]:[2,2,1,2,2,2,1],s=W.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:W.note(s+c),time:.5,amplitude:.25},[],1,c-i<9);w.toBlob()")


### De-obfuscated:

(key) => (length) => {
let match = /(.)(#?)(m?)/.exec(key),
W = synth.WAV,
wav = new W,
// 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 = W.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: W.note(semitone + current), time: .5, amplitude: .25}, [], 1, current - initial < 9);
}
}

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


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=synth.WAV,w=new W,g=m[3]?[2,1,2,2,1,3,1]:[2,2,1,2,2,2,1],s=W.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:W.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=//unpkg.com/synth-js/dst/synth.min.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 = synth.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 += synth.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=//unpkg.com/synth-js/dst/synth.min.js></script>

• Why is this noncompeting? – Conor O'Brien Jan 5 '17 at 13:15
• @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. – Patrick Roberts Jan 5 '17 at 18:03
• 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. – Patrick Roberts Jan 5 '17 at 21:55
• @PatrickRoberts Would it be ok if i added this jsfiddle: jsfiddle.net/v098s9u6 – GracefulLemming Jan 5 '17 at 23:33
• @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? – Patrick Roberts Jan 6 '17 at 0:33