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>
2Alright, what's the audio output library with the shortest function names... starts googling – SIGSTACKFAULT – 2017-01-04T15:26:37.990
@Blacksilver I live to torture golfers (; – GracefulLemming – 2017-01-04T18:03:43.693
If I chose to implement the correct chords instead of defaulting to no accidentals, would that still be a valid submission? – Patrick Roberts – 2017-01-04T19:54:03.123
@PatrickRoberts go for it! – GracefulLemming – 2017-01-04T19:54:36.860
2Using 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
, CminC Eb G
, FF A C
, GminG Bb D
EE G# B
, BB D# F#
, FF 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 saysmust conform with the key as much as possible
(which by the way is too vague.) – Level River St – 2017-01-04T20:16:05.7401@LevelRiverSt fixed. The limitation made more sense in my mind – GracefulLemming – 2017-01-04T20:18:59.103
OK, looking much better. One more thing: It's not clear to me why you've included both major and minor scales. You haven´t specified a way to differentiate between them in the input, so I would think it is best to stick to major. Cmaj and Amin (for example) form a pair with exactly the same notes. Otherwise you need to specify how to differentiate in the input. – Level River St – 2017-01-04T20:21:32.813
Finally, the audio file rule is OK, but I would like it if the program could be allowed to play the music directly :-) – Level River St – 2017-01-04T20:23:54.927
@LevelRiverSt I considered that, however I would like to be able to save samples in case something interesting occurs. With being played directly if some curious sequence happens there is no way I can replicate it. Also. Working on the input – GracefulLemming – 2017-01-04T20:32:51.260