kopia lustrzana https://github.com/backface/turtlestitch
				
				
				
			added microphone note and pitch detection
							rodzic
							
								
									01ec1a7448
								
							
						
					
					
						commit
						8ff79b0d8c
					
				| 
						 | 
				
			
			@ -9,7 +9,7 @@
 | 
			
		|||
    * parse JSON using the SPLIT reporter
 | 
			
		||||
    * new "aspect AT location" reporter in Sensing category for sniffing colors and sprites
 | 
			
		||||
    * new blocks for setting and changing the stage's background color
 | 
			
		||||
    * new "microphone" reporter in Sensing for getting volume, signals and frequencies
 | 
			
		||||
    * new "microphone" reporter in Sensing for getting volume, note, pitch signals and frequencies
 | 
			
		||||
    * new "object" reporter in the Sensing category for getting a sprite by its name
 | 
			
		||||
    * blocks for changing and querying the "flat line ends" setting 
 | 
			
		||||
    * selectors for changing and querying "draggable" and "rotation style" settings
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +47,9 @@
 | 
			
		|||
    * Catalan, thanks, Joan!
 | 
			
		||||
    * German
 | 
			
		||||
 | 
			
		||||
### 2019-03-10
 | 
			
		||||
* Objects, Blocks, Threads: added microphone note and pitch detection
 | 
			
		||||
 | 
			
		||||
### 2019-03-07
 | 
			
		||||
* AudioComp lib: added block to set the microphone's buffer and fft sizes
 | 
			
		||||
* German translation update (microphone features)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,9 +6,9 @@
 | 
			
		|||
        <link rel="shortcut icon" href="src/favicon.ico">
 | 
			
		||||
        <script type="text/javascript" src="src/morphic.js?version=2019-02-07"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/widgets.js?version=2018-10-02"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/blocks.js?version=2019-03-06"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/threads.js?version=2019-03-06"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/objects.js?version=2019-03-07"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/blocks.js?version=2019-03-10"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/threads.js?version=2019-03-10"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/objects.js?version=2019-03-10"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/gui.js?version=2019-03-06"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/paint.js?version=2019-02-22"></script>
 | 
			
		||||
        <script type="text/javascript" src="src/lists.js?version=2019-02-07"></script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -148,7 +148,7 @@ CustomCommandBlockMorph, SymbolMorph, ToggleButtonMorph, DialMorph*/
 | 
			
		|||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.blocks = '2019-March-06';
 | 
			
		||||
modules.blocks = '2019-March-10';
 | 
			
		||||
 | 
			
		||||
var SyntaxElementMorph;
 | 
			
		||||
var BlockMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -989,6 +989,8 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
 | 
			
		|||
                false, // numeric?
 | 
			
		||||
                {
 | 
			
		||||
                    'volume' : ['volume'],
 | 
			
		||||
                    'note' : ['note'],
 | 
			
		||||
                    'pitch' : ['pitch'],
 | 
			
		||||
                    'signals' : ['signals'],
 | 
			
		||||
                    'frequencies' : ['frequencies']
 | 
			
		||||
                },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										136
									
								
								src/objects.js
								
								
								
								
							
							
						
						
									
										136
									
								
								src/objects.js
								
								
								
								
							| 
						 | 
				
			
			@ -84,7 +84,7 @@ BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
 | 
			
		|||
TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph,
 | 
			
		||||
AlignmentMorph, Process, XML_Element, VectorPaintEditorMorph*/
 | 
			
		||||
 | 
			
		||||
modules.objects = '2019-March-07';
 | 
			
		||||
modules.objects = '2019-March-10';
 | 
			
		||||
 | 
			
		||||
var SpriteMorph;
 | 
			
		||||
var StageMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -8857,13 +8857,12 @@ Note.prototype.stop = function () {
 | 
			
		|||
 | 
			
		||||
// Microphone /////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// I am a microphone and know about volume, signals and frequencies
 | 
			
		||||
// I am a microphone and know about volume, note, pitch, as well as
 | 
			
		||||
// signals and frequencies.
 | 
			
		||||
// mostly meant to be a singleton of the stage
 | 
			
		||||
// I stop when I'm not queried something for 5 seconds
 | 
			
		||||
// to free up system resources
 | 
			
		||||
 | 
			
		||||
// Microphone instance creation
 | 
			
		||||
 | 
			
		||||
function Microphone() {
 | 
			
		||||
    // web audio components:
 | 
			
		||||
    this.audioContext = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -8872,13 +8871,20 @@ function Microphone() {
 | 
			
		|||
    this.analyser = null;
 | 
			
		||||
 | 
			
		||||
    // parameters:
 | 
			
		||||
    this.signalBufferSize = 512;
 | 
			
		||||
    this.fftSize = 1024;
 | 
			
		||||
    this.signalBufferSize = 512; // should probably be 1024 by default
 | 
			
		||||
    this.fftSize = 1024; // should probably be 2048 by default
 | 
			
		||||
    this.MIN_SAMPLES = 0;  // will be initialized when AudioContext is created.
 | 
			
		||||
    this.GOOD_ENOUGH_CORRELATION = 0.9;
 | 
			
		||||
 | 
			
		||||
    // buffers:
 | 
			
		||||
    this.freqBuffer = null; // will be initialized to Uint8Array
 | 
			
		||||
    this.pitchBuffer = null; // will be initialized to Float32Array
 | 
			
		||||
 | 
			
		||||
    // metered values:
 | 
			
		||||
    this.volume = 0;
 | 
			
		||||
    this.signals = [];
 | 
			
		||||
    this.frequencies = [];
 | 
			
		||||
    this.pitch = -1;
 | 
			
		||||
 | 
			
		||||
    // asynch control:
 | 
			
		||||
    this.isStarted = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -8930,6 +8936,21 @@ Microphone.prototype.start = function () {
 | 
			
		|||
    }).catch(nop);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Microphone.prototype.stop = function () {
 | 
			
		||||
    this.processor.onaudioprocess = null;
 | 
			
		||||
    this.sourceStream.getTracks().forEach(function (track) {
 | 
			
		||||
        track.stop();}
 | 
			
		||||
    );
 | 
			
		||||
    this.processor.disconnect();
 | 
			
		||||
    this.analyser.disconnect();
 | 
			
		||||
    this.audioContext.close();
 | 
			
		||||
    this.processor = null;
 | 
			
		||||
    this.analyser = null;
 | 
			
		||||
    this.audioContext = null;
 | 
			
		||||
    this.isReady = false;
 | 
			
		||||
    this.isStarted = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Microphone.prototype.setupNodes = function (stream) {
 | 
			
		||||
    this.sourceStream = stream;
 | 
			
		||||
    this.createProcessor();
 | 
			
		||||
| 
						 | 
				
			
			@ -8941,8 +8962,12 @@ Microphone.prototype.setupNodes = function (stream) {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
Microphone.prototype.createAnalyser = function () {
 | 
			
		||||
    var freqBufLength;
 | 
			
		||||
    this.analyser = this.audioContext.createAnalyser();
 | 
			
		||||
    this.analyser.fftSize = this.fftSize;
 | 
			
		||||
    freqBufLength = this.analyser.frequencyBinCount;
 | 
			
		||||
    this.freqBuffer = new Uint8Array(freqBufLength);
 | 
			
		||||
    this.pitchBuffer = new Float32Array(freqBufLength);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Microphone.prototype.createProcessor = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -8966,18 +8991,21 @@ Microphone.prototype.stepAudio = function (event) {
 | 
			
		|||
    var buf = event.inputBuffer.getChannelData(0),
 | 
			
		||||
        bufLength = buf.length,
 | 
			
		||||
        sum = 0,
 | 
			
		||||
        x, i, rms,
 | 
			
		||||
        freqBufLength = this.analyser.frequencyBinCount,
 | 
			
		||||
        dataArray = new Uint8Array(freqBufLength);
 | 
			
		||||
        x, i, rms;
 | 
			
		||||
 | 
			
		||||
    if (this.isAutoStop && ((Date.now() - this.lastTime) > 5000)) {
 | 
			
		||||
        this.stop();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // signals:
 | 
			
		||||
    this.signals = buf;
 | 
			
		||||
    this.analyser.getByteFrequencyData(dataArray);
 | 
			
		||||
    this.frequencies = dataArray;
 | 
			
		||||
 | 
			
		||||
    // frequency bins:
 | 
			
		||||
    this.analyser.getByteFrequencyData(this.freqBuffer);
 | 
			
		||||
    this.frequencies = this.freqBuffer;
 | 
			
		||||
 | 
			
		||||
    // volume:
 | 
			
		||||
    for (i = 0; i < bufLength; i += 1) {
 | 
			
		||||
        x = buf[i];
 | 
			
		||||
        if (Math.abs(x) >= this.processor.clipLevel) {
 | 
			
		||||
| 
						 | 
				
			
			@ -8989,23 +9017,83 @@ Microphone.prototype.stepAudio = function (event) {
 | 
			
		|||
    rms =  Math.sqrt(sum / bufLength);
 | 
			
		||||
    this.volume = Math.max(rms, this.volume * this.processor.averaging);
 | 
			
		||||
 | 
			
		||||
    // pitch:
 | 
			
		||||
    this.analyser.getFloatTimeDomainData(this.pitchBuffer);
 | 
			
		||||
    this.pitch = this.detectPitch(
 | 
			
		||||
        this.pitchBuffer,
 | 
			
		||||
        this.audioContext.sampleRate
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // note:
 | 
			
		||||
    if (this.pitch > 0) {
 | 
			
		||||
        this.note = Math.round(
 | 
			
		||||
            12 * (Math.log(this.pitch / 440) / Math.log(2))
 | 
			
		||||
        ) + 69;
 | 
			
		||||
    } else {
 | 
			
		||||
        this.note = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.isReady = true;
 | 
			
		||||
    this.isStarted = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Microphone.prototype.stop = function () {
 | 
			
		||||
    this.processor.onaudioprocess = null;
 | 
			
		||||
    this.sourceStream.getTracks().forEach(function (track) {
 | 
			
		||||
        track.stop();}
 | 
			
		||||
    );
 | 
			
		||||
    this.processor.disconnect();
 | 
			
		||||
    this.analyser.disconnect();
 | 
			
		||||
    this.audioContext.close();
 | 
			
		||||
    this.processor = null;
 | 
			
		||||
    this.analyser = null;
 | 
			
		||||
    this.audioContext = null;
 | 
			
		||||
    this.isReady = false;
 | 
			
		||||
    this.isStarted = false;
 | 
			
		||||
Microphone.prototype.detectPitch = function (buf, sampleRate) {
 | 
			
		||||
    // https://en.wikipedia.org/wiki/Autocorrelation
 | 
			
		||||
    // thanks to Chris Wilson:
 | 
			
		||||
    // https://plus.google.com/+ChrisWilson/posts/9zHsF9PCDAL
 | 
			
		||||
    // https://github.com/cwilso/PitchDetect/
 | 
			
		||||
 | 
			
		||||
    var SIZE = buf.length,
 | 
			
		||||
        MAX_SAMPLES = Math.floor(SIZE/2),
 | 
			
		||||
        best_offset = -1,
 | 
			
		||||
        best_correlation = 0,
 | 
			
		||||
        rms = 0,
 | 
			
		||||
        foundGoodCorrelation = false,
 | 
			
		||||
        correlations = new Array(MAX_SAMPLES),
 | 
			
		||||
        correlation,
 | 
			
		||||
        lastCorrelation,
 | 
			
		||||
        offset,
 | 
			
		||||
        shift,
 | 
			
		||||
        i,
 | 
			
		||||
        val;
 | 
			
		||||
 | 
			
		||||
    for (i = 0; i < SIZE; i += 1) {
 | 
			
		||||
        val = buf[i];
 | 
			
		||||
        rms += val * val;
 | 
			
		||||
    }
 | 
			
		||||
    rms = Math.sqrt(rms/SIZE);
 | 
			
		||||
    if (rms < 0.01)
 | 
			
		||||
        return -1;
 | 
			
		||||
 | 
			
		||||
    lastCorrelation = 1;
 | 
			
		||||
    for (offset = this.MIN_SAMPLES; offset < MAX_SAMPLES; offset += 1) {
 | 
			
		||||
        correlation = 0;
 | 
			
		||||
 | 
			
		||||
        for (i = 0; i < MAX_SAMPLES; i += 1) {
 | 
			
		||||
            correlation += Math.abs((buf[i]) - (buf[i + offset]));
 | 
			
		||||
        }
 | 
			
		||||
        correlation = 1 - (correlation/MAX_SAMPLES);
 | 
			
		||||
        correlations[offset] = correlation;
 | 
			
		||||
        if ((correlation > this.GOOD_ENOUGH_CORRELATION)
 | 
			
		||||
            && (correlation > lastCorrelation)
 | 
			
		||||
        ) {
 | 
			
		||||
            foundGoodCorrelation = true;
 | 
			
		||||
            if (correlation > best_correlation) {
 | 
			
		||||
                best_correlation = correlation;
 | 
			
		||||
                best_offset = offset;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (foundGoodCorrelation) {
 | 
			
		||||
            shift = (correlations[best_offset + 1] -
 | 
			
		||||
                correlations[best_offset - 1]) /
 | 
			
		||||
                    correlations[best_offset];
 | 
			
		||||
            return sampleRate / (best_offset + (8 * shift));
 | 
			
		||||
        }
 | 
			
		||||
        lastCorrelation = correlation;
 | 
			
		||||
    }
 | 
			
		||||
    if (best_correlation > 0.01) {
 | 
			
		||||
        return sampleRate / best_offset;
 | 
			
		||||
    }
 | 
			
		||||
    return -1;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// CellMorph //////////////////////////////////////////////////////////
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,7 +62,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
 | 
			
		|||
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, Color,
 | 
			
		||||
TableFrameMorph, ColorSlotMorph, isSnapObject, Map*/
 | 
			
		||||
 | 
			
		||||
modules.threads = '2019-March-06';
 | 
			
		||||
modules.threads = '2019-March-10';
 | 
			
		||||
 | 
			
		||||
var ThreadManager;
 | 
			
		||||
var Process;
 | 
			
		||||
| 
						 | 
				
			
			@ -2251,12 +2251,16 @@ Process.prototype.doStopAllSounds = function () {
 | 
			
		|||
 | 
			
		||||
// Process audio input (interpolated)
 | 
			
		||||
 | 
			
		||||
Process.prototype.reportAudio = function (choice) { // +++
 | 
			
		||||
Process.prototype.reportAudio = function (choice) {
 | 
			
		||||
    var stage = this.blockReceiver().parentThatIsA(StageMorph);
 | 
			
		||||
    if (stage.microphone.isOn()) {
 | 
			
		||||
        switch (this.inputOption(choice)) {
 | 
			
		||||
        case 'volume':
 | 
			
		||||
            return stage.microphone.volume;
 | 
			
		||||
        case 'pitch':
 | 
			
		||||
            return stage.microphone.pitch;
 | 
			
		||||
        case 'note':
 | 
			
		||||
            return stage.microphone.note;
 | 
			
		||||
        case 'signals':
 | 
			
		||||
            return new List(stage.microphone.signals);
 | 
			
		||||
        case 'frequencies':
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue