play sound, play drum and audio effects experiments

This commit is contained in:
Eric Rosenbaum 2016-08-11 16:47:01 -04:00
parent 951d7771f3
commit 8c40e9c383
17 changed files with 280 additions and 32 deletions

View file

@ -248,17 +248,43 @@
<block type="looks_size"></block> <block type="looks_size"></block>
</category> </category>
<category name="Sound" colour="#D65CD6"> <category name="Sound" colour="#D65CD6">
<block type="sound_playsound"> <block type="sound_playsound">
<value name="SOUND_MENU"> <value name="SOUND_NUM">
<shadow type="sound_sounds_option"></shadow> <shadow type="sound_sounds_menu">
</shadow>
</value> </value>
</block> </block>
<block type="sound_playuntildone"> <block type="sound_playuntildone">
<value name="SOUND_MENU"> <value name="SOUND_NUM">
<shadow type="sound_sounds_option"></shadow> <shadow type="sound_sounds_menu">
</shadow>
</value> </value>
</block> </block>
<block type="sound_playwithpitch">
<value name="SOUND_NUM">
<shadow type="sound_sounds_menu">
</shadow>
</value>
<value name="PITCH">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
<block type="sound_stopallsounds"></block> <block type="sound_stopallsounds"></block>
<block type="sound_playdrum">
<value name="DRUMTYPE">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
<block type="sound_playdrumforbeats"> <block type="sound_playdrumforbeats">
<value name="DRUMTYPE"> <value name="DRUMTYPE">
<shadow type="math_number"> <shadow type="math_number">
@ -266,11 +292,11 @@
</shadow> </shadow>
</value> </value>
<value name="BEATS"> <value name="BEATS">
<shadow type="math_number"> <shadow type="sound_beats_menu">
<field name="NUM">0.25</field>
</shadow> </shadow>
</value> </value>
</block> </block>
<block type="sound_restforbeats"> <block type="sound_restforbeats">
<value name="BEATS"> <value name="BEATS">
<shadow type="math_number"> <shadow type="math_number">
@ -279,6 +305,14 @@
</value> </value>
</block> </block>
<block type="sound_playnote">
<value name="NOTE">
<shadow type="math_number">
<field name="NUM">1</field>
</shadow>
</value>
</block>
<block type="sound_playnoteforbeats"> <block type="sound_playnoteforbeats">
<value name="NOTE"> <value name="NOTE">
<shadow type="math_number"> <shadow type="math_number">
@ -291,17 +325,41 @@
</value> </value>
</block> </block>
<block type="sound_setkey">
<value name="ROOT"> <block type="sound_setkey">
<shadow type="sound_roots_menu"> <value name="ROOT">
</shadow> <shadow type="sound_roots_menu">
</value> </shadow>
<value name="SCALE"> </value>
<shadow type="sound_scales_menu"> <value name="SCALE">
</shadow> <shadow type="sound_scales_menu">
</value> </shadow>
</block> </value>
</block>
<block type="sound_seteffectto">
<value name="EFFECT">
<shadow type="sound_effects_menu"></shadow>
</value>
<value name="VALUE">
<shadow type="math_number">
<field name="NUM">100</field>
</shadow>
</value>
</block>
<block type="sound_changeeffectby">
<value name="EFFECT">
<shadow type="sound_effects_menu"></shadow>
</value>
<value name="VALUE">
<shadow type="math_number">
<field name="NUM">10</field>
</shadow>
</value>
</block>
<block type="sound_cleareffects"></block>
<block type="sound_setinstrumentto"> <block type="sound_setinstrumentto">
<value name="INSTRUMENT"> <value name="INSTRUMENT">

BIN
playground/sounds/boing.mp3 Normal file

Binary file not shown.

BIN
playground/sounds/cave.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
playground/sounds/eggs.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
playground/sounds/meow.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
playground/sounds/zoop.mp3 Normal file

Binary file not shown.

View file

@ -13,20 +13,38 @@ function Scratch3SoundBlocks(runtime) {
Scratch3SoundBlocks.prototype.getPrimitives = function() { Scratch3SoundBlocks.prototype.getPrimitives = function() {
return { return {
'sound_playsound': this.playSound, 'sound_playsound': this.playSound,
'sound_playnoteforbeats': this.playNote, 'sound_playwithpitch': this.playSoundWithPitch,
'sound_stopallsounds': this.stopAllSounds,
'sound_playnote': this.playNote,
'sound_playnoteforbeats': this.playNoteForBeats,
'sound_playdrum': this.playDrum,
'sound_playdrumforbeats': this.playDrumForBeats,
'sound_setkey' : this.setKey, 'sound_setkey' : this.setKey,
'sound_seteffectto' : this.setEffect,
'sound_changeeffectby' : this.changeEffect,
'sound_cleareffects' : this.clearEffects,
'sound_scales_menu' : this.scalesMenu, 'sound_scales_menu' : this.scalesMenu,
'sound_sounds_menu' : this.soundsMenu,
'sound_roots_menu' : this.rootsMenu, 'sound_roots_menu' : this.rootsMenu,
'sound_beats_menu' : this.beatsMenu, 'sound_beats_menu' : this.beatsMenu,
'sound_effects_menu' : this.effectsMenu,
}; };
}; };
Scratch3SoundBlocks.prototype.playSound = function (args, util) { Scratch3SoundBlocks.prototype.playSound = function (args, util) {
self.postMessage({method: 'beep'}); self.postMessage({method: 'playsound', soundnum:args.SOUND_NUM});
}; };
Scratch3SoundBlocks.prototype.playNote = function (args, util) { Scratch3SoundBlocks.prototype.playSoundWithPitch = function (args, util) {
self.postMessage({method: 'playnote', note:args.NOTE, beats:args.BEATS}); self.postMessage({method: 'playsoundwithpitch', soundnum:args.SOUND_NUM, pitch:args.PITCH});
};
Scratch3SoundBlocks.prototype.stopAllSounds = function (args, util) {
self.postMessage({method: 'stopallsounds'});
};
Scratch3SoundBlocks.prototype.playNoteForBeats = function (args, util) {
self.postMessage({method: 'playnoteforbeats', note:args.NOTE, beats:args.BEATS});
return new Promise(function(resolve) { return new Promise(function(resolve) {
setTimeout(function() { setTimeout(function() {
resolve(); resolve();
@ -34,10 +52,43 @@ Scratch3SoundBlocks.prototype.playNote = function (args, util) {
}); });
}; };
Scratch3SoundBlocks.prototype.playNote = function (args, util) {
self.postMessage({method: 'playnote', note:args.NOTE});
};
Scratch3SoundBlocks.prototype.playDrumForBeats = function (args, util) {
self.postMessage({method: 'playdrumforbeats', drum:args.DRUMTYPE, beats:args.BEATS});
return new Promise(function(resolve) {
setTimeout(function() {
resolve();
}, 1000 * args.BEATS);
});
};
Scratch3SoundBlocks.prototype.playDrum = function (args, util) {
self.postMessage({method: 'playdrum', drum:args.DRUMTYPE});
};
Scratch3SoundBlocks.prototype.setKey = function (args, util) { Scratch3SoundBlocks.prototype.setKey = function (args, util) {
self.postMessage({method: 'setkey', root:args.ROOT, scale:args.SCALE}); self.postMessage({method: 'setkey', root:args.ROOT, scale:args.SCALE});
}; };
Scratch3SoundBlocks.prototype.setEffect = function (args, util) {
self.postMessage({method: 'seteffect', effect:args.EFFECT, value:args.VALUE});
};
Scratch3SoundBlocks.prototype.changeEffect = function (args, util) {
self.postMessage({method: 'changeeffect', effect:args.EFFECT, value:args.VALUE});
};
Scratch3SoundBlocks.prototype.clearEffects = function (args, util) {
self.postMessage({method: 'cleareffects'});
};
Scratch3SoundBlocks.prototype.soundsMenu = function (args, util) {
return args.SOUND_MENU;
};
Scratch3SoundBlocks.prototype.scalesMenu = function (args, util) { Scratch3SoundBlocks.prototype.scalesMenu = function (args, util) {
return args.SCALE; return args.SCALE;
}; };
@ -50,4 +101,8 @@ Scratch3SoundBlocks.prototype.beatsMenu = function (args, util) {
return args.BEATS; return args.BEATS;
}; };
Scratch3SoundBlocks.prototype.effectsMenu = function (args, util) {
return args.EFFECT;
};
module.exports = Scratch3SoundBlocks; module.exports = Scratch3SoundBlocks;

View file

@ -13,10 +13,72 @@ function VirtualMachine () {
instance.vmWorker = new Worker('../vm.js'); instance.vmWorker = new Worker('../vm.js');
// MUSIC STUFF by ericr // MUSIC STUFF by ericr
var options = {modulationEnvelope:{attack:0.1}};
// tone setup
var tone = new Tone();
// effects
var delay = new Tone.FeedbackDelay(0.25, 0.5);
delay.wet.value = 0;
var pitchShift = new Tone.PitchShift();
var panner = new Tone.Panner(0);
var reverb = new Tone.Freeverb();
reverb.wet.value = 0;
Tone.Master.chain(delay, pitchShift, panner, reverb);
// synth setup for play note block
var synth = new Tone.PolySynth(6, Tone.Synth).toMaster(); var synth = new Tone.PolySynth(6, Tone.Synth).toMaster();
var tone = new Tone();
// drum sounds
var drumFileNames = ['high_conga', 'small_cowbell', 'snare_drum', 'splash cymbal'];
var drumSamplers = loadSoundFiles(drumFileNames);
// sounds
var soundFileNames = ['meow', 'boing', 'cave', 'drip_drop', 'drum_machine', 'eggs', 'zoop'];
var soundSamplers = loadSoundFiles(soundFileNames);
// polyphonic samplers
function loadSoundFiles(filenames) {
var samplers = [];
for (var name of filenames) {
var myVoices = [];
for (var i=0; i<6; i++) {
var p = new Tone.Sampler('sounds/' + name + '.mp3').toMaster();
myVoices.push(p);
}
var polySampler = {
voices : myVoices,
currentVoice : 0,
nextVoice : function() {return this.voices[this.currentVoice++ % this.voices.length];},
stopAllVoices : function() {for (var i=0;i<this.voices.length;i++) {this.voices[i].triggerRelease()}},
};
samplers.push(polySampler);
}
return samplers;
}
// should fire once all sounds are loaded - seems to not work
Tone.Buffer.onload = function() {
console.log('loaded audio samples');
};
// scales and keys data
var scales = { var scales = {
'MAJOR' : [0,2,4,5,7,9,11], 'MAJOR' : [0,2,4,5,7,9,11],
'MINOR' : [0,2,3,5,7,8,10], 'MINOR' : [0,2,3,5,7,8,10],
@ -27,7 +89,8 @@ function VirtualMachine () {
var currentScale = scales['MAJOR']; var currentScale = scales['MAJOR'];
var rootNote = 60; var rootNote = 60;
function scaleNoteToMidiNote(scaleNote, scale, root) {
function scaleNoteToMidiNote(scaleNote, scale, root) {
var scaleIndex = (Math.round(scaleNote) - 1) % scale.length; var scaleIndex = (Math.round(scaleNote) - 1) % scale.length;
if (scaleIndex < 0) { if (scaleIndex < 0) {
scaleIndex += scale.length; scaleIndex += scale.length;
@ -42,17 +105,89 @@ function VirtualMachine () {
return freq; return freq;
} }
function clamp(input, min, max) {
return Math.min(Math.max(input, min), max);
};
function playNoteForBeats(note, beats) {
var midiNote = scaleNoteToMidiNote(note, currentScale, rootNote);
var freq = midiToFreq(midiNote);
synth.triggerAttackRelease(freq, beats);
}
// onmessage calls are converted into emitted events. // onmessage calls are converted into emitted events.
instance.vmWorker.onmessage = function (e) { instance.vmWorker.onmessage = function (e) {
if (e.data.method == 'playnote') { switch (e.data.method) {
var midiNote = scaleNoteToMidiNote(e.data.note, currentScale, rootNote); case 'playsound':
var freq = midiToFreq(midiNote); soundSamplers[e.data.soundnum].nextVoice().triggerAttack();
synth.triggerAttackRelease(freq, e.data.beats); break;
} case 'playsoundwithpitch':
if (e.data.method == 'setkey') { soundSamplers[e.data.soundnum].nextVoice().triggerAttack(e.data.pitch);
rootNote = e.data.root + 60; break;
currentScale = scales[e.data.scale]; case 'playnoteforbeats':
} playNoteForBeats(e.data.note, e.data.beats);
break;
case 'playnote':
playNoteForBeats(e.data.note, 0.25);
break;
case 'setkey':
rootNote = parseInt(e.data.root) + 60;
currentScale = scales[e.data.scale];
break;
case 'playdrumforbeats':
case 'playdrum':
var drumNum = e.data.drum - 1; // one-indexing
drumSamplers[drumNum].nextVoice().triggerAttack();
break;
case 'seteffect' :
switch (e.data.effect) {
case 'ECHO':
delay.wet.value = (e.data.value / 100) / 2; // max 50% wet (need dry signal too)
break;
case 'PANNER':
panner.pan.value = e.data.value / 100;
break;
case 'REVERB':
reverb.wet.value = e.data.value / 100;
break;
case 'PITCH':
pitchShift.pitch = e.data.value;
break;
}
break;
case 'changeeffect' :
switch (e.data.effect) {
case 'ECHO':
delay.wet.value += (e.data.value / 100) / 2; // max 50% wet (need dry signal too)
delay.wet.value = clamp(delay.wet.value, 0, 0.5);
break;
case 'PANNER':
panner.pan.value += e.data.value / 100;
panner.pan.value = clamp(panner.pan.value, -1, 1);
break;
case 'REVERB':
reverb.wet.value += e.data.value / 100;
reverb.wet.value = clamp(reverb.wet.value, 0, 1);
break;
case 'PITCH':
pitchShift.pitch += e.data.value;
break;
}
break;
case 'cleareffects' :
delay.wet.value = 0;
panner.pan.value = 0;
reverb.wet.value = 0;
pitchShift.pitch = 0;
break;
case 'stopallsounds' :
// stop sounds from the play sound block
// to do: stop sounds from play note and play drum blocks
for (var i=0; i<soundSamplers.length; i++) {
soundSamplers[i].stopAllVoices();
}
break;
}
instance.emit(e.data.method, e.data); instance.emit(e.data.method, e.data);
}; };