import * as tf from "@tensorflow/tfjs";
import {reactive} from "vue";
import {UpyunAccess} from '~/utils/upyun'
const NUM_INPUT_SAMPLES = 1024;
const MODEL_SAMPLE_RATE = 16000;
const PT_OFFSET = 25.58
const PT_SLOPE = 63.07
const CONF_THRESHOLD = 0.9;
const MODEL_URL = 'https://upyun.alchemy-studio.cn/test/model.json'
// const MODEL_URL = "model.json"

const store = reactive<{
    model?: tf.GraphModel,
    stream?: MediaStream,
    frequency?: number,
    pitch?: string,
    error?: any
}>({})


export default function usePitchDetect() {
    async function start() {
        try {
            if (!store.model) {
                let url = await UpyunAccess.getSign(MODEL_URL);
                const converter = async (path: string) => (await UpyunAccess.getSign("https://upyun.alchemy-studio.cn/test/" + path)) as string
                store.model = await tf.loadGraphModel(url as string, {
                    fromTFHub: false, weightUrlConverter: converter
                });
            }
            store.stream = await window.navigator.mediaDevices.getUserMedia({ audio: true, video: false });
            handleSuccess(store.stream)
        } catch(err) {
            handleError(err)
        }
    }

    function stop() {
        if (store.stream) {
            store.stream.getAudioTracks().forEach(track => track.stop());
            store.stream = undefined;
        }
    }

    function handleError(err: any) {
        store.error = err;
    }

    function getPitchHz(modelPitch: number) {
        const fmin = 10.0;
        const bins_per_octave = 12.0;
        const cqt_bin = modelPitch * PT_SLOPE + PT_OFFSET;
        return fmin * Math.pow(2.0, (1.0 * cqt_bin / bins_per_octave))
    }

    function getPitchName(hz: number) {
        const base = 27.5;
        const octave = Math.floor(Math.log(hz/base)/Math.log(2.));
        const step = 2**(1./12.);
        const index = Math.round(Math.log(hz / (base * 2**octave)) / Math.log(step));
        let name = '';
        switch(index) {
            case 0:
                name = 'a';
                break;
            case 1:
                name = 'a#';
                break;
            case 2:
                name = 'b';
                break;
            case 3:
                name = 'c';
                break;
            case 4:
                name = 'c#';
                break;
            case 5:
                name = 'd';
                break;
            case 6:
                name = 'd#';
                break;
            case 7:
                name = 'e';
                break;
            case 8:
                name = 'f';
                break;
            case 9:
                name = 'f#';
                break;
            case 10:
                name = 'g';
                break;
            case 11:
                name = 'g#';
                break;
            default:
                store.error = 'invalid name' + index;
        }
        return name + ((index < 3)?octave:octave+1);
    }

    function handleSuccess(stream: MediaStream) {
        let context = new AudioContext({
            latencyHint: "playback"
        });

        let source = context.createMediaStreamSource(stream);
        let processor = context.createScriptProcessor(
            NUM_INPUT_SAMPLES,
            /*num_inp_channels=*/ 1,
            /*num_out_channels=*/ 1);

        // Converts audio to mono.
        processor.channelInterpretation = 'speakers';
        processor.channelCount = 1

        // Runs processor on audio source.
        source.connect(processor);
        processor.connect(context.destination);
        processor.onaudioprocess = function(e) {
            const inputData = e.inputBuffer.getChannelData(0);
            console.log(inputData)
            debugger
            const input = tf.reshape(tf.tensor(inputData), [NUM_INPUT_SAMPLES])
            console.log(input)
            debugger
            let output = store.model?.execute({"input_audio_samples": input }) as tf.Tensor[];
            // console.log('output...', output)
            if (output) {
                const uncertainties = output[0].dataSync();
                console.log('uncertainties', uncertainties)
                const pitches = output[1].dataSync();
                for (let i = 0; i < pitches.length; ++i) {
                    let confidence = 1.0 - uncertainties[i];
                    if (confidence < CONF_THRESHOLD) {
                        continue;
                    }
                    let value = getPitchHz(pitches[i]);
                    store.error = "";
                    store.frequency = value;
                    store.pitch = getPitchName(value)
                }
            }

        }
    }

    return {store, start, stop}
}


