audio renderer v1
This commit is contained in:
parent
8be959a917
commit
64ac22f8d9
4 changed files with 103 additions and 43 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue