audio renderer v1

This commit is contained in:
riqo 2020-12-13 15:07:06 -06:00
commit 64ac22f8d9
4 changed files with 103 additions and 43 deletions

View file

@ -3,28 +3,45 @@
<g id="Group_126" data-name="Group 126" transform="translate(0 0)"> <g id="Group_126" data-name="Group 126" transform="translate(0 0)">
<rect id="Rectangle_478" data-name="Rectangle 478" :width="bubbleWidth" height="34" rx="10" transform="translate(0 0)" fill="#b9b9b9"/> <rect id="Rectangle_478" data-name="Rectangle 478" :width="bubbleWidth" height="34" rx="10" transform="translate(0 0)" fill="#b9b9b9"/>
<g id="Group_125" data-name="Group 125"> <g id="Group_125" data-name="Group 125">
<!-- <path id="Path_328" data-name="Path 328" d="M0,25v-10" transform="translate(10 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/>
<path v-for="(path, index) in paths"
:key="index"
:id="path.id"
:data-name="path.name"
:d="path.d"
:transform="path.transform"
fill="none"
stroke="#000"
stroke-linecap="round"
stroke-width="1.5"
/>
<!-- <path id="Path_328" data-name="Path 328" d="M0,25v-10" transform="translate(10 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/>
<path id="Path_328" data-name="Path 328" d="M0,25v-15" transform="translate(15 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/> <path id="Path_328" data-name="Path 328" d="M0,25v-15" transform="translate(15 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/>
<path id="Path_328" data-name="Path 328" d="M0,25v-20" transform="translate(20 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/> <path id="Path_328" data-name="Path 328" d="M0,25v-20" transform="translate(20 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/>
<path id="Path_328" data-name="Path 328" d="M0,25v-15" transform="translate(25 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/> <path id="Path_328" data-name="Path 328" d="M0,25v-15" transform="translate(25 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/>
<path id="Path_328" data-name="Path 328" d="M0,25v-10" transform="translate(30 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/> <path id="Path_328" data-name="Path 328" d="M0,25v-10" transform="translate(30 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/>
<path id="Path_328" data-name="Path 328" d="M0,25v-5" transform="translate(120 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/> --> <path id="Path_328" data-name="Path 328" d="M0,25v-5" transform="translate(120 0)" fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.5"/>
</g> --> </g>
</g> </g>
</svg> </svg>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from 'vue' import { defineComponent, computed, ref } from 'vue'
export default defineComponent ({ export default defineComponent ({
name: 'AudioInput', name: 'AudioInput',
props: { props: {
bubbleWidth: Number bubbleWidth: Number,
paths: Array
}, },
setup(props){ setup(props){
// need audioPath type/interface
const center = computed(() => { const center = computed(() => {
if (props.bubbleWidth) { if (props.bubbleWidth) {
return 175 - props.bubbleWidth / 2; return 175 - props.bubbleWidth / 2;

View file

@ -2,6 +2,7 @@
<audio-input <audio-input
v-if="renderAudio" v-if="renderAudio"
:bubbleWidth="reactiveWidth" :bubbleWidth="reactiveWidth"
:paths="paths"
/> />
<text-input <text-input
v-else v-else
@ -20,13 +21,19 @@ export default defineComponent({
components: {TextInput, AudioInput}, components: {TextInput, AudioInput},
setup() { setup() {
const { input, renderAudio, reactiveWidth, textInputXoffset } = useInputRenderer(); const {
input,
renderAudio,
reactiveWidth,
textInputXoffset,
paths } = useInputRenderer();
return { return {
input, input,
textInputXoffset, textInputXoffset,
renderAudio, renderAudio,
reactiveWidth reactiveWidth,
paths
} }
} }
}) })

View file

@ -10,17 +10,20 @@ export default function useInputRenderer() {
// program ref varaiables // program ref varaiables
const renderAudio = ref(false); const renderAudio = ref(false);
const reactiveWidth = ref(10); const reactiveWidth = ref(10);
const pathOffset = ref(10);
const paths = ref([]);
// get input feedback & handler function // get input feedback & handler function
const { keyDownHandler, input } = useKeyDownHandler(); const { keyDownHandler, input } = useKeyDownHandler();
// get text Offset and & textRender function // get text Offset and & textRender function
const { textInputXoffset, renderTextInput } = useTextInput(input, renderAudio); const { textInputXoffset, renderTextInput } = useTextInput(input, renderAudio);
// why do we have audio stuff here // why do we have audio stuff here ?
let stream: any; let stream: MediaStream;
const {handleStream} = useStreamRecorder(renderAudio) const {renderStream} = useStreamRecorder(renderAudio)
onMounted(async () => { onMounted(async () => {
stream = await useMediaStream() const streamRes = await useMediaStream()
stream = streamRes.stream;
}) })
// stop stream recording on space keyup event // stop stream recording on space keyup event
@ -30,6 +33,7 @@ export default function useInputRenderer() {
renderAudio.value = false; renderAudio.value = false;
input.value = ""; input.value = "";
reactiveWidth.value = 10; reactiveWidth.value = 10;
pathOffset.value = 10;
} }
} }
@ -54,7 +58,7 @@ export default function useInputRenderer() {
if (input === " " && renderAudio.value === false) { if (input === " " && renderAudio.value === false) {
console.log("record"); console.log("record");
renderAudio.value = true; renderAudio.value = true;
handleStream(stream.stream, reactiveWidth); renderStream(stream, reactiveWidth, pathOffset, paths);
return; return;
} }
renderTextInput(); renderTextInput();
@ -64,7 +68,8 @@ export default function useInputRenderer() {
input, input,
renderAudio, renderAudio,
reactiveWidth, reactiveWidth,
textInputXoffset textInputXoffset,
paths
} }
} }

View file

@ -1,23 +1,14 @@
import { watch, ref, inject } from "vue"; import {Ref} from '@/types/vueRef/index';
import useAudioRenderer from "@/composables/audioRenderer/useAudioRenderer"; import { ref } from 'vue';
import AnimeFunc from "@/types/animejs/index"; export default function useStreamRecorder(render: Ref<boolean>) {
export default function useStreamRecorder(render: any) {
let streamRecorder: MediaRecorder; let streamRecorder: MediaRecorder;
const chunks: Blob[] = []; const chunks: Blob[] = [];
// imports animejs safely // window.AudioContext = window.AudioContext;
let anime: AnimeFunc; const audioContext = new AudioContext();
const animeInject: AnimeFunc | undefined = inject("animejs"); const analyzer = audioContext.createAnalyser();
if (animeInject) anime = animeInject;
window.AudioContext = window.AudioContext;
let audio_context = new AudioContext();
const analyzer = audio_context.createAnalyser();
analyzer.fftSize = 2048; analyzer.fftSize = 2048;
var bufferLengthAlt = analyzer.frequencyBinCount; const dataArray = new Uint8Array(analyzer.frequencyBinCount);
var dataArrayAlt = new Uint8Array(bufferLengthAlt);
let source: any;
const initRecorder = async () => { const initRecorder = async () => {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
@ -32,36 +23,76 @@ export default function useStreamRecorder(render: any) {
} }
} }
function visualize(w: any) { interface AmplitudePath {
id: string;
name: string;
d: string;
transform: string;
}
function drawAlt() { const createAudioPath = (a: number, offset: number) => {
const identifier = `path_${offset}`
const path: AmplitudePath = {
id: identifier,
name: identifier,
d: `M0,25v${-a}`,
transform: `translate(${offset} 0)`
}
return path;
}
const xInitial = 24;
const xFinal = 640;
function visualize(w: Ref<number>, p: Ref<number>, paths: Ref<any>) {
const amplitude = () => {
const dataView = dataArray.slice(xInitial, xFinal);
const sum = dataView.reduce((a, b) => a + b);
const average = sum / dataView.length;
const scaled = average * 0.45;
return scaled;
}
function draw() {
if (!render.value) { if (!render.value) {
return; return;
} }
const drawVisual = requestAnimationFrame(drawAlt); requestAnimationFrame(draw);
analyzer.getByteTimeDomainData(dataArrayAlt); analyzer.getByteFrequencyData(dataArray);
// max-width // max-width
if (w.value === 190) { if (w.value === 300) {
return; return;
} }
// increase width and center as time passes // increase width as time passes
w.value++;
const center = 175 - w.value / 2; // get amplitude
// w.value= center; const a = amplitude();
if (a > 1) {
w.value++;
p.value += 5;
console.log(a)
const path = createAudioPath(a, p.value);
paths.value.push(path)
}
// create path item
} }
drawAlt(); draw();
} }
function handleStream(s: MediaStream, w: any) { function renderStream(s: MediaStream, w: Ref<number>, pathOffset: Ref<number>, paths: Ref<any>) {
source = audio_context.createMediaStreamSource(s); const source = audioContext.createMediaStreamSource(s);
source.connect(analyzer); source.connect(analyzer);
visualize(w); visualize(w, pathOffset, paths);
} }
return { return {
initRecorder, initRecorder,
handleStream renderStream
} }
} }