const isDevelopment = process.env.NODE_ENV !== "production";
import * as path from "path";
const fs = require('fs');
-
+import WebSocket from "ws";
const portAudio = require('naudiodon');
+console.log(portAudio.getDevices());
// Create an instance of AudioIO with inOptions (defaults are as below), which will return a ReadableStream
const audioOptions = {
closeOnError: true // Close the stream if an audio error is detected, if set false then just log the error
}
+// Create an instance of AudioIO with outOptions (defaults are as below), which will return a WritableStream
+const ao = new portAudio.AudioIO({
+ outOptions: audioOptions
+});
+
let ai = new portAudio.AudioIO({
inOptions: audioOptions
});
-import WebSocket from "ws";
+const filename = "rawAudio.wav";
+// Create a write stream to write out to a raw audio file
+const writeStream = fs.createWriteStream(filename);
+ai.pipe(writeStream);
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win: BrowserWindow | null;
ipcMain.on('update-recorder', (Event: any, record: boolean) => {
if (record) {
ai.start();
- ai.on('data', (buf: Buffer) => {
- const base64Data = buf.toString('base64');
- audioData += base64Data;
- });
+ // ai.on('data', (buf: Buffer) => {
+ // const base64Data = buf.toString('base64');
+ // audioData += base64Data;
+ // });
} else {
ai.quit();
+
+ const audio = {
+ content: fs.readFileSync(filename).toString('base64'),
+ }
const message = {
text: "",
- audio: audioData
+ audio
}
ws.send(JSON.stringify(message))
ai = new portAudio.AudioIO({
+++ /dev/null
-<template>
- <defs>
- <linearGradient
- id="linear-gradient"
- x1="0.5"
- y1="-1.438"
- x2="0.5"
- y2="0.633"
- gradientUnits="objectBoundingBox"
- >
- <stop offset="0" stop-color="#fff33b" stop-opacity="0" />
- <stop offset="0.152" stop-color="#ffde46" stop-opacity="0.141" />
- <stop offset="0.467" stop-color="#ffa764" stop-opacity="0.435" />
- <stop offset="0.581" stop-color="#ff916f" stop-opacity="0.541" />
- <stop offset="1" stop-color="#ff5782" />
- </linearGradient>
- <linearGradient
- id="linear-gradient-2"
- x1="0.498"
- y1="0.728"
- x2="0.498"
- y2="-0.433"
- gradientUnits="objectBoundingBox"
- >
- <stop offset="0" stop-color="#f7cf83" />
- <stop offset="1" stop-color="#fff" />
- </linearGradient>
- <rect id="rect" width="29" height="29" rx="10" />
- <clipPath id="clip">
- <use xlink:href="#rect" />
- </clipPath>
- </defs>
-
- <g id="titlebar" transform="translate(0, 0)">
- <g data-name="Group 73" transform="translate(20 20)">
- <ellipse
- id="Oval"
- cx="8.134"
- cy="8.134"
- rx="8.134"
- ry="8.134"
- transform="translate(0 0)"
- fill="url(#linear-gradient)"
- />
- <ellipse
- id="Oval-2"
- data-name="Oval"
- cx="8.134"
- cy="8.134"
- rx="8.134"
- ry="8.134"
- transform="translate(27 0)"
- fill="url(#linear-gradient-2)"
- />
- </g>
- </g>
-</template>
-
-<script lang="ts">
-import { defineComponent } from "vue";
-export default defineComponent({
- name: "TitleBar",
-});
-</script>
+++ /dev/null
-<template>
- <audio-input v-if="visualizeStream" :bubbleWidth="audioBubbleWidth" :paths="paths" />
- <text-input v-else :input="input" :textXoffset="textInputXoffset" />
-</template>
-
-<script lang="ts">
-import { defineComponent } from "vue";
-import TextInput from "./inputDetails/textInput.vue";
-import AudioInput from "./inputDetails/audioInput.vue";
-import useInputController from "@/core/UI/input/useInputController";
-export default defineComponent({
- name: "InputItem",
- components: { TextInput, AudioInput },
- setup() {
- const {
- input,
- visualizeStream,
- textInputXoffset,
- audioBubbleWidth,
- paths,
- } = useInputController();
-
- return {
- input,
- textInputXoffset,
- visualizeStream,
- audioBubbleWidth,
- paths,
- };
- },
-});
-</script>
--- /dev/null
+<template>
+ <audio-stream v-if="visualizeStream" :bubbleWidth="audioBubbleWidth" :paths="paths" />
+ <text-input v-else :input="input" :textXoffset="textInputXoffset" />
+</template>
+
+<script lang="ts">
+// input child components
+import TextInput from "./visualizerDetails/text.vue";
+import AudioStream from "./visualizerDetails/audio.vue";
+
+import useInputVisualizer from "./visualizer";
+
+import { defineComponent } from "vue";
+export default defineComponent({
+ name: "InputVisualizer",
+ components: { TextInput, AudioStream },
+ setup() {
+ // get text input
+
+ // get audio stream
+
+ // use Input Handler
+
+ const {
+ input,
+ visualizeStream,
+ textInputXoffset,
+ audioBubbleWidth,
+ paths,
+ } = useInputVisualizer();
+
+ return {
+ input,
+ textInputXoffset,
+ visualizeStream,
+ audioBubbleWidth,
+ paths,
+ };
+ },
+});
+</script>
--- /dev/null
+import { ref, watch, onUnmounted, onMounted } from "vue";
+
+import useKeyDownHandler from "@/composables/keyDownHandler/useKeyDownHandler";
+
+import useMediaStream from "@/utils/mediaStream/useMediaStream";
+
+import useStreamVisualizer from './visualizerDetails/audioVisualizer';
+
+import useTextVisualizer from './visualizerDetails/textVisualizer';
+
+/*
+* Input visualizer
+* Will visualize text input by default as user types or
+* if user holds space bar down, input audio stream
+* will be visualized & recorded
+*/
+// NOTE: input params should probably be input and MediaStream
+export default function useInputVisualizer() {
+ const visualizeStream = ref(false);
+ const paths = ref([]);
+ const audioBubbleWidth = ref(10);
+ let stream: MediaStream;
+
+ const { keyDownHandler, input } = useKeyDownHandler();
+ const { textInputXoffset, renderTextInput } = useTextVisualizer(input);
+ const { visualizeStreamAsPaths, expandBubble } = useStreamVisualizer(visualizeStream);
+
+ // stops stream recording on space key up
+ function keyupHandler(e: any) {
+ if (e.key === " " && visualizeStream.value) {
+ console.log("stop recording");
+
+ // window.postMessage({
+ // myTypeField: 'update-recorder',
+ // record: false
+ // }, '*')
+
+ visualizeStream.value = false;
+ paths.value = []
+ audioBubbleWidth.value = 10;
+ input.value = ''
+ }
+ }
+
+ // add window event keyboard listeners
+ window.addEventListener("keydown", keyDownHandler);
+ window.addEventListener('keyup', keyupHandler);
+
+ // get stream & initialize recorder object on mount
+ onMounted(async () => {
+ stream = await useMediaStream();
+ })
+
+ // determine whether to render text or audio based one the keyboard input
+ watch(input, (input, prevInput) => {
+ if (prevInput !== "" || prevInput.length > 0) {
+ if (!visualizeStream.value) {
+ renderTextInput();
+ }
+ return;
+ }
+ if (input === " " && visualizeStream.value === false) {
+ console.log("record MediaStream");
+ visualizeStream.value = true;
+
+ // window.postMessage({
+ // myTypeField: 'update-recorder',
+ // record: true
+ // }, '*')
+
+ expandBubble(audioBubbleWidth);
+ visualizeStreamAsPaths(stream, paths, audioBubbleWidth);
+ return;
+ }
+ renderTextInput();
+ });
+
+ // remove Event Listeners on component unMount
+ onUnmounted(() => {
+ window.removeEventListener("keydown", keyDownHandler);
+ window.removeEventListener("keyup", keyupHandler);
+ });
+
+ return {
+ input,
+ visualizeStream,
+ textInputXoffset,
+ audioBubbleWidth,
+ paths
+ }
+
+}
<template>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- :width="bubbleWidth"
- height="34"
- :x="center"
- y="440"
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ :width="bubbleWidth"
+ height="34"
+ :x="center"
+ y="440"
rx="10"
class="audioInput"
>
- <g
- id="Group_126"
- data-name="Group 126"
- transform="translate(0 0)"
- rx="10"
- >
+ <g id="Group_126" data-name="Group 126" transform="translate(0 0)" rx="10">
<rect
id="Rectangle_478"
data-name="Rectangle 478"
fill="#b9b9b9"
/>
<svg>
- <g rx="20" id="Group_125" data-name="Group 125" v-for="(path, index) in paths">
- <path
- :key="index"
- id="path"
- :data-name="path.identifier"
- :d="path.d"
- :transform="`translate(${path.offset} 0)`"
- fill="none"
- :stroke="path.color"
- stroke-linecap="round"
- stroke-width="1.5"
- />
- </g>
+ <g rx="20" id="Group_125" data-name="Group 125" v-for="(path, index) in paths">
+ <path
+ :key="index"
+ id="path"
+ :data-name="path.identifier"
+ :d="path.d"
+ :transform="`translate(${path.offset} 0)`"
+ fill="none"
+ :stroke="path.color"
+ stroke-linecap="round"
+ stroke-width="1.5"
+ />
+ </g>
</svg>
</g>
</svg>
<script lang="ts">
import { defineComponent, computed, inject } from "vue";
export default defineComponent({
- name: "AudioInput",
+ name: "AudioStream",
props: {
bubbleWidth: Number,
paths: Array,
},
setup(props) {
-
const emitter: any = inject("mitt");
- function audioSend() {
-
- }
+ function audioSend() {}
const center = computed(() => {
if (props.bubbleWidth) {
/*
* Draws MediaStream audioTrack as 'AudioPath' objects
*/
- function drawStreamAs(paths: Ref<any>, bubbleWidth: Ref<number>): void {
+ function drawStream(paths: Ref<any>, bubbleWidth: Ref<number>): void {
const xInitial = ref(10);
const maxAmplitude = 15;
const maxBubbleWidth = 300;
opacity: 0,
duration: 0
})
- }
+ }
}
}
paths.value.push(p);
}
- paths.value = [];
+ paths.value = [];
function drawPathStream(): void {
let streamPath;
* To Draw pathStream
*/
if (bubbleWidth.value === 300) {
- xInitial.value = bubbleWidth.value - 10;
+ xInitial.value = bubbleWidth.value - 10;
} else if (bubbleWidth.value > 20) {
- xInitial.value = bubbleWidth.value;
+ xInitial.value = bubbleWidth.value;
} else {
- xInitial.value +=3;
+ xInitial.value += 3;
}
streamPath = setTimeout(async () => {
const pathList = document.querySelectorAll('path');
drawNewPath();
if (bubbleWidth.value === maxBubbleWidth) {
shiftPathStream(pathList);
- }
- }, 200)
+ }
+ }, 200)
}
drawPathStream();
}
return;
}
animation = requestAnimationFrame(increase);
- if (bubbleRef.value < 300) {
- bubbleRef.value++;
- }
+ if (bubbleRef.value < 300) {
+ bubbleRef.value++;
+ }
}
increase();
}
function visualizeStreamAsPaths(s: MediaStream, paths: Ref<any>, bubbleWidth: Ref<number>): void {
const source = audioContext.createMediaStreamSource(s);
source.connect(analyzer);
- drawStreamAs(paths, bubbleWidth);
+ drawStream(paths, bubbleWidth);
}
return {
<template>
- <foreignObject
- class="inputContainer"
- :x="textXoffset"
- y="450"
- width="55%"
- height="50%"
- >
+ <foreignObject class="inputContainer" :x="textXoffset" y="450" width="55%" height="50%">
<div xmlns="http://www.w3.org/1999/xhtml">
<div class="inputBubble" contenteditable v-show="input.length > 0">
<p>{{ input }}</p>
</template>
<script lang="ts">
-import { defineComponent, watch, onMounted, ref } from "vue";
+import { defineComponent } from "vue";
export default defineComponent({
name: "TextInput",
props: {
input: String,
- textXoffset: String
+ textXoffset: String,
},
});
</script>
--- /dev/null
+import AnimeFunc from "@/types/animejs/index";
+import { inject } from "vue";
+import { Ref } from "@/types/vueRef/index";
+/*
+ * Anime js animations used by input textController
+*/
+export default function useTextInputAnims() {
+
+ // imports animejs safely
+ let anime: AnimeFunc;
+ const animeInject: AnimeFunc | undefined = inject("animejs");
+ if (animeInject) anime = animeInject;
+
+ const animateSend = (el: HTMLElement, elHeight: number, input: Ref<string>) => {
+ anime({
+ targets: el,
+ translateY: -elHeight + 15,
+ duration: 200,
+ easing: "easeInQuad",
+ complete: function() {
+ anime({
+ targets: el,
+ translateY: 50,
+ duration: 200,
+ easing: "easeOutQuad",
+ complete: function() {
+ input.value = "";
+ }
+ })
+ }
+ })
+ }
+
+ const inputAppear = (el: HTMLElement) => {
+ anime({
+ targets: el,
+ translateY: -10,
+ duration: 0
+ });
+ }
+
+ const verticalShiftInput = (el: HTMLElement, yTrans: number) => {
+ anime({
+ targets: el,
+ translateY: yTrans,
+ easing: "easeOutQuad",
+ duration: 150,
+ });
+ }
+
+ return {
+ animateSend,
+ verticalShiftInput,
+ inputAppear
+ }
+
+}
--- /dev/null
+import { ref, computed, inject } from 'vue';
+import useTextInputAnims from "./textAnime";
+import { Ref } from "@/types/vueRef/index";
+/*
+ * text rendering logic used by inputController
+*/
+export default function useTextVisualizer(input: Ref<string>) {
+ const inputHeight = ref(42);
+ const inputWidth = ref(0);
+
+ const emitter: any = inject("mitt");
+ const { animateSend, verticalShiftInput, inputAppear } = useTextInputAnims();
+
+ async function renderTextInput() {
+ // first, get input dimensions
+ const inputDivs = await document.getElementsByClassName("inputBubble");
+ const height = inputDivs[0].getBoundingClientRect().height;
+ inputWidth.value = inputDivs[0].getBoundingClientRect().width;
+ // then, query for foreignObject div
+ const foreignObjectDiv = document.getElementsByClassName(
+ "inputContainer"
+ );
+ const foreignEl = foreignObjectDiv[0] as HTMLElement;
+ if (height !== inputHeight.value) {
+ inputHeight.value = height;
+ }
+ if (inputHeight.value === 0) {
+ inputAppear(foreignEl)
+ } else if (inputHeight.value > 36) {
+ const yTrans = -inputHeight.value + 30;
+ verticalShiftInput(foreignEl, yTrans);
+ }
+ }
+
+ emitter.on("newSelfMessage", async (payload: any) => {
+ const inputEl = document.getElementsByClassName("inputContainer");
+ const foreignEl = inputEl[0] as HTMLElement;
+ animateSend(foreignEl, inputHeight.value, input);
+ });
+
+ const textInputXoffset = computed(() => {
+ return `calc(50% - ${inputWidth.value / 2})`;
+ });
+
+ return {
+ textInputXoffset,
+ renderTextInput
+ }
+}
xmlns:xlink="http://www.w3.org/1999/xlink"
@click="emittMessage"
>
- <!-- <AI /> -->
- <MessageRenderer />
- <InputItem />
- <!-- <TitleBar /> -->
+ <MessageVisualizer />
+ <InputVisualizer />
</svg>
</template>
<script lang="ts">
-import AI from "../AI/index.vue";
-import MessageRenderer from "./UIDetails/messageRenderer.vue";
-import InputItem from "@/components/input/inputItem.vue";
-import TitleBar from "./UIDetails/titleBar.vue";
-import Messages from "./UIDetails/messages";
+// mock messages
+import Messages from "./mockMessages";
+
+// child components
+import MessageVisualizer from "./messageVisualizer/index.vue";
+import InputVisualizer from "@/components/inputVisualizer/index.vue";
+
+// event emitter
import { Mitt } from "@/types/mitt/index";
+
import { defineComponent, ref, inject } from "vue";
export default defineComponent({
- name: "UI",
- components: { MessageRenderer, InputItem },
+ name: "Messenger",
+ components: { MessageVisualizer, InputVisualizer },
setup() {
const messages = Messages;
const count = ref(0);
}
window.ipcRenderer.on("render-message", (event, payload) => {
- console.log(payload.message)
+ console.log(payload.message);
emitter.emit("renderMessage", payload.message);
- })
+ });
return {
emittMessage,
import { computed, onMounted, ref } from "vue";
-export default function useMessageItemComp(m: string, id: string) {
+
+/**
+ * single message svg item computed properties
+ */
+export default function useComputedBubble(m: string, id: string) {
const x = ref(0)
const paddingLeft = 30;
const winW = 350;
}
const startingOffset = computed(() => {
- const messageRenderer = document.getElementById('messageRenderer');
- if (messageRenderer) {
- const rect = messageRenderer.getBoundingClientRect();
+ const parentView = document.getElementById('messageVisualizer');
+ if (parentView) {
+ const rect = parentView.getBoundingClientRect();
let Y = 600;
if (rect.height > 500) {
Y = rect.height + messageMarginTop;
});
onMounted(async () => {
- // to calculate X offset:
- // First: set message width ref
await setMessageWidthRef();
- // Then: calculate message X offset
calculateXoffset();
})
import { ref, onMounted } from "vue";
import { Ref } from "@/types/vueRef/index";
-export default function useMessageItemAnims({
+
+/**
+ * handles visualization of single message svg items
+ */
+export default function useBubbleDrawer({
mr,
tr,
}: {
</template>
<script lang="ts">
+import useComputedBubble from "./bubbleDetails/computed";
+import useBubbleDrawer from "./bubbleDetails/drawer";
+
+// text wrap composition
+import useTextWrap from "@/composables/textWrap";
+
import { defineComponent, onMounted, ref, reactive } from "vue";
-import useMessageItemComp from "@/core/messenger/message/messageDetails/useComputed";
-import useMessageItemAnims from "@/core/messenger/message/messageDetails/useAnimations";
-import useTextWrap from "@/utils/textWrap/useTextWrap";
export default defineComponent({
name: "MessageItem",
props: {
const messageRect = ref(`message${msgID.value}`);
const messageText = ref(`text${msgID.value}`);
- const { textFill, bubbleFill, startingOffset } = useMessageItemComp(
+ const { textFill, bubbleFill, startingOffset } = useComputedBubble(
props.item.modifier,
props.item.id
);
const { textWrap } = useTextWrap();
- const { drawMessage, signatureTranslate } = useMessageItemAnims({
+ const { drawMessage, signatureTranslate } = useBubbleDrawer({
mr: messageRect,
tr: messageText,
});
xmlns:xlink="http://www.w3.org/1999/xlink"
:viewBox="scrollView"
>
- <g id="messageRenderer" transform="translate(0, 5)">
+ <g id="messageVisualizer" transform="translate(0, 5)">
<g
v-for="(item, index) in renderArr"
:key="index"
</template>
<script lang="ts">
-import useMessageView from "@/core/messenger/messageView/useMessageView";
-import useScrollView from "@/core/UI/scrollView/useScrollView";
-import { defineComponent, reactive, inject } from "vue";
-import MessageItem from "./messageItem.vue";
+import useMessageVisualizer from "./viewDetails/visualizer";
+
+import useScrollView from "@/composables/scrollView";
+
+import MessageItem from "../messageBubble/index.vue";
+
import { Message } from "@/types/message/index";
+
import { Mitt } from "@/types/mitt/index";
+import { defineComponent, reactive, inject } from "vue";
export default defineComponent({
- name: "MessageRenderer",
+ name: "MessageVisualizer",
components: { MessageItem },
setup() {
const renderArr: Message[] = reactive([]);
});
}
- const { beforeEnter, enter, afterEnter } = useMessageView();
+ const { beforeEnter, enter, afterEnter } = useMessageVisualizer();
const { scrollView } = useScrollView({
- targetId: "messageRenderer",
+ targetId: "messageVisualizer",
});
return {
-import useMessageViewAnims from "./viewDetails/useAnimations";
-import useOffsetCalculator from './viewDetails/useOffsetCalculator';
import { VueTransitionCallback } from '@/types/vue3/vueTransitionCallback/index';
-
+import useMessageViewAnims from "./anime";
+import useOffsetCalculator from './offsetCalculator';
/**
- * messageView component logic
- * handles visualization of exisiting user messages
- * as well as incoming messages
+ * handles visualization of view messages through
+ * vue transition callbacks and anime-js animations
*/
-export default function useMessageView(): {
+export default function useMessageVisualizer(): {
beforeEnter: VueTransitionCallback;
enter: VueTransitionCallback;
afterEnter: VueTransitionCallback;
let shift = false;
let shiftOffset = 0;
-
// animates message item to 'enter' renderView
- const animateMessage = (el: SVGGElement) => {
+ const animateMessage = (el: SVGGElement): void => {
const transform = {
initialTranslate: `translate(${messageOffset.x} ${messageOffset.y})`,
shift ? shiftAnimate(el, transform, shiftOffset, messageList) : stackAnimate(el, transform);
}
- // callback called before message enter
const beforeEnter: VueTransitionCallback = () => {
const queryResult = document.querySelectorAll(".messageList");
if (queryResult.length) messageList = queryResult;
}
- // callback called on message enter
const enter: VueTransitionCallback = (el) => {
// query DOM for latest message as soon as it enters
if (el) {
}
}
- // callback called after message enter
const afterEnter: VueTransitionCallback = (el) => {
// get message positioning & shift state before animating
if (message) {
time: ""
},
];
-export default Messages;
\ No newline at end of file
+export default Messages;
<script>
import { defineComponent, onMounted, ref, inject, watch } from "vue";
export default defineComponent({
- name: "AI",
+ name: "voiceBackground",
setup() {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const analyzer = audioCtx.createAnalyser();
--- /dev/null
+export default async function useMediaStream() {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ // var audioTrack = stream.getAudioTracks()[0];
+ // console.log(audioTrack.getSettings())
+ return stream;
+}
\ No newline at end of file
-import { ref, computed, onMounted } from "vue";
+import { ref, computed, onMounted, onUnmounted } from "vue";
+
+/**
+ * handles visualization of view messages through
+ * vue transition callbacks and anime-js animations
+ */
export default function useScrollView({ targetId }: { targetId: string }) {
+
const scroll = ref(0);
let scrollTarget: HTMLElement | SVGElement | null;
const targetHeight = ref(0);
}
}
- const handleScrollEvent = (verticalScroll: number) => {
+ const updateView = (verticalScroll: number) => {
if (scrollTarget) {
const heightResult = getHeight();
if (heightResult) {
}
}
+ const scrollUpdate = (e: any): void => {
+ const verticalScroll = e.deltaY * 0.25;
+ updateView(verticalScroll);
+ }
+
onMounted(() => {
scrollTarget = document.getElementById(targetId);
if (scrollTarget) {
- window.addEventListener("wheel", (e) => {
- const verticalScroll = e.deltaY * 0.25;
- handleScrollEvent(verticalScroll);
- })
+ window.addEventListener("wheel", scrollUpdate);
}
});
+ onUnmounted(() => {
+ window.removeEventListener("wheel", scrollUpdate);
+ })
+
return {
scrollView
}
+++ /dev/null
-import AnimeFunc from "@/types/animejs/index";
-import {inject} from "vue";
-import {Ref} from "@/types/vueRef/index";
-
-export default function useAudioAnimations() {
- // imports animejs safely
- let anime: AnimeFunc;
- const animeInject: AnimeFunc | undefined = inject("animejs");
- if (animeInject) anime = animeInject;
-
- function animateSend(el: Element, trigger: Ref<boolean>) {
- console.log(el);
- anime({
- targets: el,
- opacity: 0,
- translateY: -100,
- duration: 0,
- complete: function() {
- trigger.value = false;
- }
- })
- }
-
- return {
- animateSend
- }
-
-}
\ No newline at end of file
+++ /dev/null
-
-interface Window {
- AudioContext: any;
- webkitAudioContext: any;
-}
-
-interface Event {
- inputBuffer: AudioBuffer;
-}
-
-import { Ref } from '@/types/vueRef/index';
-import { watch } from 'vue';
-
-export default function useAudioRecorder(record: Ref<boolean>, stream: MediaStream) {
-
- let audioArray: Int16Array[] = [];
- const bufferLen = 4096;
- const numChannels = 1;
-
- //window.AudioContext = window.AudioContext;
- const audioContext = new AudioContext({ sampleRate: 16000 });
-
- const source = audioContext.createMediaStreamSource(stream);
- const context = source.context;
- const sampleRate = context.sampleRate;
-
- const node = context.createScriptProcessor.call(
- context,
- bufferLen,
- numChannels,
- numChannels
- );
-
- node.onaudioprocess = (e: Event) => {
- const inputBuffer = new Float32Array(bufferLen);
-
- e.inputBuffer.copyFromChannel(inputBuffer, 0);
- const int16 = new Int16Array(inputBuffer.buffer);
- audioArray.push(int16);
- };
-
- watch(record, (beginRecording) => {
- if (beginRecording) {
- node.connect(context.destination);
- } else {
- node.disconnect(context.destination);
-
- const message = {
- text: '',
- audio: {
- sampleRate: sampleRate,
- channelCount: numChannels,
- audio: audioArray
- }
- }
- window.postMessage({
- myTypeField: 'send-message',
- message: message
- }, '*')
-
- audioArray = [];
- }
- })
-
- source.connect(node);
-
-}
+++ /dev/null
-import { inject } from 'vue';
-export default function useStreamRecord(): {
- initRecorder: (stream: MediaStream) => MediaRecorder;
-} {
- let streamRecorder: MediaRecorder;
- const chunks: Blob[] = [];
- const emitter: any = inject("mitt");
- const options = {
- mimeType: "audio/webm; codecs=opus",
- }
-
- // console.log('ogg supported',MediaRecorder.isTypeSupported('audio/mpeg; '))
- const initRecorder = (stream: MediaStream) => {
-
- const audioTrack = stream.getAudioTracks()[0];
- const audioSettings = audioTrack.getSettings()
-
- streamRecorder = new MediaRecorder(stream, options);
- streamRecorder.ondataavailable = (e: any) => {
- if (e.data.size > 0) {
- chunks.push(e.data);
- } else {
- // ...
- }
- //chunks.push(e.data)
- }
- streamRecorder.onstop = async (e: any) => {
- const audioBlob = new Blob(chunks, {
- type: options.mimeType
- });
- const arrayBuffer = await audioBlob.arrayBuffer()
- console.log(arrayBuffer)
- const message = {
- text: '',
- audio: {
- sampleRate: audioSettings.sampleRate,
- sampleSize: audioSettings.sampleSize,
- channelCount: audioSettings.channelCount,
- mimeType: options.mimeType,
- arrayBuffer: arrayBuffer
- }
- }
- window.postMessage({
- myTypeField: 'send-message',
- message: message
- }, '*')
-
- }
- return streamRecorder
- }
-
- return {
- initRecorder,
- }
-}
+++ /dev/null
-import AnimeFunc from "@/types/animejs/index";
-import {inject} from "vue";
-import {Ref} from "@/types/vueRef/index";
-/*
- * Anime js animations used by input textController
-*/
-export default function useTextInputAnims() {
-
- // imports animejs safely
- let anime: AnimeFunc;
- const animeInject: AnimeFunc | undefined = inject("animejs");
- if (animeInject) anime = animeInject;
-
- const animateSend = (el: HTMLElement, elHeight: number, input: Ref<string>) => {
- anime({
- targets: el,
- translateY: -elHeight + 15,
- duration: 200,
- easing: "easeInQuad",
- complete: function() {
- anime({
- targets: el,
- translateY: 50,
- duration: 200,
- easing: "easeOutQuad",
- complete: function () {
- input.value = "";
- }
- })
- }
- })
- }
-
- const inputAppear = (el: HTMLElement) => {
- anime({
- targets: el,
- translateY: -10,
- duration: 0
- });
- }
-
- const verticalShiftInput = (el: HTMLElement, yTrans: number) => {
- anime({
- targets: el,
- translateY: yTrans,
- easing: "easeOutQuad",
- duration: 150,
- });
- }
-
- return {
- animateSend,
- verticalShiftInput,
- inputAppear
- }
-
-}
\ No newline at end of file
+++ /dev/null
-import { ref, computed, inject } from 'vue';
-import useTextInputAnims from "./textDetails/useAnimations";
-import { Ref } from "@/types/vueRef/index";
-/*
- * text rendering logic used by inputController
-*/
-export default function useTextRender(input: Ref<string>) {
- const inputHeight = ref(42);
- const inputWidth = ref(0);
-
- const emitter: any = inject("mitt");
- const { animateSend, verticalShiftInput, inputAppear } = useTextInputAnims();
-
- async function renderTextInput() {
- // first, get input dimensions
- const inputDivs = await document.getElementsByClassName("inputBubble");
- const height = inputDivs[0].getBoundingClientRect().height;
- inputWidth.value = inputDivs[0].getBoundingClientRect().width;
- // then, query for foreignObject div
- const foreignObjectDiv = document.getElementsByClassName(
- "inputContainer"
- );
- const foreignEl = foreignObjectDiv[0] as HTMLElement;
- if (height !== inputHeight.value) {
- inputHeight.value = height;
- }
- if (inputHeight.value === 0) {
- inputAppear(foreignEl)
- } else if (inputHeight.value > 36) {
- const yTrans = -inputHeight.value + 30;
- verticalShiftInput(foreignEl, yTrans);
- }
- }
-
- emitter.on("newSelfMessage", async (payload: any) => {
- const inputEl = document.getElementsByClassName("inputContainer");
- const foreignEl = inputEl[0] as HTMLElement;
- animateSend(foreignEl, inputHeight.value, input);
- });
-
- const textInputXoffset = computed(() => {
- return `calc(50% - ${inputWidth.value / 2})`;
- });
-
- return {
- textInputXoffset,
- renderTextInput
- }
-}
import { ref, watch, onUnmounted, onMounted } from "vue";
+
import useKeyDownHandler from "@/utils/keyDownHandler/useKeyDownHandler";
+
import useMediaStream from "@/utils/mediaStream/useMediaStream";
+
import useStreamRecord from "./audio/useStreamRecord";
+
import useStreamVisualizer from './audio/useStreamVisualizer';
+
import useTextRender from './text/useTextRender';
+
import useAudioAnimations from './audio/audioDetails/useAnimations';
+
import useAudioRecorder from './audio/useAudioRecorder';
/*
-* Input controller logic
+* Input visualizer logic
* Will visualize text input by default as user types or
* if user holds space bar down, input audio stream
* will be visualized & recorded
path: "/",
name: "Home",
component: HomeIndex
- }
+ },
];
const router = createRouter({
<template>
- <UI />
+ <Messenger />
</template>
<script lang="ts">
import { defineComponent } from "vue";
-import UI from "@/components/UI/index.vue";
+import Messenger from "@/components/messenger/index.vue";
export default defineComponent({
name: "HomeIndex",
- components: { UI },
+ components: { Messenger },
});
</script>
--- /dev/null
+const fs = require('fs');
+const portAudio = require('naudiodon');
+
+// Create an instance of AudioIO with outOptions (defaults are as below), which will return a WritableStream
+const ao = new portAudio.AudioIO({
+ outOptions: {
+ channelCount: 2,
+ sampleFormat: portAudio.SampleFormat16Bit,
+ sampleRate: 48000,
+ deviceId: -1, // Use -1 or omit the deviceId to select the default device
+ closeOnError: true // Close the stream if an audio error is detected, if set false then just log the error
+ }
+});
+
+// Create a stream to pipe into the AudioOutput
+// Note that this does not strip the WAV header so a click will be heard at the beginning
+const rs = fs.createReadStream('rawAudio.wav');
+
+// Start piping data and start streaming
+rs.pipe(ao);
+ao.start();
\ No newline at end of file