Skip to content

Commit

Permalink
Merge branch 'master' into add-audio-playback
Browse files Browse the repository at this point in the history
  • Loading branch information
whitep4nth3r authored Jun 9, 2020
2 parents 4509339 + 8f9e312 commit 71cba28
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
[attr.data-degree]="(fretMap | getFretFromFretMap: string : fret)?.degree"
[attr.data-display-note]="(fretMap | getFretFromFretMap: string : fret)?.displayName"
[attr.data-mode]="mode"
(click)="playbackService.playNote(stringName, fret)"
></div>
</ng-container>
</ng-template>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
left: 0;
right: 0;
transform: translatey(calc(50% - 1px));
opacity: .9;
cursor: pointer;
}

&:hover:after{
opacity: 1;
}

&:nth-child(-n + 13) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, Input } from '@angular/core';
import { ChordMap, FretMap, Mode, Scale } from '../../util/types';
import { NotePlaybackService } from '../playback/note-playback.service';

@Component({
selector: 'app-fretonator',
Expand All @@ -16,5 +17,7 @@ export class FretonatorComponent {
@Input() note: string;
@Input() noteExtenderString: string;

constructor(public playbackService: NotePlaybackService) { }

frets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { NotePlaybackService } from './note-playback.service';

describe('NotePlaybackService', () => {
let service: NotePlaybackService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(NotePlaybackService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Injectable } from '@angular/core';
import { StringFrequencies } from '../../util/constants';

const SYNTH_BUFFER_SIZE = 4096;
const SYNTH_PLAY_DURATION = 2000;

@Injectable({
providedIn: 'root'
})
export class NotePlaybackService {
private context: AudioContext;

constructor() {}

playNote(stringName, fret) {
if (!this.context) {
try {
// Feature sniff for web audio API
this.context = new (window.AudioContext || window['webkitAudioContext']);
} catch (e) {
// No browser support :(
}
}
if(this.context){
const noteFrequency = this.getFrequency(stringName, fret);
this.pluckString(noteFrequency);
}
}

private getFrequency(stringName, fret) {
// We're using stringName here, the case sensitive alt to string, to differentiate E/e strings.
const stringFrequency = StringFrequencies[stringName];
const fretCents = fret * 100;
return stringFrequency * Math.pow(2, (fretCents / 1200));
}

private pluckString(frequency: number) {
// Use Karplus-Strong algo to simply synth guitar-like sounds.
// https://ccrma.stanford.edu/~jos/pasp/Karplus_Strong_Algorithm.html
const processor = this.context.createScriptProcessor(SYNTH_BUFFER_SIZE, 0, 1);
const signalPeriod = Math.round(this.context.sampleRate / frequency);
const currentSample = new Float32Array(signalPeriod);
// Fill sample with random noise -1 through +1
this.fillWithNoise(currentSample, signalPeriod);
let n = 0;
processor.addEventListener('audioprocess', (e) => {
// Populate output buffer with signal
const outputBuffer = e.outputBuffer.getChannelData(0);
for (let i = 0; i < outputBuffer.length; i++) {
// Lowpass the signal by averaging it with the next point
currentSample[n] = (currentSample[n] + currentSample[(n + 1) % signalPeriod]) / 2;
// Copy output to the buffer, repeat
outputBuffer[i] = currentSample[n];
n = (n + 1) % signalPeriod;
}
});
// Filter the output
const bandpass = this.createBandpassFilter(frequency);
processor.connect(bandpass);
// Kill the processor after 2 seconds
setTimeout(() => {
bandpass.disconnect();
processor.disconnect();
}, SYNTH_PLAY_DURATION);
}

private fillWithNoise(sample, signalPeriod){
for (let i = 0; i < signalPeriod; i++) {
sample[i] = (2 * Math.random()) - 1;
}
}

private createBandpassFilter(frequency){
const bandpass = this.context.createBiquadFilter();
bandpass.type = "bandpass";
bandpass.frequency.value = Math.round(frequency);
bandpass.Q.value = 1 / 6;
bandpass.connect(this.context.destination);
return bandpass;
}
}
9 changes: 9 additions & 0 deletions apps/fretonator-web/src/app/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,12 @@ export const Enharmonics = [
['G#', 'A♭'],
['A#', 'B♭']
];

export const StringFrequencies = {
'e': 329.63,
'B': 246.94,
'G': 196.00,
'D': 146.83,
'A': 110.00,
'E': 82.41
}

0 comments on commit 71cba28

Please sign in to comment.